@camstack/addon-provider-bosch 0.1.33 → 0.1.35

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.
@@ -1,4 +1,4 @@
1
- import { A as customAction, C as switchCapability, D as DeviceType, F as array, L as literal, O as errMsg, P as _enum, R as number, V as string, W as EventCategory, f as deviceAdoptionCapability, g as heatingValveCapability, j as defineCustomActions, l as climateControlCapability, n as BaseDeviceProvider, s as batteryCapability, t as BaseDevice, u as connectionTestCapability, w as temperatureSensorCapability, z as object } from "../dist-hH1ghdQ4.mjs";
1
+ import { C as switchCapability, D as customAction, F as number, H as DeviceType, I as object, M as array, O as defineCustomActions, P as literal, R as string, U as errMsg, W as EventCategory, f as deviceAdoptionCapability, g as heatingValveCapability, j as _enum, l as climateControlCapability, n as BaseDeviceProvider, s as batteryCapability, t as BaseDevice, u as connectionTestCapability, w as temperatureSensorCapability } from "../dist-CkMPcvma.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { request } from "node:https";
@@ -389,7 +389,7 @@ function proposeShcControllers(found, existingHosts) {
389
389
  function shcDiscoveryCandidates(proposals) {
390
390
  return proposals.map((found) => ({
391
391
  stableId: `bosch-shc:${found.id}`,
392
- type: DeviceType.Hub,
392
+ type: DeviceType.Gateway,
393
393
  suggestedName: found.name,
394
394
  prefilledConfig: { host: found.host },
395
395
  setupIntegration: true
@@ -409,6 +409,137 @@ function resolveShcPairingHost(input) {
409
409
  throw new Error("Enter the controller host or IP address.");
410
410
  }
411
411
  //#endregion
412
+ //#region src/shc/shc-gateway.ts
413
+ /** In-process resolver: integrationId → live SHC connection surface. */
414
+ var ShcConnectionResolver = class {
415
+ #surfaces = /* @__PURE__ */ new Map();
416
+ set(integrationId, surface) {
417
+ if (surface === null) {
418
+ this.#surfaces.delete(integrationId);
419
+ return;
420
+ }
421
+ this.#surfaces.set(integrationId, surface);
422
+ }
423
+ has(integrationId) {
424
+ return this.#surfaces.has(integrationId);
425
+ }
426
+ devices(integrationId) {
427
+ return this.#surfaces.get(integrationId)?.devices ?? [];
428
+ }
429
+ getClient(integrationId) {
430
+ return this.#surfaces.get(integrationId)?.client ?? null;
431
+ }
432
+ list() {
433
+ return Array.from(this.#surfaces.keys()).map((id) => ({ id }));
434
+ }
435
+ clear() {
436
+ for (const surface of this.#surfaces.values()) surface.client.close();
437
+ this.#surfaces.clear();
438
+ }
439
+ };
440
+ var shcConnections = new ShcConnectionResolver();
441
+ //#endregion
442
+ //#region src/shc/devices/shc-device-base.ts
443
+ var shcDeviceSchema = object({
444
+ shcDeviceId: string().min(1).describe("Bosch SHC device id"),
445
+ brokerId: string().min(1).describe("Integration id"),
446
+ deviceModel: string().optional(),
447
+ system: literal("bosch-shc").optional(),
448
+ integrationId: string().optional(),
449
+ tempStep: number().positive().optional()
450
+ });
451
+ var ShcBaseDevice = class extends BaseDevice {
452
+ features = [];
453
+ shcDeviceId;
454
+ brokerId;
455
+ deviceModel;
456
+ stateUnsub = null;
457
+ constructor(ctx) {
458
+ const persisted = shcDeviceSchema.parse(ctx.persistedConfig ?? {});
459
+ super(ctx, shcDeviceSchema, { type: ctx.deviceMeta.type });
460
+ this.shcDeviceId = persisted.shcDeviceId;
461
+ this.brokerId = persisted.brokerId;
462
+ this.deviceModel = persisted.deviceModel;
463
+ this.online = shcConnections.has(this.brokerId);
464
+ this.updateSourceInfo({
465
+ id: this.shcDeviceId,
466
+ system: "bosch-shc"
467
+ });
468
+ }
469
+ resolveClient() {
470
+ return shcConnections.getClient(this.brokerId);
471
+ }
472
+ requireClient() {
473
+ const client = this.resolveClient();
474
+ if (client === null) throw new Error(`SHC client unavailable (device=${this.shcDeviceId})`);
475
+ return client;
476
+ }
477
+ async onActivate() {
478
+ await super.onActivate();
479
+ this.registerCaps();
480
+ this.attachStateListener();
481
+ await this.refreshFromController();
482
+ }
483
+ async removeDevice() {
484
+ if (this.stateUnsub) {
485
+ try {
486
+ this.stateUnsub();
487
+ } catch {}
488
+ this.stateUnsub = null;
489
+ }
490
+ }
491
+ attachStateListener() {
492
+ const client = this.resolveClient();
493
+ if (client === null) return;
494
+ const onEvent = (event) => {
495
+ if (event.deviceId !== this.shcDeviceId) return;
496
+ this.refreshFromController().catch((err) => {
497
+ this.deviceLog("warn", "SHC refresh after controller event failed", {
498
+ shcDeviceId: this.shcDeviceId,
499
+ error: errMsg(err)
500
+ });
501
+ });
502
+ };
503
+ this.stateUnsub = client.onStateChange(onEvent);
504
+ }
505
+ deviceLog(level, message, meta) {
506
+ const extras = {
507
+ tags: { deviceId: this.ctx.id },
508
+ ...meta === void 0 ? {} : { meta }
509
+ };
510
+ if (level === "warn") this.ctx.logger.warn(message, extras);
511
+ else this.ctx.logger.info(message, extras);
512
+ }
513
+ unsupported(surface) {
514
+ throw new Error(`bosch-shc ${this.shcDeviceId}: ${surface} not supported`);
515
+ }
516
+ };
517
+ //#endregion
518
+ //#region src/shc/devices/shc-gateway-device.ts
519
+ /**
520
+ * The Smart Home Controller box, as a {@link DeviceType.Gateway}. Its connection
521
+ * lives on the INTEGRATION; this device is the box's presence in the device list
522
+ * and follows the session's online state (the integration's connect and
523
+ * disconnect callbacks flip it).
524
+ *
525
+ * It registers no action: the controller's local API offers no reboot or update
526
+ * and the box serves no web page, so there is nothing real to expose. Rooms and
527
+ * devices are NOT children of it; they are adopted as their own top-level
528
+ * devices of the same integration.
529
+ */
530
+ var ShcGatewayDevice = class extends BaseDevice {
531
+ features = [];
532
+ constructor(ctx) {
533
+ const cfg = shcDeviceSchema.parse(ctx.persistedConfig ?? {});
534
+ super(ctx, shcDeviceSchema, { type: DeviceType.Gateway });
535
+ this.online = shcConnections.has(cfg.brokerId);
536
+ this.updateSourceInfo({
537
+ id: cfg.shcDeviceId,
538
+ system: "bosch-shc"
539
+ });
540
+ }
541
+ };
542
+ //#endregion
412
543
  //#region src/shc/shc-domain-mapping.ts
413
544
  var ROOM_CLIMATE_MODEL = "ROOM_CLIMATE_CONTROL";
414
545
  /** Classify a normalised device summary into an adoption / driver kind. */
@@ -531,6 +662,11 @@ function buildRoomClimatePutBody(input) {
531
662
  }
532
663
  //#endregion
533
664
  //#region src/shc/shc-discovery.ts
665
+ var GATEWAY_SUFFIX = ":controller";
666
+ /** The `childNativeId` (and `shcDeviceId`) of the controller box itself, one per integration. */
667
+ function shcGatewayNativeId(integrationId) {
668
+ return `${integrationId}${GATEWAY_SUFFIX}`;
669
+ }
534
670
  function shouldSkipShcDevice(device) {
535
671
  if (classifyShcDevice(device) === "unsupported") return true;
536
672
  if (device.deviceModel.startsWith("HOMECONNECT_")) return true;
@@ -625,7 +761,21 @@ function leafCandidate(device, kind, adoptedId) {
625
761
  /** Map live SHC discovery rows to adoption candidates. Pure — no I/O. */
626
762
  function buildShcCandidates(input) {
627
763
  const linkedTrvs = collectTrvsLinkedToRoomClimate(input.devices);
628
- const out = [];
764
+ const gatewayId = shcGatewayNativeId(input.integrationId);
765
+ const gatewayAdopted = input.adopted.get(gatewayId) ?? null;
766
+ const out = [{
767
+ childNativeId: gatewayId,
768
+ name: input.integrationName,
769
+ type: DeviceType.Gateway,
770
+ status: "online",
771
+ metadata: {
772
+ manufacturer: "Bosch",
773
+ model: "Smart Home Controller"
774
+ },
775
+ alreadyAdopted: gatewayAdopted !== null,
776
+ adoptedDeviceId: gatewayAdopted,
777
+ capabilities: []
778
+ }];
629
779
  for (const device of input.devices) {
630
780
  if (shouldSkipShcDevice(device)) continue;
631
781
  const kind = classifyShcDevice(device);
@@ -655,112 +805,6 @@ function shcStableIdSuffixFromDeviceId(shcDeviceId) {
655
805
  return shcDeviceId.replace(/[^a-zA-Z0-9._-]+/g, "_");
656
806
  }
657
807
  //#endregion
658
- //#region src/shc/shc-gateway.ts
659
- /** In-process resolver: integrationId → live SHC connection surface. */
660
- var ShcConnectionResolver = class {
661
- #surfaces = /* @__PURE__ */ new Map();
662
- set(integrationId, surface) {
663
- if (surface === null) {
664
- this.#surfaces.delete(integrationId);
665
- return;
666
- }
667
- this.#surfaces.set(integrationId, surface);
668
- }
669
- has(integrationId) {
670
- return this.#surfaces.has(integrationId);
671
- }
672
- devices(integrationId) {
673
- return this.#surfaces.get(integrationId)?.devices ?? [];
674
- }
675
- getClient(integrationId) {
676
- return this.#surfaces.get(integrationId)?.client ?? null;
677
- }
678
- list() {
679
- return Array.from(this.#surfaces.keys()).map((id) => ({ id }));
680
- }
681
- clear() {
682
- for (const surface of this.#surfaces.values()) surface.client.close();
683
- this.#surfaces.clear();
684
- }
685
- };
686
- var shcConnections = new ShcConnectionResolver();
687
- //#endregion
688
- //#region src/shc/devices/shc-device-base.ts
689
- var shcDeviceSchema = object({
690
- shcDeviceId: string().min(1).describe("Bosch SHC device id"),
691
- brokerId: string().min(1).describe("Integration id"),
692
- deviceModel: string().optional(),
693
- system: literal("bosch-shc").optional(),
694
- integrationId: string().optional(),
695
- tempStep: number().positive().optional()
696
- });
697
- var ShcBaseDevice = class extends BaseDevice {
698
- features = [];
699
- shcDeviceId;
700
- brokerId;
701
- deviceModel;
702
- stateUnsub = null;
703
- constructor(ctx) {
704
- const persisted = shcDeviceSchema.parse(ctx.persistedConfig ?? {});
705
- super(ctx, shcDeviceSchema, { type: ctx.deviceMeta.type });
706
- this.shcDeviceId = persisted.shcDeviceId;
707
- this.brokerId = persisted.brokerId;
708
- this.deviceModel = persisted.deviceModel;
709
- this.online = shcConnections.has(this.brokerId);
710
- this.updateSourceInfo({
711
- id: this.shcDeviceId,
712
- system: "bosch-shc"
713
- });
714
- }
715
- resolveClient() {
716
- return shcConnections.getClient(this.brokerId);
717
- }
718
- requireClient() {
719
- const client = this.resolveClient();
720
- if (client === null) throw new Error(`SHC client unavailable (device=${this.shcDeviceId})`);
721
- return client;
722
- }
723
- async onActivate() {
724
- await super.onActivate();
725
- this.registerCaps();
726
- this.attachStateListener();
727
- await this.refreshFromController();
728
- }
729
- async removeDevice() {
730
- if (this.stateUnsub) {
731
- try {
732
- this.stateUnsub();
733
- } catch {}
734
- this.stateUnsub = null;
735
- }
736
- }
737
- attachStateListener() {
738
- const client = this.resolveClient();
739
- if (client === null) return;
740
- const onEvent = (event) => {
741
- if (event.deviceId !== this.shcDeviceId) return;
742
- this.refreshFromController().catch((err) => {
743
- this.deviceLog("warn", "SHC refresh after controller event failed", {
744
- shcDeviceId: this.shcDeviceId,
745
- error: errMsg(err)
746
- });
747
- });
748
- };
749
- this.stateUnsub = client.onStateChange(onEvent);
750
- }
751
- deviceLog(level, message, meta) {
752
- const extras = {
753
- tags: { deviceId: this.ctx.id },
754
- ...meta === void 0 ? {} : { meta }
755
- };
756
- if (level === "warn") this.ctx.logger.warn(message, extras);
757
- else this.ctx.logger.info(message, extras);
758
- }
759
- unsupported(surface) {
760
- throw new Error(`bosch-shc ${this.shcDeviceId}: ${surface} not supported`);
761
- }
762
- };
763
- //#endregion
764
808
  //#region src/shc/shc-climate-schedules.ts
765
809
  /** Heating profiles from `GET /climate/schedule/{id}/HEATING`. */
766
810
  function heatingProfilesFromSchedule(body) {
@@ -1128,11 +1172,15 @@ async function adoptedMapForIntegration(integrationId, listAdoptedShc) {
1128
1172
  return map;
1129
1173
  }
1130
1174
  function buildShcAdoptionProvider(deps) {
1131
- const { registry, hasIntegration, listIntegrations, listAdoptedShc, adoptDevice, removeDevice, findDeviceConfig, logger } = deps;
1175
+ const { registry, hasIntegration, listIntegrations, integrationName, listAdoptedShc, adoptDevice, removeDevice, findDeviceConfig, logger } = deps;
1132
1176
  async function allCandidatesForIntegration(integrationId) {
1177
+ const devices = registry.devices(integrationId);
1178
+ const adopted = await adoptedMapForIntegration(integrationId, listAdoptedShc);
1133
1179
  return buildShcCandidates({
1134
- devices: registry.devices(integrationId),
1135
- adopted: await adoptedMapForIntegration(integrationId, listAdoptedShc)
1180
+ integrationId,
1181
+ integrationName: integrationName(integrationId) ?? "Bosch SHC",
1182
+ devices,
1183
+ adopted
1136
1184
  });
1137
1185
  }
1138
1186
  return {
@@ -1193,6 +1241,22 @@ function buildShcAdoptionProvider(deps) {
1193
1241
  const adopted = [];
1194
1242
  let failures = 0;
1195
1243
  for (const shcDeviceId of childNativeIds) try {
1244
+ if (shcDeviceId === shcGatewayNativeId(integrationId)) {
1245
+ const name = perCandidate?.[shcDeviceId]?.name ?? integrationName(integrationId) ?? "Bosch SHC";
1246
+ const { deviceId } = await adoptDevice({
1247
+ shcDeviceId,
1248
+ integrationId,
1249
+ type: DeviceType.Gateway,
1250
+ name,
1251
+ deviceModel: "Smart Home Controller"
1252
+ });
1253
+ adopted.push({
1254
+ childNativeId: shcDeviceId,
1255
+ parentDeviceId: deviceId,
1256
+ accessoryDeviceIds: []
1257
+ });
1258
+ continue;
1259
+ }
1196
1260
  const dev = devices.find((d) => d.id === shcDeviceId);
1197
1261
  if (dev === void 0) {
1198
1262
  failures++;
@@ -1237,7 +1301,7 @@ function buildShcAdoptionProvider(deps) {
1237
1301
  const integrationId = String(cfg["brokerId"]);
1238
1302
  const shcDeviceId = String(cfg["shcDeviceId"]);
1239
1303
  if (!hasIntegration(integrationId)) throw new Error(`bosch-shc resync: integration ${integrationId} not connected`);
1240
- if (!registry.devices(integrationId).some((d) => d.id === shcDeviceId)) throw new Error(`bosch-shc resync: device ${shcDeviceId} no longer on integration`);
1304
+ if (!(shcDeviceId === shcGatewayNativeId(integrationId) || registry.devices(integrationId).some((d) => d.id === shcDeviceId))) throw new Error(`bosch-shc resync: device ${shcDeviceId} no longer on integration`);
1241
1305
  return {
1242
1306
  changed: false,
1243
1307
  rebuiltChildren: 0
@@ -23462,6 +23526,10 @@ var ShcClientRegistry = class {
23462
23526
  makeClient: opts.makeClient ?? this.#makeClient
23463
23527
  }));
23464
23528
  }
23529
+ /** The integration's display name, or null when it is not registered. */
23530
+ nameOf(integrationId) {
23531
+ return this.#entries.get(integrationId)?.manager.getInfo().name ?? null;
23532
+ }
23465
23533
  setOnConnected(cb) {
23466
23534
  this.#onConnected = cb;
23467
23535
  }
@@ -23657,6 +23725,7 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23657
23725
  addonId = "bosch-shc";
23658
23726
  providerName = "Bosch SHC";
23659
23727
  deviceClasses = {
23728
+ [DeviceType.Gateway]: ShcGatewayDevice,
23660
23729
  [DeviceType.Container]: ShcClimateRoomDevice,
23661
23730
  [DeviceType.Climate]: ShcRoomClimateDevice,
23662
23731
  [DeviceType.Thermostat]: ShcTrvDevice,
@@ -23848,6 +23917,7 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23848
23917
  logger: this.ctx.logger,
23849
23918
  hasIntegration: (integrationId) => this.requireClients().has(integrationId),
23850
23919
  listIntegrations: () => this.requireClients().list(),
23920
+ integrationName: (integrationId) => this.requireClients().nameOf(integrationId),
23851
23921
  listAdoptedShc: async () => {
23852
23922
  const reg = this.ctx.kernel.deviceRegistry;
23853
23923
  const devices = this.ctx.kernel.devices;
@@ -23867,8 +23937,25 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23867
23937
  const devices = this.ctx.kernel.devices;
23868
23938
  if (!devices) throw new Error("bosch-shc adopt: kernel.devices unavailable");
23869
23939
  const summary = shcConnections.devices(integrationId).find((d) => d.id === shcDeviceId);
23870
- const kind = summary ? classifyShcDevice(summary) : "unsupported";
23940
+ const kind = type === DeviceType.Gateway ? "gateway" : summary ? classifyShcDevice(summary) : "unsupported";
23871
23941
  if (kind === "unsupported") throw new Error(`bosch-shc adopt: unsupported device ${shcDeviceId}`);
23942
+ if (type === DeviceType.Gateway) {
23943
+ const config = {
23944
+ shcDeviceId,
23945
+ brokerId: integrationId,
23946
+ deviceModel,
23947
+ system: "bosch-shc",
23948
+ integrationId,
23949
+ name
23950
+ };
23951
+ const stableId = this.generateStableId(DeviceType.Gateway, config);
23952
+ const meta = {
23953
+ type: DeviceType.Gateway,
23954
+ name,
23955
+ integrationId
23956
+ };
23957
+ return { deviceId: (await devices.create(stableId, ShcGatewayDevice, config, null, meta)).id };
23958
+ }
23872
23959
  const baseConfig = {
23873
23960
  shcDeviceId,
23874
23961
  brokerId: integrationId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-bosch",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "description": "Bosch smart-home device-provider addons for CamStack — SHC, Home Connect, and HomeCom cloud integrations (scaffold)",
5
5
  "keywords": [
6
6
  "camstack",
@@ -127,7 +127,10 @@
127
127
  }
128
128
  ]
129
129
  }
130
- ]
130
+ ],
131
+ "requires": {
132
+ "server": ">=1.2.719"
133
+ }
131
134
  },
132
135
  "files": [
133
136
  "dist",