node-red-contrib-ccu 4.6.0 → 4.6.1
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 +15 -0
- package/nodes/lib/hadiscovery.js +127 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.1 (2026-10-22)
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Home Assistant logged an error for every published device: the
|
|
13
|
+
maintenance channel's `STICKY_UNREACH`** went out as a `switch` with
|
|
14
|
+
`device_class: problem`, a class only `binary_sensor` knows, and Home
|
|
15
|
+
Assistant refused that component ("expected SwitchDeviceClass or one of
|
|
16
|
+
'outlet', 'switch'") while it created the device and its other entities
|
|
17
|
+
(B-39). `STICKY_UNREACH` is no longer announced at all - the maintainer's
|
|
18
|
+
decision: "UNREACH is enough" -, and a device class a platform does not
|
|
19
|
+
accept is dropped from any component before it goes out. A test now checks
|
|
20
|
+
every component of every device the shipped paramsets know against Home
|
|
21
|
+
Assistant's device classes per platform.
|
|
22
|
+
|
|
8
23
|
## 4.6.0 (2026-10-22)
|
|
9
24
|
|
|
10
25
|
### Added
|
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,
|