@camstack/addon-provider-bosch 0.1.51 → 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,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_bosch_instances = require("../bosch-instances-CYBnIcHg.js");
3
- const require_account_integration_offline = require("../account-integration-offline-7CH3r5jI.js");
2
+ const require_bosch_instances = require("../bosch-instances-LWbVnsaD.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-BEzGeJcl.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_tls = require("node:tls");
6
6
  let node_zlib = require("node:zlib");
@@ -1,5 +1,5 @@
1
- import { $ as EventCategory, A as RecordIntegrationInstances, C as powerMeterCapability, E as runningStateCapability, F as entryBody, G as object, I as isSecretMarker, J as union, K as record, L as isSecretMarkerLike, M as customAction, N as defineCustomActions, P as defineEntryKind, Q as errMsg, R as rowsDepsOver, S as numericSensorCapability, T as runningFromDishwasherOperation, U as literal, V as array, X as DeviceRole, Y as unknown, Z as DeviceType, a as BaseDevice, b as ingestReading, c as RUNNING_STATE_COLD, f as consumablesCapability, g as eventEmitterCapability, h as enumSensorCapability, i as testBoschInstance, j as createEvent, m as dishwasherCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, q as string, r as keepBodyKeys, s as DishwasherOperationStateSchema, t as boschRowsMigration, u as binaryCapability, w as quantityFromUnit, x as integrationInstancesCapability, z as _enum } from "../bosch-instances-CdvcBYye.mjs";
2
- import { a as loginFailureForAttempt, c as HOME_CONNECT_REDIRECT_URI, i as createSignInAttemptStore, n as cascadeRemoveDevicesForIntegration, o as parseOAuthCallback, r as buildSignInGuideFields, t as setIntegrationDevicesOnline } from "../account-integration-offline-B1Wgr_rT.mjs";
1
+ import { $ as errMsg, B as _enum, C as powerMeterCapability, D as runningStateCapability, E as runningFromDishwasherOperation, F as defineEntryKind, H as array, I as entryBody, J as string, K as object, L as isSecretMarker, M as createEvent, N as customAction, P as defineCustomActions, Q as DeviceType, R as isSecretMarkerLike, S as numericSensorCapability, W as literal, X as unknown, Y as union, Z as DeviceRole, a as BaseDevice, b as ingestReading, c as RUNNING_STATE_COLD, et as EventCategory, f as consumablesCapability, g as eventEmitterCapability, h as enumSensorCapability, i as testBoschInstance, j as RecordIntegrationInstances, m as dishwasherCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, q as record, r as keepBodyKeys, s as DishwasherOperationStateSchema, t as boschRowsMigration, u as binaryCapability, w as quantityFromUnit, x as integrationInstancesCapability, z as rowsDepsOver } from "../bosch-instances-DSE6IXdU.mjs";
2
+ import { a as loginFailureForAttempt, c as HOME_CONNECT_REDIRECT_URI, i as createSignInAttemptStore, n as cascadeRemoveDevicesForIntegration, o as parseOAuthCallback, r as buildSignInGuideFields, t as setIntegrationDevicesOnline } from "../account-integration-offline-D5vjmfia.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { connect } from "node:tls";
5
5
  import { inflateRawSync } from "node:zlib";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_bosch_instances = require("../bosch-instances-CYBnIcHg.js");
3
- const require_account_integration_offline = require("../account-integration-offline-7CH3r5jI.js");
2
+ const require_bosch_instances = require("../bosch-instances-LWbVnsaD.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-BEzGeJcl.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_child_process = require("node:child_process");
6
6
  let node_fs = require("node:fs");
@@ -3316,7 +3316,10 @@ var HomeComClimateDevice = class extends require_bosch_instances.BaseDevice {
3316
3316
  #circuitId;
3317
3317
  constructor(ctx) {
3318
3318
  const persisted = homeComClimateSchema.parse(ctx.persistedConfig ?? {});
3319
- super(ctx, homeComClimateSchema, { type: ctx.deviceMeta.type });
3319
+ super(ctx, homeComClimateSchema, {
3320
+ type: ctx.deviceMeta.type,
3321
+ role: require_bosch_instances.DeviceRole.ClimateHeatPumpCircuit
3322
+ });
3320
3323
  this.#brokerId = persisted.brokerId;
3321
3324
  this.#circuitId = persisted.circuitId;
3322
3325
  this.online = true;
@@ -3348,7 +3351,7 @@ var HomeComClimateDevice = class extends require_bosch_instances.BaseDevice {
3348
3351
  return client;
3349
3352
  }
3350
3353
  registerClimateCap() {
3351
- this.ctx.registerNativeCap(require_bosch_instances.climateControlCapability, {
3354
+ const provider = {
3352
3355
  getStatus: async () => {
3353
3356
  this.refreshFromSnapshot();
3354
3357
  return this.runtimeState.getCapState(CLIMATE_CAP) ?? COLD_START$2;
@@ -3383,7 +3386,9 @@ var HomeComClimateDevice = class extends require_bosch_instances.BaseDevice {
3383
3386
  await this.requireClient().setSwingHorizontal(this.#circuitId, on);
3384
3387
  this.refreshFromSnapshot();
3385
3388
  }
3386
- });
3389
+ };
3390
+ this.ctx.registerNativeCap(require_bosch_instances.climateControlCapability, provider);
3391
+ require_bosch_instances.registerPowerSwitch(this, require_bosch_instances.climateControlCapability, provider);
3387
3392
  }
3388
3393
  };
3389
3394
  function hvacModeToHomeCom(mode) {
@@ -3449,7 +3454,7 @@ var HomeComWaterHeaterDevice = class extends require_bosch_instances.BaseDevice
3449
3454
  return client;
3450
3455
  }
3451
3456
  registerWaterHeaterCap() {
3452
- this.ctx.registerNativeCap(require_bosch_instances.waterHeaterCapability, {
3457
+ const provider = {
3453
3458
  getStatus: async () => {
3454
3459
  this.refreshFromSnapshot();
3455
3460
  return this.runtimeState.getCapState(WH_CAP) ?? COLD_START$1;
@@ -3465,7 +3470,9 @@ var HomeComWaterHeaterDevice = class extends require_bosch_instances.BaseDevice
3465
3470
  setAway: async () => {
3466
3471
  throw new Error("HomeCom DHW: away mode not supported");
3467
3472
  }
3468
- });
3473
+ };
3474
+ this.ctx.registerNativeCap(require_bosch_instances.waterHeaterCapability, provider);
3475
+ require_bosch_instances.registerPowerSwitch(this, require_bosch_instances.waterHeaterCapability, provider);
3469
3476
  }
3470
3477
  };
3471
3478
  //#endregion
@@ -1,5 +1,5 @@
1
- import { $ as EventCategory, A as RecordIntegrationInstances, B as _null, F as entryBody, G as object, H as boolean, J as union, K as record, M as customAction, N as defineCustomActions, P as defineEntryKind, Q as errMsg, R as rowsDepsOver, U as literal, V as array, W as number, Z as DeviceType, _ as heatPumpControlCapability, a as BaseDevice, d as climateControlCapability, i as testBoschInstance, k as waterHeaterCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, q as string, r as keepBodyKeys, t as boschRowsMigration, v as heatPumpStatusCapability, x as integrationInstancesCapability, z as _enum } from "../bosch-instances-CdvcBYye.mjs";
2
- import { a as loginFailureForAttempt, i as createSignInAttemptStore, n as cascadeRemoveDevicesForIntegration, o as parseOAuthCallback, r as buildSignInGuideFields, s as HOMECOM_REDIRECT_URI, t as setIntegrationDevicesOnline } from "../account-integration-offline-B1Wgr_rT.mjs";
1
+ import { $ as errMsg, A as waterHeaterCapability, B as _enum, F as defineEntryKind, G as number, H as array, I as entryBody, J as string, K as object, N as customAction, P as defineCustomActions, Q as DeviceType, T as registerPowerSwitch, U as boolean, V as _null, W as literal, Y as union, Z as DeviceRole, _ as heatPumpControlCapability, a as BaseDevice, d as climateControlCapability, et as EventCategory, i as testBoschInstance, j as RecordIntegrationInstances, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, q as record, r as keepBodyKeys, t as boschRowsMigration, v as heatPumpStatusCapability, x as integrationInstancesCapability, z as rowsDepsOver } from "../bosch-instances-DSE6IXdU.mjs";
2
+ import { a as loginFailureForAttempt, i as createSignInAttemptStore, n as cascadeRemoveDevicesForIntegration, o as parseOAuthCallback, r as buildSignInGuideFields, s as HOMECOM_REDIRECT_URI, t as setIntegrationDevicesOnline } from "../account-integration-offline-D5vjmfia.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { spawn } from "node:child_process";
5
5
  import { existsSync } from "node:fs";
@@ -3315,7 +3315,10 @@ var HomeComClimateDevice = class extends BaseDevice {
3315
3315
  #circuitId;
3316
3316
  constructor(ctx) {
3317
3317
  const persisted = homeComClimateSchema.parse(ctx.persistedConfig ?? {});
3318
- super(ctx, homeComClimateSchema, { type: ctx.deviceMeta.type });
3318
+ super(ctx, homeComClimateSchema, {
3319
+ type: ctx.deviceMeta.type,
3320
+ role: DeviceRole.ClimateHeatPumpCircuit
3321
+ });
3319
3322
  this.#brokerId = persisted.brokerId;
3320
3323
  this.#circuitId = persisted.circuitId;
3321
3324
  this.online = true;
@@ -3347,7 +3350,7 @@ var HomeComClimateDevice = class extends BaseDevice {
3347
3350
  return client;
3348
3351
  }
3349
3352
  registerClimateCap() {
3350
- this.ctx.registerNativeCap(climateControlCapability, {
3353
+ const provider = {
3351
3354
  getStatus: async () => {
3352
3355
  this.refreshFromSnapshot();
3353
3356
  return this.runtimeState.getCapState(CLIMATE_CAP) ?? COLD_START$2;
@@ -3382,7 +3385,9 @@ var HomeComClimateDevice = class extends BaseDevice {
3382
3385
  await this.requireClient().setSwingHorizontal(this.#circuitId, on);
3383
3386
  this.refreshFromSnapshot();
3384
3387
  }
3385
- });
3388
+ };
3389
+ this.ctx.registerNativeCap(climateControlCapability, provider);
3390
+ registerPowerSwitch(this, climateControlCapability, provider);
3386
3391
  }
3387
3392
  };
3388
3393
  function hvacModeToHomeCom(mode) {
@@ -3448,7 +3453,7 @@ var HomeComWaterHeaterDevice = class extends BaseDevice {
3448
3453
  return client;
3449
3454
  }
3450
3455
  registerWaterHeaterCap() {
3451
- this.ctx.registerNativeCap(waterHeaterCapability, {
3456
+ const provider = {
3452
3457
  getStatus: async () => {
3453
3458
  this.refreshFromSnapshot();
3454
3459
  return this.runtimeState.getCapState(WH_CAP) ?? COLD_START$1;
@@ -3464,7 +3469,9 @@ var HomeComWaterHeaterDevice = class extends BaseDevice {
3464
3469
  setAway: async () => {
3465
3470
  throw new Error("HomeCom DHW: away mode not supported");
3466
3471
  }
3467
- });
3472
+ };
3473
+ this.ctx.registerNativeCap(waterHeaterCapability, provider);
3474
+ registerPowerSwitch(this, waterHeaterCapability, provider);
3468
3475
  }
3469
3476
  };
3470
3477
  //#endregion
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() {