[ { "id": "f6f2187d.f17ca8", "type": "tab", "label": "Main Flows", "disabled": false, "info": "" }, { "id": "21801ad4.5fea0e", "type": "postgresdb", "cfgname": "timescaledb", "hostname": "172.16.10.27", "port": "5432", "db": "mainscnt", "ssl": true }, { "id": "3358c20a.fe8336", "type": "twitter-credentials", "screen_name": "wollud1969" }, { "id": "bde268f2.c163d8", "type": "ui_tab", "name": "MainsCnt", "icon": "dashboard", "disabled": false, "hidden": false }, { "id": "c5c107b9.c41048", "type": "ui_base", "theme": { "name": "theme-light", "lightTheme": { "default": "#0094CE", "baseColor": "#0094CE", "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", "edited": true, "reset": false }, "darkTheme": { "default": "#097479", "baseColor": "#097479", "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", "edited": false }, "customTheme": { "name": "Untitled Theme 1", "default": "#4B7930", "baseColor": "#4B7930", "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" }, "themeState": { "base-color": { "default": "#0094CE", "value": "#0094CE", "edited": false }, "page-titlebar-backgroundColor": { "value": "#0094CE", "edited": false }, "page-backgroundColor": { "value": "#fafafa", "edited": false }, "page-sidebar-backgroundColor": { "value": "#ffffff", "edited": false }, "group-textColor": { "value": "#1bbfff", "edited": false }, "group-borderColor": { "value": "#ffffff", "edited": false }, "group-backgroundColor": { "value": "#ffffff", "edited": false }, "widget-textColor": { "value": "#111111", "edited": false }, "widget-backgroundColor": { "value": "#0094ce", "edited": false }, "widget-borderColor": { "value": "#ffffff", "edited": false }, "base-font": { "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" } }, "angularTheme": { "primary": "indigo", "accents": "blue", "warn": "red", "background": "grey" } }, "site": { "name": "Node-RED Dashboard", "hideToolbar": "false", "allowSwipe": "false", "lockMenu": "false", "allowTempTheme": "true", "dateFormat": "DD.MM.YYYY", "sizes": { "sx": 48, "sy": 48, "gx": 6, "gy": 6, "cx": 6, "cy": 6, "px": 0, "py": 0 } } }, { "id": "a4c5e228.94ac28", "type": "ui_group", "name": "Deviation", "tab": "bde268f2.c163d8", "order": 2, "disp": false, "width": "12", "collapse": false }, { "id": "b44c1611.48c72", "type": "ui_group", "name": "Frequency&Alarm", "tab": "bde268f2.c163d8", "order": 3, "disp": false, "width": "6", "collapse": false }, { "id": "ee409cd5.fa48e8", "type": "ui_spacer", "name": "spacer", "group": "b44c1611.48c72", "order": 3, "width": 1, "height": 1 }, { "id": "938dc565.b607f", "type": "tls-config", "name": "", "cert": "", "key": "", "ca": "", "certname": "wn-mainscnt-broker-client.crt", "keyname": "wn-mainscnt-broker-client.pem", "caname": "isrgrootx1.pem", "servername": "broker.mainscnt.eu", "verifyservercert": true }, { "id": "48a55c40.d99ddc", "type": "mqtt-broker", "name": "ExternalBroker", "broker": "broker.mainscnt.eu", "port": "8883", "tls": "938dc565.b607f", "clientid": "", "usetls": true, "protocolVersion": "4", "keepalive": "60", "cleansession": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "birthMsg": {}, "closeTopic": "", "closeQos": "0", "closePayload": "", "closeMsg": {}, "willTopic": "", "willQos": "0", "willPayload": "", "willMsg": {}, "sessionExpiry": "" }, { "id": "911fb610.7082e", "type": "function", "z": "f6f2187d.f17ca8", "name": "missing values", "func": "msg.queryParameters = {\n \"threshold\": msg.threshold\n}\nmsg.payload = `\n SELECT count(freq) AS cnt, location\n FROM mainsfrequency\n WHERE time BETWEEN\n now() - interval '10 minutes' AND now() - interval '5 minutes'\n GROUP BY location\n HAVING count(freq) <= $threshold\n`\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 140, "y": 180, "wires": [ [ "b6ffa593.3030a8" ] ] }, { "id": "4faba5e0.e01474", "type": "change", "z": "f6f2187d.f17ca8", "name": "Threshold 100", "rules": [ { "t": "set", "p": "threshold", "pt": "msg", "to": "100", "tot": "num" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 140, "y": 140, "wires": [ [ "911fb610.7082e" ] ] }, { "id": "b6ffa593.3030a8", "type": "postgres", "z": "f6f2187d.f17ca8", "postgresdb": "21801ad4.5fea0e", "name": "mainscnt", "output": true, "perrow": false, "rowspermsg": "1", "return_on_error": false, "limit_queries": "0", "limit_by": "payload", "limit_value": "1", "limit_drop_intermediate": false, "limit_drop_if_in_queue": false, "outputs": true, "x": 160, "y": 220, "wires": [ [ "9dcb400a.e79da" ] ] }, { "id": "9dcb400a.e79da", "type": "function", "z": "f6f2187d.f17ca8", "name": "Prepare", "func": "if (msg.payload.length > 0) {\n msg.queryResult = msg.payload\n let output = \"Missing/too few values five minutes ago \"\n output += `(<= ${msg.threshold}), in `\n for (const [idx, item] of msg.payload.entries()) {\n output += `${item.location} (${item.cnt})`\n if (idx != msg.payload.length - 1) {\n output += \", \"\n }\n }\n output += \".\\n\"\n msg.payload = output\n msg.payloadLength = output.length\n return msg\n} else {\n return null\n}", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 160, "y": 260, "wires": [ [ "b38b3cf3.92f31" ] ] }, { "id": "b38b3cf3.92f31", "type": "pushover", "z": "f6f2187d.f17ca8", "name": "MainsCntAlarm", "device": "", "title": "", "priority": 0, "sound": "", "url": "", "url_title": "", "html": false, "x": 140, "y": 300, "wires": [] }, { "id": "e4b652d9.980e8", "type": "inject", "z": "f6f2187d.f17ca8", "name": "Once a minute", "props": [ { "p": "payload" } ], "repeat": "60", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "true", "payloadType": "bool", "x": 140, "y": 100, "wires": [ [ "4faba5e0.e01474" ] ] }, { "id": "df8fb736.a86528", "type": "function", "z": "f6f2187d.f17ca8", "name": "Frequency Average by Day", "func": "msg.payload = `\n SELECT avg(freq) AS avg, \n count(freq) as cnt_freq, \n count(distinct location) as cnt_loc\n FROM mainsfrequency\n WHERE time >= current_date - interval '1 day' AND \n time < current_date AND\n valid = 1\n`\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 520, "y": 140, "wires": [ [ "8fc75864.aa5a18" ] ] }, { "id": "8fc75864.aa5a18", "type": "postgres", "z": "f6f2187d.f17ca8", "postgresdb": "21801ad4.5fea0e", "name": "mainscnt", "output": true, "perrow": false, "rowspermsg": "1", "return_on_error": false, "limit_queries": "0", "limit_by": "payload", "limit_value": "1", "limit_drop_intermediate": false, "limit_drop_if_in_queue": false, "outputs": true, "x": 580, "y": 180, "wires": [ [ "bec353dd.5865c" ] ] }, { "id": "bec353dd.5865c", "type": "function", "z": "f6f2187d.f17ca8", "name": "Prepare", "func": "msg.result = msg.payload[0]\nmsg.result.avg = msg.result.avg.toFixed(3)\nmsg.result.cnt_freq = parseInt(msg.result.cnt_freq)\nmsg.result.cnt_loc = parseInt(msg.result.cnt_loc)\nmsg.result.pos_freq = msg.result.cnt_loc * 86400\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 580, "y": 220, "wires": [ [ "cfef36ff.319b98" ] ] }, { "id": "eacf880.4329d78", "type": "inject", "z": "f6f2187d.f17ca8", "name": "Once a day", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "30 09 * * *", "once": false, "onceDelay": 0.1, "topic": "", "payload": "true", "payloadType": "bool", "x": 570, "y": 100, "wires": [ [ "df8fb736.a86528" ] ] }, { "id": "cfef36ff.319b98", "type": "template", "z": "f6f2187d.f17ca8", "name": "Format", "field": "payload", "fieldType": "msg", "format": "handlebars", "syntax": "mustache", "template": "#netzfrequenz Yesterday, according to my measurement, the average mains frequency measured at {{result.cnt_loc}} locations and calculated from {{result.cnt_freq}} of {{result.pos_freq}} possible values was {{result.avg}} Hz. For details see https://grafana.mainscnt.eu \nPowered by #nodered, #timescaledb and #grafana.", "output": "str", "x": 580, "y": 260, "wires": [ [ "52acdab3.188644" ] ] }, { "id": "405ce4ce.49e93c", "type": "twitter out", "z": "f6f2187d.f17ca8", "twitter": "3358c20a.fe8336", "name": "Tweet", "x": 590, "y": 340, "wires": [] }, { "id": "52acdab3.188644", "type": "jimp-image", "z": "f6f2187d.f17ca8", "name": "", "data": "https://grafana.mainscnt.eu/render/d-solo/tg1-U6XGz/mainscnt?orgId=1&from=now-1d%2Fd&to=now-1d%2Fd&panelId=2&width=1000&height=1200&tz=UTC", "dataType": "str", "ret": "buf", "parameter1": "", "parameter1Type": "msg", "parameter2": "", "parameter2Type": "msg", "parameter3": "", "parameter3Type": "msg", "parameter4": "", "parameter4Type": "msg", "parameter5": "", "parameter5Type": "msg", "parameter6": "", "parameter6Type": "msg", "parameter7": "", "parameter7Type": "msg", "parameter8": "", "parameter8Type": "msg", "sendProperty": "media", "sendPropertyType": "msg", "parameterCount": 0, "jimpFunction": "none", "selectedJimpFunction": { "name": "none", "fn": "none", "description": "Just loads the image.", "parameters": [] }, "x": 590, "y": 300, "wires": [ [ "405ce4ce.49e93c" ] ] }, { "id": "f6a43d0d.ebddc", "type": "comment", "z": "f6f2187d.f17ca8", "name": "Missing Values", "info": "", "x": 140, "y": 60, "wires": [] }, { "id": "b73c5463.505428", "type": "comment", "z": "f6f2187d.f17ca8", "name": "Daily Average Frequency Tweet", "info": "", "x": 510, "y": 60, "wires": [] }, { "id": "56cef6df.8ecbe8", "type": "inject", "z": "f6f2187d.f17ca8", "name": "Once a minute", "props": [ { "p": "payload" } ], "repeat": "60", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "true", "payloadType": "bool", "x": 920, "y": 100, "wires": [ [ "aad599a3.46ee48" ] ] }, { "id": "aad599a3.46ee48", "type": "function", "z": "f6f2187d.f17ca8", "name": "Averaging Statement", "func": "msg.payload = `\n SELECT avg(freq) AS mean\n FROM mainsfrequency\n WHERE valid = 1 AND \n time BETWEEN\n now() - interval '7 minutes' AND now() - interval '5 minutes'\n`\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 900, "y": 140, "wires": [ [ "efab1827.7067d8" ] ] }, { "id": "efab1827.7067d8", "type": "postgres", "z": "f6f2187d.f17ca8", "postgresdb": "21801ad4.5fea0e", "name": "mainscnt", "output": true, "perrow": false, "rowspermsg": "1", "return_on_error": false, "limit_queries": "0", "limit_by": "payload", "limit_value": "1", "limit_drop_intermediate": false, "limit_drop_if_in_queue": false, "outputs": true, "x": 940, "y": 180, "wires": [ [ "27bc28c6.5d5b58" ] ] }, { "id": "27bc28c6.5d5b58", "type": "function", "z": "f6f2187d.f17ca8", "name": "Extract (absolute) deviation", "func": "let DESIRED_FREQUENCY = 50.0\nif (msg.payload.length > 0) {\n let v = msg.payload[0].mean\n node.status({fill:\"green\",shape:\"dot\",text:`${v}`})\n msg.payload = v\n return [ \n { 'payload': v.toFixed(3) }, \n { 'payload': (v - DESIRED_FREQUENCY).toFixed(3) }\n ]\n} else {\n node.status({fill:\"red\",shape:\"dot\",text:\"no data\"})\n node.error(\"no data\")\n return null\n}\n", "outputs": 2, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 880, "y": 220, "wires": [ [ "5a6c06e7.dca6e8" ], [ "5594701d.41a81", "7c63eb69.6d1b24" ] ] }, { "id": "7c63eb69.6d1b24", "type": "function", "z": "f6f2187d.f17ca8", "name": "Alarm", "func": "let ALARM_THRESHOLD = 0.1\nlet RE_ARM_THRESHOLD = 0.075\nlet ARMED_STATE_KEY = 'ARMED_STATE'\nlet value = msg.payload\nlet absValue = Math.abs(value)\n\nif (! context.keys().includes(ARMED_STATE_KEY)) {\n context.set(ARMED_STATE_KEY, true)\n}\n\nlet armedState = context.get(ARMED_STATE_KEY)\nlet newEvent = false\nif (armedState) {\n if (absValue > ALARM_THRESHOLD) {\n armedState = false\n newEvent = true\n }\n} else {\n if (absValue < RE_ARM_THRESHOLD) {\n armedState = true\n newEvent = true\n }\n}\n\nnode.status(\n {\n fill: armedState ? \"green\" : \"red\",\n shape:\"dot\",\n text: armedState ? \"armed\" : \"alarm sent\"\n }\n)\n\ncontext.set(ARMED_STATE_KEY, armedState)\n\nreturn [ \n {'payload': armedState}, \n newEvent ?\n { \n 'payload': {\n 'value': value,\n 'state': armedState,\n 'high': ALARM_THRESHOLD,\n 'low': RE_ARM_THRESHOLD\n }\n } : null\n]", "outputs": 2, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 950, "y": 260, "wires": [ [ "5701a2b2.9ca59c" ], [ "85489c32.b49ff", "33c01fa2.d6e88", "13ebfe6b.4e6262" ] ] }, { "id": "5594701d.41a81", "type": "ui_chart", "z": "f6f2187d.f17ca8", "name": "", "group": "a4c5e228.94ac28", "order": 1, "width": "12", "height": "9", "label": "Deviation", "chartType": "line", "legend": "false", "xformat": "HH:mm:ss", "interpolate": "linear", "nodata": "", "dot": true, "ymin": "-0.15", "ymax": "0.15", "removeOlder": 1, "removeOlderPoints": "", "removeOlderUnit": "3600", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#1f77b4", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "x": 1140, "y": 220, "wires": [ [] ] }, { "id": "5a6c06e7.dca6e8", "type": "ui_gauge", "z": "f6f2187d.f17ca8", "name": "", "group": "b44c1611.48c72", "order": 4, "width": 0, "height": 0, "gtype": "gage", "title": "Frequency", "label": "Hz", "format": "{{value}}", "min": "49.85", "max": "50.15", "colors": [ "#ff0000", "#00ff00", "#ff0000" ], "seg1": "49.9", "seg2": "50.1", "x": 1150, "y": 180, "wires": [] }, { "id": "5701a2b2.9ca59c", "type": "ui_switch", "z": "f6f2187d.f17ca8", "name": "", "label": "Alarm", "tooltip": "", "group": "b44c1611.48c72", "order": 1, "width": 0, "height": 0, "passthru": true, "decouple": "false", "topic": "topic", "topicType": "msg", "style": "", "onvalue": "true", "onvalueType": "bool", "onicon": "mood", "oncolor": "green", "offvalue": "false", "offvalueType": "bool", "officon": "mood_bad", "offcolor": "red", "animate": true, "x": 1130, "y": 260, "wires": [ [] ] }, { "id": "88a1710c.11068", "type": "twitter out", "z": "f6f2187d.f17ca8", "twitter": "3358c20a.fe8336", "name": "MainsCntTweet", "x": 920, "y": 600, "wires": [] }, { "id": "c7c60031.05b2f", "type": "pushover", "z": "f6f2187d.f17ca8", "name": "MainsCntAlarm", "device": "", "title": "", "priority": 0, "sound": "", "url": "", "url_title": "", "html": false, "x": 920, "y": 560, "wires": [] }, { "id": "33c01fa2.d6e88", "type": "function", "z": "f6f2187d.f17ca8", "name": "Prepare", "func": "let info = `Deviation is ${msg.payload.value}, High mark is ${msg.payload.high}, Low mark is ${msg.payload.low}`\nlet status = msg.payload.state ? 'clear&armed' : 'alarm'\n\nlet output = \"Frequency Out of Range Alarm\\n\"\noutput += `Status: ${status}\\n`\noutput += `Absolute devivation ${info} Hz\\n`\noutput += \"https://shorty.mainscnt.eu/30m\"\nmsg.payload = output\nmsg.payloadLength = output.length\nreturn msg\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 940, "y": 520, "wires": [ [ "c7c60031.05b2f" ] ] }, { "id": "85489c32.b49ff", "type": "function", "z": "f6f2187d.f17ca8", "name": "Alarm Event Statement", "func": "msg.queryParameters = {\n 'info': `Deviation is ${msg.payload.value}, High mark is ${msg.payload.high}, Low mark is ${msg.payload.low}`,\n 'status': msg.payload.state ? 'clear&armed' : 'alarm'\n}\nmsg.payload = `\n INSERT INTO alarm_event_t\n (name, status, info)\n values('freq_out_of_range', $status, $info)\n`\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1190, "y": 420, "wires": [ [ "a392562a.918b58" ] ] }, { "id": "a392562a.918b58", "type": "postgres", "z": "f6f2187d.f17ca8", "postgresdb": "21801ad4.5fea0e", "name": "mainscnt", "output": false, "perrow": false, "rowspermsg": "1", "return_on_error": false, "limit_queries": "0", "limit_by": "payload", "limit_value": "1", "limit_drop_intermediate": false, "limit_drop_if_in_queue": false, "outputs": false, "x": 1140, "y": 460, "wires": [] }, { "id": "13ebfe6b.4e6262", "type": "function", "z": "f6f2187d.f17ca8", "name": "EventCounter", "func": "let cnt = context.get('EventCounter') || 0\ncnt += 1\ncontext.set('EventCounter', cnt)\nnode.status({fill:\"blue\",shape:\"dot\",text:`${cnt}`})\nmsg.payload = cnt\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1160, "y": 320, "wires": [ [ "94287849.0ee7e8" ] ] }, { "id": "94287849.0ee7e8", "type": "ui_text", "z": "f6f2187d.f17ca8", "group": "b44c1611.48c72", "order": 2, "width": 0, "height": 0, "name": "", "label": "EventCnt", "format": "{{msg.payload}}", "layout": "row-spread", "x": 1140, "y": 360, "wires": [] }, { "id": "d7c897ea.2f7c78", "type": "comment", "z": "f6f2187d.f17ca8", "name": "Frequency Out of Range Alarm", "info": "", "x": 870, "y": 60, "wires": [] }, { "id": "f41aea58.180338", "type": "mqtt in", "z": "f6f2187d.f17ca8", "name": "", "topic": "MainsCnt/#", "qos": "2", "datatype": "json", "broker": "48a55c40.d99ddc", "nl": false, "rap": true, "rh": 0, "x": 230, "y": 540, "wires": [ [ "776c7457.dfa35c" ] ] }, { "id": "f37a3ea0.54c97", "type": "function", "z": "f6f2187d.f17ca8", "name": "Check and Prepare", "func": "const LOWER_BOUND = 48.5\nconst UPPER_BOUND = 51.5\n\nlet valid = msg.incoming.Valid\nlet freq = msg.incoming.Freq\n\nif (freq < LOWER_BOUND || freq > UPPER_BOUND) {\n valid = false\n}\n\nmsg.queryParameters = {\n \"time\": msg.incoming.Time,\n \"freq\": freq,\n \"location\": msg.location,\n \"valid\": valid\n}\nmsg.payload = `\n INSERT INTO mainsfrequency\n (time, freq, location)\n VALUES($time, $freq, $location)\n`\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 210, "y": 700, "wires": [ [ "afbde352.0aaa6" ] ] }, { "id": "afbde352.0aaa6", "type": "postgres", "z": "f6f2187d.f17ca8", "postgresdb": "21801ad4.5fea0e", "name": "mainscnt", "output": true, "perrow": false, "rowspermsg": "1", "return_on_error": false, "limit_queries": "0", "limit_by": "payload", "limit_value": "1", "limit_drop_intermediate": false, "limit_drop_if_in_queue": false, "outputs": true, "x": 240, "y": 740, "wires": [ [] ] }, { "id": "776c7457.dfa35c", "type": "function", "z": "f6f2187d.f17ca8", "name": "FindDevice", "func": "msg.incoming = msg.payload\nmsg.queryParameters = {\n \"deviceid\": msg.topic\n}\nmsg.payload = `\n SELECT location \n FROM device_t\n WHERE active = 't' AND\n deviceid = $deviceid\n`\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 230, "y": 580, "wires": [ [ "b8c55ac0.707e08" ] ] }, { "id": "b8c55ac0.707e08", "type": "postgres", "z": "f6f2187d.f17ca8", "postgresdb": "21801ad4.5fea0e", "name": "mainscnt", "output": true, "perrow": false, "rowspermsg": "1", "return_on_error": false, "limit_queries": "0", "limit_by": "payload", "limit_value": "1", "limit_drop_intermediate": false, "limit_drop_if_in_queue": false, "outputs": true, "x": 240, "y": 620, "wires": [ [ "46831f13.0e29d" ] ] }, { "id": "46831f13.0e29d", "type": "function", "z": "f6f2187d.f17ca8", "name": "ExtractLocation", "func": "if (msg.payload.length == 1) {\n msg.location = msg.payload[0].location\n return msg\n} else {\n return\n}", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 220, "y": 660, "wires": [ [ "f37a3ea0.54c97" ] ] }, { "id": "52a9852a.c634dc", "type": "comment", "z": "f6f2187d.f17ca8", "name": "MQTT Ingress Processor", "info": "", "x": 190, "y": 500, "wires": [] } ]