@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.
@@ -1,4 +1,4 @@
1
- import { $ as EventCategory, A as RecordIntegrationInstances, D as switchCapability, F as entryBody, G as object, M as customAction, N as defineCustomActions, O as temperatureSensorCapability, P as defineEntryKind, Q as errMsg, R as rowsDepsOver, U as literal, V as array, W as number, Z as DeviceType, a as BaseDevice, d as climateControlCapability, i as testBoschInstance, l as batteryCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, q as string, r as keepBodyKeys, t as boschRowsMigration, x as integrationInstancesCapability, y as heatingValveCapability, z as _enum } from "../bosch-instances-CdvcBYye.mjs";
1
+ import { $ as errMsg, B as _enum, F as defineEntryKind, G as number, H as array, I as entryBody, J as string, K as object, N as customAction, O as switchCapability, P as defineCustomActions, Q as DeviceType, T as registerPowerSwitch, W as literal, Z as DeviceRole, a as BaseDevice, d as climateControlCapability, et as EventCategory, i as testBoschInstance, j as RecordIntegrationInstances, k as temperatureSensorCapability, l as batteryCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, r as keepBodyKeys, t as boschRowsMigration, x as integrationInstancesCapability, y as heatingValveCapability, z as rowsDepsOver } from "../bosch-instances-DSE6IXdU.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { request } from "node:https";
@@ -39,6 +39,146 @@ var boschShcActions = defineCustomActions({
39
39
  })
40
40
  });
41
41
  //#endregion
42
+ //#region src/shc/shc-domain-mapping.ts
43
+ var ROOM_CLIMATE_MODEL = "ROOM_CLIMATE_CONTROL";
44
+ /** Classify a normalised device summary into an adoption / driver kind. */
45
+ function classifyShcDevice(device) {
46
+ const fromModel = shcKindForModel(device.deviceModel);
47
+ if (fromModel !== "unsupported") return fromModel;
48
+ if (device.services.includes("RoomClimateControl")) return "room-climate";
49
+ if (device.services.includes("PowerSwitch")) return "switch";
50
+ if (device.services.includes("TemperatureLevel") || device.services.includes("ValveTappet")) return "trv";
51
+ return "unsupported";
52
+ }
53
+ function shcKindForModel(deviceModel) {
54
+ if (deviceModel === ROOM_CLIMATE_MODEL) return "room-climate";
55
+ if (deviceModel === "TWR_SWITCH" || deviceModel === "DIMMER_LIGHT" || deviceModel === "PLUG_COMPACT" || deviceModel === "PLUG_COMPACT_DUAL") return "switch";
56
+ if (deviceModel === "HEATING_THERMOSTAT" || deviceModel === "RADIATOR_THERMOSTAT" || deviceModel === "TRV_GEN2_DUAL" || deviceModel === "TRV") return "trv";
57
+ return "unsupported";
58
+ }
59
+ function deviceTypeForShcKind(kind) {
60
+ switch (kind) {
61
+ case "room-climate": return DeviceType.Container;
62
+ case "trv": return DeviceType.Climate;
63
+ case "switch": return DeviceType.Switch;
64
+ }
65
+ }
66
+ /** D1033 — which climate device an SHC kind is: a valve is a radiator, a room a thermostat. */
67
+ function shcClimateRole(kind) {
68
+ return kind === "trv" ? DeviceRole.ClimateRadiator : DeviceRole.ClimateThermostat;
69
+ }
70
+ /** The room-climate accessory's stable-id suffix (`ShcClimateRoomDevice`). */
71
+ var SHC_ROOM_CLIMATE_SUFFIX = "climate";
72
+ /**
73
+ * D1033 — a valve and a room climate share the `climate` type, so a persisted
74
+ * row is told apart by its model when the config carries one, else by the
75
+ * room-climate accessory's stable-id suffix.
76
+ */
77
+ function shcClimateRowKind(row) {
78
+ const model = row.config?.["deviceModel"];
79
+ if (typeof model === "string") {
80
+ const kind = shcKindForModel(model);
81
+ if (kind === "trv" || kind === "room-climate") return kind;
82
+ }
83
+ return row.stableId.endsWith(`-climate`) ? "room-climate" : "trv";
84
+ }
85
+ function capsForShcKind(kind) {
86
+ switch (kind) {
87
+ case "room-climate": return ["climate-control"];
88
+ case "switch": return ["switch"];
89
+ case "trv": return [
90
+ "temperature-sensor",
91
+ "battery",
92
+ "heating-valve"
93
+ ];
94
+ }
95
+ }
96
+ function mapShcModeToHvac(state) {
97
+ if ((state.roomControlMode ?? "").toUpperCase() === "OFF") return "off";
98
+ const upper = (state.operationMode ?? state.mode ?? "").toUpperCase();
99
+ if (upper === "OFF") return "off";
100
+ if (upper === "AUTOMATIC" || upper === "AUTO") return "auto";
101
+ if (upper.includes("COOL")) return "cool";
102
+ if (upper.includes("HEAT")) return "heat";
103
+ return "heat";
104
+ }
105
+ var CLIMATE_MODES = [
106
+ "off",
107
+ "heat",
108
+ "auto"
109
+ ];
110
+ /**
111
+ * Setpoint ends the Smart Home Controller app enforces. RoomClimateControl
112
+ * state does not carry them; a value present on the payload still wins.
113
+ */
114
+ var SHC_SETPOINT_MIN = 5;
115
+ var SHC_SETPOINT_MAX = 30;
116
+ /** Map Bosch RoomClimateControl state JSON → CamStack climate-control slice. */
117
+ function mapRoomClimateStateToSlice(state) {
118
+ const now = Date.now();
119
+ return {
120
+ mode: mapShcModeToHvac(state),
121
+ availableModes: [...CLIMATE_MODES],
122
+ fanMode: "",
123
+ availableFanModes: [],
124
+ preset: "",
125
+ availablePresets: [],
126
+ target: state.setpointTemperature ?? null,
127
+ targetHigh: null,
128
+ targetLow: null,
129
+ targetHumidity: null,
130
+ currentTemp: state.temperature ?? null,
131
+ currentHumidity: null,
132
+ swingVertical: null,
133
+ swingHorizontal: null,
134
+ minTemp: state.minTemperature ?? SHC_SETPOINT_MIN,
135
+ maxTemp: state.maxTemperature ?? SHC_SETPOINT_MAX,
136
+ tempStep: .5,
137
+ lastFetchedAt: now
138
+ };
139
+ }
140
+ /**
141
+ * PUT bodies for a mode change, in order.
142
+ * Off is `roomControlMode` alone — `operationMode: OFF` is rejected.
143
+ * Heat and auto must turn heating on first; combining both fields while
144
+ * the room is off returns WRONG_THERMOSTAT_GROUP_MODE.
145
+ */
146
+ function buildRoomClimateModeBodies(mode) {
147
+ if (mode === "off") return [{
148
+ "@type": "climateControlState",
149
+ roomControlMode: "OFF"
150
+ }];
151
+ return [{
152
+ "@type": "climateControlState",
153
+ roomControlMode: "HEATING"
154
+ }, {
155
+ "@type": "climateControlState",
156
+ operationMode: mode === "auto" ? "AUTOMATIC" : "MANUAL"
157
+ }];
158
+ }
159
+ /** PowerSwitch on the controller is the string `ON` / `OFF`, not a boolean. */
160
+ function readShcSwitchOn(state) {
161
+ if (isShcSwitchOn(state.switchState)) return true;
162
+ return isShcSwitchOn(state.switchStates?.[0]?.switchState);
163
+ }
164
+ function isShcSwitchOn(value) {
165
+ if (value === true) return true;
166
+ return typeof value === "string" && value.toUpperCase() === "ON";
167
+ }
168
+ function buildPowerSwitchPutBody(on) {
169
+ return {
170
+ "@type": "powerSwitchState",
171
+ switchState: on ? "ON" : "OFF"
172
+ };
173
+ }
174
+ /** Body for PUT RoomClimateControl/state when changing the setpoint. */
175
+ function buildRoomClimatePutBody(input) {
176
+ return {
177
+ "@type": "climateControlState",
178
+ setpointTemperature: input.target
179
+ };
180
+ }
181
+ //#endregion
42
182
  //#region src/shc/config.ts
43
183
  /** Operator settings for one SHC integration (local mTLS + optional token). */
44
184
  var shcConfigSchema = object({
@@ -563,9 +703,12 @@ var ShcBaseDevice = class extends BaseDevice {
563
703
  brokerId;
564
704
  deviceModel;
565
705
  stateUnsub = null;
566
- constructor(ctx) {
706
+ constructor(ctx, role) {
567
707
  const persisted = shcDeviceSchema.parse(ctx.persistedConfig ?? {});
568
- super(ctx, shcDeviceSchema, { type: ctx.deviceMeta.type });
708
+ super(ctx, shcDeviceSchema, {
709
+ type: ctx.deviceMeta.type,
710
+ ...role !== void 0 ? { role } : {}
711
+ });
569
712
  this.shcDeviceId = persisted.shcDeviceId;
570
713
  this.brokerId = persisted.brokerId;
571
714
  this.deviceModel = persisted.deviceModel;
@@ -649,127 +792,6 @@ var ShcGatewayDevice = class extends BaseDevice {
649
792
  }
650
793
  };
651
794
  //#endregion
652
- //#region src/shc/shc-domain-mapping.ts
653
- var ROOM_CLIMATE_MODEL = "ROOM_CLIMATE_CONTROL";
654
- /** Classify a normalised device summary into an adoption / driver kind. */
655
- function classifyShcDevice(device) {
656
- const fromModel = shcKindForModel(device.deviceModel);
657
- if (fromModel !== "unsupported") return fromModel;
658
- if (device.services.includes("RoomClimateControl")) return "room-climate";
659
- if (device.services.includes("PowerSwitch")) return "switch";
660
- if (device.services.includes("TemperatureLevel") || device.services.includes("ValveTappet")) return "trv";
661
- return "unsupported";
662
- }
663
- function shcKindForModel(deviceModel) {
664
- if (deviceModel === ROOM_CLIMATE_MODEL) return "room-climate";
665
- if (deviceModel === "TWR_SWITCH" || deviceModel === "DIMMER_LIGHT" || deviceModel === "PLUG_COMPACT" || deviceModel === "PLUG_COMPACT_DUAL") return "switch";
666
- if (deviceModel === "HEATING_THERMOSTAT" || deviceModel === "RADIATOR_THERMOSTAT" || deviceModel === "TRV_GEN2_DUAL" || deviceModel === "TRV") return "trv";
667
- return "unsupported";
668
- }
669
- function deviceTypeForShcKind(kind) {
670
- switch (kind) {
671
- case "room-climate": return DeviceType.Container;
672
- case "trv": return DeviceType.Thermostat;
673
- case "switch": return DeviceType.Switch;
674
- }
675
- }
676
- function capsForShcKind(kind) {
677
- switch (kind) {
678
- case "room-climate": return ["climate-control"];
679
- case "switch": return ["switch"];
680
- case "trv": return [
681
- "temperature-sensor",
682
- "battery",
683
- "heating-valve"
684
- ];
685
- }
686
- }
687
- function mapShcModeToHvac(state) {
688
- if ((state.roomControlMode ?? "").toUpperCase() === "OFF") return "off";
689
- const upper = (state.operationMode ?? state.mode ?? "").toUpperCase();
690
- if (upper === "OFF") return "off";
691
- if (upper === "AUTOMATIC" || upper === "AUTO") return "auto";
692
- if (upper.includes("COOL")) return "cool";
693
- if (upper.includes("HEAT")) return "heat";
694
- return "heat";
695
- }
696
- var CLIMATE_MODES = [
697
- "off",
698
- "heat",
699
- "auto"
700
- ];
701
- /**
702
- * Setpoint ends the Smart Home Controller app enforces. RoomClimateControl
703
- * state does not carry them; a value present on the payload still wins.
704
- */
705
- var SHC_SETPOINT_MIN = 5;
706
- var SHC_SETPOINT_MAX = 30;
707
- /** Map Bosch RoomClimateControl state JSON → CamStack climate-control slice. */
708
- function mapRoomClimateStateToSlice(state) {
709
- const now = Date.now();
710
- return {
711
- mode: mapShcModeToHvac(state),
712
- availableModes: [...CLIMATE_MODES],
713
- fanMode: "",
714
- availableFanModes: [],
715
- preset: "",
716
- availablePresets: [],
717
- target: state.setpointTemperature ?? null,
718
- targetHigh: null,
719
- targetLow: null,
720
- targetHumidity: null,
721
- currentTemp: state.temperature ?? null,
722
- currentHumidity: null,
723
- swingVertical: null,
724
- swingHorizontal: null,
725
- minTemp: state.minTemperature ?? SHC_SETPOINT_MIN,
726
- maxTemp: state.maxTemperature ?? SHC_SETPOINT_MAX,
727
- tempStep: .5,
728
- lastFetchedAt: now
729
- };
730
- }
731
- /**
732
- * PUT bodies for a mode change, in order.
733
- * Off is `roomControlMode` alone — `operationMode: OFF` is rejected.
734
- * Heat and auto must turn heating on first; combining both fields while
735
- * the room is off returns WRONG_THERMOSTAT_GROUP_MODE.
736
- */
737
- function buildRoomClimateModeBodies(mode) {
738
- if (mode === "off") return [{
739
- "@type": "climateControlState",
740
- roomControlMode: "OFF"
741
- }];
742
- return [{
743
- "@type": "climateControlState",
744
- roomControlMode: "HEATING"
745
- }, {
746
- "@type": "climateControlState",
747
- operationMode: mode === "auto" ? "AUTOMATIC" : "MANUAL"
748
- }];
749
- }
750
- /** PowerSwitch on the controller is the string `ON` / `OFF`, not a boolean. */
751
- function readShcSwitchOn(state) {
752
- if (isShcSwitchOn(state.switchState)) return true;
753
- return isShcSwitchOn(state.switchStates?.[0]?.switchState);
754
- }
755
- function isShcSwitchOn(value) {
756
- if (value === true) return true;
757
- return typeof value === "string" && value.toUpperCase() === "ON";
758
- }
759
- function buildPowerSwitchPutBody(on) {
760
- return {
761
- "@type": "powerSwitchState",
762
- switchState: on ? "ON" : "OFF"
763
- };
764
- }
765
- /** Body for PUT RoomClimateControl/state when changing the setpoint. */
766
- function buildRoomClimatePutBody(input) {
767
- return {
768
- "@type": "climateControlState",
769
- setpointTemperature: input.target
770
- };
771
- }
772
- //#endregion
773
795
  //#region src/shc/shc-discovery.ts
774
796
  var GATEWAY_SUFFIX = ":controller";
775
797
  /** The `childNativeId` (and `shcDeviceId`) of the controller box itself, one per integration. */
@@ -800,7 +822,7 @@ function trvCandidate(device) {
800
822
  return {
801
823
  childNativeId: device.id,
802
824
  name: device.name.length > 0 ? device.name : device.id,
803
- type: DeviceType.Thermostat,
825
+ type: DeviceType.Climate,
804
826
  status: "online",
805
827
  metadata: {
806
828
  serialNumber: device.id,
@@ -993,10 +1015,10 @@ var COLD_START = {
993
1015
  };
994
1016
  var ShcRoomClimateDevice = class extends ShcBaseDevice {
995
1017
  constructor(ctx) {
996
- super(ctx);
1018
+ super(ctx, shcClimateRole("room-climate"));
997
1019
  }
998
1020
  registerCaps() {
999
- this.ctx.registerNativeCap(climateControlCapability, {
1021
+ const provider = {
1000
1022
  getStatus: async () => this.runtimeState.getCapState(CLIMATE_CAP) ?? COLD_START,
1001
1023
  setMode: async ({ mode }) => {
1002
1024
  const client = this.requireClient();
@@ -1035,7 +1057,9 @@ var ShcRoomClimateDevice = class extends ShcBaseDevice {
1035
1057
  setTargetHumidity: async () => this.unsupported("climate humidity"),
1036
1058
  setSwingVertical: async () => this.unsupported("climate swing"),
1037
1059
  setSwingHorizontal: async () => this.unsupported("climate swing")
1038
- });
1060
+ };
1061
+ this.ctx.registerNativeCap(climateControlCapability, provider);
1062
+ registerPowerSwitch(this, climateControlCapability, provider);
1039
1063
  this.runtimeState.setCapState(CLIMATE_CAP, COLD_START);
1040
1064
  }
1041
1065
  async refreshFromController() {
@@ -1093,7 +1117,7 @@ var VALVE_COLD = {
1093
1117
  /** Radiator TRV — local temperature, valve position, battery (no room setpoint). */
1094
1118
  var ShcTrvDevice = class extends ShcBaseDevice {
1095
1119
  constructor(ctx) {
1096
- super(ctx);
1120
+ super(ctx, shcClimateRole("trv"));
1097
1121
  }
1098
1122
  registerCaps() {
1099
1123
  this.ctx.registerNativeCap(temperatureSensorCapability, { getStatus: async () => this.runtimeState.getCapState(TEMP_CAP) ?? TEMP_COLD });
@@ -1190,9 +1214,10 @@ var ShcClimateRoomDevice = class extends BaseDevice {
1190
1214
  ...this.integrationId !== void 0 ? { integrationId: this.integrationId } : {}
1191
1215
  };
1192
1216
  return {
1193
- stableIdSuffix: "climate",
1217
+ stableIdSuffix: SHC_ROOM_CLIMATE_SUFFIX,
1194
1218
  meta: {
1195
1219
  type: DeviceType.Climate,
1220
+ role: shcClimateRole("room-climate"),
1196
1221
  name: shcClimateEntityName(containerName),
1197
1222
  linkDeviceId: this.id,
1198
1223
  ...this.integrationId !== void 0 ? { integrationId: this.integrationId } : {}
@@ -1211,7 +1236,8 @@ var ShcClimateRoomDevice = class extends BaseDevice {
1211
1236
  return {
1212
1237
  stableIdSuffix: `trv-${shcStableIdSuffixFromDeviceId(child.shcDeviceId)}`,
1213
1238
  meta: {
1214
- type: DeviceType.Thermostat,
1239
+ type: DeviceType.Climate,
1240
+ role: shcClimateRole("trv"),
1215
1241
  name: child.name,
1216
1242
  linkDeviceId: this.id,
1217
1243
  ...this.integrationId !== void 0 ? { integrationId: this.integrationId } : {}
@@ -13554,7 +13580,7 @@ var require_snapshot_utils = /* @__PURE__ */ __commonJSMin(((exports, module) =>
13554
13580
  //#region ../../node_modules/undici/lib/mock/snapshot-recorder.js
13555
13581
  var require_snapshot_recorder = /* @__PURE__ */ __commonJSMin(((exports, module) => {
13556
13582
  var { writeFile, readFile, mkdir } = __require("node:fs/promises");
13557
- var { dirname, resolve } = __require("node:path");
13583
+ var { dirname: dirname$1, resolve } = __require("node:path");
13558
13584
  var { setTimeout: setTimeout$1, clearTimeout: clearTimeout$1 } = __require("node:timers");
13559
13585
  var { InvalidArgumentError, UndiciError } = require_errors();
13560
13586
  var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
@@ -13840,7 +13866,7 @@ var require_snapshot_recorder = /* @__PURE__ */ __commonJSMin(((exports, module)
13840
13866
  const path = filePath || this.#snapshotPath;
13841
13867
  if (!path) throw new InvalidArgumentError("Snapshot path is required");
13842
13868
  const resolvedPath = resolve(path);
13843
- await mkdir(dirname(resolvedPath), { recursive: true });
13869
+ await mkdir(dirname$1(resolvedPath), { recursive: true });
13844
13870
  const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
13845
13871
  hash,
13846
13872
  snapshot
@@ -23832,10 +23858,14 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23832
23858
  deviceClasses = {
23833
23859
  [DeviceType.Gateway]: ShcGatewayDevice,
23834
23860
  [DeviceType.Container]: ShcClimateRoomDevice,
23835
- [DeviceType.Climate]: ShcRoomClimateDevice,
23836
- [DeviceType.Thermostat]: ShcTrvDevice,
23861
+ [DeviceType.Climate]: ShcTrvDevice,
23837
23862
  [DeviceType.Switch]: ShcSwitchDevice
23838
23863
  };
23864
+ /** D1033 — a valve and a room climate are both `climate`: rebuild each with its class. */
23865
+ deviceClassForRow(row) {
23866
+ if (row.type !== DeviceType.Climate) return super.deviceClassForRow(row);
23867
+ return shcClimateRowKind(row) === "room-climate" ? ShcRoomClimateDevice : ShcTrvDevice;
23868
+ }
23839
23869
  clients = null;
23840
23870
  instances = null;
23841
23871
  constructor() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-bosch",
3
- "version": "0.1.50",
3
+ "version": "0.1.52",
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",
@@ -112,6 +112,9 @@
112
112
  "execution": {
113
113
  "placement": "hub-only"
114
114
  },
115
+ "python": {
116
+ "requirements": "./python/requirements.txt"
117
+ },
115
118
  "capabilities": [
116
119
  {
117
120
  "name": "device-provider"
@@ -138,11 +141,12 @@
138
141
  }
139
142
  ],
140
143
  "requires": {
141
- "server": ">=1.2.834"
144
+ "server": ">=1.2.841"
142
145
  }
143
146
  },
144
147
  "files": [
145
148
  "dist",
149
+ "python",
146
150
  "i18n",
147
151
  "assets",
148
152
  "README.md",