node-red-contrib-ccu 4.4.4 → 4.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,31 @@ Notable changes to node-red-contrib-ccu. Format follows
5
5
  user-visible symptom and the cause, not the commit list (the release notes
6
6
  append commits automatically).
7
7
 
8
+ ## 4.4.5 (2026-09-26)
9
+
10
+ ### Fixed
11
+
12
+ - **A Node-RED started before rfd went through the CCU's proxy for good.**
13
+ The connection decides at start whether it runs on the CCU itself, and
14
+ looked only for a listener on rfd's direct port. On openccu-lite, where
15
+ RedMatic starts at boot before the interface processes (the early start),
16
+ that port is not open yet, so every interface went through lighttpd's
17
+ proxy ports (XML-RPC instead of BIN-RPC for BidCos) until the next restart.
18
+ A loopback host on a system that has the CCU's interface list
19
+ (`/etc/config/InterfacesList.xml`) is now taken as local as well.
20
+ - **Waiting for an interface process that is still starting is no warning**
21
+ (task 12). Before the interface was ever connected, a refused connection
22
+ is one info line (_"HmIP-RF not listening yet (connect ECONNREFUSED),
23
+ waiting for it - retrying in 1 s"_); another transport error, or a refused
24
+ connection after the interface had been connected, is still a warning, and
25
+ a fault answer an error.
26
+
27
+ ### Changed
28
+
29
+ - **A failed `init` is retried after 1 s, 2 s, 4 s and 8 s, then every 15 s**
30
+ (task 12; was 2, 4, 8, 16 s, then every 30 s), the schedule of hm2mqtt.js and
31
+ the Homematic Manager.
32
+
8
33
  ## 4.4.4 (2026-09-17)
9
34
 
10
35
  ### Fixed
package/README.md CHANGED
@@ -28,6 +28,19 @@ Some example flows can be found in the [RedMatic Wiki](https://github.com/rdmtc/
28
28
 
29
29
  **Starting with Version 3.x these Nodes need Node-RED >= 1.0 to work correctly**
30
30
 
31
+ ## openccu-lite
32
+
33
+ [openccu-lite](https://github.com/hobbyquaker/openccu-lite) is a Homematic CCU firmware based on OpenCCU **without
34
+ ReGaHSS**. The interface processes (`rfd`, `hs485d`, `hmipserver`) are the same, so everything these nodes do over
35
+ BINRPC/XMLRPC works unchanged. Device, channel, room and function names come from the system's metadata API instead of
36
+ ReGaHSS; the connection node detects this at start (`GET /api/meta/v1/version`), nothing has to be configured, and
37
+ `msg.channelName`, `msg.rooms`, `msg.functions` and the room/function filters keep their shape.
38
+
39
+ - Node-RED **on the system** (RedMatic from its addon catalogue): the system's own read-only token is used, nothing to do.
40
+ - Node-RED **elsewhere**: an API token of the system goes into the connection node's **openccu-lite token** field.
41
+ - **System variables, programs and HM-Script do not exist there.** The `ccu-sysvar`, `ccu-program`, `ccu-script` and
42
+ `ccu-poll` nodes stay in the flows and answer every message with an error.
43
+
31
44
  ## Home Assistant
32
45
 
33
46
  The `ccu-homeassistant` node publishes
@@ -5,16 +5,20 @@
5
5
  * addon started before the interfaces, a daemon restart) used to leave the
6
6
  * interface without events: rpcCheckInit only runs with cached devices and
7
7
  * then waits pingTimeout (600 s for HmIP-RF). Now a failed init schedules its
8
- * own retry, 2 s, 4 s, 8 s, 16 s and then every 30 s, until it succeeds or the
9
- * node is closed.
8
+ * own retry, 1 s, 2 s, 4 s, 8 s and then every 15 s (task 12), until it
9
+ * succeeds or the node is closed.
10
10
  *
11
- * While the process is unreachable the interface is "waiting": one warn line,
12
- * then debug. A fault answer (the process is up and refuses the init) stays an
13
- * error, logged once per distinct fault.
11
+ * While the process is unreachable the interface is "waiting": one line, then
12
+ * debug. Before the interface was ever connected, a refused connection is
13
+ * info - the process is still starting (openccu-lite's early start starts
14
+ * Node-RED before rfd and hmipserver) - and every other transport error, or a
15
+ * refusal after it had been connected, is a warning. A fault answer (the
16
+ * process is up and refuses the init) stays an error, logged once per
17
+ * distinct fault.
14
18
  */
15
19
 
16
- const RETRY_DELAYS = [2000, 4000, 8000, 16000];
17
- const RETRY_MAX_DELAY = 30000;
20
+ const RETRY_DELAYS = [1000, 2000, 4000, 8000];
21
+ const RETRY_MAX_DELAY = 15000;
18
22
 
19
23
  /**
20
24
  * The delay before the next attempt.
@@ -39,6 +43,15 @@ function isFault(error) {
39
43
  return Boolean(error) && typeof error === 'object' && error.faultCode !== undefined;
40
44
  }
41
45
 
46
+ /**
47
+ * Whether nothing listens on the port (yet): the process is not started.
48
+ * @param {*} error
49
+ * @returns {boolean}
50
+ */
51
+ function isRefused(error) {
52
+ return Boolean(error) && (error.code === 'ECONNREFUSED' || /ECONNREFUSED/.test(String(error.message || '')));
53
+ }
54
+
42
55
  /**
43
56
  * A short reason for the log line, e.g. "connect ECONNREFUSED".
44
57
  * @param {*} error
@@ -77,6 +90,7 @@ class InitRetry {
77
90
  this.timers = timers || {setTimeout, clearTimeout};
78
91
  this.timer = null;
79
92
  this.stopped = false;
93
+ this.everConnected = false;
80
94
  this.reset();
81
95
  }
82
96
 
@@ -122,7 +136,18 @@ class InitRetry {
122
136
  );
123
137
  } else {
124
138
  this.warned = true;
125
- this.logger.warn(this.iface + ' not reachable yet (' + reason + '), retrying');
139
+ if (!this.everConnected && isRefused(error)) {
140
+ this.logger.info(
141
+ this.iface +
142
+ ' not listening yet (' +
143
+ reason +
144
+ '), waiting for it - retrying in ' +
145
+ seconds +
146
+ ' s',
147
+ );
148
+ } else {
149
+ this.logger.warn(this.iface + ' not reachable yet (' + reason + '), retrying');
150
+ }
126
151
  }
127
152
 
128
153
  this.onState('waiting');
@@ -160,6 +185,7 @@ class InitRetry {
160
185
  this.logger.info(this.iface + ' connected after ' + (this.failures + 1) + ' attempts');
161
186
  }
162
187
 
188
+ this.everConnected = true;
163
189
  this.reset();
164
190
  this.onState('connected');
165
191
  }
@@ -178,4 +204,4 @@ class InitRetry {
178
204
  }
179
205
  }
180
206
 
181
- module.exports = {InitRetry, retryDelay, isFault, describeError, RETRY_DELAYS, RETRY_MAX_DELAY};
207
+ module.exports = {InitRetry, retryDelay, isFault, isRefused, describeError, RETRY_DELAYS, RETRY_MAX_DELAY};
@@ -17,7 +17,15 @@
17
17
  Rather than chasing lighttpd's include chain, ask the kernel: a
18
18
  listener on the direct port is exactly the condition that makes using
19
19
  it correct. /proc/net/tcp is a plain file, so this stays synchronous -
20
- the interface table is built in the connection node's constructor. */
20
+ the interface table is built in the connection node's constructor.
21
+
22
+ A listener alone misses a start before rfd: openccu-lite starts an addon
23
+ that copes with it before the interface processes (its early start), so
24
+ the ports are not open yet and the connection went through the proxy for
25
+ good. The CCU's own interface list, /etc/config/InterfacesList.xml, is
26
+ there from the first second on every CCU firmware (CCU3, OpenCCU,
27
+ openccu-lite), so a loopback host with that file is the CCU we run on as
28
+ well. */
21
29
 
22
30
  const fs = require('fs');
23
31
 
@@ -26,6 +34,9 @@ const LOCAL_PROBE_PORTS = [32001, 31999];
26
34
 
27
35
  const PROC_NET_TCP = ['/proc/net/tcp', '/proc/net/tcp6'];
28
36
 
37
+ /** The CCU firmware's list of its interface processes. */
38
+ const INTERFACES_LIST = '/etc/config/InterfacesList.xml';
39
+
29
40
  /** /proc/net/tcp connection state 0A = TCP_LISTEN. */
30
41
  const TCP_LISTEN = '0A';
31
42
 
@@ -67,6 +78,7 @@ function listeningPorts(files) {
67
78
  * @param {string} host the configured CCU host
68
79
  * @param {object} [options]
69
80
  * @param {string[]} [options.files] /proc files to read, for tests
81
+ * @param {string} [options.interfacesList] the CCU's interface list, for tests
70
82
  * @returns {boolean}
71
83
  */
72
84
  function isLocalCcu(host, options) {
@@ -75,7 +87,13 @@ function isLocalCcu(host, options) {
75
87
  }
76
88
 
77
89
  const ports = listeningPorts(options && options.files);
78
- return LOCAL_PROBE_PORTS.some((port) => ports.has(port));
90
+ if (LOCAL_PROBE_PORTS.some((port) => ports.has(port))) {
91
+ return true;
92
+ }
93
+
94
+ // the interface processes are not up yet (a start before them): the
95
+ // firmware's interface list still says this is the CCU
96
+ return fs.existsSync((options && options.interfacesList) || INTERFACES_LIST);
79
97
  }
80
98
 
81
- module.exports = {isLocalCcu, listeningPorts, LOCAL_PROBE_PORTS};
99
+ module.exports = {isLocalCcu, listeningPorts, LOCAL_PROBE_PORTS, INTERFACES_LIST};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-ccu",
3
- "version": "4.4.4",
3
+ "version": "4.4.5",
4
4
  "license": "MIT",
5
5
  "main": "none",
6
6
  "keywords": [