├── .editorconfig ├── .github ├── no-response.yml └── workflows │ └── nodejs.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── TODO.md ├── docs ├── ccu-config-debmatic.png ├── ccu-config-docker.png ├── ccu-config-pivccu.png ├── schema-debmatic.png ├── schema-docker.png ├── schema-multiCCU.drawio ├── schema-multiCCU.png └── schema-pivccu.png ├── nodes ├── ccu-alexa.html ├── ccu-alexa.js ├── ccu-connection.html ├── ccu-connection.js ├── ccu-display.html ├── ccu-display.js ├── ccu-get-value.html ├── ccu-get-value.js ├── ccu-mqtt.html ├── ccu-mqtt.js ├── ccu-poll.html ├── ccu-poll.js ├── ccu-program.html ├── ccu-program.js ├── ccu-rpc-event.html ├── ccu-rpc-event.js ├── ccu-rpc.html ├── ccu-rpc.js ├── ccu-script.html ├── ccu-script.js ├── ccu-set-value.html ├── ccu-set-value.js ├── ccu-signal.html ├── ccu-signal.js ├── ccu-switch.html ├── ccu-switch.js ├── ccu-sysvar.html ├── ccu-sysvar.js ├── ccu-value.html ├── ccu-value.js ├── icons │ └── ccu.png ├── lib │ └── status.js └── locales │ ├── de │ ├── ccu-connection.json │ ├── ccu-display.html │ ├── ccu-display.json │ ├── ccu-get-value.json │ ├── ccu-poll.json │ ├── ccu-program.json │ ├── ccu-rpc-event.json │ ├── ccu-rpc.json │ ├── ccu-script.json │ ├── ccu-set-value.json │ ├── ccu-signal.json │ ├── ccu-sysvar.json │ └── ccu-value.json │ └── en-US │ ├── ccu-connection.json │ ├── ccu-display.html │ ├── ccu-display.json │ ├── ccu-poll.json │ ├── ccu-program.json │ ├── ccu-rpc-event.json │ ├── ccu-rpc.json │ ├── ccu-script.json │ ├── ccu-set-value.json │ ├── ccu-signal.json │ ├── ccu-switch.json │ ├── ccu-sysvar.json │ └── ccu-value.json ├── package-lock.json ├── package.json ├── paramsets.json ├── test ├── context_spec.js ├── regahss_spec.js ├── rpc_spec.js ├── simulator-behaviors │ └── .gitignore ├── simulator-data │ ├── channels.json │ ├── devices.json │ ├── functions.json │ ├── programs.json │ ├── rooms.json │ └── variables.json └── utils.js └── tools ├── get-channels-working.js └── paramsets-join.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | charset = utf-8 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 4 8 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 14 5 | # Label requiring a response 6 | responseRequiredLabel: "⏳awaiting-feedback" 7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 8 | closeComment: > 9 | This issue has been automatically closed because there has been no response 10 | to our request for more information from the original author. 11 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [10.x, 12.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - run: npm install 21 | - run: npm test 22 | env: 23 | CI: true 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | .nyc_output 4 | ccu_*.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | .editorconfig 4 | test 5 | tools 6 | .nyc_output 7 | .travis.yml 8 | ccu_localhost.json 9 | ccu_paramsets_v2.json 10 | .github 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '14' 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018, 2019 Sebastian Raff and node-red-contrib-ccu contributors 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 | # node-red-contrib-ccu 2 | 3 | [![NPM version](https://badge.fury.io/js/node-red-contrib-ccu.svg)](http://badge.fury.io/js/node-red-contrib-ccu) 4 | [![Dependencies Status](https://david-dm.org/rdmtc/node-red-contrib-ccu/status.svg)](https://david-dm.org/rdmtc/node-red-contrib-ccu) 5 | [![Build Status](https://travis-ci.org/rdmtc/node-red-contrib-ccu.svg?branch=master)](https://travis-ci.org/rdmtc/node-red-contrib-ccu) 6 | [![Coverage Status](https://coveralls.io/repos/github/rdmtc/node-red-contrib-ccu/badge.svg?branch=master)](https://coveralls.io/github/rdmtc/node-red-contrib-ccu?branch=master) 7 | [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) 8 | [![License][mit-badge]][mit-url] 9 | 10 | > Node-RED Nodes for the Homematic CCU 11 | 12 | With these Nodes you can connect [Homematic](https://github.com/hobbyquaker/awesome-homematic) and 13 | [Node-RED](https://nodered.org/). Homematic is a series of smart home automation hardware from the manufacturer 14 | [eQ-3](http://www.eq-3.de/), popular especially in Germany. 15 | 16 | **⚠️ node-red-contrib-ccu >= 3.0 needs Node-RED >= 1.0.** If you're still on Node-RED 0.20 or lower you should use the 17 | latest 2.x version of node-red-contrib-ccu. 18 | 19 | For the communication with the CCU both RPC and ReGaHSS remote script are used. It's possible to connect to multiple 20 | CCUs from one Node-RED instance. RPC setValue calls can be comfortably complemented with ON_TIME and RAMP_TIME values 21 | and special nodes ease the control of displays and mp3 actuators. RPC events can be filtered comprehensively (even 22 | through regular expressions and also by rooms and functions). It's possible to start rega-programs and set 23 | rega-variables and last but not least there are nodes to execute arbitrary rega-scripts and RPC calls. 24 | 25 | These nodes are included in [RedMatic](https://github.com/rdmtc/RedMatic) which ships Node-RED as an addon package 26 | for installation on a Homematic CCU3 or RaspberryMatic. 27 | 28 | Some example flows can be found in the [RedMatic Wiki](https://github.com/rdmtc/RedMatic/wiki) (German language). 29 | 30 | __A modern Browser is required, Internet Explorer won't work.__ 31 | 32 | __Starting with Version 3.x these Nodes need Node-RED >= 1.0 to work correctly__ 33 | 34 | ## Configuration Examples 35 | 36 | The communication with the Homematic CCU needs independent connections in two directions. Node-red-contrib-ccu connects to the CCU's interface listeners (e.g. 2001/TCP for BidCos-RF) while the CCU connects to node-red-contrib-ccu's BINRPC/XMLRPC listeners (2048/tcp and 2049/tcp in examples below). 37 | 38 | ### NAT'd network 39 | 40 | If Node-RED/node-red-contrib-ccu runs inside a Container or a VM with NAT'd network it's necessary to forward/expose the ports for connections _from_ the CCU _to_ node-red-contrib-ccu's callback listeners (example below for a Docker container: use options `-p 2048:2048 -p 2049:2049`in the docker run command). 41 | 42 | ![schema-docker](docs/schema-docker.png) 43 | 44 | ![ccu-config-docker](docs/ccu-config-docker.png) 45 | 46 | The config option `Init address`will be used to tell the CCU on which Address node-red-contrib-ccu is reachable. As 172.17.0.20 is not reachable for the CCU the Hosts IP Address and port forwarding/exposal has to be used. As `Listen address` setting also `0.0.0.0` (which tells node-red-contrib-ccu to bind it's listeners to all available interfaces) would be possible. 47 | 48 | ### piVCCU 49 | 50 | This example shows a configuration for piVCCU and Node-RED running in containers with bridged networking. 51 | 52 | ![schema-pivccu](docs/schema-pivccu.png) 53 | 54 | ![ccu-config-pivccu](docs/ccu-config-pivccu.png) 55 | 56 | ### debmatic 57 | 58 | In this example both Node-RED and debmatic are installed on the same (possibly virtual) host. 59 | 60 | ![schema-debmatic](docs/schema-debmatic.png) 61 | 62 | ![ccu-config-debmatic](docs/ccu-config-debmatic.png) 63 | 64 | ### Multiple CCUs 65 | 66 | With the same logic as shown above, multiple CCUs can be managed within one Node-RED instance. 67 | This will require two individual configuration nodes, in which the respective connection setting are provided. 68 | 69 | ![schema-multiCCU](docs/schema-multiCCU.png) 70 | 71 | - `Listen address` typically is the same for both Configurations as it is determined by the host that is running Node-RED 72 | - `BINRPC listening port` and `XMLRPC listening port` need to be different across the two configurations. One configuration can use the defaults (2048/tcp and 2049/tcp), the other needs to use two new ports. node-red-contrib-ccu will make a proposal, but this can be modified, e.g. if the proposed ports are already used. 73 | - The examples for [NAT'd network](#NAT'd-network), [piVCCU](#piVCCU) and [debmatic](#debmatic) will apply likewise for multiple CCUs. This means, for running Node-RED within a docker, all BINRPC and XMLRPC ports must be forwarded, e.g. `-p 2048:2048 -p 2049:2049 -p 2061:2061 -p 2062:2062` 74 | 75 | 76 | ## License 77 | 78 | MIT (c) Sebastian Raff and node-red-contrib-ccu contributors 79 | 80 | [mit-badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat 81 | [mit-url]: LICENSE 82 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # node-red-contrib-ccu todo 2 | 3 | * ~~supply config object to editors~~ 4 | * ~~implement rega polling and poll node~~ 5 | * ~~implement program node~~ 6 | * ~~implement program poll and node output~~ 7 | * ~~store sysvar types and enums~~ 8 | * ~~implement sysvar node~~ 9 | * ~~store paramset descriptions~~ 10 | * ~~extend msg with type and enums~~ 11 | * ~~implement value node~~ 12 | * ~~implement rpc node~~ 13 | * ~~rpc-event filters~~ 14 | * ~~discover ccu and interfaces~~ 15 | * ~~connect autocomplete~~ 16 | * ~~configurable init address~~ 17 | * ~~fix missing rooms/functions in msg~~ 18 | * ~~fix rpc event room/function filter~~ 19 | * ~~rpc ping~~ 20 | * ~~find free listening ports~~ 21 | * ~~topic placeholders~~ 22 | * ~~catch errors in unconfigured nodes~~ 23 | * ~~msg properties program and sysvar~~ 24 | * ~~submit node~~ 25 | * ~~submit type display~~ 26 | * ~~cast setValue~~ 27 | * ~~cast putParamset~~ 28 | * handle SPECIAL paramset key (Clarifiy: Which device uses that?! How to test?) 29 | * documentation, i18n 30 | * ~~node status~~ 31 | * global object 32 | * value autocomplete / multiselect 33 | * ~~rpc-event autocomplete / multiselect~~ 34 | * ~~rpc autocomplete / multiselect~~ 35 | * submit autocomplete 36 | * submit limit list to 10 cmds 37 | * fix submit display led 38 | * submit display beep (?) 39 | * submit payload via msg 40 | * ~~processing working/direction datapoints~~ 41 | 42 | -------------------------------------------------------------------------------- /docs/ccu-config-debmatic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdmtc/node-red-contrib-ccu/e0d3cc522be0598884285d38d50c58647bcd48a2/docs/ccu-config-debmatic.png -------------------------------------------------------------------------------- /docs/ccu-config-docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdmtc/node-red-contrib-ccu/e0d3cc522be0598884285d38d50c58647bcd48a2/docs/ccu-config-docker.png -------------------------------------------------------------------------------- /docs/ccu-config-pivccu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdmtc/node-red-contrib-ccu/e0d3cc522be0598884285d38d50c58647bcd48a2/docs/ccu-config-pivccu.png -------------------------------------------------------------------------------- /docs/schema-debmatic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdmtc/node-red-contrib-ccu/e0d3cc522be0598884285d38d50c58647bcd48a2/docs/schema-debmatic.png -------------------------------------------------------------------------------- /docs/schema-docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdmtc/node-red-contrib-ccu/e0d3cc522be0598884285d38d50c58647bcd48a2/docs/schema-docker.png -------------------------------------------------------------------------------- /docs/schema-multiCCU.drawio: -------------------------------------------------------------------------------- 1 | 7VxLc6M4EP41PoZCEuJxzNiZmcNsampS+5i9EaPYqsXGi0ls769fCSSMQDY4QdiZmByCGkmg7q9b3VLLIzRebL+k4Wr+WxKReATtaDtCkxGEwMUB+8cpO0EBGBaUWUojQdsTHuh/RBBtQX2mEVkrFbMkiTO6UonTZLkk00yhhWmabNRqT0msvnUVzkiD8DAN4yb1Txpl84LqQ29P/0robC7fDFwx4kUoK4uRrOdhlGwqJHQ3QuM0SbLibrEdk5hzT/KlaPf5wNPyw1KyzLo0mP1N7R/zb6E7nkTf7v+485fj+xvHL7p5CeNnMWLxtdlOsiBNnpcR4b3YI/RpM6cZeViFU/50w6TOaPNsEbMSYLflKHmh+Yniq19ImpFthSQ++QtJFiRLd6yKeOoI7gn8uKK42cvCswVtXpEDllwPhfxnZc97FrEbwaUTOIacdo4xJqz47VNMtrcchIwXZBmJ28k0DtdrOlUZx1iU7v4STM4LP3nBwrI42VYfTnaytKVZ3swKAl+Ui5bAAaK8b8oL1ZbfSUoZX0gqaAcltk6e0yk5whcgkJSF6YxkxyqioiKJFM1rIqAiY6wTsaClJA4z+qLqq07s4g3fE8rGVgIM1RAGQcB4rnRSjF20q2pYrSsHtHZVcKfRVY7EcuhvAGcHdZ4xfV5118zStIaPsgdbLy/RCiOVC0hqTEWeINAIFCLXlM4GPVu5MKazJddkxj6uOxq9OS6fdvt3Pm5JhVC45cbsvZ8i+sLfKEbPqP8+8+nrk+BDWWZ3M/E/b/eU5HqyZ7esyB/crPNZ/5ZVAM5qe7iXR0kYj38HokEALeD6FrS4OSiqsUE/1psyWvENDXI+JEmtYYLJKKtNb1ma/EPGSZxwi7lMloQPgsZxjdQACJc4ZW7FrXiwoFHEX6NFmorF/rElnTFkAeXqqKeuBZXLFA7BcRweAEc/mEufonbs6GCm+bSe0Q9tm3HmczZdnYT4Az4K89jDXbvFMwhC17JthGwHOZ4PXQ0IdQ4eCqzAdjAEHgoAa+yYAiE8IwjnCxbmkPQlN60XikXu8/wyWPRzLOY4tH03wF5HMGIrB6Fs6WFTaHQNBR8yiFACCHw8fJABC7C4QVKCFs9tDVt4qb/oQ3p47dGHf1HRRz1kcAL7tdFHPZBxgloIbDj2cDo42aZjD09lgddcLShpqjBNrRaAvtdXOtq+UhgnBRoDMwd2sGYnMafqiI8gunP5nxGmyadYwZtmdaqsoswXrjG84ePuyvljt6/JOqvHbu5Hj91OR15tkQU2kaeP3Yzpsm7R4C263LKQbIaNQUc2AscYH3XLy0PFG490ma6mlxtr8N2KgWONiKbM/6AJ1/sNWWcmwQhq7gtAGjjq4g9zaGyZToyicbuIr2i8IDT650Yj0EUYRoR7n0Tk5sfd5C2i/cUdkHbXd1gHBOkcEFO7DzfjIi5lmshvn+iMN7+ipaufVdqN88FFt7nw7vxV3QrHwA4rOrxAPpDewavedceLZkV7WL1D1/jmoEfpDr+vd06PEgF47vjmsEd5jW9c+LHQ2HXr2Rgasc6FrTF06Oytrg5GOdP0P2P07Ki9NnerlE4PuVswsILq5RniHTjnauI1e0GLv/7hJhevPbl13J6vYApv5/TurilbAwENA0uXK6hFmm/5gSGw6XaPL2qrU5em6nz0rc7T4Qag5SlX1zxVbGHlMpUjKI80GTsSosmw4ierXpFhtT8tYtvOqJrsBT15emSI0yLvNl+rnmSFa27va7O1sMNEUgOo4YQt3MGzHiqZ8CCSLkTu9bAIutyDx9iVGcj9oICFW4OjwNR5to+AAqCiAPYFg/ryq2kM6Lb5+5zAejvT6DsDTlKdjzQ6lwXTRiawzaxKYO+vV8K0cbIvqC1jDAtaQ7nwl26ENJ5DP+JsdGT6sOq7sTrMrl+g1bm0c9RAQRPw+zpF7fZ3ipoV9z+4UFTf/24Fuvsf -------------------------------------------------------------------------------- /docs/schema-multiCCU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdmtc/node-red-contrib-ccu/e0d3cc522be0598884285d38d50c58647bcd48a2/docs/schema-multiCCU.png -------------------------------------------------------------------------------- /docs/schema-pivccu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdmtc/node-red-contrib-ccu/e0d3cc522be0598884285d38d50c58647bcd48a2/docs/schema-pivccu.png -------------------------------------------------------------------------------- /nodes/ccu-alexa.html: -------------------------------------------------------------------------------- 1 | 135 | 136 | 174 | 175 | 179 | 180 | 184 | -------------------------------------------------------------------------------- /nodes/ccu-alexa.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const statusHelper = require(path.join(__dirname, '/lib/status.js')); 4 | 5 | module.exports = function (RED) { 6 | class CcuAlexa { 7 | constructor(config) { 8 | RED.nodes.createNode(this, config); 9 | 10 | this.ccu = RED.nodes.getNode(config.ccuConfig); 11 | 12 | if (!this.ccu) { 13 | return; 14 | } 15 | 16 | this.iface = config.iface; 17 | this.channel = config.channel.split(' ')[0]; 18 | 19 | this.values = {}; 20 | 21 | this.ccu.register(this); 22 | 23 | if (!config.iface || !config.channel) { 24 | this.error('channel or iface missing'); 25 | return; 26 | } 27 | 28 | const device = this.ccu.metadata.devices[this.iface][this.channel]; 29 | const channelType = device.TYPE; 30 | //const paramsetDescription = this.ccu.getParamsetDescription(this.iface, device, 'VALUES'); 31 | 32 | this.debug('channel ' + this.channel + ' ' + channelType); 33 | 34 | const filter = { 35 | cache: true, 36 | change: true, 37 | stable: true, 38 | iface: config.iface, 39 | channel: String(config.channel).split(' ')[0] 40 | }; 41 | 42 | const payload = { 43 | acknowledge: true, 44 | state: {} 45 | }; 46 | 47 | this.idSubscription = this.ccu.subscribe(filter, message => { 48 | let change = false; 49 | switch (channelType) { 50 | case 'SHUTTER_CONTACT': 51 | case 'ROTARY_HANDLE_SENSOR': 52 | if (message.datapoint === 'STATE') { 53 | payload.state.contact = message.payload ? 'DETECTED' : 'NOT_DETECTED'; 54 | change = true; 55 | } 56 | 57 | break; 58 | 59 | case 'CLIMATECONTROL_RT_TRANSCEIVER': 60 | case 'THERMALCONTROL_TRANSMIT': 61 | if (message.datapoint === 'SET_TEMPERATURE') { 62 | payload.state.thermostatSetPoint = message.payload; 63 | change = true; 64 | } 65 | 66 | if (message.datapoint === 'ACTUAL_TEMPERATURE') { 67 | payload.state.temperature = message.payload; 68 | change = true; 69 | } 70 | // TODO if (msg.datapoint === 'CONTROL_MODE') { 71 | 72 | break; 73 | 74 | case 'HEATING_CLIMATECONTROL_TRANSCEIVER': 75 | if (message.datapoint === 'SET_POINT_TEMPERATURE') { 76 | payload.state.thermostatSetPoint = message.payload; 77 | change = true; 78 | } 79 | 80 | if (message.datapoint === 'ACTUAL_TEMPERATURE') { 81 | payload.state.temperature = message.payload; 82 | change = true; 83 | } 84 | 85 | // TODO if (msg.datapoint === 'SET_POINT_MODE') { 86 | break; 87 | 88 | case 'SWITCH_VIRTUAL_RECEIVER': 89 | case 'SWITCH': 90 | if (message.datapoint === 'STATE') { 91 | payload.state.power = message.payload ? 'ON' : 'OFF'; 92 | change = true; 93 | } 94 | 95 | break; 96 | 97 | case 'DIMMER_VIRTUAL_RECEIVER': 98 | case 'DIMMER': 99 | if (message.datapoint === 'LEVEL') { 100 | payload.state.power = message.payload ? 'ON' : 'OFF'; 101 | payload.state.brightness = message.payload * 100; 102 | this.values.brightness = payload.state.brightness; 103 | change = true; 104 | } 105 | 106 | break; 107 | 108 | case 'BLIND': 109 | case 'BLIND_VIRTUAL_RECEIVER': 110 | if (message.datapoint === 'LEVEL') { 111 | payload.state.rangeValue = message.payload * 100; 112 | change = true; 113 | } 114 | 115 | break; 116 | 117 | case 'MOTION_DETECTOR': 118 | case 'MOTIONDETECTOR_TRANSCEIVER': 119 | if (message.datapoint === 'MOTION') { 120 | payload.state.power = message.payload ? 'DETECTED' : 'NOT_DETECTED'; 121 | change = true; 122 | } 123 | 124 | break; 125 | 126 | case 'WEATHER': 127 | case 'WEATHER_TRANSMIT': 128 | if (message.datapoint === 'TEMPERATURE') { 129 | payload.state.temperature = message.payload; 130 | change = true; 131 | } 132 | 133 | break; 134 | 135 | case 'KEYMATIC': 136 | if (message.datapoint === 'STATE') { 137 | payload.state.lock = message.payload ? 'UNLOCKED' : 'LOCKED'; 138 | change = true; 139 | } 140 | 141 | break; 142 | 143 | default: 144 | this.warn('unsupported channel type ' + channelType); 145 | return; 146 | } 147 | 148 | const keys = Object.keys(payload.state); 149 | 150 | if (keys.length > 0) { 151 | if (change) { 152 | this.debug(JSON.stringify(payload)); 153 | this.status({fill: 'green', shape: 'ring', text: JSON.stringify(payload.state).replace(/^{/, '').replace(/}$/, '')}); 154 | keys.forEach(key => { 155 | const distinctPayload = { 156 | acknowledge: true, 157 | state: {} 158 | }; 159 | distinctPayload.state[key] = payload.state[key]; 160 | this.send({payload: distinctPayload}); 161 | }); 162 | } 163 | } else { 164 | this.debug('empty state object'); 165 | } 166 | }); 167 | 168 | this.on('input', message => { 169 | this.debug('alexa > ' + JSON.stringify(message)); 170 | 171 | if (!this.iface) { 172 | this.error('interface undefined'); 173 | return; 174 | } 175 | 176 | if (!this.channel) { 177 | this.error('channel undefined'); 178 | return; 179 | } 180 | 181 | switch (message.command) { 182 | case 'TurnOn': 183 | case 'TurnOff': 184 | if (channelType.startsWith('DIMMER')) { 185 | this.ccu.setValueQueued(this.iface, this.channel, 'LEVEL', message.payload === 'ON' ? 1 : 0); 186 | } else { 187 | this.ccu.setValueQueued(this.iface, this.channel, 'STATE', message.payload === 'ON'); 188 | } 189 | 190 | break; 191 | 192 | case 'SetBrightness': 193 | if (channelType.startsWith('DIMMER')) { 194 | this.ccu.setValueQueued(this.iface, this.channel, 'LEVEL', message.payload / 100); 195 | } else { 196 | this.ccu.setValueQueued(this.iface, this.channel, 'STATE', message.payload > 0); 197 | } 198 | 199 | break; 200 | 201 | case 'AdjustBrightness': 202 | if (channelType.startsWith('DIMMER')) { 203 | this.ccu.setValueQueued(this.iface, this.channel, 'LEVEL', (this.values.brightness + message.payload) / 100); 204 | } else { 205 | this.ccu.setValueQueued(this.iface, this.channel, 'STATE', message.payload > 0); 206 | } 207 | 208 | break; 209 | 210 | case 'SetTargetTemperature': 211 | switch (channelType) { 212 | case 'CLIMATECONTROL_RT_TRANSCEIVER': 213 | case 'THERMALCONTROL_TRANSMIT': 214 | this.ccu.setValueQueued(this.iface, this.channel, 'SET_TEMPERATURE', message.payload); 215 | break; 216 | 217 | case 'HEATING_CLIMATECONTROL_TRANSCEIVER': 218 | this.ccu.setValueQueued(this.iface, this.channel, 'SET_POINT_TEMPERATURE', message.payload); 219 | break; 220 | 221 | default: 222 | } 223 | 224 | break; 225 | 226 | case 'SetRangeValue': 227 | switch (channelType) { 228 | case 'BLIND': 229 | case 'BLIND_VIRTUAL_RECEIVER': 230 | this.ccu.setValueQueued(this.iface, this.channel, 'LEVEL', message.payload / 100); 231 | break; 232 | 233 | default: 234 | } 235 | 236 | break; 237 | 238 | // Todo case 'SetColor': 239 | // Todo case 'SetColorTemperature': 240 | // Todo case 'AdjustTargetTemperature': 241 | // todo case 'SetThermostatMode': 242 | 243 | default: 244 | this.warn('unknown command ' + message.command); 245 | } 246 | }); 247 | 248 | this.on('close', this._destructor); 249 | } 250 | 251 | _destructor(done) { 252 | if (this.idSubscription) { 253 | this.debug('ccu-value close'); 254 | this.ccu.unsubscribe(this.idSubscription); 255 | } 256 | 257 | done(); 258 | } 259 | 260 | setStatus(data) { 261 | statusHelper(this, data); 262 | } 263 | } 264 | 265 | RED.nodes.registerType('ccu-alexa', CcuAlexa); 266 | }; 267 | -------------------------------------------------------------------------------- /nodes/ccu-connection.html: -------------------------------------------------------------------------------- 1 | 127 | 128 | 219 | 220 | 221 | 222 | 223 | 287 | -------------------------------------------------------------------------------- /nodes/ccu-display.html: -------------------------------------------------------------------------------- 1 | 221 | 222 | 365 | -------------------------------------------------------------------------------- /nodes/ccu-display.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const statusHelper = require(path.join(__dirname, '/lib/status.js')); 4 | 5 | module.exports = function (RED) { 6 | class CcuDisplay { 7 | constructor(config) { 8 | RED.nodes.createNode(this, config); 9 | 10 | this.ccu = RED.nodes.getNode(config.ccuConfig); 11 | 12 | if (!this.ccu) { 13 | return; 14 | } 15 | 16 | this.iface = config.iface; 17 | 18 | this.ccu.register(this); 19 | 20 | function convertColor(col) { 21 | col = String(col).toLowerCase(); 22 | const map = { 23 | weiss: '0x80', 24 | weiß: '0x80', 25 | white: '0x80', 26 | rot: '0x81', 27 | red: '0x81', 28 | orange: '0x82', 29 | gelb: '0x83', 30 | yellow: '0x83', 31 | gruen: '0x84', 32 | grün: '0x84', 33 | green: '0x84', 34 | blue: '0x85' 35 | }; 36 | 37 | if (map[col]) { 38 | col = map[col]; 39 | } 40 | 41 | if (!['0x80', '0x81', '0x82', '0x83', '0x84', '0x85'].includes(col)) { 42 | col = '0x80'; 43 | } 44 | 45 | return ',0x11,' + col; 46 | } 47 | 48 | function convertIcon(ico) { 49 | ico = String(ico).toLowerCase(); 50 | const map = { 51 | aus: '0x80', 52 | off: '0x80', 53 | ein: '0x81', 54 | an: '0x81', 55 | on: '0x81', 56 | offen: '0x82', 57 | geoeffnet: '0x82', 58 | geöffnet: '0x82', 59 | open: '0x82', 60 | geschlossen: '0x83', 61 | zu: '0x83', 62 | closed: '0x83', 63 | fehler: '0x84', 64 | error: '0x84', 65 | ok: '0x85', 66 | information: '0x86', 67 | 'neue nachricht': '0x87', 68 | nachricht: '0x87', 69 | message: '0x87', 70 | servicemeldung: '0x88', 71 | servicemessage: '0x88', 72 | 'service message': '0x88', 73 | grün: '0x89', 74 | signalgrün: '0x89', 75 | 'signal grün': '0x89', 76 | green: '0x89', 77 | signalgreen: '0x89', 78 | 'signal green': '0x89', 79 | gelb: '0x8A', 80 | signalgelb: '0x8A', 81 | 'signal gelb': '0x8A', 82 | yellow: '0x8A', 83 | signalyellow: '0x8A', 84 | 'signal yellow': '0x8A', 85 | rot: '0x8B', 86 | signalrot: '0x8B', 87 | 'signal rot': '0x8B', 88 | red: '0x8B', 89 | signalred: '0x8B', 90 | 'signal red': '0x8B' 91 | }; 92 | 93 | if (map[ico]) { 94 | ico = map[ico]; 95 | } 96 | 97 | if (!['0x80', '0x81', '0x82', '0x83', '0x84', '0x85', '0x86', '0x87', '0x88', '0x89', '0x8a', '0x8b'].includes(ico)) { 98 | ico = ''; 99 | } 100 | 101 | return ico ? (',0x13,' + ico) : ''; 102 | } 103 | 104 | function convertString(string) { 105 | if (typeof string !== 'string') { 106 | string = String(string); 107 | } 108 | 109 | if (!string) { 110 | string = ' '; 111 | } 112 | 113 | const charcodes = { 114 | Ä: '0x5B', 115 | Ö: '0x23', 116 | Ü: '0x24', 117 | ä: '0x7B', 118 | ö: '0x7C', 119 | ü: '0x7D', 120 | ß: '0x5F' 121 | }; 122 | const res = []; 123 | string.split('').forEach(c => { 124 | res.push(charcodes[c] || ('0x' + c.charCodeAt(0).toString(16).toUpperCase())); 125 | }); 126 | 127 | return ',0x12,' + res.slice(0, 12).join(','); 128 | } 129 | 130 | this.on('input', (message, send, done) => { 131 | let payload = '0x02'; 132 | 133 | let countLines = 6; // HM-Dis-WM55 134 | if (config.channelType === 'HM-Dis-EP-WM55') { 135 | payload += ',0x0A'; 136 | countLines = 3; 137 | } 138 | 139 | for (let i = 1; i <= countLines; i++) { 140 | if (!message['disable' + i] && !config['disable' + i]) { 141 | payload += convertString(message['line' + i] || config['line' + i]); 142 | if (config.channelType === 'HM-Dis-WM55') { 143 | payload += convertColor(message['color' + i] || config['color' + i]); 144 | } 145 | 146 | payload += convertIcon(message['icon' + i] || config['icon' + i]); 147 | } 148 | 149 | payload += ',0x0A'; 150 | } 151 | 152 | if (config.channelType === 'HM-Dis-EP-WM55') { 153 | if (config.sound) { 154 | payload += ',0x14,' + config.sound + ',0x1C'; 155 | } 156 | 157 | payload += ',' + config.repeat + ',0x1D,' + config.pause + ',0x16'; 158 | 159 | if (config.signal) { 160 | payload += ',' + config.signal; 161 | } 162 | } 163 | 164 | payload += ',0x03'; 165 | 166 | this.ccu.setValue(config.iface, config.channel, 'SUBMIT', payload) 167 | .then(() => { 168 | done(); 169 | }).catch(error => { 170 | done(error); 171 | }); 172 | }); 173 | } 174 | 175 | setStatus(data) { 176 | statusHelper(this, data); 177 | } 178 | } 179 | 180 | RED.nodes.registerType('ccu-display', CcuDisplay); 181 | }; 182 | -------------------------------------------------------------------------------- /nodes/ccu-get-value.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | class CcuGetValue { 3 | constructor(config) { 4 | RED.nodes.createNode(this, config); 5 | 6 | this.ccu = RED.nodes.getNode(config.ccuConfig); 7 | this.setProp = config.setProp || 'payload'; 8 | this.setPropType = config.setPropType || 'msg'; 9 | 10 | if (!this.ccu) { 11 | return; 12 | } 13 | 14 | this.on('input', (message, send, done) => { 15 | let value; 16 | const iface = config.iface || message.iface || message.interface; 17 | const channel = String(config.channel || message.channel).split(' ')[0]; 18 | const datapoint = config.datapoint || message.datapoint; 19 | const sysvar = config.sysvar || message.sysvar; 20 | 21 | if (iface === 'ReGaHSS') { 22 | value = this.ccu.sysvar[sysvar]; 23 | if (!value) { 24 | const err = new Error('unknown variable ' + sysvar); 25 | this.status({fill: 'red', shape: 'ring', text: 'error: unknown variable'}); 26 | done(err); 27 | 28 | return; 29 | } 30 | } else { 31 | const address = iface + '.' + channel + '.' + datapoint; 32 | value = this.ccu.values[address]; 33 | if (!value) { 34 | const err = new Error('unknown datapoint ' + address); 35 | this.status({fill: 'red', shape: 'ring', text: 'error: unknown datapoint'}); 36 | done(err); 37 | 38 | return; 39 | } 40 | } 41 | 42 | if (config.setPropType === 'cmsg') { 43 | Object.assign(message, value); 44 | send(message); 45 | this.status({fill: 'green', shape: 'ring', text: String(value.payload)}); 46 | done(); 47 | } else { 48 | if (iface === 'ReGaHSS') { 49 | if (config.sysvarProperty !== 'all') { 50 | value = value[config.sysvarProperty]; 51 | } 52 | } else if (config.datapointProperty !== 'all') { 53 | value = value[config.datapointProperty]; 54 | } 55 | 56 | this.status({fill: 'green', shape: 'ring', text: String(value)}); 57 | 58 | if (config.setPropType === 'msg') { 59 | RED.util.setMessageProperty(message, config.setProp, value); 60 | if (send) { 61 | send(message); 62 | } else { 63 | this.send(message); 64 | } 65 | 66 | if (done) { 67 | done(); 68 | } 69 | } else if ((this.setPropType === 'flow') || (this.setPropType === 'global')) { 70 | const context = RED.util.parseContextStore(this.setProp); 71 | const target = this.context()[this.setPropType]; 72 | target.set(context.key, value, context.store, err => { 73 | if (err) { 74 | done(err); 75 | } else { 76 | send(message); 77 | 78 | done(); 79 | } 80 | }); 81 | } 82 | } 83 | }); 84 | } 85 | } 86 | 87 | RED.nodes.registerType('ccu-get-value', CcuGetValue); 88 | }; 89 | -------------------------------------------------------------------------------- /nodes/ccu-mqtt.html: -------------------------------------------------------------------------------- 1 | 44 | 45 | 142 | 143 | 146 | 147 | 150 | -------------------------------------------------------------------------------- /nodes/ccu-mqtt.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const mw = require('mqtt-wildcard'); 3 | 4 | const statusHelper = require(path.join(__dirname, '/lib/status.js')); 5 | 6 | module.exports = function (RED) { 7 | class CcuMqttNode { 8 | constructor(config) { 9 | RED.nodes.createNode(this, config); 10 | 11 | this.ccu = RED.nodes.getNode(config.ccuConfig); 12 | 13 | if (!this.ccu) { 14 | return; 15 | } 16 | 17 | this.topicOutputEvent = config.topicOutputEvent; 18 | this.topicInputSetValue = config.topicInputSetValue; 19 | 20 | this.topicOutputSysvar = config.topicOutputSysvar; 21 | this.topicInputSysvar = config.topicInputSysvar; 22 | 23 | this.topicInputPutParam = config.topicInputPutParam; 24 | this.topicInputPutParamset = config.topicInputPutParamset; 25 | 26 | this.topicInputRpc = config.topicInputRpc; 27 | 28 | this.topicCounters = config.topicCounters; 29 | this.rxCounters = {}; 30 | this.txCounters = {}; 31 | 32 | this.payloadOutput = config.payloadOutput; 33 | 34 | this.ccu.register(this); 35 | 36 | this.on('input', message => { 37 | this.input(message); 38 | }); 39 | 40 | this.idEventSubscription = this.ccu.subscribe({cache: config.cache}, message => { 41 | this.event(message); 42 | }); 43 | 44 | this.idSysvarSubscription = this.ccu.subscribeSysvar({cache: config.cache, change: true}, message => { 45 | this.sysvarOutput(message); 46 | }); 47 | 48 | this.idProgramSubscription = this.ccu.subscribeProgram({}, message => { 49 | this.programOutput(message); 50 | }); 51 | 52 | this.on('close', this._destructor); 53 | 54 | if (this.topicCounters) { 55 | setTimeout(() => { 56 | this.ccu.enabledIfaces.forEach(iface => { 57 | this.send({topic: this.ccu.topicReplace(this.topicCounters, {iface, rxtx: 'rx'}), payload: '0', retain: true}); 58 | this.send({topic: this.ccu.topicReplace(this.topicCounters, {iface, rxtx: 'tx'}), payload: '0', retain: true}); 59 | }); 60 | }, 25000); 61 | setInterval(() => { 62 | this.checkCounters('rxCounters'); 63 | this.checkCounters('txCounters'); 64 | }, 30000); 65 | } 66 | } 67 | 68 | _destructor(done) { 69 | this.trace('ccu-mqtt close'); 70 | this.ccu.unsubscribe(this.idEventSubscription); 71 | this.ccu.unsubscribeSysvar(this.idSysvarSubscription); 72 | this.ccu.unsubscribeProgram(this.idProgramSubscription); 73 | this.ccu.deregister(this); 74 | done(); 75 | } 76 | 77 | checkCounters(c) { 78 | Object.keys(this.ccu[c]).forEach(iface => { 79 | if (this.ccu[c][iface] !== this[c][iface]) { 80 | this[c][iface] = this.ccu[c][iface]; 81 | const topic = this.ccu.topicReplace(this.topicCounters, {iface, rxtx: c.slice(0, 2)}); 82 | const payload = this[c][iface]; 83 | this.send({topic, payload, retain: true}); 84 | } 85 | }); 86 | } 87 | 88 | setStatus(data) { 89 | statusHelper(this, data); 90 | } 91 | 92 | event(message) { 93 | const topic = this.ccu.topicReplace(this.topicOutputEvent, message); 94 | const retain = !(message.datapoint && message.datapoint.startsWith('PRESS_')); 95 | this.send({topic, payload: this.output(message), retain}); 96 | 97 | if (['LEVEL', 'STATE'].includes(message.datapoint) && message.working === false) { 98 | const messageNotWorking = RED.util.cloneMessage(message); 99 | messageNotWorking.datapoint += '_NOTWORKING'; 100 | messageNotWorking.datapointName += '_NOTWORKING'; 101 | this.send({topic: this.ccu.topicReplace(this.topicOutputEvent, messageNotWorking), payload: this.output(messageNotWorking), retain: true}); 102 | } 103 | } 104 | 105 | sysvarOutput(message) { 106 | const topic = this.ccu.topicReplace(this.topicOutputSysvar, message); 107 | this.send({topic, payload: this.output(message), retain: true}); 108 | } 109 | 110 | programOutput(message) { 111 | const topic = this.ccu.topicReplace(this.topicOutputSysvar, message); 112 | this.send({topic, payload: this.output(message), retain: true}); 113 | } 114 | 115 | output(message) { 116 | message = RED.util.cloneMessage(message); 117 | switch (this.payloadOutput) { 118 | case 'mqsh-basic': { 119 | return { 120 | val: message.payload, 121 | ts: message.ts, 122 | lc: message.lc 123 | }; 124 | } 125 | 126 | case 'mqsh-extended': { 127 | const payload = { 128 | val: message.payload, 129 | ts: message.ts, 130 | lc: message.lc, 131 | hm: message 132 | }; 133 | delete payload.hm.topic; 134 | delete payload.hm.payload; 135 | delete payload.hm.value; 136 | 137 | return payload; 138 | } 139 | 140 | default: { 141 | if (typeof message.payload === 'boolean') { 142 | return Number(message.payload); 143 | } 144 | 145 | return message.payload; 146 | } 147 | } 148 | } 149 | 150 | input(message) { 151 | const {topic, payload} = message; 152 | this.debug('input ' + topic + ' ' + JSON.stringify(payload).slice(0, 40)); 153 | 154 | const topicList = { 155 | setValue: this.topicInputSetValue, 156 | sysvar: this.topicInputSysvar, 157 | putParam: this.topicInputPutParam, 158 | putParamset: this.topicInputPutParamset, 159 | rpc: this.topicInputRpc 160 | }; 161 | 162 | let command; 163 | let filter; 164 | Object.keys(topicList).forEach(key => { 165 | if (!command) { 166 | const parts = topicList[key].split('/'); 167 | const patternArray = []; 168 | const placeholders = []; 169 | for (let i = 0, {length} = parts; i < length; i++) { 170 | let match; 171 | if (match = parts[i].match(/^\${([\w-]+)}$/)) { // eslint-disable-line no-cond-assign 172 | placeholders.push(match[1]); 173 | patternArray[i] = (i + 1) < length ? '+' : '#'; 174 | } else { 175 | patternArray[i] = parts[i]; 176 | } 177 | } 178 | 179 | const pattern = patternArray.join('/'); 180 | const match = mw(topic, pattern); 181 | if (match && match.length === placeholders.length) { 182 | command = key; 183 | filter = Object.assign.apply({}, placeholders.map((v, i) => ({[v]: match[i]}))); 184 | } 185 | } 186 | }); 187 | 188 | if (command && typeof this[command] === 'function') { 189 | this[command](filter, payload); 190 | } 191 | } 192 | 193 | setValue(filter, payload) { 194 | if (filter.channelNameOrAddress) { 195 | if (this.ccu.channelNames[filter.channelNameOrAddress]) { 196 | filter.channel = filter.channelNameOrAddress; 197 | } else { 198 | filter.channel = this.ccu.findChannel(filter.channelNameOrAddress, true); 199 | } 200 | 201 | if (!filter.channel) { 202 | this.error('channel ' + filter.channelNameOrAddress + ' not found'); 203 | return; 204 | } 205 | } 206 | 207 | if (!filter.channel) { 208 | this.error('channel undefined'); 209 | return; 210 | } 211 | 212 | const iface = this.ccu.findIface(filter.channel); 213 | 214 | if (!iface) { 215 | this.error('no interface found for channel ' + filter.channel); 216 | return; 217 | } 218 | 219 | this.ccu.setValue(iface, filter.channel, filter.datapoint, payload).catch(() => {}); 220 | } 221 | 222 | sysvar(filter, payload) { 223 | if (!filter.name) { 224 | this.error('name undefined'); 225 | return; 226 | } 227 | 228 | if (this.ccu.sysvar[filter.name]) { 229 | this.ccu.setVariable(filter.name, payload); 230 | } else if (this.ccu.program[filter.name]) { 231 | if (typeof payload === 'boolean') { 232 | this.ccu.programActive(filter.name, payload); 233 | } else { 234 | this.ccu.programExecute(filter.name); 235 | } 236 | } else { 237 | this.error('no sysvar or program with name ' + filter.name + ' found'); 238 | } 239 | } 240 | 241 | putParam(filter, payload) { 242 | if (filter.channelNameOrAddress) { 243 | if (this.ccu.channelNames[filter.channelNameOrAddress]) { 244 | filter.channel = filter.channelNameOrAddress; 245 | } else { 246 | filter.channel = this.ccu.findChannel(filter.channelNameOrAddress); 247 | } 248 | 249 | if (!filter.channel) { 250 | this.error('channel ' + filter.channelNameOrAddress + ' not found'); 251 | return; 252 | } 253 | } 254 | 255 | if (!filter.channel) { 256 | this.error('channel undefined'); 257 | return; 258 | } 259 | 260 | const iface = this.ccu.findIface(filter.channel); 261 | 262 | if (!iface) { 263 | this.error('no interface found for channel ' + filter.channel); 264 | return; 265 | } 266 | 267 | const psName = this.ccu.paramsetName(iface, this.ccu.metadata.devices[iface][filter.channel], filter.paramset); 268 | const paramsetDescription = this.ccu.paramsetDescriptions[psName]; 269 | if (paramsetDescription && paramsetDescription[filter.param]) { 270 | if (!(paramsetDescription[filter.param].OPERATIONS) && 2) { 271 | this.error('param ' + filter.param + ' not writeable'); 272 | } 273 | 274 | payload = this.paramCast(payload, paramsetDescription[filter.param]); 275 | } else { 276 | this.warn('unknown paramset/param ' + filter.paramset + ' ' + filter.param); 277 | } 278 | 279 | const paramset = {}; 280 | paramset[filter.param] = payload; 281 | 282 | this.ccu.methodCall(iface, 'putParamset', [filter.channel, filter.paramset, paramset]) 283 | .catch(error => this.error(error.message)); 284 | } 285 | 286 | putParamset(filter, payload) { 287 | if (typeof payload !== 'object') { 288 | this.error('payload is not an object'); 289 | return; 290 | } 291 | 292 | if (filter.channelNameOrAddress) { 293 | if (this.ccu.channelNames[filter.channelNameOrAddress]) { 294 | filter.channel = filter.channelNameOrAddress; 295 | } else { 296 | filter.channel = this.ccu.findChannel(filter.channelNameOrAddress); 297 | } 298 | 299 | if (!filter.channel) { 300 | this.error('channel ' + filter.channelNameOrAddress + ' not found'); 301 | return; 302 | } 303 | } 304 | 305 | if (!filter.channel) { 306 | this.error('channel undefined'); 307 | return; 308 | } 309 | 310 | const iface = this.ccu.findIface(filter.channel); 311 | 312 | if (!iface) { 313 | this.error('no interface found for channel ' + filter.channel); 314 | return; 315 | } 316 | 317 | const psName = this.ccu.paramsetName(iface, this.ccu.metadata.devices[iface][filter.channel], filter.paramset); 318 | const paramsetDescription = this.ccu.paramsetDescriptions[psName]; 319 | 320 | const paramset = {}; 321 | 322 | Object.keys(payload).forEach(parameter => { 323 | if (paramsetDescription && paramsetDescription[parameter]) { 324 | if (!(paramsetDescription[parameter].OPERATIONS) && 2) { 325 | this.error('param ' + parameter + ' not writeable'); 326 | } 327 | 328 | paramset[parameter] = this.paramCast(payload[parameter], paramsetDescription[filter.param]); 329 | } else { 330 | this.warn('unknown paramset/param ' + filter.paramset + ' ' + parameter); 331 | paramset[parameter] = payload[parameter]; 332 | } 333 | }); 334 | 335 | this.ccu.methodCall(iface, 'putParamset', [filter.channel, filter.paramset, paramset]) 336 | .catch(error => this.error(error.message)); 337 | } 338 | 339 | paramCast(value, paramset) { 340 | switch (paramset && paramset.TYPE) { 341 | case 'BOOL': 342 | // Fallthrough by intention 343 | case 'ACTION': 344 | // OMG this is so ugly... 345 | if (value === 'false') { 346 | value = false; 347 | } else if (!isNaN(value)) { // Make sure that the string "0" gets casted to boolean false 348 | value = Number(value); 349 | } 350 | 351 | value = Boolean(value); 352 | break; 353 | case 'FLOAT': 354 | value = Number.parseFloat(value); 355 | if (value < paramset.MIN) { 356 | value = paramset.MIN; 357 | } else if (value > paramset.MAX) { 358 | value = paramset.MAX; 359 | } 360 | 361 | value = {explicitDouble: value}; 362 | break; 363 | case 'ENUM': 364 | if (typeof value === 'string') { 365 | if (paramset.ENUM && (paramset.ENUM.includes(value))) { 366 | value = paramset.ENUM.indexOf(value); 367 | } 368 | } 369 | 370 | // Fallthrough by intention 371 | case 'INTEGER': 372 | value = Number.parseInt(value, 10); 373 | if (value < paramset.MIN) { 374 | value = paramset.MIN; 375 | } else if (value > paramset.MAX) { 376 | value = paramset.MAX; 377 | } 378 | 379 | break; 380 | case 'STRING': 381 | value = String(value); 382 | break; 383 | default: 384 | } 385 | 386 | return value; 387 | } 388 | } 389 | 390 | RED.nodes.registerType('ccu-mqtt', CcuMqttNode); 391 | }; 392 | -------------------------------------------------------------------------------- /nodes/ccu-poll.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | 36 | 37 | 45 | 46 | 53 | -------------------------------------------------------------------------------- /nodes/ccu-poll.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const statusHelper = require(path.join(__dirname, '/lib/status.js')); 4 | 5 | module.exports = function (RED) { 6 | class CcuPollNode { 7 | constructor(config) { 8 | RED.nodes.createNode(this, config); 9 | 10 | this.ccu = RED.nodes.getNode(config.ccuConfig); 11 | this.iface = 'ReGaHSS'; 12 | 13 | if (!this.ccu) { 14 | return; 15 | } 16 | 17 | this.ccu.register(this); 18 | 19 | this.on('input', (message, send, done) => { 20 | this.ccu.regaPoll(); // TODO catch errors 21 | done(); 22 | }); 23 | } 24 | 25 | setStatus(data) { 26 | statusHelper(this, data); 27 | } 28 | } 29 | 30 | RED.nodes.registerType('ccu-poll', CcuPollNode); 31 | }; 32 | -------------------------------------------------------------------------------- /nodes/ccu-program.html: -------------------------------------------------------------------------------- 1 | 58 | 59 | 88 | 89 | 115 | 116 | 141 | -------------------------------------------------------------------------------- /nodes/ccu-program.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const statusHelper = require(path.join(__dirname, '/lib/status.js')); 4 | 5 | module.exports = function (RED) { 6 | class CcuProgramNode { 7 | constructor(config) { 8 | RED.nodes.createNode(this, config); 9 | 10 | this.ccu = RED.nodes.getNode(config.ccuConfig); 11 | this.iface = 'ReGaHSS'; 12 | 13 | if (!this.ccu) { 14 | return; 15 | } 16 | 17 | this.ccu.register(this); 18 | 19 | this.name = config.name; 20 | 21 | if (this.name) { 22 | this.idSubscription = this.ccu.subscribeProgram(this.name, message => { 23 | message.topic = this.ccu.topicReplace(config.topic, message); 24 | this.send(message); 25 | }); 26 | } 27 | 28 | this.on('input', this._input); 29 | this.on('close', this._destructor); 30 | } 31 | 32 | _input(message, send, done) { 33 | switch (typeof message.payload) { 34 | case 'boolean': 35 | this.ccu.programActive(this.name || message.topic, message.payload) 36 | .then(message_ => { 37 | send(message_); 38 | 39 | this.status({fill: 'green', shape: 'dot', text: 'connected'}); 40 | done(); 41 | }) 42 | .catch(error => { 43 | this.status({fill: 'red', shape: 'dot', text: 'error'}); 44 | done(error); 45 | }); 46 | break; 47 | default: 48 | this.ccu.programExecute(this.name || message.topic) 49 | .then(message => { 50 | send(message); 51 | 52 | this.status({fill: 'green', shape: 'dot', text: 'connected'}); 53 | done(); 54 | }) 55 | .catch(error => { 56 | this.status({fill: 'red', shape: 'dot', text: 'error'}); 57 | done(error); 58 | }); 59 | } 60 | } 61 | 62 | _destructor(done) { 63 | if (this.idSubscription) { 64 | this.debug('unsubscribe'); 65 | this.ccu.unsubscribeProgram(this.idSubscription); 66 | } 67 | 68 | done(); 69 | } 70 | 71 | setStatus(data) { 72 | statusHelper(this, data); 73 | } 74 | } 75 | 76 | RED.nodes.registerType('ccu-program', CcuProgramNode); 77 | }; 78 | -------------------------------------------------------------------------------- /nodes/ccu-rpc-event.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const statusHelper = require(path.join(__dirname, '/lib/status.js')); 4 | 5 | module.exports = function (RED) { 6 | class CcuRpcEventNode { 7 | constructor(config) { 8 | RED.nodes.createNode(this, config); 9 | this.ccu = RED.nodes.getNode(config.ccuConfig); 10 | 11 | if (!this.ccu) { 12 | return; 13 | } 14 | 15 | this.iface = config.iface; 16 | 17 | const filter = { 18 | cache: config.cache, 19 | change: config.change, 20 | stable: config.working, 21 | iface: config.iface 22 | }; 23 | 24 | this.ccu.register(this); 25 | 26 | [ 27 | 'rooms', 28 | 'functions', 29 | 'device', 30 | 'deviceName', 31 | 'deviceType', 32 | 'channel', 33 | 'channelName', 34 | 'channelType', 35 | 'channelIndex', 36 | 'datapoint' 37 | 38 | ].forEach(attr => { 39 | if (!config[attr]) { 40 | return; 41 | } 42 | 43 | if (config[attr + 'Rx'] === 're') { 44 | filter[attr] = new RegExp(config[attr]); 45 | } else { 46 | filter[attr] = config[attr]; 47 | } 48 | }); 49 | this.idSubscription = this.ccu.subscribe(filter, message => { 50 | message.topic = this.ccu.topicReplace(config.topic, message); 51 | this.send(message); 52 | }); 53 | this.on('close', this._destructor); 54 | } 55 | 56 | _destructor(done) { 57 | this.debug('ccu-rpc-event close'); 58 | this.ccu.unsubscribe(this.idSubscription); 59 | this.ccu.deregister(this); 60 | done(); 61 | } 62 | 63 | setStatus(data) { 64 | statusHelper(this, data); 65 | } 66 | } 67 | 68 | RED.nodes.registerType('ccu-rpc-event', CcuRpcEventNode); 69 | }; 70 | -------------------------------------------------------------------------------- /nodes/ccu-rpc.html: -------------------------------------------------------------------------------- 1 | 125 | 126 | 167 | 168 | 169 | 200 | 201 | 231 | -------------------------------------------------------------------------------- /nodes/ccu-rpc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const statusHelper = require(path.join(__dirname, '/lib/status.js')); 4 | 5 | module.exports = function (RED) { 6 | class CcuRpcNode { 7 | constructor(config) { 8 | RED.nodes.createNode(this, config); 9 | this.ccu = RED.nodes.getNode(config.ccuConfig); 10 | 11 | if (!this.ccu) { 12 | return; 13 | } 14 | 15 | this.ccu.register(this); 16 | 17 | this.on('input', (message, send, done) => { 18 | let parameters = config.params || message.payload; 19 | 20 | if (parameters && typeof parameters === 'string') { 21 | try { 22 | parameters = JSON.parse(parameters); 23 | } catch (error) { 24 | this.error(error); 25 | return; 26 | } 27 | } else if (!parameters) { 28 | parameters = []; 29 | } 30 | 31 | const method = config.method || message.method || message.topic; 32 | const iface = config.iface || message.iface || message.interface; 33 | 34 | if (method === 'setValue') { 35 | const [address, parameter, value] = parameters; 36 | parameters[2] = this.ccu.paramCast(iface, address, 'VALUES', parameter, value); 37 | } else if (method === 'putParamset') { 38 | let [address, paramset, values] = parameters; 39 | values = values || {}; 40 | Object.keys(values).forEach(parameter => { 41 | values[parameter] = this.ccu.paramCast(iface, address, paramset, parameter, values[parameter]); 42 | }); 43 | parameters[2] = values; 44 | } 45 | 46 | this.ccu.methodCall(iface, method, parameters) 47 | .then(res => { 48 | const message = { 49 | ccu: this.ccu.host, 50 | iface, 51 | topic: config.topic, 52 | payload: res, 53 | ts: (new Date()).getTime(), 54 | method 55 | }; 56 | message.topic = this.ccu.topicReplace(config.topic, message); 57 | send(message); 58 | 59 | done(); 60 | }).catch(error => { 61 | done(error); 62 | }); 63 | }); 64 | } 65 | 66 | setStatus(data) { 67 | statusHelper(this, data); 68 | } 69 | } 70 | 71 | RED.nodes.registerType('ccu-rpc', CcuRpcNode); 72 | }; 73 | -------------------------------------------------------------------------------- /nodes/ccu-script.html: -------------------------------------------------------------------------------- 1 | 25 | 26 | 47 | 48 | 51 | 52 | -------------------------------------------------------------------------------- /nodes/ccu-script.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | class CcuScriptNode { 3 | constructor(config) { 4 | RED.nodes.createNode(this, config); 5 | 6 | this.ccu = RED.nodes.getNode(config.ccuConfig); 7 | 8 | if (!this.ccu) { 9 | return; 10 | } 11 | 12 | this.script = config.script; 13 | this.topic = config.topic; 14 | this.iface = 'ReGaHSS'; 15 | 16 | this.on('input', this._input); 17 | 18 | this.status({}); 19 | } 20 | 21 | _input(message, send, done) { 22 | let script = this.script || message.payload; 23 | script += '\n\nvar nr_script_call_success = true;\n'; 24 | this.ccu.script(script) 25 | .then(message => { 26 | message.iface = this.iface; 27 | message.ccu = this.ccu.host; 28 | message.topic = this.ccu.topicReplace(this.topic, message); 29 | send(message); 30 | 31 | if (message && message.objects && message.objects.nr_script_call_success === 'true') { 32 | this.status({fill: 'green', shape: 'dot', text: 'success'}); 33 | done(); 34 | } else { 35 | this.status({fill: 'red', shape: 'dot', text: 'error'}); 36 | done(new Error('Script call failed')); 37 | } 38 | }) 39 | .catch(error => { 40 | this.status({fill: 'red', shape: 'dot', text: 'error'}); 41 | done(error); 42 | }); 43 | } 44 | } 45 | 46 | RED.nodes.registerType('ccu-script', CcuScriptNode); 47 | }; 48 | -------------------------------------------------------------------------------- /nodes/ccu-set-value.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const statusHelper = require(path.join(__dirname, '/lib/status.js')); 4 | 5 | module.exports = function (RED) { 6 | class CcuSetValue { 7 | constructor(config) { 8 | RED.nodes.createNode(this, config); 9 | 10 | this.ccu = RED.nodes.getNode(config.ccuConfig); 11 | 12 | if (!this.ccu) { 13 | return; 14 | } 15 | 16 | this.ccu.register(this); 17 | 18 | this.config = { 19 | iface: config.iface, 20 | rooms: config.rooms, 21 | functions: config.functions, 22 | device: config.device, 23 | deviceType: config.deviceType, 24 | deviceName: config.deviceName, 25 | channel: config.channel, 26 | channelType: config.channelType, 27 | channelIndex: config.channelIndex, 28 | channelName: config.channelName, 29 | datapoint: config.datapoint, 30 | roomsRx: config.roomsRx, 31 | functionsRx: config.functionsRx, 32 | deviceRx: config.deviceRx, 33 | deviceTypeRx: config.deviceTypeRx, 34 | deviceNameRx: config.deviceNameRx, 35 | channelRx: config.channelRx, 36 | channelTypeRx: config.channelTypeRx, 37 | channelIndexRx: config.channelIndexRx, 38 | channelNameRx: config.channelNameRx, 39 | datapointRx: config.datapointRx, 40 | force: config.force 41 | }; 42 | 43 | this.blacklist = new Set(); 44 | this.whitelist = new Set(); 45 | 46 | this.on('input', message => { 47 | this.setValues(message); 48 | }); 49 | 50 | this.on('close', this._destructor); 51 | } 52 | 53 | _destructor(done) { 54 | if (this.idSubscription) { 55 | this.debug('ccu-set-value close'); 56 | this.ccu.unsubscribe(this.idSubscription); 57 | } 58 | 59 | done(); 60 | } 61 | 62 | setStatus(data) { 63 | statusHelper(this, data); 64 | } 65 | 66 | setValues(message) { 67 | const {config} = this; 68 | let dynamicConfig = false; 69 | Object.keys(config).forEach(key => { 70 | if (!config[key]) { 71 | if (key in message) { 72 | dynamicConfig = true; 73 | config[key] = message[key]; 74 | } 75 | } 76 | }); 77 | 78 | if (dynamicConfig) { 79 | this.whitelist.clear(); 80 | this.blacklist.clear(); 81 | } 82 | 83 | let count = 0; 84 | Object.keys(this.ccu.metadata.devices).forEach(iface => { 85 | if (config.iface && iface !== config.iface) { 86 | return; 87 | } 88 | 89 | Object.keys(this.ccu.metadata.devices[iface]).forEach(address => { 90 | if (this.blacklist.has(address)) { 91 | return; 92 | } 93 | 94 | const channel = this.ccu.metadata.devices[iface][address]; 95 | 96 | if (!channel.PARENT) { 97 | this.blacklist.add(address); 98 | return; 99 | } 100 | 101 | if (!this.whitelist.has(address)) { 102 | const device = this.ccu.metadata.devices[iface][channel.PARENT]; 103 | if (config.device) { 104 | if (config.deviceRx === 'str' && config.device !== channel.PARENT) { 105 | this.blacklist.add(address); 106 | return; 107 | } 108 | 109 | if (config.deviceRx === 're' && !channel.PARENT.match(new RegExp(config.device))) { 110 | this.blacklist.add(address); 111 | return; 112 | } 113 | } 114 | 115 | if (config.deviceType) { 116 | if (config.deviceTypeRx === 'str' && config.deviceType !== device.TYPE) { 117 | this.blacklist.add(address); 118 | return; 119 | } 120 | 121 | if (config.deviceTypeRx === 're' && !device.TYPE.match(new RegExp(config.deviceType))) { 122 | this.blacklist.add(address); 123 | return; 124 | } 125 | } 126 | 127 | if (config.deviceName) { 128 | if (!this.ccu.channelNames[address]) { 129 | this.blacklist.add(address); 130 | return; 131 | } 132 | 133 | if (config.deviceNameRx === 'str' && this.ccu.channelNames[channel.PARENT] !== config.deviceName) { 134 | this.blacklist.add(address); 135 | return; 136 | } 137 | 138 | if (config.deviceNameRx === 're' && !this.ccu.channelNames[channel.PARENT].match(new RegExp(config.deviceName))) { 139 | this.blacklist.add(address); 140 | return; 141 | } 142 | } 143 | 144 | if (config.channel) { 145 | if (config.channelRx === 'str' && config.channel !== address) { 146 | this.blacklist.add(address); 147 | return; 148 | } 149 | 150 | if (config.channelRx === 're' && !address.match(new RegExp(config.channel))) { 151 | this.blacklist.add(address); 152 | return; 153 | } 154 | } 155 | 156 | if (config.channelType) { 157 | if (config.channelTypeRx === 'str' && config.channelType !== channel.TYPE) { 158 | this.blacklist.add(address); 159 | return; 160 | } 161 | 162 | if (config.channelTypeRx === 're' && !channel.TYPE.match(new RegExp(config.channelType))) { 163 | this.blacklist.add(address); 164 | return; 165 | } 166 | } 167 | 168 | if (config.channelIndex) { 169 | if (config.channelIndexRx === 'str' && !address.endsWith(':' + config.channelIndex)) { 170 | this.blacklist.add(address); 171 | return; 172 | } 173 | 174 | if (config.channelIndexRx === 're' && !address.split(':')[1].match(new RegExp(String(config.channelIndex)))) { 175 | this.blacklist.add(address); 176 | return; 177 | } 178 | } 179 | 180 | if (config.channelName) { 181 | if (!this.ccu.channelNames[address]) { 182 | this.blacklist.add(address); 183 | return; 184 | } 185 | 186 | if (config.channelNameRx === 'str' && this.ccu.channelNames[address] !== config.channelName) { 187 | this.blacklist.add(address); 188 | return; 189 | } 190 | 191 | if (config.channelNameRx === 're' && !this.ccu.channelNames[address].match(new RegExp(config.channelName))) { 192 | this.blacklist.add(address); 193 | return; 194 | } 195 | } 196 | 197 | if (config.rooms) { 198 | if (!this.ccu.channelRooms[address]) { 199 | this.blacklist.add(address); 200 | return; 201 | } 202 | 203 | if (config.roomsRx === 'str' && !this.ccu.channelRooms[address].includes(config.rooms)) { 204 | this.blacklist.add(address); 205 | return; 206 | } 207 | 208 | if (config.roomsRx === 're') { 209 | let match = false; 210 | this.ccu.channelRooms[address].forEach(room => { 211 | if (room.match(new RegExp(config.rooms))) { 212 | match = true; 213 | } 214 | }); 215 | if (!match) { 216 | this.blacklist.add(address); 217 | return; 218 | } 219 | } 220 | } 221 | 222 | if (config.functions) { 223 | if (!this.ccu.channelFunctions[address]) { 224 | this.blacklist.add(address); 225 | return; 226 | } 227 | 228 | if (config.functionsRx === 'str' && !this.ccu.channelFunctions[address].includes(config.functions)) { 229 | this.blacklist.add(address); 230 | return; 231 | } 232 | 233 | if (config.functionsRx === 're') { 234 | let match = false; 235 | this.ccu.channelFunctions[address].forEach(func => { 236 | if (func.match(new RegExp(config.functions))) { 237 | match = true; 238 | } 239 | }); 240 | if (!match) { 241 | this.blacklist.add(address); 242 | return; 243 | } 244 | } 245 | } 246 | 247 | this.whitelist.add(address); 248 | } 249 | 250 | const psKey = this.ccu.paramsetName(iface, channel, 'VALUES'); 251 | if (this.ccu.paramsetDescriptions[psKey]) { 252 | const rx = new RegExp(config.datapoint); 253 | Object.keys(this.ccu.paramsetDescriptions[psKey]).forEach(dp => { 254 | if (config.datapointRx === 'str' && dp !== config.datapoint) { 255 | return; 256 | } 257 | 258 | if (config.datapointRx === 're' && !dp.match(rx)) { 259 | return; 260 | } 261 | 262 | const datapointName = iface + '.' + address + '.' + dp; 263 | const currentValue = this.ccu.values[datapointName] && this.ccu.values[datapointName].value; 264 | count += 1; 265 | if (dp.startsWith('PRESS_') || typeof currentValue === 'undefined' || currentValue !== message.payload) { 266 | this.ccu.setValueQueued(iface, address, dp, message.payload, false, config.force).catch(() => {}); 267 | } 268 | }); 269 | } 270 | }); 271 | }); 272 | this.status({fill: 'green', shape: 'ring', text: String(count) + ' datapoints'}); 273 | } 274 | } 275 | 276 | RED.nodes.registerType('ccu-set-value', CcuSetValue); 277 | }; 278 | -------------------------------------------------------------------------------- /nodes/ccu-signal.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const statusHelper = require(path.join(__dirname, '/lib/status.js')); 4 | 5 | module.exports = function (RED) { 6 | class CcuSignal { 7 | constructor(config) { 8 | RED.nodes.createNode(this, config); 9 | 10 | this.config = config; 11 | 12 | this.ccu = RED.nodes.getNode(config.ccuConfig); 13 | 14 | if (!this.ccu) { 15 | return; 16 | } 17 | 18 | this.iface = config.iface; 19 | 20 | this.ccu.register(this); 21 | 22 | this.values = {}; 23 | 24 | this.on('input', (message, send, done) => { 25 | const values = {}; 26 | 27 | this.getValue(values, 'rampTimeValue', message) 28 | .then(() => this.getValue(values, 'durationValue', message)) 29 | .then(() => this.getValue(values, 'repeat', message)) 30 | .then(() => this.getValue(values, 'volume', message)) 31 | .then(() => this.getValue(values, 'soundLevel', message)) 32 | .catch(error => { 33 | this.error(error.message); 34 | }) 35 | .then(() => { 36 | this.sendCommand({...this.config, ...values}).then(() => { 37 | done(); 38 | }).catch(error => { 39 | done(error); 40 | }); 41 | }).catch(() => {}); 42 | }); 43 | } 44 | 45 | sendCommand(config) { 46 | let payload; 47 | this.debug(config.channelType); 48 | 49 | switch (config.channelType) { 50 | case 'SIGNAL_CHIME': 51 | console.log('config.chime', config.chime); 52 | payload = [config.volume / 100, config.repeat, 108000, ...config.chime.split(',')]; 53 | console.log('payload', payload); 54 | return this.ccu.setValue(config.iface, config.channel, 'SUBMIT', payload); 55 | case 'SIGNAL_LED': 56 | payload = ['1', config.repeat, 108000, ...config.led.split(',')]; 57 | return this.ccu.setValue(config.iface, config.channel, 'SUBMIT', payload); 58 | case 'ALARM_SWITCH_VIRTUAL_RECEIVER': 59 | return this.ccu.methodCall(config.iface, 'putParamset', [config.channel, 'VALUES', { 60 | ACOUSTIC_ALARM_SELECTION: config.acousticAlarmSelection, 61 | DURATION_UNIT: config.durationUnit, 62 | DURATION_VALUE: Number.parseInt(config.durationValue, 10) || 0, 63 | OPTICAL_ALARM_SELECTION: config.opticalAlarmSelection 64 | }]); 65 | case 'DIMMER_VIRTUAL_RECEIVER': { 66 | const parameters = { 67 | LEVEL: config.dimmerLevel / 100, 68 | RAMP_TIME_UNIT: config.rampTimeUnit, 69 | RAMP_TIME_VALUE: Number(config.rampTimeValue), 70 | DURATION_UNIT: config.durationUnit, 71 | DURATION_VALUE: Number.parseInt(config.durationValue, 10) || 0, 72 | REPETITIONS: Number(config.repetitions), 73 | OUTPUT_SELECT_SIZE: config.dimmerList.length 74 | }; 75 | config.dimmerList.forEach((item, i) => { 76 | const index = i + 1; 77 | parameters['COLOR_LIST_' + index] = Number(item.color); 78 | parameters['ON_TIME_LIST_' + index] = Number(item.ontime); 79 | }); 80 | return this.ccu.methodCall(config.iface, 'putParamset', [config.channel, 'VALUES', parameters]); 81 | } 82 | 83 | case 'BSL_DIMMER_VIRTUAL_RECEIVER': { 84 | return this.ccu.methodCall(config.iface, 'putParamset', [config.channel, 'VALUES', { 85 | LEVEL: config.dimmerLevel / 100, 86 | RAMP_TIME_UNIT: config.rampTimeUnit, 87 | RAMP_TIME_VALUE: Number(config.rampTimeValue), 88 | DURATION_UNIT: config.durationUnit, 89 | DURATION_VALUE: Number.parseInt(config.durationValue, 10) || 0, 90 | COLOR: Number(config.dimmerColor) 91 | }]); 92 | } 93 | 94 | case 'ACOUSTIC_SIGNAL_VIRTUAL_RECEIVER': { 95 | const parameters = { 96 | LEVEL: config.soundLevel / 100, 97 | RAMP_TIME_UNIT: config.rampTimeUnit, 98 | RAMP_TIME_VALUE: Number(config.rampTimeValue), 99 | DURATION_UNIT: config.durationUnit, 100 | DURATION_VALUE: Number.parseInt(config.durationValue, 10) || 0, 101 | REPETITIONS: Number(config.repetitions), 102 | OUTPUT_SELECT_SIZE: config.soundList.length 103 | }; 104 | config.soundList.forEach((item, i) => { 105 | const index = i + 1; 106 | parameters['SOUNDFILE_LIST_' + index] = Number(item.sound); 107 | }); 108 | return this.ccu.methodCall(config.iface, 'putParamset', [config.channel, 'VALUES', parameters]); 109 | } 110 | 111 | default: 112 | return Promise.reject(new Error(`channelType ${config.channelType} unknown`)); 113 | } 114 | } 115 | 116 | setStatus(data) { 117 | statusHelper(this, data); 118 | } 119 | 120 | getValue(values, name, message) { 121 | return new Promise((resolve, reject) => { 122 | const type = this.config[name + 'Type']; 123 | const value = this.config[name]; 124 | 125 | switch (type) { 126 | case 'msg': 127 | values[name] = RED.util.getMessageProperty(message, value); 128 | resolve(); 129 | break; 130 | 131 | case 'flow': 132 | case 'global': { 133 | const contextKey = RED.util.parseContextStore(value); 134 | this.context()[type].get(contextKey.key, contextKey.store, (err, res) => { 135 | if (err) { 136 | reject(err); 137 | } else { 138 | values[name] = res; 139 | resolve(); 140 | } 141 | }); 142 | break; 143 | } 144 | 145 | case 'env': 146 | values[name] = RED.util.evaluateNodeProperty(value, 'env', this); 147 | resolve(); 148 | break; 149 | 150 | default: 151 | values[name] = value; 152 | resolve(); 153 | } 154 | }); 155 | } 156 | } 157 | 158 | RED.nodes.registerType('ccu-signal', CcuSignal); 159 | }; 160 | -------------------------------------------------------------------------------- /nodes/ccu-sysvar.html: -------------------------------------------------------------------------------- 1 | 71 | 72 | 115 | 116 | 152 | 153 | 165 | -------------------------------------------------------------------------------- /nodes/ccu-sysvar.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const statusHelper = require(path.join(__dirname, '/lib/status.js')); 4 | 5 | module.exports = function (RED) { 6 | class CcuSysvarNode { 7 | constructor(config) { 8 | RED.nodes.createNode(this, config); 9 | 10 | this.ccu = RED.nodes.getNode(config.ccuConfig); 11 | this.iface = 'ReGaHSS'; 12 | 13 | if (!this.ccu) { 14 | return; 15 | } 16 | 17 | this.ccu.register(this); 18 | 19 | // Migration 20 | if (typeof config.change === 'undefined') { 21 | config.change = true; 22 | } 23 | 24 | if (typeof config.cache === 'undefined') { 25 | config.cache = true; 26 | } 27 | 28 | this.name = config.name; 29 | this.topic = config.topic; 30 | 31 | this.idSubscription = this.ccu.subscribeSysvar({name: this.name, cache: config.cache, change: config.change}, message => { 32 | this.status({fill: 'green', shape: 'ring', text: String(message.payload)}); 33 | message.topic = this.ccu.topicReplace(config.topic, message); 34 | this.send(message); 35 | }); 36 | 37 | this.on('input', this._input); 38 | this.on('close', this._destructor); 39 | } 40 | 41 | _input(message, send, done) { 42 | const name = this.name || message.topic; 43 | const value = message.payload; 44 | this.ccu.setVariable(name, value) 45 | .then(() => { 46 | this.status({fill: 'green', shape: 'ring', text: String(value)}); 47 | done(); 48 | }) 49 | .catch(error => { 50 | this.currentStatus = 'red'; 51 | this.status({fill: 'red', shape: 'dot', text: 'error'}); 52 | done(error); 53 | }); 54 | } 55 | 56 | _destructor(done) { 57 | if (this.idSubscription) { 58 | this.debug('unsubscribe'); 59 | this.ccu.unsubscribeSysvar(this.idSubscription); 60 | } 61 | 62 | done(); 63 | } 64 | 65 | setStatus(data) { 66 | statusHelper(this, data); 67 | } 68 | } 69 | 70 | RED.nodes.registerType('ccu-sysvar', CcuSysvarNode); 71 | }; 72 | -------------------------------------------------------------------------------- /nodes/ccu-value.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const statusHelper = require(path.join(__dirname, '/lib/status.js')); 4 | 5 | module.exports = function (RED) { 6 | class CcuValue { 7 | constructor(config) { 8 | RED.nodes.createNode(this, config); 9 | 10 | this.ccu = RED.nodes.getNode(config.ccuConfig); 11 | 12 | if (!this.ccu) { 13 | return; 14 | } 15 | 16 | this.iface = config.iface; 17 | this.queue = config.queue; 18 | 19 | this.ccu.register(this); 20 | 21 | if (config.iface && config.channel && config.datapoint) { 22 | const filter = { 23 | cache: config.cache, 24 | change: config.change, 25 | stable: config.working, 26 | iface: config.iface, 27 | channel: String(config.channel).split(' ')[0], 28 | datapoint: config.datapoint 29 | }; 30 | 31 | this.idSubscription = this.ccu.subscribe(filter, message => { 32 | this.status({fill: 'green', shape: 'ring', text: String(message.payload)}); 33 | message.topic = this.ccu.topicReplace(config.topic, message); 34 | this.send(message); 35 | }); 36 | } 37 | 38 | this.on('input', (message, send, done) => { 39 | const [tIface, tChannel, tDatapoint] = (message.topic || '').split('.'); 40 | const iface = config.iface || message.interface || message.iface || tIface; 41 | const channel = (config.channel || this.ccu.findChannel(message.channelName, true) || message.channel || tChannel || '').split(' ')[0]; 42 | const datapoint = config.datapoint || message.datapoint || tDatapoint; 43 | 44 | if (!iface) { 45 | this.error('interface undefined'); 46 | return; 47 | } 48 | 49 | if (!channel) { 50 | this.error('channel undefined'); 51 | return; 52 | } 53 | 54 | if (!datapoint) { 55 | this.error('datapoint undefined'); 56 | return; 57 | } 58 | 59 | let ramp; 60 | switch (config.rampType) { 61 | case 'msg': 62 | ramp = message[config.ramp]; 63 | break; 64 | case 'flow': 65 | ramp = this.context().flow.get(config.ramp); 66 | break; 67 | case 'global': 68 | ramp = this.context().global.get(config.ramp); 69 | break; 70 | case 'num': 71 | ramp = config.ramp; 72 | break; 73 | default: 74 | } 75 | 76 | ramp = Number.parseFloat(ramp); 77 | 78 | let on; 79 | switch (config.onType) { 80 | case 'msg': 81 | on = message[config.on]; 82 | break; 83 | case 'flow': 84 | on = this.context().flow.get(config.on); 85 | break; 86 | case 'global': 87 | on = this.context().global.get(config.on); 88 | break; 89 | case 'num': 90 | on = config.on; 91 | break; 92 | default: 93 | } 94 | 95 | on = Number.parseFloat(on); 96 | 97 | if (!ramp && !on) { 98 | this.ccu[this.queue ? 'setValueQueued' : 'setValue'](iface, channel, datapoint, message.payload, config.burst).then(() => { 99 | done(); 100 | }).catch(error => { 101 | done(error); 102 | }); 103 | } else { 104 | const parameters = {}; 105 | if (on) { 106 | parameters.ON_TIME = this.ccu.paramCast(iface, channel, 'VALUES', 'ON_TIME', on); 107 | } 108 | 109 | if (ramp) { 110 | parameters.RAMP_TIME = this.ccu.paramCast(iface, channel, 'VALUES', 'RAMP_TIME', ramp); 111 | } 112 | 113 | parameters[datapoint] = this.ccu.paramCast(iface, channel, 'VALUES', datapoint, message.payload); 114 | // Todo queue 115 | this.ccu.methodCall(iface, 'putParamset', [channel, 'VALUES', parameters]).then(() => { 116 | done(); 117 | }).catch(error => { 118 | done(error); 119 | }); 120 | } 121 | }); 122 | 123 | this.on('close', this._destructor); 124 | } 125 | 126 | _destructor(done) { 127 | if (this.idSubscription) { 128 | this.debug('ccu-value close'); 129 | this.ccu.unsubscribe(this.idSubscription); 130 | } 131 | 132 | done(); 133 | } 134 | 135 | setStatus(data) { 136 | statusHelper(this, data); 137 | } 138 | } 139 | 140 | RED.nodes.registerType('ccu-value', CcuValue); 141 | }; 142 | -------------------------------------------------------------------------------- /nodes/icons/ccu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdmtc/node-red-contrib-ccu/e0d3cc522be0598884285d38d50c58647bcd48a2/nodes/icons/ccu.png -------------------------------------------------------------------------------- /nodes/lib/status.js: -------------------------------------------------------------------------------- 1 | module.exports = (that, data) => { 2 | data = data || {}; 3 | if (!that.iface) { 4 | if (data.ifaceStatus) { 5 | let status = 0; 6 | Object.keys(data.ifaceStatus).forEach(s => { 7 | if (data.ifaceStatus[s]) { 8 | status += 1; 9 | } 10 | }); 11 | if (status < 1) { 12 | that.status({fill: 'red', shape: 'dot', text: 'disconnected'}); 13 | that.currentStatus = 'red'; 14 | } else if (status === Object.keys(data.ifaceStatus).length) { 15 | if (that.currentStatus !== 'green') { 16 | that.status({fill: 'green', shape: 'dot', text: 'connected'}); 17 | that.currentStatus = 'green'; 18 | } 19 | } else { 20 | that.status({fill: 'yellow', shape: 'dot', text: 'partly connected'}); 21 | that.currentStatus = 'yellow'; 22 | } 23 | } else { 24 | that.status({fill: 'red', shape: 'dot', text: 'disconnected'}); 25 | that.currentStatus = 'red'; 26 | } 27 | } else if (data.ifaceStatus && data.ifaceStatus[that.iface]) { 28 | if (that.currentStatus !== 'green') { 29 | that.status({fill: 'green', shape: 'dot', text: 'connected'}); 30 | that.currentStatus = 'green'; 31 | } 32 | } else { 33 | that.status({fill: 'red', shape: 'dot', text: 'disconnected'}); 34 | that.currentStatus = 'red'; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /nodes/locales/de/ccu-connection.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-connection": { 3 | 4 | }, 5 | "common": { 6 | "label": { 7 | "rooms":"Räume", 8 | "functions": "Gewerke", 9 | "allDevices": "Geräte", 10 | "sysVars": "Systemvariable", 11 | "value": "Wert (value)", 12 | "valueEnum": "value enumeration (valueEnum)", 13 | "ts": "Zeitpunkt letzte Aktualisierung (ts)", 14 | "lc": "Zeitpunkt letzte Änderung (lc)", 15 | "working": "im Schaltvorgang (working)", 16 | "direction": "Richtung (direction)", 17 | "all": "alle Eigenschaften als Objekt" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /nodes/locales/de/ccu-display.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /nodes/locales/de/ccu-display.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-display": { 3 | "disable": "nicht verändern", 4 | "line": "Zeile", 5 | "color": "Farbe", 6 | "icon": "Icon", 7 | "channel": "Kanal", 8 | "sound": "Ton", 9 | "repeat": "Wiederholungen", 10 | "infinity": "Unendlich", 11 | "off": "Aus", 12 | "long_long":"Lang Lang", 13 | "long_short":"Lang Kurz", 14 | "long_short_short":"Lang Kurz Kurz", 15 | "short":"Kurz", 16 | "short_short":"Kurz Kurz", 17 | "long":"Lang", 18 | "seconds": "Sekunden", 19 | "red": "Rot", 20 | "green": "Grün", 21 | "orange": "Orange", 22 | "white": "Weiß", 23 | "yellow": "Gelb", 24 | "blue": "Blau", 25 | "on": "Ein", 26 | "open":"Offen", 27 | "close":"Geschlossen", 28 | "error":"Fehler", 29 | "ok":"Ok", 30 | "information":"Information", 31 | "new_message":"Neue Nachricht", 32 | "service_message": "Servicemeldung", 33 | "signal_green":"Signal Grün", 34 | "signal_yellow":"Signal Gelb", 35 | "signal_red":"Signal Rot" 36 | } 37 | } -------------------------------------------------------------------------------- /nodes/locales/de/ccu-get-value.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-get-value": { 3 | 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /nodes/locales/de/ccu-poll.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-poll": { 3 | 4 | } 5 | } -------------------------------------------------------------------------------- /nodes/locales/de/ccu-program.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-program": { 3 | "tip": "Wird kein Programm ausgewählt kann der Programm-Name über msg.topic übergeben werden." 4 | } 5 | } -------------------------------------------------------------------------------- /nodes/locales/de/ccu-rpc-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-rpc-event": { 3 | "tip": "Tipp: ", 4 | "change": "Nur geänderte Werte ausgeben", 5 | "cache": "Beim Start letzten bekannten Wert ausgeben", 6 | "working": "Während WORKING keine Werte ausgeben", 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /nodes/locales/de/ccu-rpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-rpc": { 3 | "tip1": "Die Methode leer lassen um die Methode in", 4 | "tip2": "Attribut der Nachricht zu übergeben. Werden keine Params konfiguriert können die Parameter im", 5 | "tip3": "Attribut der Nachricht übergeben werden." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /nodes/locales/de/ccu-script.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-script": { 3 | "tip": "Tipp: Um ein Script via msg.payload zu senden das Script-Eingabefeld leer lassen." 4 | } 5 | } -------------------------------------------------------------------------------- /nodes/locales/de/ccu-set-value.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-set-value": { 3 | "tip-message": "Alternativ können die hier einzustellenden Werte auch in der Input-Nachricht festgelegt werden, z.B.:" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /nodes/locales/de/ccu-signal.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-signal": { 3 | 4 | } 5 | } -------------------------------------------------------------------------------- /nodes/locales/de/ccu-sysvar.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-sysvar": { 3 | "tip": "Wird keine Variable ausgewählt kann der Variablen-Name über msg.topic übergeben werden.", 4 | "change": "Nur geänderte Werte ausgeben", 5 | "cache": "Beim Start aktuellen Wert ausgeben" 6 | } 7 | } -------------------------------------------------------------------------------- /nodes/locales/de/ccu-value.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-value": { 3 | "tip1": "Interface und/oder Channel und/oder Datapoint können leer gelassen werden um über", 4 | "tip2": "und", 5 | "tip3": "übergeben zu werden. Alternativ können auch alle 3 über", 6 | "tip4": "(durch Punkte getrennt) übergeben werden, z.B.", 7 | "tip5": "Es ist zu beachten dass dieser Node nur dann Nachrichten ausgibt wenn Interface, Channel und Datapoint gesetzt sind. Um Events von mehreren Aktoren zu empfangen bitte den \"rpc event\" Node nutzen.", 8 | "change": "Nur geänderte Werte ausgeben", 9 | "cache": "Beim Start letzten bekannten Wert ausgeben", 10 | "working": "Während WORKING keine Werte ausgeben", 11 | "queue": "Queue" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-connection.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-connection": { 3 | 4 | }, 5 | "common": { 6 | "label": { 7 | "rooms":"Room", 8 | "functions": "Function", 9 | "allDevices": "Device", 10 | "sysVars": "System Variable", 11 | "value": "value", 12 | "valueEnum": "value enumeration (valueEnum)", 13 | "ts": "timestamp (ts)", 14 | "lc": "last change (lc)", 15 | "working": "working", 16 | "direction": "direction", 17 | "all": "all properties as object" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-display.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-display.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-display": { 3 | "disable": "Don't touch", 4 | "line": "Line", 5 | "color": "Color", 6 | "icon": "Icon", 7 | "channel": "Channel", 8 | "sound": "Sound", 9 | "repeat": "Repeat", 10 | "infinity": "infinity", 11 | "off": "Off", 12 | "long_long":"Long Long", 13 | "long_short":"Long Short", 14 | "long_short_short":"Long Short Short", 15 | "short":"Short", 16 | "short_short":"Short Short", 17 | "long":"Long", 18 | "seconds": "seconds", 19 | "red": "Red", 20 | "green": "Green", 21 | "orange": "Orange", 22 | "white": "White", 23 | "yellow": "Yellow", 24 | "blue": "Blue", 25 | "on": "On", 26 | "open":"Open", 27 | "close":"Closed", 28 | "error":"Error", 29 | "ok":"Ok", 30 | "information":"information", 31 | "new_message":"new message", 32 | "service_message": "service message", 33 | "signal_green":"signal green", 34 | "signal_yellow":"signal yellow", 35 | "signal_red":"signal red" 36 | } 37 | } -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-poll.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-poll": { 3 | 4 | } 5 | } -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-program.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-program": { 3 | "tip": "Omit the program name to supply it via msg.topic" 4 | } 5 | } -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-rpc-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-rpc-event": { 3 | "tip": "Tip: ", 4 | "change": "Emit only changed values", 5 | "cache": "Emit cached value on start", 6 | "working": "Don't emit values while actuator is in WORKING state" 7 | } 8 | } -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-rpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-rpc": { 3 | "tip1": "Omit Method to supply it via", 4 | "tip2": "property. If Params are ommited they can be supplied via", 5 | "tip3": "property." 6 | } 7 | } -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-script.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-script": { 3 | "tip": "Tip: Leave Script blank if you want to deliver it via msg.payload." 4 | } 5 | } -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-set-value.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-set-value": { 3 | "tip-message": "Alternatively, the settings can also be defined in the input message, e.g:" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-signal.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-signal": { 3 | 4 | } 5 | } -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-switch.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-switch": { 3 | "switch": "switch", 4 | "label": { 5 | "name": "Name", 6 | "property": "Property", 7 | "rule": "rule", 8 | "repair": "recreate message sequences" 9 | }, 10 | "and": "and", 11 | "checkall": "checking all rules", 12 | "stopfirst": "stopping after first match", 13 | "ignorecase": "ignore case", 14 | "rules": { 15 | "btwn": "is between", 16 | "cont": "contains", 17 | "regex": "matches regex", 18 | "true": "is true", 19 | "false": "is false", 20 | "null": "is null", 21 | "nnull": "is not null", 22 | "istype": "is of type", 23 | "empty": "is empty", 24 | "nempty": "is not empty", 25 | "head": "head", 26 | "tail": "tail", 27 | "index": "index between", 28 | "exp": "JSONata exp", 29 | "else": "otherwise" 30 | }, 31 | "errors": { 32 | "invalid-expr": "Invalid JSONata expression: __error__", 33 | "too-many": "too many pending messages in switch node" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-sysvar.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-sysvar": { 3 | "tip": "Omit the variable name to supply it via msg.payload", 4 | "change": "Emit changed values only", 5 | "cache": "Emit value on start" 6 | } 7 | } -------------------------------------------------------------------------------- /nodes/locales/en-US/ccu-value.json: -------------------------------------------------------------------------------- 1 | { 2 | "ccu-value": { 3 | "tip1": "Omit Interface, Channel and/or Datapoint if you want to supply them via", 4 | "tip2": "and", 5 | "tip3": "Alternatively you can supply all 3 via", 6 | "tip4": "(separated through dots) e.g.", 7 | "tip5": "Mind that this node only emits messages if Interface, Channel und Datapoint are set. To receive events from multiple actuators please use the \"rpc event\" node.", 8 | "change": "Emit changed values only", 9 | "cache": "Emit cached value on start", 10 | "working": "Don't emit values while actuator is in WORKING state", 11 | "queue": "Queue Commands" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-ccu", 3 | "version": "3.4.2", 4 | "license": "MIT", 5 | "main": "none", 6 | "keywords": [ 7 | "node-red", 8 | "homematic", 9 | "ccu", 10 | "bidcos", 11 | "cuxd", 12 | "smart home automation", 13 | "eq-3" 14 | ], 15 | "author": { 16 | "name": "Sebastian Raff", 17 | "email": "hobbyquaker@gmail.com" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/rdmtc/node-red-contrib-ccu/issues" 21 | }, 22 | "bundleDependencies": false, 23 | "contributors": [ 24 | { 25 | "name": "Hypnos3", 26 | "email": "hypnos3@online.de" 27 | }, 28 | { 29 | "name": "psi-4ward", 30 | "email": "gh@psi.cx" 31 | }, 32 | { 33 | "name": "Timo Wendt https://github.com/twendt" 34 | }, 35 | { 36 | "name": "Konrad Mattheis https://github.com/konne" 37 | }, 38 | { 39 | "name": "Simon Christmann https://github.com/dersimn", 40 | "email": "simon@christmann.email" 41 | }, 42 | { 43 | "name": "Konrni https://github.com/Konrni" 44 | }, 45 | { 46 | "name": "Bjo-Frei https://github.com/bjo-frei" 47 | }, 48 | { 49 | "name": "Sineos https://github.com/Sineos" 50 | } 51 | ], 52 | "dependencies": { 53 | "binrpc": "^3.3.1", 54 | "buffer-base62": "^0.1.2", 55 | "hm-discover": "^1.1.3", 56 | "homematic-rega": "^1.5.2", 57 | "homematic-xmlrpc": "^1.0.2", 58 | "mqtt-wildcard": "^3.0.9", 59 | "nextport": "^1.0.0", 60 | "obj-ease": "^1.0.1", 61 | "promise.prototype.finally": "^3.1.2", 62 | "string-similarity": "^4.0.2" 63 | }, 64 | "deprecated": false, 65 | "description": "Node-RED Nodes for the Homematic CCU", 66 | "devDependencies": { 67 | "camo-purge": "^1.0.2", 68 | "coveralls": "^3.1.0", 69 | "eslint-plugin-html": "^6.0.2", 70 | "eslint-plugin-promise": "^4.3.1", 71 | "hm-simulator": "^0.1.1", 72 | "husky": "^4.2.5", 73 | "mocha": "^8.1.0", 74 | "node-red": "^1.1.2", 75 | "node-red-node-test-helper": "^0.2.5", 76 | "nyc": "^15.1.0", 77 | "should": "^13.2.3", 78 | "xo": "^0.32.1" 79 | }, 80 | "engines": { 81 | "node": ">= 10.0.0" 82 | }, 83 | "homepage": "https://github.com/rdmtc/node-red-contrib-ccu#readme", 84 | "husky": { 85 | "hooks": { 86 | "pre-commit": "npm run lintonly", 87 | "pre-push": "npm run lintonly && npm run testonly" 88 | } 89 | }, 90 | "node-red": { 91 | "nodes": { 92 | "ccu-connection": "nodes/ccu-connection.js", 93 | "ccu-value": "nodes/ccu-value.js", 94 | "ccu-rpc-event": "nodes/ccu-rpc-event.js", 95 | "ccu-set-value": "nodes/ccu-set-value.js", 96 | "ccu-rpc": "nodes/ccu-rpc.js", 97 | "ccu-signal": "nodes/ccu-signal.js", 98 | "ccu-display": "nodes/ccu-display.js", 99 | "ccu-sysvar": "nodes/ccu-sysvar.js", 100 | "ccu-program": "nodes/ccu-program.js", 101 | "ccu-script": "nodes/ccu-script.js", 102 | "ccu-poll": "nodes/ccu-poll.js", 103 | "ccu-get-value": "nodes/ccu-get-value.js", 104 | "ccu-switch": "nodes/ccu-switch.js", 105 | "ccu-hm2mqtt": "nodes/ccu-mqtt.js", 106 | "ccu-alexa": "nodes/ccu-alexa.js" 107 | } 108 | }, 109 | "repository": { 110 | "type": "git", 111 | "url": "git+https://github.com/rdmtc/node-red-contrib-ccu.git" 112 | }, 113 | "scripts": { 114 | "checkgit": "([[ $(git rev-parse --abbrev-ref HEAD) == \"master\" ]] && git diff --exit-code && git diff --cached --exit-code && git diff --exit-code origin/master..master)", 115 | "lintfix": "xo --fix", 116 | "lintonly": "xo", 117 | "postpublish": "git tag v$(jq -r '.version' package.json) && git push --no-verify --tags", 118 | "prepublishOnly": "npm run checkgit --silent", 119 | "test": "camo-purge; xo && nyc mocha \"test/**/*_spec.js\" --exit && nyc report --reporter=text-lcov | coveralls --force", 120 | "testcov": "nyc mocha \"test/**/*_spec.js\" --exit", 121 | "testonly": "mocha \"test/**/*_spec.js\" --exit" 122 | }, 123 | "xo": { 124 | "space": 4, 125 | "plugins": [ 126 | "html", 127 | "promise" 128 | ], 129 | "global": [ 130 | "$", 131 | "RED" 132 | ], 133 | "extensions": [ 134 | "js", 135 | "html" 136 | ], 137 | "rules": { 138 | "promise/catch-or-return": [ 139 | "warn", 140 | { 141 | "allowThen": true, 142 | "allowFinally": true 143 | } 144 | ], 145 | "no-template-curly-in-string": "warn", 146 | "camelcase": "warn", 147 | "capitalized-comments": 0, 148 | "spaced-comment": 0, 149 | "unicorn/catch-error-name": "warn", 150 | "unicorn/prefer-string-slice": "warn", 151 | "valid-jsdoc": 0, 152 | "eslint-comments/no-unused-disable": 0, 153 | "promise/prefer-await-to-then": "warn", 154 | "prefer-object-spread": "warn", 155 | "unicorn/prevent-abbreviations": "warn", 156 | "unicorn/prefer-number-properties": "warn", 157 | "unicorn/no-reduce": "warn" 158 | }, 159 | "ignore": [ 160 | "nodes/ccu-switch.js", 161 | "nodes/ccu-switch.html", 162 | "paramsets-join.js" 163 | ] 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /test/context_spec.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, after, before, afterEach */ 2 | /* eslint-disable no-template-curly-in-string, no-unused-vars, unicorn/filename-case */ 3 | 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const should = require('should'); 7 | const helper = require('node-red-node-test-helper'); 8 | const HmSim = require('hm-simulator/sim'); 9 | 10 | const nodeGetValue = require('../nodes/ccu-get-value.js'); 11 | const nodeSwitch = require('../nodes/ccu-switch.js'); 12 | const nodeConnection = require('../nodes/ccu-connection.js'); 13 | const {removeFiles, hmSimOptions} = require('./utils'); 14 | 15 | helper.init(require.resolve('node-red')); 16 | 17 | const flow1 = [ 18 | { 19 | id: 'nc', 20 | type: 'ccu-connection', 21 | name: 'localhost', 22 | host: 'localhost', 23 | regaEnabled: true, 24 | bcrfEnabled: false, 25 | iprfEnabled: true, 26 | virtEnabled: false, 27 | bcwiEnabled: false, 28 | cuxdEnabled: false, 29 | regaPoll: false, 30 | regaInterval: '30', 31 | rpcPingTimeout: '60', 32 | rpcInitAddress: '127.0.0.1', 33 | rpcServerHost: '127.0.0.1', 34 | rpcBinPort: '2047', 35 | rpcXmlPort: '2048' 36 | }, 37 | { 38 | id: 'ns', 39 | type: 'ccu-switch', 40 | name: 'switch', 41 | ccuConfig: 'nc', 42 | iface: 'HmIP-RF', 43 | channel: '00131709AE37B4:3 Test-WGC:3', 44 | sysvar: 'Alarmmeldungen', 45 | sysvarProperty: 'value', 46 | datapoint: 'STATE', 47 | datapointProperty: 'value', 48 | property: 'payload', 49 | propertyType: 'msg', 50 | rules: [ 51 | { 52 | t: 'true' 53 | }, 54 | { 55 | t: 'else' 56 | } 57 | ], 58 | checkall: 'true', 59 | repair: false, 60 | outputs: 2, 61 | wires: [ 62 | [ 63 | 'nhtrue' 64 | ], 65 | [ 66 | 'nhfalse' 67 | ] 68 | ] 69 | }, 70 | { 71 | id: 'nhtrue', 72 | type: 'helper' 73 | 74 | }, 75 | { 76 | id: 'nhfalse', 77 | type: 'helper' 78 | 79 | }, 80 | { 81 | id: 'nh', 82 | type: 'helper' 83 | }, 84 | { 85 | id: 'ng', 86 | type: 'ccu-get-value', 87 | name: 'getvalue', 88 | ccuConfig: 'nc', 89 | iface: 'HmIP-RF', 90 | channel: '00131709AE37B4:3 Test-WGC:3', 91 | sysvar: 'Alarmmeldungen', 92 | sysvarProperty: 'value', 93 | datapoint: 'STATE', 94 | datapointProperty: 'value', 95 | setProp: 'payload', 96 | setPropType: 'msg', 97 | wires: [ 98 | [ 99 | 'nh' 100 | ] 101 | ] 102 | } 103 | ]; 104 | 105 | describe('context flow1', () => { 106 | let hmSim; 107 | let nc; 108 | let ng; 109 | let nh; 110 | let nhtrue; 111 | let nhfalse; 112 | let ns; 113 | 114 | afterEach(function (done) { 115 | this.timeout(3000); 116 | setTimeout(() => { 117 | done(); 118 | }, 2000); 119 | }); 120 | 121 | before(function (done) { 122 | this.timeout(12000); 123 | hmSim = new HmSim(hmSimOptions()); 124 | helper.startServer(() => { 125 | helper.load([nodeConnection, nodeSwitch, nodeGetValue], flow1, () => { 126 | nc = helper.getNode('nc'); 127 | nhtrue = helper.getNode('nhtrue'); 128 | nhfalse = helper.getNode('nhfalse'); 129 | ns = helper.getNode('ns'); 130 | nh = helper.getNode('nh'); 131 | ng = helper.getNode('ng'); 132 | setTimeout(() => { 133 | done(); 134 | }, 5000); 135 | }); 136 | }); 137 | }); 138 | 139 | after(function (done) { 140 | this.timeout(7000); 141 | helper.unload().then(() => { 142 | hmSim.close(); 143 | helper.stopServer(() => { 144 | removeFiles(); 145 | done(); 146 | }); 147 | }); 148 | }); 149 | 150 | describe('node switch', () => { 151 | it('should send msg to second output if HmIP-RF/Test-WGC:3/STATE is not true', function (done) { 152 | this.timeout(10000); 153 | nhfalse.once('input', () => { 154 | done(); 155 | }); 156 | ns.receive({}); 157 | }); 158 | 159 | it('should send msg to first output if HmIP-RF/Test-WGC:3/STATE is true', function (done) { 160 | this.timeout(10000); 161 | nhtrue.once('input', () => { 162 | done(); 163 | }); 164 | hmSim.api.emit('setValue', 'hmip', '00131709AE37B4:3', 'STATE', true); 165 | 166 | setTimeout(() => { 167 | ns.receive({}); 168 | }, 500); 169 | }); 170 | }); 171 | 172 | describe('node get value', () => { 173 | it('should send value of if HmIP-RF/Test-WGC:3/STATE', function (done) { 174 | this.timeout(10000); 175 | nh.once('input', message => { 176 | message.payload.should.equal(false); 177 | done(); 178 | }); 179 | hmSim.api.emit('setValue', 'hmip', '00131709AE37B4:3', 'STATE', false); 180 | 181 | setTimeout(() => { 182 | ng.receive({}); 183 | }, 500); 184 | }); 185 | it('should send value of if HmIP-RF/Test-WGC:3/STATE', function (done) { 186 | this.timeout(10000); 187 | nh.once('input', message => { 188 | message.payload.should.equal(true); 189 | done(); 190 | }); 191 | hmSim.api.emit('setValue', 'hmip', '00131709AE37B4:3', 'STATE', true); 192 | 193 | setTimeout(() => { 194 | ng.receive({}); 195 | }, 500); 196 | }); 197 | }); 198 | }); 199 | -------------------------------------------------------------------------------- /test/rpc_spec.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, after, before, afterEach */ 2 | /* eslint-disable no-template-curly-in-string, no-unused-vars, unicorn/filename-case */ 3 | 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const should = require('should'); 7 | const helper = require('node-red-node-test-helper'); 8 | const HmSim = require('hm-simulator/sim'); 9 | 10 | const nodeRpcEvent = require('../nodes/ccu-rpc-event.js'); 11 | const nodeRpc = require('../nodes/ccu-rpc.js'); 12 | const nodeValue = require('../nodes/ccu-value.js'); 13 | const nodeSetValue = require('../nodes/ccu-set-value.js'); 14 | const nodeGetValue = require('../nodes/ccu-get-value.js'); 15 | const nodeDisplay = require('../nodes/ccu-display.js'); 16 | const nodeSignal = require('../nodes/ccu-signal.js'); 17 | const nodeSwitch = require('../nodes/ccu-switch.js'); 18 | const nodeMqtt = require('../nodes/ccu-mqtt.js'); 19 | const nodeConnection = require('../nodes/ccu-connection.js'); 20 | const {removeFiles, hmSimOptions} = require('./utils'); 21 | 22 | helper.init(require.resolve('node-red')); 23 | 24 | const flow1 = [ 25 | { 26 | id: 'ne', 27 | type: 'ccu-rpc-event', 28 | name: '', 29 | iface: '', 30 | ccuConfig: 'nc', 31 | rooms: '', 32 | roomsRx: 'str', 33 | functions: '', 34 | functionsRx: 'str', 35 | device: '', 36 | deviceRx: 'str', 37 | deviceName: '', 38 | deviceNameRx: 'str', 39 | deviceType: '', 40 | deviceTypeRx: 'str', 41 | channel: '', 42 | channelRx: 'str', 43 | channelName: '', 44 | channelNameRx: 'str', 45 | channelType: '', 46 | channelTypeRx: 'str', 47 | datapoint: '', 48 | datapointRx: 'str', 49 | change: false, 50 | working: false, 51 | cache: true, 52 | topic: '${CCU}/${Interface}/${channelName}/${datapoint}', 53 | wires: [ 54 | [ 55 | 'nh' 56 | ] 57 | ] 58 | }, 59 | { 60 | id: 'nh', 61 | type: 'helper' 62 | 63 | }, 64 | { 65 | id: 'nc', 66 | type: 'ccu-connection', 67 | name: 'localhost', 68 | host: 'localhost', 69 | regaEnabled: true, 70 | bcrfEnabled: true, 71 | bcrfBinRpc: true, 72 | iprfEnabled: true, 73 | virtEnabled: false, 74 | bcwiEnabled: false, 75 | cuxdEnabled: false, 76 | regaPoll: false, 77 | regaInterval: '30', 78 | rpcPingTimeout: '60', 79 | rpcInitAddress: '127.0.0.1', 80 | rpcServerHost: '127.0.0.1', 81 | rpcBinPort: '2047', 82 | rpcXmlPort: '2048' 83 | }, 84 | { 85 | id: 'nv', 86 | type: 'ccu-value', 87 | name: '', 88 | iface: 'HmIP-RF', 89 | channel: '00131709AE37B4:3 Test-WGC:3', 90 | datapoint: 'STATE', 91 | mode: '', 92 | start: true, 93 | change: true, 94 | cache: false, 95 | queue: true, 96 | on: 0, 97 | onType: 'undefined', 98 | ramp: 0, 99 | rampType: 'undefined', 100 | working: false, 101 | ccuConfig: 'nc', 102 | topic: '${CCU}/${Interface}/${channel}/${datapoint}', 103 | wires: [ 104 | [ 105 | 'nh' 106 | ] 107 | ] 108 | }, 109 | { 110 | id: 'nr', 111 | type: 'ccu-rpc', 112 | name: '', 113 | iface: 'BidCos-RF', 114 | topic: '${CCU}/${Interface}/${Method}', 115 | method: '', 116 | params: '', 117 | ccuConfig: 'nc' 118 | } 119 | ]; 120 | 121 | describe('rpc flow1', () => { 122 | let hmSim; 123 | let nc; 124 | let nh; 125 | let nv; 126 | let ne; 127 | let nr; 128 | 129 | afterEach(function (done) { 130 | this.timeout(3000); 131 | setTimeout(() => { 132 | done(); 133 | }, 2000); 134 | }); 135 | 136 | before(function (done) { 137 | this.timeout(7000); 138 | hmSim = new HmSim(hmSimOptions()); 139 | helper.startServer(() => { 140 | helper.load([nodeConnection, nodeRpcEvent, nodeValue, nodeRpc], flow1, () => { 141 | nc = helper.getNode('nc'); 142 | nh = helper.getNode('nh'); 143 | nv = helper.getNode('nv'); 144 | ne = helper.getNode('ne'); 145 | nr = helper.getNode('nr'); 146 | setTimeout(() => { 147 | done(); 148 | }, 5000); 149 | }); 150 | }); 151 | }); 152 | 153 | after(function (done) { 154 | this.timeout(7000); 155 | helper.unload().then(() => { 156 | hmSim.close(); 157 | helper.stopServer(() => { 158 | removeFiles(); 159 | done(); 160 | }); 161 | }); 162 | }); 163 | 164 | describe('node rpc', () => { 165 | it('should call setValue method', function (done) { 166 | this.timeout(10000); 167 | nh.once('input', message => { 168 | message.should.have.properties({ 169 | topic: 'localhost/BidCos-RF/HM-RCV-50:2/PRESS_SHORT', 170 | payload: true 171 | }); 172 | done(); 173 | }); 174 | nr.receive({topic: 'setValue', payload: ['BidCoS-RF:2', 'PRESS_SHORT', true]}); 175 | }); 176 | }); 177 | 178 | describe('node rpc-event', () => { 179 | it('should send msg on BidCos-RF/HM-RCV-50:1/PRESS_SHORT event', function (done) { 180 | this.timeout(10000); 181 | nh.once('input', message => { 182 | message.should.have.properties({ 183 | topic: 'localhost/BidCos-RF/HM-RCV-50:1/PRESS_SHORT', 184 | payload: true, 185 | ccu: 'localhost', 186 | iface: 'BidCos-RF', 187 | device: 'BidCoS-RF', 188 | deviceName: 'HM-RCV-50', 189 | deviceType: 'HM-RCV-50', 190 | channel: 'BidCoS-RF:1', 191 | channelName: 'HM-RCV-50:1', 192 | channelType: 'VIRTUAL_KEY', 193 | channelIndex: 1, 194 | datapoint: 'PRESS_SHORT', 195 | datapointName: 'BidCos-RF.BidCoS-RF:1.PRESS_SHORT', 196 | datapointType: 'ACTION', 197 | datapointMin: false, 198 | datapointMax: true, 199 | datapointDefault: false, 200 | datapointControl: 'BUTTON.SHORT', 201 | value: true, 202 | valueStable: true, 203 | rooms: ['Schlafzimmer', 'Kinderzimmer 1'], 204 | room: 'Schlafzimmer', 205 | functions: ['Taster', 'Zentrale'], 206 | function: 'Taster', 207 | change: true, 208 | cache: false, 209 | stable: true 210 | }); 211 | done(); 212 | }); 213 | hmSim.api.emit('setValue', 'rfd', 'BidCoS-RF:1', 'PRESS_SHORT', true); 214 | }); 215 | it('should send msg on HmIP-RF/Test-WGC:1/PRESS_SHORT event', function (done) { 216 | this.timeout(10000); 217 | nh.once('input', message => { 218 | message.should.have.properties({ 219 | topic: 'localhost/HmIP-RF/Test-WGC:1/PRESS_SHORT', 220 | payload: true, 221 | ccu: 'localhost', 222 | iface: 'HmIP-RF', 223 | device: '00131709AE37B4', 224 | deviceName: 'Test-WGC', 225 | deviceType: 'HmIP-WGC', 226 | channel: '00131709AE37B4:1', 227 | channelName: 'Test-WGC:1', 228 | channelType: 'KEY_TRANSCEIVER', 229 | channelIndex: 1, 230 | datapoint: 'PRESS_SHORT', 231 | datapointName: 'HmIP-RF.00131709AE37B4:1.PRESS_SHORT', 232 | datapointType: 'ACTION', 233 | datapointMin: false, 234 | datapointMax: true, 235 | datapointDefault: false, 236 | datapointControl: 'BUTTON_NO_FUNCTION.SHORT', 237 | value: true, 238 | valueStable: true, 239 | rooms: ['Garage'], 240 | room: 'Garage', 241 | functions: ['Taster'], 242 | function: 'Taster', 243 | change: true, 244 | cache: false, 245 | stable: true 246 | }); 247 | done(); 248 | }); 249 | hmSim.api.emit('setValue', 'hmip', '00131709AE37B4:1', 'PRESS_SHORT', true); 250 | }); 251 | }); 252 | 253 | describe('node rpc-value', () => { 254 | it('should set HmIP-RF/Test-WGC:3/STATE to true', function (done) { 255 | this.timeout(10000); 256 | function handler(message) { 257 | if (message.topic === 'localhost/HmIP-RF/Test-WGC:3/STATE') { 258 | message.should.have.properties({topic: 'localhost/HmIP-RF/Test-WGC:3/STATE', 259 | payload: true, 260 | ccu: 'localhost', 261 | iface: 'HmIP-RF', 262 | device: '00131709AE37B4', 263 | deviceName: 'Test-WGC', 264 | deviceType: 'HmIP-WGC', 265 | channel: '00131709AE37B4:3', 266 | channelName: 'Test-WGC:3', 267 | channelType: 'SWITCH_VIRTUAL_RECEIVER', 268 | channelIndex: 3, 269 | datapoint: 'STATE', 270 | datapointName: 'HmIP-RF.00131709AE37B4:3.STATE', 271 | datapointType: 'BOOL', 272 | datapointMin: false, 273 | datapointMax: true, 274 | datapointDefault: false, 275 | datapointControl: 'SWITCH.STATE', 276 | value: true, 277 | rooms: ['Garage'], 278 | room: 'Garage', 279 | functions: ['Verschluss'], 280 | function: 'Verschluss', 281 | change: true, 282 | cache: false 283 | }); 284 | nh.removeListener('input', handler); 285 | done(); 286 | } 287 | } 288 | 289 | nh.on('input', handler); 290 | nv.receive({payload: true}); 291 | }); 292 | 293 | it('should set HmIP-RF/Test-WGC:3/STATE to false', function (done) { 294 | this.timeout(10000); 295 | function handler(message) { 296 | if (message.topic === 'localhost/HmIP-RF/Test-WGC:3/STATE') { 297 | message.should.have.properties({topic: 'localhost/HmIP-RF/Test-WGC:3/STATE', 298 | payload: false, 299 | ccu: 'localhost', 300 | iface: 'HmIP-RF', 301 | device: '00131709AE37B4', 302 | deviceName: 'Test-WGC', 303 | deviceType: 'HmIP-WGC', 304 | channel: '00131709AE37B4:3', 305 | channelName: 'Test-WGC:3', 306 | channelType: 'SWITCH_VIRTUAL_RECEIVER', 307 | channelIndex: 3, 308 | datapoint: 'STATE', 309 | datapointName: 'HmIP-RF.00131709AE37B4:3.STATE', 310 | datapointType: 'BOOL', 311 | datapointMin: false, 312 | datapointMax: true, 313 | datapointDefault: false, 314 | datapointControl: 'SWITCH.STATE', 315 | value: false, 316 | valuePrevious: true, 317 | rooms: ['Garage'], 318 | room: 'Garage', 319 | functions: ['Verschluss'], 320 | function: 'Verschluss', 321 | change: true, 322 | cache: false 323 | }); 324 | nh.removeListener('input', handler); 325 | done(); 326 | } 327 | } 328 | 329 | nh.on('input', handler); 330 | nv.receive({payload: false}); 331 | }); 332 | }); 333 | }); 334 | -------------------------------------------------------------------------------- /test/simulator-behaviors/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdmtc/node-red-contrib-ccu/e0d3cc522be0598884285d38d50c58647bcd48a2/test/simulator-behaviors/.gitignore -------------------------------------------------------------------------------- /test/simulator-data/channels.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1011, 4 | "address": "BidCoS-RF", 5 | "name": "HM-RCV-50" 6 | }, 7 | { 8 | "id": 1012, 9 | "address": "BidCoS-RF:0", 10 | "name": "HM-RCV-50:0" 11 | }, 12 | { 13 | "id": 1014, 14 | "address": "BidCoS-RF:1", 15 | "name": "HM-RCV-50:1" 16 | }, 17 | { 18 | "id": 1018, 19 | "address": "BidCoS-RF:2", 20 | "name": "HM-RCV-50:2" 21 | }, 22 | { 23 | "id": 1022, 24 | "address": "BidCoS-RF:3", 25 | "name": "HM-RCV-50:3" 26 | }, 27 | { 28 | "id": 1026, 29 | "address": "BidCoS-RF:4", 30 | "name": "HM-RCV-50:4" 31 | }, 32 | { 33 | "id": 1030, 34 | "address": "BidCoS-RF:5", 35 | "name": "HM-RCV-50:5" 36 | }, 37 | { 38 | "id": 1034, 39 | "address": "BidCoS-RF:6", 40 | "name": "HM-RCV-50:6" 41 | }, 42 | { 43 | "id": 1038, 44 | "address": "BidCoS-RF:7", 45 | "name": "HM-RCV-50:7" 46 | }, 47 | { 48 | "id": 1042, 49 | "address": "BidCoS-RF:8", 50 | "name": "HM-RCV-50:8" 51 | }, 52 | { 53 | "id": 1046, 54 | "address": "BidCoS-RF:9", 55 | "name": "HM-RCV-50:9" 56 | }, 57 | { 58 | "id": 1050, 59 | "address": "BidCoS-RF:10", 60 | "name": "HM-RCV-50:10" 61 | }, 62 | { 63 | "id": 1054, 64 | "address": "BidCoS-RF:11", 65 | "name": "HM-RCV-50:11" 66 | }, 67 | { 68 | "id": 1058, 69 | "address": "BidCoS-RF:12", 70 | "name": "HM-RCV-50:12" 71 | }, 72 | { 73 | "id": 1062, 74 | "address": "BidCoS-RF:13", 75 | "name": "HM-RCV-50:13" 76 | }, 77 | { 78 | "id": 1066, 79 | "address": "BidCoS-RF:14", 80 | "name": "HM-RCV-50:14" 81 | }, 82 | { 83 | "id": 1070, 84 | "address": "BidCoS-RF:15", 85 | "name": "HM-RCV-50:15" 86 | }, 87 | { 88 | "id": 1074, 89 | "address": "BidCoS-RF:16", 90 | "name": "HM-RCV-50:16" 91 | }, 92 | { 93 | "id": 1078, 94 | "address": "BidCoS-RF:17", 95 | "name": "HM-RCV-50:17" 96 | }, 97 | { 98 | "id": 1082, 99 | "address": "BidCoS-RF:18", 100 | "name": "HM-RCV-50:18" 101 | }, 102 | { 103 | "id": 1086, 104 | "address": "BidCoS-RF:19", 105 | "name": "HM-RCV-50:19" 106 | }, 107 | { 108 | "id": 1090, 109 | "address": "BidCoS-RF:20", 110 | "name": "HM-RCV-50:20" 111 | }, 112 | { 113 | "id": 1094, 114 | "address": "BidCoS-RF:21", 115 | "name": "HM-RCV-50:21" 116 | }, 117 | { 118 | "id": 1098, 119 | "address": "BidCoS-RF:22", 120 | "name": "HM-RCV-50:22" 121 | }, 122 | { 123 | "id": 1102, 124 | "address": "BidCoS-RF:23", 125 | "name": "HM-RCV-50:23" 126 | }, 127 | { 128 | "id": 1106, 129 | "address": "BidCoS-RF:24", 130 | "name": "HM-RCV-50:24" 131 | }, 132 | { 133 | "id": 1110, 134 | "address": "BidCoS-RF:25", 135 | "name": "HM-RCV-50:25" 136 | }, 137 | { 138 | "id": 1114, 139 | "address": "BidCoS-RF:26", 140 | "name": "HM-RCV-50:26" 141 | }, 142 | { 143 | "id": 1118, 144 | "address": "BidCoS-RF:27", 145 | "name": "HM-RCV-50:27" 146 | }, 147 | { 148 | "id": 1122, 149 | "address": "BidCoS-RF:28", 150 | "name": "HM-RCV-50:28" 151 | }, 152 | { 153 | "id": 1126, 154 | "address": "BidCoS-RF:29", 155 | "name": "HM-RCV-50:29" 156 | }, 157 | { 158 | "id": 1130, 159 | "address": "BidCoS-RF:30", 160 | "name": "HM-RCV-50:30" 161 | }, 162 | { 163 | "id": 1134, 164 | "address": "BidCoS-RF:31", 165 | "name": "HM-RCV-50:31" 166 | }, 167 | { 168 | "id": 1138, 169 | "address": "BidCoS-RF:32", 170 | "name": "HM-RCV-50:32" 171 | }, 172 | { 173 | "id": 1142, 174 | "address": "BidCoS-RF:33", 175 | "name": "HM-RCV-50:33" 176 | }, 177 | { 178 | "id": 1146, 179 | "address": "BidCoS-RF:34", 180 | "name": "HM-RCV-50:34" 181 | }, 182 | { 183 | "id": 1150, 184 | "address": "BidCoS-RF:35", 185 | "name": "HM-RCV-50:35" 186 | }, 187 | { 188 | "id": 1154, 189 | "address": "BidCoS-RF:36", 190 | "name": "HM-RCV-50:36" 191 | }, 192 | { 193 | "id": 1158, 194 | "address": "BidCoS-RF:37", 195 | "name": "HM-RCV-50:37" 196 | }, 197 | { 198 | "id": 1162, 199 | "address": "BidCoS-RF:38", 200 | "name": "HM-RCV-50:38" 201 | }, 202 | { 203 | "id": 1166, 204 | "address": "BidCoS-RF:39", 205 | "name": "HM-RCV-50:39" 206 | }, 207 | { 208 | "id": 1170, 209 | "address": "BidCoS-RF:40", 210 | "name": "HM-RCV-50:40" 211 | }, 212 | { 213 | "id": 1174, 214 | "address": "BidCoS-RF:41", 215 | "name": "HM-RCV-50:41" 216 | }, 217 | { 218 | "id": 1178, 219 | "address": "BidCoS-RF:42", 220 | "name": "HM-RCV-50:42" 221 | }, 222 | { 223 | "id": 1182, 224 | "address": "BidCoS-RF:43", 225 | "name": "HM-RCV-50:43" 226 | }, 227 | { 228 | "id": 1186, 229 | "address": "BidCoS-RF:44", 230 | "name": "HM-RCV-50:44" 231 | }, 232 | { 233 | "id": 1190, 234 | "address": "BidCoS-RF:45", 235 | "name": "HM-RCV-50:45" 236 | }, 237 | { 238 | "id": 1194, 239 | "address": "BidCoS-RF:46", 240 | "name": "HM-RCV-50:46" 241 | }, 242 | { 243 | "id": 1198, 244 | "address": "BidCoS-RF:47", 245 | "name": "HM-RCV-50:47" 246 | }, 247 | { 248 | "id": 1202, 249 | "address": "BidCoS-RF:48", 250 | "name": "HM-RCV-50:48" 251 | }, 252 | { 253 | "id": 1206, 254 | "address": "BidCoS-RF:49", 255 | "name": "HM-RCV-50:49" 256 | }, 257 | { 258 | "id": 1210, 259 | "address": "BidCoS-RF:50", 260 | "name": "HM-RCV-50:50" 261 | }, 262 | { 263 | "id": 1664, 264 | "address": "MEQ1849910", 265 | "name": "Test-MDIR-WM55" 266 | }, 267 | { 268 | "id": 1665, 269 | "address": "MEQ1849910:0", 270 | "name": "Test-MDIR-WM55:0" 271 | }, 272 | { 273 | "id": 1693, 274 | "address": "MEQ1849910:1", 275 | "name": "Test-MDIR-WM55:1" 276 | }, 277 | { 278 | "id": 1699, 279 | "address": "MEQ1849910:2", 280 | "name": "Test-MDIR-WM55:2" 281 | }, 282 | { 283 | "id": 1705, 284 | "address": "MEQ1849910:3", 285 | "name": "Test-MDIR-WM55:3" 286 | }, 287 | { 288 | "id": 1237, 289 | "address": "001F58A9A728D4", 290 | "name": "HmIP-RCV-50" 291 | }, 292 | { 293 | "id": 1238, 294 | "address": "001F58A9A728D4:0", 295 | "name": "HmIP-RCV-50:0" 296 | }, 297 | { 298 | "id": 1239, 299 | "address": "001F58A9A728D4:1", 300 | "name": "HmIP-RCV-50:1" 301 | }, 302 | { 303 | "id": 1242, 304 | "address": "001F58A9A728D4:2", 305 | "name": "HmIP-RCV-50:2" 306 | }, 307 | { 308 | "id": 1245, 309 | "address": "001F58A9A728D4:3", 310 | "name": "HmIP-RCV-50:3" 311 | }, 312 | { 313 | "id": 1248, 314 | "address": "001F58A9A728D4:4", 315 | "name": "HmIP-RCV-50:4" 316 | }, 317 | { 318 | "id": 1251, 319 | "address": "001F58A9A728D4:5", 320 | "name": "HmIP-RCV-50:5" 321 | }, 322 | { 323 | "id": 1254, 324 | "address": "001F58A9A728D4:6", 325 | "name": "HmIP-RCV-50:6" 326 | }, 327 | { 328 | "id": 1257, 329 | "address": "001F58A9A728D4:7", 330 | "name": "HmIP-RCV-50:7" 331 | }, 332 | { 333 | "id": 1260, 334 | "address": "001F58A9A728D4:8", 335 | "name": "HmIP-RCV-50:8" 336 | }, 337 | { 338 | "id": 1263, 339 | "address": "001F58A9A728D4:9", 340 | "name": "HmIP-RCV-50:9" 341 | }, 342 | { 343 | "id": 1266, 344 | "address": "001F58A9A728D4:10", 345 | "name": "HmIP-RCV-50:10" 346 | }, 347 | { 348 | "id": 1269, 349 | "address": "001F58A9A728D4:11", 350 | "name": "HmIP-RCV-50:11" 351 | }, 352 | { 353 | "id": 1272, 354 | "address": "001F58A9A728D4:12", 355 | "name": "HmIP-RCV-50:12" 356 | }, 357 | { 358 | "id": 1275, 359 | "address": "001F58A9A728D4:13", 360 | "name": "HmIP-RCV-50:13" 361 | }, 362 | { 363 | "id": 1278, 364 | "address": "001F58A9A728D4:14", 365 | "name": "HmIP-RCV-50:14" 366 | }, 367 | { 368 | "id": 1281, 369 | "address": "001F58A9A728D4:15", 370 | "name": "HmIP-RCV-50:15" 371 | }, 372 | { 373 | "id": 1284, 374 | "address": "001F58A9A728D4:16", 375 | "name": "HmIP-RCV-50:16" 376 | }, 377 | { 378 | "id": 1287, 379 | "address": "001F58A9A728D4:17", 380 | "name": "HmIP-RCV-50:17" 381 | }, 382 | { 383 | "id": 1290, 384 | "address": "001F58A9A728D4:18", 385 | "name": "HmIP-RCV-50:18" 386 | }, 387 | { 388 | "id": 1293, 389 | "address": "001F58A9A728D4:19", 390 | "name": "HmIP-RCV-50:19" 391 | }, 392 | { 393 | "id": 1296, 394 | "address": "001F58A9A728D4:20", 395 | "name": "HmIP-RCV-50:20" 396 | }, 397 | { 398 | "id": 1299, 399 | "address": "001F58A9A728D4:21", 400 | "name": "HmIP-RCV-50:21" 401 | }, 402 | { 403 | "id": 1302, 404 | "address": "001F58A9A728D4:22", 405 | "name": "HmIP-RCV-50:22" 406 | }, 407 | { 408 | "id": 1305, 409 | "address": "001F58A9A728D4:23", 410 | "name": "HmIP-RCV-50:23" 411 | }, 412 | { 413 | "id": 1308, 414 | "address": "001F58A9A728D4:24", 415 | "name": "HmIP-RCV-50:24" 416 | }, 417 | { 418 | "id": 1311, 419 | "address": "001F58A9A728D4:25", 420 | "name": "HmIP-RCV-50:25" 421 | }, 422 | { 423 | "id": 1314, 424 | "address": "001F58A9A728D4:26", 425 | "name": "HmIP-RCV-50:26" 426 | }, 427 | { 428 | "id": 1317, 429 | "address": "001F58A9A728D4:27", 430 | "name": "HmIP-RCV-50:27" 431 | }, 432 | { 433 | "id": 1320, 434 | "address": "001F58A9A728D4:28", 435 | "name": "HmIP-RCV-50:28" 436 | }, 437 | { 438 | "id": 1323, 439 | "address": "001F58A9A728D4:29", 440 | "name": "HmIP-RCV-50:29" 441 | }, 442 | { 443 | "id": 1326, 444 | "address": "001F58A9A728D4:30", 445 | "name": "HmIP-RCV-50:30" 446 | }, 447 | { 448 | "id": 1329, 449 | "address": "001F58A9A728D4:31", 450 | "name": "HmIP-RCV-50:31" 451 | }, 452 | { 453 | "id": 1332, 454 | "address": "001F58A9A728D4:32", 455 | "name": "HmIP-RCV-50:32" 456 | }, 457 | { 458 | "id": 1335, 459 | "address": "001F58A9A728D4:33", 460 | "name": "HmIP-RCV-50:33" 461 | }, 462 | { 463 | "id": 1338, 464 | "address": "001F58A9A728D4:34", 465 | "name": "HmIP-RCV-50:34" 466 | }, 467 | { 468 | "id": 1341, 469 | "address": "001F58A9A728D4:35", 470 | "name": "HmIP-RCV-50:35" 471 | }, 472 | { 473 | "id": 1344, 474 | "address": "001F58A9A728D4:36", 475 | "name": "HmIP-RCV-50:36" 476 | }, 477 | { 478 | "id": 1347, 479 | "address": "001F58A9A728D4:37", 480 | "name": "HmIP-RCV-50:37" 481 | }, 482 | { 483 | "id": 1350, 484 | "address": "001F58A9A728D4:38", 485 | "name": "HmIP-RCV-50:38" 486 | }, 487 | { 488 | "id": 1353, 489 | "address": "001F58A9A728D4:39", 490 | "name": "HmIP-RCV-50:39" 491 | }, 492 | { 493 | "id": 1356, 494 | "address": "001F58A9A728D4:40", 495 | "name": "HmIP-RCV-50:40" 496 | }, 497 | { 498 | "id": 1359, 499 | "address": "001F58A9A728D4:41", 500 | "name": "HmIP-RCV-50:41" 501 | }, 502 | { 503 | "id": 1362, 504 | "address": "001F58A9A728D4:42", 505 | "name": "HmIP-RCV-50:42" 506 | }, 507 | { 508 | "id": 1365, 509 | "address": "001F58A9A728D4:43", 510 | "name": "HmIP-RCV-50:43" 511 | }, 512 | { 513 | "id": 1368, 514 | "address": "001F58A9A728D4:44", 515 | "name": "HmIP-RCV-50:44" 516 | }, 517 | { 518 | "id": 1371, 519 | "address": "001F58A9A728D4:45", 520 | "name": "HmIP-RCV-50:45" 521 | }, 522 | { 523 | "id": 1374, 524 | "address": "001F58A9A728D4:46", 525 | "name": "HmIP-RCV-50:46" 526 | }, 527 | { 528 | "id": 1377, 529 | "address": "001F58A9A728D4:47", 530 | "name": "HmIP-RCV-50:47" 531 | }, 532 | { 533 | "id": 1380, 534 | "address": "001F58A9A728D4:48", 535 | "name": "HmIP-RCV-50:48" 536 | }, 537 | { 538 | "id": 1383, 539 | "address": "001F58A9A728D4:49", 540 | "name": "HmIP-RCV-50:49" 541 | }, 542 | { 543 | "id": 1386, 544 | "address": "001F58A9A728D4:50", 545 | "name": "HmIP-RCV-50:50" 546 | }, 547 | { 548 | "id": 1445, 549 | "address": "001518A9A59D73", 550 | "name": "Test-MP3P" 551 | }, 552 | { 553 | "id": 1446, 554 | "address": "001518A9A59D73:0", 555 | "name": "Test-MP3P:0" 556 | }, 557 | { 558 | "id": 1474, 559 | "address": "001518A9A59D73:1", 560 | "name": "Test-MP3P:1" 561 | }, 562 | { 563 | "id": 1482, 564 | "address": "001518A9A59D73:2", 565 | "name": "Test-MP3P:2" 566 | }, 567 | { 568 | "id": 1509, 569 | "address": "001518A9A59D73:3", 570 | "name": "Test-MP3P:3" 571 | }, 572 | { 573 | "id": 1520, 574 | "address": "001518A9A59D73:4", 575 | "name": "Test-MP3P:4" 576 | }, 577 | { 578 | "id": 1531, 579 | "address": "001518A9A59D73:5", 580 | "name": "Test-MP3P:5" 581 | }, 582 | { 583 | "id": 1540, 584 | "address": "001518A9A59D73:6", 585 | "name": "Test-MP3P:6" 586 | }, 587 | { 588 | "id": 1580, 589 | "address": "001518A9A59D73:7", 590 | "name": "Test-MP3P:7" 591 | }, 592 | { 593 | "id": 1620, 594 | "address": "001518A9A59D73:8", 595 | "name": "Test-MP3P:8" 596 | }, 597 | { 598 | "id": 1660, 599 | "address": "001518A9A59D73:9", 600 | "name": "Test-MP3P:9" 601 | }, 602 | { 603 | "id": 1390, 604 | "address": "00131709AE37B4", 605 | "name": "Test-WGC" 606 | }, 607 | { 608 | "id": 1391, 609 | "address": "00131709AE37B4:0", 610 | "name": "Test-WGC:0" 611 | }, 612 | { 613 | "id": 1419, 614 | "address": "00131709AE37B4:1", 615 | "name": "Test-WGC:1" 616 | }, 617 | { 618 | "id": 1422, 619 | "address": "00131709AE37B4:2", 620 | "name": "Test-WGC:2" 621 | }, 622 | { 623 | "id": 1427, 624 | "address": "00131709AE37B4:3", 625 | "name": "Test-WGC:3" 626 | }, 627 | { 628 | "id": 1433, 629 | "address": "00131709AE37B4:4", 630 | "name": "Test-WGC:4" 631 | }, 632 | { 633 | "id": 1439, 634 | "address": "00131709AE37B4:5", 635 | "name": "Test-WGC:5" 636 | } 637 | ] 638 | -------------------------------------------------------------------------------- /test/simulator-data/functions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1221, 4 | "name": "Taster", 5 | "channels": [ 6 | 1014, 7 | 1239, 8 | 1419, 9 | 1693, 10 | 1699 11 | ] 12 | }, 13 | { 14 | "id": 1222, 15 | "name": "Zentrale", 16 | "channels": [ 17 | 1014, 18 | 1050, 19 | 1054, 20 | 1058, 21 | 1062, 22 | 1066, 23 | 1070, 24 | 1074, 25 | 1078, 26 | 1082, 27 | 1086, 28 | 1018, 29 | 1090, 30 | 1094, 31 | 1098, 32 | 1102, 33 | 1106, 34 | 1110, 35 | 1114, 36 | 1118, 37 | 1122, 38 | 1126, 39 | 1022, 40 | 1130, 41 | 1134, 42 | 1138, 43 | 1142, 44 | 1146, 45 | 1150, 46 | 1154, 47 | 1158, 48 | 1162, 49 | 1166, 50 | 1026, 51 | 1170, 52 | 1174, 53 | 1178, 54 | 1182, 55 | 1186, 56 | 1190, 57 | 1194, 58 | 1198, 59 | 1202, 60 | 1206, 61 | 1030, 62 | 1210, 63 | 1034, 64 | 1038, 65 | 1042, 66 | 1046, 67 | 1238, 68 | 1239, 69 | 1266, 70 | 1269, 71 | 1272, 72 | 1275, 73 | 1278, 74 | 1281, 75 | 1284, 76 | 1287, 77 | 1290, 78 | 1293, 79 | 1242, 80 | 1296, 81 | 1299, 82 | 1302, 83 | 1305, 84 | 1308, 85 | 1311, 86 | 1314, 87 | 1317, 88 | 1320, 89 | 1323, 90 | 1245, 91 | 1326, 92 | 1329, 93 | 1332, 94 | 1335, 95 | 1338, 96 | 1341, 97 | 1344, 98 | 1347, 99 | 1350, 100 | 1353, 101 | 1248, 102 | 1356, 103 | 1359, 104 | 1362, 105 | 1365, 106 | 1368, 107 | 1371, 108 | 1374, 109 | 1377, 110 | 1380, 111 | 1383, 112 | 1251, 113 | 1386, 114 | 1254, 115 | 1257, 116 | 1260, 117 | 1263 118 | ] 119 | }, 120 | { 121 | "id": 1216, 122 | "name": "Klima", 123 | "channels": [] 124 | }, 125 | { 126 | "id": 1223, 127 | "name": "Energiemanagement", 128 | "channels": [] 129 | }, 130 | { 131 | "id": 1218, 132 | "name": "Umwelt", 133 | "channels": [] 134 | }, 135 | { 136 | "id": 1215, 137 | "name": "Heizung", 138 | "channels": [] 139 | }, 140 | { 141 | "id": 1214, 142 | "name": "Licht", 143 | "channels": [ 144 | 1540 145 | ] 146 | }, 147 | { 148 | "id": 1220, 149 | "name": "Verschluss", 150 | "channels": [ 151 | 1427 152 | ] 153 | }, 154 | { 155 | "id": 1219, 156 | "name": "Sicherheit", 157 | "channels": [ 158 | 1705 159 | ] 160 | }, 161 | { 162 | "id": 1217, 163 | "name": "Wetter", 164 | "channels": [] 165 | } 166 | ] 167 | -------------------------------------------------------------------------------- /test/simulator-data/programs.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"id": 2338, "name":"Abwesend", "info": "", "active":true,"ts":"2019-03-04 18:43:59"}, 3 | {"id": 2329, "name":"Anwesend", "info": "", "active":true,"ts":"2019-03-04 18:41:49"} 4 | ] 5 | -------------------------------------------------------------------------------- /test/simulator-data/rooms.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1230, 4 | "name": "Badezimmer", 5 | "channels": [ 6 | 1693, 7 | 1699, 8 | 1705 9 | ] 10 | }, 11 | { 12 | "id": 1226, 13 | "name": "Schlafzimmer", 14 | "channels": [ 15 | 1014 16 | ] 17 | }, 18 | { 19 | "id": 1227, 20 | "name": "Kinderzimmer 1", 21 | "channels": [ 22 | 1014 23 | ] 24 | }, 25 | { 26 | "id": 1228, 27 | "name": "Kinderzimmer 2", 28 | "channels": [] 29 | }, 30 | { 31 | "id": 1231, 32 | "name": "Garage", 33 | "channels": [ 34 | 1419, 35 | 1427 36 | ] 37 | }, 38 | { 39 | "id": 1233, 40 | "name": "Garten", 41 | "channels": [] 42 | }, 43 | { 44 | "id": 1232, 45 | "name": "Hauswirtschaftsraum", 46 | "channels": [] 47 | }, 48 | { 49 | "id": 1225, 50 | "name": "Küche", 51 | "channels": [] 52 | }, 53 | { 54 | "id": 1224, 55 | "name": "Wohnzimmer", 56 | "channels": [ 57 | 1482, 58 | 1540 59 | ] 60 | }, 61 | { 62 | "id": 1229, 63 | "name": "Büro", 64 | "channels": [] 65 | }, 66 | { 67 | "id": 1234, 68 | "name": "Terrasse", 69 | "channels": [] 70 | } 71 | ] 72 | -------------------------------------------------------------------------------- /test/simulator-data/variables.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"id":40,"name":"Alarmmeldungen","val":0,"min":0,"max":65000,"unit":"","type":"number","enum":[]}, 3 | {"id":41,"name":"Servicemeldungen","val":5,"min":0,"max":65000,"unit":"","type":"number","enum":[]}, 4 | {"id": 1235, "name": "%24%7BsysVarAlarmZone1%7D", "info": "%24%7BsysVarAlarmZone1Msg%7D", "val": false,"ts":"1970-01-01 01:00:00","min":null,"max":null,"unit":"", "type": "boolean", "enum": "%24%7BsysVarAlarmZone1NotTriggered%7D;%24%7BsysVarAlarmZone1Triggered%7D", "channel": "65535"}, 5 | {"id": 950, "name": "Anwesenheit", "info": "Anwesenheit", "val": false,"ts":"2019-03-05 22:48:40","min":null,"max":null,"unit":"", "type": "boolean", "enum": "nicht anwesend;anwesend", "channel": "65535"} 6 | ] 7 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | removeFiles() { 6 | try { 7 | fs.unlinkSync(path.join(__dirname, '..', 'ccu_localhost.json')); 8 | fs.unlinkSync(path.join(__dirname, '..', 'ccu_paramsets_v2.json')); 9 | fs.unlinkSync(path.join(__dirname, '..', 'ccu_rega_localhost.json')); 10 | fs.unlinkSync(path.join(__dirname, '..', 'ccu_values_localhost.json')); 11 | } catch {} 12 | }, 13 | hmSimOptions() { 14 | const {devices} = JSON.parse(fs.readFileSync(path.join(__dirname, 'simulator-data/devices.json'))); 15 | return { 16 | /* 17 | log: { 18 | debug: console.log, 19 | info: console.log, 20 | warn: console.log, 21 | error: console.log 22 | },*/ 23 | 24 | devices: { 25 | rfd: {devices: Object.keys(devices['BidCos-RF']).map(addr => devices['BidCos-RF'][addr])}, 26 | hmip: {devices: Object.keys(devices['HmIP-RF']).map(addr => devices['HmIP-RF'][addr])} 27 | }, 28 | rega: { 29 | port: 8181, 30 | variables: JSON.parse(fs.readFileSync(path.join(__dirname, 'simulator-data/variables.json'))), 31 | programs: JSON.parse(fs.readFileSync(path.join(__dirname, 'simulator-data/programs.json'))), 32 | rooms: JSON.parse(fs.readFileSync(path.join(__dirname, 'simulator-data/rooms.json'))), 33 | functions: JSON.parse(fs.readFileSync(path.join(__dirname, 'simulator-data/functions.json'))), 34 | channels: JSON.parse(fs.readFileSync(path.join(__dirname, 'simulator-data/channels.json'))) 35 | }, 36 | config: { 37 | listenAddress: '127.0.0.1', 38 | binrpcListenPort: 2001, 39 | xmlrpcListenPort: 2010 40 | }, 41 | behaviorPath: path.join(__dirname, 'simulator-behaviors') 42 | }; 43 | } 44 | 45 | }; 46 | -------------------------------------------------------------------------------- /tools/get-channels-working.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const f1 = 'paramsets.json'; 4 | 5 | const ps1 = require(path.join(__dirname, f1)); 6 | 7 | console.log('WORKING:'); 8 | console.log([...new Set(Object.keys(ps1).filter(key => { 9 | return typeof ps1[key].WORKING !== 'undefined'; 10 | }).map(key => key.split('/')[key.split('/').length - 2]))]); 11 | 12 | console.log('\nPROCESS:'); 13 | console.log([...new Set(Object.keys(ps1).filter(key => { 14 | return typeof ps1[key].PROCESS !== 'undefined'; 15 | }).map(key => key.split('/')[key.split('/').length - 2]))]); 16 | -------------------------------------------------------------------------------- /tools/paramsets-join.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const oe = require('obj-ease'); 4 | 5 | const f1 = 'paramsets.json'; 6 | const f2 = 'ccu_paramsets_v2.json'; 7 | 8 | const ps1 = require(path.join(__dirname, '..', f1)); 9 | const ps2 = require(path.join(__dirname, '..', f2)); 10 | 11 | console.log(f1, Object.keys(ps1).length); 12 | console.log(f2, Object.keys(ps2).length); 13 | 14 | oe.extend(ps1, ps2); 15 | 16 | console.log('joined', Object.keys(ps1).length); 17 | 18 | fs.writeFileSync(path.join(__dirname, '..', f1), JSON.stringify(ps1, null, ' ')); 19 | --------------------------------------------------------------------------------