-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
1 addition
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
[{"type":"tab","id":"74fa93c2.8b056c","label":"4901 Occupancy"},{"id":"5192fc22.ae6d04","type":"mqtt-broker","z":"74fa93c2.8b056c","broker":"localhost","port":"1883","clientid":"","usetls":false,"verifyservercert":true,"compatmode":true,"keepalive":"15","cleansession":true,"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":""},{"id":"cbb314fa.344ce8","type":"wemo out","z":"74fa93c2.8b056c","ipaddr":"141.212.11.101","name":"Sconce WeMo","x":1686,"y":389,"wires":[]},{"id":"4735cc91.b8ca34","type":"mqtt in","z":"74fa93c2.8b056c","name":"Projector PowerBlade","topic":"device/PowerBlade/c098e570002e","broker":"5192fc22.ae6d04","x":311,"y":273,"wires":[["141b66e9.ebe499"]]},{"id":"c53fd57.f3ac028","type":"function","z":"74fa93c2.8b056c","name":"Projector Power Threshold","func":"var threshold = 100; // above 100 watts we assume projector is on\n\nvar power = parseFloat(msg.payload.power);\n\nif (power > threshold) {\n msg.payload = true;\n} else {\n msg.payload = false;\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":741,"y":273,"wires":[["cfc59ad3.303a68"]]},{"id":"141b66e9.ebe499","type":"json","z":"74fa93c2.8b056c","name":"","x":505,"y":273,"wires":[["c53fd57.f3ac028"]]},{"id":"8fed47a.f7012b8","type":"rbe","z":"74fa93c2.8b056c","name":"","func":"rbe","gap":"","x":1493,"y":390,"wires":[["cbb314fa.344ce8"]]},{"id":"e18f1c39.1e70e","type":"mqtt in","z":"74fa93c2.8b056c","name":"Squall PIR projector case","topic":"device/Blink/c098e5900089","broker":"5192fc22.ae6d04","x":300,"y":518,"wires":[["1a331115.e5ccef"]]},{"id":"1a331115.e5ccef","type":"json","z":"74fa93c2.8b056c","name":"","x":506,"y":519,"wires":[["71f8e189.8e072"]]},{"id":"71f8e189.8e072","type":"function","z":"74fa93c2.8b056c","name":"get projector case last minute","func":"var out = msg.payload.motion_last_minute;\nmsg.payload = out;\nreturn msg;\n","outputs":1,"noerr":0,"x":739,"y":519,"wires":[["cfc59ad3.303a68"]]},{"id":"cfc59ad3.303a68","type":"function","z":"74fa93c2.8b056c","name":"JOIN","func":"var join = context.join || {};\n\nvar now = Date.now();\n\njoin[msg.topic] = [msg.payload, now];\ncontext.join = join;\n\nreturn {payload: join};","outputs":1,"noerr":0,"x":1061,"y":398,"wires":[["4400afc4.bbff5","cf3e5592.30c1a8","c3f1553e.3c0ea8"]]},{"id":"4400afc4.bbff5","type":"function","z":"74fa93c2.8b056c","name":"Occupancy Detection","func":"var state = context.state || 'UNOCCUPIED';\nvar entered_unsure_timestamp = context.entered_unsure_timestamp || 0;\n\nvar now = Date.now();\n\n// Get some easy to use variables. True == sensed something\nvar p = msg.payload['device/PowerBlade/c098e570002e'] || [false, 0];\nvar dp = msg.payload['device/PowerBlade/c098e5700045'] || [false, 0];\nvar rm = msg.payload['device/Blink/c098e5900089'] || [false, 0];\nvar dm = msg.payload['device/Blink/c098e5900004'] || [false, 0];\n\nvar projector = p[0];\nvar desk_power = dp[0];\nvar room_motion = rm[0];\nvar door_motion = dm[0];\n\nvar projector_time = p[1];\nvar desk_power_time = dp[1];\nvar room_motion_time = rm[1];\nvar door_motion_time = dm[1];\n\n// State machine\nif (state == 'UNOCCUPIED') {\n // On any detection, we move to occupied\n if (projector || room_motion || door_motion || desk_power) {\n state = 'OCCUPIED';\n }\n\n} else if (state == 'OCCUPIED') {\n // We stay here unless we see door. Then we are not sure if\n // everyone just left.\n if (door_motion) {\n state = 'UNSURE';\n entered_unsure_timestamp = now;\n }\n\n} else if (state == 'UNSURE') {\n // If door motion is still here, we still don't know much.\n // We stay in this state because someone must be at least\n // near the room.\n if (door_motion) {\n // stay\n entered_unsure_timestamp = now;\n } else if (!projector && !room_motion && !desk_power) {\n // Nothing is happening. This looks like the room\n // is empty.\n state = 'UNOCCUPIED';\n } else {\n // This is where things get tricky. We need to decide whether\n // the door was because the last person left, or if someone\n // entered.\n // As a heuristic, we need to see something happen in the\n // room 15 seconds after anything happened near the door\n // in order to say there are people in the room. This handles\n // any sensor weirdness and dropped packets.\n var threshold_timestamp = entered_unsure_timestamp + (15*1000);\n if ((projector && projector_time >= threshold_timestamp) ||\n (room_motion && room_motion_time >= threshold_timestamp) ||\n (desk_power && desk_power_time >= threshold_timestamp)) {\n // We saw some indication after the door that the room is still occupied.\n state = 'OCCUPIED';\n }\n }\n}\n\n// Save state\ncontext.state = state;\ncontext.entered_unsure_timestamp = entered_unsure_timestamp;\n\n// Convert to determination of occupancy\nvar occupancy = false;\nif (state == 'OCCUPIED' || state == 'UNSURE') {\n occupancy = true;\n}\n\nreturn [{payload: occupancy}, {payload: state}];\n","outputs":"2","noerr":0,"x":1290,"y":396,"wires":[["8fed47a.f7012b8"],["c55442c1.3aabc"]]},{"id":"7129aeba.8ed65","type":"debug","z":"74fa93c2.8b056c","name":"current state of sensors","active":true,"console":"false","complete":"payload","x":1658,"y":285,"wires":[]},{"id":"a268e62d.5d9718","type":"mqtt in","z":"74fa93c2.8b056c","name":"Squall PIR door","topic":"device/Blink/c098e5900004","broker":"5192fc22.ae6d04","x":291,"y":631,"wires":[["75730ec6.8a8cf"]]},{"id":"75730ec6.8a8cf","type":"json","z":"74fa93c2.8b056c","name":"","x":502,"y":630,"wires":[["1618f10e.e9e70f"]]},{"id":"1618f10e.e9e70f","type":"function","z":"74fa93c2.8b056c","name":"get projector case last minute","func":"var out = msg.payload.motion_last_minute;\nmsg.payload = out;\nreturn msg;\n","outputs":1,"noerr":0,"x":723,"y":630,"wires":[["cfc59ad3.303a68"]]},{"id":"2ea0c713.d15f38","type":"debug","z":"74fa93c2.8b056c","name":"Room state","active":true,"console":"false","complete":"payload","x":1562,"y":521,"wires":[]},{"id":"ced4dec5.312b2","type":"mqtt in","z":"74fa93c2.8b056c","name":"Desk in 4901 PowerBlade","topic":"device/PowerBlade/c098e5700045","broker":"5192fc22.ae6d04","x":301,"y":395,"wires":[["e3eb9f19.1c146"]]},{"id":"e3eb9f19.1c146","type":"json","z":"74fa93c2.8b056c","name":"","x":513,"y":396,"wires":[["69e0ceeb.961f3"]]},{"id":"69e0ceeb.961f3","type":"function","z":"74fa93c2.8b056c","name":"Desk Power Threshold","func":"var threshold = 10; // above 100 watts we assume projector is on\n\nvar power = parseFloat(msg.payload.power);\n\nif (power > threshold) {\n msg.payload = true;\n} else {\n msg.payload = false;\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":740,"y":397,"wires":[["cfc59ad3.303a68"]]},{"id":"c55442c1.3aabc","type":"rbe","z":"74fa93c2.8b056c","name":"","func":"rbe","gap":"","x":1379,"y":535,"wires":[["2ea0c713.d15f38","6c6e782e.939188"]]},{"id":"cf3e5592.30c1a8","type":"function","z":"74fa93c2.8b056c","name":"dedup obj","func":"var last = context.last || {}\n\nvar different = false;\nfor (var key in msg.payload) {\n if (key in last) {\n if (msg.payload[key][0] != last[key][0]) {\n different = true;\n break;\n }\n } else {\n different = true;\n break;\n }\n}\n\ncontext.last = msg.payload;\n\nif (different) {\n return msg;\n}","outputs":1,"noerr":0,"x":1450,"y":285,"wires":[["7129aeba.8ed65"]]},{"id":"ee1a2396.11e5e","type":"http request","z":"74fa93c2.8b056c","name":"","method":"POST","ret":"txt","url":"http://141.212.11.163/status/update?group={{{payload.group}}}&key={{{payload.key}}}&value={{{payload.value}}}","x":1788,"y":579,"wires":[[]]},{"id":"6c6e782e.939188","type":"function","z":"74fa93c2.8b056c","name":"Create status blob","func":"var state = msg.payload;\n\nmsg.payload = {\n group: 'Conference Rooms',\n key: '4901',\n value: state\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":1584,"y":579,"wires":[["ee1a2396.11e5e"]]},{"id":"a20693c9.5df97","type":"comment","z":"74fa93c2.8b056c","name":"Input from MQTT based sensor streams","info":"","x":371,"y":211,"wires":[]},{"id":"ae58869b.51a778","type":"comment","z":"74fa93c2.8b056c","name":"All sensors are converted to booleans","info":"","x":725,"y":677,"wires":[]},{"id":"5d95a570.a26a5c","type":"comment","z":"74fa93c2.8b056c","name":"PowerBlades are thresholded based on power","info":"","x":755,"y":714,"wires":[]},{"id":"c3fd135b.3c02f","type":"comment","z":"74fa93c2.8b056c","name":"Blink (PIR) sensors use the \"motion in last minute field\"","info":"","x":775,"y":746,"wires":[]},{"id":"ecd9a49a.132658","type":"comment","z":"74fa93c2.8b056c","name":"\"JOIN\" synchronizes the data streams","info":"","x":1102,"y":310,"wires":[]},{"id":"85ba62c.f7a45a","type":"comment","z":"74fa93c2.8b056c","name":"Occupancy block is the application state machine","info":"","x":1292,"y":445,"wires":[]},{"id":"a8a7c1f9.57584","type":"comment","z":"74fa93c2.8b056c","name":"A light bulb is lit when the room is occupied","info":"","x":1774,"y":429,"wires":[]},{"id":"98e1206c.671ee","type":"comment","z":"74fa93c2.8b056c","name":"Room occupancy is posted to a generic \"status\" webpage","info":"","x":1680,"y":626,"wires":[]},{"id":"61dd532f.9e22ac","type":"comment","z":"74fa93c2.8b056c","name":"Debugging output","info":"","x":1644,"y":235,"wires":[]},{"id":"c3f1553e.3c0ea8","type":"debug","z":"74fa93c2.8b056c","name":"","active":true,"console":"false","complete":"false","x":1336,"y":199,"wires":[]}] |