RBoy
(www.rboyapps.com - Making SmartThings Easy!)
1
If you run this piece of code in a SmartApp:
def installed() {
def evt = [name:"lock", data:[usedCode:null, type:"keypad"], value:"locked"]
someFunc(evt) // This runs fine
runIn(10, someFunc, [data: evt]) // This will lead to an exception
}
def someFunc(evt) {
def data = evt.data
if ((data?.usedCode != null) && (data?.usedCode >= 0)) {
return true
} else {
return false
}
}
it throws an exception when the someFunc is called from runIn with the parameters
error groovy.lang.GroovyRuntimeException: Cannot compare org.codehaus.groovy.grails.web.json.JSONObject$Null with value ānullā and java.lang.Integer with value ā0ā
According to the docs runIn is supposed to pass a map but apparently itās converting the map to a JSON object which is having trouble with the null object.
@Jim@gausnes - is runIn supposed to pass a JSON object or a map?
tgauchat
(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy)
2
My guess is that it gets serialized to JSON when written to the scheduler; and thatās whatās causing the mismatch.
1 Like
RBoy
(www.rboyapps.com - Making SmartThings Easy!)
3
Now methods are no longer reuseable and one needs to explicitly handle JSONObject.NULL instead of just null.
IMHO since ST says it should be passing a map, they should just convert the JSON object into a map and pass it to the method which will avoid this issue in the first place.
I apologize in advance for even trying to help here because I am not really a programmer but I Brute Force till I find a workaround. I had similar issues trying to pass a map using runIn. Using logs I realized that using the normal method passes different results from runIn:
def installed() {
def evt = [name:"lock", data:[usedCode:null, type:"keypad"], value:"locked"]
someFunc(evt) // This runs fine
runIn(10, someFunc, [data: evt]) // This will lead to an exception
}
def someFunc(evt) {
def data = evt.data
log.debug data
if ((data?.usedCode != null) && (data?.usedCode >= 0)) {
return true
} else {
return false
}
}
the first run returns 5:39:05 PM: debug [usedCode:null, type:keypad]
and the second (from runIn) returns 5:39:15 PM: debug {"usedCode":null,"type":"keypad"}
A real pain in the you know what. With my smartApp I considered breaking the text string up and manually readding to a new mapā¦but nope. Too much trouble for me. Since this was for adding delays to execution in my button controller, I ended up just using device.on([delay:xxx]) instead. None of this is probably helpful to you but I will be looking at this thread to see if someone smarter than me has an actual solution.
1 Like
RBoy
(www.rboyapps.com - Making SmartThings Easy!)
6
Thatās correct, for the second method the platform is converting the map to a JSON Object which is what youāre seeing with { and }, however JSON Objects (with the grails version ST is using) doesnāt check for null the way maps do.
Would be nice of @gausnes or someone from ST looked into it and hopefully patch up the platform to return a map instead of a JSON object, a VERY easy fix for ST to do to convert a JSON to map object, far more difficult to handle it in a reuseable method in the SmartApp/Device Handler.
2 Likes
tgauchat
(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy)
7
An unfortunate side-effect of the new āSmartThings Cloud APIā is that the current / āoldā Groovy-based SmartApp environment is less and less likely to receive improvements. I presume if something is fatally broken it will get the necessary attention, but other than thatā¦, my expectations are low.
Sorry for not responding earlier, I was on vacation and then got sick right after I got back. So Iāve been playing catch up for the last week.
Iām a little torn here, I feel like SmartApp should just be getting a map back and not have to worry about parsing JSON. However we have a number of SmartApps assuming that event.data will be JSON, so making this change would require a significant amount of effort across multiple teams. Iām guessing there are also community SmartApps that would break if we just started to return Maps instead of JSON.
I think to change this functionality we need to add a flag to runIn to allow users to migrate their apps over to using Maps instead of JSON.
1 Like
RBoy
(www.rboyapps.com - Making SmartThings Easy!)
10
Welcome back Luke.
So Iām a little confused here. Why would a SmartApp think that itās returning a JSON because the documentation indicates otherwise ?
You have a doable solution but I think itās adding more complexity to the issue in the long run.
Because it has been returning JSONā¦ I realize this is wrong, but if you followed the documentation currently you wonāt end up with a working SmartApps so I would imagine that some people have worked around it.
I totally agree with not wanting to add complexity, let me chat with some other people at ST and see what we want to do.
Returns:
String - A JSON string representing a map of the additional data (if any) on the Event.
Example: createEvent(name: "myevent", value: "myvalue", data: [key1: "val", key2: 42])
Then in an event handler method, we can get at the data like this:
Yes but we invoke the handler method with an Event, I can see where this would be confusing though.
2 Likes
tgauchat
(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy)
15
Itās not confusing to me at allā¦ with due respect to everyone involved (and the high quality of the documentation in general!); I think it boils down to the Developer Documentation being incomplete or inaccurate for this subject area.
The snip I took from the documentation says explicitly:
A map of data that will be passed to the handler method.
It doesnāt say: "A map of data that will be converted to or encapsulated in an Event object that will be passed to the handler method.
Iāve worked a large software company (Sybase), and believe me, Iām completely empathetic here. I worked in a QA testing group that had a role between the Development team, Documentation team, and the Coding team. Getting them to all agree on what the āspecā was ā¦ was impossible in many cases. It wasnāt anyoneās fault. It was just a sub-optimal workflow.
The ārealā spec and implementation of runIn() is becoming clearā¦ i.e., the use of the Event invocation and Event object, etcā¦ But, unfortunately, external developers had and have to rely on the published Documentation.
And, unfortunately, there is no established remediation process for when the documentation is corrected or the spec+implementation+documentation is modified.
2 Likes
RBoy
(www.rboyapps.com - Making SmartThings Easy!)
16
Right as @tgauchat pointed out the documentation says map all over so the expectation is to have a map returned to the method ā Input should be = output (map in = map out). See my first post, it also makes for more intuitive code to have reusable functions.
Whatever happened to this??
I suspect THIS is what is causing ME great griefā¦ 'cuz the documentation says MAP, so Iām trying, but it doesnāt workā¦ this may have been the clue I was looking forā¦
1 Like
tgauchat
(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy)
18
As explained in the related Topic, the documentation is not accurate.
The parameters are converted into an Event Object, which in turn is serialized into JSON (or ā¦ something like that).
A pure map or Object cannot be passed throughrunIn(), regardless of what data you give it.
parseJson()
Parses the specified string into a JSON data structure.
Signature:
Map parseJson(stringToParse)
Parameters:
String stringToParse - The string to parse into JSON
Returns:
Map - a map that represents the passed-in string in JSON format.
Does it parse Into a JSON format or does it return a MAP!?
tgauchat
(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy)
20
Yes and no.ā¦
You get a representation of the Device, but not the ability to directly access the Device Object. You cannot use this passed in JSON or MAP to access the Deviceās Commands and Attributes. So you can try to use a some key from the map to find your Device in the settings input variable that contained that Device.
You canāt access the Device Object itself is for Security / Isolation reasons. SmartThings only lets you access Attributes and Commands of Devices specified in Preferences. If you could access āanyā of the customerās Devices (even those not authorized in Preferences), then this eliminates the ability of the Customer to limit access to their specifically input Things. Similarly, you canāt obviously must not be able to access Things of other Locations or other Customersā Accounts.
1 Like
RBoy
(www.rboyapps.com - Making SmartThings Easy!)
21
Update: After the recent grails update looks like @gausnes fixed this issue