Plugins with child devices: a few questions.
-
Not sure why, but after many years, I'm still unsure how plugins with children are meant to be coded, although I've got away with doing a few with no problems!
I could try out a few test cases but messing around with luup.chdev.append() is tricky. If the appended info changes, a luup.engine restart occurs. Any stuff ups and this can go into a continuous loop. Plus I don't want random new devices scattered all over my installation.
So imagine a plugin that controls say ten binary lights, ten dimmers and ten blinds/shutters:
- the plugin should have in the device file D_xyx.xml the tag:
<handleChildren>1</handleChildren>
OK it makes sense for the parent to have the routines to control the child devices.
Question: when wouldn't the plugin parent not have the code to control its children? ie
<handleChildren>0</handleChildren>
Seems redundant or I have misunderstood something?
- The service files for both dimmers and blinds have the function:
<action> <name>SetLoadLevelTarget</name> <argumentList> <argument> <name>newLoadlevelTarget</name> <direction>in</direction> <relatedStateVariable>LoadLevelTarget</relatedStateVariable> </argument> </argumentList> </action>
And to call the action in each case, we have say 'dimmingLevel' vs 'blindPosition':
luup.call_action('urn:upnp-org:serviceId:Dimming1', "SetLoadLevelTarget", {newLoadlevelTarget = dimmingLevel}, deviceId) luup.call_action('urn:upnp-org:serviceId:Dimming1', "SetLoadLevelTarget", {newLoadlevelTarget = blindPosition}, deviceId)
Question: how do you direct the same calls to different functions in the plugin - one to dim the light and the other to postion the blind? It would seem that you would have to look at the target child device and see what type it is, then act accordingly? An example would be good!
- In this plugin, devices can come and go - not often - but still needs to be attended to in the code. So the luup.chdev.append() call is executed at every start up. Each child has a descriptive name. When the child is first created this name can be set up by the software to say "Light 1" to "light 10". The user can then subsequently change this descriptive name.
Question: How do I know that this descriptive name has been changed by the user and needs to be preserved and how do I check what this descriptive name is ready for when the luup.chdev.append() call is executed? As the function needs this descriptive name.
And I wonder how openluup (without looking at the code) emulates the C blob:
local childDevices = luup.chdev.start(THIS_LUL_DEVICE)
-
Not sure why, but after many years, I'm still unsure how plugins with children are meant to be coded, although I've got away with doing a few with no problems!
I could try out a few test cases but messing around with luup.chdev.append() is tricky. If the appended info changes, a luup.engine restart occurs. Any stuff ups and this can go into a continuous loop. Plus I don't want random new devices scattered all over my installation.
So imagine a plugin that controls say ten binary lights, ten dimmers and ten blinds/shutters:
- the plugin should have in the device file D_xyx.xml the tag:
<handleChildren>1</handleChildren>
OK it makes sense for the parent to have the routines to control the child devices.
Question: when wouldn't the plugin parent not have the code to control its children? ie
<handleChildren>0</handleChildren>
Seems redundant or I have misunderstood something?
- The service files for both dimmers and blinds have the function:
<action> <name>SetLoadLevelTarget</name> <argumentList> <argument> <name>newLoadlevelTarget</name> <direction>in</direction> <relatedStateVariable>LoadLevelTarget</relatedStateVariable> </argument> </argumentList> </action>
And to call the action in each case, we have say 'dimmingLevel' vs 'blindPosition':
luup.call_action('urn:upnp-org:serviceId:Dimming1', "SetLoadLevelTarget", {newLoadlevelTarget = dimmingLevel}, deviceId) luup.call_action('urn:upnp-org:serviceId:Dimming1', "SetLoadLevelTarget", {newLoadlevelTarget = blindPosition}, deviceId)
Question: how do you direct the same calls to different functions in the plugin - one to dim the light and the other to postion the blind? It would seem that you would have to look at the target child device and see what type it is, then act accordingly? An example would be good!
- In this plugin, devices can come and go - not often - but still needs to be attended to in the code. So the luup.chdev.append() call is executed at every start up. Each child has a descriptive name. When the child is first created this name can be set up by the software to say "Light 1" to "light 10". The user can then subsequently change this descriptive name.
Question: How do I know that this descriptive name has been changed by the user and needs to be preserved and how do I check what this descriptive name is ready for when the luup.chdev.append() call is executed? As the function needs this descriptive name.
And I wonder how openluup (without looking at the code) emulates the C blob:
local childDevices = luup.chdev.start(THIS_LUL_DEVICE)
@a-lurker said in Plugins with child devices: a few questions.:
I could try out a few test cases but messing around with luup.chdev.append() is tricky. If the appended info changes, a luup.engine restart occurs. Any stuff ups and this can go into a continuous loop.
I usually code in a way to stop the plugin. Reactor, for example, can be stopped by creating a file called Z_Reactor.zzz in
/etc/cmh-ludl
that just makes the plugin startup exit (return false) without doing anything. So just in case things get wild, all I have to do is SSH in and create that file, and I've got control of the system back.Aside from this, you need to keep track of your children and be able to identify them and their purpose, so you don't needlessly create new ones. When using append on existing devices, always pass in the current values from the luup.devices entry: device type, name, id, device file, implementation file. You won't have any problems. The state variable initialization field doesn't matter.
I try to only use the chdev loop when I know things are changing. So I tend to pre-scan the devices I "discover" against what I already have created, and if I make my own determination that the lists are different or devices have changed, then I run the loop to handle the changes. This is just an extra layer of protection. And I usually include a way (like a state variable flag) to force that loop to run, even if I can't see any changes (helps debugging and in field if there's a bug in your own change detection).
Question: when wouldn't the plugin parent not have the code to control its children?
All of my plugins use
handleChildren
= 1, so I have no idea what it implies otherwise, and I've never bothered to think about it. You question is/was my question. Not sure what the alternative really implies.@a-lurker said in Plugins with child devices: a few questions.:
Question: how do you direct the same calls to different functions in the plugin - one to dim the light and the other to postion the blind?
You don't. You grab the device number you're given, which will be the device number of the child that was targeted, and determine what to do based on what you know about that device. You can use its device type, etc. to make that determination.
@a-lurker said in Plugins with child devices: a few questions.:
Question: How do I know that this descriptive name has been changed by the user and needs to be preserved and how do I check what this descriptive name is ready for when the luup.chdev.append() call is executed? As the function needs this descriptive name.
The device name for Luup is stored on the
name
ordescription
attribute (depending on where you are looking at it -- it'sluup.devices[devnum].description
but you change it withluup.attr_set("name", "newname", devnum)
and get it withluup.attr_get("name", devnum)
). So you can see its current value and compare it to a prior value that you've stored somewhere before yourself, maybe in a state variable, if you need to detect changes. Since the Luup storage of the name is in an attribute and not a state variable, you can'tluup.variable_watch()
it (only works for state variables, not device attributes), so you need to plan accordingly -- just detect at reload/restart of plugin, or poll every minute if you're not into the whole brevity thing, etc.A lot of the problems I first had learning to use luup.chdev were due to this disconnect between the keys in
luup.devices[devnum]
vs the attribute names used elsewhere. Make sure you know you are using the right name. Lots of debug. If you don't have a debug logger that can log the contents of an object/table in human-readable form, feel free to grab the code fordump()
,L()
andD()
from pretty much any of my plugins.And I wonder how openluup (without looking at the code) emulates the C blob
luup.chdev
works the same on openLuup as it does on Vera Luup, with the added advantage that openLuup restarts faster so it's less painful when changes happen. The blob is proprietary data on both platforms, so just carry it as required and don't think about what's in it or ways to use it otherwise. You don't need to know. -
What he said … ^^^
In Vera, it’s (I think) possible for child devices to have their own implementation files, but I don’t know of any which actually use this, and it’s probably somewhat buggy in implementation. The handleChildren flag is essential to ensure that any actions (luup or HTTP) targeted to a child device are passed up the chain to the (grand)parent, otherwise you’ll get the error that the action is not implemented by the child.