├── .eslintrc.js ├── .github └── workflows │ ├── node.js-ci.yml │ └── npm.yml ├── .gitignore ├── .npmignore ├── .vscode └── tasks.json ├── CONTRIBUTING.md ├── LICENSE ├── README.MD ├── _template.html ├── _template.js ├── createNode.ps1 ├── dashboard └── zigbee2mqtt_dashboard_node_red.json ├── docker-compose.yml ├── docs ├── config │ ├── bridge-config.md │ ├── device-config.md │ ├── img │ │ ├── bridge-config-config.png │ │ ├── device-config-config-nonz2m-support.png │ │ ├── device-config-config.png │ │ └── mqtt-config-config.png │ └── mqtt-config.md ├── docker-compose.yml ├── documentation.md ├── getting-started.md ├── img │ ├── 0-18-update-delete-old-nodes.png │ ├── 0-18-update-reconfigue-bridge.png │ ├── 0.18-update-new-mqttt-config.png │ ├── getting-started-flow01-send-messages.png │ ├── getting-started-flow02-bridge-config.png │ ├── getting-started-flow03-mqtt-config.png │ ├── getting-started-flow04-connected.png │ ├── getting-started-flow05-generic-lamp.png │ ├── getting-started-flow06-hue-device-config.png │ ├── getting-started-flow07-brightness-color-hue.png │ ├── getting-started-flow08-inject-node.png │ ├── getting-started-flow09-toggle-device-config.png │ ├── getting-started-flow10-toggle.gif │ ├── getting-started-flow11-off-device-config.png │ ├── getting-started-flow12-on-off-flow-1.png │ ├── getting-started-flow12-on-off-flow-2.png │ ├── getting-started-flow13-on-off-flow-3.gif │ ├── getting-started-flow14-second-lamp-paralell.png │ ├── getting-started-flow15-second-lamp-series.png │ ├── getting-started-flow16-override-nodes.gif │ ├── getting-started-flow17-override-multiple.gif │ ├── getting-started-flow18-inject-override.png │ ├── getting-started-flow19-inject-override-flow.png │ ├── getting-started-install-palette.png │ ├── getting-started-z2m-renamed.png │ └── overview.png └── nodes │ ├── bridge-log.md │ ├── button-switch.md │ ├── climate-sensor.md │ ├── device-status.md │ ├── generic-lamp.md │ ├── get-lamp-state.md │ ├── img │ ├── bridge-log-config-consolidate-output.png │ ├── bridge-log-config.png │ ├── bridge-log-flow-consolidate-output.png │ ├── bridge-log-flow.png │ ├── button-switch-config-hold-repeat.png │ ├── button-switch-config.png │ ├── button-switch-flow.png │ ├── climate-sensor-config.png │ ├── device-status-config-experimental.png │ ├── device-status-config.png │ ├── generic-lamp-config.png │ ├── generic-lamp-flow.png │ ├── get-lamp-state-config.png │ ├── ota-node-autoUpdate-msg.png │ ├── ota-node-config-blacklist.png │ ├── ota-node-config.png │ ├── ota-node-context-updates-available.png │ ├── ota-node-example-flow-over-night.png │ ├── ota-node-update-available.png │ ├── ota-node-update-finished.png │ ├── ota-node-update-in-progress.png │ ├── ota-node-update-progress-output.png │ ├── override-action-config.png │ ├── override-brightness-config.png │ ├── override-color-config.png │ ├── override-nodes-example.png │ ├── override-state-config.png │ ├── override-temperature-config.png │ ├── scene-in-config.png │ ├── scene-selector-config.png │ ├── scene-selector-example.png │ ├── send-messages-config.png │ ├── shelly25-config.png │ ├── shelly25-listen-input.png │ ├── shelly25-toggle-relay-settings.png │ └── shelly25-toggle-relay.png │ ├── ota-node.md │ ├── override-action.md │ ├── override-brightness.md │ ├── override-color.md │ ├── override-nodes.md │ ├── override-state.md │ ├── override-temperature.md │ ├── scene-in.md │ ├── scene-selector.md │ ├── send-messages.md │ └── shelly-25-node.md ├── examples ├── getting-started │ ├── one-lamp-on-off.json │ └── two-lamps-override.json ├── ota │ └── ota_over_night.json ├── scenes │ └── basic_scene.json └── shelly-25 │ ├── shelly25-listen-to-input.json │ └── shelly25-toggle-relay.json ├── install.ps1 ├── package-lock.json ├── package.json ├── resources ├── logo.svg └── logo_white_bg.svg ├── robocopy.ps1 ├── src ├── _api.html ├── _api.js ├── bridge-log.html ├── bridge-log.js ├── device-types.ts ├── icons │ ├── remote-black.svg │ └── remote.svg ├── lib │ ├── mqtt.ts │ ├── outputHandler.js │ └── utils.js ├── nodes │ ├── devices-eurotronic.html │ ├── devices-eurotronic.js │ ├── devices-ikea.html │ ├── devices-ikea.js │ ├── devices-scenic.html │ ├── devices-scenic.js │ ├── devices-sonoff.html │ ├── devices-sonoff.js │ ├── devices-tasmota.html │ ├── devices-tasmota.js │ ├── devices-tint.html │ ├── devices-tint.js │ ├── hue-dimmer │ │ ├── devices-hue.html │ │ └── devices-hue.js │ ├── override │ │ ├── override-nodes.html │ │ ├── override-nodes.ts │ │ └── types.ts │ ├── scenes.html │ ├── scenes.js │ ├── sensors.html │ └── sensors.js ├── non-z2m-nodes │ ├── devices-shelly-25.html │ └── devices-shelly-25.js ├── ota.html ├── ota.js ├── types.ts ├── zigbee2mqtt-config.html ├── zigbee2mqtt-config.ts ├── zigbee2mqtt.html └── zigbee2mqtt.js ├── test-integration ├── docker │ ├── .gitignore │ ├── docker-compose.yml │ └── mosquitto │ │ └── config │ │ └── mosquitto.conf └── non-z2m-nodes │ └── devices-shelly-25_spec.js ├── test ├── scenes_scene-selector_spec.js └── zigbee2mqtt_generic-lamp_spec.js ├── tsconfig.json └── upcoming-changelog.md /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true, 6 | "amd": true, 7 | "mocha": true 8 | }, 9 | "extends": "eslint:recommended", 10 | "parserOptions": { 11 | "ecmaVersion": 12, 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "indent": [ 16 | "error", 17 | 4, 18 | { 19 | "SwitchCase":1 20 | } 21 | 22 | ], 23 | "linebreak-style": [ 24 | "off", 25 | "unix" 26 | ], 27 | "quotes": [ 28 | "warn", 29 | "double" 30 | ], 31 | "semi": [ 32 | "error", 33 | "always" 34 | ], 35 | "no-unused-vars" : [ 36 | "off" 37 | ] 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /.github/workflows/node.js-ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm ci 28 | - run: npm run build 29 | - run: npm run test 30 | - run: npm run testintegration 31 | - run: npm run lint 32 | - run: npm install -g node-red-dev 33 | - run: node-red-dev validate 34 | 35 | -------------------------------------------------------------------------------- /.github/workflows/npm.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Release Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | publish-npm: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 12 18 | registry-url: https://registry.npmjs.org/ 19 | - run: npm ci 20 | - run: npm run build 21 | - run: npm publish 22 | env: 23 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | debug 4 | node_modules 5 | dist 6 | node-red-contrib-zigbee2mqtt-devices*.tgz -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .*/ 2 | src/ 3 | test/ 4 | test-integration/ 5 | debug 6 | *.ps1 7 | docker-compose.yml 8 | tsconfig.json 9 | _template.html 10 | _template.js 11 | upcoming-changelog.md 12 | docs/ 13 | .eslintrc.js 14 | dashboard/ 15 | resources/ 16 | node-red-contrib-zigbee2mqtt-devices*.tgz 17 | CONTRIBUTING.md -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "node-red deploy", 6 | "command": "npm run builddocker", 7 | "type": "shell", 8 | "problemMatcher": [], 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Everyone is welcome to create a pull request. If you are unsure what or how to do something, we can discuss it on [GitHub](https://github.com/Dirnei/node-red-contrib-zigbee2mqtt-devices) or [Discord](https://discord.gg/4qCMEhJ). 4 | 5 | 6 | # Development Environment 7 | 8 | The project is written in JavaScript, HTML, and Typescript. 9 | 10 | For easier development, we have a couple of npm tasks: 11 | 12 | `npm run build` calls the typescript compiler and copies the HTML and JavaScript files to the `dist/` folder. 13 | 14 | `npm run builddocker` runs the build task and launches the project within a Node-RED docker container. 15 | In VS-Code you can also press Ctrl + Shift + B to run this task 16 | 17 | `npm run lint` calls eslint. 18 | 19 | `npm run createnode` to create a new JavaScript node. There is no template for a Typescript node yet. 20 | 21 | `npm test` runs the unit tests in the `test` folder. 22 | 23 | `npm testintegration` runs the integration tests in the `test-integration` folder. It starts the services in `test-integration/docker` and runs the integration tests against those services. 24 | 25 | # System requirements 26 | Developing is possible on Windows, Linux, and macOS. The IDE is up to you; we use VS-Code. 27 | 28 | - **Node.js & NPM** - One of the [LTS releases](https://nodejs.org/en/about/releases/) 29 | - **Docker & Docker Compose** - It's the easiest way to run the nodes with `npm run builddocker` 30 | - **Powershell**. On Windows, it is preinstalled. On Linux and macOS, you have to [install](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.1) it. 31 | - **rsync** - only required on Linux and macOS. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dirnei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Zigbee2MQTT Nodes for Node-RED 2 | 3 | [![npm](https://img.shields.io/npm/v/node-red-contrib-zigbee2mqtt-devices?style=for-the-badge)](https://www.npmjs.com/package/node-red-contrib-zigbee2mqtt-devices) 4 | [![img](https://img.shields.io/badge/Node--RED-node--red--contrib--zigbee2mqtt--devices-%23aa4444?style=for-the-badge)](https://flows.nodered.org/node/node-red-contrib-zigbee2mqtt-devices) 5 | [![GitHub license](https://img.shields.io/github/license/Dirnei/node-red-contrib-zigbee2mqtt-devices?style=for-the-badge)](https://github.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/blob/master/LICENSE) 6 | 7 | 8 |

9 | 10 |

11 | 12 | This project contains Node-RED nodes that allow you to build Smart Home scenarios with your Zigbee devices connected to [ZigBee2MQTT](https://www.zigbee2mqtt.io/). 13 | 14 | ![overview](docs/img/overview.png) 15 | 16 | ## Available Nodes 17 | 18 | Many nodes are available right now, and there are still new node ideas that will be added in the near future. Here is a list of the currently available ones: 19 | 20 | - [generic-lamp](docs/nodes/generic-lamp.md) 21 | - [send-messages](docs/nodes/send-messages.md) 22 | - [override-nodes](docs/nodes/override-nodes.md) 23 | - [button-switch](docs/nodes/button-switch.md) 24 | - [scene-in](docs/nodes/scene-in.md) 25 | - [scene-selector](docs/nodes/scene-selector.md) 26 | - [ota-node](docs/nodes/ota-node.md) 27 | - [bridge-log](docs/nodes/bridge-log.md) 28 | - And many more for sensors and remotes... 29 | 30 | ## Getting started 31 | 32 | Have a look at [the getting started guide.](docs/getting-started.md). All you need are some already paired Zigbee lamps and an installed Node-RED. What are you waiting for? Find out now how easy it is to get started. 33 | 34 | ## Documentation 35 | 36 | The [documentation](docs/documentation.md) for the project is located in the `docs` folder. 37 | 38 | ## You need some addition help? 39 | 40 | Join the [Discord server](https://discord.gg/4qCMEhJ) for a more in depth support or problems that aren't metioned anywhere :) 41 | 42 | [![node-red-contrib-zigbee2mqtt-devices](https://discordapp.com/api/guilds/760063909465686067/widget.png?style=banner2)](https://discord.gg/4qCMEhJ) 43 | 44 | ## You want to contribute? 45 | 46 | That's easy! Feel free to post suggestions, adding documentation or add new features. Every support is welcome! 47 | 48 | If you feel very generous and want to contribute with coffee, you sure can: 49 | 50 | [![Buy me a coffee][buymeacoffee-shield]][buymeacoffee] 51 | 52 | [buymeacoffee]: https://www.buymeacoffee.com/dirnei 53 | [buymeacoffee-shield]: https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png 54 | 55 | ## Changelog 56 | 57 | ### Release: `0.20.0` _3 Jan 2024_ 58 | 59 | #### Features: 60 | 61 | - Add support for Ikea Styrbar (E2001) remote control 62 | 63 | #### Bug fixes: 64 | 65 | - Setting brightnes to 0 did not turn it off. Now it will override the state to "OFF" 66 | - Fixed Scenic Friend of Hue switch. Property names has changed between versions 67 | 68 | ### Release: `0.19.6` _13 Nov 2022_ 69 | 70 | #### Features: 71 | 72 | #### Bug fixes: 73 | - Bridge did not connect in newer z2m versions because z2m changed the format of 'bridge/state' from string to JSON. 74 | - Shelly 2.5 did not connect to the broker, because it used an old configuration node. 75 | - Shelly 2.5 node did not unsubscribe from the old channel with no full redeploy. 76 | 77 | #### Behind the scenes 78 | - Add mocha and first-unit test examples 79 | - Add integration tests to test with a real MQTT broker 80 | 81 | ### Release: `0.19.5` - _14 Mar 2022_ 82 | 83 | #### Bug fixes: 84 | 85 | - Hosting the Node-RED UI under a different root path than `http://localhost:1880/` resulted in failing web requests to load the device list. For example, when the UI was set to `http://localhost:1880/admin` or Node-RED was running as a Home Assistant plugin. 86 | - Fixed a bug where the configured device name gets deleted, when the device list couldn't be loaded. Further details in #105 and #119 87 | 88 | ### Release: `0.19.4` - _01 Jan 2022_ 89 | 90 | Happy new year to all! 91 | 92 | #### Bug fixes: 93 | - @itupsk fixed the issue #114, where we forgot a null check on the ikea bugfix in the last release. Thanks a lot! 94 | - Also fixed the same issue in hue, scenic and sonoff devices 95 | 96 | ### Release: `0.19.3` - _30 Dec 2021_ 97 | 98 | #### Features: 99 | - Documented additional settings for upgrading to mosquitto 2.0 in the getting started guide. 100 | - Documented Mirek scale. 101 | 102 | #### Bug fixes: 103 | - Not set property on Ikea Remote device caused a complete Node-Red restart 104 | - Not set property on Hue Remote device caused a complete Node-Red restart 105 | - Preventive measure: Check action for empty string in scenic remote and sonnoff buttons 106 | 107 | ### Release: `0.19.2` - 12 Mar 2021 108 | 109 | #### Bug fixes: 110 | 111 | - Local node-red installation with nodejs 12 had a problem to load the node-red dependency. Removed it for now as it is only used for logging. 112 | 113 | ### Release: `0.19.1` - 8 Mar 2021 114 | 115 | #### Bug fixes: 116 | 117 | - Better handling for invalid MQTT messages from z2m. Sometimes a required/expected property is missing or empty which caused an error. 118 | - Hue Dimmer switch crashed NodeRED because the power status with no action came with no action. 119 | 120 | #### Behind the scenes 121 | 122 | - Removed some unnecessary files from the package, reducing the size from 324 kB to 229 kB 123 | - Removed vulnerable dependencies 124 | - Updated and thinned out dependencies so the installation will be faster 125 | - Dev feature: Cleaned up npm build scripts so there are no warnings, and they behave the same on Windows, Linux, and macOS. 126 | 127 | ### Release: `0.19.0` - 23 Jan 2021 128 | 129 | #### Features: 130 | - Two new example flows, from the getting started guide 131 | - Documentation overview page 132 | - Documented examples 133 | - Show **switch to manual** button if the device-list request failed. 134 | - Added `deviceName` to the ouput of the climate-sensor. Resolves #75 135 | - Added `separateOuputs` option the the climate sensor. Resolves #75 136 | 137 | ### Older changelogs 138 | 139 | You can find all the release notes at the [Release-Page](https://github.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/releases) 140 | -------------------------------------------------------------------------------- /_template.html: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 32 | 33 | -------------------------------------------------------------------------------- /_template.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | const utils = require("dist/lib/utils.js"); 3 | const bavaria = utils.bavaria(); 4 | 5 | function $$NAME_FUNC$$(config) { 6 | RED.nodes.createNode(this, config); 7 | var node = this; 8 | 9 | node.on("input", function (msg) { 10 | }); 11 | 12 | node.on("close", function () { 13 | }); 14 | } 15 | 16 | RED.nodes.registerType("$$NAME_NODE$$", $$NAME_FUNC$$); 17 | }; -------------------------------------------------------------------------------- /createNode.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory = $True)] 3 | [string]$NodeName, 4 | [Parameter(Mandatory = $True)] 5 | [string]$Label, 6 | [Parameter(Mandatory = $True)] 7 | [string]$FuncName, 8 | [Parameter(Mandatory = $True)] 9 | [string]$Category, 10 | [Parameter(Mandatory = $True)] 11 | [int]$Inputs, 12 | [Parameter(Mandatory = $True)] 13 | [int]$Outputs 14 | ) 15 | 16 | Write-Host "=============================================================================" 17 | Write-Host " Name of the node : $NodeName" 18 | Write-Host " Display-label value : $Label" 19 | Write-Host " Creating func name : $FuncName" 20 | Write-Host " Category : $Category" 21 | Write-Host " Inputs : $Inputs" 22 | Write-Host " Outputs : $Outputs" 23 | Write-Host "=============================================================================" 24 | 25 | $nodeJsPath = "src/$NodeName.js" 26 | $nodeHtmlPath = "src/$NodeName.html" 27 | 28 | Write-Host " Copy templates..." 29 | Copy-Item -Path "_template.js" -Destination $nodeJsPath 30 | Copy-Item -Path "_template.html" -Destination $nodeHtmlPath 31 | 32 | Write-host " Replacing tokens..." 33 | (Get-Content -path $nodeJsPath -Raw).Replace('$$NAME_FUNC$$', $FuncName).Replace('$$NAME_NODE$$', $NodeName) | Set-Content -Path $nodeJsPath 34 | (Get-Content -path $nodeHtmlPath -Raw).Replace('$$NAME_LABEL$$', $Label).Replace('$$NAME_NODE$$', $NodeName).Replace('$$NAME_UPPER$$', $NodeName.ToUpper()).Replace('$$CATEGORY$$', $Category).Replace('$$INPUTS$$', $Inputs).Replace('$$OUTPUTS$$', $Outputs) | Set-Content -Path $nodeHtmlPath 35 | Write-host " Finished!" 36 | Write-Host "=============================================================================" 37 | Write-Host -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | version: "3.8" 3 | 4 | services: 5 | nodered: 6 | image: nodered/node-red:latest-12 7 | container_name: nodered_testing 8 | volumes: 9 | - ./debug/:/data 10 | ports: 11 | - "1880:1880" 12 | privileged: true -------------------------------------------------------------------------------- /docs/config/bridge-config.md: -------------------------------------------------------------------------------- 1 | # Bridge configuration 2 | 3 | Define the connection to your MQTT-Broker and the base MQTT-Topic, which is defined in the zigbee2mqtt configuration.yaml. 4 | 5 | ![img](img/bridge-config-config.png) 6 | 7 | --- 8 | 9 | ## Broker 10 | 11 | Detailed information about the mqtt-config can be found [here](mqtt-config.md) 12 | 13 | --- 14 | 15 | ## Base MQTT Topic 16 | 17 | This is defined in your zigbee2mqtt ```configuration.yaml``` 18 | 19 | Default value is __zigbee2mqtt__ 20 | 21 | --- 22 | 23 | ## Output Bridge logs 24 | 25 | If enabled, logs published to topic `zigbee2mqtt/bridge/logging` will be printed on the debug tab. 26 | 27 | > Default: off 28 | 29 | > Only messages with log-level warning and error will be written to the debug tab. The info log-level adds to much noise, because every published message will also be published under the same topic. 30 | (Current discussion about this topic: https://github.com/Koenkk/zigbee2mqtt/discussions/5633) 31 | 32 | --- 33 | 34 | ## Allow Device Status Refresh 35 | 36 | To reduce the amount of messages that will be sent, you can turn off the status refresh messages, as they only are used to update the state of the node and for nothing else. 37 | 38 | > Default: on 39 | 40 | 41 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/config/device-config.md: -------------------------------------------------------------------------------- 1 | # Device configuration 2 | 3 | ![img](img/device-config-config.png) 4 | 5 | ## Bridge 6 | 7 | For more information see [bridge-config](../config/bridge-config.md) 8 | 9 | ## Device 10 | 11 | Select your device from the list. If nothing is shown here, you have probably just created a new bridge configuration, and you need to deploy it first in order to get a result here. If not, open a ticket and explain as detailed as possible what you have done :) 12 | 13 | ## Dimmable 14 | 15 | Define here if your lamp supports a brightness change. 16 | 17 | >If this is not checked, the generic-lamp configuration view will hide the brightness field from you. 18 | 19 | ## Temperate support 20 | 21 | Define here if your lamp supports color temperature. 22 | 23 | > If this is not checked, the generic-lamp configuration view will hide the color temperature field from you. 24 | 25 | ## Color support 26 | 27 | Define here if your lamp supports RGB colors. 28 | 29 | > If this is not checked, the generic-lamp configuration view will hide the RGB color fields from you. 30 | 31 | ## Experimental non z2m device support 32 | 33 | It is planned to also support non-z2m devices that can be controlled by MQTT. It is highly experimental and does not have a high priority right now. It will get more attention when the project is near version 1.0 34 | 35 | For now it is only supported in the [generic-lamp](../nodes/generic-lamp.md) and [send-messages](../nodes/send-messages.md) nodes. 36 | 37 | ![img](img/device-config-config-nonz2m-support.png) 38 | 39 | ### Status topic 40 | 41 | Devices should send there state change to this topic. 42 | 43 | > Can be conpared with *zigbee2mqtt/friendly_name* 44 | 45 | ### Command topic 46 | 47 | When something is published to this topic, the device should change its state. It has to be able to parse the payload that is also be sent to zigbee2mqtt 48 | 49 | > Can be conpared with *zigbee2mqtt/friendly_name/set* 50 | 51 | ### Refresh topic 52 | 53 | When something is published to this topic, the device should respond with it's current state to the **status topic**. It has to be the same payload that zigbee2mqtt sends when a ZigBee device changes it state. 54 | 55 | > Can be conpared with *zigbee2mqtt/friendly_name/get* 56 | 57 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/config/img/bridge-config-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/config/img/bridge-config-config.png -------------------------------------------------------------------------------- /docs/config/img/device-config-config-nonz2m-support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/config/img/device-config-config-nonz2m-support.png -------------------------------------------------------------------------------- /docs/config/img/device-config-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/config/img/device-config-config.png -------------------------------------------------------------------------------- /docs/config/img/mqtt-config-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/config/img/mqtt-config-config.png -------------------------------------------------------------------------------- /docs/config/mqtt-config.md: -------------------------------------------------------------------------------- 1 | # MQTT configuration 2 | 3 | Define the connection to your MQTT-Broker here. This is the same configuration used in the `mqtt-in` and `mqtt-out` node-red core nodes. 4 | 5 | ![img](img/mqtt-config-config.png) 6 | 7 | 8 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | zigbee2mqtt: 5 | image: koenkk/zigbee2mqtt:latest 6 | container_name: z2m_composed 7 | volumes: 8 | - /run/udev:/run/udev:ro 9 | - ${PWD}/z2m:/app/data 10 | ports: 11 | - "8080:8080" 12 | environment: 13 | - TZ=Europe/Berlin 14 | devices: 15 | - device=/dev/ttyACM0 16 | network_mode: "host" 17 | restart: always 18 | privileged: true 19 | depends_on: 20 | - mosquitto 21 | 22 | mosquitto: 23 | image: eclipse-mosquitto 24 | container_name: mosquitto_composed 25 | volumes: 26 | - ${PWD}/mosquitto:/mosquitto 27 | ports: 28 | - "1883:1883" 29 | - "9001:9001" 30 | restart: always 31 | 32 | nodered: 33 | image: nodered/node-red 34 | container_name: nodered_composed 35 | volumes: 36 | - ${PWD}/nodered:/data 37 | ports: 38 | - "1880:1880" 39 | restart: always 40 | depends_on: 41 | - zigbee2mqtt 42 | - mosquitto -------------------------------------------------------------------------------- /docs/documentation.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # Zigbee2MQTT Nodes for Node-RED Documentation 6 | 7 | The Zigbee2MQTT Nodes for Node-REDs allow you to build Smart Home scenarios with your Zigbee 8 | devices connected to [ZigBee2MQTT](https://www.zigbee2mqtt.io/). 9 | 10 | 11 | ## 1. Getting started: 12 | The [**Getting started guide**](getting-started.md) contains an 13 | exemplary tutorial on setting up Node-RED, Zigbee2MQTT, etc., and how to define your first flow 14 | with the Zigbee2MQTT nodes for Node-RED. The guide will give you some examples of how to use the 15 | nodes and insight into the inner workings of the messages the library uses. 16 | 17 | If you already paired Zigbee lamps and an installed Node-RED, you can directly skip to the 18 | [Getting started guide: define your first flow](getting-started.md#define-your-first-flow) 19 | section. 20 | 21 | 22 | ## 2. Node documentation 23 | 24 | ### 2.1 Documentation per node 25 | 26 | - [generic-lamp](nodes/generic-lamp.md) Used to define which lamps you want to control. 27 | - [send-messages](nodes/send-messages.md) Prepares and sends the mqtt messages to the MQTT-Broker. 28 | - [override-nodes](nodes/override-nodes.md) Overrides the properties that are set a generic-lamp. 29 | - [override-state](nodes/override-state.md) Overrides the `ON`/`OFF` state. 30 | - [override-brightness](nodes/override-brightness.md) Overrides the brightness of a lamp. 31 | - [override-temperature](nodes/override-temperature.md) Overrides light temperature of a lamp. 32 | - [override-color](nodes/override-color.md) Overrides the light color of a lamp. 33 | - [override-action](nodes/override-action.md) Modify brightness/color over time or in steps. 34 | - [button-switch](nodes/button-switch.md) Is used after a remote node to redirect the message to a seperate output. 35 | - [scene-in](nodes/scene-in.md) Configures a scene which can be activated via the scene-selector. 36 | - [scene-selector](nodes/scene-selector.md) Select scenes in a defined order. 37 | - [device-satus](nodes/device-status.md) Retrieve notifications when a device status changes. 38 | - [get-lamp-state](nodes/get-lamp-state.md) Retrieve the current state of a lamp. 39 | - [ota-node](nodes/ota-node.md) Used to start OTA updates. 40 | - [bridge-log](nodes/bridge-log.md) Filter logs of the Zigbee2MQTT bridge. 41 | - [climate-sensor](nodes/climate-sensor.md) Used to get values from a climate sensor. 42 | 43 | > Note: There are a few nodes for sensors and remotes that have not been documented in the wiki yet, but you can read about them in the internal help documentation. 44 | 45 | ### 2.2 Configs 46 | - [bridge-config](config/bridge-config.md) Select your MQTT-Broker and the base MQTT topic used for Zigbee2MQTT. 47 | - [mqtt-config](config/mqtt-config.md) Configuret the connection and credentials a your MQTT-Broker. 48 | - [device-config](config/device-config.md) Configure devices and capabilities to use them with the nodes. 49 | 50 | 51 | ## 3. Examples 52 | 53 | The examples are already included, if you installed the package. 54 | 55 | Open `Node-RED Menu > Import > Examples > node-red-contrib-zigbee2mqtt-devices` to import the example flows. 56 | 57 | ### Getting started 58 | [**One lamp On/Off**](../examples/getting-started/one-lamp-on-off.json) is a simple example to turn one lamp on and off with two inject nodes. 59 | 60 | ![One lamp On/Off example flow](img/getting-started-flow13-on-off-flow-3.gif) 61 | 62 | 63 | [**Two lamps override**](../examples/getting-started/two-lamps-override.json) demonstrates how to use override nodes to control multiple lamps. 64 | 65 | ![inject override example flow](img/getting-started-flow19-inject-override-flow.png) 66 | 67 | ### One Time Update 68 | [**OTA over night**](../examples/ota/ota_over_night.json) enables the auto update between 00:00 and 04:00. 69 | 70 | ![OTA over night example flow](nodes/img/ota-node-example-flow-over-night.png) 71 | 72 | ### Scenes 73 | [**Basic scenes**](../examples/scenes/basic_scene.json) switches scenes with the scene selector. 74 | 75 | ![scene selector example flow](nodes/img/scene-selector-example.png) -------------------------------------------------------------------------------- /docs/img/0-18-update-delete-old-nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/0-18-update-delete-old-nodes.png -------------------------------------------------------------------------------- /docs/img/0-18-update-reconfigue-bridge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/0-18-update-reconfigue-bridge.png -------------------------------------------------------------------------------- /docs/img/0.18-update-new-mqttt-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/0.18-update-new-mqttt-config.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow01-send-messages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow01-send-messages.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow02-bridge-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow02-bridge-config.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow03-mqtt-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow03-mqtt-config.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow04-connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow04-connected.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow05-generic-lamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow05-generic-lamp.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow06-hue-device-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow06-hue-device-config.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow07-brightness-color-hue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow07-brightness-color-hue.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow08-inject-node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow08-inject-node.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow09-toggle-device-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow09-toggle-device-config.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow10-toggle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow10-toggle.gif -------------------------------------------------------------------------------- /docs/img/getting-started-flow11-off-device-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow11-off-device-config.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow12-on-off-flow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow12-on-off-flow-1.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow12-on-off-flow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow12-on-off-flow-2.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow13-on-off-flow-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow13-on-off-flow-3.gif -------------------------------------------------------------------------------- /docs/img/getting-started-flow14-second-lamp-paralell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow14-second-lamp-paralell.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow15-second-lamp-series.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow15-second-lamp-series.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow16-override-nodes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow16-override-nodes.gif -------------------------------------------------------------------------------- /docs/img/getting-started-flow17-override-multiple.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow17-override-multiple.gif -------------------------------------------------------------------------------- /docs/img/getting-started-flow18-inject-override.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow18-inject-override.png -------------------------------------------------------------------------------- /docs/img/getting-started-flow19-inject-override-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-flow19-inject-override-flow.png -------------------------------------------------------------------------------- /docs/img/getting-started-install-palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-install-palette.png -------------------------------------------------------------------------------- /docs/img/getting-started-z2m-renamed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/getting-started-z2m-renamed.png -------------------------------------------------------------------------------- /docs/img/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/img/overview.png -------------------------------------------------------------------------------- /docs/nodes/bridge-log.md: -------------------------------------------------------------------------------- 1 | # Bridge log 2 | 3 | The bridge log node provides an easy way to filter logs that are published into the 4 | `zigbee2mqtt/bridge/log` topic. You configure the types you are interested in, and the node creates one output for each type. This can be helpful if you only want to see specific logs (with the debug node) or perform actions based on the logs. 5 | 6 | In this example, only the log types `pairing` and `device_announced` are selected. The `pairing` logs trigger a web request, the `device_announced` logs are sent to the debug node. 7 | 8 | ![Bridge log flow with debug node and webrequest](img/bridge-log-flow.png) 9 | 10 | 11 | 12 | ## Configuration 13 | 14 | In the configuration, you select the log types you want to have an output created. 15 | The following configuration creates the node in the example above with two outputs. 16 | 17 | ![Consolidate logs to one output.](img/bridge-log-config.png) 18 | 19 | The bridge log node supports all 28 types. For more details about the types, look at the description in the configuration window or the [Zigbee2MQTT MQTT Topics and Message structure documentation](https://www.zigbee2mqtt.io/information/mqtt_topics_and_message_structure.html#zigbee2mqttbridgelog). 20 | 21 | ### Bridge 22 | 23 | The Zigbee2MQTT bridge. For more information see [bridge-config](../config/bridge-config.md). 24 | 25 | ### Consolidated output 26 | 27 | The consolidate output option is attractive if you want all or certain log types, but every log message results in the same action. If the consolidate output option is selected, all the selected log types will be sent over a single output. 28 | 29 | The following example sends the logs with the type `pairing` or `device_connected` into the debug node. 30 | 31 | ![Consolidate logs to one output.](img/bridge-log-config-consolidate-output.png) 32 | 33 | ![Flow with logs consolidated into one output](img/bridge-log-flow-consolidate-output.png) 34 | 35 | 36 | 37 | ## Message format 38 | 39 | Zigbee2MQTT sends the logs to the `zigbee2mqtt/bridge/log` topic in the following format: 40 | ``` json 41 | { "type": "TYPE", "message": "MESSAGE" } 42 | ``` 43 | The bridge log node unwraps the message in the `message` property and put's it directly into the `payload`. The type is put into `action.name`. The payload is not necessarily a string - e.g., the `devices` log contains a list of devices. 44 | 45 | 46 | ### Example 47 | The following MQTT message from Zigbee2MQTT: 48 | 49 | ``` json 50 | { 51 | "type": "device_announced", 52 | "message": "announce" 53 | } 54 | ``` 55 | 56 | Is converted into this Node-RED message object as it leaves the bridge log node. 57 | ``` json 58 | { 59 | "action": { 60 | "name": "device_announced", 61 | "description": "Logs with the type: device_announced" 62 | }, 63 | "payload": "announce", 64 | "_msgid": "770eadcb.231f54" 65 | } 66 | ``` 67 | 68 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/button-switch.md: -------------------------------------------------------------------------------- 1 | # Button switch 2 | 3 | The button switch node is used after a remote node. It redirects the message to it's own seperate output by the ```button_type``` property of the message payload. 4 | 5 | The are currently the following ```button_type``` values supported: 6 | 7 | - pressed 8 | - hold 9 | - released 10 | - double (Only the SONOFF touch button supports this type right now) 11 | 12 | ![img](img/button-switch-flow.png) 13 | 14 | ## Configuration 15 | 16 | To use the button switch, you have to enable the outputs you need. You also can define a custom ```msg.payload``` for each ```button_type```. You can define the following types: 17 | 18 | - string 19 | - number 20 | - boolean 21 | - json 22 | 23 | ![img](img/button-switch-config.png) 24 | 25 | ## Repeatedly send hold message 26 | 27 | In some cases, you may want to send multiple messages while a button is held down. If that is the case, you can enable it after you enabled the output for ```hold```. The ```released``` output has not to be enabled for this to work. 28 | 29 | > To not end up in an endless loop, you have to specify a ```maximum amount of messages```! If your amount is 0 or less, it will not produce any output! 30 | 31 | ![img](img/button-switch-config-hold-repeat.png) 32 | 33 | 34 | ## Support remote devices 35 | 36 | - Ikea Dimmer 37 | - Ikea Remote 38 | - Sonoff Touch Button 39 | - Scenic Friend of Hue 40 | - Hue Dimmer switch 41 | 42 | ### Future implementations 43 | 44 | - Hue Smart button 45 | - Tint Color remote 46 | 47 | Or join our [Discord server](https://discord.gg/4qCMEhJ) and suggest a device we should support :) 48 | 49 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/climate-sensor.md: -------------------------------------------------------------------------------- 1 | # Climate sensor 2 | 3 | The climate sensor node can be used to retrieve data from a climate sensor. In the configuration, you can define which values you want to display in the status of the node. If the **separate outputs** option is enabled, the selected properties each get their own output, which only outputs the value of the property. 4 | 5 | ## Configuration 6 | 7 | ![img](img/climate-sensor-config.png) 8 | 9 | ### Bridge 10 | 11 | For more information see [bridge-config](../config/bridge-config.md). 12 | 13 | ### Device 14 | 15 | Select the zigbee2mqtt device you want to listen to. 16 | 17 | ### Temperature 18 | 19 | Displays the temperature in the status of the node. 20 | 21 | ### Pressure 22 | 23 | Displays the pressure in the status of the node. 24 | 25 | ### Humidity 26 | 27 | Displays the humidity in the status of the node. 28 | 29 | ### CO2 30 | 31 | Displays the CO2 in the status of the node. 32 | 33 | ### Separate outputs 34 | 35 | Separates the selected properties into different outputs. If this is enabled, the first output still outputs the raw MQTT message as well. 36 | 37 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/device-status.md: -------------------------------------------------------------------------------- 1 | # Device Status 2 | 3 | A device status node can be used to retrieve notifications whenever a device status was updated. In the configuration, you can define and select your device which you want to listen to. Note that only zigbee router devices can be selected. 4 | 5 | ## Configuration 6 | 7 | ![img](img/device-status-config.png) 8 | 9 | ### Bridge 10 | 11 | For more information see [bridge-config](../config/bridge-config.md). 12 | 13 | ### Device 14 | 15 | Select the zigbee2mqtt device you want to listen to. 16 | 17 | ### Generic MQTT Device (experimental) 18 | 19 | ![img](img/device-status-config-experimental.png) 20 | 21 | When checking the checkbox, experimental mode is activated. For further information, see [device-config](../config/device-config.md). 22 | 23 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/generic-lamp.md: -------------------------------------------------------------------------------- 1 | # Generic lamp 2 | 3 | A genric lamp is used to define which lamps you want to control. In the configuration you can define and select your device and set all states that the devices supports. When you done setting your state you have to put a [send-messages](send-messages.md) node at the end. 4 | 5 | ![img](img/generic-lamp-flow.png) 6 | 7 | # Configuration 8 | 9 | ![img](img/generic-lamp-config.png) 10 | 11 | ## Device 12 | 13 | For more information see [device-config](../config/device-config.md) 14 | 15 | ## Bridge 16 | 17 | For more information see [bridge-config](../config/bridge-config.md) 18 | 19 | ## State 20 | 21 | Set the desired state you want to send to the lamp. Following values are available: 22 | 23 | - ON 24 | - OFF 25 | - TOGGLE 26 | 27 | ## Delay 28 | 29 | The delay is used to delay the message befor it will be sent to the mqtt broker. This can be used for animation. It is defined in milliseconds. The order of your lamps withing the flow is important. 30 | 31 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/get-lamp-state.md: -------------------------------------------------------------------------------- 1 | # Get lamp state 2 | 3 | A get lamp state node can be used to retrieve the current state of a lamp (which was configured as [device-config](../config/device-config.md) previously) on message input. In the configuration, you can define and select your device which you want to listen to. 4 | 5 | ## Configuration 6 | 7 | ![img](img/get-lamp-state-config.png) 8 | 9 | ### Bridge 10 | 11 | For more information see [bridge-config](../config/bridge-config.md). 12 | 13 | ### Device 14 | 15 | Select the lamp you want to retrieve the state from. For more information see [device-config](../config/device-config.md). 16 | 17 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/img/bridge-log-config-consolidate-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/bridge-log-config-consolidate-output.png -------------------------------------------------------------------------------- /docs/nodes/img/bridge-log-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/bridge-log-config.png -------------------------------------------------------------------------------- /docs/nodes/img/bridge-log-flow-consolidate-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/bridge-log-flow-consolidate-output.png -------------------------------------------------------------------------------- /docs/nodes/img/bridge-log-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/bridge-log-flow.png -------------------------------------------------------------------------------- /docs/nodes/img/button-switch-config-hold-repeat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/button-switch-config-hold-repeat.png -------------------------------------------------------------------------------- /docs/nodes/img/button-switch-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/button-switch-config.png -------------------------------------------------------------------------------- /docs/nodes/img/button-switch-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/button-switch-flow.png -------------------------------------------------------------------------------- /docs/nodes/img/climate-sensor-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/climate-sensor-config.png -------------------------------------------------------------------------------- /docs/nodes/img/device-status-config-experimental.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/device-status-config-experimental.png -------------------------------------------------------------------------------- /docs/nodes/img/device-status-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/device-status-config.png -------------------------------------------------------------------------------- /docs/nodes/img/generic-lamp-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/generic-lamp-config.png -------------------------------------------------------------------------------- /docs/nodes/img/generic-lamp-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/generic-lamp-flow.png -------------------------------------------------------------------------------- /docs/nodes/img/get-lamp-state-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/get-lamp-state-config.png -------------------------------------------------------------------------------- /docs/nodes/img/ota-node-autoUpdate-msg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/ota-node-autoUpdate-msg.png -------------------------------------------------------------------------------- /docs/nodes/img/ota-node-config-blacklist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/ota-node-config-blacklist.png -------------------------------------------------------------------------------- /docs/nodes/img/ota-node-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/ota-node-config.png -------------------------------------------------------------------------------- /docs/nodes/img/ota-node-context-updates-available.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/ota-node-context-updates-available.png -------------------------------------------------------------------------------- /docs/nodes/img/ota-node-example-flow-over-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/ota-node-example-flow-over-night.png -------------------------------------------------------------------------------- /docs/nodes/img/ota-node-update-available.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/ota-node-update-available.png -------------------------------------------------------------------------------- /docs/nodes/img/ota-node-update-finished.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/ota-node-update-finished.png -------------------------------------------------------------------------------- /docs/nodes/img/ota-node-update-in-progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/ota-node-update-in-progress.png -------------------------------------------------------------------------------- /docs/nodes/img/ota-node-update-progress-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/ota-node-update-progress-output.png -------------------------------------------------------------------------------- /docs/nodes/img/override-action-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/override-action-config.png -------------------------------------------------------------------------------- /docs/nodes/img/override-brightness-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/override-brightness-config.png -------------------------------------------------------------------------------- /docs/nodes/img/override-color-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/override-color-config.png -------------------------------------------------------------------------------- /docs/nodes/img/override-nodes-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/override-nodes-example.png -------------------------------------------------------------------------------- /docs/nodes/img/override-state-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/override-state-config.png -------------------------------------------------------------------------------- /docs/nodes/img/override-temperature-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/override-temperature-config.png -------------------------------------------------------------------------------- /docs/nodes/img/scene-in-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/scene-in-config.png -------------------------------------------------------------------------------- /docs/nodes/img/scene-selector-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/scene-selector-config.png -------------------------------------------------------------------------------- /docs/nodes/img/scene-selector-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/scene-selector-example.png -------------------------------------------------------------------------------- /docs/nodes/img/send-messages-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/send-messages-config.png -------------------------------------------------------------------------------- /docs/nodes/img/shelly25-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/shelly25-config.png -------------------------------------------------------------------------------- /docs/nodes/img/shelly25-listen-input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/shelly25-listen-input.png -------------------------------------------------------------------------------- /docs/nodes/img/shelly25-toggle-relay-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/shelly25-toggle-relay-settings.png -------------------------------------------------------------------------------- /docs/nodes/img/shelly25-toggle-relay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dirnei/node-red-contrib-zigbee2mqtt-devices/65c1898ad79b7c0209ed9a08f7b1c33a77241c9f/docs/nodes/img/shelly25-toggle-relay.png -------------------------------------------------------------------------------- /docs/nodes/ota-node.md: -------------------------------------------------------------------------------- 1 | # OTA node 2 | 3 | For basic information about OTA updates via zigbee2mqtt, look at the documentation [here](https://www.zigbee2mqtt.io/information/ota_updates.html). 4 | 5 | > If you use our dashboard, have a look at the end of the flow. The node is used there to auto-update overnight. 6 | 7 | The OTA-node is used to start OTA updates. The updates can be executed manually or automatically. 8 | 9 | ## Outputs 10 | 11 | This node has three outputs. **start/end**, **progress** and **queue changed**. The name of the output matches the trigger of the message. E.g., when the progress of the current update changes, a message will be sent to output 2 (Label: "progress") 12 | 13 | ## Manual update 14 | 15 | To start the update process manually, you have to send a ```msg``` to the input of the node with the following structure: 16 | 17 | ``` js 18 | { 19 | payload: { 20 | device: "friendly_name" 21 | } 22 | } 23 | ``` 24 | 25 | ## Automatic update 26 | 27 | ### Always on 28 | 29 | If you want the feature always available, you can turn it on in the config. 30 | 31 | ![img](img/ota-node-config.png) 32 | 33 | ### Turn on by message 34 | 35 | It is also possible to turn this feature **on** and **off** by sending a message to the input with the following structure: 36 | 37 | ``` js 38 | { 39 | payload: { 40 | autoUpdate: true 41 | } 42 | } 43 | ``` 44 | 45 | A real-world example would be to allow the auto-update only during the night. To archive this, you could use two **inject nodes** to send a message at a specific time — one to turn the auto-update on, one to turn it off. 46 | 47 | You can copy-paste the flow example below from the example folder in the repo. 48 | 49 | ![img](img/ota-node-autoUpdate-msg.png) 50 | 51 | ## Blacklist 52 | 53 | You may want to update some lamps only manually for various reasons. For example, the Ikea lamps turn on for a short time after the installation process succeeded. If this lamp is over your head while you are asleep, it might wake you. To prevent those situations, you can add those lamps to a blacklist. All lamps on the blacklist can only be updated manually with a **msg** sent to the input (see description above), or you use our dashboard you find in the dashboard folder at the root of the repo. 54 | 55 | ![img](img/ota-node-config-blacklist.png) 56 | 57 | ## Some screenshots 58 | 59 | ### Example 1: "Updates available" 60 | 61 | ![img](img/ota-node-update-available.png) 62 | 63 | ### Example 2: "Node-context from contextview on the sidebar" 64 | 65 | ![img](img/ota-node-context-updates-available.png) 66 | 67 | ### Example 3: "Update in progress" 68 | ![img](img/ota-node-update-in-progress.png) 69 | 70 | ### Example 4: "Debug output" 71 | 72 | ![img](img/ota-node-update-progress-output.png) 73 | ![img](img/ota-node-update-finished.png) 74 | 75 | ## Nice to know 76 | > Only one OTA-update node per bridge is allowed. If more than one is deployed, only the first one will work. All others will show an error status. 77 | 78 | ## Example 79 | 80 | The [**OTA over night example flow**](../../examples/ota/ota_over_night.json) enables the auto update between 00:00 and 04:00 and is included in this package. 81 | 82 | Import it via: `Node-RED Menu > Import > Examples > node-red-contrib-zigbee2mqtt-devices > ota > ota_over_night` 83 | 84 | ![OTA over night example flow](img/ota-node-example-flow-over-night.png) 85 | 86 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/override-action.md: -------------------------------------------------------------------------------- 1 | # Override action 2 | 3 | Instead of setting a brightness, color_temp, hue or saturation it is also possible to: 4 | 5 | __move__ - this will automatically move the value over time, to stop send value 0. 6 | 7 | __step__ - this will increment/decrement the current value by the given one. 8 | The direction of move and step can be either up or down, provide a negative value to move/step down, a positive value to move/step up. 9 | 10 | ![img](img/override-action-config.png) 11 | 12 | ### Notes 13 | 14 | > brightness move/step will stop at the minimum brightness and won't turn on the light when it's off. 15 | 16 | > When a action is set in the current flow, other changes will be ignored. It is only possible to do one action at a time. 17 | 18 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/override-brightness.md: -------------------------------------------------------------------------------- 1 | # Override brightness 2 | 3 | Brightness values can be set between 254 (100%) and 0 (0%). All lamps in the flow will be set to the same value defined in this node. 4 | 5 | ![img](img/override-brightness-config.png) 6 | 7 | ## Example flow 8 | 9 | ![img](img/override-nodes-example.png) 10 | 11 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/override-color.md: -------------------------------------------------------------------------------- 1 | # Override color 2 | 3 | RGB values can be set between 0 and 255. All lamps in the flow will be set to the same value defined in this node. 4 | 5 | ![img](img/override-color-config.png) 6 | 7 | ## Example flow 8 | 9 | ![img](img/override-nodes-example.png) 10 | 11 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/override-nodes.md: -------------------------------------------------------------------------------- 1 | # Override nodes 2 | 3 | There are multiple override node available. The behaviour of each node is still the same. It overrides the properties that are set in a [genric-lamp](generic-lamp.md) node. If a device does not support the properties that the override node will set, it will have no effect. The override is applied to all *generic-lamp* nodes in the flow. 4 | 5 | ## Available nodes 6 | 7 | - [override-state](override-state.md) Overrides the `ON`/`OFF` state. 8 | - [override-brightness](override-brightness.md) Overrides the brightness of a lamp. 9 | - [override-temperature](override-temperature.md) Overrides light temperature of a lamp. 10 | - [override-color](override-color.md) Overrides the light color of a lamp. 11 | - [override-action](override-action.md) Modify brightness/color over time or in steps. 12 | 13 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/override-state.md: -------------------------------------------------------------------------------- 1 | # Override state 2 | 3 | The state can be set to `ON`, `OFF` or `TOGGLE`. Be carefull with `TOGGLE` because when not all lamps in your flow are in the same state, the ones that are on turn off and vice versa. 4 | 5 | ![img](img/override-state-config.png) 6 | 7 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/override-temperature.md: -------------------------------------------------------------------------------- 1 | # Override temperature 2 | 3 | Used to set the color temperature for all lamps in the flow to the same value. 4 | 5 | ![img](img/override-temperature-config.png) 6 | 7 | Color temperature in Reciprocal MegaKelvin, a.k.a. Mirek scale. 8 | 9 | ``` 10 | Mirek = 1,000,000 / Color Temperature in Kelvin 11 | ``` 12 | 13 | > Values typically range between 50 and 400. The higher the value, the warmer the color. 14 | 15 | | Kelvin | Mirek | 16 | |---------------|-------| 17 | | 2400 K | 416 | 18 | | 2700 K | 370 | 19 | | 3000 K | 333 | 20 | | 3200 K | 312 | 21 | | 3350 K | 299 | 22 | | 5000 K | 200 | 23 | | 6200 K | 161 | 24 | | 6500 K | 155 | 25 | | 8000 K | 125 | 26 | | 10000 K | 100 | 27 | 28 | ## Example flow 29 | 30 | ![img](img/override-nodes-example.png) 31 | 32 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/scene-in.md: -------------------------------------------------------------------------------- 1 | # Scene 2 | 3 | A scene-in node configures a scene which can be activated via the [scene-selector](scene-selector.md). 4 | 5 | ## Configuration 6 | 7 | ![img](img/scene-in-config.png) 8 | 9 | ### Name 10 | 11 | Changes the display label of the node. 12 | 13 | ### Scene 14 | 15 | The name of the scene which can be used within the [scene-selector](scene-selector.md)s ```set``` command. 16 | 17 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/scene-selector.md: -------------------------------------------------------------------------------- 1 | # Scene selector 2 | 3 | A scene selector can be used to select scenes in a defined order. When the selector receives a message with a given ```msg.command``` property, it will select the corresponding scene and trigger its' activation. For more information on scenes see [scene-in](scene-in.md). 4 | 5 | ## Input 6 | 7 | You can choose among the following commands: 8 | - ```next``` activates the next scene 9 | - ```previous``` activates the previous scene 10 | - ```set``` directly activate a given scene. To specifiy which scene, a ```msg.scene``` property has to be set. It can either contain an integer corresponding to the array index of the scene or can contain a string corresponding to the "Scene" property of the [scene-in](scene-in.md) node. 11 | 12 | ## Configuration 13 | 14 | ![img](img/scene-selector-config.png) 15 | 16 | ### Scenes 17 | 18 | Using the dropdown and the "add scene" button, you can generate a sequence of scenes. A scene can be removed by pressing the "X" button. 19 | 20 | ### Wrap around 21 | 22 | This property decides, which behaviour the selector has, whenever the selected scene index reaches the beginning (when stepping down) or the end (when stepping up) of the scene sequence. 23 | - If ```true```: 24 | | Command | Current scene | New scene | 25 | |----------|---------------|-----------| 26 | | previous | first | last | 27 | | next | last | first | 28 | - If ```false```: 29 | | Command | Current scene | New scene | 30 | |----------|---------------|-----------| 31 | | previous | first | first | 32 | | next | last | last | 33 | 34 | ### Changed output only 35 | 36 | If set to true, the node only outputs a scene selection if the selected scene has changed. If e.g. a ```set``` command with the same index is sent multiple time consecutively, the scene will be only triggered once. 37 | 38 | ## Example 39 | 40 | The [**basic scene example flow**](../../examples/scenes/basic_scene.json) is included in the package. 41 | 42 | Import it via: `Node-RED Menu > Import > Examples > node-red-contrib-zigbee2mqtt-devices > scenes > basic_scene` 43 | 44 | 45 | ![scene selector example flow](img/scene-selector-example.png) 46 | 47 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/send-messages.md: -------------------------------------------------------------------------------- 1 | # Send messages 2 | 3 | A send message node is needed at the and of a flow that should control zigbee2mqtt devices. 4 | It prepares and sends the mqtt messages to the MQTT-Broker. 5 | 6 | ![img](img/generic-lamp-flow.png) 7 | 8 | ## Configuration 9 | 10 | There is not much configuration needed for this node. Just select or configure a new [bridge configuration](../config/bridge-config.md) 11 | 12 | If you want to change the label of the node on flow you can set a name. 13 | 14 | ![img](img/send-messages-config.png) 15 | 16 | [*← back to the index*](../documentation.md) -------------------------------------------------------------------------------- /docs/nodes/shelly-25-node.md: -------------------------------------------------------------------------------- 1 | # Shelly 2.5 2 | 3 | The shelly 2.5 node controls the relays on a shelly and receives input, relay, and status changes. 4 | 5 | Tested with the following Shellies: 6 | - Shelly 1 7 | - Shelly 2.5 8 | 9 | ## Configuration 10 | 11 | ![img](img/shelly25-config.png) 12 | 13 | ### Broker 14 | 15 | For more information see [mqtt-config](../config/mqtt-config.md) 16 | 17 | ### Device 18 | 19 | The device section configures a shelly's MQTT topic and, optionally, its friendly name. 20 | For example, `shellies/my-device`, the shelly node will subscribe and publish on this topic. 21 | 22 | ### Channel 23 | 24 | The channel configures which relay will be triggered when the node receives an input. Furthermore, the node only publishes relay and input changes for the selected input. 25 | Possible options are: 26 | - `Channel 1`: Switches and subscribes to channel 1 (MQTT topic `relay/0` and `input/0`). 27 | - `Channel 2`: Switches and subscribes to channel 2 (MQTT topic `relay/1` and `input/1`). 28 | - `Both`: Switches and subscribes to channels 1 and 2. 29 | 30 | For the shelly 1, one should stick to channel one, as it has only one input and relay. 31 | 32 | ### State 33 | The state is used to control the relay when the node receives an input. 34 | - `On`: Switches the relay for the selected channel to on. 35 | - `Off`: Switches the relay for the selected channel to off. 36 | - `Toggle`: Switches the relay for the selected channel on if it is off and vice versa. 37 | 38 | ### Enable Input 39 | This option enables the input connector on the node. Only when it is enabled is it possible to switch the relays. 40 | 41 | ### Custom payload 42 | The custom payload allows you to define a specific payload to be published when the shelly's input state changes since the default are `0` and `1`, which might not be too helpful, primarily if two channels are used. For example: 43 | - Payload 1: Input one was triggered 44 | - Payload 2: Input zero was triggered 45 | 46 | 47 | ## Usage 48 | 49 | ### Control a shelly's relay 50 | 51 | On input, the shelly 2.5 node prepares a payload sent to the shelly by [send-messages](send-messages.md) via MQTT. 52 | 53 | The inject node triggers the shelly node to create a payload. The shelly node is configured to toggle its state between on and off. Moreover, only channel one is selected. The send-messages node is configured to use an MQTT broker as well as a zigbee2mqtt topic. The zigbee2mqtt topic is ignored for shellies, and they publish at the configured topic, usually `shellies/my-device`. 54 | 55 | 56 | ![img](img/shelly25-toggle-relay-settings.png) 57 | 58 | ![img](img/shelly25-toggle-relay.png) 59 | 60 | To try it, have a look at the example: [shelly25-toggle-relay.json](../../examples/shelly-25/shelly25-toggle-relay.json) 61 | 62 | 63 | ### Receive input or relay changes from a shelly 64 | 65 | When the relay actuates, or a payload is sent to the first output to the 'Log shelly relay' debug node. 66 | When the input changes, a payload is sent to the second output to the 'Log shelly input' debug node. 67 | 68 | ![img](img/shelly25-listen-input.png) 69 | 70 | To try it, have a look at the example: [shelly25-listen-to-input.json](../../examples/shelly-25/shelly25-listen-to-input.json) 71 | 72 | 73 | [*← back to the index*](../documentation.md) 74 | -------------------------------------------------------------------------------- /examples/getting-started/one-lamp-on-off.json: -------------------------------------------------------------------------------- 1 | [{"id":"2dad0b7b.81c984","type":"send-messages","z":"6d72d1fc.47431","name":"","bridge":"c34db5f4.463458","x":500,"y":200,"wires":[]},{"id":"7e359dfa.509e14","type":"inject","z":"6d72d1fc.47431","name":"On","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":140,"wires":[["2ad3c58c.a46f2a"]]},{"id":"26e69b6b.c47ec4","type":"inject","z":"6d72d1fc.47431","name":"Off","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":260,"wires":[["e9988615.fb6cd8"]]},{"id":"e1fb381.4bb73c8","type":"comment","z":"6d72d1fc.47431","name":"State: ON","info":"","x":260,"y":100,"wires":[]},{"id":"9a5f81fb.3c992","type":"comment","z":"6d72d1fc.47431","name":"State: OFF","info":"","x":260,"y":220,"wires":[]},{"id":"2ad3c58c.a46f2a","type":"generic-lamp","z":"6d72d1fc.47431","device":"b9730eda.9ab35","name":"Ikea Trådfri 806 lm","state":"ON","brightness":"254","temperature":50,"red":0,"green":0,"blue":0,"transition":"2","delay":0,"x":290,"y":140,"wires":[["2dad0b7b.81c984"]]},{"id":"e9988615.fb6cd8","type":"generic-lamp","z":"6d72d1fc.47431","device":"b9730eda.9ab35","name":"Ikea Trådfri 806 lm","state":"OFF","brightness":"254","temperature":50,"red":0,"green":0,"blue":0,"transition":"2","delay":0,"x":290,"y":260,"wires":[["2dad0b7b.81c984"]]},{"id":"c34db5f4.463458","type":"zigbee2mqtt-bridge-config","name":"Zigbee2MQTT Intel NUC","broker":"cbf4dc5b.3a4d7","baseTopic":"zigbee2mqtt","enabledLogging":false,"allowDeviceStatusRefresh":false},{"id":"b9730eda.9ab35","type":"zigbee2mqtt-device-config","name":"Ikea Trådfri 806 lm","bridge":"c34db5f4.463458","deviceName":"Ikea Trådfri 806lm","brightnessSupport":true,"temperatureSupport":false,"colorSupport":false,"genericMqttDevice":false,"statusTopic":"","commandTopic":"","refreshTopic":""},{"id":"cbf4dc5b.3a4d7","type":"mqtt-broker","name":"MQTT Broker","broker":"192.168.178.49","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}] -------------------------------------------------------------------------------- /examples/getting-started/two-lamps-override.json: -------------------------------------------------------------------------------- 1 | [{"id":"5f37476b.814558","type":"send-messages","z":"24f36957.60dbd6","name":"","bridge":"c34db5f4.463458","x":1020,"y":180,"wires":[]},{"id":"f86a33a4.b9a0a","type":"generic-lamp","z":"24f36957.60dbd6","device":"6a600609.e54ca8","name":"Phillips Hue white ambience","state":"ON","brightness":"30","temperature":"153","red":0,"green":0,"blue":0,"transition":"2","delay":0,"x":560,"y":180,"wires":[["f99218ad.8866b8"]]},{"id":"74938da8.28c264","type":"inject","z":"24f36957.60dbd6","name":"On","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":90,"y":160,"wires":[["a6abb9ca.9847c8"]]},{"id":"f051fef9.5fbb3","type":"inject","z":"24f36957.60dbd6","name":"Off","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":90,"y":200,"wires":[["3d8ddf9c.5ebca"]]},{"id":"f99218ad.8866b8","type":"generic-lamp","z":"24f36957.60dbd6","device":"b9730eda.9ab35","name":"Ikea Trådfri 806 lm","state":"ON","brightness":"254","temperature":50,"red":0,"green":0,"blue":0,"transition":"2","delay":0,"x":810,"y":180,"wires":[["5f37476b.814558","57d4f951.9e8498"]]},{"id":"a6abb9ca.9847c8","type":"override-state","z":"24f36957.60dbd6","name":"override-state: On","state":"ON","x":310,"y":160,"wires":[["f86a33a4.b9a0a"]]},{"id":"3d8ddf9c.5ebca","type":"override-state","z":"24f36957.60dbd6","name":"override-state: Off","state":"OFF","x":310,"y":200,"wires":[["f86a33a4.b9a0a"]]},{"id":"9ef91f6a.b3c26","type":"inject","z":"24f36957.60dbd6","name":"Relax mode","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":100,"wires":[["78ef023d.a3e8dc"]]},{"id":"78ef023d.a3e8dc","type":"override-brightness","z":"24f36957.60dbd6","name":"override-brightness: 50% (127)","brightness":"127","x":350,"y":100,"wires":[["95023793.a57d38"]]},{"id":"95023793.a57d38","type":"override-temperature","z":"24f36957.60dbd6","name":"override-temperature: (333)","temperature":333,"x":640,"y":100,"wires":[["a6abb9ca.9847c8"]]},{"id":"236ac64a.d73c2a","type":"inject","z":"24f36957.60dbd6","name":"Work mode","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"override\":{\"brightness\":\"255\",\"temperature\":100,\"state\":\"ON\"}}","payloadType":"json","x":350,"y":260,"wires":[["f86a33a4.b9a0a"]]},{"id":"57d4f951.9e8498","type":"debug","z":"24f36957.60dbd6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":990,"y":240,"wires":[]},{"id":"c34db5f4.463458","type":"zigbee2mqtt-bridge-config","name":"Zigbee2MQTT Intel NUC","broker":"cbf4dc5b.3a4d7","baseTopic":"zigbee2mqtt","enabledLogging":false,"allowDeviceStatusRefresh":false},{"id":"6a600609.e54ca8","type":"zigbee2mqtt-device-config","name":"Phillips Hue white ambience","bridge":"c34db5f4.463458","deviceName":"Lamp Terrace","brightnessSupport":true,"temperatureSupport":true,"colorSupport":false,"genericMqttDevice":false,"statusTopic":"","commandTopic":"","refreshTopic":""},{"id":"b9730eda.9ab35","type":"zigbee2mqtt-device-config","name":"Ikea Trådfri 806 lm","bridge":"c34db5f4.463458","deviceName":"Ikea Trådfri 806lm","brightnessSupport":true,"temperatureSupport":false,"colorSupport":false,"genericMqttDevice":false,"statusTopic":"","commandTopic":"","refreshTopic":""},{"id":"cbf4dc5b.3a4d7","type":"mqtt-broker","name":"MQTT Broker","broker":"192.168.178.49","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}] -------------------------------------------------------------------------------- /examples/ota/ota_over_night.json: -------------------------------------------------------------------------------- 1 | [{"id":"d3d215cf.2efe18","type":"inject","z":"e5f179a9.dd2658","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"00 00 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"{\"autoUpdate\":true}","payloadType":"json","x":620,"y":1520,"wires":[["4a909756.e271c8"]]},{"id":"a0c0155d.aa5a38","type":"inject","z":"e5f179a9.dd2658","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"00 04 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"{\"autoUpdate\":false}","payloadType":"json","x":620,"y":1560,"wires":[["4a909756.e271c8"]]},{"id":"4a909756.e271c8","type":"ota-update","z":"e5f179a9.dd2658","name":"","bridge":"8d961b4d.cfe858","autoUpdate":false,"verboseLogging":false,"x":830,"y":1520,"wires":[["7b9d12.9410c2f"],["538eb538.6377ac","c70fc71.94ab938"]]},{"id":"8d961b4d.cfe858","type":"zigbee2mqtt-bridge-config","z":"","name":"raspberry","baseTopic":"zigbee2mqtt","broker":"mqtt://localhost:1883","requireLogin":false}] -------------------------------------------------------------------------------- /examples/scenes/basic_scene.json: -------------------------------------------------------------------------------- 1 | [{"id":"2746ea7f.991456","type":"debug","z":"f99a795c.3e73c8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":810,"y":140,"wires":[]},{"id":"272ec6fa.1c208a","type":"inject","z":"f99a795c.3e73c8","name":"next","props":[{"p":"command","v":"next","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":80,"wires":[["ad17b0b6.40d5c"]]},{"id":"7f911100.72e77","type":"inject","z":"f99a795c.3e73c8","name":"previous","props":[{"p":"command","v":"previous","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":140,"y":120,"wires":[["ad17b0b6.40d5c"]]},{"id":"56f6521d.3c5a5c","type":"inject","z":"f99a795c.3e73c8","name":"set scene 3","props":[{"p":"command","v":"set","vt":"str"},{"p":"scene","v":"Scene 3","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":200,"wires":[["ad17b0b6.40d5c"]]},{"id":"8b2d9945.0a7068","type":"inject","z":"f99a795c.3e73c8","name":"set 0","props":[{"p":"command","v":"set","vt":"str"},{"p":"scene","v":"0","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":160,"wires":[["ad17b0b6.40d5c"]]},{"id":"cc1d9423.c045c8","type":"scene-in","z":"f99a795c.3e73c8","name":"","scene":"Scene 1","active":true,"x":560,"y":80,"wires":[["2746ea7f.991456"]]},{"id":"b88b51ff.be731","type":"scene-in","z":"f99a795c.3e73c8","name":"","scene":"Scene 2","active":true,"x":560,"y":120,"wires":[["2746ea7f.991456"]]},{"id":"c0b6f568.7f5ea8","type":"scene-in","z":"f99a795c.3e73c8","name":"","scene":"Scene 3","active":false,"x":560,"y":160,"wires":[["2746ea7f.991456"]]},{"id":"b9ff1d83.cb0b5","type":"scene-in","z":"f99a795c.3e73c8","name":"","scene":"Scene 4","active":true,"x":560,"y":200,"wires":[["2746ea7f.991456"]]},{"id":"4e1e206f.61c9f","type":"scene-in","z":"f99a795c.3e73c8","name":"Scene 5 (unused)","scene":"Scene 5","active":true,"x":590,"y":240,"wires":[["2746ea7f.991456"]]},{"id":"ad17b0b6.40d5c","type":"scene-selector","z":"f99a795c.3e73c8","name":"","scenes":["Scene 1","Scene 2","Scene 3","Scene 4"],"wrapAround":true,"changedOutputOnly":false,"x":340,"y":140,"wires":[]}] -------------------------------------------------------------------------------- /examples/shelly-25/shelly25-listen-to-input.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "ddc73c09685b6ca2", 4 | "type": "debug", 5 | "z": "d6c6189e516c5861", 6 | "name": "Log shelly input", 7 | "active": true, 8 | "tosidebar": true, 9 | "console": false, 10 | "tostatus": false, 11 | "complete": "true", 12 | "targetType": "full", 13 | "statusVal": "", 14 | "statusType": "auto", 15 | "x": 440, 16 | "y": 200, 17 | "wires": [] 18 | }, 19 | { 20 | "id": "876f6827a210a969", 21 | "type": "shelly-25", 22 | "z": "d6c6189e516c5861", 23 | "name": "Shelly 1 Terrace Light", 24 | "mqtt": "b1503ab27dfa3ccd", 25 | "shelly": "daf62c0c2a5c1ebd", 26 | "enableInput": false, 27 | "state": "on", 28 | "channel": "2", 29 | "inputs": 0, 30 | "outputs": 2, 31 | "customPayload": true, 32 | "payloadInput0": "", 33 | "typeInput0": "str", 34 | "payloadInput1": "", 35 | "typeInput1": "str", 36 | "x": 200, 37 | "y": 180, 38 | "wires": [ 39 | [ 40 | "2212d8cf185ffa8e" 41 | ], 42 | [ 43 | "ddc73c09685b6ca2" 44 | ] 45 | ] 46 | }, 47 | { 48 | "id": "2212d8cf185ffa8e", 49 | "type": "debug", 50 | "z": "d6c6189e516c5861", 51 | "name": "Log shelly relay", 52 | "active": true, 53 | "tosidebar": true, 54 | "console": false, 55 | "tostatus": false, 56 | "complete": "true", 57 | "targetType": "full", 58 | "statusVal": "", 59 | "statusType": "auto", 60 | "x": 440, 61 | "y": 160, 62 | "wires": [] 63 | }, 64 | { 65 | "id": "b1503ab27dfa3ccd", 66 | "type": "mqtt-broker", 67 | "name": "Mosquitto", 68 | "broker": "127.0.0.1", 69 | "port": "1883", 70 | "clientid": "", 71 | "autoConnect": true, 72 | "usetls": false, 73 | "protocolVersion": "4", 74 | "keepalive": "60", 75 | "cleansession": true, 76 | "birthTopic": "", 77 | "birthQos": "0", 78 | "birthPayload": "", 79 | "birthMsg": {}, 80 | "closeTopic": "", 81 | "closeQos": "0", 82 | "closePayload": "", 83 | "closeMsg": {}, 84 | "willTopic": "", 85 | "willQos": "0", 86 | "willPayload": "", 87 | "willMsg": {}, 88 | "sessionExpiry": "", 89 | "credentials": {} 90 | }, 91 | { 92 | "id": "daf62c0c2a5c1ebd", 93 | "type": "shelly-config", 94 | "name": "terrace-light", 95 | "prefix": "shellies/terrace-light" 96 | } 97 | ] -------------------------------------------------------------------------------- /examples/shelly-25/shelly25-toggle-relay.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "8bcc1c584da5c6f6", 4 | "type": "shelly-25", 5 | "z": "d6c6189e516c5861", 6 | "name": "Shelly 1 Terrace Light", 7 | "mqtt": "b1503ab27dfa3ccd", 8 | "shelly": "daf62c0c2a5c1ebd", 9 | "enableInput": true, 10 | "state": "toggle", 11 | "channel": "0", 12 | "inputs": 1, 13 | "outputs": 3, 14 | "customPayload": false, 15 | "payloadInput0": "", 16 | "typeInput0": "str", 17 | "payloadInput1": "", 18 | "typeInput1": "str", 19 | "x": 440, 20 | "y": 400, 21 | "wires": [ 22 | [], 23 | [], 24 | [ 25 | "0e5a9766c04a1a6a" 26 | ] 27 | ] 28 | }, 29 | { 30 | "id": "2b95eb1c49dc7662", 31 | "type": "inject", 32 | "z": "d6c6189e516c5861", 33 | "name": "Virtual light switch", 34 | "props": [ 35 | { 36 | "p": "payload" 37 | }, 38 | { 39 | "p": "topic", 40 | "vt": "str" 41 | } 42 | ], 43 | "repeat": "", 44 | "crontab": "", 45 | "once": false, 46 | "onceDelay": 0.1, 47 | "topic": "", 48 | "payload": "", 49 | "payloadType": "date", 50 | "x": 170, 51 | "y": 400, 52 | "wires": [ 53 | [ 54 | "8bcc1c584da5c6f6" 55 | ] 56 | ] 57 | }, 58 | { 59 | "id": "0e5a9766c04a1a6a", 60 | "type": "send-messages", 61 | "z": "d6c6189e516c5861", 62 | "name": "", 63 | "bridge": "11358734e0ff49d5", 64 | "x": 660, 65 | "y": 400, 66 | "wires": [] 67 | }, 68 | { 69 | "id": "b1503ab27dfa3ccd", 70 | "type": "mqtt-broker", 71 | "name": "Mosquitto", 72 | "broker": "127.0.0.1", 73 | "port": "1883", 74 | "clientid": "", 75 | "autoConnect": true, 76 | "usetls": false, 77 | "protocolVersion": "4", 78 | "keepalive": "60", 79 | "cleansession": true, 80 | "birthTopic": "", 81 | "birthQos": "0", 82 | "birthPayload": "", 83 | "birthMsg": {}, 84 | "closeTopic": "", 85 | "closeQos": "0", 86 | "closePayload": "", 87 | "closeMsg": {}, 88 | "willTopic": "", 89 | "willQos": "0", 90 | "willPayload": "", 91 | "willMsg": {}, 92 | "sessionExpiry": "" 93 | }, 94 | { 95 | "id": "daf62c0c2a5c1ebd", 96 | "type": "shelly-config", 97 | "name": "terrace-light", 98 | "prefix": "shellies/terrace-light" 99 | }, 100 | { 101 | "id": "11358734e0ff49d5", 102 | "type": "zigbee2mqtt-bridge-config", 103 | "name": "Mosquitto", 104 | "broker": "b1503ab27dfa3ccd", 105 | "baseTopic": "zigbee2mqtt", 106 | "enabledLogging": false, 107 | "allowDeviceStatusRefresh": true 108 | } 109 | ] -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | # Install script to setup the project in a Test Node-RED docker container for development. 2 | # Tested under Windows and Linux (Ubuntu 20.04 with powershell) 3 | # Required software: powsershell, docker, docker-compose, nodejs, npm 4 | 5 | function copy-files { 6 | PARAM ( 7 | $src, 8 | $destRoot, 9 | $destFolder 10 | ) 11 | 12 | $src = Resolve-Path $src 13 | $destRoot = Resolve-Path $destRoot 14 | 15 | Write-Output "From : $src" 16 | Write-Output "To : $destRoot" 17 | 18 | $files = Get-ChildItem -Recurse $src | Where-Object { ! $_.PSIsContainer } 19 | foreach ($file in $files) { 20 | $relativeFile = $file.FullName.Substring($src.Path.Length) 21 | $dest = Join-Path -Path $destRoot -ChildPath $destFolder 22 | $dest = Join-Path -Path $dest -ChildPath $relativeFile 23 | 24 | $folder = $dest.Substring(0, $dest.LastIndexOf([IO.Path]::DirectorySeparatorChar)) 25 | if (-not (Test-Path $folder)) { 26 | New-Item -ItemType Directory $folder 27 | } 28 | 29 | Write-Output $dest 30 | [System.IO.File]::Copy($file.FullName, $dest, $true); 31 | } 32 | } 33 | 34 | if (-not (Test-Path ".\debug\")) { 35 | New-Item -ItemType Directory ".\debug" 36 | } 37 | 38 | if (-not (Test-Path ".\debug\package.json")) { 39 | $check = docker container inspect -f '{{.State.Running}}' "nodered_testing" 40 | 41 | if ($check) { 42 | docker-compose down 43 | } 44 | 45 | docker-compose up -d 46 | 47 | Write-Host "Waiting for node-red to boot ..." -NoNewLine 48 | while (!(Test-Path ".\debug\package.json")) { 49 | Start-Sleep 2 50 | } 51 | 52 | Write-Host -ForegroundColor Green " done" 53 | } 54 | 55 | docker-compose down 56 | 57 | $origin = [Environment]::CurrentDirectory; 58 | $repoName = $origin.Split([IO.Path]::DirectorySeparatorChar) | Select-Object -Last 1 59 | Write-Host "Name: $repoName" 60 | if (-not (Test-Path ".\debug\myModules\$repoName")) { 61 | Write-Output "Creating Path..." 62 | New-Item -ItemType Directory ".\debug\myModules\$repoName" 63 | New-Item -ItemType Directory ".\debug\myModules\$repoName\lib" 64 | } 65 | 66 | Write-Output "Copy files..." 67 | 68 | copy-files -src '.\dist' -destRoot ".\debug\myModules\$repoName" -destFolder "dist" 69 | copy-files -src '.\examples' -destRoot ".\debug\myModules\$repoName" -destFolder "examples" 70 | 71 | $packageModified = $false 72 | $destRoot = Resolve-Path ".\debug\myModules\$repoName" 73 | # Check if package.json needs install 74 | if (Test-Path $destRoot/package.json) { 75 | Write-Output "Comparing package.json ..." 76 | $packageModified = (Get-FileHash ./package.json).hash -ne (Get-FileHash $destRoot/package.json).hash 77 | if ($packageModified) { 78 | Write-Host "package.json was modified. Reinstall again after copy!" -ForegroundColor Yellow 79 | } 80 | else { 81 | Write-Host "Unmodified. No reinstallation needed!" -ForegroundColor Green 82 | } 83 | } 84 | else { 85 | $packageModified = $true; 86 | } 87 | 88 | Copy-Item -Path ./package.json -Destination $destRoot/package.json 89 | 90 | 91 | $destination = Resolve-Path .\debug\ 92 | Write-Output "Change location to: $destination" 93 | Set-Location $destination 94 | 95 | if ($packageModified) { 96 | npm install --production ".\myModules\$repoName" 97 | } 98 | 99 | Write-Output "Switching back to: $origin" 100 | Set-Location $origin 101 | 102 | # fix for windwos docker 103 | # windows docker can't handle links so we need to copy the files.... 104 | if ($IsWindows -or $Env:OS -eq "Windows_NT") { 105 | Write-Host "Fixing Windows Docker Problems" -ForegroundColor Red 106 | Write-Host "Resolve symlink" -ForegroundColor Yellow 107 | $files = Get-ChildItem "./debug/node_modules/" | Where-Object { $_.Attributes -match "ReparsePoint" } 108 | 109 | Write-Host "Updating modules..." -ForegroundColor Yellow 110 | $files = Get-ChildItem "./debug/node_modules/" -Directory 111 | foreach ($file in $files) { 112 | 113 | $moduleName = $file.FullName.Split("\") | Select-Object -Last 1 114 | 115 | if (Test-Path "./debug/myModules/$moduleName") { 116 | Write-Host "Update module: " -NoNewline -ForegroundColor Magenta 117 | Write-Host $moduleName 118 | Remove-Item $file.FullName -Force -Recurse 119 | Copy-Item -Recurse -Path "./debug/myModules/$moduleName/" -Destination "./debug/node_modules/" 120 | } 121 | 122 | } 123 | } 124 | 125 | docker-compose up -d -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-zigbee2mqtt-devices", 3 | "version": "0.20.0", 4 | "description": "Nodes to interact with zigbee2mqtt for Node-RED", 5 | "author": "Christian Dirnhofer", 6 | "license": "MIT", 7 | "keywords": [ 8 | "node-red", 9 | "zigbee2mqtt", 10 | "smart home" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/Dirnei/node-red-contrib-zigbee2mqtt-devices" 15 | }, 16 | "node-red": { 17 | "version": ">=1.2.2", 18 | "nodes": { 19 | "api": "dist/_api.js", 20 | "brdige-log": "dist/bridge-log.js", 21 | "config": "dist/zigbee2mqtt-config.js", 22 | "devices-eurotronics": "dist/nodes/devices-eurotronic.js", 23 | "devices-hue": "dist/nodes/hue-dimmer/devices-hue.js", 24 | "devices-ikea": "dist/nodes/devices-ikea.js", 25 | "devices-scenic": "dist/nodes/devices-scenic.js", 26 | "devices-sonoff": "dist/nodes/devices-sonoff.js", 27 | "devices-tasmota": "dist/nodes/devices-tasmota.js", 28 | "devices-tint": "dist/nodes/devices-tint.js", 29 | "generic-lamp": "dist/zigbee2mqtt.js", 30 | "non-z2m-devices-shelly-25": "dist/non-z2m-nodes/devices-shelly-25.js", 31 | "ota": "dist/ota.js", 32 | "override": "dist/nodes/override/override-nodes.js", 33 | "scenes": "dist/nodes/scenes.js", 34 | "sensors": "dist/nodes/sensors.js" 35 | } 36 | }, 37 | "dependencies": { 38 | "mqtt": "^4.3.4", 39 | "node-red-ext-bavaria-black": "^0.5.0" 40 | }, 41 | "scripts": { 42 | "build": "run-script-os", 43 | "build:nix": "tsc && rsync -rv --exclude='*.js' --exclude='*.ts' ./src/ ./dist/", 44 | "build:win32": "tsc && @powershell -NoProfile -Command ./robocopy.ps1", 45 | "builddocker": "run-script-os", 46 | "builddocker:nix": "npm run build:nix && pwsh ./install.ps1", 47 | "builddocker:win32": "npm run build:win32 && @powershell -NoProfile -Command ./install.ps1", 48 | "createnode": "run-script-os", 49 | "createnode:nix": "pwsh ./createNode.ps1", 50 | "createnode:win32": "@powershell -NoProfile -Command ./createNode.ps1", 51 | "lint": "eslint ./src/**/*.js ./test/**/*.js ./test-integration/**/*.js", 52 | "test": "mocha \"test/**/*_spec.js\"", 53 | "testintegration": "docker-compose -f test-integration/docker/docker-compose.yml up -d && mocha \"test-integration/**/*_spec.js\"" 54 | }, 55 | "devDependencies": { 56 | "@types/jquery": "^3.5.13", 57 | "@types/node": "^14.18.10", 58 | "@types/node-red": "^1.2.0", 59 | "eslint": "^8.8.0", 60 | "mocha": "^10.1.0", 61 | "node-red": "^3.0.2", 62 | "node-red-node-test-helper": "^0.3.0", 63 | "run-script-os": "^1.1.6", 64 | "typescript": "^4.5.5", 65 | "zigbee-herdsman": "^0.14.10" 66 | }, 67 | "engines": { 68 | "node": ">=12.0.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /robocopy.ps1: -------------------------------------------------------------------------------- 1 | robocopy.exe ./src/ ./dist/ /MIR /xf *.js /xf *.ts /NFL /NDL /NJH /NJS /nc /ns /np 2 | 3 | Write-Output $LastExitCode 4 | if ($LastExitCode -ge 8) { 5 | Write-Output "Error copying files" 6 | exit $LastExitCode 7 | } Else { 8 | Write-Output "Copied src/ to dist/" 9 | exit 0 10 | } -------------------------------------------------------------------------------- /src/_api.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | const utils = require("./lib/utils.js"); 3 | 4 | RED.httpAdmin.get("/z2m/devices/:broker/:deviceType/:vendor/:model", function (req, res) { 5 | try { 6 | var response = { 7 | success: false, 8 | message: "", 9 | devices: [] 10 | }; 11 | 12 | var broker = RED.nodes.getNode(req.params.broker.replace("_", ".")); 13 | 14 | if (broker === undefined || broker === null) { 15 | response.message = "Unable to find broker. Please deploy first and try it again."; 16 | res.end(JSON.stringify(response)); 17 | return; 18 | } 19 | 20 | var devices = broker.getDeviceList(); 21 | var type = req.params.deviceType.toLowerCase(); 22 | 23 | var vendor = decodeURI(req.params.vendor).toLowerCase(); 24 | var model = decodeURI(req.params.model).toLowerCase(); 25 | 26 | if (model !== "all" && model.includes(",")) { 27 | model = model.split(","); 28 | } 29 | else if (model !== "all") { 30 | model = [model]; 31 | } 32 | 33 | response.devices = minimizeDeviceList(filterDevices(devices, type, vendor, model)); 34 | response.success = devices.length > 0; 35 | 36 | if (!response.success) { 37 | response.message = "No devices found!"; 38 | } 39 | 40 | if (response.success && response.devices.length == 0) { 41 | response.success = false; 42 | response.message = `No devices found after filtering! Used Filter: (Vendor: ${vendor}, Model: ${model}, Type: ${type})`; 43 | } 44 | 45 | console.log("---------------- RESPONSE -----------------"); 46 | console.log(response); 47 | console.log("-------------------------------------------"); 48 | 49 | res.end(JSON.stringify(response)); 50 | } catch (err) { 51 | res.end(JSON.stringify(response)); 52 | console.log(err); 53 | } 54 | }); 55 | 56 | function minimizeDeviceList(devices) { 57 | return devices.map((value) => { 58 | return { 59 | friendly_name: value.friendly_name, 60 | address: value.ieee_address, 61 | type: value.type, 62 | model: value.definition.model, 63 | vendor: value.definition.vendor, 64 | version: value.software_build_id 65 | }; 66 | }); 67 | } 68 | 69 | function isClimateSensorProperty(expose) { 70 | switch (expose.property) { 71 | case "temperature": 72 | case "humidity": 73 | case "pressure": 74 | return true; 75 | } 76 | 77 | return false; 78 | } 79 | 80 | function isContactSensorProperty(expose) { 81 | return expose.property === "contact"; 82 | } 83 | 84 | function isOccupancySensorProperty(expose) { 85 | return expose.property === "occupancy"; 86 | } 87 | 88 | function filterDevices(devices, type, vendor, model) { 89 | var specialTypes = ["climate", "contact", "occupancy"]; 90 | var isSpecialType = specialTypes.includes(type); 91 | 92 | return devices.filter(e => { 93 | try { 94 | 95 | if (e.definition === undefined || e.definition === null) { 96 | return false; 97 | } 98 | 99 | var dt = e.type.toLowerCase(); 100 | var dv = "all"; 101 | var dm = "all"; 102 | 103 | if (isSpecialType === true && dt == "enddevice" && e.definition.exposes !== undefined) { 104 | var match = 0; 105 | e.definition.exposes.forEach(expose => { 106 | if (match === 0 && expose.property !== undefined) { 107 | switch (type) { 108 | case "climate": 109 | match |= isClimateSensorProperty(expose); 110 | break; 111 | case "contact": 112 | match |= isContactSensorProperty(expose); 113 | break; 114 | case "occupancy": 115 | match |= isOccupancySensorProperty(expose); 116 | break; 117 | } 118 | } 119 | }); 120 | 121 | if (match === 1) { 122 | return true; 123 | } 124 | } 125 | 126 | if (e.definition.vendor) { 127 | dv = e.definition.vendor.toLowerCase(); 128 | } 129 | 130 | if (e.definition.model) { 131 | dm = e.definition.model.toLowerCase(); 132 | } 133 | 134 | return (dt == type || (type == "enddevice" && dt == "greenpower") || (type == "all" && dt !== "coordinator")) && 135 | (dv == vendor || (vendor == "all")) && 136 | ((model == "all") || model.includes(dm)); 137 | } catch (err) { 138 | console.log(err); 139 | console.log(e); 140 | } 141 | }); 142 | } 143 | 144 | RED.httpAdmin.get("/z2m/scenes", function (req, res) { 145 | try { 146 | var scenes = []; 147 | RED.nodes.eachNode(n => { 148 | if (n.type === "scene-in") { 149 | if (scenes.every(s => s != n.scene)) { 150 | scenes.push(n.scene); 151 | } 152 | } 153 | }); 154 | 155 | res.end(JSON.stringify({ 156 | scenes: scenes 157 | })); 158 | } catch (err) { 159 | console.log(err); 160 | } 161 | }); 162 | }; 163 | -------------------------------------------------------------------------------- /src/bridge-log.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | // @ts-ignore 3 | const OutputHandler = require("./lib/outputHandler.js"); 4 | const utils = require("./lib/utils.js"); 5 | const bavaria = utils.bavaria(); 6 | 7 | /** 8 | * The bridge log provides an easy way to filter logs that are 9 | * published into the zigbee2mqtt/bridge/log MQTT topic. 10 | * You configure the types you are interested in, 11 | * and the node creates one output for each type. 12 | * @param {*} config for the node 13 | */ 14 | function bridgeLog(config) { 15 | RED.nodes.createNode(this, config); 16 | const bridgeNode = RED.nodes.getNode(config.bridge); 17 | const node = this; 18 | const outputHandler = new OutputHandler(); 19 | 20 | utils.setConnectionState(bridgeNode, node); 21 | 22 | // Create a list of log message types to listen to 23 | const enabledLogTypes = new Array(); 24 | let outputCount = 0; 25 | 26 | Object.keys(config).forEach(function(key) { 27 | if(key.startsWith("type_") && config[key]) { 28 | const logType = key.replace("type_", ""); 29 | enabledLogTypes.push(logType); 30 | outputHandler.addOutput(outputCount, logType, logType, "Logs with the type: " + logType, msg => { return msg.message; }); 31 | outputCount++; 32 | } 33 | }); 34 | 35 | bridgeNode.on("bridge-log", (message) => { 36 | 37 | // Check if the log type is configured 38 | if (enabledLogTypes.indexOf(message.type) >= 0) { 39 | let messageForNOutputs = outputHandler.prepareOutput("type", message); 40 | 41 | if(config.consolidate_output) { 42 | // If all messages should be consolidated into one output, 43 | // use the last element of the array. 44 | // Because the output handler sets all the other indexes to null, 45 | // nothing is sent to those outputs. 46 | messageForNOutputs = messageForNOutputs.slice(-1); 47 | } 48 | 49 | node.send(messageForNOutputs); 50 | } 51 | }); 52 | 53 | // Set the node status 54 | bavaria.observer.register(bridgeNode.id + "_connected", function (message) { 55 | node.status({ fill: "green", text: "connected" }); 56 | }); 57 | } 58 | RED.nodes.registerType("bridge-log", bridgeLog); 59 | }; -------------------------------------------------------------------------------- /src/device-types.ts: -------------------------------------------------------------------------------- 1 | export type Z2mDeviceEntry = { 2 | date_code: number, 3 | friendly_name: string, 4 | ieee_address: string, 5 | interview_completed: boolean, 6 | interviewing: boolean, 7 | model_id: string, 8 | network_address: number, 9 | power_source: string, 10 | supported: boolean, 11 | type: string, 12 | definition: Z2mDeviceDefinition 13 | } 14 | 15 | export type Z2mDeviceDefinition = { 16 | description: string, 17 | model: string, 18 | vendor: string, 19 | exposes: Array 20 | } 21 | 22 | export type Z2mDeviceExposesBase = { 23 | type: string 24 | } 25 | 26 | export type Z2mDeviceExposesFeatures = Z2mDeviceExposesBase & { 27 | features: Array 28 | } 29 | 30 | export type Z2mDeviceProperty = Z2mDeviceExposesBase & { 31 | access: number, 32 | description: string, 33 | name: string, 34 | property: string, 35 | unit: string 36 | } 37 | 38 | export type Z2mDeviceSwitchableProperty = Z2mDeviceProperty & { 39 | value_on: string, 40 | value_off: string, 41 | value_toggle: string, 42 | } 43 | 44 | export type Z2mDeviceListProperty = Z2mDeviceProperty & { 45 | values: Array, 46 | } 47 | 48 | export type Z2mDeviceRangeProperty = Z2mDeviceProperty & { 49 | value_min: number, 50 | value_max: number, 51 | } 52 | 53 | export type Z2mDeviceStepableProperty = Z2mDeviceRangeProperty & { 54 | value_step: number, 55 | } -------------------------------------------------------------------------------- /src/icons/remote-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 54 | 56 | 58 | 59 | 66 | 70 | 75 | 80 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/icons/remote.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 54 | 56 | 58 | 59 | 66 | 70 | 75 | 80 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/lib/mqtt.ts: -------------------------------------------------------------------------------- 1 | // import { log } from "node-red"; 2 | 3 | export type MqttSubscriptionCallback = (payload: any, topic: string) => void 4 | 5 | export class MqttSubscription { 6 | topic: string; 7 | regex: RegExp; 8 | jsonPayload: boolean; 9 | callback: MqttSubscriptionCallback; 10 | 11 | constructor(topic: string, jsonPayload: boolean, callback: MqttSubscriptionCallback){ 12 | this.topic = topic; 13 | 14 | // create regex for matching mqtt subscriptions containing whitespace chars # and + 15 | let tmp: string = topic.split("+").join("[^\/]*"); 16 | tmp = tmp.split("#").join(".+"); 17 | this.regex = new RegExp(tmp + "$"); 18 | this.callback = callback; 19 | this.jsonPayload = jsonPayload; 20 | } 21 | 22 | invokeIfMatch(topic: string, payload: any) : void { 23 | if (this.comapreTopic(topic)) { 24 | if (this.jsonPayload) { 25 | try{ 26 | payload = JSON.parse(payload); 27 | } catch(err){ 28 | // log.error("################################################################"); 29 | // log.error(" node-red-contrib-zigbee2mqtt-devices error:"); 30 | // log.error(err); 31 | // log.error("################################################################"); 32 | } 33 | } 34 | 35 | this.callback(payload, topic); 36 | } 37 | } 38 | 39 | comapreTopic(topic: string) : boolean { 40 | return this.regex.test(topic); 41 | } 42 | } -------------------------------------------------------------------------------- /src/lib/outputHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates dynamic outputs for nodes and sends messages to the desired output. 3 | */ 4 | module.exports = class OutputHandler { 5 | 6 | /** 7 | * Creates a new dynamic output for the node. 8 | * @param {outputIndex}: Fixed index of the output. 9 | * @param {outputLabel}: The label of the output that is shown when hovering over it with the mouse. This value will be stored in msg.action.name to trace form which output the message came from. 10 | * @param {compareValue}: A unique identifier that is used in the prepareOutput method to send messages to this output. Prepare output checks that a property in the message object matches the actionName. 11 | * @param {actionDescription}: Human readable description stored in: message.action.description 12 | * @param {payload}: Fixed payload or function where to get the payload from e.g. "test" or msg => { msg.payload.valueX; }. 13 | */ 14 | addOutput(outputIndex, outputLabel, compareValue, actionDescription, payload) { 15 | 16 | if (!this.ioMap) { 17 | this.ioMap = {}; 18 | } 19 | 20 | this.ioMap[compareValue] = { 21 | index: outputIndex, 22 | name: outputLabel, 23 | description: actionDescription, 24 | payload: payload 25 | }; 26 | 27 | return this; 28 | } 29 | 30 | /** 31 | * Checks if the property specified in actionNamePathFilter matches the compareValue of one of the outputs. Then it sends the message to the matching output. 32 | * If actionNamePathFilter is set to "myvalue", this function checks that one of the outputs compareValues has the same value as message.myvalue. 33 | * @param {actionNamePathFilter}: Path to property to compare with compareValue. 34 | */ 35 | prepareOutput(actionNamePathFilter, msg) { 36 | var path = actionNamePathFilter.split("."); 37 | var value = msg; 38 | var isExtendedMessage = false; 39 | path.forEach(property => { 40 | value = value[property]; 41 | }); 42 | 43 | var data = this.ioMap[value]; 44 | var payload = data.payload; 45 | 46 | if (typeof data.payload === "function") { 47 | payload = data.payload(msg); 48 | } 49 | 50 | if (payload && Object.prototype.hasOwnProperty.call(payload, "payload") === true) { 51 | isExtendedMessage = true; 52 | } 53 | 54 | var output = []; 55 | for (var i = 0; i < data.index; i++) { 56 | output.push(null); 57 | } 58 | 59 | var action = { 60 | name: data.name, 61 | description: data.description, 62 | }; 63 | 64 | if (isExtendedMessage) { 65 | payload = payload.payload; 66 | } 67 | 68 | var preparedMessage = { 69 | action: action, 70 | payload: Object.assign({}, payload), 71 | }; 72 | 73 | output.push(preparedMessage); 74 | return output; 75 | } 76 | }; -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | 2 | function setConnectionState(bridgeNode, node) { 3 | if (bridgeNode.isConnected() === true) { 4 | node.status({ fill: "green", text: "connected" }); 5 | } else { 6 | node.status({ fill: "blue", text: "not connected" }); 7 | } 8 | } 9 | 10 | function createButtonOutput(output, name, type, payload) { 11 | return { 12 | index: output, 13 | button_name: name, 14 | button_type: type, 15 | button_payload: payload, 16 | }; 17 | } 18 | 19 | function sendAt(node, index, msg) { 20 | var output = []; 21 | for (var i = 0; i < index; i++) { 22 | output.push(null); 23 | } 24 | 25 | output.push(msg); 26 | node.send(output); 27 | } 28 | function createAction(property, type, value) { 29 | return { 30 | payload: { 31 | override: { 32 | action: { 33 | name: `${property}_${type}`, 34 | value: value 35 | } 36 | } 37 | } 38 | }; 39 | } 40 | 41 | function createSceneCommand(command, value) { 42 | return { 43 | command: command, 44 | scene: value 45 | }; 46 | } 47 | 48 | function createStateOverride(value) { 49 | var convertedValue = convertToOnOff(value); 50 | var override = createOverride("state", convertedValue); 51 | if(convertedValue === "ON"){ 52 | override.brightness = 254; 53 | } 54 | 55 | return override; 56 | } 57 | 58 | function createOverride(name, value) { 59 | var override = {}; 60 | override[name] = value; 61 | return override; 62 | } 63 | 64 | function convertToOnOff(value) { 65 | switch (value) { 66 | case "on": 67 | case "ON": 68 | case 1: 69 | case true: 70 | return "ON"; 71 | case "off": 72 | case "OFF": 73 | case 0: 74 | case false: 75 | return "OFF"; 76 | } 77 | } 78 | 79 | function propertyExists(obj, name) 80 | { 81 | return obj !== undefined && Object.prototype.hasOwnProperty.call(obj, name); 82 | } 83 | 84 | const bavaria = require("node-red-ext-bavaria-black"); 85 | 86 | module.exports = { 87 | createButtonOutput: createButtonOutput, 88 | bavaria: () => bavaria, 89 | sendAt: sendAt, 90 | propertyExists: propertyExists, 91 | setConnectionState: setConnectionState, 92 | payloads: { 93 | createColorTempStep: (value) => { return createAction("color_temp", "step", value); }, 94 | createColorTempMove: (value) => { return createAction("color_temp", "move", value == 0 ? "stop" : value); }, 95 | 96 | createBrightnessStep: (value) => { return createAction("brightness", "step_onoff", value); }, 97 | createBrightnessMove: (value) => { return createAction("brightness", "move_onoff", value); }, 98 | 99 | createHueStep: (value) => { return createAction("hue", "step", value); }, 100 | createHueMove: (value) => { return createAction("hue", "move", value); }, 101 | 102 | createSaturationStep: (value) => { return createAction("saturation", "step", value); }, 103 | createSaturationMove: (value) => { return createAction("saturation", "move", value); }, 104 | createNextSceneCommand: () => { return createSceneCommand("next", undefined); }, 105 | createPreviousSceneCommand: () => { return createSceneCommand("previous", undefined); }, 106 | createSetSceneCommand: (value) => { return createSceneCommand("set", value); }, 107 | convertToOnOff: convertToOnOff, 108 | overrides: { 109 | createStateOverride: (value) => { return { override: createStateOverride(value) }; }, 110 | createBrightnessOverride: (value) => { return { override: createOverride("brightness", value) }; }, 111 | }, 112 | devices: { 113 | addDevice: (msg, device) => { 114 | if (msg.payload === undefined || typeof msg.payload != "object") { 115 | msg.payload = {}; 116 | } 117 | 118 | if (msg.payload.devices === undefined) { 119 | msg.payload.devices = []; 120 | } 121 | 122 | msg.payload.devices.push(device); 123 | return msg; 124 | } 125 | } 126 | }, 127 | outputs: { 128 | preapreOutputFor: (index, payload) => { 129 | var output = []; 130 | for (var i = 0; i < index; i++) { 131 | output.push(null); 132 | } 133 | 134 | output.push(payload); 135 | return output; 136 | } 137 | }, 138 | ui: { 139 | input: { 140 | getPayload: function (data, type) { 141 | switch (type) { 142 | case "num": 143 | return Number.parseFloat(data); 144 | case "bool": 145 | return data == true; 146 | case "json": 147 | return JSON.parse(data); 148 | } 149 | 150 | return data; 151 | } 152 | } 153 | } 154 | }; -------------------------------------------------------------------------------- /src/nodes/devices-eurotronic.html: -------------------------------------------------------------------------------- 1 | 2 | 49 | 50 | 79 | 80 | -------------------------------------------------------------------------------- /src/nodes/devices-eurotronic.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | const utils = require("../lib/utils.js"); 3 | const bavaria = utils.bavaria(); 4 | 5 | function createEurotronicsSpirit(config) { 6 | RED.nodes.createNode(this, config); 7 | var node = this; 8 | var bridgeNode = RED.nodes.getNode(config.bridge); 9 | 10 | if (config.windowSensor !== undefined) { 11 | bridgeNode.subscribeDevice(node.id, config.windowSensor, function (msg) { 12 | bridgeNode.publishDevice(config.deviceName, { 13 | eurotronic_host_flags: { 14 | window_open: !msg.contact, 15 | } 16 | }); 17 | }); 18 | } 19 | 20 | bridgeNode.subscribeDevice(node.id + "1", config.deviceName, function (msg) { 21 | var text = `Battery: ${msg.battery}% `; 22 | text += `T: ${msg.local_temperature}° `; 23 | text += `SP: ${msg.current_heating_setpoint}°`; 24 | node.status({ fill: "green", text: text }); 25 | }); 26 | 27 | function containsProperty(msg, name, type) { 28 | return msg.payload[name] !== undefined && typeof msg.payload[name] == type; 29 | } 30 | 31 | utils.setConnectionState(bridgeNode, node); 32 | const regId = bavaria.observer.register(bridgeNode.id + "_connected", function (message) { 33 | node.status({ fill: "green", text: "connected" }); 34 | }); 35 | 36 | node.on("input", function (msg) { 37 | if (containsProperty(msg, "heatingSetpoint", "number") 38 | || containsProperty(msg, "child_protection", "boolean") 39 | || containsProperty(msg, "mirror_display", "boolean")) { 40 | if (msg.payload.devices === undefined) { 41 | msg.payload.devices = []; 42 | } 43 | 44 | var device = { 45 | topic: config.deviceName, 46 | current_heating_setpoint: msg.payload.heatingSetpoint, 47 | eurotronic_host_flags: { 48 | child_protection: msg.payload.child_protection !== undefined ? msg.payload.child_protection : config.childProtection === true, 49 | mirror_display: msg.payload.mirror_display !== undefined ? msg.payload.mirror_display : config.mirrorDisplay === true, 50 | }, 51 | target: "z2m" 52 | }; 53 | 54 | msg.payload.heatingSetpoint = undefined; 55 | msg.payload.child_protection = undefined; 56 | msg.payload.mirror_display = undefined; 57 | 58 | msg.payload.devices.push(device); 59 | node.send(msg); 60 | } 61 | }); 62 | 63 | node.on("close", function () { 64 | bavaria.observer.unregister(regId); 65 | }); 66 | } 67 | 68 | RED.nodes.registerType("eurotronic-spirit", createEurotronicsSpirit); 69 | }; 70 | -------------------------------------------------------------------------------- /src/nodes/devices-scenic.html: -------------------------------------------------------------------------------- 1 | 2 | 27 | 28 | 44 | 45 | -------------------------------------------------------------------------------- /src/nodes/devices-scenic.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (RED) { 3 | const utils = require("../lib/utils.js"); 4 | const bavaria = utils.bavaria(); 5 | 6 | function scenicSwitch(config) { 7 | RED.nodes.createNode(this, config); 8 | var bridgeNode = RED.nodes.getNode(config.bridge); 9 | var node = this; 10 | 11 | utils.setConnectionState(bridgeNode, node); 12 | const regId = bavaria.observer.register(bridgeNode.id + "_connected", function (message) { 13 | node.status({ fill: "green", text: "connected" }); 14 | bridgeNode.subscribeDevice(node.id, config.deviceName, function (message) { 15 | if (message.action === undefined || message.action === "" || message.action === null) { 16 | // Ignore message with empty action 17 | return; 18 | } 19 | 20 | var ioMap = { 21 | press_1: utils.createButtonOutput(0, "A0", "pressed"), 22 | release_1: utils.createButtonOutput(0, "A0", "released"), 23 | press_2: utils.createButtonOutput(1, "A1", "pressed"), 24 | release_2: utils.createButtonOutput(1, "A1", "released"), 25 | press_3: utils.createButtonOutput(2, "B0", "pressed"), 26 | release_3: utils.createButtonOutput(2, "B0", "released"), 27 | press_4: utils.createButtonOutput(3, "B1", "pressed"), 28 | release_4: utils.createButtonOutput(3, "B1", "released"), 29 | press_1_and_3: utils.createButtonOutput(4, "UP", "pressed"), 30 | release_1_and_3: utils.createButtonOutput(4, "UP", "released"), 31 | press_2_and_4: utils.createButtonOutput(5, "DOWN", "pressed"), 32 | release_2_and_4: utils.createButtonOutput(5, "DOWN", "released"), 33 | }; 34 | 35 | var output = ioMap[message.action]; 36 | utils.sendAt(node, output.index, { 37 | payload: { 38 | button_name: output.button_name, 39 | button_type: output.button_type, 40 | } 41 | }); 42 | }); 43 | }); 44 | 45 | node.on("close", ()=>{ 46 | bavaria.observer.unregister(regId); 47 | }); 48 | } 49 | RED.nodes.registerType("scenic-foh-switch", scenicSwitch); 50 | }; -------------------------------------------------------------------------------- /src/nodes/devices-sonoff.html: -------------------------------------------------------------------------------- 1 | 2 | 27 | 28 | 44 | 45 | -------------------------------------------------------------------------------- /src/nodes/devices-sonoff.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | const utils = require("../lib/utils.js"); 3 | const bavaria = utils.bavaria(); 4 | 5 | function sonoffButton(config) { 6 | RED.nodes.createNode(this, config); 7 | var bridgeNode = RED.nodes.getNode(config.bridge); 8 | var node = this; 9 | 10 | utils.setConnectionState(bridgeNode, node); 11 | const regId = bavaria.observer.register(bridgeNode.id + "_connected", function (message) { 12 | node.status({ fill: "green", text: "connected" }); 13 | bridgeNode.subscribeDevice(node.id, config.deviceName, function (message) { 14 | try { 15 | if (message.action === undefined || message.action === "" || message.action === null) { 16 | // Ignore message with empty action 17 | return; 18 | } 19 | 20 | const ioMap = { 21 | single: utils.createButtonOutput(0, "button", "pressed"), 22 | long: utils.createButtonOutput(0, "button", "released"), 23 | double: utils.createButtonOutput(0, "button", "double"), 24 | }; 25 | 26 | var output = ioMap[message.action]; 27 | utils.sendAt(node, output.index, { 28 | payload: { 29 | button_name: output.button_name, 30 | button_type: output.button_type, 31 | } 32 | }); 33 | 34 | node.status({ fill: "green", "text": "Last action: " + output.button_type }); 35 | setTimeout(function () { 36 | node.status({ fill: "green", text: "connected" }); 37 | }, 2000); 38 | } catch (err) { 39 | node.error(err); 40 | node.status({ fill: "red", "text": "error" }); 41 | } 42 | }); 43 | }); 44 | 45 | node.on("close", () => { 46 | bavaria.observer.unregister(regId); 47 | }); 48 | } 49 | RED.nodes.registerType("sonoff-button", sonoffButton); 50 | }; -------------------------------------------------------------------------------- /src/nodes/devices-tasmota.html: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 47 | 48 | -------------------------------------------------------------------------------- /src/nodes/devices-tasmota.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | const utils = require("../lib/utils.js"); 3 | const bavaria = utils.bavaria(); 4 | 5 | function createTasmota(config) { 6 | RED.nodes.createNode(this, config); 7 | var node = this; 8 | const possibleValues = ["on", "off", "toggle", "0", "1", "2"]; 9 | 10 | function preparePayload(msg) 11 | { 12 | node.warn(msg); 13 | return msg; 14 | } 15 | 16 | node.on("input", function (msg) { 17 | if (possibleValues.includes(msg.payload.toString().toLowerCase())) 18 | { 19 | utils.payloads.devices.addDevice(msg, { 20 | topic: `cmnd/${config.topic}/power`, 21 | state: utils.payloads.convertToOnOff(msg.payload), 22 | target: "mqtt", 23 | payloadGenerator: preparePayload 24 | }); 25 | node.send(msg); 26 | } 27 | }); 28 | 29 | node.on("close", function () { 30 | }); 31 | } 32 | 33 | RED.nodes.registerType("tasmota", createTasmota); 34 | }; 35 | -------------------------------------------------------------------------------- /src/nodes/devices-tint.html: -------------------------------------------------------------------------------- 1 | 2 | 40 | 41 | 86 | 87 | -------------------------------------------------------------------------------- /src/nodes/devices-tint.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | const OutputHandler = require("../lib/outputHandler.js"); 3 | const utils = require("../lib/utils.js"); 4 | const bavaria = utils.bavaria(); 5 | 6 | function tintRemote(config) { 7 | RED.nodes.createNode(this, config); 8 | var bridgeNode = RED.nodes.getNode(config.bridge); 9 | var node = this; 10 | var nodeConext = this.context(); 11 | 12 | utils.setConnectionState(bridgeNode, node); 13 | const regId = bavaria.observer.register(bridgeNode.id + "_connected", function (message) { 14 | node.status({ fill: "green", text: "connected" }); 15 | }); 16 | 17 | function getDirection(msg) { 18 | var lastTemp = nodeConext.get("lastColorTemp"); 19 | var currentTemp = msg.action_color_temperature; 20 | nodeConext.set("lastColorTemp", currentTemp); 21 | 22 | if (currentTemp === 150) { 23 | return "colder"; 24 | } else if (currentTemp >= 370 && (!lastTemp || lastTemp < 370)) { 25 | return "warmer"; 26 | } 27 | 28 | if (!lastTemp) { 29 | return "unknown"; 30 | } 31 | 32 | return lastTemp > currentTemp ? "colder" : "warmer"; 33 | } 34 | 35 | function getColorTempStep(msg) { 36 | var step = config.temperatureChange || 50; 37 | var direction = getDirection(msg); 38 | if (direction === "colder") { 39 | step *= -1; 40 | } else if (direction === "unknown") { 41 | step = 0; 42 | } 43 | 44 | return step; 45 | } 46 | 47 | var handler = new OutputHandler(); 48 | handler 49 | .addOutput(0, "power", "on", "pressed", config.suppressPowerpayload ? "toggle" : "on") 50 | .addOutput(0, "power", "off", "pressed", config.suppressPowerpayload ? "toggle" : "off") 51 | .addOutput(1, "color", "color_wheel", "pressed", (msg) => { 52 | return bavaria.converter.xyToRgb(msg.action_color.x, msg.action_color.y); 53 | }) 54 | .addOutput(2, "temperature", "color_temp", "pressed", (msg) => { return utils.payloads.createColorTempStep(getColorTempStep(msg)); }) 55 | .addOutput(3, "brightness_up", "brightness_up_click", "pressed", utils.payloads.createBrightnessStep(config.brightnessChange)) 56 | .addOutput(3, "brightness_up", "brightness_up_hold", "hold", utils.payloads.createBrightnessMove(config.brightnessChange)) 57 | .addOutput(3, "brightness_up", "brightness_up_release", "released", utils.payloads.createBrightnessMove(0)) 58 | .addOutput(4, "brightness_down", "brightness_down_click", "pressed", utils.payloads.createBrightnessStep(0 - config.brightnessChange)) 59 | .addOutput(4, "brightness_down", "brightness_down_hold", "hold", utils.payloads.createBrightnessMove(0 - config.brightnessChange)) 60 | .addOutput(4, "brightness_down", "brightness_down_release", "released", utils.payloads.createBrightnessMove(0)) 61 | .addOutput(5, "scene", "scene_3", "pressed", utils.payloads.createSetSceneCommand(0)) 62 | .addOutput(5, "scene", "scene_1", "pressed", utils.payloads.createSetSceneCommand(1)) 63 | .addOutput(5, "scene", "scene_2", "pressed", utils.payloads.createSetSceneCommand(2)) 64 | .addOutput(5, "scene", "scene_6", "pressed", utils.payloads.createSetSceneCommand(3)) 65 | .addOutput(5, "scene", "scene_4", "pressed", utils.payloads.createSetSceneCommand(4)) 66 | .addOutput(5, "scene", "scene_5", "pressed", utils.payloads.createSetSceneCommand(5)); 67 | 68 | bridgeNode.subscribeDevice(node.id, config.deviceName, function (msg) { 69 | try { 70 | node.send(handler.prepareOutput("action", msg)); 71 | node.status({ fill: "green", "text": "Last action: " + msg.action }); 72 | setTimeout(function () { 73 | utils.setConnectionState(bridgeNode, node); 74 | }, 2000); 75 | } catch (err) { 76 | node.error(err); 77 | node.status({ fill: "red", "text": "error" }); 78 | } 79 | }); 80 | 81 | node.on("close", () => { 82 | bridgeNode.unsubscribe(node.id); 83 | bavaria.observer.unregister(regId); 84 | }); 85 | } 86 | RED.nodes.registerType("tint-remote", tintRemote); 87 | }; -------------------------------------------------------------------------------- /src/nodes/hue-dimmer/devices-hue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | 30 | 46 | 47 | 65 | -------------------------------------------------------------------------------- /src/nodes/hue-dimmer/devices-hue.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | const utils = require("../../lib/utils.js"); 3 | const bavaria = utils.bavaria(); 4 | 5 | function hueDimmerSwitch(config) { 6 | RED.nodes.createNode(this, config); 7 | var bridgeNode = RED.nodes.getNode(config.bridge); 8 | var node = this; 9 | 10 | utils.setConnectionState(bridgeNode, node); 11 | const regId = bavaria.observer.register(bridgeNode.id + "_connected", function (message) { 12 | node.status({ fill: "green", text: "connected" }); 13 | bridgeNode.subscribeDevice(node.id, config.deviceName, function (message) { 14 | if (message.action === undefined || message.action === "" || message.action === null) { 15 | // Ignore message with empty action 16 | return; 17 | } 18 | 19 | message.action = message.action.split("-").join("_"); 20 | 21 | const ioMap = {}; 22 | ioMap["on_press"] = utils.createButtonOutput(0, "on", "pressed"); 23 | ioMap["on_hold"] = utils.createButtonOutput(0, "on", "hold"); 24 | ioMap["on_hold_release"] = utils.createButtonOutput(0, "on", "released"); 25 | ioMap["off_press"] = utils.createButtonOutput(1, "off", "pressed"); 26 | ioMap["off_hold"] = utils.createButtonOutput(1, "off", "hold"); 27 | ioMap["off_hold_release"] = utils.createButtonOutput(1, "off", "released"); 28 | ioMap["up_press"] = utils.createButtonOutput(2, "up", "pressed"); 29 | ioMap["up_hold"] = utils.createButtonOutput(2, "up", "hold"); 30 | ioMap["up_hold_release"] = utils.createButtonOutput(2, "up", "released"); 31 | ioMap["down_press"] = utils.createButtonOutput(3, "down", "pressed"); 32 | ioMap["down_hold"] = utils.createButtonOutput(3, "down", "hold"); 33 | ioMap["down_hold_release"] = utils.createButtonOutput(3, "down", "released"); 34 | 35 | var output = ioMap[message.action]; 36 | utils.sendAt(node, output.index, { 37 | payload: { 38 | button_name: output.button_name, 39 | button_type: output.button_type, 40 | } 41 | }); 42 | }); 43 | }); 44 | 45 | node.on("close", ()=>{ 46 | bavaria.observer.unregister(regId); 47 | }); 48 | } 49 | RED.nodes.registerType("hue-dimmer-switch", hueDimmerSwitch); 50 | }; 51 | -------------------------------------------------------------------------------- /src/nodes/override/override-nodes.ts: -------------------------------------------------------------------------------- 1 | import {NodeAPI, NodeMessageInFlow} from "node-red"; 2 | import { 3 | OverrideActionNode, 4 | OverrideActionNodeDef, 5 | OverrideBrightnessNode, 6 | OverrideBrightnessNodeDef, 7 | OverrideColorNode, 8 | OverrideColorNodeDef, 9 | OverrideStateNode, 10 | OverrideStateNodeDef, 11 | OverrideTemperatureNode, 12 | OverrideTemperatureNodeDef 13 | } from "./types"; 14 | 15 | module.exports = function (RED: NodeAPI) { 16 | const utils = require("../../lib/utils.js"); 17 | const bavaria = utils.bavaria(); 18 | 19 | function PayloadOverride(msg: T, payload: U): asserts msg is (T & { payload: { override: U } }) { 20 | Object.assign(msg, { 21 | payload: { 22 | ...msg.payload as object, 23 | override: { 24 | // @ts-ignore TODO: Weg finden welcher TypeGuard sagt was sache ist 25 | ...(typeof msg.payload === "object" && msg.payload !== null && ("override" in msg.payload) && msg.payload.override), 26 | ...payload 27 | } 28 | } 29 | }); 30 | } 31 | 32 | /** 33 | * 34 | * @param config 35 | * @constructor 36 | */ 37 | function OverrideStateConstructor(this: OverrideStateNode, config: OverrideStateNodeDef) { 38 | RED.nodes.createNode(this, config); 39 | 40 | this.on("input", (msg: NodeMessageInFlow) => { 41 | PayloadOverride(msg, {state: config.state}); 42 | this.send(msg); 43 | }); 44 | } 45 | 46 | RED.nodes.registerType("override-state", OverrideStateConstructor); 47 | 48 | /** 49 | * 50 | * @param config 51 | * @constructor 52 | */ 53 | function OverrideBrightnessConstructor(this: OverrideBrightnessNode, config: OverrideBrightnessNodeDef) { 54 | RED.nodes.createNode(this, config); 55 | 56 | this.on("input", (msg) => { 57 | PayloadOverride(msg, {brightness: config.brightness}); 58 | this.send(msg); 59 | }); 60 | } 61 | 62 | RED.nodes.registerType("override-brightness", OverrideBrightnessConstructor); 63 | 64 | /** 65 | * 66 | * @param config 67 | * @constructor 68 | */ 69 | function OverrideTemperatureConstructor(this: OverrideTemperatureNode, config: OverrideTemperatureNodeDef) { 70 | RED.nodes.createNode(this, config); 71 | this.on("input", (msg) => { 72 | 73 | PayloadOverride(msg, {temperature: config.temperature}); 74 | this.send(msg); 75 | }); 76 | } 77 | 78 | RED.nodes.registerType("override-temperature", OverrideTemperatureConstructor); 79 | 80 | /** 81 | * 82 | * @param config 83 | * @constructor 84 | */ 85 | function OverrideColorConstructor(this: OverrideColorNode, config: OverrideColorNodeDef) { 86 | RED.nodes.createNode(this, config); 87 | this.on("input", (msg) => { 88 | PayloadOverride(msg, { 89 | color: { 90 | r: config.red, 91 | g: config.green, 92 | b: config.blue, 93 | } 94 | }); 95 | this.send(msg); 96 | }); 97 | } 98 | 99 | RED.nodes.registerType("override-color", OverrideColorConstructor); 100 | 101 | /** 102 | * 103 | * @param config 104 | * @constructor 105 | */ 106 | function OverrideActionConstructor(this: OverrideActionNode, config: OverrideActionNodeDef) { 107 | RED.nodes.createNode(this, config); 108 | 109 | this.on("input", (msg) => { 110 | 111 | switch (config.mode) { 112 | case "brightness_move": 113 | PayloadOverride(msg, {action: utils.payloads.createBrightnessMove(config.value)}); 114 | break; 115 | case "brightness_step": 116 | PayloadOverride(msg, {action: utils.payloads.createBrightnessStep(config.value)}); 117 | break; 118 | case "color_temp_move": 119 | PayloadOverride(msg, {action: utils.payloads.createColorTempMove(config.value)}); 120 | break; 121 | case "color_temp_step": 122 | PayloadOverride(msg, {action: utils.payloads.createColorTempStep(config.value)}); 123 | break; 124 | case "hue_move": 125 | PayloadOverride(msg, {action: utils.payloads.createHueMove(config.value)}); 126 | break; 127 | case "hue_step": 128 | PayloadOverride(msg, {action: utils.payloads.createHueStep(config.value)}); 129 | break; 130 | case "saturation_move": 131 | PayloadOverride(msg, {action: utils.payloads.createSaturationMove(config.value)}); 132 | break; 133 | case "saturation_step": 134 | PayloadOverride(msg, {action: utils.payloads.createSaturationStep(config.value)}); 135 | break; 136 | } 137 | msg.payload.override = msg.payload.override.action.payload.override; 138 | this.send(msg); 139 | }); 140 | } 141 | 142 | RED.nodes.registerType("override-action", OverrideActionConstructor); 143 | }; -------------------------------------------------------------------------------- /src/nodes/override/types.ts: -------------------------------------------------------------------------------- 1 | import {Node, NodeDef, NodeMessage} from "node-red"; 2 | 3 | 4 | export type OverridePayload = { override: OverrideStatePayload | OverrideBrightnessPayload | OverrideTemperaturePayload | OverrideColorPayload | OverrideActionPayload } 5 | 6 | /********* 7 | * OverrideState 8 | */ 9 | export interface OverrideStateOptions { 10 | state: "ON" | "OFF" | "TOGGLE" // default ON 11 | } 12 | 13 | export interface OverrideStateNodeDef extends NodeDef, OverrideStateOptions { 14 | } 15 | 16 | export interface OverrideStateNode extends Node { 17 | state: OverrideStateOptions['state'] 18 | 19 | send(msg: OverrideSateMessageOut): void 20 | } 21 | 22 | export interface OverrideSateMessageOut extends NodeMessage { 23 | payload: { 24 | override: OverrideStatePayload 25 | } 26 | } 27 | 28 | export type OverrideStatePayload = { 29 | state: OverrideStateOptions['state'] 30 | } 31 | 32 | /********* 33 | * OverrideState 34 | */ 35 | export interface OverrideBrightnessOptions { 36 | brightness: number 37 | } 38 | 39 | export interface OverrideBrightnessNodeDef extends NodeDef, OverrideBrightnessOptions { 40 | } 41 | 42 | export interface OverrideBrightnessNode extends Node { 43 | 44 | send(msg: OverrideBrightnessMessageOut): void 45 | } 46 | 47 | export interface OverrideBrightnessMessageOut extends NodeMessage { 48 | payload: { 49 | override: OverrideBrightnessPayload 50 | } 51 | } 52 | 53 | export type OverrideBrightnessPayload = { 54 | brightness: OverrideBrightnessOptions['brightness'] 55 | } 56 | 57 | 58 | /********* 59 | * OverrideTemperature 60 | */ 61 | export interface OverrideTemperatureOptions { 62 | temperature: number 63 | } 64 | 65 | export interface OverrideTemperatureNodeDef extends NodeDef, OverrideTemperatureOptions { 66 | } 67 | 68 | export interface OverrideTemperatureNode extends Node { 69 | 70 | send(msg: OverrideTemperatureMessageOut): void 71 | } 72 | 73 | export interface OverrideTemperatureMessageOut extends NodeMessage { 74 | payload: { 75 | override: OverrideTemperaturePayload 76 | } 77 | } 78 | 79 | export type OverrideTemperaturePayload = { 80 | temperature: OverrideTemperatureOptions['temperature'] 81 | } 82 | 83 | 84 | /********* 85 | * OverrideColor 86 | */ 87 | export interface OverrideColorOptions { 88 | red: number 89 | green: number 90 | blue: number 91 | } 92 | 93 | export interface OverrideColorNodeDef extends NodeDef, OverrideColorOptions { 94 | } 95 | 96 | export interface OverrideColorNode extends Node { 97 | 98 | send(msg: OverrideColorMessageOut): void 99 | } 100 | 101 | export interface OverrideColorMessageOut extends NodeMessage { 102 | payload: { 103 | override: OverrideColorPayload 104 | } 105 | } 106 | 107 | export type OverrideColorPayload = { 108 | color: { 109 | r: OverrideColorOptions['red'] 110 | g: OverrideColorOptions['green'] 111 | b: OverrideColorOptions['blue'] 112 | } 113 | } 114 | 115 | 116 | /********* 117 | * OverrideAction 118 | */ 119 | export type ActionTypes = "brightness_move" | "brightness_step" | "color_temp_move" | "color_temp_step" | "hue_move" | "hue_step" | "saturation_move" | "saturation_step" 120 | 121 | export interface OverrideActionOptions { 122 | value: number 123 | mode: ActionTypes 124 | 125 | } 126 | 127 | export interface OverrideActionNodeDef extends NodeDef, OverrideActionOptions { 128 | } 129 | 130 | export interface OverrideActionNode extends Node { 131 | 132 | send(msg: OverrideActionMessageOut): void 133 | } 134 | 135 | export interface OverrideActionMessageOut extends NodeMessage { 136 | payload: { 137 | override: OverrideActionPayload 138 | } 139 | } 140 | 141 | export type OverrideActionPayload = {} 142 | 143 | 144 | -------------------------------------------------------------------------------- /src/nodes/scenes.html: -------------------------------------------------------------------------------- 1 | 2 | 44 | 45 | 58 | 59 | 62 | 63 | 64 | 140 | 141 | 170 | 171 | -------------------------------------------------------------------------------- /src/nodes/scenes.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | 3 | function getSceneInNodes(scene) { 4 | var nodes = []; 5 | RED.nodes.eachNode(n => { 6 | try { 7 | if (n.type === "scene-in" && n.scene === scene) { 8 | nodes.push(RED.nodes.getNode(n.id)); 9 | } 10 | } catch (err) { 11 | console.log(err); 12 | } 13 | }); 14 | 15 | return nodes; 16 | } 17 | 18 | function sceneIn(config) { 19 | RED.nodes.createNode(this, config); 20 | var node = this; 21 | 22 | this.isActive = function () { 23 | return config.active; 24 | }; 25 | 26 | this.trigger = function (msg) { 27 | if (config.active === true) { 28 | msg.scene = config.scene; 29 | node.send(msg); 30 | } 31 | }; 32 | } 33 | RED.nodes.registerType("scene-in", sceneIn); 34 | 35 | function sceneSelector(config) { 36 | RED.nodes.createNode(this, config); 37 | var node = this; 38 | 39 | if (config.scenes.length === 0) { 40 | node.status({ fill: "red", text: "no scenes configured" }); 41 | return; 42 | } 43 | 44 | var nodeContext = this.context(); 45 | if (!nodeContext.get("index")) { 46 | nodeContext.set("index", - 1); 47 | } 48 | 49 | function setState(index) { 50 | var scene = index == -1 ? "---" : config.scenes[index]; 51 | node.status({ 52 | fill: "yellow", 53 | text: "Idx: " + index + ", Scene: " + scene 54 | }); 55 | } 56 | 57 | var index = nodeContext.get("index"); 58 | setState(index); 59 | 60 | node.on("input",msg => handleMessage(msg, 0)); 61 | 62 | function handleMessage(msg, revCount) { 63 | if(revCount >= config.scenes.length ){ 64 | node.error("All configured scenes are inactive"); 65 | return; 66 | } 67 | revCount++; 68 | 69 | var command = msg.command !== undefined ? msg.command : msg.payload.command; 70 | var scene = msg.scene; 71 | 72 | if(!scene && msg.payload) { 73 | scene = msg.payload.scene; 74 | } 75 | 76 | if (!command) { 77 | return; 78 | } 79 | 80 | switch (command) { 81 | case "next": 82 | index++; 83 | break; 84 | case "previous": 85 | case "prev": 86 | index--; 87 | break; 88 | case "set": 89 | if (typeof scene === "number" && scene < config.scenes.length && scene >= 0) { 90 | index = scene; 91 | } else if (typeof scene === "string") { 92 | index = config.scenes.indexOf(scene); 93 | if (index < 0) { 94 | node.error("invalid scene"); 95 | return; 96 | } 97 | } else { 98 | node.error("invalid scene"); 99 | return; 100 | } 101 | break; 102 | default: 103 | node.error("Command not found"); 104 | return; 105 | } 106 | 107 | if (config.wrapAround === false) { 108 | index = Math.min(config.scenes.length - 1, index); 109 | index = Math.max(0, index); 110 | } else { 111 | if (index >= config.scenes.length) { 112 | index = 0; 113 | } else if (index < 0) { 114 | index = config.scenes.length - 1; 115 | } 116 | } 117 | 118 | if (config.changedOutputOnly === false || nodeContext.get("index") != index) { 119 | var nodes = getSceneInNodes(config.scenes[index]).filter(n => n.isActive()); 120 | 121 | if (nodes.length === 0) { 122 | if (command === "next" || command === "previous") { 123 | handleMessage(msg, revCount); 124 | } else { 125 | node.error(`No active node for scene "${config.scenes[index]}" found.`); 126 | return; 127 | } 128 | } 129 | 130 | msg.command = undefined; 131 | nodes.forEach(n => { 132 | n.trigger(msg); 133 | }); 134 | 135 | nodeContext.set("index", index); 136 | setState(index); 137 | } 138 | } 139 | } 140 | RED.nodes.registerType("scene-selector", sceneSelector); 141 | }; -------------------------------------------------------------------------------- /src/nodes/sensors.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable quotes */ 2 | module.exports = function (RED) { 3 | const utils = require("../lib/utils.js"); 4 | const bavaria = utils.bavaria(); 5 | 6 | function contactSensor(config) { 7 | RED.nodes.createNode(this, config); 8 | var bridgeNode = RED.nodes.getNode(config.bridge); 9 | var node = this; 10 | 11 | utils.setConnectionState(bridgeNode, node); 12 | const regId = bavaria.observer.register(bridgeNode.id + "_connected", function (message) { 13 | node.status({ fill: "green", text: "connected" }); 14 | 15 | bridgeNode.subscribeDevice(node.id, config.deviceName, function (message) { 16 | if (message.contact) { 17 | node.send({ payload: message }); 18 | } else { 19 | node.send([null, { payload: message }]); 20 | } 21 | }); 22 | }); 23 | 24 | node.on("close", () => { 25 | bavaria.observer.unregister(regId); 26 | }); 27 | } 28 | RED.nodes.registerType("contact-sensor", contactSensor); 29 | 30 | function occupancySensor(config) { 31 | RED.nodes.createNode(this, config); 32 | var bridgeNode = RED.nodes.getNode(config.bridge); 33 | var node = this; 34 | 35 | utils.setConnectionState(bridgeNode, node); 36 | const regId = bavaria.observer.register(bridgeNode.id + "_connected", function (message) { 37 | node.status({ fill: "green", text: "connected" }); 38 | 39 | bridgeNode.subscribeDevice(node.id, config.deviceName, function (message) { 40 | if (message.occupancy) { 41 | node.send({ payload: message }); 42 | } else { 43 | node.send([null, { payload: message }]); 44 | } 45 | }); 46 | }); 47 | 48 | node.on("close", () => { 49 | bavaria.observer.unregister(regId); 50 | }); 51 | } 52 | RED.nodes.registerType("occupancy-sensor", occupancySensor); 53 | 54 | function climateSensor(config) { 55 | RED.nodes.createNode(this, config); 56 | var bridgeNode = RED.nodes.getNode(config.bridge); 57 | var node = this; 58 | utils.setConnectionState(bridgeNode, node); 59 | 60 | function messageReceived(message) { 61 | var outputs = [message]; 62 | 63 | var text = ""; 64 | if (config.temperature === true) { 65 | text += "T: " + message.temperature + "C° "; 66 | outputs.push({ payload: message.temperature, device_name: config.deviceName }); 67 | } 68 | 69 | if (config.pressure === true) { 70 | text += "P: " + message.pressure + "mBar "; 71 | outputs.push({ payload: message.pressure, device_name: config.deviceName }); 72 | } 73 | 74 | if (config.humidity === true) { 75 | text += "H: " + message.humidity + "%"; 76 | outputs.push({ payload: message.humidity, device_name: config.deviceName }); 77 | } 78 | 79 | if (config.co2 === true) { 80 | text += "CO2: " + message.co2 + "ppm"; 81 | outputs.push({ payload: message.co2, device_name: config.deviceName }); 82 | } 83 | 84 | node.status({ fill: "green", text: text }); 85 | if (config.separateOutputs === true) { 86 | node.send(outputs); 87 | 88 | } else { 89 | node.send({ payload: message, device_name: config.deviceName }); 90 | } 91 | } 92 | 93 | function subscribe() { 94 | node.status({ fill: "green", text: "connected" }); 95 | bridgeNode.subscribeDevice(node.id, config.deviceName, messageReceived); 96 | } 97 | 98 | if (bridgeNode.isConnected() === true) { 99 | subscribe(); 100 | } 101 | 102 | const regId = bavaria.observer.register(bridgeNode.id + "_connected", function (message) { 103 | subscribe(); 104 | }); 105 | 106 | node.on('close', function () { 107 | bridgeNode.unsubscribe(node.id); 108 | bavaria.observer.unregister(regId); 109 | }); 110 | } 111 | RED.nodes.registerType("climate-sensor", climateSensor); 112 | }; -------------------------------------------------------------------------------- /src/non-z2m-nodes/devices-shelly-25.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | const utils = require("../lib/utils.js"); 3 | const bavaria = utils.bavaria(); 4 | 5 | function createShellyConfig(config) { 6 | RED.nodes.createNode(this, config); 7 | this.prefix = config.prefix; 8 | this.name = config.name; 9 | 10 | } 11 | 12 | RED.nodes.registerType("shelly-config", createShellyConfig); 13 | 14 | function createShelly25(config) { 15 | RED.nodes.createNode(this, config); 16 | const node = this; 17 | const broker = RED.nodes.getNode(config.mqtt); 18 | const shelly = RED.nodes.getNode(config.shelly); 19 | 20 | const context = node.context(); 21 | const status = context.get("status") || { relay: [{ state: "off", energy: 0, power: 0 }, { state: "off", energy: 0, power: 0 }] }; 22 | const channel = parseInt(config.channel); 23 | 24 | let subscribedTopics = []; 25 | 26 | broker.register(node); 27 | 28 | function subscribe(channel) { 29 | subscribeRaw(node.id, `${shelly.prefix}/relay/${channel}/power`, (msg) => { setPower(channel, msg); }); 30 | subscribeRaw(node.id, `${shelly.prefix}/relay/${channel}`, (msg) => { setRelay(channel, msg); }); 31 | subscribeRaw(node.id, `${shelly.prefix}/relay/${channel}/energy`, (msg) => { setEnergy(channel, msg); }); 32 | subscribeRaw(node.id, `${shelly.prefix}/input/${channel}`, (msg) => { inputReceived(msg, channel); }); 33 | } 34 | 35 | function subscribeRaw(id, topic, callback) 36 | { 37 | broker.subscribe(topic, 0, (topic, payload, packet) => { 38 | callback(payload.toString("utf8")); 39 | }, id); 40 | subscribedTopics.push(topic); 41 | } 42 | 43 | /** 44 | * Unsubscribe from all topics 45 | */ 46 | function unsubscribeAll(){ 47 | for(const topic of subscribedTopics){ 48 | broker.unsubscribe(topic, node.id, true); 49 | } 50 | 51 | subscribedTopics = []; 52 | } 53 | 54 | if (channel === 2) { 55 | subscribe(0); 56 | subscribe(1); 57 | } else { 58 | subscribe(channel, 0); 59 | } 60 | 61 | function inputReceived(msg, channel) { 62 | msg = { 63 | payload: msg 64 | }; 65 | if (config.customPayload === true) { 66 | const data = config["payloadInput" + channel]; 67 | const type = config["typeInput" + channel]; 68 | msg.payload = utils.ui.input.getPayload(data, type); 69 | } 70 | 71 | node.send(utils.outputs.preapreOutputFor(1, msg)); 72 | } 73 | 74 | function setRelay(index, value) { 75 | status.relay[index].state = value; 76 | setStatus(status, index); 77 | } 78 | 79 | function setEnergy(index, value) { 80 | status.relay[index].energy = value; 81 | setStatus(status, index); 82 | } 83 | 84 | function setPower(index, value) { 85 | status.relay[index].power = value; 86 | setStatus(status, index); 87 | } 88 | 89 | function setStatus(status, index) { 90 | context.set("status", status); 91 | publishState(index); 92 | } 93 | 94 | function publishState(index) { 95 | if (channel === 2) { 96 | node.send({ payload: status.relay }); 97 | } else { 98 | node.send({ payload: status.relay[index] }); 99 | } 100 | } 101 | 102 | node.on("input", function (msg) { 103 | function getOutputPayload(msg, channel, state) { 104 | return utils.payloads.devices.addDevice(msg, { 105 | topic: `${shelly.prefix}/relay/${channel}/command`, 106 | state: utils.payloads.convertToOnOff(state), 107 | target: "mqtt", 108 | payloadGenerator: preparePayload 109 | }); 110 | } 111 | 112 | function getNewState(channel) { 113 | if (config.state === "toggle") { 114 | return status.relay[channel].state == "on" ? "off" : "on"; 115 | } 116 | 117 | return config.state; 118 | } 119 | 120 | if (channel === 2) { 121 | msg = getOutputPayload(msg, 0, getNewState(0)); 122 | msg = getOutputPayload(msg, 1, getNewState(1)); 123 | } else { 124 | msg = getOutputPayload(msg, channel, getNewState(channel)); 125 | } 126 | 127 | node.send(utils.outputs.preapreOutputFor(this.wires.length - 1, msg)); 128 | }); 129 | 130 | function preparePayload(data) { 131 | if (data.state === undefined) { 132 | data.state = "off"; 133 | } 134 | 135 | return data.state.toLowerCase(); 136 | } 137 | 138 | node.on("close", function () { 139 | unsubscribeAll(); 140 | broker.deregister(node, ()=>{}); 141 | }); 142 | } 143 | 144 | RED.nodes.registerType("shelly-25", createShelly25); 145 | }; -------------------------------------------------------------------------------- /src/ota.html: -------------------------------------------------------------------------------- 1 | 2 | 76 | 77 | 113 | 114 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { MqttClient } from "mqtt"; 2 | import { Node, NodeCredentials, NodeDef, NodeMessage } from "node-red"; 3 | import { Z2mDeviceEntry } from "./device-types" 4 | 5 | export interface DeviceConfigOptions { 6 | name: string, 7 | bridge: string 8 | // bridge: { value: "", type: "zigbee2mqtt-bridge-config" }, 9 | deviceName: string 10 | brightnessSupport: boolean 11 | temperatureSupport: boolean 12 | colorSupport: boolean 13 | genericMqttDevice: boolean 14 | statusTopic: string 15 | commandTopic: string 16 | refreshTopic: string 17 | } 18 | 19 | export interface DeviceConfigNodeDef extends NodeDef, DeviceConfigOptions { } 20 | 21 | export interface DeviceConfigNode extends Node { 22 | bridge: string 23 | deviceName: string 24 | brightnessSupport: boolean 25 | temperatureSupport: boolean 26 | colorSupport: boolean 27 | genericMqttDevice: boolean 28 | statusTopic: string 29 | commandTopic: string 30 | refreshTopic: string 31 | } 32 | 33 | export interface BridgeConfigOptions { 34 | 35 | } 36 | 37 | export interface BridgeConfigCredentials { 38 | username: string; 39 | password: string; 40 | } 41 | 42 | export const BridgeConfigCredentials: NodeCredentials = { 43 | username: { type: "text" }, 44 | password: { type: "password" }, 45 | }; 46 | 47 | export interface BridgeConfigOptions { 48 | name: string 49 | mqtt: string 50 | broker: string 51 | baseTopic: string // default "zigbee2mqtt" 52 | enabledLogging: boolean 53 | allowDeviceStatusRefresh: boolean 54 | } 55 | 56 | export interface BridgeConfigNode extends Node { 57 | isConnected: MqttConfigNode["isConnected"] 58 | isReconnecting: MqttConfigNode["isReconnecting"] 59 | baseTopic: string 60 | publish: MqttConfigNode["publish"] 61 | knownDevices: Array 62 | getDeviceList: (callback: () => void) => Array 63 | subscribeDevice: MqttConfigNode["subscribeDevice"] 64 | publishDevice: (device: string, msg: string | any) => void 65 | subscribe: MqttConfigNode["subscribe"] 66 | unsubscribe: MqttConfigNode["unsubscribe"] 67 | setDeviceState: (device: string | undefined, payload: string) => void 68 | refreshDevice: (deviceName: string, force:boolean) => void 69 | registerOtaNode: (nodeId: string, otaStatusCallback: OtaStatusCallback, deviceStatusCallback: DeviceStatusCallback) => void 70 | } 71 | 72 | export interface BridgeConfigNodeDef extends NodeDef, BridgeConfigOptions { } 73 | 74 | export type OtaStatusCallback = (msg: any) => void 75 | export type DeviceStatusCallback = (deviceName: string, msg: any) => void 76 | 77 | export interface MqttConfigCredentials { 78 | username: string; 79 | password: string; 80 | } 81 | 82 | export const MqttConfigCredentials: NodeCredentials = { 83 | username: { type: "text" }, 84 | password: { type: "password" }, 85 | }; 86 | 87 | // TODO: Switch to core-mqtt from node-red 88 | export type MqttConfigOptions = { 89 | name: string 90 | protocol?: string // default: mqtt 91 | broker: string // default: localhost 92 | requireLogin: boolean 93 | } 94 | 95 | export interface NodeMqttBroker extends Node { 96 | register: (mqttNode: Node) => void; 97 | deregister: (mqttNode: Node, done: () => void) => void; 98 | subscribe: (topic: string, qos: number, callback: NodeMqttBrokerMessageCallback, ref : any) => void; 99 | publish: (topic: NodeMessage, done?: () => void) => void; 100 | connected: boolean 101 | } 102 | 103 | export type NodeMqttMessage = NodeMessage & { 104 | topic: string, 105 | retain: boolean 106 | qos:number 107 | } 108 | 109 | export type NodeMqttBrokerMessageCallback = (topic: string, payload: any, packet: any) => void; 110 | 111 | export interface MqttConfigNode extends Node { 112 | broker: string 113 | requireLogin: boolean 114 | isConnected: () => MqttClient['connected'] 115 | isReconnecting: () => MqttClient['reconnecting'] 116 | publish: (topic: string, message: string | Buffer) => void // MqttClient['publish'] (return type void instant of client) 117 | mqttClient: MqttClient 118 | subscribeDevice: (nodeId: string, topic: string, callback: MqttConfigCallback) => void 119 | subscribe: (nodeId: string, topic: string, callback: MqttConfigCallback, jsonPayload?: boolean) => void 120 | unsubscribe: (nodeId: string) => void 121 | } 122 | 123 | export interface MqttConfigNodeDef extends NodeDef, MqttConfigOptions { } 124 | 125 | export type MqttConfigCallback = (message: any, topic: string) => void 126 | 127 | export type MqttConfigSubsType = { 128 | nodeId: string, 129 | topic: string, 130 | callback: MqttConfigCallback 131 | isDevice: boolean 132 | } 133 | 134 | export type Z2mDeviceContextObsolete = { 135 | ieeeAddr: string, 136 | friendly_name: string, 137 | model: string, 138 | vendor: string 139 | type: string 140 | } 141 | 142 | export type Z2mDevice = { 143 | type: string, 144 | ieee_address: string, 145 | friendly_name: string, 146 | definition: Z2mDeviceDefinition, 147 | } 148 | 149 | export type Z2mDeviceDefinition = { 150 | model: string, 151 | vendor: string 152 | } -------------------------------------------------------------------------------- /src/zigbee2mqtt-config.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 46 | 47 | 59 | 60 | 61 | 107 | 108 | -------------------------------------------------------------------------------- /test-integration/docker/.gitignore: -------------------------------------------------------------------------------- 1 | mosquitto.log -------------------------------------------------------------------------------- /test-integration/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | mosquitto: 5 | image: eclipse-mosquitto:latest 6 | volumes: 7 | - ./mosquitto:/mosquitto 8 | - ./mosquitto/data:/mosquitto/data 9 | - ./mosquitto/log:/mosquitto/log 10 | ports: 11 | - "1883:1883" 12 | restart: unless-stopped -------------------------------------------------------------------------------- /test-integration/docker/mosquitto/config/mosquitto.conf: -------------------------------------------------------------------------------- 1 | persistence false 2 | 3 | log_dest file /mosquitto/log/mosquitto.log 4 | 5 | connection_messages true 6 | allow_anonymous true 7 | 8 | listener 1883 -------------------------------------------------------------------------------- /test/scenes_scene-selector_spec.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var helper = require("node-red-node-test-helper"); 3 | var sceneSelectorNode = require("../src/nodes/scenes.js"); 4 | 5 | helper.init(require.resolve("node-red")); 6 | 7 | describe("scene-selector Node", function () { 8 | 9 | beforeEach(function (done) { 10 | helper.startServer(done); 11 | }); 12 | 13 | afterEach(function (done) { 14 | helper.unload(); 15 | helper.stopServer(done); 16 | }); 17 | 18 | it("should be loaded", function (done) { 19 | var flow = [{ id: "n1", type: "scene-in", name: "scene-selector" }]; 20 | helper.load(sceneSelectorNode, flow, function () { 21 | var n1 = helper.getNode("n1"); 22 | try { 23 | n1.should.have.property("name", "scene-selector"); 24 | done(); 25 | } catch(err) { 26 | done(err); 27 | } 28 | }); 29 | }); 30 | 31 | }); -------------------------------------------------------------------------------- /test/zigbee2mqtt_generic-lamp_spec.js: -------------------------------------------------------------------------------- 1 | const should = require("should"); 2 | const helper = require("node-red-node-test-helper"); 3 | const requiredNodes = [ 4 | require("../dist/zigbee2mqtt.js"), 5 | require("../dist/zigbee2mqtt-config.js"), 6 | require("@node-red/nodes/core/common/20-inject.js"), 7 | require("@node-red/nodes/core/common/21-debug.js"), 8 | require("@node-red/nodes/core/network/10-mqtt.js"), 9 | ]; 10 | 11 | helper.init(require.resolve("node-red")); 12 | 13 | describe("generic-lamp Node", function () { 14 | 15 | beforeEach(function (done) { 16 | helper.startServer(done); 17 | }); 18 | 19 | afterEach(function (done) { 20 | helper.unload(); 21 | helper.stopServer(done); 22 | }); 23 | 24 | it("should be loaded", function (done) { 25 | var flow = [ 26 | { 27 | "id": "caa96857ce2cb799", 28 | "type": "generic-lamp", 29 | "z": "b46cd66b87ed23ae", 30 | "device": "7716bf2cc109979b", 31 | "name": "Ikea test lamp", 32 | "state": "ON", 33 | "brightness": 0, 34 | "temperature": 50, 35 | "red": 0, 36 | "green": 0, 37 | "blue": 0, 38 | "transition": 2, 39 | "delay": 0, 40 | "x": 480, 41 | "y": 160, 42 | "wires": [ 43 | [ 44 | "fd3fb7b09d9d0524" 45 | ] 46 | ] 47 | }, 48 | { 49 | "id": "fd3fb7b09d9d0524", 50 | "type": "debug", 51 | "z": "b46cd66b87ed23ae", 52 | "name": "", 53 | "active": true, 54 | "tosidebar": true, 55 | "console": false, 56 | "tostatus": false, 57 | "complete": "false", 58 | "statusVal": "", 59 | "statusType": "auto", 60 | "x": 690, 61 | "y": 160, 62 | "wires": [] 63 | }, 64 | { 65 | "id": "ec3f8c76406956ef", 66 | "type": "inject", 67 | "z": "b46cd66b87ed23ae", 68 | "name": "", 69 | "props": [ 70 | { 71 | "p": "payload" 72 | }, 73 | { 74 | "p": "topic", 75 | "vt": "str" 76 | } 77 | ], 78 | "repeat": "", 79 | "crontab": "", 80 | "once": false, 81 | "onceDelay": 0.1, 82 | "topic": "", 83 | "payload": "", 84 | "payloadType": "date", 85 | "x": 280, 86 | "y": 160, 87 | "wires": [ 88 | [ 89 | "caa96857ce2cb799" 90 | ] 91 | ] 92 | }, 93 | { 94 | "id": "7716bf2cc109979b", 95 | "type": "zigbee2mqtt-device-config", 96 | "name": "Ikea test lamp", 97 | "bridge": "f8ba4931243895a8", 98 | "deviceName": "Ikea Test Lamp", 99 | "brightnessSupport": false, 100 | "temperatureSupport": false, 101 | "colorSupport": false, 102 | "genericMqttDevice": false, 103 | "statusTopic": "", 104 | "commandTopic": "", 105 | "refreshTopic": "" 106 | }, 107 | { 108 | "id": "f8ba4931243895a8", 109 | "type": "zigbee2mqtt-bridge-config", 110 | "name": "z2m2", 111 | "broker": "eb1b1a7d89c241b8", 112 | "baseTopic": "zigbee2mqtt", 113 | "enabledLogging": false, 114 | "allowDeviceStatusRefresh": true 115 | }, 116 | { 117 | "id": "eb1b1a7d89c241b8", 118 | "type": "mqtt-broker", 119 | "name": "mosquitto", 120 | "broker": "192.168.178.49", 121 | "port": "1883", 122 | "clientid": "", 123 | "autoConnect": true, 124 | "usetls": false, 125 | "protocolVersion": "4", 126 | "keepalive": "60", 127 | "cleansession": true, 128 | "birthTopic": "", 129 | "birthQos": "0", 130 | "birthPayload": "", 131 | "birthMsg": {}, 132 | "closeTopic": "", 133 | "closeQos": "0", 134 | "closePayload": "", 135 | "closeMsg": {}, 136 | "willTopic": "", 137 | "willQos": "0", 138 | "willPayload": "", 139 | "willMsg": {}, 140 | "sessionExpiry": "" 141 | } 142 | ]; 143 | helper.load(requiredNodes, flow, function () { 144 | var n1 = helper.getNode("caa96857ce2cb799"); 145 | try { 146 | n1.should.have.property("name", "Ikea test lamp"); 147 | done(); 148 | } catch (err) { 149 | done(err); 150 | } 151 | }); 152 | }); 153 | 154 | 155 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // until we are done, allow plain JS 11 | "allowJs": true, /* Allow javascript files to be compiled. */ 12 | // "checkJs": true, /* Report errors in .js files. */ 13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 14 | "declaration": false, /* Generates corresponding '.d.ts' file. */ 15 | "declarationMap": false, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 16 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 17 | // "outFile": "./", /* Concatenate and emit output to single file. */ 18 | "outDir": "./dist", /* Redirect output structure to the directory. */ 19 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 20 | // "composite": true, /* Enable project compilation */ 21 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 22 | // "removeComments": true, /* Do not emit comments to output. */ 23 | // "noEmit": true, /* Do not emit outputs. */ 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 27 | 28 | /* Strict Type-Checking Options */ 29 | "strict": true, /* Enable all strict type-checking options. */ 30 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | // "strictNullChecks": true, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 37 | 38 | /* Additional Checks */ 39 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 43 | 44 | /* Module Resolution Options */ 45 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 52 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 55 | 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | 62 | /* Experimental Options */ 63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 65 | 66 | /* Advanced Options */ 67 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 69 | }, 70 | "include": [ 71 | "src/**/*" 72 | ], 73 | "exclude": [ 74 | "node_modules" 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /upcoming-changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog for the upcoming version 2 | > This document is an internal note for the changelog of the upcoming release. 3 | 4 | > If new features are added, increase the minor version || if bug fixes are added, increase the patch version. 5 | 6 | ### Release: `0.19.7` 7 | 8 | #### Features: 9 | 10 | #### Bug fixes: 11 | 12 | 13 | #### Behind the scenes 14 | --------------------------------------------------------------------------------