Why is my "runIn" not working as expected

My understanding is that the runIn() function allows me to execute the named function at a later time. This is the code that shows the problem. If needed I can paste in the whole App, but for now the problem is here:

def nightHandler(evt)
{
    log.debug "nightHandler called"
    setState()
    runIn(5, "setState")
}

def setState()
{
    String isNightStr = nighttime.currentValue("variable")
    boolean isNight = isNightStr == "true"
    log.debug "setState() isOn $state.isOn isNight $isNight"
    // change the color of the bulb based on isOn and isNight
}

nightHandler(evt) gets called twice a day, due to it being triggered by a change in a Hub Variable, using a Connector. The Hub Variable is changed by a couple of rules in Rule Machine, triggered at sunrise and sunset. All that logic works correctly, but I suspect that due to caching / lazy write of the variable the bulb doesn't always change state at sunrise and/or sunset. Thus the desire for a delay.

Except the logs show that the direct call to setState() in nightHandler(evt) works, but that the same call is not made five seconds later. I changed the rule to a repeated event that fires every 30 seconds, and uses an If / ELSE / ENDIF condition to make the Hub Variable flip state every time. Using that, the logs show this:

[code]
app:109 2021-09-16 02:19:00.157 pm debug setState() isOn false isNight true
app:109 2021-09-16 02:19:00.150 pm debug nightHandler called
app:161 2021-09-16 02:19:00.134 pm info Setting Test to true
dev:196 2021-09-16 02:19:00.127 pm info Test variable is true
[/code[

Test is the variable name, the third line is a log line from the Rule to verify it's activating. I'd expect the top line to be duplicated five seconds later but there's no sign of that call.

You aren't showing enough of the logs, and use a screenshot, not copy/paste.

Also, you might try

runIn(5, setState)

That's a screencap of the logs.

Switching to runIn(5, setState) makes no difference, which is to be expected. Since the signature of runIn is runIn(Integer delay, String functionName, map options = null) I'd be incredibly surprised if it did change anything.

I know that groovy permits the string to be typed without quotes, and figures it out at runtime based on the signature of runIn() which takes a string as the second parameter. With something like 40 years of C/C++ programming behind me, it has become second nature to ensure that the signature when the function is called exactly matches what is expected. It becomes a form of documentation when you get used to it: you have the signature (prototype in C/C++ parlance) of the function explicitly called out at the point you're invoking it.

Different styles, I guess. :slight_smile:

1 Like

That's odd. One thing you could try is to make the duration longer than 5 seconds, just so you have time to look at the App Status page for the app during that period. See if it has the Scheduled Job or not.

I have a theory based on some things I read about groovy the other day. May or may not be right.
Try either just adding a "return" to the bottom of nightEvent, or move the runIn up, so it is above the direct call to setState(). I think if you do not have a return I read something about automatic closure and groovy assumes the last statement is the return, so maybe that is breaking the runIn?

def nightHandler(evt)
{
    log.debug "nightHandler called"
    setState()
    runIn(5, "setState")
    return
}

def nightHandler(evt)
{
    log.debug "nightHandler called"
    runIn(5, "setState")
    setState()
}

You know what also might work, defining the function as void, I have a void function where the last thing is a runIn and it works.

void nightHandler(evt)
{
    log.debug "nightHandler called"
    setState()
    runIn(5, "setState")
}

No, this isn't needed. Return is implicit. Also, void vs def is irrelevant in this case. These don't explain his lack of the double log entry, no more than quotes or not on the runIn handler method mattered.

1 Like

I Increased the delay to 20 seconds, and found where the scheduled jobs are shown in the settings. The job is shown as scheduled when expected. I'm glad to see that the HE appears to be pinging an NTP server to set its time, makes it easy to debug when things happen by showing a clock on my PC.

That said, the scheduled job shows immediately after nightHandler() gets called, and vanishes again 20 seconds later. Refreshing the page brings it up to date. However there is no output from the log for that second call.

I can provide the gory details if requested, but by using means other than log.debug() calls, I have obtained a second confirmation that setState() is not being called, even though the scheduled job appears and then disappears. My thinking was that I have been in the business long enough to have seen it all. The possibility that the function was being called, but someone unable to output to log.debug() had crossed my mind. No harm in being thourough and checking it out.

Shouldn't this have no parameters? It might be failing because no signature matches. I suppose you could use evt=null to provide a default if this ala serves as an event handler for you.

EDIT: nevermind, that's exactly what that is! I mis-tememnered the method name of interest....

preferences
{
    section("Night Time variable:")
    {
        input "nighttime", "capability.sensor", required: true, title: "Select"
    }
    // Other preferences here
}

def installed()
{
    log.debug "Installed with settings: ${settings}"
    initialize()
}

def updated()
{
    log.debug "Updated with settings: ${settings}"
    unsubscribe()
    initialize()
}

def initialize()
{
    // Other subscribe calls here
    subscribe(nighttime, "variable", nightHandler)
    state.isOn = false
}

nightHandler(evt) should only be invoked as a result of the specified Hub Variable being changed, since it's an event handler, I was under the impression it has to have the evt parameter. I fully concede that I'm not using the parameter, but I thought I needed it to make the signatures match. I'm doing the delayed call on setState() which is defined with no parameters.

Definitely something weird here. See my pm.

Yes, this is correct. It would throw an error if it didn't find the method, but clearly it is, and there is no error in your logs.

/*
 * Night Light switch controller
 *
 * This controls a smart color bulb
*/

import groovy.transform.Field

@Field final int ON_BUTTON = 1
@Field final int OFF_BUTTON = 2

definition(
    name: "Night Light",
    namespace: "dgnuff",
    author: "David Goodenough",
    description: "Smart LED nightlight controller",
    category: "Utility",
    iconUrl: "",
    iconX2Url: ""
)

preferences
{
    section("Switch to read:")
    {
        input "theswitch", "capability.pushableButton", required: true, title: "Select"
    }
    section("Night Time variable:")
    {
        input "nighttime", "capability.sensor", required: true, title: "Select"
    }
    section("Light to control:")
    {
        input "thelight", "capability.colorControl", required: true, title: "Select"
    }
    section("Nightlight color:")
    {
        input "nightLightHue", "int", title: "Hue", required: true, range: "0..100"
        input "nightLightSaturation", "int", title: "Saturation", required: true, range: "0..100"      
        input "nightLightLevel", "int", title: "Level", required: true, range: "0..100"
    }
}

def installed()
{
    log.debug "Installed with settings: ${settings}"
    initialize()
}

def updated()
{
    log.debug "Updated with settings: ${settings}"
    unsubscribe()
    initialize()
}

def initialize()
{
    subscribe(theswitch, "pushed", pushHandler)
    subscribe(theswitch, "held", pushHandler)
    subscribe(nighttime, "variable", nightHandler)
    state.isOn = false
}

def pushHandler(evt)
{
    log.debug("pushHandler called button $evt.value")
    def isOn = state.isOn;
    state.isOn = evt.value.toInteger() == ON_BUTTON
    def newIsOn = state.isOn
    log.debug("pushHandler says isOn changed from $isOn to $newIsOn")
    setState()
}

def nightHandler(evt)
{
    log.debug "nightHandler called"
    runIn(20, "setState")
    state.isOn = true
    setState()
    state.isOn = false
}

def setState()
{
    String isNightStr = nighttime.currentValue("variable")
    boolean isNight = isNightStr == "true"
    log.debug "setState() isOn $state.isOn isNight $isNight"
    if (state.isOn)
    {
        log.debug "setState on"
        thelight.setColorTemperature(2700, 100, 0)
    }
    else if (isNight)
    {
        log.debug "setState night $nightLightHue, $nightLightSaturation, $nightLightLevel"
        Map color =
        [
            hue: nightLightHue.toInteger(),
            saturation: nightLightSaturation.toInteger(),
            level: nightLightLevel.toInteger()
        ]
        thelight.setColor(color)
    }
    else
    {
        log.debug "setState off"
        thelight.off()
    }
}

That's the code of the App. Intended use is to have a smart bulb in the bathroom in one of three states. on and off are controlled by a switch (Zooz ZEN34 in my case), and when off it selects either fully off during the day, or very low intensity during the night. In that instance I'd use a couple of rules scheduled at sunrise and sunset to flip a Hub Variable and ultimately control the nightlight mode from there.

For the sake of completeness here's the rule I'm using to flip it every 30 seconds:

Can you try naming your method something other than setState?
There's already a state property, so there may be a naming collision, with unpredictable outcome.

6 Likes

Just a little up thread, I mentioned having seen it all. Now I really have seen it all. I renamed setState() to controlBulb() and it all started working. Here's the logs caught at about 25 seconds past the minute:

This has the scent of what I know as a namespace problem. Since the second parameter to runIn() is a function name sent as a string, it appears to be finding some other function in the system with the same name as mine, and calling that instead. i.e. everything is in what I know as the global namespace. The workaround to that would be to do your best to ensure function names in your App are distinct from those anywhere else in the system.

This was always a problem in the early days of C, before C++ came along and gave us classes and namespaces as ways of keeping things separate. So I'm no stranger to the issue, I just wasn't expecting to bump into it again after all these years.

Thanks again for all the help!

2 Likes

Download the Hubitat app