UNPKG

node-red-contrib-xmihome

Version:

Node-RED nodes for controlling Xiaomi Mi Home devices using the xmihome library.

1 lines 13.6 kB
[{"id":"init-subscription","type":"inject","z":"a1b2c3d4e5f6a7b8","name":"Subscription","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"subscription","x":110,"y":100,"wires":[["3cb48b40878a4957"]]},{"id":"kettle-subscribe","type":"xmihome-device","z":"a1b2c3d4e5f6a7b8","settings":"e7713296f5f01d59","name":"Subscription Kettle Status","device":"device","deviceType":"msg","action":"subscribe","property":"status","propertyType":"str","value":"","valueType":"str","topic":"notify","topicType":"str","x":450,"y":100,"wires":[["kettle-logic"],["error-filter"]]},{"id":"kettle-logic","type":"function","z":"a1b2c3d4e5f6a7b8","name":"Kettle Logic","func":"const state = context.get('state') || {};\nconst SAFE_TEMP = 40, HEAT_TEMP = 80;\n\nfunction updateOutputs(fill, shape, text, temp, action, armed) {\n\tnode.status({ fill, shape, text });\n\tnode.send([null, { payload: temp }, { payload: action }, { payload: armed }]);\n}\n\nfunction sendCommand(payload) {\n\tnode.log(`Sending: ${JSON.stringify(payload)}`);\n\tnode.send([{ payload, topic: msg.topic, device: msg.device }, null, null, null]);\n}\n\nif (!state.initialized) {\n\tObject.assign(state, {\n\t\tkettle_armed: false, heating_request: false, target_temperature: HEAT_TEMP,\n\t\tlast_physical_press_ts: 0, last_command_sent: null, initialized: true\n\t});\n\tcontext.set('state', state);\n\tupdateOutputs('grey', 'ring', 'Initializing...', '--', 'Initializing...', 'Unknown');\n}\n\nswitch (msg.topic) {\n\tcase 'unsubscribe': {\n\t\tsendCommand({ property: 'keep_warm_settings', value: { temperature: HEAT_TEMP, type: 'heat_to_temperature' } });\n\t\treturn null;\n\t};\n\tcase 'disconnect': {\n\t\tstate.initialized = false;\n\t\tcontext.set('state', state);\n\t\tflow.set('kettle_last_status', null);\n\t\tupdateOutputs('red', 'ring', 'Disconnected', '--', 'Disconnected', 'Disconnected');\n\t\treturn null;\n\t};\n\tcase 'heat_water': {\n\t\tif (state.kettle_armed) {\n\t\t\tstate.heating_request = true;\n\t\t\tstate.target_temperature = HEAT_TEMP;\n\t\t\tnode.log(`Heat command: ${HEAT_TEMP}°C`);\n\t\t} else\n\t\t\tnode.warn(\"Ignored: Kettle not armed. Press 'Warm' first.\");\n\t\tbreak;\n\t};\n\tcase 'stop_heating': {\n\t\tnode.log('Stop heating: switching to 40°C');\n\t\tstate.heating_request = false;\n\t\tif (state.kettle_armed) {\n\t\t\tstate.last_command_sent = `heat_to_temperature-${SAFE_TEMP}`;\n\t\t\tsendCommand({ property: 'keep_warm_settings', value: { temperature: SAFE_TEMP, type: 'heat_to_temperature' } });\n\t\t\treturn null;\n\t\t}\n\t\tbreak;\n\t};\n\tdefault: {\n\t\tif (msg.payload?.action && msg.payload?.mode) {\n\t\t\tconst status = msg.payload;\n\t\t\tflow.set('kettle_last_status', status);\n\t\t\tconst was_armed = state.kettle_armed;\n\t\t\tstate.kettle_armed = (status.mode === 'keep_warm');\n\t\t\tconst now = Date.now();\n\t\t\tif (state.kettle_armed && !was_armed) {\n\t\t\t\tif (now - state.last_physical_press_ts < 2000) {\n\t\t\t\t\tnode.log('Double-press detected, starting heating');\n\t\t\t\t\tstate.heating_request = true;\n\t\t\t\t\tstate.target_temperature = status.keep_warm_set_temperature > SAFE_TEMP ?\n\t\t\t\t\t\tstatus.keep_warm_set_temperature : HEAT_TEMP;\n\t\t\t\t}\n\t\t\t\tstate.last_physical_press_ts = now;\n\t\t\t} else if (!state.kettle_armed && was_armed) {\n\t\t\t\tnode.log('Kettle disarmed - stopping heating');\n\t\t\t\tstate.heating_request = false;\n\t\t\t\tstate.last_physical_press_ts = now;\n\t\t\t}\n\t\t}\n\t};\n};\n\nconst current_status = flow.get('kettle_last_status');\nif (current_status) {\n\tconst { current_temperature: temp, action } = current_status;\n\tconst armed_text = state.kettle_armed ? 'Armed' : 'Disarmed';\n\tlet status_fill = state.kettle_armed ? 'green' : 'grey';\n\tlet status_text = `${armed_text} | ${action} | ${temp}°C`;\n\tlet desired_command = null;\n\tif (state.heating_request && state.kettle_armed) {\n\t\tstatus_text = `Heating to ${state.target_temperature}°C (${temp}°C)`;\n\t\tstatus_fill = 'blue';\n\t\tdesired_command = { property: 'keep_warm_settings', value: { temperature: state.target_temperature, type: 'heat_to_temperature' } };\n\t\tif (temp >= state.target_temperature && action === 'keeping_warm') {\n\t\t\tnode.log(`Target ${state.target_temperature}°C reached! Maintaining.`);\n\t\t\tstate.heating_request = false;\n\t\t\tstatus_text = `Target reached! Maintaining ${temp}°C`;\n\t\t\tstatus_fill = 'green';\n\t\t}\n\t} else if (!state.kettle_armed) {\n\t\tdesired_command = { property: 'keep_warm_settings', value: { temperature: SAFE_TEMP, type: 'boil_and_cool_down' } };\n\t\tstatus_text = `Cooling mode (${temp}°C)`;\n\t\tstatus_fill = 'grey';\n\t}\n\tif (desired_command) {\n\t\tconst command_key = `${desired_command.value.type}-${desired_command.value.temperature}`;\n\t\tconst current_key = `${current_status.keep_warm_type}-${current_status.keep_warm_set_temperature}`;\n\t\tif ((command_key !== current_key) && (state.last_command_sent !== command_key)) {\n\t\t\tstate.last_command_sent = command_key;\n\t\t\tsendCommand(desired_command);\n\t\t}\n\t}\n\tupdateOutputs(status_fill, 'dot', status_text, temp, action, armed_text);\n}\n\ncontext.set('state', state);\nreturn null;","outputs":4,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":200,"wires":[["set-kettle-property"],["debug-temp"],["debug-action"],["debug-armed"]]},{"id":"set-kettle-property","type":"xmihome-device","z":"a1b2c3d4e5f6a7b8","settings":"e7713296f5f01d59","name":"Set Kettle Property","device":"device","deviceType":"msg","action":"setProperty","property":"payload.property","propertyType":"msg","value":"payload.value","valueType":"msg","topic":"topic","topicType":"msg","x":650,"y":200,"wires":[["c26359371fce841d"],[]]},{"id":"heat-to-80","type":"inject","z":"a1b2c3d4e5f6a7b8","name":"Heat Water","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"heat_water","x":100,"y":260,"wires":[["3cb48b40878a4957"]]},{"id":"stop-heating","type":"inject","z":"a1b2c3d4e5f6a7b8","name":"Stop Heating","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"stop_heating","x":110,"y":320,"wires":[["3cb48b40878a4957"]]},{"id":"error-filter","type":"switch","z":"a1b2c3d4e5f6a7b8","name":"Error Filter","property":"payload.event","propertyType":"msg","rules":[{"t":"eq","v":"error","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":130,"y":40,"wires":[["reconnect-delay"]]},{"id":"debug-temp","type":"debug","z":"a1b2c3d4e5f6a7b8","name":"Temperature","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":610,"y":380,"wires":[]},{"id":"debug-action","type":"debug","z":"a1b2c3d4e5f6a7b8","name":"Action","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":590,"y":440,"wires":[]},{"id":"debug-armed","type":"debug","z":"a1b2c3d4e5f6a7b8","name":"Armed State","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":610,"y":500,"wires":[]},{"id":"3cb48b40878a4957","type":"change","z":"a1b2c3d4e5f6a7b8","name":"device","rules":[{"t":"set","p":"device","pt":"msg","to":"{ \"mac\": $env('mac'), \"token\": $env('token'), \"model\": \"yunmi.kettle.v2\" }","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":200,"wires":[["3a0611db7d2094de"]]},{"id":"3a0611db7d2094de","type":"switch","z":"a1b2c3d4e5f6a7b8","name":"topic router","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"subscription","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":260,"y":140,"wires":[["kettle-subscribe"],["kettle-logic"]]},{"id":"3c626b36cb443596","type":"inject","z":"a1b2c3d4e5f6a7b8","name":"Unsubscribe","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"unsubscribe","x":110,"y":160,"wires":[["3cb48b40878a4957"]]},{"id":"493bdf6233c373df","type":"xmihome-device","z":"a1b2c3d4e5f6a7b8","settings":"e7713296f5f01d59","name":"Unsubscription Kettle Status","device":"device","deviceType":"msg","action":"unsubscribe","property":"status","propertyType":"str","value":"","valueType":"str","topic":"topic","topicType":"msg","x":600,"y":300,"wires":[[],[]]},{"id":"reconnect-delay","type":"delay","z":"a1b2c3d4e5f6a7b8","name":"Reconnect Delay","pauseType":"delay","timeout":"30","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":310,"y":40,"wires":[["1f3160aedb50eac4"]]},{"id":"1f3160aedb50eac4","type":"change","z":"a1b2c3d4e5f6a7b8","name":"subscription","rules":[{"t":"set","p":"topic","pt":"msg","to":"subscription","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":40,"wires":[["3cb48b40878a4957"]]},{"id":"c26359371fce841d","type":"switch","z":"a1b2c3d4e5f6a7b8","name":"if unsubscription","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"unsubscribe","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":640,"y":260,"wires":[["493bdf6233c373df","set-disconnect-status"]]},{"id":"set-disconnect-status","type":"change","z":"a1b2c3d4e5f6a7b8","name":"Set Disconnect Status","rules":[{"t":"set","p":"topic","pt":"msg","to":"disconnect","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":620,"y":340,"wires":[["3cb48b40878a4957"]]},{"id":"bd756171d8f2460e","type":"comment","z":"a1b2c3d4e5f6a7b8","name":"README","info":"# 📱 Управление чайником Xiaomi через Node-RED\n*Xiaomi Kettle Control via Node-RED*\n\n## 🔧 Настройка / Setup\n1. **Установите чайник на док-станцию** \n *Place the kettle on the docking station*\n\n2. **Укажите MAC-адрес и токен чайника в переменных окружения:** \n *Set kettle MAC address and token in environment variables:*\n - `mac` - MAC-адрес чайника / *Kettle MAC address*\n - `token` - Токен устройства / *Device token*\n\n## 🎮 Управление / Control\n\n### 🟢 Активация чайника / Kettle Activation\n- **Нажмите кнопку \"Warm\" на чайнике** - активирует режим управления \n *Press \"Warm\" button on kettle - activates control mode*\n\n### 🔥 Запуск нагрева / Start Heating\n**Способ 1:** Быстро дважды нажмите \"Warm\" на чайнике (в течение 2 сек) \n*Method 1: Double-press \"Warm\" button on kettle quickly (within 2 sec)*\n\n**Способ 2:** Нажмите кнопку \"Heat Water\" в Node-RED \n*Method 2: Press \"Heat Water\" button in Node-RED*\n\n➡️ Чайник нагреет воду до 80°C и будет поддерживать температуру \n*Kettle will heat water to 80°C and maintain temperature*\n\n### ⏹️ Остановка нагрева / Stop Heating\n- **Нажмите \"Stop Heating\"** - снизит температуру до 40°C, но останется в активном режиме \n *Press \"Stop Heating\" - lowers temperature to 40°C but keeps active mode*\n\n### 🔴 Полное отключение / Complete Shutdown\n- **Поднимите чайник или отпустите кнопку \"Warm\"** - переведет в режим остывания \n *Lift kettle or release \"Warm\" button - switches to cooling mode*\n\n## 📊 Индикаторы состояния / Status Indicators\n\n| Статус | Описание | *Status* | *Description* |\n|--------|----------|-----------|---------------|\n| 🔵 Синий | Нагрев до целевой температуры | *Blue* | *Heating to target temperature* |\n| 🟢 Зеленый | Поддержание температуры | *Green* | *Maintaining temperature* |\n| 🟡 Желтый | Переключение режима | *Yellow* | *Switching mode* |\n| ⚪ Серый | Режим ожидания/остывания | *Gray* | *Standby/cooling mode* |\n| 🔴 Красный | Отключен/ошибка | *Red* | *Disconnected/error* |\n\n## ⚠️ Важно / Important\n- **Чайник должен быть на док-станции** для работы по Bluetooth \n *Kettle must be on docking station for Bluetooth operation*\n- **Кнопка \"Warm\" должна быть активна** для удаленного управления \n *\"Warm\" button must be active for remote control*\n- **Автоматическое переподключение** при потере связи каждые 30 секунд \n *Automatic reconnection on connection loss every 30 seconds*\n\n## 🔄 Логика работы / Operation Logic\n1. **Нагрев (heat_to_temperature)** - точный контроль температуры, НЕ кипятит \n *Heating (heat_to_temperature) - precise temperature control, does NOT boil*\n2. **Остывание (boil_and_cool_down)** - используется только при отключении \n *Cooling (boil_and_cool_down) - used only when kettle is off*","x":700,"y":40,"wires":[]},{"id":"e7713296f5f01d59","type":"xmihome-config","name":"ble","debug":true,"connectionType":"bluetooth"}]