Error when turning on switch

I just purchased a Hubitat C-8 hub since SmartThings removed support for Groovy and I have an app that I wrote that couldn't be transitioned with their existing functionality.

I've been working out issues transitioning my Groovy app from SmartThings to Hubitat, but there is one issue I haven't been able to work out. When I call the .on() or .off() function on my switch it gives me an error

groovy.lang.MissingMethodException: No signature of method: com.hubitat.handler.GenericDeviceHandler.handleRunDeviceMethodExceptions() is applicable for argument types: (java.lang.String, [Ljava.lang.Object;, com.hubitat.handler.BaseDeviceHandler$_runDeviceMethod_closure4, java.lang.Boolean, com.hubitat.handler.BaseDeviceHandler$MissingMethodExceptionLogging) values: [on, [], com.hubitat.handler.BaseDeviceHandler$_runDeviceMethod_closure4@8a2331, ...] on line 293 (method deviceCommand)

It seems like the first time I call either the .on() or .off() function of the switch it works fine, but then subsequent ones give me the error, at least until I either reboot the hub, or walk away for a while and come back. I notice that this issue isn't unique just to the code I have written, I also see the error message come up in the logs when I try turning the device on or off in the app. Is there a bug in the hub? What is the solution? Should I return my hub?

Here are some of the details of what I'm working with:
Hub: C-8
Platform Version: 2.3.5.131
Switch Type: Sengled Element Color Plus

What "Type" is the device set to on the devices page? (commonly called the driver, or DTH on the ST platform)

If you press the on/off commands on the device page, you also get an error? What is that error?

The type is "Sengled Element Color Plus". When the issue is present pressing the on or off command in the device page does nothing and does not result in any logs. When I press the on or off command in the Lights/Switches section of the Hubitat app, the icon blinks indicating that the command did not work, and the logs show the same error as the command in my Groovy app.

One thing that I noticed this morning is that I can turn the device on/off all I want from the Hubitat app without any issues, but then when the on or off command is sent from my Groovy app the first command works, but then after that the command stops working from all sources.

First, Welcome to the Hubitat Community!

This is not a common issue that I am aware of. In fact, this is the first time I have ever heard this reported in the 5+ years that I have been using Hubitat.

Thus, in order for the community to provide effective guidance, I would recommend you share your App's source code. There are quite a few very talented community developers that could take a look at your app and possibly identify any issues.

One possible way to try and separate whether the issue is with the App or the Device, would be to create a Virtual Device, using one of Hubitat's Virtual Driver Types. Then, use the virtual device in your app and see if the problem persists or if the behavior changes.

2 Likes

Here is the code. It's pretty messy, but it did the trick when SmartThings worked with Groovy. I'm still in the process of transitioning the app to Hubitat and this On Off issue is preventing the basic functionality from working. Although this app has grown with additional functionalities over the years, the most basic purpose is to allow a light to be turned on/off with motion with the added functionality of not turning the light back on with motion if it wasn't turned off from motion inactivity.

The problematic lines are the item.off() line in the checkMotion function and the item.on() in the motionDetectedHandler

/**
 *  Motion Plus
 *
 *  Copyright 2016 Matthew Williams
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
//import groovy.time.TimeDuration
////import groovy.time.TimeCategory
 
definition(
    name: "Motion Plus",
    namespace: "MatthewWilliams",
    author: "Matthew Williams",
    description: "Turn on lights with motion sensor. Allow lights to be turned off without being turned back on by motion sensor.",
    category: "Convenience",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")


preferences {
    section("Turn on when motion detected:") {
        input "themotion", "capability.motionSensor", required: true, title: "Where?", multiple: true
    }
    section("Turn off when there's been no movement for") {
        input "minutes", "number", required: true, title: "Minutes?"
    }
    section("Turn on this light") {
        input "theswitch", "capability.switch", required: true, multiple: true
    }
    section("Enabled during daylight hours?") {
        input "duringdaylight", "boolean", required: true, multiple: false, title: "Daylight hours?"
    }
    section("Daylight offset (default 60 minutes)") {
    	input "daylightoffset", "number", required: false, title: "Offset minutes"
    }
    section("Light sensor"){
    	input "lightsensor", "capability.illuminanceMeasurement", required: false, title: "Light sensor", multiple: false
    }
    section("Light threshold"){
    	input "lux", "number", required: false, title: "Lux"
    }
    section("Inverse?") {
        input "isinverse", "boolean", required: true, multiple: false, title: "Inverse?"
    }
    section("Night time hours") {
    	input "nightDimmers", "capability.switchLevel", multiple: true, required: false, title: "Night dimmers"
        input "nightTempLights", "capability.colorTemperature", multiple: true, required: false, title: "Night color temperature lights"
    	input "nightStart", "time", required: false, title: "Night start time"
      	input "nightEnd", "time", required: false, title: "Night end time"
        input "dayLevel", "number", required: false, title: "Day brightness"
        input "nightLevel", "number", required: false, title: "Night brightness"
        input "dayTemp", "number", required: false, title: "Day color temp"
        input "nightTemp", "number", required: false, title: "Night color temp"
    }
}

def installed() {
    initialize()
}

def updated() {
    unsubscribe()
    initialize()
}

def initialize() {
    subscribe(themotion, "motion.active", motionDetectedHandler)
    subscribe(themotion, "motion.inactive", motionStoppedHandler)
    subscribe(theswitch, "switch.on", lightOnHandler)
    subscribe(theswitch, "switch.off", lightOffHandler)
    subscribe(lightsensor, "illuminance", lightChangeHandler)
    sunsetHandler()
    //Backup schedule to prevent lights from staying on if the runOnce fails
	runEvery1Hour(checkMotion)
}

def sunsetHandler(){
    //log.debug "sunsetHandler"
    if(duringdaylight == "false"){
        //The room lighting can change below the threshold while there is motion. 
        //If there is motion call the motion detected handler so lights will turn on if the threshold has been met.
        if (themotion.currentState("motion").value == "active"){
            motionDetectedHandler()
        }
        def sunsetTime = getSunsetTime()
        def nextHour = now() + (1000*60*60)
        if(nextHour > sunsetTime){
            sunsetTime = nextHour
        }
        sunsetTime = new Date(sunsetTime)
        //log.debug(sunsetTime)
        runOnce(sunsetTime,sunsetHandler)
    }
}

def getSunsetTime(){
	use(groovy.time.TimeCategory){
		//Get the time of midnight the next day
        //For example if it is 3/2/2019 16:10 midnight time will be 3/3/2019 00:00 in epoch time
        def midnightTime = (new Date().clearTime() + 1.days)
        //Offset time is not included
        midnightTime = midnightTime + 5.hours
        midnightTime = midnightTime.time
        //Get sunrise and sunset for current day
        //def sunsetTime = Date.parse("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", location.currentValue("sunsetTime"))
        def sunsetTime = location.sunset
        if (sunsetTime.time > midnightTime){
        	//if sunrise time is for the current day change it to the next day
            sunsetTime = sunsetTime - 1.days
        }
        sunsetTime = sunsetTime.time
        //Only turn the light on if it is set to go on during daylight hours or it is not daylight hours
        def offsetMinutes = 60
        if(daylightoffset != null){
        	offsetMinutes = daylightoffset
        }
        def sunOffset = 60 * 1000 * offsetMinutes
        sunsetTime = (sunsetTime - sunOffset)
        return sunsetTime
	}
}

def getSunriseTime(){
	use(groovy.time.TimeCategory){
		//Get the time of midnight the next day
        //For example if it is 3/2/2019 16:10 midnight time will be 3/3/2019 00:00 in epoch time
        def midnightTime = (new Date().clearTime() + 1.days).time
        //Get sunrise and sunset for current day
        //def sunriseTime = Date.parse("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", location.sunrise)
        def sunriseTime = location.sunrise
        if (sunriseTime.time > midnightTime){
        //if sunrise time is for the next day change it to the current day
        	sunriseTime = sunriseTime - 1.days
        }
        sunriseTime = sunriseTime.time
        //Only turn the light on if it is set to go on during daylight hours or it is not daylight hours
        def offsetMinutes = 60
        if(daylightoffset != null){
        	offsetMinutes = daylightoffset
        }
        def sunOffset = 60 * 1000 * offsetMinutes
        sunriseTime = (sunriseTime + sunOffset)
        return sunriseTime
	}
}

def lightChangeHandler(evt){
	//The room lighting can change below the threshold while there is motion. 
    //If there is motion call the motion detected handler so lights will turn on if the threshold has been met.
    if (themotion.currentState("motion").value == "active"){
    	motionDetectedHandler()
    }
}

def lightOnHandler(evt) {
    //log.debug "lightOnHandler called: $evt"
    use(groovy.time.TimeCategory){
        def schedDate = new Date()
        for(def i = 0; i < minutes; i++){
            schedDate = schedDate + 61.seconds
        }
        runOnce(schedDate, checkMotion)
    }
    //runIn((61 * minutes), "checkMotion", [overwrite:true])
}

def lightOffHandler(evt) {
    //log.debug "lightOffHandler called: $evt"
}

def motionDetectedHandler(evt) {
    log.debug "motionDetectedHandler called: $evt"
    int index = 0
    
    log.debug "theswitch"
    log.debug theswitch
    for(item in theswitch){
        def onState = item.currentState("switch")
        log.debug "onState"
        log.debug onstate
        if((onState.value == "off" && isinverse == "false")||(onState.value == "on" && isinverse == "true")) {
            use(groovy.time.TimeCategory){
                def duration = 60.seconds//new TimeDuration(0, minutes, 0, 0)
                def exactDuration = 60.seconds
                //time the light turned off
                def start = onState.date
                def exactStart = onState.date
                for(def i = 0; i < minutes; i++){
                    start = start - duration
                    exactStart = exactStart - exactDuration
                }
                def end = onState.date
                //def switchStates = item.statesBetween("switch", start + 120.seconds, end - 120.seconds)
                //def switchStates = item.statesSince("switch", start)
                //Hubitat doesn't have states between. Use states since then remove the end range states
                def switchStates = item.statesSince("switch", start + 10.seconds)
                switchStates.removeAll { it.date >= (end - 10.seconds) }
                
                // New - Sum up the motion states for all the motion sensors
                def motionStateCount = 0
                for(motionSensor in themotion){
                    def motionEvents = motionSensor.eventsBetween(start + 10.seconds, end - 10.seconds)
                    motionEvents.removeAll { it.name != "motion" }
                	motionStateCount = motionStateCount + motionEvents.size()
				}                
                
                // Old
                //def motionStates = themotion.statesBetween("motion", start + 120.seconds, end - 120.seconds)
                
                //Check to see if the light was last on for less than the set number of minutes. If this is true then the light must have turned off from another source
                def lastOnStatesBetween = item.eventsBetween(exactStart, end)
                lastOnStatesBetween.removeAll { it.name != "switch" }
                
                def lastOnState = lastOnStatesBetween[1]
                def isOnLessThanMinutes = false
                if(lastOnState != null){
                    def onStateDur = (onState.date.getTime() - lastOnState.date.getTime())/1000/60
                    //subtract a minute from minutes in case motion sensor turns off lights a little before minutes
                    isOnLessThanMinutes = onStateDur < (minutes/2)
                }
                else{
                	//log.debug "lastOnState is null the state before that is"
                    //log.debug lastOnStatesBetween[0]
                    if(lastOnStatesBetween[0] != null){
                    	//log.debug lastOnStatesBetween[0].value
                    }
                }
                
                //Todo: check corner case of if motion has been active for longer than minutes when light is turned off
                //...look into statesBetween to see look for motion active as the latest state during the specified date range
                //...don't turn on lights if there are three or more events, or if the light wasn't even on for the configured inactive time.
                if(!isOnLessThanMinutes && ((switchStates.size() + motionStateCount) < 3)){	// New code
                	def beforeSunrise = (getSunriseTime()) >= now()
                    def afterSunset = (getSunsetTime()) <= now()
                    if( (duringdaylight == "true") || beforeSunrise || afterSunset){
                    	if((beforeSunrise || afterSunset || lightsensor == null) || (lightsensor != null && lightsensor.currentState("illuminance").value.toInteger() <= lux.toInteger())){
                    		if(isinverse == "true"){
                            	item.off()
                            }
                            else {
                                log.debug "turning on light"
                                setNightDimmerLevel(item)
                                setNightColorTemp(item)
                                log.debug "item"
                                log.debug item
                                item.on()
                            }
                        }
                    }
                }
                else {
                    //log.debug "detected shut off from other source. Switch and Motion states count:"
                    //log.debug switchStates.size()
                    //log.debug motionStates.size()
                }
            }
        }
        else {
            //log.debug "light already on"
        }
        index++
    }
}

def setNightDimmerLevel(dimmer){
	if(nightStart != null && nightEnd != null){
        for(dimmerItem in nightDimmers){
            if(dimmerItem.id == dimmer.id){
                use(groovy.time.TimeCategory){
                    def nightStartTime = timeToday(nightStart.substring(11,16), location.timeZone).time
                    def nightEndTime = timeToday(nightEnd.substring(11,16), location.timeZone).time
                    def isNight = false
                    if(nightStartTime > nightEndTime){
                        isNight = now() >= nightStartTime || now() < nightEndTime
                    }
                    else{
                        isNight = now() >= nightStartTime && now() < nightEndTime 
                    }
                    if(isNight){
                        if(nightLevel != null){
                            dimmerItem.setLevel(nightLevel, 1)
                        }
                    }
                    else{
                        if(dayLevel != null){
                            dimmerItem.setLevel(dayLevel, 1)
                        }
                    }
                }
            }
        }
	}
}

def setNightColorTemp(tempLight){
	if(nightStart != null && nightEnd != null){
        for(tempItem in nightTempLights){
            if(tempItem.id == tempLight.id){
                use(groovy.time.TimeCategory){
                    def nightStartTime = timeToday(nightStart.substring(11,16), location.timeZone).time
                    def nightEndTime = timeToday(nightEnd.substring(11,16), location.timeZone).time
                    def isNight = false
                    if(nightStartTime > nightEndTime){
                        isNight = now() >= nightStartTime || now() < nightEndTime
                    }
                    else{
                        isNight = now() >= nightStartTime && now() < nightEndTime 
                    }
                    if(isNight){
                        if(nightTemp != null){
                            tempItem.setColorTemperature(nightTemp)
                        }
                    }
                    else{
                        if(dayTemp != null){
                            tempItem.setColorTemperature(dayTemp)
                        }
                    }
                }
            }
        }
	}
}


def motionStoppedHandler(evt) {
    //log.debug "motionStoppedHandler called: $evt"
    //log.debug "Starting motion stopped handler"
    use(groovy.time.TimeCategory){
        def schedDate = new Date()
        for(def i = 0; i < minutes; i++){
            schedDate = schedDate + 61.seconds
        }
        runOnce(schedDate, checkMotion)
    }
}

def checkMotion() {
    log.debug "In checkMotion scheduled method"
    //def motionState = themotion.currentState("motion") //Old code

	// New - Loop through motion sensors to report inactive in below IF when all are inactive
    // New - Loop through motion sensors to get the smallest elapsed time
    def areAllInactive = true
    def elapsed = -1
    for(motionSensor in themotion){
    	def motionState = motionSensor.currentState("motion")
    	if(motionState.value != "inactive"){
        	areAllInactive = false
            break
        }
        def curElapsed = now() - motionState.date.time
        if(elapsed == -1 || curElapsed < elapsed){
        	elapsed = curElapsed
        }
    }

    //if (motionState.value == "inactive") { // Old code
    if(areAllInactive){ // New code
    
        // get the time elapsed between now and when the motion reported inactive
        //def elapsed = now() - motionState.date.time // Old code
        log.debug "now"
        log.debug now()

        // elapsed time is in milliseconds, so the threshold must be converted to milliseconds too
        def threshold = 1000 * 60 * minutes
		
        if (elapsed >= threshold) {
            log.debug "Motion has stayed inactive long enough since last check ($elapsed ms):  turning switch off"
            int index = 0
            log.debug "theswitch"
            log.debug theswitch
            for(item in theswitch){
            	def lightState = item.currentState("switch")
            	log.debug "The light should be getting turned off... The state of the light is:"
                log.debug lightState.value
                if((lightState.value == "on" && isinverse == "false") || (lightState.value == "off" && isinverse == "true")){
                    def lightOnElapsed = now() - lightState.date.time
                    if(lightOnElapsed >= threshold){
                        use(groovy.time.TimeCategory){
                            def schedDate = new Date()
                            for(def i = 0; i < minutes; i++){
                                schedDate = schedDate + 61.seconds
                            }
                            runOnce(schedDate, checkMotion)
                        }
                        if (isinverse == "true"){
                            setNightDimmerLevel(item)
                            setNightColorTemp(item)
                            item.on()
                        }
                        else {
                            log.debug "item"
                            log.debug item
                    		item.off()
                        }
                        log.debug "The light should have been turned off... The state of the light is:"
                        log.debug lightState.value
                    }
                }
                index++
            }
            //theswitch.off()
        } else {
            //log.debug "Motion has not stayed inactive long enough since last check ($elapsed ms):  doing nothing"
            //log.debug "There is no other scheduled task to check the motion... scheduling check motion..."
            //runIn(61 * minutes, checkMotion)
        }
    } else {
        // Motion active; just log it and do nothing
        //log.debug "Motion is active, do nothing and wait for inactive"
    }
    //atomicState.isTurningOff = isTurningOff
}

After finding some time to do a little bit of troubleshooting, I was able to pinpoint the issue. It turns out if I try turning on the light within a use(groovy.time.TimeCategory) block this error happens. I fixed it by creating a turnOnLight flag outside of the use block, setting it to true when I would have normally turned on the light, and then waiting until outside the use block to turn on the light if the flag value is true.

2 Likes