Dynamic Capabilities, Commands, and Attributes for Drivers

I know this HAS been brought up in the past but I saw most of those threads were old, got sidetracked into particular use cases, or focused on libraries (which are now possible). So I wanted to specifically cover something that has been a thorn in my side for a long time:

  • Capabilities
  • Commands
  • Attributes

These are set by the driver code and cannot be altered without resaving the code. It would be a useful feature if there was SOME method for altering these without changing the code itself. Two quick examples based on my UnifiNetworkChild-Plug and UnifiNetworkChild-UP6 drivers:

        capability "Actuator"
        capability "Outlet"
        capability "Refresh"
        
        // Commands
        command "StartLocateDevice"
        command "StopLocateDevice"
        command "PowerCycleOutlet"
        command "ToggleOutlet"

AND:

        capability "Actuator"
        capability "Refresh"
        
        // Commands
        command "StartLocateDevice"
        command "StopLocateDevice"
        command "LEDOn"
        command "LEDOff"
        command "OutletOn", [
            [ name: "Outlet", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6 ], description: "Enter Outlet # (ex: 0, 1, 2... 6)" ]
        ] // Power on a specific outlet
        command "OutletOff", [
            [ name: "Outlet", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6 ], description: "Enter Outlet # (ex: 0, 1, 2... 6)" ]
        ] // Power off a specific outlet
        command "ToggleOutlet", [
            [ name: "Outlet", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6 ], description: "Enter Outlet # (ex: 0, 1, 2... 6)" ]
        ] // Toggle a specific outlet
        command "PowerCycleOutlet", [
            [ name: "Outlet", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6 ], description: "Enter Outlet # (ex: 0, 1, 2... 6)" ]
]

These are a single-outlet plug and a 6 outlet (with a set of USB ports counting as # 7). As you can see from this section, they have fairly similar code. But not the same, so I need to maintain both. If I could dynamically alter the capabilities, commands, and attributes I could have a single child driver to support both. An example would be:

capability "Actuator"
capability "Refresh"
if( Type == "Plug" || Type == null ){
  capability "Outlet"
}
// Commands
command "StartLocateDevice"
command "StopLocateDevice"
if( Type == "UP6" ){
  command "LEDOn"
  command "LEDOff"
  command "OutletOn", [
    [ name: "Outlet", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6 ], description: "Enter Outlet # (ex: 0, 1, 2... 6)" ]
  ] // Power on a specific outlet
  command "OutletOff", [
    [ name: "Outlet", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6 ], description: "Enter Outlet # (ex: 0, 1, 2... 6)" ]
  ] // Power off a specific outlet
  command "ToggleOutlet", [
    [ name: "Outlet", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6 ], description: "Enter Outlet # (ex: 0, 1, 2... 6)" ]
  ] // Toggle a specific outlet
  command "PowerCycleOutlet", [
    [ name: "Outlet", type: "INTEGER", constraints: [ 1, 2, 3, 4, 5, 6 ], description: "Enter Outlet # (ex: 0, 1, 2... 6)" ]
  ]
} else {
  command "PowerCycleOutlet"
  command "ToggleOutlet"
}

While it does not make the specific driver code any simpler it would make the child(ren) much simpler for me to maintain but more importantly for my users. They only need to have the parent and a single child driver. In the case of my Unifi Network drivers I am now up to 23 child drivers (one of which is a "generic" one to catch all the cases I do not have a specific one made for). Many of these are SIMILAR (like above) but have varying numbers of ports, whether those ports have PoE control or not, etc... Many features that make it unreasonable to have a single driver (as they work presently) to control, they would expose way too many attributes and commands would be "broken".

What I am proposing is a method (or methods) to dynamically "refresh" or set the driver, even in only VERY limited circumstances it would still be useful.

Proposed solutions:

  1. When addDevice and optional data map could be sent that could include variables (like the Type mentioned above). Something like that Type is already used in my code for Preferences (to "dynamically" alter what Preferences are shown), so I know it does not need to be a state variable.
addChildDevice( "UnifiNetworkChild", DNI, [ name: "${ DNI }" ], [ Type: "Plug" ] )
addChildDevice( "UnifiNetworkChild", DNI, [ name: "${ DNI }" ], [ Type: "UP6" ] )
  1. As an alternative to a data map, a series of optional variables (1 or more) could be added to the end and those could be used by the "installed" code. So it would now be something like:
def installed( string Type = null ){
  1. The code could be VERY dynamic and allow for checking State Variables. Using the example if from the beginning:
if( state.Type == "Plug" || state.Type == null ){

If there are concerns about how often such a thing could be set... In my case there are two spots that would solve all of my specific issues, at device install and when users Save Preferences (updated). Some of my drivers would be best at just install but a couple of the weather ones would be better after the user selects something in Preferences.

This would still keep a child driver handy but it would make it so a developer would really only need 1 child driver. Technically they could get by with just 1 driver entirely (making children using the core driver and dynamically limiting it) but that would probably be more of a mess code-wise (all the extra data checking and initial processing). Hubitat COULD nip that in the bud by not preventing a device from calling addDevice with it's own driver... (not sure if that is blocked now, I have never tried it).

There are probably more than the 3 proposed methods I gave... but those were quick examples that would solve this problem I am having.

Other developers have worked around this (from the ease of use for the end user) with Apps or HPM (in a sense) but the core problem of still needing a pile of separate child drivers exists.

3 Likes

Download the Hubitat app