mirror of
/repos/node-red-data.git
synced 2025-12-30 08:01:32 +01:00
686 lines
32 KiB
JSON
686 lines
32 KiB
JSON
[
|
|
{
|
|
"type": "tab",
|
|
"id": "e3ca4149.1c35c",
|
|
"label": "PI43"
|
|
},
|
|
{
|
|
"type": "tab",
|
|
"id": "5751578d.a8aea8",
|
|
"label": "Domoticz"
|
|
},
|
|
{
|
|
"id": "7b9bfb4b.846404",
|
|
"type": "mqtt-broker",
|
|
"z": "",
|
|
"broker": "mqtt.aiko.sh",
|
|
"port": "1883",
|
|
"clientid": "pi2.aiko.sh",
|
|
"usetls": false,
|
|
"verifyservercert": true,
|
|
"compatmode": true,
|
|
"keepalive": "15",
|
|
"cleansession": true,
|
|
"willTopic": "",
|
|
"willQos": "0",
|
|
"willRetain": "false",
|
|
"willPayload": "",
|
|
"birthTopic": "",
|
|
"birthQos": "0",
|
|
"birthRetain": "false",
|
|
"birthPayload": ""
|
|
},
|
|
{
|
|
"id": "34f9355a.cb06ca",
|
|
"type": "exec",
|
|
"z": "e3ca4149.1c35c",
|
|
"command": "cat",
|
|
"addpay": true,
|
|
"append": "/sys/class/thermal/thermal_zone0/temp",
|
|
"useSpawn": "",
|
|
"name": "temp",
|
|
"x": 327,
|
|
"y": 119,
|
|
"wires": [
|
|
[
|
|
"6a09262a.95f6d8"
|
|
],
|
|
[],
|
|
[]
|
|
]
|
|
},
|
|
{
|
|
"id": "5e02bbe3.a1fd44",
|
|
"type": "inject",
|
|
"z": "e3ca4149.1c35c",
|
|
"name": "",
|
|
"topic": "",
|
|
"payload": "",
|
|
"payloadType": "none",
|
|
"repeat": "60",
|
|
"crontab": "",
|
|
"once": false,
|
|
"x": 146,
|
|
"y": 111,
|
|
"wires": [
|
|
[
|
|
"34f9355a.cb06ca",
|
|
"64325516.9bcdac",
|
|
"596d73cf.a6928c",
|
|
"13f94a14.ec06b6",
|
|
"bd13d8fb.42ec28"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "428ea0cf.bd716",
|
|
"type": "debug",
|
|
"z": "e3ca4149.1c35c",
|
|
"name": "",
|
|
"active": false,
|
|
"console": "false",
|
|
"complete": "payload",
|
|
"x": 848,
|
|
"y": 57,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "6a09262a.95f6d8",
|
|
"type": "function",
|
|
"z": "e3ca4149.1c35c",
|
|
"name": "convert to C",
|
|
"func": "var cpuTemp0=msg.payload.trim();\nvar cpuTemp1=~~(cpuTemp0/1000);\nvar cpuTemp2=~~(cpuTemp0/100);\nvar cpuTempM=cpuTemp2 % cpuTemp1;\n\nmsg.payload={ \n timestamp: Date.now(),\n degrees: cpuTemp1,\n raw: cpuTemp0\n};\n\nreturn msg;",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"x": 550,
|
|
"y": 140,
|
|
"wires": [
|
|
[
|
|
"1a28d1d1.e5d72e"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "596d73cf.a6928c",
|
|
"type": "exec",
|
|
"z": "e3ca4149.1c35c",
|
|
"command": "pm2",
|
|
"addpay": true,
|
|
"append": "jlist",
|
|
"useSpawn": "",
|
|
"name": "",
|
|
"x": 321,
|
|
"y": 238,
|
|
"wires": [
|
|
[
|
|
"7fd702.ff8029"
|
|
],
|
|
[],
|
|
[]
|
|
]
|
|
},
|
|
{
|
|
"id": "7fd702.ff8029",
|
|
"type": "json",
|
|
"z": "e3ca4149.1c35c",
|
|
"name": "",
|
|
"x": 535,
|
|
"y": 238,
|
|
"wires": [
|
|
[
|
|
"f47e2c77.0b81d"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "64325516.9bcdac",
|
|
"type": "exec",
|
|
"z": "e3ca4149.1c35c",
|
|
"command": "cat",
|
|
"addpay": true,
|
|
"append": "/proc/uptime",
|
|
"useSpawn": "",
|
|
"name": "uptime",
|
|
"x": 320,
|
|
"y": 357,
|
|
"wires": [
|
|
[
|
|
"46790ddf.b986f4"
|
|
],
|
|
[],
|
|
[]
|
|
]
|
|
},
|
|
{
|
|
"id": "46790ddf.b986f4",
|
|
"type": "function",
|
|
"z": "e3ca4149.1c35c",
|
|
"name": "convert uptime to json",
|
|
"func": "var values=msg.payload.trim().split(' ');\nvar uptime=parseFloat(values[0]);\nvar idletime=parseFloat(values[1]);\n\nmsg.payload={\n timestamp: Date.now(),\n raw: msg.payload.trim(),\n seconds: uptime,\n idle_total: idletime,\n idle_per_core: idletime / 4\n};\nreturn msg;",
|
|
"outputs": 1,
|
|
"x": 552,
|
|
"y": 352,
|
|
"wires": [
|
|
[
|
|
"45bafe05.ba45"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "45bafe05.ba45",
|
|
"type": "mqtt out",
|
|
"z": "e3ca4149.1c35c",
|
|
"name": "",
|
|
"topic": "pi2/status/uptime",
|
|
"qos": "",
|
|
"retain": "true",
|
|
"broker": "7b9bfb4b.846404",
|
|
"x": 889,
|
|
"y": 353,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "f47e2c77.0b81d",
|
|
"type": "mqtt out",
|
|
"z": "e3ca4149.1c35c",
|
|
"name": "",
|
|
"topic": "pi2/status/pm2",
|
|
"qos": "",
|
|
"retain": "true",
|
|
"broker": "7b9bfb4b.846404",
|
|
"x": 890,
|
|
"y": 234,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "1a28d1d1.e5d72e",
|
|
"type": "mqtt out",
|
|
"z": "e3ca4149.1c35c",
|
|
"name": "",
|
|
"topic": "pi2/sensors/temp/cpu",
|
|
"qos": "",
|
|
"retain": "true",
|
|
"broker": "7b9bfb4b.846404",
|
|
"x": 890,
|
|
"y": 123,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "13f94a14.ec06b6",
|
|
"type": "exec",
|
|
"z": "e3ca4149.1c35c",
|
|
"command": "cat",
|
|
"addpay": true,
|
|
"append": "/proc/loadavg",
|
|
"useSpawn": "",
|
|
"name": "loadavg",
|
|
"x": 320,
|
|
"y": 460,
|
|
"wires": [
|
|
[
|
|
"8a7d6fb9.75829"
|
|
],
|
|
[],
|
|
[]
|
|
]
|
|
},
|
|
{
|
|
"id": "8a7d6fb9.75829",
|
|
"type": "function",
|
|
"z": "e3ca4149.1c35c",
|
|
"name": "convert load to json",
|
|
"func": "var values=msg.payload.trim().split(' ');\nvar load_1min_part=parseFloat(values[0]);\nvar load_5min_part=parseFloat(values[1]);\nvar load_10min_part=parseFloat(values[2]);\n\nmsg.payload={\n timestamp: Date.now(),\n raw: msg.payload.trim(),\n load: {\n one: load_1min_part,\n five: load_5min_part,\n ten: load_10min_part\n }\n};\nreturn msg;",
|
|
"outputs": 1,
|
|
"x": 550,
|
|
"y": 464,
|
|
"wires": [
|
|
[
|
|
"3aa055f6.c55faa"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "3aa055f6.c55faa",
|
|
"type": "mqtt out",
|
|
"z": "e3ca4149.1c35c",
|
|
"name": "",
|
|
"topic": "pi2/status/loadavg",
|
|
"qos": "",
|
|
"retain": "true",
|
|
"broker": "7b9bfb4b.846404",
|
|
"x": 887,
|
|
"y": 462,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "bd13d8fb.42ec28",
|
|
"type": "exec",
|
|
"z": "e3ca4149.1c35c",
|
|
"command": "cat",
|
|
"addpay": true,
|
|
"append": "/proc/meminfo",
|
|
"useSpawn": "",
|
|
"name": "meminfo",
|
|
"x": 314,
|
|
"y": 566,
|
|
"wires": [
|
|
[
|
|
"f714138c.08ebf"
|
|
],
|
|
[],
|
|
[]
|
|
]
|
|
},
|
|
{
|
|
"id": "f714138c.08ebf",
|
|
"type": "function",
|
|
"z": "e3ca4149.1c35c",
|
|
"name": "convert meminfo to json",
|
|
"func": "/*\n$ cat /proc/meminfo\nMemTotal: 947444 kB\nMemFree: 715036 kB\nMemAvailable: 806656 kB\nBuffers: 23904 kB\nCached: 74096 kB\nSwapCached: 0 kB\nActive: 171528 kB\nInactive: 31968 kB\nActive(anon): 105524 kB\nInactive(anon): 796 kB\nActive(file): 66004 kB\nInactive(file): 31172 kB\nUnevictable: 8 kB\nMlocked: 8 kB\nSwapTotal: 0 kB\nSwapFree: 0 kB\nDirty: 4 kB\nWriteback: 0 kB\nAnonPages: 105520 kB\nMapped: 29252 kB\nShmem: 828 kB\nSlab: 15904 kB\nSReclaimable: 8344 kB\nSUnreclaim: 7560 kB\nKernelStack: 1048 kB\nPageTables: 1496 kB\nNFS_Unstable: 0 kB\nBounce: 0 kB\nWritebackTmp: 0 kB\nCommitLimit: 473720 kB\nCommitted_AS: 428368 kB\nVmallocTotal: 1105920 kB\nVmallocUsed: 1156 kB\nVmallocChunk: 885752 kB\n*/\n\n\nvar lines=msg.payload.split('\\n');\nvar mem_total=parseInt(lines[0].split(':')[1].trim().split(' ')[0]);\nvar mem_free=parseInt(lines[1].split(':')[1].trim().split(' ')[0]);\nvar mem_available=parseInt(lines[2].split(':')[1].trim().split(' ')[0]);\nvar buffers=parseInt(lines[3].split(':')[1].trim().split(' ')[0]);\nvar cache=parseInt(lines[4].split(':')[1].trim().split(' ')[0]);\nvar swap_cache=parseInt(lines[5].split(':')[1].trim().split(' ')[0]);\nvar swap_total=parseInt(lines[14].split(':')[1].trim().split(' ')[0]);\nvar swap_free=parseInt(lines[15].split(':')[1].trim().split(' ')[0]);\n\nmsg.payload={\n timestamp: Date.now(),\n raw: msg.payload.trim(),\n mem: {\n total: mem_total,\n free: mem_free,\n available: mem_available,\n buffers: buffers\n },\n cache: {\n cache: cache,\n swap_cache: swap_cache\n },\n swap: {\n total: swap_total,\n free: swap_free\n }\n};\nreturn msg;",
|
|
"outputs": 1,
|
|
"x": 544,
|
|
"y": 570,
|
|
"wires": [
|
|
[
|
|
"28b96119.d7469e"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "28b96119.d7469e",
|
|
"type": "mqtt out",
|
|
"z": "e3ca4149.1c35c",
|
|
"name": "",
|
|
"topic": "pi2/status/meminfo",
|
|
"qos": "",
|
|
"retain": "true",
|
|
"broker": "7b9bfb4b.846404",
|
|
"x": 881,
|
|
"y": 568,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "98897726.677688",
|
|
"type": "mqtt in",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "OwnTracks",
|
|
"topic": "owntracks/user/+",
|
|
"broker": "7b9bfb4b.846404",
|
|
"x": 122,
|
|
"y": 87,
|
|
"wires": [
|
|
[
|
|
"cf3debf5.30c218"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "50ac2f84.af53d",
|
|
"type": "mqtt out",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "-> domoticz",
|
|
"topic": "domoticz/in",
|
|
"qos": "",
|
|
"retain": "",
|
|
"broker": "7b9bfb4b.846404",
|
|
"x": 722,
|
|
"y": 226,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "fc8b685e.037498",
|
|
"type": "debug",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "",
|
|
"active": false,
|
|
"console": "false",
|
|
"complete": "payload",
|
|
"x": 731.5,
|
|
"y": 414,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "b2660ad8.4d99f8",
|
|
"type": "geofence",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "Zwenkgras",
|
|
"mode": "circle",
|
|
"inside": "true",
|
|
"rad": 19.052575362829838,
|
|
"points": [],
|
|
"centre": {
|
|
"latitude": 52.179497356310755,
|
|
"longitude": 4.50451523065567
|
|
},
|
|
"x": 139.5,
|
|
"y": 170,
|
|
"wires": [
|
|
[
|
|
"a3705e81.5c8fa",
|
|
"bee69fe2.41196"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "745cb8b.f8ba348",
|
|
"type": "geofence",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "Haarlemmerstraat",
|
|
"mode": "circle",
|
|
"inside": "true",
|
|
"rad": 6.491419969978727,
|
|
"points": [],
|
|
"centre": {
|
|
"latitude": 52.16045383788138,
|
|
"longitude": 4.490457773208618
|
|
},
|
|
"x": 139,
|
|
"y": 256,
|
|
"wires": [
|
|
[
|
|
"31ba2f6d.ce45d",
|
|
"bee69fe2.41196"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "cf3debf5.30c218",
|
|
"type": "function",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "Convert Owntracks -> geofence",
|
|
"func": "var loc = JSON.parse(msg.payload);\nif (loc.lat && loc.lon) {\n var newmsg = { lat: loc.lat, lon: loc.lon };\n msg.location = newmsg;\n}\nreturn msg;",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"x": 405.5,
|
|
"y": 86,
|
|
"wires": [
|
|
[
|
|
"745cb8b.f8ba348",
|
|
"b2660ad8.4d99f8",
|
|
"f2fed1ae.0d013",
|
|
"40c1d833.bf3e28"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "bee69fe2.41196",
|
|
"type": "debug",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "",
|
|
"active": false,
|
|
"console": "false",
|
|
"complete": "location",
|
|
"x": 409,
|
|
"y": 416,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "31ba2f6d.ce45d",
|
|
"type": "function",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "Map to Aiko@Haarlemmerstraat",
|
|
"func": "var idx = 29;\ndelete msg.topic;\nif (msg.location) {\n var isat = msg.location.isat;\n msg.payload = {\n idx: idx,\n command: \"switchlight\", \n switchcmd: (isat === undefined ? 'Off' : 'On'),\n// level: 100 \n svalue: (isat === undefined ? '0' : '1')\n };\n} else {\n msg.payload = {};\n}\nreturn msg;\n",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"x": 440.5,
|
|
"y": 275,
|
|
"wires": [
|
|
[
|
|
"fc8b685e.037498",
|
|
"50ac2f84.af53d"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "f2fed1ae.0d013",
|
|
"type": "geofence",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "notHaarlemmerstraat",
|
|
"mode": "circle",
|
|
"inside": "false",
|
|
"rad": 6.491419969978727,
|
|
"points": [],
|
|
"centre": {
|
|
"latitude": 52.16045383788138,
|
|
"longitude": 4.490457773208618
|
|
},
|
|
"x": 144,
|
|
"y": 306,
|
|
"wires": [
|
|
[
|
|
"bee69fe2.41196",
|
|
"31ba2f6d.ce45d"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "a3705e81.5c8fa",
|
|
"type": "function",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "Map to Aiko@Zwenkgras",
|
|
"func": "var idx = 30;\ndelete msg.topic;\nif (msg.location) {\n var isat = msg.location.isat;\n msg.payload = {\n idx: idx,\n command: \"switchlight\", \n switchcmd: (isat === undefined ? 'Off' : 'On'),\n// level: 100 \n svalue: (isat === undefined ? '0' : '1')\n };\n} else {\n msg.payload = {};\n}\nreturn msg;\n",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"x": 443,
|
|
"y": 187,
|
|
"wires": [
|
|
[
|
|
"fc8b685e.037498",
|
|
"50ac2f84.af53d"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "40c1d833.bf3e28",
|
|
"type": "geofence",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "notZwenkgras",
|
|
"mode": "circle",
|
|
"inside": "false",
|
|
"rad": 19.052575362829838,
|
|
"points": [],
|
|
"centre": {
|
|
"latitude": 52.179497356310755,
|
|
"longitude": 4.50451523065567
|
|
},
|
|
"x": 139,
|
|
"y": 214,
|
|
"wires": [
|
|
[
|
|
"a3705e81.5c8fa"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "182445e0.3190ea",
|
|
"type": "debug",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "",
|
|
"active": false,
|
|
"console": "false",
|
|
"complete": "true",
|
|
"x": 609.5,
|
|
"y": 840,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "af2676e9.4d2048",
|
|
"type": "mqtt in",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "domoticz ->",
|
|
"topic": "domoticz/out",
|
|
"broker": "7b9bfb4b.846404",
|
|
"x": 121.5,
|
|
"y": 486,
|
|
"wires": [
|
|
[
|
|
"da82479.34da7b8"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "5080268a.606bd8",
|
|
"type": "function",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "advise badkamer fan",
|
|
"func": " /*\n g/m^3 =((0.000002*Temp^4)+(0.0002*Temp^3)+(0.0095*Temp^2)+( 0.337*Temp)+4.9034)*RH\n */\n function calcGM3(Temp, RH) {\n var temp2 = Temp * Temp;\n var temp3 = temp2 * Temp;\n var temp4 = temp2 * temp2;\n return Math.floor((\n (0.000002 * temp4) +\n (0.0002 * temp3) +\n (0.0095 * temp2) +\n ( 0.337 * Temp) +\n 4.9034\n ) * RH);\n\n }\n\n function switchFan(payload, inside, outside) {\n if (inside && outside) {\n if (inside > outside) {\n payload.fanOn = true;\n } else {\n payload.fanOn = false;\n }\n payload.inside = inside;\n payload.outside = outside;\n }\n return payload;\n }\n\n var payload = JSON.parse(msg.payload);\n\n if (payload.idx === 15 || payload.idx === 84) {\n var temp = parseFloat(payload.svalue1);\n var hum = parseFloat(payload.svalue2);\n var gm3 = calcGM3(temp, hum);\n if ((payload.idx === 15 ) && (gm3 !== context.outside)) {\n payload = switchFan({}, context.inside, gm3);\n payload.insideT = context.insideT;\n payload.insideRH = context.insideRH;\n payload.outsiteT = context.outsideT = temp;\n payload.outsideRH = context.outsideRH = hum;\n context.outside = gm3;\n msg.payload = payload;\n return msg;\n }\n\n if ((payload.idx === 84) && (gm3 !== context.inside)) {\n payload = switchFan({}, gm3, context.outside);\n payload.outsiteT = context.outsideT;\n payload.outsideRH = context.outsideRH;\n payload.insideT = context.insideT = temp;\n payload.insideRH = context.insideRH = hum;\n context.inside = gm3;\n msg.payload = payload;\n return msg;\n }\n }\n return null;\n",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"x": 401.5,
|
|
"y": 618,
|
|
"wires": [
|
|
[
|
|
"182445e0.3190ea"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "da82479.34da7b8",
|
|
"type": "function",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "filter badkamer info",
|
|
"func": "var payload = JSON.parse(msg.payload);\n\nif (payload.idx === 15 || \n payload.idx === 84 ||\n payload.idx === 48\n) {\n return msg;\n}\nreturn null;",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"x": 167,
|
|
"y": 618,
|
|
"wires": [
|
|
[
|
|
"5080268a.606bd8",
|
|
"182445e0.3190ea",
|
|
"7aaeeb3f.855114"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "2cbff763.27d548",
|
|
"type": "function",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "Switch Fan",
|
|
"func": "var idx = 48;\ndelete msg.topic;\nvar fanOn = msg.payload.fanOn;\nmsg.payload = {\n idx: idx,\n command: \"switchlight\", \n switchcmd: (fanOn ? 'On' : 'Off'),\n// level: 100 \n svalue: (fanOn ? '1' : '0')\n};\nreturn msg;\n",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"x": 564,
|
|
"y": 498,
|
|
"wires": [
|
|
[
|
|
"182445e0.3190ea",
|
|
"50ac2f84.af53d"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "8509eb15.7af618",
|
|
"type": "inject",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "",
|
|
"topic": "",
|
|
"payload": "",
|
|
"payloadType": "date",
|
|
"repeat": "",
|
|
"crontab": "",
|
|
"once": false,
|
|
"x": 99.5,
|
|
"y": 843,
|
|
"wires": [
|
|
[
|
|
"c914d39a.36eb3"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "c914d39a.36eb3",
|
|
"type": "function",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "",
|
|
"func": "var _ = global.get('_');\nmsg.payload = _.first(['a','b']);\nreturn msg;",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"x": 311.5,
|
|
"y": 838,
|
|
"wires": [
|
|
[
|
|
"182445e0.3190ea"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "7aaeeb3f.855114",
|
|
"type": "function",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "advanced advise bathroom fan ",
|
|
"func": "var payload = JSON.parse(msg.payload);\npayload.log = '';\nvar _ = global.get('lodash');\n// some constants\n// % rise in humidity that will trigger the fan\nvar fanDeltaTrigger = 5;\n// maximum amount of minutes the fan should be on,\n// in case we never reach the target humidity\nvar fanMaxMinutesOn = 45;\n// ventilator turns off when humidity within 2% of target.\nvar humTargetOffset = 2;\nvar gm3TargetOffset = 200;\n// interval to determine lowest humidity over\nvar recentMinutes = 10;\n// never ventilate below the hard minimum.\nvar hardMinimumHumidity = 40;\n// keep bathroom humidity history\nvar nrHistoryToKeep = 10;\n\nvar outsideSensor = 15;\nvar insideSensor = 84;\nvar sensors = [insideSensor, outsideSensor];\nvar manualSwitch = 48;\nvar switches = [manualSwitch];\n\nvar minutes = 60 * 1000;\nvar fanMaxMinutesOnInMilliseconds = fanMaxMinutesOn * minutes;\nvar domotIdx = payload.idx;\nvar now = Date.now();\n\nfunction log(str) {\n\tpayload.log += str + '. ';\n}\n\n//noinspection JSUnresolvedVariable\n//node.warn('msg.topic === ' + msg.topic);\nif (msg.topic === 'node-red/fan') {\n\tif (!context.lastKnownFanState) {\n\t\t_.forEach(payload.context, function (value, key) {\n\t\t\tnode.warn('node-red/fan context: ' + key + ' = ' + JSON.stringify(value));\n\t\t\tcontext[key] = value;\n\t\t});\n\t\t//noinspection JSUnresolvedVariable\n\t\t// \tnode.warn('node-red/fan context reset');\n\t\t// } else {\n\t\t// \t//noinspection JSUnresolvedVariable\n\t\t// \tnode.warn('node-red/fan context skipped');\n\t}\n\t//noinspection JSAnnotator\n\treturn [null, null];\n}\n\n\nvar bathroomHistory = [];\nif (context.bathroomHistory) {\n\tbathroomHistory = context.bathroomHistory;\n} else {\n\tcontext.bathroomHistory = [];\n}\nvar bathroomHistoryLength = bathroomHistory.length;\nvar lastBathroomHistory = bathroomHistory[bathroomHistoryLength - 1] || {gm3: 10000};\n\nvar targetHumidity = hardMinimumHumidity;\nif (context.targetHumidity) {\n\ttargetHumidity = context.targetHumidity;\n} else {\n\tcontext.targetHumidity = hardMinimumHumidity;\n}\nvar lastKnownOutsideData = null;\nif (context.lastKnownOutsideData) {\n\tlastKnownOutsideData = context.lastKnownOutsideData;\n} else {\n\tcontext.lastKnownOutsideData = null;\n}\nvar lastKnownFanState = {on: false, ts: 0};\nif (context.lastKnownFanState) {\n\tlastKnownFanState = context.lastKnownFanState\n} else {\n\tcontext.lastKnownFanState = {on: false, ts: 0};\n}\nvar fanIsTryingToRestoreHumidity = false;\nif (context.fanIsTryingToRestoreHumidity) {\n\tfanIsTryingToRestoreHumidity = context.fanIsTryingToRestoreHumidity\n} else {\n\tcontext.fanIsTryingToRestoreHumidity = false;\n}\n\nvar riseInHumidity = false;\n\n/*\n g/m^3 =((0.000002*Temp^4)+(0.0002*Temp^3)+(0.0095*Temp^2)+( 0.337*Temp)+4.9034)*RH\n */\nfunction calcGM3(Temp, RH) {\n\tvar temp2 = Temp * Temp;\n\tvar temp3 = temp2 * Temp;\n\tvar temp4 = temp2 * temp2;\n\treturn Math.floor((\n\t\t\t(0.000002 * temp4) +\n\t\t\t(0.0002 * temp3) +\n\t\t\t(0.0095 * temp2) +\n\t\t\t(0.337 * Temp) +\n\t\t\t4.9034\n\t\t) * RH);\n\n}\n\nvar sensorData = {};\nif (_.indexOf(sensors, domotIdx) > -1) {\n\tsensorData.ts = now;\n\tsensorData.temp = parseFloat(payload.svalue1);\n\tsensorData.hum = parseFloat(payload.svalue2);\n\tsensorData.gm3 = calcGM3(sensorData.temp, sensorData.hum);\n}\nvar switchData = {};\nif (_.indexOf(switches, domotIdx) > -1) {\n\tswitchData.ts = now;\n\tswitchData.on = payload.nvalue == 1;\n\tlog('Switch sensor updated to:' + switchData.on);\n\tlastKnownFanState = switchData;\n\tcontext.lastKnownFanState = lastKnownFanState;\n}\n\nif (domotIdx === outsideSensor) {\n\tlog('Outside sensor updated to:' + sensorData.hum);\n\tlastKnownOutsideData = sensorData;\n\tcontext.lastKnownOutsideData = lastKnownOutsideData;\n\tif (lastKnownFanState.on === true) {\n\t\tif (fanIsTryingToRestoreHumidity === false) {\n\t\t\tfanStartedManually();\n\t\t} else {\n\t\t\tfanIsRestoringHumidity();\n\t\t}\n\t}\n}\n\nfunction fanStartedManually() {\n\t/*\n\t -- fan manually started\n\t */\n\tvar testLastHistoryGm3 = lastBathroomHistory.gm3 + gm3TargetOffset;\n\tlog('Fan was manually turned on');\n\tlog('Timeout difference:' + ((lastKnownFanState.ts + fanMaxMinutesOnInMilliseconds) - now) +\n\t\t' minutes:' + Math.floor(((lastKnownFanState.ts + fanMaxMinutesOnInMilliseconds) - now) / minutes) +\n\t\t' lastKnown in:' + lastBathroomHistory.gm3 +\n\t\t' out:' + (lastKnownOutsideData && lastKnownOutsideData.gm3));\n\tif (\n\t\t((lastKnownFanState.ts + fanMaxMinutesOnInMilliseconds) < now) ||\n\t\t(lastKnownOutsideData && (testLastHistoryGm3 <= lastKnownOutsideData.gm3))\n\t) {\n\t\t/*\n\t\t -- fan is on longer than max, turning it off\n\t\t -- absolute humidity outside is higher then inside\n\t\t */\n\t\tif (\n\t\t\t((lastKnownFanState.ts +\n\t\t\tfanMaxMinutesOnInMilliseconds) < now)\n\t\t) {\n\t\t\tlog('Timeout triggered');\n\t\t}\n\t\tif (\n\t\t\t(lastKnownOutsideData && (\n\t\t\ttestLastHistoryGm3 <= lastKnownOutsideData.gm3))\n\t\t) {\n\t\t\tlog('Outside humidity reached');\n\t\t}\n\t\tlog('Turning fan off');\n\t\tpayload.fanOn = false;\n\t}\n}\n\nfunction fanIsRestoringHumidity() {\n\tvar testLastHistoryGm3 = lastBathroomHistory.gm3 + gm3TargetOffset;\n\tif (\n\t\t((fanIsTryingToRestoreHumidity + fanMaxMinutesOnInMilliseconds) < now) ||\n\t\t(lastBathroomHistory.hum <= targetHumidity) ||\n\t\t(lastKnownOutsideData && (testLastHistoryGm3 <= lastKnownOutsideData.gm3))\n\t) {\n\t\t/*\n\t\t -- fan is on long enough\n\t\t -- target humidity has been reached\n\t\t -- absolute humidity outside is higher then inside\n\t\t */\n\t\tif (\n\t\t\t((fanIsTryingToRestoreHumidity + fanMaxMinutesOnInMilliseconds) < now)\n\t\t) {\n\t\t\tlog('Timeout triggered');\n\t\t}\n\t\tif (\n\t\t\t(\n\t\t\tlastBathroomHistory.hum <= targetHumidity)\n\t\t) {\n\t\t\tlog('Target humidity (' + targetHumidity + ') reached');\n\t\t}\n\t\tif (\n\t\t\t(lastKnownOutsideData &&\n\t\t\t(\n\t\t\ttestLastHistoryGm3 <=\n\t\t\tlastKnownOutsideData.gm3))\n\t\t) {\n\t\t\tlog('Outside humidity reached, ');\n\t\t}\n\t\tlog('Turning fan off');\n\n\t\tpayload.fanOn = false;\n\t\tfanIsTryingToRestoreHumidity = false;\n\t\tcontext.fanIsTryingToRestoreHumidity = fanIsTryingToRestoreHumidity;\n\t\ttargetHumidity = hardMinimumHumidity;\n\t\tcontext.targetHumidity = targetHumidity;\n\t}\n}\n\nif (domotIdx === insideSensor) {\n\tvar recent = sensorData.ts - recentMinutes * minutes;\n\tvar lowestRecent = _.reduce(bathroomHistory,\n\t\tfunction (min, data) {\n\t\t\tif (data.ts > recent) {\n\t\t\t\tif (data.hum < min) {\n\t\t\t\t\treturn data.hum;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn min;\n\t\t}, sensorData.hum);\n\tvar delta = sensorData.hum - lowestRecent;\n\triseInHumidity = delta >= fanDeltaTrigger;\n\tlog('Inside sensor updated to:' + sensorData.hum + '. delta:' + delta);\n\tpayload.riseInHumidity = riseInHumidity;\n\ttargetHumidity = Math.max(lowestRecent + humTargetOffset, hardMinimumHumidity);\n\tpayload.targetHumidity = targetHumidity;\n\tif (bathroomHistoryLength > 0) {\n\t\tvar lastHistory = bathroomHistory[bathroomHistoryLength - 1];\n\t\tif (lastHistory.gm3 !== sensorData.gm3) {\n\t\t\tlog('added to history');\n\t\t\tbathroomHistory.push(sensorData);\n\t\t} else {\n\t\t\t// ignore new timestamp\n\t\t\tlog('ignore new timestamp');\n\t\t\tsensorData = lastHistory;\n\t\t}\n\t} else {\n\t\tlog('started keeping a history');\n\t\tbathroomHistory.push(sensorData);\n\t}\n\tif (bathroomHistoryLength > nrHistoryToKeep) {\n\t\tbathroomHistory.shift();\n\t}\n\tcontext.bathroomHistory = bathroomHistory;\n\tbathroomHistoryLength = bathroomHistory.length;\n\tlastBathroomHistory = bathroomHistory[bathroomHistoryLength - 1] || {gm3: 10000};\n\n\tif (\n\t\t(lastKnownFanState.on === false) ||\n\t\t(lastKnownFanState.on === true && fanIsTryingToRestoreHumidity === false)\n\t) {\n\t\t/*\n\t\t -- either the fan is off or it is on but the decrease program has not started\n\t\t -- in that latter case we start the program anyway. This could happen if someone turns on the ventilator\n\t\t -- manually because he/she is about to take a shower and doesn't like damp mirrors.\n\t\t */\n\n\t\tlog('Fan is:' + lastKnownFanState.on + ', restoring:' + fanIsTryingToRestoreHumidity);\n\n\t\tif (lastKnownFanState.on === true && fanIsTryingToRestoreHumidity === false) {\n\t\t\tfanStartedManually();\n\t\t}\n\t\t\n\t\tif (fanIsTryingToRestoreHumidity !== false && lastKnownFanState.on === false) {\n\t\t\t/*\n\t\t\t -- likely someone turned off the ventilator while the program was running\n\t\t\t */\n\t\t\tlog('Fan was turned off while trying to restore humidity');\n\t\t\tfanIsTryingToRestoreHumidity = false;\n\t\t\tcontext.fanIsTryingToRestoreHumidity = fanIsTryingToRestoreHumidity;\n\t\t}\n\t\t\n\t\tif (riseInHumidity) {\n\t\t\tlog('Rise in humidity, turning fan on');\n\n\t\t\tpayload.fanOn = true;\n\t\t\tif (fanIsTryingToRestoreHumidity === false) {\n\t\t\t\tlog('Setting target humidity: ' + targetHumidity + '.');\n\t\t\t\tcontext.targetHumidity = targetHumidity;\n\t\t\t}\n\t\t\tfanIsTryingToRestoreHumidity = now;\n\t\t\tcontext.fanIsTryingToRestoreHumidity = fanIsTryingToRestoreHumidity;\n\t\t}\n\n\t} else {\n\t\t/*\n\t\t -- fan is on and possibly trying to restore humidity\n\t\t */\n\n\n\t\tif (fanIsTryingToRestoreHumidity) {\n\t\t\tif (riseInHumidity) {\n\t\t\t\t/*\n\t\t\t\t -- ok, there is another FAN_DELTA_TRIGGER rise in humidity\n\t\t\t\t -- when this happen we reset the fanMaxTimer to a new count down\n\t\t\t\t -- because we have to ventilate a bit longer due to the extra humidity\n\t\t\t\t */\n\t\t\t\tfanIsTryingToRestoreHumidity = now;\n\t\t\t\tcontext.fanIsTryingToRestoreHumidity = fanIsTryingToRestoreHumidity;\n\t\t\t\tlog('It\\'s getting more humid, extend shutdown time');\n\t\t\t}\n\t\t\tfanIsRestoringHumidity();\n\t\t} else {\n\t\t\tfanStartedManually();\n\t\t}\n\t}\n}\n\nif (fanIsTryingToRestoreHumidity) {\n\t//noinspection JSUnresolvedVariable\n\tnode.status({\n\t\tfill: \"blue\",\n\t\tshape: \"dot\",\n\t\ttext: \"fanIsTryingToRestoreHumidity\"\n\t});\n} else {\n\tif (lastKnownFanState.on) {\n\t\t//noinspection JSUnresolvedVariable\n\t\tnode.status({\n\t\t\tfill: \"blue\",\n\t\t\tshape: \"ring\",\n\t\t\ttext: \"lastKnownFanState.on\"\n\t\t});\n\t} else {\n\t\t//noinspection JSUnresolvedVariable\n\t\tnode.status({});\n\t}\n}\n\nlastBathroomHistory = bathroomHistory[bathroomHistoryLength - 1] || {gm3: 10000};\n\nvar contextMsg = {\n\tpayload: {\n\t\tlog: payload.log,\n\t\tcontext: {\n\t\t\tbathroomHistory: context.bathroomHistory,\n\t\t\tlastKnownInsideData: lastBathroomHistory,\n\t\t\ttargetHumidity: context.targetHumidity,\n\t\t\tlastKnownOutsideData: context.lastKnownOutsideData,\n\t\t\tlastKnownFanState: context.lastKnownFanState,\n\t\t\tfanIsTryingToRestoreHumidity: context.fanIsTryingToRestoreHumidity\n\t\t}\n\t}\n};\nif (typeof(payload.fanOn) === 'boolean') {\n\t//only pass on fan state changes;\n\tpayload.context = contextMsg.payload.context;\n\tmsg.payload = payload;\n} else {\n\tmsg = null;\n}\n//noinspection JSAnnotator\nreturn [msg, contextMsg];\n",
|
|
"outputs": "2",
|
|
"noerr": 0,
|
|
"x": 368.5,
|
|
"y": 559,
|
|
"wires": [
|
|
[
|
|
"2cbff763.27d548",
|
|
"d028818.f2fd78"
|
|
],
|
|
[
|
|
"182445e0.3190ea",
|
|
"b6dda284.49226",
|
|
"5caeaf10.a3515"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "d028818.f2fd78",
|
|
"type": "debug",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "",
|
|
"active": true,
|
|
"console": "false",
|
|
"complete": "payload",
|
|
"x": 684.5,
|
|
"y": 717,
|
|
"wires": []
|
|
},
|
|
{
|
|
"id": "b6dda284.49226",
|
|
"type": "function",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "send log message",
|
|
"func": "delete msg.topic;\nvar log = msg.payload.log;\nmsg.payload = {\n subject: \"Bathroom Fan\",\n body: log,\n command: \"sendnotification\", \n};\nif(log){\n return msg;\n}\nreturn null\n",
|
|
"outputs": 1,
|
|
"noerr": 0,
|
|
"x": 652.5,
|
|
"y": 584,
|
|
"wires": [
|
|
[
|
|
"50ac2f84.af53d",
|
|
"d028818.f2fd78"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "8827eee3.77d81",
|
|
"type": "mqtt in",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "",
|
|
"topic": "node-red/fan",
|
|
"broker": "7b9bfb4b.846404",
|
|
"x": 117.5,
|
|
"y": 417,
|
|
"wires": [
|
|
[
|
|
"7aaeeb3f.855114"
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"id": "5caeaf10.a3515",
|
|
"type": "mqtt out",
|
|
"z": "5751578d.a8aea8",
|
|
"name": "",
|
|
"topic": "node-red/fan",
|
|
"qos": "",
|
|
"retain": "true",
|
|
"broker": "7b9bfb4b.846404",
|
|
"x": 833.5,
|
|
"y": 481,
|
|
"wires": []
|
|
}
|
|
] |