Alexa Echo Skill -- doesn't seem to provide response to Alexa when controlling child devices

I implemented a custom driver for switches and exposed them to Alexa using the Alexa Echo Skill. Everything worked fine. I did not have to enable the "Respond Immediately without waiting for device" in the Amazon Echo Skill app.

I re-implemented the driver to use a parent/child architecture. With this implementation, I have to enable the "Respond immediately..." , else the Echo doesn't speak the normal "OK".

The switches turn on/off correctly, but after several seconds, the Echo says something like "I don't know what went wrong".

So, there must be some sort of reply needed from my driver or the Amazon Skill immediately after receiving the message from the echo. But, I didn't (knowingly) do anything like that in the original implementation. Here's the "off" implementation in the one that results in the "OK":

def off() {
   if (logEnable) log.debug "Sending off request to [${settings.ip}, station ${settings.station}]"
   sendEvent(name: "switch", value: "off", isStateChange: true)

   interfaces.rawSocket.sendMessage("rf ${settings.station} off\n")
  pauseExecution(2000)
  //refresh()
}

Here's the componentOff implementation in the parent/child implementation. It is in the parent, not the child.

void componentOff(cd){
    
      station = cd.getDataValue("station")
   
      if (logEnable) log.debug "Sending off request to [${settings.ip} ${station}]"
          
      interfaces.rawSocket.sendMessage("rf ${station} off\n")
      log.info "before pause"
      pauseExecution(2000)
      log.info "after pause"
      sendEvent(name: "switch", value: "off", isStateChange: true)
}

I tried moving the sendEvent before doing the write (so it would occur before the 2 second delay) but that didn't change anything.

Is it possible that it's because the sendEvent is issued from the parent instead of the child?

Alexa knows nothing about the parent--only the child devices were added to the Amazon Echo Skill.

Or, is the sendEvent event even relevant? Is there something else in a driver that must be done for Alexa to get the expected response? Or, should the Amazon Echo Skill (in Hubitat) take care of that?

I don't see anything in your second code snippet that sets the "switch" attribute state on the child device. If the child device is the one you're adding to (and controlling via) Alexa, then that device is the one that would need to have the "switch" attribute change from "on" to "off" in order for Alexa to see that there was a change.

But this isn't really Alexa-specific. If you just look at the (child) device page in Hubitat, do you see any changes when you run the "On" and "Off" commands there directly? I'd guess not (not unless you have more code that affects this than was shown above). If you're using Hubitat's built-in component driver for the child, you can see one way to do this in their example parent code: HubitatPublic/genericComponentParentDemo.groovy at master · hubitat/HubitatPublic · GitHub. Look at the childSwitchOff() or componentOff() methods to see how they use the parse() method on the child device to pass an event down do it (and you can look at the child driver code too if you want to see what it really does with that). I'm pretty sure you could also call sendEvent() directly on the child device, but I'd probably stick with the paradigm in their example unless you have a reason not to.

From what I can see, you're only calling sendEvent() on the parent (which you may or may not need to, depending on whether you still have a "switch" attribute on the parent device and if/how you want child device manipulation to affect it). As far as most apps, including Alexa, are concerned, there's no special relationship between a parent and child device--they're basically completely separate other than whatever you may do in the driver.

2 Likes

You are right on all you are assuming.

Thanks!

When I copy/paste the cd.parse command:

cd.parse([[name:"switch", value:"off", descriptionText:"${cd.displayName} was turned off"]])

from the parent code example, I get the error:

2021-06-04 04:41:10.197 pm [error](http://hubitat/device/edit/139)java.lang.IllegalArgumentException: Command 'parse' is not supported by device 151. on line 150 (componentOff)

It looks like the generic component switch doesn't have a parse method that takes an argument? Or, the expression

[[name:"switch", value:"off", descriptionText:"${cd.displayName} was turned off"]]

is not resolving to a "String" type in this context?

But, if I do this:

testString = [[name:"switch", value:"off", descriptionText:"${cd.displayName} was turned off"]]
 log.info "This is the test string ${testString}"

I see this in the logs:

[dev:139](http://hubitat/logs#dev139)2021-06-04 05:45:26.269 pm [info](http://hubitat/device/edit/139)This is the test string [[name:switch, value:off, descriptionText:office was turned off]]

Which says the expression does resolve to a string.

How can I confirm a Generic Component Switch actually has a parse(String) method?

Or, maybe I can set a required attribute a different way?

If you just look at the (child) device page in Hubitat, do you see any changes when you run the "On" and "Off" commands there directly?

Actually,I don't see any state variables in child device. There's nothing that indicates on or off on the child devcie page. It's a generic component switch.

In the child driver example, there are actually two parse methods, one that takes a String argument and another that takes a map.

Do all devices have these methods?

Generic component switch should indeed have a parse() method. Like the component driver, this takes a List, not a Map (and not a String, though that is technically implemented so the driver catches it instead of throwing an error; this is the type most other kinds of devices would use for this method).

If this isn't working for you, I'd either start with a simple parent/child driver to see if you can get that to work (I'm using this in one of my drivers, so it should work...unless I'm looking at the wrong thing) or pare down your existing driver to just this much to see if you can produce a minimal non-working example. On the way, you'll likely figure out what's wrong.

Based on the Demo parent example, and the implementation of the child parse method in the code for the Generic Component switch, the child parse method takes a list of maps, correct?

Yet the invocation of the child parse method (a direct copy/paste of the Demo Driver code fragment) is not recognized. I think it is a list of maps, but with just one map in the list.

cd.parse([[name:"switch", value:"on", descriptionText:"${cd.displayName} was turned on"]])

2021-06-04 07:48:42.283 pm [error](http://hubitat/device/edit/139)java.lang.IllegalArgumentException: Command 'parse' is not supported by device 151. on line 141 (componentOn)

Simplifying, I did this:

def mylist = [[name:"switch", value:"on"]]
    log.info mylist
    cd.parse mylist

Still, I get:

[dev:139](http://hubitat/logs#dev139)2021-06-04 07:52:05.119 pm [error](http://hubitat/device/edit/139)java.lang.IllegalArgumentException: Command 'parse' is not supported by device 151. on line 141 (componentOn)

Not sure how I would simplify--I just can't seem to call the parse method in child Generic Component Switch.

I was able to call the child's parse method with a string an got the warning consistent with the child code you pasted.

I don't think it's possible to troubleshoot more without seeing the actual code you're working with. The parse() method on the Generic Component Switch driver definitely works as it should. Here is a minimal example like the one I was talking about, and it works fine for me:

/*
    Minimal parent device demo
*/

metadata {
    definition (name: "TEST Parent Demo", namespace: "Test", author: "Test") {
        capability "Actuator"
        
        command "createChildDevice"
        command "childSwitchOn"
        command "childSwitchOff"
    }
    preferences {
        input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
        input name: "txtEnable", type: "bool", title: "Enable descriptionText logging", defaultValue: true
    }
}

void logsOff() {
    log.warn "debug logging disabled..."
    device.updateSetting("logEnable", [value:"false",type:"bool"])
}

void updated() {
    log.debug "updated()"
    log.warn "debug logging is: ${logEnable == true}"
    log.warn "description logging is: ${txtEnable == true}"
    if (logEnable) runIn(1800,logsOff)
}

// Custom commands for demonstration:
void childSwitchOn() {
    log.debug "childSwitchOn()"
    com.hubitat.app.DeviceWrapper childDevice = fetchChild()
    log.debug "childDevice = ${childDevice.displayName} (DNI: ${childDevice.deviceNetworkId}"
    childDevice.parse([[name:"switch", value:"on", descriptionText:"${childDevice.displayName} was turned on"]])
}

void childSwitchOff() {
    com.hubitat.app.DeviceWrapper childDevice = fetchChild()
    log.debug "childDevice = ${childDevice.displayName} (DNI: ${childDevice.deviceNetworkId}"
    childDevice.parse([[name:"switch", value:"off", descriptionText:"${childDevice.displayName} was turned off"]])
}

void createChildDevice() {
    String cdDNI = "${device.id}/child-switch"
    com.hubitat.app.DeviceWrapper cd = getChildDevice(cdDNI)
    if (cd != null) {
        log.warn "Not creating child device; already exists"
    }
    else {
        cd = addChildDevice("hubitat", "Generic Component Switch", cdDNI, [name: "${device.displayName} Child Switch", isComponent: false])
        //set initial attribute values, with a real device you would not do this here...
        List<Map<String,String>> defaultValues = [[name:"switch", value:"off", descriptionText:"(populated initial default values)"]]
        cd.parse(defaultValues)
    }
}

com.hubitat.app.DeviceWrapper fetchChild() {
    String cdDNI = "${device.id}/child-switch"
    com.hubitat.app.DeviceWrapper cd = getChildDevice(cdDNI)
    if (cd == null) {
        log.warn "Child device not found"
        return null
    }
    return cd
}

void componentRefresh(cd) { log.warn "componentRefresh() not implemented" }

void componentOn(com.hubitat.app.DeviceWrapper cd){
    if (logEnable) log.debug "componentOn(${cd.displayName})"
    cd.parse([[name:"switch", value:"on", descriptionText:"${cd.displayName} was turned on"]])
}

void componentOff(com.hubitat.app.DeviceWrapper cd){
    if (logEnable) log.debug "componentOff(${cd.displayName})"
    cd.parse([[name:"switch", value:"off", descriptionText:"${cd.displayName} was turned off"]])
}
1 Like

A couple other thoughts that may be helpful given the error, though again I think you might be best served by paring down your driver to a minimal non-working example of the failure or working your way up from a minimal working example, either of which will likely show you whatever is wrong:

  • are you sure your device reference is a child device? (you wouldn't have access to parse() as a driver method, even if it exists as such, if it's not a device command, unless it's a parent/child relationship; from a driver, not an app, that's really the only other device you'd be able to access, so probably, but just checking...)
  • are you using the "Generic Component Switch" driver? I could see someone wanting to use "Virtual Switch" or one of the generic "Z" switch drivers, neither of which would work for this purpose--a component (child/parent) device.

Thanks for the reply and suggestions.

*again I think you might be best served by paring down your driver to a minimal non-working example of the failure or working your way up from a minimal working example, either of which will likely show you whatever is wrong:

I thought I was working from a pared down version of the Parent Demo. The difference being I had a command in the parent to create the child rather than them getting creating without user input in the parent code. I'll go back and see if I can get the unaltered Parent Demo to work.

are you sure your device reference is a child device? (you wouldn't have access to parse() as a driver method, even if it exists as such, if it's not a device command, unless it's a parent/child relationship; from a driver, not an app, that's really the only other device you'd be able to access, so probably, but just checking...)

Well, it was created by the parent driver using addChild method. It shows up in the list of devices under the parent device:

How can I be sure it's a child device? The device page in Hubitat shows it as a "Generic Component Switch". Here's what it looks like on the device list page:

And its individual page:

Yes, it's a Generic Component Switch Driver.

Ok, found the problem.

The commands:

void componentOff(cd)
void componentOn(cd)

Are called in chlid devices. But, the parameter cd is not the child device itself, but cd.device

The on command in the genericChild code you posted:

void on() {
    parent?.componentOn(this.device)
}

The componentOn method in the parent must get the child device using:

cd = getChildDevice(cd.deviceNetworkId)

That's in the demo. What confused me was the same variable name is used elsewhere in methods like this:

def cd = fetchChild("Switch")
cd.parse([[name:"switch", value:"off", descriptionText:"${cd.displayName} was turned off"]])

That cd is not the same type as the cd passed to the componentOn and componentOff commands!

Now the parse command gets called. I"ll check to see if the Alexa Echo Skill still needs to bypass waiting for a response.

Other than posting questions, how would one know what methods like componentOn and componentOff (and their signatures) exist for each component device? Are they all the same?

Yes, doing the cd.parse command eliminated the need to have the Alexa skill respond immediately without waiting for a reply!

So @bertacd1234, you nailed it!

3 Likes

Download the Hubitat app