@camstack/addon-provider-bosch 0.1.47 → 0.1.49

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-D0eF-Jjy.js");
3
- const require_account_integration_offline = require("../account-integration-offline-C6tzFB-H.js");
2
+ const require_bosch_instances = require("../bosch-instances-BHFcR1TK.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-Dcx0sq_v.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 { A as RecordIntegrationInstances, B as array, C as powerMeterCapability, E as runningStateCapability, F as entryBody, G as string, I as isSecretMarker, J as DeviceRole, K as union, L as isSecretMarkerLike, M as customAction, N as defineCustomActions, P as defineEntryKind, R as rowsDepsOver, S as numericSensorCapability, T as runningFromDishwasherOperation, U as object, V as literal, W as record, X as errMsg, Y as DeviceType, Z as EventCategory, 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 unknown, r as keepBodyKeys, s as DishwasherOperationStateSchema, t as boschRowsMigration, u as binaryCapability, w as quantityFromUnit, x as integrationInstancesCapability, z as _enum } from "../bosch-instances-DAqkAsRO.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-Bie4aS5e.mjs";
1
+ import { A as RecordIntegrationInstances, B as array, C as powerMeterCapability, E as runningStateCapability, F as entryBody, G as string, I as isSecretMarker, J as DeviceRole, K as union, L as isSecretMarkerLike, M as customAction, N as defineCustomActions, P as defineEntryKind, R as rowsDepsOver, S as numericSensorCapability, T as runningFromDishwasherOperation, U as object, V as literal, W as record, X as errMsg, Y as DeviceType, Z as EventCategory, 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 unknown, r as keepBodyKeys, s as DishwasherOperationStateSchema, t as boschRowsMigration, u as binaryCapability, w as quantityFromUnit, x as integrationInstancesCapability, z as _enum } from "../bosch-instances-B2e4DAI1.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-BKDol6kz.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-D0eF-Jjy.js");
3
- const require_account_integration_offline = require("../account-integration-offline-C6tzFB-H.js");
2
+ const require_bosch_instances = require("../bosch-instances-BHFcR1TK.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-Dcx0sq_v.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  //#region src/homecom/config.ts
6
6
  var HOMECOM_BRANDS = ["bosch", "buderus"];
@@ -735,6 +735,14 @@ var HEAT_PUMP_PATHS = {
735
735
  pressureBar: "/heatSources/hs1/waterPressure",
736
736
  starts: "/heatSources/hs1/numberOfStarts"
737
737
  },
738
+ /**
739
+ * Resources only a heat pump's heat source has. A gateway that answers one of them with
740
+ * anything but 404 (a 403 or a 500 included) has a heat pump it will not let us read.
741
+ */
742
+ identity: {
743
+ heatPumpType: "/heatSources/hs1/heatPumpType",
744
+ heatPumpPower: "/heatSources/hs1/heatPumpPower"
745
+ },
738
746
  circuit: {
739
747
  operationMode: "operationMode",
740
748
  heatCoolMode: "heatCoolMode",
@@ -763,6 +771,7 @@ async function readHeatPump(input) {
763
771
  const circuits = await Promise.all([...input.circuitIds.hc.map((id) => readHc(get, id)), ...input.circuitIds.dhw.map((id) => readDhwCircuit(get, id))]);
764
772
  const r = HEAT_PUMP_PATHS.readings;
765
773
  const s = HEAT_PUMP_PATHS.system;
774
+ const heatSourceAnswered = await anyAnswers(input.answers, input.onFailure, [HEAT_PUMP_PATHS.identity.heatPumpType, HEAT_PUMP_PATHS.identity.heatPumpPower]);
766
775
  const [silent, away, additionalHeater, holiday] = await Promise.all([
767
776
  setting(get, s.silent),
768
777
  setting(get, s.away),
@@ -785,6 +794,7 @@ async function readHeatPump(input) {
785
794
  ]);
786
795
  return {
787
796
  circuits,
797
+ heatSourceAnswered,
788
798
  silent,
789
799
  away,
790
800
  additionalHeater,
@@ -801,6 +811,17 @@ async function readHeatPump(input) {
801
811
  starts
802
812
  };
803
813
  }
814
+ async function anyAnswers(answers, onFailure, paths) {
815
+ if (answers === void 0) return false;
816
+ return (await Promise.all(paths.map(async (path) => {
817
+ try {
818
+ return await answers(path);
819
+ } catch (error) {
820
+ onFailure?.(path, error);
821
+ return false;
822
+ }
823
+ }))).some(Boolean);
824
+ }
804
825
  /** Any failure of a heat-pump read (HTTP 4xx/5xx, a throw) is "not answered", never an error. */
805
826
  function safeGet(get, onFailure) {
806
827
  return async (path) => {
@@ -828,16 +849,28 @@ var HEAT_PUMP_ONLY_READINGS = [
828
849
  *
829
850
  * A system mode counts when the gateway answered it: silent, away, the
830
851
  * additional heater, or holiday. An empty string is an answer (holiday often
831
- * comes back empty); a path that did not answer is not. HomeCom returns no
832
- * value for silent mode and the auxiliary heater on some heat-pump firmware,
833
- * so away and holiday alone are enough. A heat-pump-only reading counts too
834
- * (thermal power, electrical power, compressor modulation). Supply, return,
835
- * outdoor, pressure, starts and the energy meters do not: a gas boiler's heat
836
- * source answers flow, pressure and starts, and that is not a heat pump.
852
+ * comes back empty); a path that did not answer is not. A heat-pump-only
853
+ * reading counts too (thermal power, electrical power, compressor modulation).
854
+ * So does a circuit whose `heatCoolMode` can be cooling, and a heat source that
855
+ * answers a heat-pump-only resource with any status but 404: some gateways
856
+ * refuse (403) or fail (500) the whole system tree and still serve the
857
+ * circuits. Supply, return, outdoor, pressure, starts and the energy meters do
858
+ * not count: a gas boiler's heat source answers flow, pressure and starts, and
859
+ * that is not a heat pump.
837
860
  */
838
861
  function hasSystemDevice(reading) {
839
862
  if (SYSTEM_MODE_KEYS.some((key) => reading[key] !== null)) return true;
840
- return HEAT_PUMP_ONLY_READINGS.some((key) => reading[key] !== null);
863
+ if (HEAT_PUMP_ONLY_READINGS.some((key) => reading[key] !== null)) return true;
864
+ if (reading.heatSourceAnswered) return true;
865
+ return reading.circuits.some((c) => c.kind === "hc" && offersCooling(c.heatCoolMode));
866
+ }
867
+ /**
868
+ * A boiler circuit can carry a `heatCoolMode` that only ever says heating, so the mode alone
869
+ * is no proof. A circuit that can switch to (or already is in) cooling is a heat pump's.
870
+ */
871
+ function offersCooling(setting) {
872
+ if (setting === null) return false;
873
+ return setting.value === "cooling" || setting.allowed.includes("cooling");
841
874
  }
842
875
  /** Which heat-pump paths this gateway answered. One log per gateway corrects the path table. */
843
876
  function heatPumpPathCensus(reading) {
@@ -1376,6 +1409,17 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
1376
1409
  if (!res.ok) throw new Error(`HomeCom API ${path} failed: HTTP ${res.status}`);
1377
1410
  return res.body;
1378
1411
  }
1412
+ /**
1413
+ * Evidence that the resource exists: a 2xx, or the 403 the gateway gives a resource it
1414
+ * serves but will not show. 404 is "absent". Anything else (429, 5xx) is a transient
1415
+ * failure, so it is NOT evidence either way: it is logged and counts as unknown.
1416
+ */
1417
+ async function resourceAnswers(path) {
1418
+ const res = await authed(homeComResourceUrl(gatewayId, path), { method: "GET" });
1419
+ if (res.ok || res.status === 403) return true;
1420
+ if (res.status !== 404) logHeatPumpFailure(path, /* @__PURE__ */ new Error(`HTTP ${res.status}`));
1421
+ return false;
1422
+ }
1379
1423
  async function putWrite(write) {
1380
1424
  const res = await authed(homeComResourceUrl(gatewayId, write.path), {
1381
1425
  method: "PUT",
@@ -1457,6 +1501,7 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
1457
1501
  const heatPump = await readHeatPump({
1458
1502
  get: getResource,
1459
1503
  onFailure: logHeatPumpFailure,
1504
+ answers: resourceAnswers,
1460
1505
  circuitIds: {
1461
1506
  hc: snapshot.circuits.map((circuit) => circuit.id),
1462
1507
  dhw: dhwId === void 0 ? [] : [dhwId]
@@ -1851,6 +1896,15 @@ var HomeComConnectionResolver = class {
1851
1896
  this.#surfaces.clear();
1852
1897
  }
1853
1898
  };
1899
+ /** Native id of the gateway pick: a Gateway device, sibling of the circuits. */
1900
+ var HOMECOM_GATEWAY_NATIVE_ID = "gateway:0";
1901
+ function gatewayDiscoverable(snapshot) {
1902
+ return {
1903
+ nativeId: HOMECOM_GATEWAY_NATIVE_ID,
1904
+ name: snapshot.gateway.name.length > 0 ? snapshot.gateway.name : "HomeCom gateway",
1905
+ kind: "gateway"
1906
+ };
1907
+ }
1854
1908
  var BOILER_DHW_NAME = "Domestic hot water";
1855
1909
  function circuitDiscoverable(circuit) {
1856
1910
  return {
@@ -1868,18 +1922,25 @@ function dhwDiscoverable(name) {
1868
1922
  };
1869
1923
  }
1870
1924
  /**
1871
- * A gateway with a heat pump is one pick. A boiler still offers each circuit
1925
+ * The gateway itself is always the first pick. A gateway with a heat pump is one more pick. A boiler still offers each circuit
1872
1926
  * and the hot-water row on its own.
1873
1927
  */
1874
1928
  function buildDiscoverables(snapshot) {
1875
1929
  const circuits = snapshot.circuits.map(circuitDiscoverable);
1876
- if (snapshot.heatPump !== void 0) return [{
1877
- nativeId: "hp:0",
1878
- name: "Heat pump",
1879
- kind: "heat-pump",
1880
- children: [...circuits, ...snapshot.dhw === null ? [] : [dhwDiscoverable(HOMECOM_HOT_WATER_CHILD_NAME)]]
1881
- }];
1882
- return [...circuits, ...snapshot.dhw === null ? [] : [dhwDiscoverable(BOILER_DHW_NAME)]];
1930
+ if (snapshot.heatPump !== void 0) {
1931
+ const children = [...circuits, ...snapshot.dhw === null ? [] : [dhwDiscoverable(HOMECOM_HOT_WATER_CHILD_NAME)]];
1932
+ return [gatewayDiscoverable(snapshot), {
1933
+ nativeId: "hp:0",
1934
+ name: "Heat pump",
1935
+ kind: "heat-pump",
1936
+ children
1937
+ }];
1938
+ }
1939
+ return [
1940
+ gatewayDiscoverable(snapshot),
1941
+ ...circuits,
1942
+ ...snapshot.dhw === null ? [] : [dhwDiscoverable(BOILER_DHW_NAME)]
1943
+ ];
1883
1944
  }
1884
1945
  var homeComConnections = new HomeComConnectionResolver();
1885
1946
  //#endregion
@@ -1962,6 +2023,10 @@ function shapeOf(kind) {
1962
2023
  type: require_bosch_instances.DeviceType.WaterHeater,
1963
2024
  caps: ["water-heater"]
1964
2025
  };
2026
+ case "gateway": return {
2027
+ type: require_bosch_instances.DeviceType.Gateway,
2028
+ caps: []
2029
+ };
1965
2030
  case "heat-pump": return {
1966
2031
  type: require_bosch_instances.DeviceType.HeatPump,
1967
2032
  caps: ["heat-pump-status", "heat-pump-control"]
@@ -2809,6 +2874,35 @@ var HomeComWaterHeaterDevice = class extends require_bosch_instances.BaseDevice
2809
2874
  }
2810
2875
  };
2811
2876
  //#endregion
2877
+ //#region src/homecom/devices/homecom-gateway-device.ts
2878
+ var homeComGatewaySchema = require_bosch_instances.object({
2879
+ nativeId: require_bosch_instances.string().min(1),
2880
+ brokerId: require_bosch_instances.string().min(1),
2881
+ system: require_bosch_instances.literal("homecom").optional(),
2882
+ integrationId: require_bosch_instances.string().optional()
2883
+ });
2884
+ /**
2885
+ * The HomeCom gateway, as a {@link DeviceType.Gateway}. Its connection lives on the
2886
+ * INTEGRATION; this device is the gateway's presence in the device list and follows the
2887
+ * integration's online state (its connect and disconnect callbacks flip it).
2888
+ *
2889
+ * It registers no action: the cloud account offers no reboot, no update and no local page,
2890
+ * so there is nothing real to expose. The circuits, the hot water and the heat pump are NOT
2891
+ * children of it; each is adopted as its own top-level device of the same integration.
2892
+ */
2893
+ var HomeComGatewayDevice = class extends require_bosch_instances.BaseDevice {
2894
+ features = [];
2895
+ constructor(ctx) {
2896
+ const cfg = homeComGatewaySchema.parse(ctx.persistedConfig ?? {});
2897
+ super(ctx, homeComGatewaySchema, { type: require_bosch_instances.DeviceType.Gateway });
2898
+ this.online = homeComConnections.has(cfg.brokerId);
2899
+ this.updateSourceInfo({
2900
+ id: cfg.nativeId,
2901
+ system: "homecom"
2902
+ });
2903
+ }
2904
+ };
2905
+ //#endregion
2812
2906
  //#region src/homecom/devices/homecom-heat-pump-device.ts
2813
2907
  var homeComHeatPumpSchema = require_bosch_instances.object({
2814
2908
  nativeId: require_bosch_instances.string().min(1),
@@ -2959,7 +3053,8 @@ var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDevice
2959
3053
  deviceClasses = {
2960
3054
  [require_bosch_instances.DeviceType.Climate]: HomeComClimateDevice,
2961
3055
  [require_bosch_instances.DeviceType.WaterHeater]: HomeComWaterHeaterDevice,
2962
- [require_bosch_instances.DeviceType.HeatPump]: HomeComHeatPumpDevice
3056
+ [require_bosch_instances.DeviceType.HeatPump]: HomeComHeatPumpDevice,
3057
+ [require_bosch_instances.DeviceType.Gateway]: HomeComGatewayDevice
2963
3058
  };
2964
3059
  clients = null;
2965
3060
  instances = null;
@@ -1,5 +1,5 @@
1
- import { A as RecordIntegrationInstances, B as array, F as entryBody, G as string, H as number, M as customAction, N as defineCustomActions, P as defineEntryKind, R as rowsDepsOver, U as object, V as literal, X as errMsg, Y as DeviceType, Z as EventCategory, _ as heatPumpControlCapability, a as BaseDevice, d as climateControlCapability, i as testBoschInstance, k as waterHeaterCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, r as keepBodyKeys, t as boschRowsMigration, v as heatPumpStatusCapability, x as integrationInstancesCapability, z as _enum } from "../bosch-instances-DAqkAsRO.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-Bie4aS5e.mjs";
1
+ import { A as RecordIntegrationInstances, B as array, F as entryBody, G as string, H as number, M as customAction, N as defineCustomActions, P as defineEntryKind, R as rowsDepsOver, U as object, V as literal, X as errMsg, Y as DeviceType, Z as EventCategory, _ as heatPumpControlCapability, a as BaseDevice, d as climateControlCapability, i as testBoschInstance, k as waterHeaterCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, r as keepBodyKeys, t as boschRowsMigration, v as heatPumpStatusCapability, x as integrationInstancesCapability, z as _enum } from "../bosch-instances-B2e4DAI1.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-BKDol6kz.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  //#region src/homecom/config.ts
5
5
  var HOMECOM_BRANDS = ["bosch", "buderus"];
@@ -734,6 +734,14 @@ var HEAT_PUMP_PATHS = {
734
734
  pressureBar: "/heatSources/hs1/waterPressure",
735
735
  starts: "/heatSources/hs1/numberOfStarts"
736
736
  },
737
+ /**
738
+ * Resources only a heat pump's heat source has. A gateway that answers one of them with
739
+ * anything but 404 (a 403 or a 500 included) has a heat pump it will not let us read.
740
+ */
741
+ identity: {
742
+ heatPumpType: "/heatSources/hs1/heatPumpType",
743
+ heatPumpPower: "/heatSources/hs1/heatPumpPower"
744
+ },
737
745
  circuit: {
738
746
  operationMode: "operationMode",
739
747
  heatCoolMode: "heatCoolMode",
@@ -762,6 +770,7 @@ async function readHeatPump(input) {
762
770
  const circuits = await Promise.all([...input.circuitIds.hc.map((id) => readHc(get, id)), ...input.circuitIds.dhw.map((id) => readDhwCircuit(get, id))]);
763
771
  const r = HEAT_PUMP_PATHS.readings;
764
772
  const s = HEAT_PUMP_PATHS.system;
773
+ const heatSourceAnswered = await anyAnswers(input.answers, input.onFailure, [HEAT_PUMP_PATHS.identity.heatPumpType, HEAT_PUMP_PATHS.identity.heatPumpPower]);
765
774
  const [silent, away, additionalHeater, holiday] = await Promise.all([
766
775
  setting(get, s.silent),
767
776
  setting(get, s.away),
@@ -784,6 +793,7 @@ async function readHeatPump(input) {
784
793
  ]);
785
794
  return {
786
795
  circuits,
796
+ heatSourceAnswered,
787
797
  silent,
788
798
  away,
789
799
  additionalHeater,
@@ -800,6 +810,17 @@ async function readHeatPump(input) {
800
810
  starts
801
811
  };
802
812
  }
813
+ async function anyAnswers(answers, onFailure, paths) {
814
+ if (answers === void 0) return false;
815
+ return (await Promise.all(paths.map(async (path) => {
816
+ try {
817
+ return await answers(path);
818
+ } catch (error) {
819
+ onFailure?.(path, error);
820
+ return false;
821
+ }
822
+ }))).some(Boolean);
823
+ }
803
824
  /** Any failure of a heat-pump read (HTTP 4xx/5xx, a throw) is "not answered", never an error. */
804
825
  function safeGet(get, onFailure) {
805
826
  return async (path) => {
@@ -827,16 +848,28 @@ var HEAT_PUMP_ONLY_READINGS = [
827
848
  *
828
849
  * A system mode counts when the gateway answered it: silent, away, the
829
850
  * additional heater, or holiday. An empty string is an answer (holiday often
830
- * comes back empty); a path that did not answer is not. HomeCom returns no
831
- * value for silent mode and the auxiliary heater on some heat-pump firmware,
832
- * so away and holiday alone are enough. A heat-pump-only reading counts too
833
- * (thermal power, electrical power, compressor modulation). Supply, return,
834
- * outdoor, pressure, starts and the energy meters do not: a gas boiler's heat
835
- * source answers flow, pressure and starts, and that is not a heat pump.
851
+ * comes back empty); a path that did not answer is not. A heat-pump-only
852
+ * reading counts too (thermal power, electrical power, compressor modulation).
853
+ * So does a circuit whose `heatCoolMode` can be cooling, and a heat source that
854
+ * answers a heat-pump-only resource with any status but 404: some gateways
855
+ * refuse (403) or fail (500) the whole system tree and still serve the
856
+ * circuits. Supply, return, outdoor, pressure, starts and the energy meters do
857
+ * not count: a gas boiler's heat source answers flow, pressure and starts, and
858
+ * that is not a heat pump.
836
859
  */
837
860
  function hasSystemDevice(reading) {
838
861
  if (SYSTEM_MODE_KEYS.some((key) => reading[key] !== null)) return true;
839
- return HEAT_PUMP_ONLY_READINGS.some((key) => reading[key] !== null);
862
+ if (HEAT_PUMP_ONLY_READINGS.some((key) => reading[key] !== null)) return true;
863
+ if (reading.heatSourceAnswered) return true;
864
+ return reading.circuits.some((c) => c.kind === "hc" && offersCooling(c.heatCoolMode));
865
+ }
866
+ /**
867
+ * A boiler circuit can carry a `heatCoolMode` that only ever says heating, so the mode alone
868
+ * is no proof. A circuit that can switch to (or already is in) cooling is a heat pump's.
869
+ */
870
+ function offersCooling(setting) {
871
+ if (setting === null) return false;
872
+ return setting.value === "cooling" || setting.allowed.includes("cooling");
840
873
  }
841
874
  /** Which heat-pump paths this gateway answered. One log per gateway corrects the path table. */
842
875
  function heatPumpPathCensus(reading) {
@@ -1375,6 +1408,17 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
1375
1408
  if (!res.ok) throw new Error(`HomeCom API ${path} failed: HTTP ${res.status}`);
1376
1409
  return res.body;
1377
1410
  }
1411
+ /**
1412
+ * Evidence that the resource exists: a 2xx, or the 403 the gateway gives a resource it
1413
+ * serves but will not show. 404 is "absent". Anything else (429, 5xx) is a transient
1414
+ * failure, so it is NOT evidence either way: it is logged and counts as unknown.
1415
+ */
1416
+ async function resourceAnswers(path) {
1417
+ const res = await authed(homeComResourceUrl(gatewayId, path), { method: "GET" });
1418
+ if (res.ok || res.status === 403) return true;
1419
+ if (res.status !== 404) logHeatPumpFailure(path, /* @__PURE__ */ new Error(`HTTP ${res.status}`));
1420
+ return false;
1421
+ }
1378
1422
  async function putWrite(write) {
1379
1423
  const res = await authed(homeComResourceUrl(gatewayId, write.path), {
1380
1424
  method: "PUT",
@@ -1456,6 +1500,7 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
1456
1500
  const heatPump = await readHeatPump({
1457
1501
  get: getResource,
1458
1502
  onFailure: logHeatPumpFailure,
1503
+ answers: resourceAnswers,
1459
1504
  circuitIds: {
1460
1505
  hc: snapshot.circuits.map((circuit) => circuit.id),
1461
1506
  dhw: dhwId === void 0 ? [] : [dhwId]
@@ -1850,6 +1895,15 @@ var HomeComConnectionResolver = class {
1850
1895
  this.#surfaces.clear();
1851
1896
  }
1852
1897
  };
1898
+ /** Native id of the gateway pick: a Gateway device, sibling of the circuits. */
1899
+ var HOMECOM_GATEWAY_NATIVE_ID = "gateway:0";
1900
+ function gatewayDiscoverable(snapshot) {
1901
+ return {
1902
+ nativeId: HOMECOM_GATEWAY_NATIVE_ID,
1903
+ name: snapshot.gateway.name.length > 0 ? snapshot.gateway.name : "HomeCom gateway",
1904
+ kind: "gateway"
1905
+ };
1906
+ }
1853
1907
  var BOILER_DHW_NAME = "Domestic hot water";
1854
1908
  function circuitDiscoverable(circuit) {
1855
1909
  return {
@@ -1867,18 +1921,25 @@ function dhwDiscoverable(name) {
1867
1921
  };
1868
1922
  }
1869
1923
  /**
1870
- * A gateway with a heat pump is one pick. A boiler still offers each circuit
1924
+ * The gateway itself is always the first pick. A gateway with a heat pump is one more pick. A boiler still offers each circuit
1871
1925
  * and the hot-water row on its own.
1872
1926
  */
1873
1927
  function buildDiscoverables(snapshot) {
1874
1928
  const circuits = snapshot.circuits.map(circuitDiscoverable);
1875
- if (snapshot.heatPump !== void 0) return [{
1876
- nativeId: "hp:0",
1877
- name: "Heat pump",
1878
- kind: "heat-pump",
1879
- children: [...circuits, ...snapshot.dhw === null ? [] : [dhwDiscoverable(HOMECOM_HOT_WATER_CHILD_NAME)]]
1880
- }];
1881
- return [...circuits, ...snapshot.dhw === null ? [] : [dhwDiscoverable(BOILER_DHW_NAME)]];
1929
+ if (snapshot.heatPump !== void 0) {
1930
+ const children = [...circuits, ...snapshot.dhw === null ? [] : [dhwDiscoverable(HOMECOM_HOT_WATER_CHILD_NAME)]];
1931
+ return [gatewayDiscoverable(snapshot), {
1932
+ nativeId: "hp:0",
1933
+ name: "Heat pump",
1934
+ kind: "heat-pump",
1935
+ children
1936
+ }];
1937
+ }
1938
+ return [
1939
+ gatewayDiscoverable(snapshot),
1940
+ ...circuits,
1941
+ ...snapshot.dhw === null ? [] : [dhwDiscoverable(BOILER_DHW_NAME)]
1942
+ ];
1882
1943
  }
1883
1944
  var homeComConnections = new HomeComConnectionResolver();
1884
1945
  //#endregion
@@ -1961,6 +2022,10 @@ function shapeOf(kind) {
1961
2022
  type: DeviceType.WaterHeater,
1962
2023
  caps: ["water-heater"]
1963
2024
  };
2025
+ case "gateway": return {
2026
+ type: DeviceType.Gateway,
2027
+ caps: []
2028
+ };
1964
2029
  case "heat-pump": return {
1965
2030
  type: DeviceType.HeatPump,
1966
2031
  caps: ["heat-pump-status", "heat-pump-control"]
@@ -2808,6 +2873,35 @@ var HomeComWaterHeaterDevice = class extends BaseDevice {
2808
2873
  }
2809
2874
  };
2810
2875
  //#endregion
2876
+ //#region src/homecom/devices/homecom-gateway-device.ts
2877
+ var homeComGatewaySchema = object({
2878
+ nativeId: string().min(1),
2879
+ brokerId: string().min(1),
2880
+ system: literal("homecom").optional(),
2881
+ integrationId: string().optional()
2882
+ });
2883
+ /**
2884
+ * The HomeCom gateway, as a {@link DeviceType.Gateway}. Its connection lives on the
2885
+ * INTEGRATION; this device is the gateway's presence in the device list and follows the
2886
+ * integration's online state (its connect and disconnect callbacks flip it).
2887
+ *
2888
+ * It registers no action: the cloud account offers no reboot, no update and no local page,
2889
+ * so there is nothing real to expose. The circuits, the hot water and the heat pump are NOT
2890
+ * children of it; each is adopted as its own top-level device of the same integration.
2891
+ */
2892
+ var HomeComGatewayDevice = class extends BaseDevice {
2893
+ features = [];
2894
+ constructor(ctx) {
2895
+ const cfg = homeComGatewaySchema.parse(ctx.persistedConfig ?? {});
2896
+ super(ctx, homeComGatewaySchema, { type: DeviceType.Gateway });
2897
+ this.online = homeComConnections.has(cfg.brokerId);
2898
+ this.updateSourceInfo({
2899
+ id: cfg.nativeId,
2900
+ system: "homecom"
2901
+ });
2902
+ }
2903
+ };
2904
+ //#endregion
2811
2905
  //#region src/homecom/devices/homecom-heat-pump-device.ts
2812
2906
  var homeComHeatPumpSchema = object({
2813
2907
  nativeId: string().min(1),
@@ -2958,7 +3052,8 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
2958
3052
  deviceClasses = {
2959
3053
  [DeviceType.Climate]: HomeComClimateDevice,
2960
3054
  [DeviceType.WaterHeater]: HomeComWaterHeaterDevice,
2961
- [DeviceType.HeatPump]: HomeComHeatPumpDevice
3055
+ [DeviceType.HeatPump]: HomeComHeatPumpDevice,
3056
+ [DeviceType.Gateway]: HomeComGatewayDevice
2962
3057
  };
2963
3058
  clients = null;
2964
3059
  instances = null;
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-D0eF-Jjy.js");
5
+ const require_bosch_instances = require("../bosch-instances-BHFcR1TK.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");
@@ -1,4 +1,4 @@
1
- import { A as RecordIntegrationInstances, B as array, D as switchCapability, F as entryBody, G as string, H as number, M as customAction, N as defineCustomActions, O as temperatureSensorCapability, P as defineEntryKind, R as rowsDepsOver, U as object, V as literal, X as errMsg, Y as DeviceType, Z as EventCategory, a as BaseDevice, d as climateControlCapability, i as testBoschInstance, 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 _enum } from "../bosch-instances-DAqkAsRO.mjs";
1
+ import { A as RecordIntegrationInstances, B as array, D as switchCapability, F as entryBody, G as string, H as number, M as customAction, N as defineCustomActions, O as temperatureSensorCapability, P as defineEntryKind, R as rowsDepsOver, U as object, V as literal, X as errMsg, Y as DeviceType, Z as EventCategory, a as BaseDevice, d as climateControlCapability, i as testBoschInstance, 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 _enum } from "../bosch-instances-B2e4DAI1.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { request } from "node:https";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-bosch",
3
- "version": "0.1.47",
3
+ "version": "0.1.49",
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",
@@ -138,7 +138,7 @@
138
138
  }
139
139
  ],
140
140
  "requires": {
141
- "server": ">=1.2.823"
141
+ "server": ">=1.2.834"
142
142
  }
143
143
  },
144
144
  "files": [