We have moved at community.getvera.com

Author Topic: How do I write intercept code?  (Read 9281 times)

Offline martin12345

  • Sr. Member
  • ****
  • Posts: 321
  • Karma: +48/-4
How do I write intercept code?
« on: May 08, 2011, 06:36:59 pm »
Hi. I am trying to write some intercept code to send a udp message to my PC whenever a device switches on or off or dims. However, I just can't find any documentation or examples at all on writing an intercept, other than some notes that say it is possible.

Has anyone got an example of some intercept code that does anything at all that I can crib from?

Thanks

Martin

Offline guessed

  • Community Beta
  • Master Member
  • ******
  • Posts: 5301
  • Karma: +92/-22
  • Release compat is not a bolted-on afterthought
Re: How do I write intercept code?
« Reply #1 on: May 08, 2011, 08:20:29 pm »
If you want to do something when a [single] switch changes state, then that's just a Luup-based Scene.  In your case, you'd need to use the LuaSocket Library (bundled with Vera) to make the UDP callouts.

http://w3.impa.br/~diego/software/luasocket/udp.html

You're original request, about intercepts, is a little confusing so it's possible you're looking to do something a little bigger.

Offline martin12345

  • Sr. Member
  • ****
  • Posts: 321
  • Karma: +48/-4
Re: How do I write intercept code?
« Reply #2 on: May 09, 2011, 03:17:37 am »
Perhaps I should explain more about what I am trying to achieve. I currently have a Windows Media Center based interface for controlling multiple different types of home automation devices;

http://www.milliesoft.co.uk/index.php/mce-software-1228996485/powercontrollermce

It currently supports Bye Bye Standby and Z-Wave, with Media Center, Android, Web and Mobile Web front ends. I am in the process of adding MiOS support. At the moment I have it controlling devices fine via upnp, but you don't get any notifications of device changes. I need to show users when a device is turned on or off. For other protocols, the code broadcasts a UDP message to your LAN when it detects a Z-Wave or BBSB device change so that the client interfaces can update their displays, but with MiOS, I currently have no way other than polling to see if a device has changed.

What I want to have is a MiOS plugin which will send the same UDP broadcast message whenever any device state changes - whether it is changed from the device itself or from the web interface. The plugin shouldn't require users to configure it for each device, so it should (presumably) launch at startup, and for each device it should send the message whenever the device changes. I have read on the forums that the best way to do that is with an intercept, but I just can't find any documentation on doing that. The current documentation simply says "TBD: Add a function to do this**".

So, my problem isn't actually the UDP side of this. My problem is that I haven't got a clue how to attach an intercept to a device because there is no documentation or example on it. Perhaps the intercept is a red herring - perhaps there is a better way to send a message whenever any device state changes, but if there is, I'd really appreciate some advice on what it is, and a pointer to some documentation or examples.

Thanks

Martin

P.S. the barrier to entry for this stuff is an awful lot higher than perhaps you guys realize - a new language that I would imagine that most people are not familiar with, combined with a system architecture that isn't documented. I'm sure it is trivial once you know what you are doing, and working with it every day probably makes it seem really easy to you guys, but it is quite daunting for a new starter. A walkthrough with pictures for achieving some common tasks would go a long way to lowering that barrier.

Offline Ap15e

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 1998
  • Karma: +12/-0
Re: How do I write intercept code?
« Reply #3 on: May 09, 2011, 04:38:54 am »
AFAIK, you cannot 'watch' an UPnP/Z-Wave device via Luup. What Luup does support is 'watching' UPnP variables.

IIUC what you're going to accomplish:

  • Get a list of your devices via luup.devices,
  • map the device_type to the corresponding UPnP service(s)/UPnP variable(s),
  • luup.variable_watch all relevant UPnP variables.

LOUIS4Vera contains a mapping from MCV's device_type to the UPnP world.

Further reading:
http://wiki.micasaverde.com/index.php/Luup_Lua_extensions
http://code.mios.com/
« Last Edit: May 09, 2011, 04:43:42 am by Ap15e »

Offline martin12345

  • Sr. Member
  • ****
  • Posts: 321
  • Karma: +48/-4
Re: How do I write intercept code?
« Reply #4 on: May 09, 2011, 04:43:52 am »
Thanks. What little documentation there is for intercept implies that it isn't a "watch" as such, but a hook that always gets called when a device changes;

http://wiki.mios.com/index.php/Luup_Lua_extensions#function:_intercept

i.e. add an intercept and your code gets called as part of the cycle of setting the data for the device. That sounds like exactly what I need, but I can't find any examples or even function definition.

Martin

Offline guessed

  • Community Beta
  • Master Member
  • ******
  • Posts: 5301
  • Karma: +92/-22
  • Release compat is not a bolted-on afterthought
Re: How do I write intercept code?
« Reply #5 on: May 09, 2011, 11:43:52 am »
@martin12345,
That intercept is in the [Serial] io module, not what you want based upon your new description.

Look at the old Energy Monitoring Plugin on Code.mios.com.  Specifically look at the calls for luup.variable_watch.  You should be able to transform that code into something that would subscribe to more/different types of change events, as well as perform network-notifications when these events occur.

More commonly, MiOS Control Points use the Blocking user_data2 (etc) calls, or the ones described in the Wiki under "UI Simple" like the more compact lu_sdata.  This is what the SQRemote/iVera/HomeBuddy folks do to integrate at their end, and MiOS is optimized to do this as compared to writing a Plugin that Hooks every UPnP Variable per the above - esp on larger systems.



... and yes, MCV's doco is really bad.  It's been mentioned to them a number of times that this is one of the bigger impediments to both getting started, as well as doing advanced things.  Most folks here "tweak" it occasionally, to make bits of it better, but it would be stronger if they re-worked it.

Offline Ap15e

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 1998
  • Karma: +12/-0
Re: How do I write intercept code?
« Reply #6 on: May 10, 2011, 03:41:51 am »
Quote
More commonly, MiOS Control Points use the Blocking user_data2 (etc) calls, or the ones described in the Wiki under "UI Simple" like the more compact lu_sdata.

... and if user_data2 or lu_sdata is too 'fat' or too complex to parse for your use case, you could even create a custom handler : http://wiki.micasaverde.com/index.php/Luup_Lua_extensions#function:_register_handler

Offline martin12345

  • Sr. Member
  • ****
  • Posts: 321
  • Karma: +48/-4
Re: How do I write intercept code?
« Reply #7 on: May 10, 2011, 04:27:30 am »
Thanks for the suggestions. I'll try them out and let you know how it goes.

Martin

Offline martin12345

  • Sr. Member
  • ****
  • Posts: 321
  • Karma: +48/-4
Re: How do I write intercept code?
« Reply #8 on: May 16, 2011, 03:52:06 am »
Hi. I created code to watch the variables, and it appears to work (quite) well, but there are a few things I don't understand that I would appreciate some help with please. First of all, here is my code;

Code: [Select]
do

local socket = require "socket"
local udp = socket.try(socket.udp())
broadcast_ip = '255.255.255.255'
port = 53007


local sp_serv = 'urn:upnp-org:serviceId:SwitchPower1'
local dim_serv= 'urn:upnp-org:serviceId:Dimming1'


function w_switch (lul_device, lul_service, lul_variable, lul_value_old, lul_value_new)
luup.log (("Power Controller w_switch(%s/%s/%s/%s/%s)"):format (tostring(lul_device), lul_service, lul_variable, tostring(lul_value_old), tostring(lul_value_new)))
socket.try(udp:sendto("PowerController:MiOSStateChange", broadcast_ip, port))
end


function start_monit ()
luup.log ('Initalizing PowerController Monitor')

assert(udp:setoption("broadcast", true))

local watchedDevices = {}
for k,v in pairs (luup.devices) do
if luup.device_supports_service (sp_serv, k) then
luup.log (string.format('PowerController monitoring switch #%s:%s', k, v.description))
luup.variable_watch ('w_switch', sp_serv, 'Status', k)
end
if luup.device_supports_service (dim_serv, k) then
luup.log (string.format('PowerController monitoring dimmer #%s:%s', k, v.description))
luup.variable_watch ('w_switch', sp_serv, 'LoadLevelStatus', k)
end
end
luup.log ('PowerController Monitor initialized')
end

start_monit()
end

My issues are;
1) I save this in a file called L_PowerController.lua, then I go to Toolbox-> MiOS developers -> Luup files, and I upload the file. It displays in the list as L_PowerController, not L_PowerController.lua like the other similar files. When I look in the /etc/cmh-ludl directory, I see it is the only file without a .lzo prefix. And it doesn't do anything at all. However, if instead I paste the code above in to Toolbox-> MiOS developers -> Edit Startup Lua, it all works fine. That doesn't seem right to me though. I got the impression that I should be uploading the file, not editing the startup code - which is not a very end user friendly thing to do, especially when it comes to patching. So what am I missing?

2) I don't get any messages in the log file from my code. I know it is working because it UDP messages are sent, but the log doesn't have any of my messages. Why not?

Thanks

Martin

Offline Ap15e

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 1998
  • Karma: +12/-0
Re: How do I write intercept code?
« Reply #9 on: May 16, 2011, 06:04:04 am »
I'd recommend converting your code to a Luup plugin. You could use my WAI plugin as an example.

For instant logging, turn on 'Verbose logging' ('Adavanced' -> 'Logs').

You might find http://forum.micasaverde.com/index.php?topic=5600.0 interesting.

Offline guessed

  • Community Beta
  • Master Member
  • ******
  • Posts: 5301
  • Karma: +92/-22
  • Release compat is not a bolted-on afterthought
Re: How do I write intercept code?
« Reply #10 on: May 16, 2011, 11:35:40 pm »
@Ap15e is correct about making it a plugin. 

The *.lua files uploaded don't just get run, something has to "load" (require) them in order for them to get loaded into memory, and any do blocks to execute.

Also, don't worry about the .lua extension disappearing in the UI.  All the MiOS files get compressed, but the .lua files are effectively libraries, and don't get compressed (presumably so that the standard Lua Lib loader works on them without mods).

The UI bit (shell script) that renders the stuff in the /etc/cmd-ludl directory attempts to strip out the .lzo extensions, so show them to users as the users uploaded them.... only it doesn't deal with the .lua files correctly in this trimming process.


Offline martin12345

  • Sr. Member
  • ****
  • Posts: 321
  • Karma: +48/-4
Re: How do I write intercept code?
« Reply #11 on: May 17, 2011, 05:59:28 pm »
Thanks for the code to copy Ap15e, and thanks for the explanation of how it works guessed. Here is my finished code in case anyone wants to copy it in future. I will be publishing it, along with the updated version of PowerControllerMCE, on my website shortly for anyone who wants to control their devices from Windows Media Center, or from the cool web interface.

Martin


Device definition:


Code: [Select]
<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
  <specVersion>
    <major>1</major>
    <minor>0</minor>
  </specVersion>
  <device>
    <deviceType>urn:schemas-milliesoft-com:device:PowerControllerNotifier</deviceType>
    <staticJson>D_PowerController.json</staticJson>
    <friendlyName>PowerController Notifier</friendlyName>
    <manufacturer>MillieSoft</manufacturer>
    <manufacturerURL>http://www.milliesoft.co.uk</manufacturerURL>
    <modelDescription>Notifies PowerControllerMCE of a power state change</modelDescription>
    <modelName>PowerController Notifier</modelName>
    <modelNumber>1.0</modelNumber>
    <UDN>uuid:upnp-milliesoft-1_0-0000000000001</UDN>
    <serviceList>
      <service>
        <serviceType>urn:milliesoft-com:service:PCA:1</serviceType>
        <serviceId>urn:upnp-milliesoft-com:serviceId:PCA1</serviceId>
        <SCPDURL>S_PowerController.xml</SCPDURL>
      </service>
    </serviceList>
    <eventList>
    </eventList>
    <handleChildren>0</handleChildren>
    <implementationList>
      <implementationFile>I_PowerController.xml</implementationFile>
    </implementationList>
  </device>
</root>

Service definition

Code: [Select]
<?xml version="1.0"?>
<scpd xmlns="urn:schemas-upnp-org:service-1-0">
<specVersion>
  <major>1</major>
  <minor>0</minor>
</specVersion>
<serviceStateTable>

  <stateVariable>
    <name>PowerControllerAddress</name>
    <sendEventsAttribute>no</sendEventsAttribute>
    <dataType>string</dataType>
    <defaultValue></defaultValue>
    <shortCode>powerControllerAddress</shortCode>
  </stateVariable>

</serviceStateTable>
</scpd>

Device json file

Code: [Select]
{
    "flashicon": "icons\/plugins.swf",
    "imgIconBody": "",
    "imgIconDimmable": "",
    "imgIconTurnable": "",
    "imgIconMin": "",
    "imgIconMax": "",
    "halloIconsDir": "pics\/hallo",
    "DisplayStatus": {
    },
    "doc_url": {
        "doc_language": 1,
        "doc_manual": 1,
        "doc_version": 1,
        "doc_platform": 0,
        "doc_page": "devices"
    },


    "Tabs": [
        {
            "Label": {
                "lang_tag": "tabname_control",
                "text": "PowerController Notifier"
            },
            "Position": "0",
            "TabType": "flash",
            "ControlGroup": [
                {
                    "id": "1",
                    "type": "info"
                }
            ],
            "Control": [
             {
                "ControlType": "label",
                "Label": {
                    "lang_tag": "lblMsg",
                    "text": "Server:"
                },
                "Display": {
                    "Top": 10,
                    "Left": 20,
                    "Width": 200,
                    "Height": 20
                }
              },
              {
    "ControlGroup":"1",
      "ControlPair":"1",
      "ControlHeader":"1",
                "ControlType": "variable",
                "Display": {
                    "Service": "urn:upnp-milliesoft-com:serviceId:PCA1",
                    "Variable": "PowerControllerAddress",
                    "Top": 40,
                    "Left": 20,
                    "Width": 500,
                    "Height": 20
                }
              }
          ]
         }
    ],
    "DeviceType": "urn:schemas-milliesoft-com:device:PowerController"
}

Implementation file

Code: [Select]
<?xml version="1.0"?>
<implementation>
 
  <functions>
 
  local socket = require "socket"
  local udp = socket.try(socket.udp())
  broadcast_ip = '255.255.255.255'
  port = 53007
 
 
  local sp_serv = 'urn:upnp-org:serviceId:SwitchPower1'
  local dim_serv= 'urn:upnp-org:serviceId:Dimming1'
 
 
  function w_switch (lul_device, lul_service, lul_variable, lul_value_old, lul_value_new)
          -- a change is detected, so send a UDP message for the PowerController server to pick up
  luup.log (("Power Controller w_switch(%s/%s/%s/%s/%s)"):format (tostring(lul_device), lul_service, lul_variable, tostring(lul_value_old), tostring(lul_value_new)))
  socket.try(udp:sendto("PowerController:MiOSStateChange", broadcast_ip, port))
  end
 
  function getAddress()
 
  -- try to discover the PowerController server by broadcasting a request for it to respond to
 
    socket.try(udp:sendto("PowerController:FindServer", broadcast_ip, port))
local message = socket.try(udp:receive())

-- the first response will be our outgoing message
if message=='PowerController:FindServer' then
  luup.log ('PowerController Address Response 1')
  -- wait for the second message
  message = socket.try(udp:receive())
end


  luup.log ('PowerController Address = ' .. message)
 
  local addressStr='Not Found'
 
  -- if the response is not empty, build an HTML fragment to link to the server
 
  if message~='' then
    addressStr = '&lt;a href=\"http://'..message..':53107/\" target=\"_blank\">http://'..message..':53107&lt;/a>'
  end
return addressStr
 
 
  end
 
 
  function start_powerControllerMonitor (PCA_DEVICE)
  luup.log ('Initalizing PowerController Monitor')
 
  assert(udp:setoption("broadcast", true))
  udp:settimeout (10)                  -- wait 10 seconds

 
socket.try (udp:setsockname ('*', port))

-- the call to UDP can fail with an error, so wrap it in an pcall

local success,addressStr=pcall(getAddress)

-- set the address in the service variable if the PowerController server is found

if success then
      luup.variable_set( 'urn:upnp-milliesoft-com:serviceId:PCA1', 'PowerControllerAddress', addressStr , PCA_DEVICE )
  else
      luup.variable_set( 'urn:upnp-milliesoft-com:serviceId:PCA1', 'PowerControllerAddress', 'Server Not Found' , PCA_DEVICE )
 
  end

-- loop over all devices to find switches and dimmers to monitor

  local watchedDevices = {}
  for k,v in pairs (luup.devices) do
  if luup.device_supports_service (sp_serv, k) then
  luup.log (string.format('PowerController monitoring switch #%s:%s', k, v.description))
  luup.variable_watch ('w_switch', sp_serv, 'Status', k)
  end
  if luup.device_supports_service (dim_serv, k) then
  luup.log (string.format('PowerController monitoring dimmer #%s:%s', k, v.description))
  luup.variable_watch ('w_switch', sp_serv, 'LoadLevelStatus', k)
  end
  end
  luup.log ('PowerController Monitor initialized')
  end
     
  </functions>

  <incoming>
    <lua>
   
    </lua>
  </incoming>

  <startup>start_powerControllerMonitor</startup>
</implementation>