node-red-contrib-ccu 4.6.0 → 4.6.2
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 +49 -28
- package/nodes/ccu-connection.js +15 -0
- package/nodes/lib/hadiscovery.js +127 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ 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.6.2 (2026-10-23)
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **One event a few times a day was lost on the way from hmipserver, which
|
|
13
|
+
logged a `RpcIOException … Connection reset` for it.** hmipserver delivers
|
|
14
|
+
its events through a pooled HTTP keep-alive client whose idle timeout is
|
|
15
|
+
1200 s and sends the next event on a pooled connection without checking it;
|
|
16
|
+
the XML-RPC callback server closed an idle connection after Node's default
|
|
17
|
+
5 s, so at the idle boundary the event went out onto a socket this side was
|
|
18
|
+
closing. The callback server now keeps an idle connection open for 1300 s,
|
|
19
|
+
longer than the client pool does, so it is always the client that lets go.
|
|
20
|
+
|
|
21
|
+
## 4.6.1 (2026-10-22)
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **Home Assistant logged an error for every published device: the
|
|
26
|
+
maintenance channel's `STICKY_UNREACH`** went out as a `switch` with
|
|
27
|
+
`device_class: problem`, a class only `binary_sensor` knows, and Home
|
|
28
|
+
Assistant refused that component ("expected SwitchDeviceClass or one of
|
|
29
|
+
'outlet', 'switch'") while it created the device and its other entities. `STICKY_UNREACH` is no longer announced at all
|
|
30
|
+
(UNREACH covers it), and a device class a platform does not
|
|
31
|
+
accept is dropped from any component before it goes out. A test now checks
|
|
32
|
+
every component of every device the shipped paramsets know against Home
|
|
33
|
+
Assistant's device classes per platform.
|
|
34
|
+
|
|
8
35
|
## 4.6.0 (2026-10-22)
|
|
9
36
|
|
|
10
37
|
### Added
|
|
@@ -29,7 +56,7 @@ append commits automatically).
|
|
|
29
56
|
### Fixed
|
|
30
57
|
|
|
31
58
|
- **Home Assistant showed every entity of a device as "unavailable" for
|
|
32
|
-
good** (
|
|
59
|
+
good** (reported by mail). The discovery configs name the device's
|
|
33
60
|
`…:0/UNREACH` topic as its availability, and Home Assistant treats an
|
|
34
61
|
entity as unavailable until that topic delivers a value. Only `ccu-mqtt`
|
|
35
62
|
published UNREACH: when it changed, or once in its start-up burst of
|
|
@@ -49,8 +76,7 @@ append commits automatically).
|
|
|
49
76
|
|
|
50
77
|
### Fixed
|
|
51
78
|
|
|
52
|
-
- **No HmIP-RF events for up to 10 minutes after hmipserver restarted
|
|
53
|
-
(B-37). hmipserver keeps its subscribers over a restart: it calls
|
|
79
|
+
- **No HmIP-RF events for up to 10 minutes after hmipserver restarted**. hmipserver keeps its subscribers over a restart: it calls
|
|
54
80
|
`listDevices` and `newDevices` on them and answers their pings, but
|
|
55
81
|
delivers no event to such a kept subscription until it is subscribed
|
|
56
82
|
afresh. The liveness ping saw its PONG and was content, so the
|
|
@@ -65,7 +91,7 @@ append commits automatically).
|
|
|
65
91
|
### Fixed
|
|
66
92
|
|
|
67
93
|
- **A burst of `getParamsetDescription Error: XML-RPC fault: Invalid device`
|
|
68
|
-
at the first start after an update
|
|
94
|
+
at the first start after an update**. A device deleted from the CCU
|
|
69
95
|
while Node-RED was not running stayed in the cached device table
|
|
70
96
|
(`ccu_<host>.json`) and in the values cache (`ccu_values_<host>.json`).
|
|
71
97
|
At the start its paramset descriptions were queued from the cached table;
|
|
@@ -90,7 +116,7 @@ append commits automatically).
|
|
|
90
116
|
### Added
|
|
91
117
|
|
|
92
118
|
- **TLS and authentication for every connection to a CCU on the network**
|
|
93
|
-
(
|
|
119
|
+
(#27). With _TLS/SSL_ on, all XML-RPC interfaces are reached over
|
|
94
120
|
the CCU's TLS ports (42000, 42001, 42010, 49292; CCU-Jack 2122), the ReGaHSS
|
|
95
121
|
over 48181 and openccu-lite's metadata api over 443; with _Authentication_
|
|
96
122
|
on, the username and password go out as HTTP basic auth on every call, the
|
|
@@ -111,7 +137,7 @@ append commits automatically).
|
|
|
111
137
|
certificates_, the CCU's self-signed certificate is refused, as before.
|
|
112
138
|
|
|
113
139
|
- **One topic template in every node that emits, the value node's input
|
|
114
|
-
takes the emitted shape** (
|
|
140
|
+
takes the emitted shape** (#39 @psi-4ward). The `get value` node has a _Topic_
|
|
115
141
|
field now, the same `${…}` template as rpc-event, value, sysvar, program,
|
|
116
142
|
rpc and script; empty (the default, and every existing flow) leaves
|
|
117
143
|
`msg.topic` as it came in. The value node's input accepts a datapoint
|
|
@@ -125,7 +151,7 @@ append commits automatically).
|
|
|
125
151
|
rpc-event, value and get value the placeholders.
|
|
126
152
|
|
|
127
153
|
- **Help texts for every node in German and English, as locale files**
|
|
128
|
-
(
|
|
154
|
+
(#58). The help moved from inline blocks in the node files into
|
|
129
155
|
`nodes/locales/de/` and `nodes/locales/en-US/`, the layout Node-RED
|
|
130
156
|
documents for node help: the runtime serves one language's text per
|
|
131
157
|
request through the same lookup as its own nodes (`de` and `de-DE` both
|
|
@@ -143,7 +169,7 @@ append commits automatically).
|
|
|
143
169
|
### Fixed
|
|
144
170
|
|
|
145
171
|
- **Writing one party-mode datapoint of an HmIP thermostat reset the other
|
|
146
|
-
two** (
|
|
172
|
+
two** (#156 @ben-5555, #161 @lolli78). An HmIP eTRV/WTH takes
|
|
147
173
|
`PARTY_TIME_START`, `PARTY_TIME_END` and `PARTY_SET_POINT_TEMPERATURE` only
|
|
148
174
|
together: a `setValue` of one of them put the others back to
|
|
149
175
|
`1999_11_30 00:00`, so party or holiday mode could not be set from the
|
|
@@ -155,7 +181,7 @@ append commits automatically).
|
|
|
155
181
|
Date, an ISO string or epoch milliseconds; they are formatted for the device
|
|
156
182
|
in local time. Any other datapoint of the channel is written as before. The
|
|
157
183
|
older HM thermostats (`PARTY_MODE_SUBMIT`) are not covered.
|
|
158
|
-
- **A callback call with an unknown interface id ended Node-RED
|
|
184
|
+
- **A callback call with an unknown interface id ended Node-RED**.
|
|
159
185
|
Anything on the system can connect to the connection's callback ports on
|
|
160
186
|
the loopback; a `system.listMethods` without parameters, an `event` with an
|
|
161
187
|
id that is not one of ours, or a `newDevices` list with a malformed entry
|
|
@@ -165,7 +191,7 @@ append commits automatically).
|
|
|
165
191
|
`system.listMethods`) and logged at debug; a throw inside a handler is
|
|
166
192
|
logged as an error and answered instead of ending the process.
|
|
167
193
|
- **A metadata api detection that timed out at start left the connection in
|
|
168
|
-
ReGa mode for good
|
|
194
|
+
ReGa mode for good**. On openccu-lite the connection asks
|
|
169
195
|
`GET /api/meta/v1/version` once at start; when that probe ran into its 5 s
|
|
170
196
|
timeout - on a Raspberry Pi 3 Node-RED's own start blocks the event loop
|
|
171
197
|
long enough for that at every start, on a busy system now and then - the
|
|
@@ -176,8 +202,7 @@ append commits automatically).
|
|
|
176
202
|
the detection is repeated after 1, 2, 4, 8 s and then every 15 s (up to 20
|
|
177
203
|
times, then the five-minute rule again), and the failing ReGa poll asks for
|
|
178
204
|
a re-detection too. A CCU's 404 is still a conclusive "no".
|
|
179
|
-
- **After an hmipserver restart HmIP-RF events stopped for up to ten minutes
|
|
180
|
-
(B-29). hmipserver forgets its clients when it restarts and does not tell
|
|
205
|
+
- **After an hmipserver restart HmIP-RF events stopped for up to ten minutes**. hmipserver forgets its clients when it restarts and does not tell
|
|
181
206
|
them: the `init` had succeeded, nothing failed, and the events simply stopped
|
|
182
207
|
until the 600 s silence timeout called `init` again. The connection now
|
|
183
208
|
pings HmIP-RF after 30 s without an event and, when no event (the PONG)
|
|
@@ -188,7 +213,7 @@ append commits automatically).
|
|
|
188
213
|
events are back well within a minute. An interface that never delivered an
|
|
189
214
|
event since its `init` is not re-subscribed on a missing PONG alone.
|
|
190
215
|
- **A queued write of the same set point was dropped after the actuator had
|
|
191
|
-
been operated by hand** (
|
|
216
|
+
been operated by hand** (#151 @vigeland). The value node's _Queue_ compared the
|
|
192
217
|
new value with the value cache, and the cache of a control channel
|
|
193
218
|
(HmIP-FROLL/BROLL `:4`, the status lives in `:3`) holds the echo of our own
|
|
194
219
|
last write for good - a blind moved by hand reports no new set point there,
|
|
@@ -202,7 +227,7 @@ append commits automatically).
|
|
|
202
227
|
### Security
|
|
203
228
|
|
|
204
229
|
- **The CCU password and the openccu-lite token were stored in plain text in
|
|
205
|
-
`flows.json`** and went out with every exported flow
|
|
230
|
+
`flows.json`** and went out with every exported flow. Both are
|
|
206
231
|
Node-RED credentials now (`flows_cred.json`, encrypted, never exported).
|
|
207
232
|
A flow written before this version still carries them as plain properties:
|
|
208
233
|
the connection uses them once, moves them into the credentials and warns;
|
|
@@ -211,8 +236,7 @@ append commits automatically).
|
|
|
211
236
|
|
|
212
237
|
### Changed
|
|
213
238
|
|
|
214
|
-
- **The device table follows the CCU, and can be re-read by hand** (
|
|
215
|
-
#146 @guny74, #181 @vore). A device or channel the CCU reports with another TYPE,
|
|
239
|
+
- **The device table follows the CCU, and can be re-read by hand** (#146 @guny74, #181 @vore). A device or channel the CCU reports with another TYPE,
|
|
216
240
|
VERSION or FIRMWARE than the cache holds replaces the cached entry, leaves
|
|
217
241
|
its old type list and gets its datapoints fetched again - a channel whose
|
|
218
242
|
type changed in place kept its old datapoints before, until the cache file
|
|
@@ -222,14 +246,14 @@ append commits automatically).
|
|
|
222
246
|
reports what changed. The editor's channel pickers no longer empty the
|
|
223
247
|
configured channel when the connection is still initialising: they show
|
|
224
248
|
_loading device data_ and ask again.
|
|
225
|
-
- **An interface without devices is watched too
|
|
249
|
+
- **An interface without devices is watched too**. The
|
|
226
250
|
ping watchdog returned early for an interface with no devices in the
|
|
227
251
|
cache, so such an interface never pinged and never re-initialised after a
|
|
228
252
|
CCU reboot. It pings now, and re-initialises after the ping timeout once
|
|
229
253
|
it has ever delivered an event (a PONG counts) - a process that answers no
|
|
230
254
|
ping at all is not asked to `init` every timeout.
|
|
231
255
|
- **The `listDevices` answer to hmipserver lists the CCU's virtual remote
|
|
232
|
-
control (HmIP-RCV-1) again
|
|
256
|
+
control (HmIP-RCV-1) again**. It was left out since CCU3 firmware
|
|
233
257
|
3.43.15 (2019), whose hmipserver failed on an answer that held it; current
|
|
234
258
|
hmipservers take it, and without it every `init` made hmipserver log a
|
|
235
259
|
_handleIDMigration_ warning and send the whole virtual remote (52
|
|
@@ -247,8 +271,7 @@ append commits automatically).
|
|
|
247
271
|
proxy ports (XML-RPC instead of BIN-RPC for BidCos) until the next restart.
|
|
248
272
|
A loopback host on a system that has the CCU's interface list
|
|
249
273
|
(`/etc/config/InterfacesList.xml`) is now taken as local as well.
|
|
250
|
-
- **Waiting for an interface process that is still starting is no warning
|
|
251
|
-
(task 12). Before the interface was ever connected, a refused connection
|
|
274
|
+
- **Waiting for an interface process that is still starting is no warning**. Before the interface was ever connected, a refused connection
|
|
252
275
|
is one info line (_"HmIP-RF not listening yet (connect ECONNREFUSED),
|
|
253
276
|
waiting for it - retrying in 1 s"_); another transport error, or a refused
|
|
254
277
|
connection after the interface had been connected, is still a warning, and
|
|
@@ -257,7 +280,7 @@ append commits automatically).
|
|
|
257
280
|
### Changed
|
|
258
281
|
|
|
259
282
|
- **A failed `init` is retried after 1 s, 2 s, 4 s and 8 s, then every 15 s**
|
|
260
|
-
(
|
|
283
|
+
(was 2, 4, 8, 16 s, then every 30 s), the schedule of hm2mqtt.js and
|
|
261
284
|
the Homematic Manager.
|
|
262
285
|
|
|
263
286
|
## 4.4.4 (2026-09-17)
|
|
@@ -265,7 +288,7 @@ append commits automatically).
|
|
|
265
288
|
### Fixed
|
|
266
289
|
|
|
267
290
|
- **An interface stayed without events for minutes, or for good, when its
|
|
268
|
-
process did not answer the first `init
|
|
291
|
+
process did not answer the first `init`**. A failed `init` at
|
|
269
292
|
start (connection refused, timeout) was only retried by the ping
|
|
270
293
|
supervision, which does nothing without cached devices and waits the ping
|
|
271
294
|
timeout otherwise: 60 s, and 600 s for HmIP-RF. A Node-RED that starts
|
|
@@ -283,8 +306,7 @@ append commits automatically).
|
|
|
283
306
|
|
|
284
307
|
### Fixed
|
|
285
308
|
|
|
286
|
-
- **Node-RED at 100 % CPU and unresponsive while ReGaHSS is unreachable
|
|
287
|
-
(B-27). binrpc up to 4.2.0 armed a new reconnect timer for both the `error`
|
|
309
|
+
- **Node-RED at 100 % CPU and unresponsive while ReGaHSS is unreachable**. binrpc up to 4.2.0 armed a new reconnect timer for both the `error`
|
|
288
310
|
and the `close` event of every refused connect, so the number of attempts to
|
|
289
311
|
the ReGaHSS port 31999 doubled every 2.5 s without bound: about 7,000 refused
|
|
290
312
|
connects per second, 30,000 pending timers and 68 million connects in under
|
|
@@ -302,7 +324,7 @@ append commits automatically).
|
|
|
302
324
|
### Fixed
|
|
303
325
|
|
|
304
326
|
- **`getRegaVariables Error: socket hang up` every few minutes on a CCU**
|
|
305
|
-
(
|
|
327
|
+
([RedMatic #601](https://github.com/rdmtc/RedMatic/issues/601)
|
|
306
328
|
follow-up). Since 4.3.0 a local install talks to ReGaHSS itself on port 8183. ReGaHSS answers HTTP/1.1 without `Connection: close` — and then
|
|
307
329
|
closes the connection 0.4–7 ms after every response (measured on a
|
|
308
330
|
CCU3 3.89.8, median 0.55 ms). `homematic-rega` 2.0 (since 4.0.0) sent
|
|
@@ -359,7 +381,7 @@ hang up` — became an **unhandled rejection**, which Node >= 15 turns
|
|
|
359
381
|
|
|
360
382
|
### Added
|
|
361
383
|
|
|
362
|
-
- **Dynamic node configuration through `msg.config
|
|
384
|
+
- **Dynamic node configuration through `msg.config`**. Put
|
|
363
385
|
an object in `msg.config` and its properties override that node's own
|
|
364
386
|
configuration for that one message; the stored configuration is never
|
|
365
387
|
modified, so the next message starts from the dialog settings again.
|
|
@@ -434,7 +456,6 @@ hang up` — became an **unhandled rejection**, which Node >= 15 turns
|
|
|
434
456
|
`/etc/lighttpd/conf.d/proxy.conf` for a port number; on current
|
|
435
457
|
firmware that file is a one-line include and the port markers live two
|
|
436
458
|
levels down. It now looks for a listener on the direct port instead.
|
|
437
|
-
(Roadmap B-4)
|
|
438
459
|
- Writing several system variables at once left all but the first
|
|
439
460
|
invisible until the next scheduled ReGa poll (30 s by default): the
|
|
440
461
|
immediate re-poll each write triggers was dropped while another poll
|
|
@@ -466,7 +487,7 @@ hang up` — became an **unhandled rejection**, which Node >= 15 turns
|
|
|
466
487
|
presence, smoke, water), event (keys), lock (KeyMatic), sensor (energy,
|
|
467
488
|
weather, maintenance) plus, optionally, disabled-by-default entities for
|
|
468
489
|
every other datapoint. Unchecking a device removes it from Home
|
|
469
|
-
Assistant on the next deploy.
|
|
490
|
+
Assistant on the next deploy.
|
|
470
491
|
- ccu-mqtt: the setValue topic accepts Home Assistant's command words —
|
|
471
492
|
`OPEN`, `CLOSE`, `STOP`, `ON`, `OFF` on LEVEL and `AUTO-MODE`,
|
|
472
493
|
`MANU-MODE`, `BOOST-MODE`, `COMFORT-MODE`, `LOWERING-MODE` on the
|
package/nodes/ccu-connection.js
CHANGED
|
@@ -21,6 +21,10 @@ const {InitRetry, retryDelay} = require('./lib/initretry.js');
|
|
|
21
21
|
const xmlrpc = require('homematic-xmlrpc');
|
|
22
22
|
const binrpc = require('binrpc');
|
|
23
23
|
|
|
24
|
+
/** how long the XML-RPC callback server keeps an idle keep-alive connection open: longer than
|
|
25
|
+
* hmipserver's HTTP client pool (1200 s), so the pool never reuses a connection this side closed */
|
|
26
|
+
const KEEP_ALIVE_TIMEOUT_MS = 1300 * 1000;
|
|
27
|
+
|
|
24
28
|
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
25
29
|
|
|
26
30
|
// B-37: how long after our init the process's listDevices/newDevices still belong to it -
|
|
@@ -2862,6 +2866,17 @@ module.exports = function (RED) {
|
|
|
2862
2866
|
this.logger.info(protocol === 'binrpc' ? 'binrpc' : 'xmlrpc', 'server listening on', url);
|
|
2863
2867
|
this.serverError[iface] = null;
|
|
2864
2868
|
});
|
|
2869
|
+
if (this.servers[protocol].httpServer) {
|
|
2870
|
+
// hmipserver delivers its events through a pooled HTTP keep-alive client whose idle
|
|
2871
|
+
// timeout is 1200 s, and sends the next event on a pooled connection without
|
|
2872
|
+
// checking it. Node closes an idle keep-alive connection after 5 s by default, so
|
|
2873
|
+
// at the idle boundary the two raced: the event went out onto a socket this side was
|
|
2874
|
+
// closing, hmipserver logged "Connection reset" and the event was lost, a few times a
|
|
2875
|
+
// day. The callback server keeps an idle connection longer than the client pool
|
|
2876
|
+
// does, so it is always the client that lets go.
|
|
2877
|
+
this.servers[protocol].httpServer.keepAliveTimeout = KEEP_ALIVE_TIMEOUT_MS;
|
|
2878
|
+
this.servers[protocol].httpServer.headersTimeout = KEEP_ALIVE_TIMEOUT_MS + 5000;
|
|
2879
|
+
}
|
|
2865
2880
|
|
|
2866
2881
|
// Todo binrpc module: emit error event on server object to eliminate the reach-in
|
|
2867
2882
|
const errorEmitter = protocol === 'binrpc' ? this.servers[protocol].server : this.servers[protocol];
|
package/nodes/lib/hadiscovery.js
CHANGED
|
@@ -19,6 +19,122 @@ const TILT_TYPES = /BLIND/;
|
|
|
19
19
|
/** Platforms without a state topic. */
|
|
20
20
|
const STATELESS = new Set(['button', 'notify', 'scene', 'tag']);
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* The device classes Home Assistant accepts per platform (B-39). A component whose class is not in
|
|
24
|
+
* its platform's list is refused by Home Assistant together with its error log line, so `e()` drops
|
|
25
|
+
* such a class and the unit tests check every component of every known device against this table.
|
|
26
|
+
* Platforms missing here (light, climate, lock, select) take no device class at all.
|
|
27
|
+
*/
|
|
28
|
+
const SENSOR_CLASSES = [
|
|
29
|
+
'absolute_humidity',
|
|
30
|
+
'apparent_power',
|
|
31
|
+
'aqi',
|
|
32
|
+
'area',
|
|
33
|
+
'atmospheric_pressure',
|
|
34
|
+
'battery',
|
|
35
|
+
'blood_glucose_concentration',
|
|
36
|
+
'carbon_dioxide',
|
|
37
|
+
'carbon_monoxide',
|
|
38
|
+
'conductivity',
|
|
39
|
+
'current',
|
|
40
|
+
'data_rate',
|
|
41
|
+
'data_size',
|
|
42
|
+
'distance',
|
|
43
|
+
'duration',
|
|
44
|
+
'energy',
|
|
45
|
+
'energy_distance',
|
|
46
|
+
'energy_storage',
|
|
47
|
+
'frequency',
|
|
48
|
+
'gas',
|
|
49
|
+
'humidity',
|
|
50
|
+
'illuminance',
|
|
51
|
+
'irradiance',
|
|
52
|
+
'moisture',
|
|
53
|
+
'monetary',
|
|
54
|
+
'nitrogen_dioxide',
|
|
55
|
+
'nitrogen_monoxide',
|
|
56
|
+
'nitrous_oxide',
|
|
57
|
+
'ozone',
|
|
58
|
+
'ph',
|
|
59
|
+
'pm1',
|
|
60
|
+
'pm10',
|
|
61
|
+
'pm25',
|
|
62
|
+
'pm4',
|
|
63
|
+
'power',
|
|
64
|
+
'power_factor',
|
|
65
|
+
'precipitation',
|
|
66
|
+
'precipitation_intensity',
|
|
67
|
+
'pressure',
|
|
68
|
+
'radon',
|
|
69
|
+
'reactive_energy',
|
|
70
|
+
'reactive_power',
|
|
71
|
+
'signal_strength',
|
|
72
|
+
'sound_pressure',
|
|
73
|
+
'speed',
|
|
74
|
+
'sulphur_dioxide',
|
|
75
|
+
'temperature',
|
|
76
|
+
'temperature_delta',
|
|
77
|
+
'volatile_organic_compounds',
|
|
78
|
+
'volatile_organic_compounds_parts',
|
|
79
|
+
'voltage',
|
|
80
|
+
'volume',
|
|
81
|
+
'volume_flow_rate',
|
|
82
|
+
'volume_storage',
|
|
83
|
+
'water',
|
|
84
|
+
'weight',
|
|
85
|
+
'wind_direction',
|
|
86
|
+
'wind_speed',
|
|
87
|
+
];
|
|
88
|
+
const DEVICE_CLASSES = {
|
|
89
|
+
binary_sensor: new Set([
|
|
90
|
+
'battery',
|
|
91
|
+
'battery_charging',
|
|
92
|
+
'carbon_monoxide',
|
|
93
|
+
'cold',
|
|
94
|
+
'connectivity',
|
|
95
|
+
'door',
|
|
96
|
+
'garage_door',
|
|
97
|
+
'gas',
|
|
98
|
+
'heat',
|
|
99
|
+
'light',
|
|
100
|
+
'lock',
|
|
101
|
+
'moisture',
|
|
102
|
+
'motion',
|
|
103
|
+
'moving',
|
|
104
|
+
'occupancy',
|
|
105
|
+
'opening',
|
|
106
|
+
'plug',
|
|
107
|
+
'power',
|
|
108
|
+
'presence',
|
|
109
|
+
'problem',
|
|
110
|
+
'running',
|
|
111
|
+
'safety',
|
|
112
|
+
'smoke',
|
|
113
|
+
'sound',
|
|
114
|
+
'tamper',
|
|
115
|
+
'update',
|
|
116
|
+
'vibration',
|
|
117
|
+
'window',
|
|
118
|
+
]),
|
|
119
|
+
sensor: new Set([...SENSOR_CLASSES, 'date', 'enum', 'timestamp', 'uptime']),
|
|
120
|
+
number: new Set(SENSOR_CLASSES),
|
|
121
|
+
switch: new Set(['outlet', 'switch']),
|
|
122
|
+
button: new Set(['identify', 'restart', 'update']),
|
|
123
|
+
cover: new Set(['awning', 'blind', 'curtain', 'damper', 'door', 'garage', 'gate', 'shade', 'shutter', 'window']),
|
|
124
|
+
event: new Set(['button', 'doorbell', 'motion']),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/** true when Home Assistant accepts `deviceClass` on `platform` */
|
|
128
|
+
function validDeviceClass(platform, deviceClass) {
|
|
129
|
+
return Boolean(DEVICE_CLASSES[platform] && DEVICE_CLASSES[platform].has(deviceClass));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Datapoints that are never announced (B-39; the maintainer, 2026-10-22: "don't publish
|
|
134
|
+
* STICKY_UNREACH to home assistant auto discovery. UNREACH is enough.").
|
|
135
|
+
*/
|
|
136
|
+
const NOT_ANNOUNCED = new Set(['STICKY_UNREACH']);
|
|
137
|
+
|
|
22
138
|
/** device classes that only binary_sensor knows — a sensor/number with one of them is rejected by HA */
|
|
23
139
|
const BINARY_ONLY_CLASSES = new Set([
|
|
24
140
|
'running',
|
|
@@ -267,7 +383,7 @@ function deviceBlock(ctx, iface, device) {
|
|
|
267
383
|
const e = (dp, platform, label, more = {}) => {
|
|
268
384
|
const stateless = STATELESS.has(platform);
|
|
269
385
|
const uid = more.uid || `${ch.index}_${dp}`;
|
|
270
|
-
|
|
386
|
+
const component = compact({
|
|
271
387
|
p: platform,
|
|
272
388
|
uniq_id: `${id}_${uid}`,
|
|
273
389
|
name: label,
|
|
@@ -276,6 +392,12 @@ function deviceBlock(ctx, iface, device) {
|
|
|
276
392
|
...(more.command && {cmd_t: scmd(dp)}),
|
|
277
393
|
...(more.extra || {}),
|
|
278
394
|
});
|
|
395
|
+
// a class the platform does not know makes HA refuse the component (B-39)
|
|
396
|
+
if (component.dev_cla !== undefined && !validDeviceClass(platform, component.dev_cla)) {
|
|
397
|
+
delete component.dev_cla;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return component;
|
|
279
401
|
};
|
|
280
402
|
|
|
281
403
|
const fallback = ch.index === 0 ? 'Maintenance' : `Channel ${ch.index}`;
|
|
@@ -659,7 +781,7 @@ function deviceBlock(ctx, iface, device) {
|
|
|
659
781
|
|
|
660
782
|
// generic layer: everything else in the channel's VALUES description
|
|
661
783
|
for (const [dp, d] of Object.entries(ch.description)) {
|
|
662
|
-
if (consumed.has(dp) || !d || typeof d !== 'object') {
|
|
784
|
+
if (consumed.has(dp) || NOT_ANNOUNCED.has(dp) || !d || typeof d !== 'object') {
|
|
663
785
|
continue;
|
|
664
786
|
}
|
|
665
787
|
|
|
@@ -879,6 +1001,9 @@ function discoveryMessages(ctx) {
|
|
|
879
1001
|
}
|
|
880
1002
|
|
|
881
1003
|
module.exports = {
|
|
1004
|
+
DEVICE_CLASSES,
|
|
1005
|
+
NOT_ANNOUNCED,
|
|
1006
|
+
validDeviceClass,
|
|
882
1007
|
discoveryId,
|
|
883
1008
|
discoveryTopic,
|
|
884
1009
|
ifaceOf,
|