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 +25 -0
- package/README.md +13 -0
- package/nodes/lib/initretry.js +35 -9
- package/nodes/lib/localccu.js +21 -3
- package/package.json +1 -1
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
|
package/nodes/lib/initretry.js
CHANGED
|
@@ -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,
|
|
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
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
|
17
|
-
const RETRY_MAX_DELAY =
|
|
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
|
-
|
|
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};
|
package/nodes/lib/localccu.js
CHANGED
|
@@ -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
|
-
|
|
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};
|