Variable Scope

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.

@kevin Not sure how I implement this let me explain. The thermostat capability dictates I must have a method "setCoolingSetpoint(temperature)" where temperature is a number. This method sends the command via telenet to the thermostat and I want it to wait for the reply from parse() before deciding whether to resend the command or terminate.

If I call this method from parse() won't it be a new instance of setCoolingSetpoint rather than the instance that is waiting for a reply? Similarly if I create a dummy method (as per your code snippet) won't that run in the same thread as parse() and so I still need to work out how to get the return value back to the setCoolingSetpoint that is waiting for a reply?

Sorry I missed the intention that setCoolingSetpoint was being implemented synchronously and would therefore await, and require a response. tomw clearly identified that a few posts back.

I assume that one such response could be a timeout and another failure. You imply that timeout would be handled by an automatic retry mechanism. Would people know to expect such returned values when calling setCoolingSetpoint(temperature) and I assume similarly some additional methods you might implement?

I’m my usage everything is asynchronous and I have opted to throw returned data as events using sendEvent so I have no synchronous wait for response type issues.

First I should say I already have a driver running for this device on another system (not in groovy though!) and the majority of failures can be remedied with a commend resend. If after, three retries, I still get a failed response I'll write to the log or see if I can trigger the Notifications app.

I have been thinking about using attributes/events - that will be my task for tomorrow!

Hopefully my last question on this topic!

I will try using an attribute to pass the thermostat response status from the callback (at the moment that is parse) to the command sending method. It looks like I should use sendEvent() from the Driver Object to write the attribute value and currentValue() from the Device Object (with the skipCache option true) to read the attribute value.

Correct?

Problem solved!

Using attributes to carry messages across processes works well.

@tomw: I did try specific callbacks for each command. It would have made the code cleaner but the telnet protocol will pass everything back to parse, custom calbacks are ignored. See hubAction callback not working .

1 Like