├── .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 | [](http://badge.fury.io/js/node-red-contrib-ccu)
4 | [](https://david-dm.org/rdmtc/node-red-contrib-ccu)
5 | [](https://travis-ci.org/rdmtc/node-red-contrib-ccu)
6 | [](https://coveralls.io/github/rdmtc/node-red-contrib-ccu?branch=master)
7 | [](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 | 
43 |
44 | 
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 | 
53 |
54 | 
55 |
56 | ### debmatic
57 |
58 | In this example both Node-RED and debmatic are installed on the same (possibly virtual) host.
59 |
60 | 
61 |
62 | 
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 | 
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 |
--------------------------------------------------------------------------------