diff --git a/flows.json b/flows.json index 7f338e3..5f12e1b 100644 --- a/flows.json +++ b/flows.json @@ -71,6 +71,14 @@ "info": "", "env": [] }, + { + "id": "b7d34d3e9d0c9486", + "type": "tab", + "label": "Laundry", + "disabled": false, + "info": "", + "env": [] + }, { "id": "458e4c0055c8c533", "type": "tab", @@ -692,6 +700,94 @@ ], "icon": "node-red-contrib-color-convert/color-convert.png" }, + { + "id": "384eaf2f63dcb5db", + "type": "subflow", + "name": "Input Datetime - With Date", + "info": "Takes a timestamp as input at msg.timestamp and converts it to the proper format for an input_datetime with date enabled. Output is on msg.datetime", + "category": "", + "in": [ + { + "x": 60, + "y": 80, + "wires": [ + { + "id": "8b3c203e7e901a8c" + } + ] + } + ], + "out": [ + { + "x": 420, + "y": 80, + "wires": [ + { + "id": "8b3c203e7e901a8c", + "port": 0 + } + ] + } + ], + "env": [], + "meta": {}, + "color": "#C0DEED", + "icon": "font-awesome/fa-clock-o", + "status": { + "x": 420, + "y": 140, + "wires": [ + { + "id": "8b3c203e7e901a8c", + "port": 0 + } + ] + } + }, + { + "id": "9766d47584651edb", + "type": "subflow", + "name": "Convert Minutes to Seconds", + "info": "Input minutes on msg.duration, and it will output seconds on msg.duration", + "category": "", + "in": [ + { + "x": 60, + "y": 80, + "wires": [ + { + "id": "b2d071ca42e4a3c6" + } + ] + } + ], + "out": [ + { + "x": 400, + "y": 80, + "wires": [ + { + "id": "b2d071ca42e4a3c6", + "port": 0 + } + ] + } + ], + "env": [], + "meta": {}, + "color": "#C0DEED", + "icon": "font-awesome/fa-clock-o", + "status": { + "x": 400, + "y": 140, + "wires": [ + { + "id": "b2d071ca42e4a3c6", + "port": 1 + } + ] + } + }, { "id": "eccca853a916b662", "type": "group", @@ -871,7 +967,9 @@ "51b11f9d5b8bfbb0" ], "x": 34, - "y": 179 + "y": 179, + "w": 1292, + "h": 382 }, { "id": "477e752fcd2e2a19", @@ -1246,7 +1344,6 @@ "label": true }, "nodes": [ - "b2d071ca42e4a3c6", "7fc67fc156c0f4be", "34dcb204b421710d", "797d3e7cc53d773f", @@ -1273,7 +1370,8 @@ "0e39331337b021bb", "82172c8ab1a9c8e2", "afeb1d238a623c3d", - "49dbe17ff03ed572" + "49dbe17ff03ed572", + "9612270d4cdfbf07" ], "x": 34, "y": 39, @@ -1332,7 +1430,9 @@ "a8d7f5e5d4f549b0" ], "x": 34, - "y": 579 + "y": 579, + "w": 732, + "h": 162 }, { "id": "4886967d0f8c0156", @@ -1392,7 +1492,9 @@ "f9bc4d36a0df7947" ], "x": 34, - "y": 759 + "y": 759, + "w": 672, + "h": 222 }, { "id": "6e83103b5c5311e9", @@ -1410,7 +1512,9 @@ "a75651492e779bfc" ], "x": 34, - "y": 19 + "y": 19, + "w": 912, + "h": 142 }, { "id": "88f099618e56926d", @@ -1492,7 +1596,9 @@ "15f7382daddb4b3e" ], "x": 34, - "y": 999 + "y": 999, + "w": 632, + "h": 82 }, { "id": "31767ffd6a647579", @@ -3784,6 +3890,147 @@ "resend": false, "debugEnabled": false }, + { + "id": "aebd1e9ea95e45da", + "type": "ha-device-config", + "name": "Laundry Tracking", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "5627513f9469cbb8", + "type": "ha-entity-config", + "server": "9e87348d.9c1c48", + "deviceConfig": "aebd1e9ea95e45da", + "name": "Washer Start", + "version": "6", + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Washer Start" + }, + { + "property": "icon", + "value": "mdi:washing-machine" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "d2d7710df0394f16", + "type": "ha-entity-config", + "server": "9e87348d.9c1c48", + "deviceConfig": "aebd1e9ea95e45da", + "name": "Dryer Start", + "version": "6", + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Dryer Start" + }, + { + "property": "icon", + "value": "mdi:tumble-dryer" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "52dccff6d2c39667", + "type": "ha-entity-config", + "server": "9e87348d.9c1c48", + "deviceConfig": "aebd1e9ea95e45da", + "name": "Washer Cancel", + "version": "6", + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Washer Cancel" + }, + { + "property": "icon", + "value": "mdi:washing-machine-off" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "9da998d5f74df33c", + "type": "ha-entity-config", + "server": "9e87348d.9c1c48", + "deviceConfig": "aebd1e9ea95e45da", + "name": "Dryer Cancel", + "version": "6", + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Dryer Cancel" + }, + { + "property": "icon", + "value": "mdi:tumble-dryer-off" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, { "id": "afead614fabc01bb", "type": "function", @@ -4838,26 +5085,52 @@ "y": 80, "wires": [] }, + { + "id": "8b3c203e7e901a8c", + "type": "moment", + "z": "384eaf2f63dcb5db", + "name": "", + "topic": "", + "input": "timestamp", + "inputType": "msg", + "inTz": "America/New_York", + "adjAmount": 0, + "adjType": "days", + "adjDir": "add", + "format": "YYYY-MM-DD HH:mm:00", + "locale": "en_us", + "output": "datetime", + "outputType": "msg", + "outTz": "America/New_York", + "x": 240, + "y": 80, + "wires": [ + [] + ] + }, { "id": "b2d071ca42e4a3c6", "type": "function", - "z": "4a8821853d4dd1ad", - "g": "a46fc8e7e70ca893", + "z": "9766d47584651edb", "name": "Convert to seconds", - "func": "msg.duration = msg.duration * 60\n\nnode.status({ fill: \"green\", shape: \"dot\", text: msg.duration + \"s\"});\n\nreturn msg;", - "outputs": 1, + "func": "let duration = msg.duration\nlet seconds = duration * 60\n\nlet statusMsg = {\n \"payload\": seconds + \"s\"\n}\n\nmsg.duration = seconds\n\nnode.status({ fill: \"green\", shape: \"dot\", text: msg.duration + \"s\"});\n\nreturn[msg,statusMsg]", + "outputs": 2, + "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 675, - "y": 200, + "x": 230, + "y": 80, "wires": [ - [ - "7fc67fc156c0f4be" - ] + [], + [] ], - "l": false + "outputLabels": [ + "Output", + "Status" + ], + "icon": "font-awesome/fa-clock-o" }, { "id": "7fc67fc156c0f4be", @@ -6096,19 +6369,19 @@ "y": 160, "wires": [ [ - "b2d071ca42e4a3c6", "b8ca8a1a2655570b", - "49dbe17ff03ed572" + "49dbe17ff03ed572", + "9612270d4cdfbf07" ], [ - "b2d071ca42e4a3c6", "b8ca8a1a2655570b", - "49dbe17ff03ed572" + "49dbe17ff03ed572", + "9612270d4cdfbf07" ], [ - "b2d071ca42e4a3c6", "b8ca8a1a2655570b", - "49dbe17ff03ed572" + "49dbe17ff03ed572", + "9612270d4cdfbf07" ] ], "outputLabels": [ @@ -6315,6 +6588,21 @@ [] ] }, + { + "id": "9612270d4cdfbf07", + "type": "subflow:9766d47584651edb", + "z": "4a8821853d4dd1ad", + "g": "a46fc8e7e70ca893", + "name": "", + "x": 675, + "y": 200, + "wires": [ + [ + "7fc67fc156c0f4be" + ] + ], + "l": false + }, { "id": "fd4bbf481e16e6ea", "type": "sunrise", @@ -17472,6 +17760,382 @@ ], "l": false }, + { + "id": "48b8af9419ed18d6", + "type": "subflow:384eaf2f63dcb5db", + "z": "b7d34d3e9d0c9486", + "name": "", + "x": 460, + "y": 420, + "wires": [ + [ + "54a88968618ed545" + ] + ] + }, + { + "id": "54a88968618ed545", + "type": "function", + "z": "b7d34d3e9d0c9486", + "name": "Processing", + "func": "// Set Constants\nconst states = global.get('homeassistant.homeAssistant.states')\nconst washerCycle = 'input_number.washer_cycle_length'\nconst washerFinished = 'input_boolean.washer_finished'\nconst washerTimer = 'timer.washer_timer'\nconst washerDateTime = 'input_datetime.washer_finished'\nconst dryerCycle = 'input_number.dryer_cycle_length'\nconst dryerFinished = 'input_boolean.dryer_finished'\nconst dryerTimer = 'timer.dryer_timer'\nconst dryerDateTime = 'input_datetime.dryer_finished'\nconst topic = msg.topic\nconst payload = msg.payload\n\n// Init variables\nlet timerEntity = []\nlet boolFinished = []\nlet servFinished = {}\nlet dateTimeEntity = []\nlet servTimer = {}\nlet setCycle = []\nlet dateTime = []\nlet deviceName = {}\nlet notifyMsg = {}\n\n// Set entity IDs and states\nif (topic === 'washer') {\n setCycle = washerCycle\n boolFinished = washerFinished\n dateTimeEntity = washerDateTime\n timerEntity = washerTimer\n deviceName = 'washer'\n} else if (topic === 'dryer') {\n setCycle = dryerCycle\n boolFinished = dryerFinished\n dateTimeEntity = dryerDateTime\n timerEntity = dryerTimer\n deviceName = 'dryer'\n}\n\n// Get states from the entities\n// @ts-ignore\nlet cycle = states[setCycle].state\n// @ts-ignore\nlet timerState = states[timerEntity].state\n// @ts-ignore\nlet finishedState = states[boolFinished].state\n\n// Convert minutes into seconds for the timer\n//! Figure out how to take off the decimal point\nlet timerDuration = cycle * 60\n\n// Decide services\nif (payload === 'start') {\n servFinished = 'turn_off'\n notifyMsg = 'The ' + deviceName + ' has been started for a ' + cycle + ' minute cycle'\n} else if (payload === 'finish') {\n dateTime = msg.datetime\n servFinished = 'turn_on'\n notifyMsg = 'The ' + deviceName + ' has finished its cycle'\n} else if (payload === 'cancel') {\n notifyMsg = 'The ' + deviceName + ' cycle has been cancelled'\n}\n\n// Prepare message payloads\nlet sendTimerStart = {\n \"payload\": {\n \"domain\": \"timer\",\n \"service\": \"start\",\n \"target\": {\n \"entity_id\": timerEntity\n },\n \"data\": {\n \"duration\": timerDuration\n }\n }\n}\n\nlet sendTimerCancel = {\n \"payload\": {\n \"domain\": \"timer\",\n \"service\": \"cancel\",\n \"target\": {\n \"entity_id\": timerEntity\n },\n \"data\": {}\n }\n}\n\nlet sendBoolFinished = {\n \"payload\": {\n \"domain\": \"input_boolean\",\n \"service\": servFinished,\n \"target\": {\n \"entity_id\": boolFinished\n },\n \"data\": {}\n }\n}\n\nlet sendDateTimeFinished = {\n \"payload\": {\n \"domain\": \"input_datetime\",\n \"service\": \"set_datetime\",\n \"target\": {\n \"entity_id\": dateTimeEntity\n },\n \"data\": {\n \"datetime\": dateTime\n }\n }\n}\n\nlet sendTextNotify = {\n \"payload\": {\n \"domain\": \"script\",\n \"service\": \"text_notify\",\n \"data\": {\n \"type\": \"normal\",\n \"who\": \"all\",\n \"title\": \"Laundry Tracking\",\n \"message\": notifyMsg\n }\n }\n}\n\nif (payload === 'start') {\n node.send([sendBoolFinished,sendTimerStart,sendTextNotify])\n node.status({fill:\"green\",shape:\"dot\",text:\"Starting \" + deviceName + \" timer for \" + cycle + \" minutes\"})\n node.log(\"Starting \" + deviceName + \" timer for \" + cycle + \" minutes\")\n} else if (payload === 'finish') {\n node.send([[sendBoolFinished,sendDateTimeFinished],null,sendTextNotify])\n node.status({fill:\"green\",shape:\"dot\",text:\"The \" + deviceName + \" has finished\"})\n node.log(\"The \" + deviceName + \" has finished\")\n} else if (payload === 'cancel') {\n node.send([null,sendTimerCancel,sendTextNotify])\n node.status({fill:\"red\",shape:\"ring\",text:\"The \" + deviceName + \" has been cancelled\"})\n node.log(\"The \" + deviceName + \" has been cancelled\")\n}", + "outputs": 3, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 140, + "wires": [ + [ + "7f4ccf77cb54cbf4" + ], + [ + "3c238eb49495cdf7" + ], + [ + "2f59c100baa80e72" + ] + ], + "outputLabels": [ + "Services", + "Timers", + "Notify" + ], + "icon": "node-red-contrib-cpu/cpu.png" + }, + { + "id": "1c69c3f111ae9550", + "type": "ha-button", + "z": "b7d34d3e9d0c9486", + "name": "Washer Start", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "5627513f9469cbb8", + "outputProperties": [ + { + "property": "timestamp", + "propertyType": "msg", + "value": "", + "valueType": "date" + }, + { + "property": "topic", + "propertyType": "msg", + "value": "washer", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "start", + "valueType": "str" + } + ], + "x": 190, + "y": 140, + "wires": [ + [ + "54a88968618ed545" + ] + ] + }, + { + "id": "b45bbd2dbe71e041", + "type": "ha-button", + "z": "b7d34d3e9d0c9486", + "name": "Dryer Start", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "d2d7710df0394f16", + "outputProperties": [ + { + "property": "timestamp", + "propertyType": "msg", + "value": "", + "valueType": "date" + }, + { + "property": "topic", + "propertyType": "msg", + "value": "dryer", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "start", + "valueType": "str" + } + ], + "x": 200, + "y": 200, + "wires": [ + [ + "54a88968618ed545" + ] + ] + }, + { + "id": "e8999bcf95ae6e5e", + "type": "ha-button", + "z": "b7d34d3e9d0c9486", + "name": "Washer Cancel", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "52dccff6d2c39667", + "outputProperties": [ + { + "property": "timestamp", + "propertyType": "msg", + "value": "", + "valueType": "date" + }, + { + "property": "topic", + "propertyType": "msg", + "value": "washer", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "cancel", + "valueType": "str" + } + ], + "x": 180, + "y": 260, + "wires": [ + [ + "54a88968618ed545" + ] + ] + }, + { + "id": "dffb38d3d2973f4b", + "type": "ha-button", + "z": "b7d34d3e9d0c9486", + "name": "Dryer Cancel", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "9da998d5f74df33c", + "outputProperties": [ + { + "property": "timestamp", + "propertyType": "msg", + "value": "", + "valueType": "date" + }, + { + "property": "topic", + "propertyType": "msg", + "value": "dryer", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "cancel", + "valueType": "str" + } + ], + "x": 190, + "y": 320, + "wires": [ + [ + "54a88968618ed545" + ] + ] + }, + { + "id": "d983ad1b9e54e1d5", + "type": "server-events", + "z": "b7d34d3e9d0c9486", + "name": "Washer Finish", + "server": "9e87348d.9c1c48", + "version": 2, + "eventType": "timer.finished", + "exposeToHomeAssistant": false, + "eventData": "{\"entity_id\":\"timer.washer_timer\"}", + "haConfig": [ + { + "property": "name", + "value": "" + }, + { + "property": "icon", + "value": "" + } + ], + "waitForRunning": true, + "outputProperties": [ + { + "property": "timestamp", + "propertyType": "msg", + "value": "", + "valueType": "date" + }, + { + "property": "topic", + "propertyType": "msg", + "value": "washer", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "finish", + "valueType": "str" + } + ], + "x": 190, + "y": 380, + "wires": [ + [ + "48b8af9419ed18d6" + ] + ] + }, + { + "id": "74fd80d93138997a", + "type": "server-events", + "z": "b7d34d3e9d0c9486", + "name": "Dryer Finish", + "server": "9e87348d.9c1c48", + "version": 2, + "eventType": "timer.finished", + "exposeToHomeAssistant": false, + "eventData": "{\"entity_id\":\"timer.dryer_timer\"}", + "haConfig": [ + { + "property": "name", + "value": "" + }, + { + "property": "icon", + "value": "" + } + ], + "waitForRunning": true, + "outputProperties": [ + { + "property": "timestamp", + "propertyType": "msg", + "value": "", + "valueType": "date" + }, + { + "property": "topic", + "propertyType": "msg", + "value": "dryer", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "finish", + "valueType": "str" + } + ], + "x": 190, + "y": 440, + "wires": [ + [ + "48b8af9419ed18d6" + ] + ] + }, + { + "id": "fd8f610799588222", + "type": "comment", + "z": "b7d34d3e9d0c9486", + "name": "See if it's possible to alter a running timer by the amount an input number has changed by", + "info": "I'd like to be able to have the timer add or subtract the amount that the input number changed.", + "x": 370, + "y": 660, + "wires": [] + }, + { + "id": "2f59c100baa80e72", + "type": "api-call-service", + "z": "b7d34d3e9d0c9486", + "name": "Notify", + "server": "9e87348d.9c1c48", + "version": 5, + "debugenabled": false, + "domain": "script", + "service": "text_notify", + "areaId": [], + "deviceId": [], + "entityId": [], + "data": "", + "dataType": "jsonata", + "mergeContext": "", + "mustacheAltTags": false, + "outputProperties": [], + "queue": "none", + "x": 670, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "7f4ccf77cb54cbf4", + "type": "api-call-service", + "z": "b7d34d3e9d0c9486", + "name": "Services", + "server": "9e87348d.9c1c48", + "version": 5, + "debugenabled": false, + "domain": "", + "service": "", + "areaId": [], + "deviceId": [], + "entityId": [], + "data": "", + "dataType": "json", + "mergeContext": "", + "mustacheAltTags": false, + "outputProperties": [], + "queue": "none", + "x": 680, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "3c238eb49495cdf7", + "type": "api-call-service", + "z": "b7d34d3e9d0c9486", + "name": "Timers", + "server": "9e87348d.9c1c48", + "version": 5, + "debugenabled": false, + "domain": "", + "service": "", + "areaId": [], + "deviceId": [], + "entityId": [], + "data": "", + "dataType": "json", + "mergeContext": "", + "mustacheAltTags": false, + "outputProperties": [], + "queue": "none", + "x": 670, + "y": 140, + "wires": [ + [] + ] + }, { "id": "9256f78107619596", "type": "ha-time",