Variable Scope

I was doing exactly that but using parse as the callback. But I thought the state variables were not "carrying over" from one process to another so I presumed I was doing it wrong. I am travelling now but I think my test (state,variable == "success") was incorrect. Will check it out when I get back.

However I guessed at what state variables are used for by looking at others code. I could not find any Hubitat documentation on how I'm supposed to use them. At the moment I "register" them all in my initialize routine and then treat them just like a script variable, is that correct?

The old ST docs may help. Usage is the same. Device Handler — SmartThings Documentation 1.0 documentation

Here it is for apps. Basically the same for drivers except atomicState is not an option: App Overview | Hubitat Documentation

I have had hiccups with state that I couldn't explain, and I just assumed they were related to its implementation and cacheing behaviors or other potential issues that were opaque to me because it is a closed source function that I couldn't review.

When I want to be absolutely sure that data is available and updated across multiple threads, I implement my own thing like this:

@Field static volatileState = [:].asSynchronized()

and I make setters and getters for members within that. It always works for me, but I have also been advised by Hubitat staff that I shouldn't be so paranoid about this and that they'd like to hear about issues where state doesn't behave as intended. So, YMMV. :slight_smile:

1 Like

@coreystup , @bertabcd1234 : Many thanks for the documentation links, very helpful.

@tomw : With those documentation links I think I have found a clue to my issue. In my logs I see the parse() method set the state variable at 03:23:41.963 then the sending methods checks it at 1 second intervals up to 03:23:46.312 but does not see a change and so times out. In other words the sending method has still not seen a change in the state variable 4 seconds after parse() has updated it.

I use pauseExecution(1000) to create my timeout loop and I am wondering if this maybe the cause of the problem? The Hubitat documentation says " state writes data just before the app goes to sleep again". The ST documentation says sate is "A map of name/value pairs that a Device Handler can use to save and retrieve data across executions". Maybe the sending thread only reads the state variable when it starts up after sleep() and pause execution does allow the thread to update the state variable?

Anything is possible, since unfortunately neither of us can read the internal implementation of state to understand how it might behave in various situations. :wink:

You could also try experimenting with this setting to see if it makes any difference for better or worse: [2.2.9] singleThreaded option for apps/drivers

It was my impression in the past that state is only sampled once at the "top" of a method call and isn't updated continuously throughout, so maybe you're seeing something of that sort. It would be interesting to see if the singleThreaded setting makes any difference. If not, you may have to refactor your method to make individual method calls to do the checking while you're waiting so that you get an updated sample of state. This may be as simple as creating a sub-function to check state instead of doing it directly in the calling method.

Thanks for all the help so far, it looks like state variables will not work here. I can get round most of the issues but I think I still need the calling function to wait for a "Success" signal from the callback and if it doesn't get it within 5 seconds resend the command. Sounds like I should use the following for that:

Agree?

That may be overkill. You could still do something like this which causes state to be resampled each time it is checked. Here's a demonstration. Note that I had to select the singleThreaded option for it to be reliable.

def trace()
{
    // always clear the variable first
    setDoneVar(false)
    
    //
    // TODO: send your command to the device here
    //
    
    // the actual wait for completion happens here    
    checkDone(4)    
    
    // this completion would actually happen later in parse() for a real response
    runIn(2, simulateComplete)
}

def setDoneVar(val)
{
    state.done = val   
}

def checkDoneVar()
{
    return state.done
}

def simulateComplete()
{
    setDoneVar(true)
}

def checkDone(timeoutSecs)
{
    log.debug "${timeoutSecs} time remaining"
    if(!timeoutSecs || checkDoneVar())
    {
        log.debug "state.done = ${state.done}"
        return
    }
    
    runIn(1, checkDone, [data: --timeoutSecs])
}

@tomw - Many thanks - I have been playing with this code snippet to make sure I understand it!

One of your comments in trace() says "// the actual wait for completion happens here" but I don't think trace does actually wait there. It runs through to completion in ~100msec, leaving checkDone() running and presumably the hub OS to fire off simulateComplete() after two seconds.

Doesn't that mean this code is running multi-threaded and so we still have the same problem of how we communicate success back to the method that sends the command, trace() in this example?

Yes, good point. This synchronizes the calling thread with the success in a separate thread, but it doesn't tell you whether it was a success or a timeout. so it isn't the complete solution to your problem.

I'll have to give it some more thought, but maybe others have ideas on how to pass data back to the caller from a callback like parse().

One other thing to try that may help with behaviors around state would be to create getter/setter methods instead of interacting with it directly.

You could try something like this with your original approach


def modifyState(name, value)
{
   state.putAt(name, value)
}

def readState(name)
{
   return state.getAt(name)
}

@tomw: Many thanks for sticking with me!

I swapped out the direct references to state.variable and replaced them with the method calls in your last reply and set the singleThreaded flag to true. I think the single threading forces parse() to run in the same thread as the command sending method. Consequently I see the command sent, this method then waits for the state variable to change (which it doesn't) before the method times out. Then parse() fires and changes the state variable. Bottom line is I don't think you can run this driver as single threaded unless you can force parse() to run in its own thread.

Without the single threaded flag set it behaves as before, parse() fires at the correct time, sets the state variable but the sending method does not see the change.

Could I use another "high level" variable (attribute, hub variable) or a semaphore?

I think we could use some advice from @bravenel or someone else in known from the Hubitat team.

What is the recommended structure and method for sending data from an async callback like parse() back to a synchronous device caller that wants to wait for a response from the device?

Sorry haven’t fully digested this but I think I had similar difficulty in the MQTT app.

I think previous responses mentioned this and it avoids state variables.

If you have a parent app and also a device driver that contains the parse method then you can recover the returned value within parse (and test it if needed) and then return your value instantly to the parent app’s recoveredData method which you include in the app. You can additional parameters too if wanted

parent.recoveredData (value)

If the expected value doesn’t arrive in the expected timeout you can re-call the driver method from the app to try again

This will avoid the use of a state variable which I also found to not be updated quite as I expected and atomicState can’t be used in drivers.

My data was arriving totally asynchronously from the MQTT broker and wasn’t usually related to the last data I sent so this allowed me to test from where the payloads were coming from and include additional parameters for the topic and also choose which app method handled the particular data returned.

1 Like

@kevin Many thanks for joining the conversation. Let me briefly give you a few more details.

I am writing a driver to communicate with a Carrier thermostat. I use a Carrier System Access Module that works on a very simple RS232 ASCII send command/receive response protocol. The hub driver talks to the RS232 port through a telnet server. The commands are sent through a hubAction method and, if successful, the parse() method fires about 2 seconds later.

The problem is how to get the "success" state back from parse() to the sending method - no response or a bad response after say, 5 seconds, means you should resend the command.

It looks like the recoveredData method does this for an parent app/child driver. Could it be adapted for communication between two methods (ie two threads?) in the same driver?

You could just drop parent. from the method call. recoveredData was just an example method name I used, it is nothing special. You could maybe even call your same sending routine with the returned value or with a ‘retry’ or ‘timeout’ value. Test this value and decide how to progress. Trying to keep state variables in synch (or in scope) between methods just didn’t work for me but the method call does pass the correct value.

I think I also experimented with setting the device drivers attributes using sendEvent and that was more successful (consistent) than state.

There’s bound to be a more elegant solution, my approach as a Groovy newbie was just to find a solution that worked consistently for me and then go with that.

@kevin First the disclaimer - I am very much a groovy newbie!

I think you are saying there is a method to get a return value from parse()? Is there any documentation on how I call this method?

Sorry I should explain better…

Within the parse method you get the value returned via Telnet. . Then you insert within parse a method call to another method say dataReturned (value) passing the value as a parameter. Now you create your method dataReturned(value) and within that method you will have access to the value to do with as you wish. It isn’t going via a state variable.

You could also handle this entirely within the parse method, and in my MQTT app I do some validation within parse before deciding to call a subsequent method.

parse (String value) {
   If (value *is as expected*) {
      dataReturned(value)
   else
    ……
    }
}

dataReturned (String value) {
    Your code here .. can use variable `value` as you wish
}

This creates a method call to dataReturned (your created method) in which value is available to handle further as you wish.

Ah, I get it! Will try that later and report back.