Part 2: Creating custom devices in Octoblu
Part 3: Setting the state of an Octoblu device from a flow
Part 4: Listening to and acting on device state change in Octoblu
Part 5: Breaking values into new keys with a function node
Part 6: Reformatting nested JSON with JavaScript
Part 7: Logical data nesting with your Octoblu state device
Okay, here is the big post that I have spent an entire week working up to.
I have to admit, I don't write code every day and am self taught JavaScript (along with Python, PowerShell, and batch) so this took me a while to work through.
From my last post, my incoming message looks this this:
{
"msg": {
"rooms": {
"Redmond": {
"lunch": {
"motion": {
"name": "Redmond_lunch_motion",
"mapTitle": "Redmond",
"room": "lunch",
"device": "motion",
},
"refrigerator": {
"name": "Redmond_lunch_refrigerator",
"mapTitle": "Redmond",
"room": "lunch",
"device": "refrigerator",
},
"door": {
"name": "Redmond_lunch_door",
"mapTitle": "Redmond",
"room": "lunch",
"device": "door",
}
}
}
},
"fromUuid": "d5b77d9b-aaf3-f089a7096ee0"
},
"node": "b5149300-9cbd-1f1b56e5d7bb"
}
There can be a variable number of devices per room, and a variable number of rooms per map, and a variable number of maps. My nesting pattern above is
rooms.map.room.devices
Now for the hard part.
I want to evaluate the differences between values of different devices, per room.
This ends up being a lesson in how values are referenced in arrays in JavaScript.
Before I move forward, I have abbreviated the above JSON to spare you scrolling. There are additional fields, and these additional fields contain Date objects that I am interested in. And these Dates are formatted as odd numbers which are actually Epoch Time.
So, to give you the full treatment, here is a real message:
{
"msg": {
"rooms": {
"Redmond": {
"lunch": {
"motion": {
"name": "Redmond_lunch_motion",
"mapTitle": "Redmond",
"room": "lunch",
"device": "motion",
"motion": false,
"motion_updated_at": 1485539337.480442,
"battery": 1,
"battery_updated_at": 1485539337.480442,
"tamper_detected": null,
"tamper_detected_updated_at": null,
"temperature": 21.666666666666668,
"temperature_updated_at": 1485539337.480442,
"motion_true": "N/A",
"motion_true_updated_at": 1485539125.483463,
"tamper_detected_true": null,
"tamper_detected_true_updated_at": null,
"connection": true,
"connection_updated_at": 1485539337.480442,
"agent_session_id": null,
"agent_session_id_updated_at": null,
"connection_changed_at": 1485175984.3230183,
"motion_changed_at": 1485539337.480442,
"motion_true_changed_at": 1485539125.483463,
"temperature_changed_at": 1485529054.5705206
},
"refrigerator": {
"name": "Redmond_lunch_refrigerator",
"mapTitle": "Redmond",
"room": "lunch",
"device": "refrigerator",
"opened": false,
"opened_updated_at": 1485539969.6240845,
"tamper_detected": false,
"tamper_detected_updated_at": 1476739884.682764,
"battery": 1,
"battery_updated_at": 1485539969.6240845,
"tamper_detected_true": "N/A",
"tamper_detected_true_updated_at": 1476739866.2962902,
"connection": true,
"connection_updated_at": 1485539969.6240845,
"agent_session_id": null,
"agent_session_id_updated_at": null,
"opened_changed_at": 1485539969.6240845
},
"door": {
"name": "Redmond_lunch_door",
"mapTitle": "Redmond",
"room": "lunch",
"device": "door",
"opened": false,
"opened_updated_at": 1485538007.9089093,
"tamper_detected": null,
"tamper_detected_updated_at": null,
"battery": 1,
"battery_updated_at": 1485538007.9089093,
"tamper_detected_true": null,
"tamper_detected_true_updated_at": null,
"connection": true,
"connection_updated_at": 1485538007.9089093,
"agent_session_id": null,
"agent_session_id_updated_at": null,
"opened_changed_at": 1485538007.9089093
}
}
}
},
"fromUuid": "d5b77d9b-aaf3-f089a7096ee0"
},
"node": "b5149300-9cbd-1f1b56e5d7bb"
}
Now, the output I am looking for is to take some of these sensor Date values and evaluate them between each of the three devices. Such as: door-refrigerator, motion-door, motion-refrigerator and so on.
If these values were in the same part of the message, it would be really easy. I could simply dot reverence the values and do the math.
But they are not. Each sensor is in its own document, in an array.
Now, if you recall a few posts back, I have a naming convention and I am standardizing three of the names: "door", "refrigerator", and "motion". Those I am not allowing to change. But the room can and the map can.
Recall, I began this exercise this with just an array of devices with values. Processed them to group by a logical naming pattern, saved that to an Octoblu state device, and now I am further processing that into my actionable data which I can easily handle with Octoblu filters to handle alerting or whatever I want to do.
So, to get you to read to the end and not just steal my code here is the output that I am producing, per room.
This gives me a nice single document per room as output - I can pass that to a demultiplex node to break the rot array apart and evaluate each document.
My output looks like this:
{
"msg": [
{
"motion": "motion",
"motionAt": 1485544607.3195794,
"motionAtHuman": "2017-01-27T19:16:47.319Z",
"mapTitle": "Redmond",
"room": "lunch",
"refrigerator": "refrigerator",
"fridgeOpenedAt": 1485539969.6240845,
"fridgeOpenedAtHuman": "2017-01-27T17:59:29.624Z",
"door": "door",
"doorOpenedAt": 1485538007.9089093,
"doorOpenedAtHuman": "2017-01-27T17:26:47.908Z",
"diffDoorsOpenedMinutes": 32,
"diffDoorMotionMinutes": 109,
"diffRefrigeratorMotionMinutes": 77,
"sinceDoorOpenMinutes": 115,
"sinceRefrigeratorOpenMinutes": 82,
"sinceMotionMinutes": 5
}
],
"node": "98cb8680-a264-1b8483214e06"
}
Now, to end this long, long story the JavaScript is below.
What I tried to do was have an intuitive way to read the code and reference each level of the document arrays, so you could understand where you were in the hierarchy.
// array to output
var output = [];
for ( var map in (msg.rooms) ){
for ( var room in msg.rooms[map] ){
var doorOpenedAt;
var fridgeOpenedAt;
var motionAt;
var roomOutput = {};
for ( var sensor in msg.rooms[map][room]){
switch ( msg.rooms[map][room][sensor].device ) {
case "door":
doorOpenedAt = moment.unix(msg.rooms[map][room][sensor].opened_changed_at);
roomOutput.door = msg.rooms[map][room][sensor].device;
roomOutput.doorOpenedAt = msg.rooms[map][room][sensor].opened_changed_at;
roomOutput.doorOpenedAtHuman = doorOpenedAt;
break;
case "refrigerator":
fridgeOpenedAt = moment.unix(msg.rooms[map][room][sensor].opened_changed_at);
roomOutput.refrigerator = msg.rooms[map][room][sensor].device;
roomOutput.fridgeOpenedAt = msg.rooms[map][room][sensor].opened_changed_at;
roomOutput.fridgeOpenedAtHuman = fridgeOpenedAt;
break;
case "motion":
motionAt = moment.unix(msg.rooms[map][room][sensor].motion_true_changed_at);
roomOutput.motion = msg.rooms[map][room][sensor].device;
roomOutput.motionAt = msg.rooms[map][room][sensor].motion_true_changed_at;
roomOutput.motionAtHuman = motionAt;
break;
} // close of switch
roomOutput.mapTitle = msg.rooms[map][room][sensor].mapTitle;
roomOutput.room = msg.rooms[map][room][sensor].room;
} // close of sensor
roomOutput.diffDoorsOpenedMinutes = Math.abs(doorOpenedAt.diff(fridgeOpenedAt, 'minutes')); //removing Math.abs will give a + - if the refrigerator opens and the door does not it will be negative
roomOutput.diffDoorMotionMinutes = Math.abs(doorOpenedAt.diff(motionAt, 'minutes'));
roomOutput.diffRefrigeratorMotionMinutes = Math.abs(fridgeOpenedAt.diff(motionAt, 'minutes'));
roomOutput.sinceDoorOpenMinutes = moment().diff(doorOpenedAt, 'minutes');
roomOutput.sinceRefrigeratorOpenMinutes = moment().diff(fridgeOpenedAt, 'minutes');
roomOutput.sinceMotionMinutes = moment().diff(motionAt, 'minutes');
output.push(roomOutput);
} //close of room
} // close of map
return output;
Lots of leading up to this post. But I like to expand folks' understanding along the way.
And I know we don't all tolerate long articles.
I can thank Tobias Kreidl for even getting me started on this series of posts.
He asked a simple question, and I had a final answer, but I wanted to tell the journey so that he understood how I got to where I did.
That leaves it up to you to take what you need. That's just how I write and respond to questions.
No comments:
Post a Comment