node-red-contrib-ccu 4.5.3 → 4.6.0

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,25 @@ 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.0 (2026-10-22)
9
+
10
+ ### Added
11
+
12
+ - **A device is created in Home Assistant in the room of its channels.** The
13
+ discovery config carries the room as the device's `suggested_area` (task
14
+ 18). It did so before only when every channel of the device lay in one and
15
+ the same room: a device with channels in two rooms got no area, and on
16
+ openccu-lite, whose rooms can be nested and whose room list of a channel
17
+ names the parents too (`Küche`, `Erdgeschoss`), no device got one at all.
18
+ Now the area is the room shared by all channels of the device that lie in a
19
+ room, otherwise the room of the lowest-numbered channel that lies in one; a
20
+ channel in several rooms counts with its first (on openccu-lite the most
21
+ specific), and a room of the device itself comes first. The rooms are the
22
+ ReGaHSS's on a CCU and the system's metadata's on openccu-lite. New option
23
+ _Suggest the channel's room as the device's area_ on the node, on by
24
+ default. Home Assistant applies the suggestion only when it first creates
25
+ the device; a room changed later stays as assigned in Home Assistant.
26
+
8
27
  ## 4.5.3 (2026-10-05)
9
28
 
10
29
  ### Fixed
package/README.md CHANGED
@@ -81,6 +81,15 @@ publishes its current value (retained) right after the discovery configs and eve
81
81
  is available without ever having been unreachable. Any message on the node's input republishes both; the example
82
82
  wires Home Assistant's birth message (`homeassistant/status`) to it, so a Home Assistant restart gets them again.
83
83
 
84
+ The device is created in the room of its channels: the discovery config carries it as the device's `suggested_area`
85
+ (Home Assistant's areas are per device, the CCU's rooms per channel). The rule: the room shared by all channels of the
86
+ device that lie in a room, otherwise the room of the lowest-numbered channel that lies in one; a channel in several
87
+ rooms counts with its first - on openccu-lite, where rooms can be nested, with the most specific one - and a room of
88
+ the device itself comes first. The rooms come from the ReGaHSS on a CCU and from the system's metadata on openccu-lite.
89
+ Home Assistant applies the suggestion only when it first creates the device; a room changed later on the CCU stays as
90
+ assigned in Home Assistant (change it there, or delete the device in Home Assistant and republish the discovery). The
91
+ node's option _Suggest the channel's room as the device's area_ (on by default) switches it off.
92
+
84
93
  ## Dynamic configuration
85
94
 
86
95
  Most nodes can be reconfigured per message: put an object in `msg.config` and its
@@ -7,6 +7,7 @@
7
7
  mqttNode: {value: '', required: true},
8
8
  prefix: {value: 'homeassistant'},
9
9
  generic: {value: true},
10
+ area: {value: true},
10
11
  devices: {value: {}},
11
12
  },
12
13
  inputs: 1,
@@ -267,6 +268,15 @@
267
268
  </label>
268
269
  </div>
269
270
  </div>
271
+ <div class="form-row">
272
+ <label for="node-input-area"></label>
273
+ <div style="width: 70%; display: inline-block; vertical-align: text-top;">
274
+ <label class="ccu-checkbox">
275
+ <input type="checkbox" id="node-input-area">
276
+ <span>Suggest the channel's room as the device's area</span>
277
+ </label>
278
+ </div>
279
+ </div>
270
280
 
271
281
  <div class="form-row">
272
282
  <b>Devices published to Home Assistant</b>
@@ -37,6 +37,7 @@ module.exports = function (RED) {
37
37
  this.mqttNodeId = config.mqttNode;
38
38
  this.prefix = String(config.prefix || 'homeassistant').replace(/\/+$/, '');
39
39
  this.generic = config.generic !== false;
40
+ this.area = config.area !== false;
40
41
  this.devices = config.devices || {};
41
42
  this.selected = Object.keys(this.devices).filter(
42
43
  (a) => this.devices[a] && this.devices[a].enabled === true,
@@ -214,6 +215,7 @@ module.exports = function (RED) {
214
215
  origin: {name: pkg.name, sw: pkg.version, url: pkg.homepage},
215
216
  jsonPayloads: mqtt.payloadOutput !== 'plain',
216
217
  generic: this.generic,
218
+ area: this.area,
217
219
  devices: ccu.metadata.devices,
218
220
  selected: this.selected,
219
221
  description: (iface, address) => {
@@ -51,7 +51,9 @@ const BINARY_ONLY_CLASSES = new Set([
51
51
  * @property {string[]} selected device addresses to publish
52
52
  * @property {(iface: string, address: string) => object | undefined} description VALUES description of a channel
53
53
  * @property {(address: string) => string | undefined} channelName
54
- * @property {(address: string) => string[] | undefined} [rooms]
54
+ * @property {(address: string) => string[] | undefined} [rooms] the rooms of a channel (or device), the
55
+ * most specific first where rooms are nested (openccu-lite)
56
+ * @property {boolean} [area] the room becomes the device's `suggested_area` (default true, task 18)
55
57
  * @property {(iface: string, address: string, datapoint: string) => string} statusTopicFor
56
58
  * @property {(iface: string, address: string, datapoint: string) => string} setTopicFor
57
59
  */
@@ -184,6 +186,32 @@ function resolveVirtualReceivers(channels) {
184
186
  * @property {string} topic the status topic the discovery config names in `avty`
185
187
  */
186
188
 
189
+ /**
190
+ * The room Home Assistant is to suggest as the device's area (task 18). Home
191
+ * Assistant's area is per device, the CCU's rooms are per channel, so: a room
192
+ * every channel that lies in a room shares - the first one in the order of the
193
+ * lowest-numbered such channel - or, when they share none, the first room of
194
+ * the lowest-numbered channel that has one. A room of the device itself (an
195
+ * openccu-lite device may be in a room as a whole) comes first. Home Assistant
196
+ * applies `suggested_area` only when it creates the device; a room changed
197
+ * later stays as assigned in Home Assistant.
198
+ * @param {(address: string) => string[] | undefined} roomsOf
199
+ * @param {string} deviceAddress
200
+ * @param {Array<{ADDRESS: string}>} channels sorted by channel index
201
+ * @returns {string | undefined}
202
+ */
203
+ function suggestedArea(roomsOf, deviceAddress, channels) {
204
+ const lists = [deviceAddress, ...channels.map((ch) => ch.ADDRESS)]
205
+ .map((address) => roomsOf(address))
206
+ .filter((rooms) => Array.isArray(rooms) && rooms.length > 0);
207
+ if (lists.length === 0) {
208
+ return undefined;
209
+ }
210
+
211
+ const [first, ...rest] = lists;
212
+ return first.find((room) => rest.every((rooms) => rooms.includes(room))) || first[0];
213
+ }
214
+
187
215
  /**
188
216
  * Builds the discovery block of one device.
189
217
  * @param {DiscoveryContext} ctx
@@ -224,12 +252,7 @@ function deviceBlock(ctx, iface, device) {
224
252
  components[key] = e;
225
253
  };
226
254
 
227
- const rooms = new Set();
228
- for (const ch of channels) {
229
- for (const r of roomsOf(ch.ADDRESS) || []) {
230
- rooms.add(r);
231
- }
232
- }
255
+ const area = ctx.area === false ? undefined : suggestedArea(roomsOf, device.ADDRESS, channels);
233
256
 
234
257
  const maintenance = channels.find((ch) => ch.index === 0);
235
258
 
@@ -783,7 +806,7 @@ function deviceBlock(ctx, iface, device) {
783
806
  mf: 'eQ-3',
784
807
  mdl: device.TYPE,
785
808
  sw: device.FIRMWARE ? String(device.FIRMWARE) : undefined,
786
- sa: rooms.size === 1 ? [...rooms][0] : undefined,
809
+ sa: area,
787
810
  }),
788
811
  components,
789
812
  availability,
@@ -860,6 +883,7 @@ module.exports = {
860
883
  discoveryTopic,
861
884
  ifaceOf,
862
885
  resolveVirtualReceivers,
886
+ suggestedArea,
863
887
  deviceBlock,
864
888
  devicePayload,
865
889
  removalMessage,
@@ -28,4 +28,15 @@
28
28
  bis auf diesem Topic ein Wert ankommt. Deshalb veröffentlicht der Node direkt nach den
29
29
  Discovery-Konfigurationen den aktuellen UNREACH-Wert jedes Geräts (retained), fragt ihn dazu bei der
30
30
  CCU ab und sendet jede Änderung. Ein Gerät, das nie unerreichbar war, ist so trotzdem verfügbar.</p>
31
+ <h3>Raum und Bereich</h3>
32
+ <p>Home Assistant ordnet Geräte Bereichen zu, die CCU Kanäle Räumen. Der Node schlägt Home Assistant den
33
+ Raum der Kanäle als Bereich des Geräts vor (<code>suggested_area</code>): den Raum, den alle Kanäle des
34
+ Geräts teilen, die in einem Raum liegen, sonst den Raum des Kanals mit der niedrigsten Nummer, der in
35
+ einem liegt. Liegt ein Kanal in mehreren Räumen, zählt der erste; auf openccu-lite, wo Räume verschachtelt
36
+ sein können, der speziellste (das Zimmer vor dem Stockwerk), und ein Raum des Geräts selbst geht vor. Die
37
+ Räume kommen auf einer CCU aus der ReGaHSS, auf openccu-lite aus den Metadaten des Systems.</p>
38
+ <p>Home Assistant übernimmt den Vorschlag nur, wenn es das Gerät neu anlegt. Ein später auf der CCU
39
+ geänderter Raum bleibt in Home Assistant, wie er dort zugeordnet ist: dort ändern, oder das Gerät in Home
40
+ Assistant löschen und die Discovery erneut veröffentlichen. Die Option <i>Raum als Bereich vorschlagen</i>
41
+ schaltet den Vorschlag ab.</p>
31
42
  </script>
@@ -28,4 +28,15 @@
28
28
  a value arrives on that topic. So right after the discovery configs the node publishes each
29
29
  device's current UNREACH (retained), asking the CCU for it, and sends every change. A device that
30
30
  was never unreachable is available all the same.</p>
31
+ <h3>Room and area</h3>
32
+ <p>Home Assistant assigns devices to areas, the CCU assigns channels to rooms. The node suggests the
33
+ channels' room as the device's area (<code>suggested_area</code>): the room shared by all channels of
34
+ the device that lie in a room, otherwise the room of the lowest-numbered channel that lies in one. A
35
+ channel in several rooms counts with its first; on openccu-lite, where rooms can be nested, with the most
36
+ specific one (the room before the floor), and a room of the device itself comes first. The rooms come
37
+ from the ReGaHSS on a CCU and from the system's metadata on openccu-lite.</p>
38
+ <p>Home Assistant applies the suggestion only when it creates the device. A room changed later on the
39
+ CCU stays as assigned in Home Assistant: change it there, or delete the device in Home Assistant and
40
+ republish the discovery. The option <i>Suggest the channel's room as the device's area</i> switches the
41
+ suggestion off.</p>
31
42
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-ccu",
3
- "version": "4.5.3",
3
+ "version": "4.6.0",
4
4
  "license": "MIT",
5
5
  "main": "none",
6
6
  "keywords": [