@camstack/addon-provider-homematic 1.2.161 → 1.2.163

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/dist/index.js CHANGED
@@ -1,561 +1,11 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_addon = require("./addon.js");
3
- //#region src/homematic-device.ts
4
- /**
5
- * Persisted config every Homematic-backed device carries. `address` is
6
- * the CCU device address (e.g. `0001D3C99AB123`) — the dispatch key the
7
- * device uses to resolve its live `HmDevice` from the facade and to
8
- * filter inbound `valueChanged` events to its own datapoints.
9
- */
10
- var homematicDeviceSchema = require_addon.object({
11
- address: require_addon.string().min(1).describe("CCU device address"),
12
- /** The registry broker id this device belongs to (e.g. `hm_001`).
13
- * Absent on devices persisted before the multi-CCU migration; those
14
- * fall back to the primary broker via `homematicFacades.get(null)`. */
15
- brokerId: require_addon.string().optional()
16
- });
17
- /** Cold-start defaults so reads never miss a slice before the first push. */
18
- var COLD_SWITCH = {
19
- on: false,
20
- lastChangedAt: 0
21
- };
22
- var COLD_COVER = {
23
- state: "closed",
24
- position: null,
25
- tiltPosition: null,
26
- lastChangedAt: 0
27
- };
28
- var COLD_LOCK = {
29
- state: "unlocked",
30
- lastChangedAt: 0
31
- };
32
- var COLD_MOTION = {
33
- detected: false,
34
- lastDetectedAt: null,
35
- autoClearAfterMs: null
36
- };
37
- var COLD_CLIMATE = {
38
- mode: "auto",
39
- availableModes: [...[
40
- "off",
41
- "heat",
42
- "auto"
43
- ]],
44
- fanMode: "",
45
- availableFanModes: [],
46
- preset: "",
47
- availablePresets: ["boost"],
48
- target: null,
49
- targetHigh: null,
50
- targetLow: null,
51
- targetHumidity: null,
52
- currentTemp: null,
53
- currentHumidity: null,
54
- swingVertical: null,
55
- swingHorizontal: null,
56
- lastFetchedAt: 0
57
- };
58
- /**
59
- * The standard HmIP key-channel datapoint vocabulary, used VERBATIM (NO
60
- * normalization) as the `event-emitter` event types. These are the native
61
- * uppercase tokens a `KEY_TRANSCEIVER` / `KEY_TRANSMITTER` / `VIRTUAL_KEY`
62
- * / `KEY` channel emits on the facade `valueChanged` stream (via HA they
63
- * surface lowercased as `press_short` etc.; natively they are uppercase):
64
- * - PRESS_SHORT single short press
65
- * - PRESS_LONG long press (repeats while held on some firmware)
66
- * - PRESS_LONG_START long-press begin
67
- * - PRESS_LONG_RELEASE long-press end
68
- * - PRESS_CONT continuous-press tick while held
69
- * Used both as the seed vocabulary fallback and as the fire-detection set
70
- * (a non-key datapoint such as a maintenance LOW_BAT is ignored).
71
- */
72
- var HM_KEY_PRESS_DATAPOINTS = [
73
- "PRESS_SHORT",
74
- "PRESS_LONG",
75
- "PRESS_LONG_START",
76
- "PRESS_LONG_RELEASE",
77
- "PRESS_CONT"
78
- ];
79
- /** Set form for O(1) fire-detection membership tests. */
80
- var HM_KEY_PRESS_SET = new Set(HM_KEY_PRESS_DATAPOINTS);
81
- /** Cold-start event-emitter slice; vocabulary supplied per-device. */
82
- function coldEventEmitter(eventTypes) {
83
- return {
84
- eventTypes: [...eventTypes],
85
- lastEvent: null,
86
- eventCountSinceStart: 0
87
- };
88
- }
89
- /**
90
- * Collect the union of native `PRESS_*` datapoints a device's key channels
91
- * support, VERBATIM. Reads each key channel's `dataPoints` (when the
92
- * facade enumerated them) and keeps only the recognised press tokens. Falls
93
- * back to the standard HmIP key vocabulary when no datapoints were
94
- * enumerated, so the cap always advertises a non-empty vocabulary.
95
- */
96
- function keyEventVocabulary(keyChannels) {
97
- const found = /* @__PURE__ */ new Set();
98
- for (const channel of keyChannels) for (const dp of channel.dataPoints ?? []) if (HM_KEY_PRESS_SET.has(dp.parameter)) found.add(dp.parameter);
99
- if (found.size === 0) return HM_KEY_PRESS_DATAPOINTS;
100
- return HM_KEY_PRESS_DATAPOINTS.filter((dp) => found.has(dp));
101
- }
102
- /**
103
- * The CamStack cap names that are ACTUATORS — driven by the library's typed
104
- * custom entities (correct channel), NOT by the raw mapped channels. The
105
- * raw-channel registration loop skips these so they are not double-registered
106
- * against the wrong channel.
107
- */
108
- var ACTUATOR_CAP_NAMES = new Set([
109
- "switch",
110
- "cover",
111
- "light",
112
- "lock-control",
113
- "climate-control"
114
- ]);
115
- /**
116
- * Map a `nodehomematic` custom-entity `kind` to its CamStack actuator cap
117
- * name. The union is discriminated by `kind`; the exhaustive switch needs no
118
- * cast. Returns `null` for kinds with no actuator cap wired yet.
119
- */
120
- function actuatorCapForEntityKind(kind) {
121
- switch (kind) {
122
- case "switch": return "switch";
123
- case "cover":
124
- case "blind": return "cover";
125
- case "light": return "light";
126
- case "lock": return "lock-control";
127
- case "climate": return "climate-control";
128
- default: return null;
129
- }
130
- }
131
- /** Parse the numeric channel index suffix (`:N`) from a channel address.
132
- * Returns the raw suffix string (kept as a string for the event payload,
133
- * mirroring the CCU's `device:index` addressing). Null when absent. */
134
- function channelIndexFromAddress(channelAddress) {
135
- const idx = channelAddress.lastIndexOf(":");
136
- if (idx === -1) return null;
137
- const suffix = channelAddress.slice(idx + 1);
138
- return suffix.length > 0 ? suffix : null;
139
- }
140
- /**
141
- * One CamStack device per Homematic device. Maps the device's channels to
142
- * CamStack caps (via `homematic-mapping.ts`) and wires each cap's
143
- * get/set surface to the `nodehomematic` facade's typed helpers
144
- * (`switchTurnOn` / `coverSetPosition` / `climateSetTemperature` /
145
- * `lockLock`) and raw `getValue`/`setValue` for read-only sensors.
146
- *
147
- * Status flows back via the facade `valueChanged` stream: each push is
148
- * decoded by parameter name into the relevant cap's runtime-state slice
149
- * (see `homematic-decode.ts` for the pure decoders).
150
- *
151
- * MAPPED (control + status):
152
- * - switch (HmIP-BSM / BS2)
153
- * - cover (HmIP-BROLL)
154
- * - climate-control (HmIP-eTRV-C / BWTH / STHD / WTH-2 / HEATING)
155
- * - lock-control (HmIP-DLD)
156
- * MAPPED (read-only sensors):
157
- * - temperature-sensor / humidity-sensor (climate channels)
158
- * - motion + ambient-light-sensor (HmIP-SMI)
159
- * - power-meter (HmIP-BSM / ESI energy)
160
- * MAPPED (push-only events):
161
- * - event-emitter (HmIP-WRC* wall switches / HmIP-RC* remotes / key
162
- * channels) — one singleton cap over all key channels;
163
- * native PRESS_* datapoints fired verbatim with the
164
- * button index in `data.channel`.
165
- */
166
- var HomematicDevice = class extends require_addon.BaseDevice {
167
- features = [];
168
- /** CCU device address — dispatch + event-filter key. */
169
- address;
170
- /**
171
- * Registry broker id this device belongs to. Undefined for devices
172
- * persisted before the multi-CCU migration; those resolve to the primary
173
- * broker via `homematicFacades.get(null)`.
174
- */
175
- brokerId;
176
- /** Unsubscribe for the facade `valueChanged` listener. */
177
- valueChangedUnsub = null;
178
- /** Channel address that owns each registered cap (for decode routing). */
179
- capChannels = /* @__PURE__ */ new Map();
180
- /** Native press-datapoint vocabulary for this device's key channels
181
- * (verbatim, NO normalization). Empty when the device has no key
182
- * channel — gates the `event-emitter` fire path. */
183
- eventTypes = [];
184
- /** Monotonic event sequence — two identical presses still produce
185
- * distinct slice writes / bus emissions. */
186
- #eventSeq = 0;
187
- /** Count of key presses observed since this device instance started. */
188
- #eventCount = 0;
189
- constructor(ctx) {
190
- const persisted = ctx.persistedConfig ?? {};
191
- const address = persisted.address;
192
- if (typeof address !== "string" || address.length === 0) throw new Error(`HomematicDevice: missing address in persisted config (id=${ctx.id})`);
193
- super(ctx, homematicDeviceSchema, { type: ctx.deviceMeta.type });
194
- this.address = address;
195
- this.brokerId = typeof persisted.brokerId === "string" ? persisted.brokerId : void 0;
196
- this.registerAdminLinkCap();
197
- this.registerCapabilities();
198
- this.attachValueChangedListener();
199
- this.updateSourceInfo({
200
- id: this.address,
201
- system: "homematic"
202
- });
203
- }
204
- /** The live facade for this device's CCU, or null when disconnected. */
205
- get facade() {
206
- return require_addon.homematicFacades.get(this.brokerId ?? null);
207
- }
208
- /** Resolve this device's current `HmDevice` snapshot from the facade. */
209
- resolveHmDevice() {
210
- const facade = this.facade;
211
- if (!facade) return null;
212
- return facade.devices().find((d) => d.address === this.address) ?? null;
213
- }
214
- /**
215
- * device-admin-link — a Homematic device has no web server; the CCU3 /
216
- * RaspberryMatic WebUI is where it is configured. The CCU has no stable
217
- * per-device deep link (the WebUI addresses devices through a session-bound
218
- * frameset), so every device on a CCU gets that CCU's root, labelled for
219
- * what it is. A device persisted before multi-CCU support carries no
220
- * `brokerId` and cannot be attributed to a CCU at all — it answers null.
221
- *
222
- * Registered OUTSIDE `registerCapabilities`, which returns early when the
223
- * facade has not yet seen the device: whether the CCU has a WebUI does not
224
- * depend on that, and an early return there used to be how a device ended up
225
- * with no caps at all.
226
- */
227
- registerAdminLinkCap() {
228
- const brokerId = this.brokerId;
229
- this.ctx.registerNativeCap(require_addon.deviceAdminLinkCapability, { getAdminLink: async ({ deviceId }) => {
230
- if (deviceId !== this.id) throw new Error(`HomematicDevice: deviceId mismatch, expected ${this.id}, got ${deviceId}`);
231
- if (brokerId === void 0) {
232
- this.ctx.logger.debug("homematic admin link unavailable: no brokerId on device", {
233
- tags: { deviceId: this.id },
234
- meta: { address: this.address }
235
- });
236
- return null;
237
- }
238
- const cfg = await this.ctx.api.broker.getBrokerConfig.query({ id: brokerId });
239
- const host = typeof cfg?.["host"] === "string" ? cfg["host"] : "";
240
- const built = require_addon.buildDeviceAdminUrl({
241
- host,
242
- port: null,
243
- scheme: cfg?.["tls"] === true ? "https" : "http",
244
- path: "/"
245
- });
246
- if (!built.ok) {
247
- this.ctx.logger.debug("homematic admin link unavailable", {
248
- tags: { deviceId: this.id },
249
- meta: {
250
- reason: built.reason,
251
- brokerId
252
- }
253
- });
254
- return null;
255
- }
256
- return {
257
- url: built.url,
258
- label: "CCU WebUI",
259
- target: "integration",
260
- host
261
- };
262
- } });
263
- }
264
- registerCapabilities() {
265
- const hmDevice = this.resolveHmDevice();
266
- if (!hmDevice) {
267
- this.ctx.logger.warn("HomematicDevice: device not found on facade at construction", { meta: { address: this.address } });
268
- return;
269
- }
270
- this.registerActuatorCapsFromCustomEntities();
271
- const keyChannels = [];
272
- for (const { channel, mapping } of require_addon.mapHmDeviceChannels(hmDevice)) for (const capName of mapping.capabilities) {
273
- if (capName === "event-emitter") {
274
- keyChannels.push(channel);
275
- continue;
276
- }
277
- if (ACTUATOR_CAP_NAMES.has(capName)) continue;
278
- this.registerCap(capName, channel.address);
279
- }
280
- if (keyChannels.length > 0) this.registerEventEmitterCap(keyChannels);
281
- }
282
- /**
283
- * Register the device's ACTUATOR caps (switch / cover / light / lock /
284
- * climate-control) from the library's typed custom entities. Each
285
- * `HmCustomEntity` carries the EXACT channel the lib bound the entity to —
286
- * the channel the facade control helpers require. The cap channel stored in
287
- * `capChannels` is therefore the custom-entity channel, so `coverOpen`
288
- * (etc.) dispatch to the correct one.
289
- *
290
- * When `customEntities()` is empty (facade not fully built yet at
291
- * construction time), this is a no-op beyond a debug note; the actuator
292
- * caps register on the next reconcile/re-probe once the entities exist.
293
- */
294
- registerActuatorCapsFromCustomEntities() {
295
- const entities = this.facade?.customEntities()?.filter((e) => e.device === this.address) ?? [];
296
- if (entities.length === 0) {
297
- this.ctx.logger.debug("HomematicDevice: no custom entities at registration; actuator caps deferred to next reconcile", { meta: { address: this.address } });
298
- return;
299
- }
300
- for (const entity of entities) {
301
- const capName = actuatorCapForEntityKind(entity.kind);
302
- if (capName === null) continue;
303
- this.registerCap(capName, entity.channel);
304
- }
305
- }
306
- /** Dispatch one cap registration to its concrete wiring. */
307
- registerCap(capName, channelAddress) {
308
- this.capChannels.set(capName, channelAddress);
309
- switch (capName) {
310
- case "switch": return this.registerSwitchCap(channelAddress);
311
- case "cover": return this.registerCoverCap(channelAddress);
312
- case "climate-control": return this.registerClimateCap(channelAddress);
313
- case "lock-control": return this.registerLockCap(channelAddress);
314
- case "motion": return this.registerMotionCap();
315
- case "ambient-light-sensor": return require_addon.registerAmbientLightCap(this.ctx, this.runtimeState);
316
- case "temperature-sensor": return require_addon.registerTemperatureCap(this.ctx, this.runtimeState);
317
- case "humidity-sensor": return require_addon.registerHumidityCap(this.ctx, this.runtimeState);
318
- case "power-meter": return require_addon.registerPowerMeterCap(this.ctx, this.runtimeState);
319
- default:
320
- this.ctx.logger.debug("HomematicDevice: cap not yet wired", { meta: {
321
- capName,
322
- channelAddress,
323
- address: this.address
324
- } });
325
- return;
326
- }
327
- }
328
- registerSwitchCap(channelAddress) {
329
- this.ctx.registerNativeCap(require_addon.switchCapability, {
330
- getStatus: async () => this.runtimeState.getCapState("switch") ?? COLD_SWITCH,
331
- setState: async ({ on }) => {
332
- const facade = this.requireFacade();
333
- if (on) await facade.switchTurnOn(this.address, channelAddress);
334
- else await facade.switchTurnOff(this.address, channelAddress);
335
- }
336
- });
337
- this.runtimeState.setCapState("switch", COLD_SWITCH);
338
- }
339
- registerCoverCap(channelAddress) {
340
- this.ctx.registerNativeCap(require_addon.coverCapability, {
341
- getStatus: async () => this.runtimeState.getCapState("cover") ?? COLD_COVER,
342
- open: async () => {
343
- await this.requireFacade().coverOpen(this.address, channelAddress);
344
- },
345
- close: async () => {
346
- await this.requireFacade().coverClose(this.address, channelAddress);
347
- },
348
- stop: async () => {
349
- await this.requireFacade().coverStop(this.address, channelAddress);
350
- },
351
- setPosition: async ({ position }) => {
352
- await this.requireFacade().coverSetPosition(this.address, channelAddress, position);
353
- },
354
- setTiltPosition: async () => this.unsupported("cover tilt")
355
- });
356
- this.runtimeState.setCapState("cover", COLD_COVER);
357
- }
358
- registerClimateCap(channelAddress) {
359
- this.ctx.registerNativeCap(require_addon.climateControlCapability, {
360
- getStatus: async () => this.runtimeState.getCapState("climate-control") ?? COLD_CLIMATE,
361
- setMode: async ({ mode }) => {
362
- await this.requireFacade().climateSetMode(this.address, channelAddress, require_addon.toHmClimateMode(mode));
363
- },
364
- setTarget: async ({ target }) => {
365
- await this.requireFacade().climateSetTemperature(this.address, channelAddress, target);
366
- },
367
- setPreset: async ({ preset }) => {
368
- await this.requireFacade().climateSetBoost(this.address, channelAddress, preset === "boost");
369
- },
370
- setFanMode: async () => this.unsupported("climate fan mode"),
371
- setTargetRange: async () => this.unsupported("climate dual setpoint"),
372
- setTargetHumidity: async () => this.unsupported("climate humidity control"),
373
- setSwingVertical: async () => this.unsupported("climate vertical swing"),
374
- setSwingHorizontal: async () => this.unsupported("climate horizontal swing")
375
- });
376
- this.runtimeState.setCapState("climate-control", COLD_CLIMATE);
377
- }
378
- registerLockCap(channelAddress) {
379
- this.ctx.registerNativeCap(require_addon.lockControlCapability, {
380
- getStatus: async () => this.runtimeState.getCapState("lock-control") ?? COLD_LOCK,
381
- lock: async () => {
382
- await this.requireFacade().lockLock(this.address, channelAddress);
383
- },
384
- unlock: async () => {
385
- await this.requireFacade().lockUnlock(this.address, channelAddress);
386
- },
387
- open: async () => {
388
- await this.requireFacade().lockOpen(this.address, channelAddress);
389
- }
390
- });
391
- this.runtimeState.setCapState("lock-control", COLD_LOCK);
392
- }
393
- registerMotionCap() {
394
- this.ctx.registerNativeCap(require_addon.motionCapability, {
395
- getStatus: async () => this.runtimeState.getCapState("motion") ?? COLD_MOTION,
396
- isDetected: async () => (this.runtimeState.getCapState("motion") ?? COLD_MOTION).detected
397
- });
398
- this.runtimeState.setCapState("motion", require_addon.coldMotionKeepingRise(this.runtimeState.getCapState("motion")));
399
- }
400
- registerEventEmitterCap(keyChannels) {
401
- this.eventTypes = keyEventVocabulary(keyChannels);
402
- this.ctx.registerNativeCap(require_addon.eventEmitterCapability, { getStatus: async () => this.runtimeState.getCapState("event-emitter") ?? coldEventEmitter(this.eventTypes) });
403
- this.runtimeState.setCapState("event-emitter", coldEventEmitter(this.eventTypes));
404
- }
405
- attachValueChangedListener() {
406
- const facade = this.facade;
407
- if (!facade) return;
408
- this.valueChangedUnsub = facade.on("valueChanged", (event) => {
409
- if (event.device !== this.address) return;
410
- this.handleValueChanged(event);
411
- });
412
- }
413
- /**
414
- * Translate an inbound facade `valueChanged` into the relevant cap
415
- * slice, keyed by parameter name. Only updates caps the device
416
- * actually registered (`capChannels`).
417
- */
418
- handleValueChanged(event) {
419
- const now = event.ts || Date.now();
420
- const { parameter, value } = event;
421
- if (this.capChannels.has("switch") && parameter === "STATE" && typeof value === "boolean") this.runtimeState.setCapState("switch", {
422
- on: value,
423
- lastChangedAt: now
424
- });
425
- if (this.capChannels.has("cover")) {
426
- const next = require_addon.decodeCover(this.runtimeState.getCapState("cover") ?? COLD_COVER, parameter, value, now);
427
- if (next) this.runtimeState.setCapState("cover", next);
428
- }
429
- if (this.capChannels.has("lock-control")) {
430
- const next = require_addon.decodeLock(parameter, value, now);
431
- if (next) this.runtimeState.setCapState("lock-control", next);
432
- }
433
- if (this.capChannels.has("climate-control")) {
434
- const next = require_addon.decodeClimate(this.runtimeState.getCapState("climate-control") ?? COLD_CLIMATE, parameter, value, now);
435
- if (next) this.runtimeState.setCapState("climate-control", next);
436
- }
437
- if (this.capChannels.has("motion") && parameter === "MOTION" && typeof value === "boolean") this.runtimeState.setCapState("motion", {
438
- detected: value,
439
- lastDetectedAt: value ? now : this.runtimeState.getCapState("motion")?.lastDetectedAt ?? null,
440
- autoClearAfterMs: null
441
- });
442
- if (this.eventTypes.length > 0) this.decodeKeyPress(event, now);
443
- this.decodeSensorPush(parameter, value, now);
444
- }
445
- /**
446
- * Translate a key-channel `PRESS_*` datapoint into an `EventFire`: emit
447
- * `EventCategory.EventEmitted` on the bus and write the `event-emitter`
448
- * slice. The `eventType` is the native datapoint name VERBATIM (no
449
- * normalization); the button index (`:N` suffix of the channel address)
450
- * travels in `data.channel`. Non-key datapoints and value resets (the
451
- * key datapoint dropping back to `false`) are ignored.
452
- */
453
- decodeKeyPress(event, now) {
454
- const { parameter, value, channel } = event;
455
- if (!HM_KEY_PRESS_SET.has(parameter)) return;
456
- if (typeof value === "boolean" && value === false) return;
457
- const channelIndex = channelIndexFromAddress(channel);
458
- const fire = {
459
- deviceId: this.id,
460
- eventType: parameter,
461
- data: channelIndex !== null ? { channel: channelIndex } : {},
462
- timestamp: now,
463
- seq: ++this.#eventSeq
464
- };
465
- this.ctx.eventBus.emit(require_addon.createEvent(require_addon.EventCategory.EventEmitted, {
466
- type: "device",
467
- id: this.stableId,
468
- deviceId: this.id,
469
- addonId: this.ctx.deviceMeta.addonId
470
- }, fire));
471
- const slice = {
472
- eventTypes: [...this.eventTypes],
473
- lastEvent: fire,
474
- eventCountSinceStart: ++this.#eventCount
475
- };
476
- this.runtimeState.setCapState("event-emitter", slice);
477
- }
478
- /** Decode read-only sensor pushes into their slices. */
479
- decodeSensorPush(parameter, value, now) {
480
- if (typeof value !== "number") return;
481
- if (this.capChannels.has("temperature-sensor") && parameter === "ACTUAL_TEMPERATURE") this.runtimeState.setCapState("temperature-sensor", {
482
- temperature: value,
483
- lastFetchedAt: now
484
- });
485
- if (this.capChannels.has("humidity-sensor") && parameter === "HUMIDITY") this.runtimeState.setCapState("humidity-sensor", {
486
- humidity: value,
487
- lastFetchedAt: now
488
- });
489
- if (this.capChannels.has("ambient-light-sensor") && parameter === "ILLUMINATION") this.runtimeState.setCapState("ambient-light-sensor", {
490
- illuminance: Math.max(0, value),
491
- lastFetchedAt: now
492
- });
493
- this.decodePowerPush(parameter, value, now);
494
- }
495
- /** Decode HmIP energy-meter parameters into the power-meter slice. */
496
- decodePowerPush(parameter, value, now) {
497
- if (!this.capChannels.has("power-meter")) return;
498
- const prev = this.runtimeState.getCapState("power-meter") ?? { lastFetchedAt: 0 };
499
- switch (parameter) {
500
- case "POWER":
501
- this.runtimeState.setCapState("power-meter", {
502
- ...prev,
503
- power: value,
504
- lastFetchedAt: now
505
- });
506
- return;
507
- case "ENERGY_COUNTER":
508
- this.runtimeState.setCapState("power-meter", {
509
- ...prev,
510
- energy: value / 1e3,
511
- lastFetchedAt: now
512
- });
513
- return;
514
- case "VOLTAGE":
515
- this.runtimeState.setCapState("power-meter", {
516
- ...prev,
517
- voltage: value,
518
- lastFetchedAt: now
519
- });
520
- return;
521
- case "CURRENT":
522
- this.runtimeState.setCapState("power-meter", {
523
- ...prev,
524
- current: value / 1e3,
525
- lastFetchedAt: now
526
- });
527
- return;
528
- default: return;
529
- }
530
- }
531
- /** Reject an operation the underlying HmIP hardware does not support. */
532
- unsupported(surface) {
533
- throw new Error(`HomematicDevice: ${surface} not supported (address=${this.address})`);
534
- }
535
- /** Narrow the optional facade to a present one or throw a clear error. */
536
- requireFacade() {
537
- const facade = this.facade;
538
- if (!facade) throw new Error(`HomematicDevice: CCU connection unavailable (address=${this.address})`);
539
- return facade;
540
- }
541
- async removeDevice() {
542
- if (this.valueChangedUnsub) {
543
- try {
544
- this.valueChangedUnsub();
545
- } catch {}
546
- this.valueChangedUnsub = null;
547
- }
548
- }
549
- };
550
- require_addon.DeviceType.Container, require_addon.DeviceType.Switch, require_addon.DeviceType.Cover, require_addon.DeviceType.Thermostat, require_addon.DeviceType.Lock, require_addon.DeviceType.Sensor, require_addon.DeviceType.EventEmitter, require_addon.DeviceType.Update, require_addon.DeviceType.Generic;
551
- //#endregion
552
- exports.HomematicDevice = HomematicDevice;
553
3
  exports.HomematicProviderAddon = require_addon.HomematicProviderAddon;
554
4
  exports.buildConnectionFormSchema = require_addon.buildConnectionFormSchema;
5
+ exports.connectionFromSettings = require_addon.connectionFromSettings;
555
6
  exports.deriveHmEntitySpecs = require_addon.deriveHmEntitySpecs;
556
- exports.homematicAddonConfigSchema = require_addon.homematicAddonConfigSchema;
557
7
  exports.homematicConfigSchema = require_addon.homematicConfigSchema;
558
- exports.homematicDeviceSchema = homematicDeviceSchema;
8
+ exports.homematicGatewayDeviceSchema = require_addon.homematicGatewayDeviceSchema;
559
9
  exports.inferHmDeviceType = require_addon.inferHmDeviceType;
560
10
  exports.mapHmChannel = require_addon.mapHmChannel;
561
11
  exports.mapHmDeviceChannels = require_addon.mapHmDeviceChannels;