Get Child Device from Parent Device

I'm trying to get the child devices of another device given only the device object. It seems that "getChildDevices()" is not a method of the device object, but rather only the app object or the driver object.

I have setup my app to get the device I'm looking for this way:

        section("Settings"){
            input(name: "mondoDevice", type: "device.GraceDigitalMondoElite",title: "Mondo Elite Device",width:4)
}

The end user then selects the device from the list. This all works just fine, in that I can easily access the device with "settings.mondoDevice".

The problem is that this device, when installed, creates several child devices from inside the driver (not the app). I need to get those child devices, but I cannot because, as mentioned, getChildDevices() is not a method of the device object.

So, is there a way to get the driver object of a device (and thus use getChildDevices()), or some other way to get the child devices given a parent device object?

This is not a method you can use unless the devices in question are child devices of the parent driver or app code you're writing/using. For all other purposes, child devices are like regular devices: the user has to select them from a device input in your app in order to get a reference to them that you can use.

2 Likes

Thanks for the info. This seems like a very odd object model to me. The device has children in one context, but not in another. I must be missing something about how the system is setup that would make this make more sense. Even if the driver object was accessible from the device, it would make more sense to me.

This does however leave me with another problem. I can create a child device of the app, but I don't think I can create a child device, of the child device, of the app (a grandchild of the app). Not being able to do that seems a little messy to me.

It would seem that to keep the child devices of the main device, I need some kind of a pass through command that identifies the device targeted, as well as the payload. That is easy enough, even if it is a little awkward.

Could you make a custom function on the parent device driver, that when called, returns the child device objects back to the app? Seems like that should work. Basically just a wrapper for getChildDevices()

2 Likes

It would seem that custom conlmmands in drivers cannot return data to a caller when the caller is an app. It works within the driver, but not between the app and the driver. Further, you can't put objects into attributes, so there really is no way to mimic the getChildren() functionality.

I wonder if you saved the device Id or DNI of the child devices in the data on the parent. Could you retrieve that and then look up the child from the app using that? Seems like you should be able to just not sure how exactly.

I think you are right where I am. We should be able to do something here, but can't. It seems like a hole in the object model.

If these devices are children of the parent device, if I have the parent device, I should be able to get to the child, and the other way around. It seems to be tied up in a paradigm that attaches device definition to apps, which to me makes no sense. Apps interface with devices through drivers that are universally accessible. This is how every system I know of works, but this system seems to really to tie the device to the app. By tying devices to apps, the difference between an app and a driver gets confused a bit.

An update on this one. I was inspired by @tony.fleisher and his work on zwave tools (git hub link So, I set out to find a better solution. And...here it is:

The process flow works like this:

  • A device pre-exists that has child devices attached to it
  • In an app, select the parent device (note that HE doesn't allow you to navigate from parent device to child device in the object model)
  • The scheme below can be used to automatically find all the child devices of that selected parent device in the app, and thus have device objects for the parent, and the children without having the user select all the child devices.

First, by setting multiple:true and offerAll:true on the input, you get an option to toggle all on or off on a device list in an app.

            input(name: "DimmerDevices",
                  type:"capability.switchLevel",
                  title: "Display Dimmers<br><span style='font-style:italic;color:grey;font-size:.8em;'>If Unsure Which Belong to This Device, Select All</span>",
                  width:4,
                  multiple:true,
                  offerAll:true)

Note that by switching to type:"capability.*" you could find all the child devices and not just the ones with the specified capability.

And then, with some javascript (sorry...I'm really not a pro at that, so this is a little ugly), upon the page loading, I hide the input button and automate the clicking of the select all button.

            paragraph(title:"script","""
                        <span id='toggleBtn' onclick='toggleDisplay()' style='background-color:lightgrey; padding:5px 5px 5px 5px;'>Show Child Device List</span>
                        <script id="mainScript">
                            function toggleDisplay(){
                                var btn=\$('[data-elemname="DimmerDevices"').parent();
                                if(btn.css('display')==='none'){
                                    btn.css({display:'block'});
                                    \$('#toggleBtn').text("Hide Child Device List")
                                }else{
                                    btn.css({display:'none'});
                                    \$('#toggleBtn').text("Show Child Device List")
                                }
                            }

                            function dropList(){
//click the device selection button to drop down the list to select
                                \$('[data-elemname="DimmerDevices"').trigger('click');
//wait for .1s and then select the button to "select all"
                                setTimeout(function(){selectAll()},100);  //  This is really not clean, but I don't know a better way to do this.
                            }
                            function selectAll(){
                                \$('#DimmerDevices-checkbox-0').trigger('click');
                            }

                            \$(document).ready(loadFun);
                            function loadFun(){
                                var btn=\$('[data-elemname="DimmerDevices"').parent();
                                btn.css({display: "none"});
                                dropList();
                                };
                        </script>
                        """)

Where you see "DimmerDevices" in the jquery, that is the name of my input in the groovy app. I haven't thoroughly tested it, but I'd assume that it would track perfectly to just replace "DimmerDevices" with what ever you input element is.

Now, to be fair, there are some really odd things in here. First, there is some odd loading issue. If I initiate the click on the select all too fast after the click on the drop list button, it will click the button and check it, but not actually select everything. Further, I actually have to click on the button to drop the list, or I can't get the select all to work. Oddly, this sequence is still required if the whole series of HTML elements is display:none. I couldn't figure out what I was supposed to wait for to go from the drop click to the select click, and couldn't find a cleaner way to do this. So, waiting .1s between functions was fast enough and it worked. Also, I was expecting to have to click the "update" button, but magically, it takes care of itself. (Yes, I know, this doesn't sound robust, but maybe someone smarter can figure out the why's in here and make this idea really work.)

The last step was just some nice little groovy code to search the selected devices and compare their parent id to the id of the main device that you selected in another input element.

    def childDimmers=settings.DimmerDevices.findAll(){it.getParentDeviceId()==settings.mondoDevice.getIdAsLong()}

It all seems to be working great. I still consider this issue of not having references to the children in the parent device object to be a flaw in the object model, but I suspect the developers are looking at this as a security issue. They seem to want the user to explicitly select everything device the reference. I'd argue that by selecting the parent device, you are selecting the child devices and making that link in the object model is no riskier than burying child devices inside app code, like you can do now.

If anyone has improvements to this, I'd love to hear them.

not sure if i am missing something but take a look at lgk sendmail vv3 i have a main email driver and children that send the individial emails multiple for concurrency.. both are devices.. and i create and delete and enumerate devices in the code..

ie
]]

i guess the issue is that the device is not the instance but another device in a differnt driver..

if that is the case you can add a method in that device that you can call (i know you can from a rule, so at least you can write a rule to is triggered from the current device) to do any manipulation of child devices.

It is a little hard to see exactly what you are doing without all your code, but here is what I think the difference is.

In my case, the main device has child devices defined from inside the driver. Then I have an app where the user selects only the main device. You use getChildDevices(), but that only works in a driver where the child devices are defined in the driver, or in an app where the app itself has child devices. In my case, I have an app, that has the device object for the main device. The device object does not have the getChildDevice() method. Only the app object and the driver object do.

So, from inside the app, I can get the main device's device object, by user input, but that device object has no access to its driver object, so I can't get the child devices that way and the child devices are children of the driver, not the app, so app.getChildDevices() won't return the child devices.

Did I misunderstand your method? Do you ahve a way around this that I missed?

Yes any device has the getbchilddevices method but it only works for itself ie in its own code base. Then once you have the dni of its child you can get the device object. It just doesn't work when you try to call the function outside the parents code base. If you want access to what you csll the druver object (there really is no differentiation) from outside just put a method inside it you can call from outside.