Populate payload array with state data from multiple like devices

I have one gateway device (Controller) and multiple peripheral devices (Area1, Area2, etc., and Scene1, Scene2, …). The Controller device has 3 attributes - name, nAreas, and nScenes. Each Area peripheral device has 2 attributes - “name” and “level”. And each scene peripheral device has one attribute - name.

The device state for all the above is being set via the controller device running on my Raspberry Pi.

Using workflow, I am trying to create a payload that organizes the latest state information for all of these device in some reasonable manner - preferably using arrays for areas and scenes. Something like this for example.

{ “systemName” : “Lake House”, “nAreas” : 4, “nScenes” : 4,
“area”: [
{“name”:“Living Room”,“level”:25},
{“name”:“Master Bedroom”,“level”:75},
{“name”:“Kitchen”,“level”:100},
{“name”:“Hall”,“level”:0}
],
“scene”:[
{“name”:“Good Morning”},
{“name”:“Good Night”},
{“name”:“Home”},
{“name”: “Away”}
]}

I have been trying to brute force it using Losant API (Get Device State) and Guage Query calls with no luck. Has anyone discovered an elegant way (or at least A way) to do this?

I’d probably use a combination of gauge queries to get the data and a function node to arrange it. You’d put the 8 gauge queries in a series and put the result back on the payload at something like “data.area1”, “data.area2”, etc. Then you’d have a function node the looks something like:

payload.data.reasonable = {
  "systemName" : "Lake House", "nAreas" : 4, "nScenes": 4,
  "area": [
    { "name" : "Living Room", "level" : payload.data.area1.value },
    { "name" : "Master Bedroom", "level" payload.data.area2.value },
    ...
  ],
  "scene" : [
    { "name" : payload.data.scene1.value },
    ...
  ]
}

The scenes would look essentially the same.

Thanks Brandon, Very helpful.

The function below seems to work, but I can find the return function that will allow this to replace the current payload. As it works now, the “reasonable” object is added to the current payload, which of course, is a duplication of data. I think the documentation on how to replace the payload is little unclear.

It would also be nice to incorporate the loading of the area[] and scene[] arrays using a for loop based on nAreas and nScenes, respectfully. Right not my workflow is hard coded to 4 areas and 4 scenes, but in reality I don’t know how many there are until the values are updated by the Javascript running on my Raspberry Pi in the controller device.

payload.data.reasonable = {
“systemName” : payload.data.controller.name.value,
“nAreas” : payload.data.controller.nAreas.value,
“nScenes”: payload.data.controller.nScenes.value,
“area”: [
{ “name” : payload.data.area1.name.value, “level” : payload.data.area1.level },
{ “name” : payload.data.area2.name.value, “level” : payload.data.area2.level },
{ “name” : payload.data.area3.name.value, “level” : payload.data.area3.level },
{ “name” : payload.data.area4.name.value, “level” : payload.data.area4.level }
],
“scene” : [
{ “name” : payload.data.scene1.name.value },
{ “name” : payload.data.scene2.name.value },
{ “name” : payload.data.scene3.name.value },
{ “name” : payload.data.scene4.name.value }
]
}

You can replace the current payload by simply returning a new object from the function. For example:

var reasonable = {
  // Populate it here.
}

return reasonable;

The payload variable is automatically passed in to the function and you can modify it however you want. If you don’t return anything from the function, the payload variable is automatically returned with any modifications. If you return something else, it will completely replace the payload.

I agree on the loops. This example will get challenging if you don’t know the number of areas and scenes up front.

If you’re always using the most recent state data for this object, you could look into use workflow storage as another solution. I’ve successfully used this when combining lots of data into a single object. When a device reports state, you can grab an object from storage, update a property on the object for the specific area or scene, then push the object back into storage. This means the object in storage is always kept up-to-date with all the most recent data from all devices. Then when you want to build this result, you can simply grab the object and iterate over it in a function node to get the final result. You might even be able to keep and maintain the exact return object in storage, which may be even easier.