@camstack/addon-provider-bosch 0.1.50 → 0.1.52

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/shc/addon.js CHANGED
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region \0rolldown/runtime.js
3
3
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
4
4
  //#endregion
5
- const require_bosch_instances = require("../bosch-instances-CYBnIcHg.js");
5
+ const require_bosch_instances = require("../bosch-instances-LWbVnsaD.js");
6
6
  let node_crypto = require("node:crypto");
7
7
  let node_https = require("node:https");
8
8
  let node_child_process = require("node:child_process");
@@ -38,6 +38,146 @@ var boschShcActions = require_bosch_instances.defineCustomActions({
38
38
  })
39
39
  });
40
40
  //#endregion
41
+ //#region src/shc/shc-domain-mapping.ts
42
+ var ROOM_CLIMATE_MODEL = "ROOM_CLIMATE_CONTROL";
43
+ /** Classify a normalised device summary into an adoption / driver kind. */
44
+ function classifyShcDevice(device) {
45
+ const fromModel = shcKindForModel(device.deviceModel);
46
+ if (fromModel !== "unsupported") return fromModel;
47
+ if (device.services.includes("RoomClimateControl")) return "room-climate";
48
+ if (device.services.includes("PowerSwitch")) return "switch";
49
+ if (device.services.includes("TemperatureLevel") || device.services.includes("ValveTappet")) return "trv";
50
+ return "unsupported";
51
+ }
52
+ function shcKindForModel(deviceModel) {
53
+ if (deviceModel === ROOM_CLIMATE_MODEL) return "room-climate";
54
+ if (deviceModel === "TWR_SWITCH" || deviceModel === "DIMMER_LIGHT" || deviceModel === "PLUG_COMPACT" || deviceModel === "PLUG_COMPACT_DUAL") return "switch";
55
+ if (deviceModel === "HEATING_THERMOSTAT" || deviceModel === "RADIATOR_THERMOSTAT" || deviceModel === "TRV_GEN2_DUAL" || deviceModel === "TRV") return "trv";
56
+ return "unsupported";
57
+ }
58
+ function deviceTypeForShcKind(kind) {
59
+ switch (kind) {
60
+ case "room-climate": return require_bosch_instances.DeviceType.Container;
61
+ case "trv": return require_bosch_instances.DeviceType.Climate;
62
+ case "switch": return require_bosch_instances.DeviceType.Switch;
63
+ }
64
+ }
65
+ /** D1033 — which climate device an SHC kind is: a valve is a radiator, a room a thermostat. */
66
+ function shcClimateRole(kind) {
67
+ return kind === "trv" ? require_bosch_instances.DeviceRole.ClimateRadiator : require_bosch_instances.DeviceRole.ClimateThermostat;
68
+ }
69
+ /** The room-climate accessory's stable-id suffix (`ShcClimateRoomDevice`). */
70
+ var SHC_ROOM_CLIMATE_SUFFIX = "climate";
71
+ /**
72
+ * D1033 — a valve and a room climate share the `climate` type, so a persisted
73
+ * row is told apart by its model when the config carries one, else by the
74
+ * room-climate accessory's stable-id suffix.
75
+ */
76
+ function shcClimateRowKind(row) {
77
+ const model = row.config?.["deviceModel"];
78
+ if (typeof model === "string") {
79
+ const kind = shcKindForModel(model);
80
+ if (kind === "trv" || kind === "room-climate") return kind;
81
+ }
82
+ return row.stableId.endsWith(`-climate`) ? "room-climate" : "trv";
83
+ }
84
+ function capsForShcKind(kind) {
85
+ switch (kind) {
86
+ case "room-climate": return ["climate-control"];
87
+ case "switch": return ["switch"];
88
+ case "trv": return [
89
+ "temperature-sensor",
90
+ "battery",
91
+ "heating-valve"
92
+ ];
93
+ }
94
+ }
95
+ function mapShcModeToHvac(state) {
96
+ if ((state.roomControlMode ?? "").toUpperCase() === "OFF") return "off";
97
+ const upper = (state.operationMode ?? state.mode ?? "").toUpperCase();
98
+ if (upper === "OFF") return "off";
99
+ if (upper === "AUTOMATIC" || upper === "AUTO") return "auto";
100
+ if (upper.includes("COOL")) return "cool";
101
+ if (upper.includes("HEAT")) return "heat";
102
+ return "heat";
103
+ }
104
+ var CLIMATE_MODES = [
105
+ "off",
106
+ "heat",
107
+ "auto"
108
+ ];
109
+ /**
110
+ * Setpoint ends the Smart Home Controller app enforces. RoomClimateControl
111
+ * state does not carry them; a value present on the payload still wins.
112
+ */
113
+ var SHC_SETPOINT_MIN = 5;
114
+ var SHC_SETPOINT_MAX = 30;
115
+ /** Map Bosch RoomClimateControl state JSON → CamStack climate-control slice. */
116
+ function mapRoomClimateStateToSlice(state) {
117
+ const now = Date.now();
118
+ return {
119
+ mode: mapShcModeToHvac(state),
120
+ availableModes: [...CLIMATE_MODES],
121
+ fanMode: "",
122
+ availableFanModes: [],
123
+ preset: "",
124
+ availablePresets: [],
125
+ target: state.setpointTemperature ?? null,
126
+ targetHigh: null,
127
+ targetLow: null,
128
+ targetHumidity: null,
129
+ currentTemp: state.temperature ?? null,
130
+ currentHumidity: null,
131
+ swingVertical: null,
132
+ swingHorizontal: null,
133
+ minTemp: state.minTemperature ?? SHC_SETPOINT_MIN,
134
+ maxTemp: state.maxTemperature ?? SHC_SETPOINT_MAX,
135
+ tempStep: .5,
136
+ lastFetchedAt: now
137
+ };
138
+ }
139
+ /**
140
+ * PUT bodies for a mode change, in order.
141
+ * Off is `roomControlMode` alone — `operationMode: OFF` is rejected.
142
+ * Heat and auto must turn heating on first; combining both fields while
143
+ * the room is off returns WRONG_THERMOSTAT_GROUP_MODE.
144
+ */
145
+ function buildRoomClimateModeBodies(mode) {
146
+ if (mode === "off") return [{
147
+ "@type": "climateControlState",
148
+ roomControlMode: "OFF"
149
+ }];
150
+ return [{
151
+ "@type": "climateControlState",
152
+ roomControlMode: "HEATING"
153
+ }, {
154
+ "@type": "climateControlState",
155
+ operationMode: mode === "auto" ? "AUTOMATIC" : "MANUAL"
156
+ }];
157
+ }
158
+ /** PowerSwitch on the controller is the string `ON` / `OFF`, not a boolean. */
159
+ function readShcSwitchOn(state) {
160
+ if (isShcSwitchOn(state.switchState)) return true;
161
+ return isShcSwitchOn(state.switchStates?.[0]?.switchState);
162
+ }
163
+ function isShcSwitchOn(value) {
164
+ if (value === true) return true;
165
+ return typeof value === "string" && value.toUpperCase() === "ON";
166
+ }
167
+ function buildPowerSwitchPutBody(on) {
168
+ return {
169
+ "@type": "powerSwitchState",
170
+ switchState: on ? "ON" : "OFF"
171
+ };
172
+ }
173
+ /** Body for PUT RoomClimateControl/state when changing the setpoint. */
174
+ function buildRoomClimatePutBody(input) {
175
+ return {
176
+ "@type": "climateControlState",
177
+ setpointTemperature: input.target
178
+ };
179
+ }
180
+ //#endregion
41
181
  //#region src/shc/config.ts
42
182
  /** Operator settings for one SHC integration (local mTLS + optional token). */
43
183
  var shcConfigSchema = require_bosch_instances.object({
@@ -562,9 +702,12 @@ var ShcBaseDevice = class extends require_bosch_instances.BaseDevice {
562
702
  brokerId;
563
703
  deviceModel;
564
704
  stateUnsub = null;
565
- constructor(ctx) {
705
+ constructor(ctx, role) {
566
706
  const persisted = shcDeviceSchema.parse(ctx.persistedConfig ?? {});
567
- super(ctx, shcDeviceSchema, { type: ctx.deviceMeta.type });
707
+ super(ctx, shcDeviceSchema, {
708
+ type: ctx.deviceMeta.type,
709
+ ...role !== void 0 ? { role } : {}
710
+ });
568
711
  this.shcDeviceId = persisted.shcDeviceId;
569
712
  this.brokerId = persisted.brokerId;
570
713
  this.deviceModel = persisted.deviceModel;
@@ -648,127 +791,6 @@ var ShcGatewayDevice = class extends require_bosch_instances.BaseDevice {
648
791
  }
649
792
  };
650
793
  //#endregion
651
- //#region src/shc/shc-domain-mapping.ts
652
- var ROOM_CLIMATE_MODEL = "ROOM_CLIMATE_CONTROL";
653
- /** Classify a normalised device summary into an adoption / driver kind. */
654
- function classifyShcDevice(device) {
655
- const fromModel = shcKindForModel(device.deviceModel);
656
- if (fromModel !== "unsupported") return fromModel;
657
- if (device.services.includes("RoomClimateControl")) return "room-climate";
658
- if (device.services.includes("PowerSwitch")) return "switch";
659
- if (device.services.includes("TemperatureLevel") || device.services.includes("ValveTappet")) return "trv";
660
- return "unsupported";
661
- }
662
- function shcKindForModel(deviceModel) {
663
- if (deviceModel === ROOM_CLIMATE_MODEL) return "room-climate";
664
- if (deviceModel === "TWR_SWITCH" || deviceModel === "DIMMER_LIGHT" || deviceModel === "PLUG_COMPACT" || deviceModel === "PLUG_COMPACT_DUAL") return "switch";
665
- if (deviceModel === "HEATING_THERMOSTAT" || deviceModel === "RADIATOR_THERMOSTAT" || deviceModel === "TRV_GEN2_DUAL" || deviceModel === "TRV") return "trv";
666
- return "unsupported";
667
- }
668
- function deviceTypeForShcKind(kind) {
669
- switch (kind) {
670
- case "room-climate": return require_bosch_instances.DeviceType.Container;
671
- case "trv": return require_bosch_instances.DeviceType.Thermostat;
672
- case "switch": return require_bosch_instances.DeviceType.Switch;
673
- }
674
- }
675
- function capsForShcKind(kind) {
676
- switch (kind) {
677
- case "room-climate": return ["climate-control"];
678
- case "switch": return ["switch"];
679
- case "trv": return [
680
- "temperature-sensor",
681
- "battery",
682
- "heating-valve"
683
- ];
684
- }
685
- }
686
- function mapShcModeToHvac(state) {
687
- if ((state.roomControlMode ?? "").toUpperCase() === "OFF") return "off";
688
- const upper = (state.operationMode ?? state.mode ?? "").toUpperCase();
689
- if (upper === "OFF") return "off";
690
- if (upper === "AUTOMATIC" || upper === "AUTO") return "auto";
691
- if (upper.includes("COOL")) return "cool";
692
- if (upper.includes("HEAT")) return "heat";
693
- return "heat";
694
- }
695
- var CLIMATE_MODES = [
696
- "off",
697
- "heat",
698
- "auto"
699
- ];
700
- /**
701
- * Setpoint ends the Smart Home Controller app enforces. RoomClimateControl
702
- * state does not carry them; a value present on the payload still wins.
703
- */
704
- var SHC_SETPOINT_MIN = 5;
705
- var SHC_SETPOINT_MAX = 30;
706
- /** Map Bosch RoomClimateControl state JSON → CamStack climate-control slice. */
707
- function mapRoomClimateStateToSlice(state) {
708
- const now = Date.now();
709
- return {
710
- mode: mapShcModeToHvac(state),
711
- availableModes: [...CLIMATE_MODES],
712
- fanMode: "",
713
- availableFanModes: [],
714
- preset: "",
715
- availablePresets: [],
716
- target: state.setpointTemperature ?? null,
717
- targetHigh: null,
718
- targetLow: null,
719
- targetHumidity: null,
720
- currentTemp: state.temperature ?? null,
721
- currentHumidity: null,
722
- swingVertical: null,
723
- swingHorizontal: null,
724
- minTemp: state.minTemperature ?? SHC_SETPOINT_MIN,
725
- maxTemp: state.maxTemperature ?? SHC_SETPOINT_MAX,
726
- tempStep: .5,
727
- lastFetchedAt: now
728
- };
729
- }
730
- /**
731
- * PUT bodies for a mode change, in order.
732
- * Off is `roomControlMode` alone — `operationMode: OFF` is rejected.
733
- * Heat and auto must turn heating on first; combining both fields while
734
- * the room is off returns WRONG_THERMOSTAT_GROUP_MODE.
735
- */
736
- function buildRoomClimateModeBodies(mode) {
737
- if (mode === "off") return [{
738
- "@type": "climateControlState",
739
- roomControlMode: "OFF"
740
- }];
741
- return [{
742
- "@type": "climateControlState",
743
- roomControlMode: "HEATING"
744
- }, {
745
- "@type": "climateControlState",
746
- operationMode: mode === "auto" ? "AUTOMATIC" : "MANUAL"
747
- }];
748
- }
749
- /** PowerSwitch on the controller is the string `ON` / `OFF`, not a boolean. */
750
- function readShcSwitchOn(state) {
751
- if (isShcSwitchOn(state.switchState)) return true;
752
- return isShcSwitchOn(state.switchStates?.[0]?.switchState);
753
- }
754
- function isShcSwitchOn(value) {
755
- if (value === true) return true;
756
- return typeof value === "string" && value.toUpperCase() === "ON";
757
- }
758
- function buildPowerSwitchPutBody(on) {
759
- return {
760
- "@type": "powerSwitchState",
761
- switchState: on ? "ON" : "OFF"
762
- };
763
- }
764
- /** Body for PUT RoomClimateControl/state when changing the setpoint. */
765
- function buildRoomClimatePutBody(input) {
766
- return {
767
- "@type": "climateControlState",
768
- setpointTemperature: input.target
769
- };
770
- }
771
- //#endregion
772
794
  //#region src/shc/shc-discovery.ts
773
795
  var GATEWAY_SUFFIX = ":controller";
774
796
  /** The `childNativeId` (and `shcDeviceId`) of the controller box itself, one per integration. */
@@ -799,7 +821,7 @@ function trvCandidate(device) {
799
821
  return {
800
822
  childNativeId: device.id,
801
823
  name: device.name.length > 0 ? device.name : device.id,
802
- type: require_bosch_instances.DeviceType.Thermostat,
824
+ type: require_bosch_instances.DeviceType.Climate,
803
825
  status: "online",
804
826
  metadata: {
805
827
  serialNumber: device.id,
@@ -992,10 +1014,10 @@ var COLD_START = {
992
1014
  };
993
1015
  var ShcRoomClimateDevice = class extends ShcBaseDevice {
994
1016
  constructor(ctx) {
995
- super(ctx);
1017
+ super(ctx, shcClimateRole("room-climate"));
996
1018
  }
997
1019
  registerCaps() {
998
- this.ctx.registerNativeCap(require_bosch_instances.climateControlCapability, {
1020
+ const provider = {
999
1021
  getStatus: async () => this.runtimeState.getCapState(CLIMATE_CAP) ?? COLD_START,
1000
1022
  setMode: async ({ mode }) => {
1001
1023
  const client = this.requireClient();
@@ -1034,7 +1056,9 @@ var ShcRoomClimateDevice = class extends ShcBaseDevice {
1034
1056
  setTargetHumidity: async () => this.unsupported("climate humidity"),
1035
1057
  setSwingVertical: async () => this.unsupported("climate swing"),
1036
1058
  setSwingHorizontal: async () => this.unsupported("climate swing")
1037
- });
1059
+ };
1060
+ this.ctx.registerNativeCap(require_bosch_instances.climateControlCapability, provider);
1061
+ require_bosch_instances.registerPowerSwitch(this, require_bosch_instances.climateControlCapability, provider);
1038
1062
  this.runtimeState.setCapState(CLIMATE_CAP, COLD_START);
1039
1063
  }
1040
1064
  async refreshFromController() {
@@ -1092,7 +1116,7 @@ var VALVE_COLD = {
1092
1116
  /** Radiator TRV — local temperature, valve position, battery (no room setpoint). */
1093
1117
  var ShcTrvDevice = class extends ShcBaseDevice {
1094
1118
  constructor(ctx) {
1095
- super(ctx);
1119
+ super(ctx, shcClimateRole("trv"));
1096
1120
  }
1097
1121
  registerCaps() {
1098
1122
  this.ctx.registerNativeCap(require_bosch_instances.temperatureSensorCapability, { getStatus: async () => this.runtimeState.getCapState(TEMP_CAP) ?? TEMP_COLD });
@@ -1189,9 +1213,10 @@ var ShcClimateRoomDevice = class extends require_bosch_instances.BaseDevice {
1189
1213
  ...this.integrationId !== void 0 ? { integrationId: this.integrationId } : {}
1190
1214
  };
1191
1215
  return {
1192
- stableIdSuffix: "climate",
1216
+ stableIdSuffix: SHC_ROOM_CLIMATE_SUFFIX,
1193
1217
  meta: {
1194
1218
  type: require_bosch_instances.DeviceType.Climate,
1219
+ role: shcClimateRole("room-climate"),
1195
1220
  name: shcClimateEntityName(containerName),
1196
1221
  linkDeviceId: this.id,
1197
1222
  ...this.integrationId !== void 0 ? { integrationId: this.integrationId } : {}
@@ -1210,7 +1235,8 @@ var ShcClimateRoomDevice = class extends require_bosch_instances.BaseDevice {
1210
1235
  return {
1211
1236
  stableIdSuffix: `trv-${shcStableIdSuffixFromDeviceId(child.shcDeviceId)}`,
1212
1237
  meta: {
1213
- type: require_bosch_instances.DeviceType.Thermostat,
1238
+ type: require_bosch_instances.DeviceType.Climate,
1239
+ role: shcClimateRole("trv"),
1214
1240
  name: child.name,
1215
1241
  linkDeviceId: this.id,
1216
1242
  ...this.integrationId !== void 0 ? { integrationId: this.integrationId } : {}
@@ -23831,10 +23857,14 @@ var BoschShcProviderAddon = class extends require_bosch_instances.BaseDeviceProv
23831
23857
  deviceClasses = {
23832
23858
  [require_bosch_instances.DeviceType.Gateway]: ShcGatewayDevice,
23833
23859
  [require_bosch_instances.DeviceType.Container]: ShcClimateRoomDevice,
23834
- [require_bosch_instances.DeviceType.Climate]: ShcRoomClimateDevice,
23835
- [require_bosch_instances.DeviceType.Thermostat]: ShcTrvDevice,
23860
+ [require_bosch_instances.DeviceType.Climate]: ShcTrvDevice,
23836
23861
  [require_bosch_instances.DeviceType.Switch]: ShcSwitchDevice
23837
23862
  };
23863
+ /** D1033 — a valve and a room climate are both `climate`: rebuild each with its class. */
23864
+ deviceClassForRow(row) {
23865
+ if (row.type !== require_bosch_instances.DeviceType.Climate) return super.deviceClassForRow(row);
23866
+ return shcClimateRowKind(row) === "room-climate" ? ShcRoomClimateDevice : ShcTrvDevice;
23867
+ }
23838
23868
  clients = null;
23839
23869
  instances = null;
23840
23870
  constructor() {