State variable not storing object

@slagle, @jody.albritton when trying to store an object in the state variable, it ends up being cleared after the function exits.

e…g

def modeChangeHandler(evt) {
            state.queuedActions = [[event:evt, time:(now() + (1 * 60 * 1000) as Long)]]
            log.trace "Queued actions: $state.queuedActions" // DEBUG
}

modeChangeHandler is a subscription to mode. When called it the live log correctly shows (note it stores the event object):

Queued actions: [[event:physicalgraph.app.EventWrapper@12a537be, time:1469894923933]]

There is a another scheduled function heartBeat which is called every 5 minutes:

def heartBeat() {
	log.trace "Heartbeat called, pending actions: $state.queuedActions"
}

When heartBeat is called it shows:

Heartbeat called, pending actions:

If I use:

state.queuedActions = [[time:(now() + (1 * 60 * 1000) as Long)]]

instead of:

state.queuedActions = [[event:evt, time:(now() + (1 * 60 * 1000) as Long)]]

it works fine in both functions.

Is this by design for state to not store objects or is there an issue I should report?

I am seeing the same behavior. I’ve spent all morning trying to figure out what I am doing wrong… “doNotifications” works fine, “doDelayedNotifications” fails when “delayNotification” == true. I should also note that I am seeing very strange logging results. As in my log messages are showing out of order, some by 10-20 seconds.

—Leaving my on the fly code here, the corrected copied and pasted code is in post 4 below.—

input "speech", "capability.speechSynthesis", title: "Which?", required: false, multiple: true
input "delayNotification", "bool", title: "Delay Notification?", required: false, defaultValue: false

def sendNotification() {
	if(delayNotification) {
		state.notificationMessage = evt.descriptionText
		runIn(60, doDelayedNotifications)
	} else {
		doNotifications(evt.descriptionText)
	}
}

def doNotifications(phrase) {
	speech?.speak(phrase)
}

def doDelayedNotifications() {
	speech?.speak(state.notificationMessage)
}

That’s because your definition of the function is wrong, where’s evt defined? It should be

def sendNotification(evt)

The issues I was talking about is storing evt in the state variable in a mapped array

Sorry, I should have just copied an pasted but I wasn’t by my computer at the time. This is the actual method…

def motionHandler(evt) {        
    if (!isDuplicateCommand(state.lastEvent, ignoreFrequentEventsDuration)) {
        state.lastEvent = new Date().time    
        if(delayNotification) {
	   state.notificationMessage = evt.descriptionText
	   runIn(60, doDelayedNotifications)
        } else {
	   doNotifications(evt.descriptionText)
        }
    } else {
    	log("Frequent Event: Ignoring", "DEBUG")
    }
}

def doNotifications(phrase) {
	speech?.speak(phrase)
}

def doDelayedNotifications() {
	speech?.speak(state.notificationMessage)
}

state is a Map, and, yes, you should be able to store Maps in a Map (and, I’m quite certain we could and … hopefully … still can).

###A few random recollections (without me bothering to find an actual use-case example):

  1. atomicState works differently than state in some situations, and, I think nested Map storage may be such a case.

  2. Because your case for state is Map inside Map, the syntax may have some quirks.

  3. I seem to recall a very recent Platform release notes which mention a change to Map / Array storage in state and/or atomicState – be sure to read through all the recent release notes!

EventWrapper, DeviceWrapper, just a few examples of non-serializable objects. You cannot save them to state, you will get a silent error and abnormal SmartApp exit. Make your own Map object and copy the properties you need from evt.

4 Likes

Map inside Map works for State variable, using it in my first post. @whoismoses you sure that evt.descriptionText isn’t blank or null?

Thanks @ady624 that explains it possibly, but the confusing part is that you can see the object in the state variable while within the context of the function, but it disappears after it exits.

1 Like

I was printing it to the before so I know it was not null. Plus when not using the runIn() it works just fine.

You guys mind answering another state question for me? I recently turned on of my apps in the a parent / child app versus installing it multiple times. I use state just a few time to store values, I try to only use it when required. Is “state” sandbox per child app or is it shared?

Okay, the way state and atomicState work:

At app start, state is read from DB
During app execution, when state is read, the data is read from the state variable, no DB read
During app execution, when atomicState is read, the data is read from the DB synchronously
During app execution, when state is written, the data is written to the state variable, no DB write, flag is set for state usage
During app execution, when atomicState is written, the data is written to the DB synchronously, state is not written and becomes out of sync at this time
At app end, state is written to DB IF the flag is set during any state write/set <<< THIS IS WHERE THE APP FAILS

If you try to save an object into state, it will! Problem arises at end of app, when the state is serialized and saved to the DB. This is where to error occurs, state is not saved, app ends in silent error - no error whatsoever is exposed to the developer, whatever things app did may not actually execute, YMMV. I determined this empirically.

NOTE: Documentation says to not use both state and atomicState. I am. If you happen to use both state and atomicState, state may be written at the end of the app run, overwriting whatever you wrote during the app using atomicState. A workaround that works 99% of the time is: if you use atomicState to set an object, make sure you do this right before the app ends: state.object = atomicState.object <<< this will update the state to the value of atomicState allowing you to use both - may still break, since the read/write is not “atomic”. I do this to avoid using atomicState for everything - seems rather unprofessional and a resource hog.

@Jim can you please confirm this is the way it works? As I said, I figured this out empirically. If so, please update documentation to explain EventWrapper, DeviceWrapper, etc cannot be used with state. App dies silently leaving the developer confused. @slagle, @Aaron, @vlad, any input please?

6 Likes

Get @Jim put this in the official Documentation, please? Would be super info for every developer…

(And thanks for this, @ady624!).

3 Likes

Super complex topic which no one from ST cared to verify the intended behavior. I’ve posted lots of results on this.

  1. For parent child apps, state variable isn’t shared between them.
  2. When a child calls a parent function, the parent’s state variable isn’t a global context so it can’t be accessed from the function in the context of a child call. However the atomicState variable is a global context within the parent and be accessed by the parent function in the context of a child calling it.
1 Like

Each app instance has its own state, 100k limit. Parent gets a state, child gets a state. Each child has its own state. You can create a def function(param) { return state.field } in the parent app and then use parent.function(field) in the child to retrieve data from the parent state.

EDIT: Maybe @RBoy is right and that only works with atomicState? Not sure.

This is more or less correct. When the app is done executing, it will write the entire contents of state into persistent storage if it has been modified in any way (it compares the original state before execution with state after). One other thing to note is that both atomicState and state use the same underlying data store.

Any object that can be serialized to JSON (and within the size limitation) can be stored in state. The domain objects like EventWrapper, DeviceWrapper, etc., don’t declare this behavior in any way (e.g., through an interface), so what is serializable and not serializable for objects is not always clear, and of course can get tricky as the object graph gets more complex. I’ll look into this more, but I do wonder if trying groovy.json.JsonOutput.toJson(object) would catch this sort of error as a way of testing it. I’m not able to try it now, but would be interesting if that failed with an event object for example.

And as noted, exception handling with state can be tricky - because the data is written after execution, there’s no exception that can be caught! Even things like the state storage size limitation, which does through an exception, cannot be handled in the SmartApp for this reason.

Regarding using atomicState and state in the same app, as mentioned, it’s definitely not recommended. Writing back to state from atomicState may alleviate some issues, but in general using both is a pattern that is prone to inconsistency and potential data loss, which is why we recommend against it. (I’d advocate for simply not allowing both to be used and causing a compile-time error, rather than allow for such odd and error-prone behavior. Just my opinion.)

I have plans to update the state docs with some of this information, and working with engineering to see how we can make these APIs easier to reason about and more consistent. Just because I know it will be asked, the plan would be to begin looking at docs updates around this the week after next (so the 15th).

Good questions!

4 Likes

Also worth pulling into the state docs is that sharing state between parent child apps is not supported (http://docs.smartthings.com/en/latest/smartapp-developers-guide/parent-child-smartapps.html#tips-best-practices) but @ady624 pattern seems like it would work.

1 Like

Thank guys. In this case I don’t want the state shared, I was just wondering if “sharing” would have been causing the weird behavior I am seeing. Thanks for the info.

BTW guys, the issue I was having earlier was related to the Generic Media Renderer / DNLA Player… It seems I have exceeded some limit. My guess if it was everyone I would see more complaints. Any ideas? I didn’t do anything to configure this with my account. I’d gladly do so, I don’t think it is mean to.

4:23:07 PM: error org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 254; The reference to entity "client" must end with the ';' delimiter.
4:23:05 PM: error org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 154; The reference to entity "client" must end with the ';' delimiter.
4:23:04 PM: error com.amazonaws.AmazonServiceException: Account is disabled. Limit reached. (Service: tts; Status Code: 403; Error Code: null; Request ID: 6b4f296f-5693-11e6-843a-8352f6f45205)

Thank you @Jim for the clarifications. Any chance we could at least get an error in the logs if state failed to be saved? There is absolutely no warning as it is now. Thank you :wink:

1 Like

This is not a good idea in the current state @Jim. See my other thread on state and atomicState issues. State variable is not accessible to the SmartApp when the method is called through a child app, only atomicState is accessible and atomicState does not support using maps within maps. So there is no option but to use a combination of both for parent child apps. If the state variable is made “global” within the context of the SmartApp functions, then you can do this.

Yeah, silent failures are no good. I’ll create a ticket to fix this; do you have any tickets you created that I can reference?[quote=“RBoy, post:19, topic:53940”]
See my other thread on state and atomicState issues.
[/quote]

Can you link to that thread? I couldn’t find it in a quick search.[quote=“RBoy, post:19, topic:53940”]
State variable is not accessible to the SmartApp when the method is called through a child app, only atomicState is accessible
[/quote]

Perhaps covered in the other thread, but if I could see and test a simple example illustrating this it would be helpful. I haven’t tested it much since sharing state between child/parent isn’t supported, but my tests actually show the opposite :open_mouth:

You can do this, but it’s not very intuitive and is different than state:

atomicState.myMap = [key1: [nested: "nested value"]]
log.debug "atomicState: $atomicState"

// assign collection to local variable and update
def temp = atomicState.myMap
// update existing entry
temp.key1.nested = "UPDATED"

// assign collection back to atomicState
atomicState.myMap = temp
log.debug "atomicState: $atomicState"
2 Likes