@camstack/addon-provider-bosch 0.1.45 → 0.1.47
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/CHANGELOG.md +12 -0
- package/dist/{account-integration-offline-DAEciV_Q.mjs → account-integration-offline-Bie4aS5e.mjs} +1 -1
- package/dist/{account-integration-offline-BiXbzZsg.js → account-integration-offline-C6tzFB-H.js} +1 -1
- package/dist/{bosch-instances-DdklwoKS.js → bosch-instances-D0eF-Jjy.js} +181 -30
- package/dist/{bosch-instances-DXID-jMD.mjs → bosch-instances-DAqkAsRO.mjs} +181 -30
- package/dist/home-connect/addon.js +2 -2
- package/dist/home-connect/addon.mjs +2 -2
- package/dist/homecom/addon.js +439 -113
- package/dist/homecom/addon.mjs +439 -113
- package/dist/shc/addon.js +1 -1
- package/dist/shc/addon.mjs +1 -1
- package/package.json +2 -2
package/dist/homecom/addon.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_bosch_instances = require("../bosch-instances-
|
|
3
|
-
const require_account_integration_offline = require("../account-integration-offline-
|
|
2
|
+
const require_bosch_instances = require("../bosch-instances-D0eF-Jjy.js");
|
|
3
|
+
const require_account_integration_offline = require("../account-integration-offline-C6tzFB-H.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
//#region src/homecom/config.ts
|
|
6
6
|
var HOMECOM_BRANDS = ["bosch", "buderus"];
|
|
@@ -449,7 +449,7 @@ async function readHydronic(input) {
|
|
|
449
449
|
const heating = await input.get("/heatingCircuits");
|
|
450
450
|
const dhwBody = await input.get("/dhwCircuits");
|
|
451
451
|
const circuits = [];
|
|
452
|
-
for (const
|
|
452
|
+
for (const ref of circuitRefs(heating, "hc")) circuits.push(await readCircuit(input.get, ref.id, ref.name));
|
|
453
453
|
const dhwIds = referenceKeys(dhwBody, "dhw");
|
|
454
454
|
const first = dhwIds[0];
|
|
455
455
|
const dhw = first === void 0 ? null : await readDhw(input.get, first);
|
|
@@ -509,7 +509,7 @@ async function readRac(input) {
|
|
|
509
509
|
setpointResource: "/airConditioning/temperatureSetpoint"
|
|
510
510
|
}], null);
|
|
511
511
|
}
|
|
512
|
-
async function readCircuit(get, id) {
|
|
512
|
+
async function readCircuit(get, id, givenName) {
|
|
513
513
|
const base = `/heatingCircuits/${id}`;
|
|
514
514
|
const loaded = await loadEnvelopes(get, [
|
|
515
515
|
`${base}/operationMode`,
|
|
@@ -532,7 +532,7 @@ async function readCircuit(get, id) {
|
|
|
532
532
|
const humidity = numberOf(loaded.get(`${base}/actualHumidity`) ?? null);
|
|
533
533
|
return {
|
|
534
534
|
id,
|
|
535
|
-
name: id,
|
|
535
|
+
name: heatingCircuitName(id, givenName),
|
|
536
536
|
operationMode: stringOf(operation) ?? "off",
|
|
537
537
|
setpoint: cooling ? numberOf(coolingSet) ?? numberOf(current) ?? numberOf(manual) : numberOf(current) ?? numberOf(manual),
|
|
538
538
|
currentRoomTemp: numberOf(loaded.get(`${base}/roomtemperature`) ?? null),
|
|
@@ -634,18 +634,37 @@ function pointtMode(mode) {
|
|
|
634
634
|
default: return mode;
|
|
635
635
|
}
|
|
636
636
|
}
|
|
637
|
-
|
|
637
|
+
/** `hc1` → "Heating circuit 1", unless the gateway already named the circuit. */
|
|
638
|
+
function heatingCircuitName(id, given) {
|
|
639
|
+
if (given !== null && given.length > 0 && given !== id) return given;
|
|
640
|
+
const n = id.replace(/^hc/, "");
|
|
641
|
+
return /^\d+$/.test(n) ? `Heating circuit ${n}` : "Heating circuit";
|
|
642
|
+
}
|
|
643
|
+
function circuitRefs(body, prefix) {
|
|
638
644
|
if (!isRecord$2(body) || !Array.isArray(body["references"])) return [];
|
|
639
645
|
const pattern = prefix === "hc" ? /^hc\d+$/ : /^dhw\d+$/;
|
|
640
646
|
const out = [];
|
|
641
647
|
for (const ref of body["references"]) {
|
|
642
648
|
if (!isRecord$2(ref) || typeof ref["id"] !== "string") continue;
|
|
643
649
|
const tail = ref["id"].split("/").filter((part) => part.length > 0).at(-1);
|
|
644
|
-
if (tail === void 0 || !pattern.test(tail) || out.
|
|
645
|
-
out.push(
|
|
650
|
+
if (tail === void 0 || !pattern.test(tail) || out.some((item) => item.id === tail)) continue;
|
|
651
|
+
out.push({
|
|
652
|
+
id: tail,
|
|
653
|
+
name: refName(ref, tail)
|
|
654
|
+
});
|
|
646
655
|
}
|
|
647
656
|
return out;
|
|
648
657
|
}
|
|
658
|
+
function refName(ref, id) {
|
|
659
|
+
const raw = ref["name"];
|
|
660
|
+
if (typeof raw !== "string") return null;
|
|
661
|
+
const trimmed = raw.trim();
|
|
662
|
+
if (trimmed.length === 0 || trimmed === id) return null;
|
|
663
|
+
return trimmed;
|
|
664
|
+
}
|
|
665
|
+
function referenceKeys(body, prefix) {
|
|
666
|
+
return circuitRefs(body, prefix).map((ref) => ref.id);
|
|
667
|
+
}
|
|
649
668
|
function indexStandard(body) {
|
|
650
669
|
const map = /* @__PURE__ */ new Map();
|
|
651
670
|
if (!isRecord$2(body) || !Array.isArray(body["references"])) return map;
|
|
@@ -793,19 +812,186 @@ function safeGet(get, onFailure) {
|
|
|
793
812
|
}
|
|
794
813
|
};
|
|
795
814
|
}
|
|
815
|
+
var SYSTEM_MODE_KEYS = [
|
|
816
|
+
"silent",
|
|
817
|
+
"away",
|
|
818
|
+
"additionalHeater",
|
|
819
|
+
"holiday"
|
|
820
|
+
];
|
|
821
|
+
var HEAT_PUMP_ONLY_READINGS = [
|
|
822
|
+
"thermalW",
|
|
823
|
+
"electricalW",
|
|
824
|
+
"compressorPct"
|
|
825
|
+
];
|
|
796
826
|
/**
|
|
797
|
-
* True when
|
|
798
|
-
*
|
|
799
|
-
*
|
|
827
|
+
* True when the gateway should be offered as a system device (`hp:0`).
|
|
828
|
+
*
|
|
829
|
+
* A system mode counts when the gateway answered it: silent, away, the
|
|
830
|
+
* 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.
|
|
800
837
|
*/
|
|
801
|
-
function
|
|
838
|
+
function hasSystemDevice(reading) {
|
|
839
|
+
if (SYSTEM_MODE_KEYS.some((key) => reading[key] !== null)) return true;
|
|
840
|
+
return HEAT_PUMP_ONLY_READINGS.some((key) => reading[key] !== null);
|
|
841
|
+
}
|
|
842
|
+
/** Which heat-pump paths this gateway answered. One log per gateway corrects the path table. */
|
|
843
|
+
function heatPumpPathCensus(reading) {
|
|
844
|
+
const rows = [
|
|
845
|
+
...systemPathRows(reading),
|
|
846
|
+
...readingPathRows(reading),
|
|
847
|
+
...reading.circuits.flatMap(circuitPathRows)
|
|
848
|
+
];
|
|
849
|
+
const answered = [];
|
|
850
|
+
const unanswered = [];
|
|
851
|
+
for (const row of rows) if (row.present) answered.push(row.path);
|
|
852
|
+
else unanswered.push(row.path);
|
|
853
|
+
return {
|
|
854
|
+
answered,
|
|
855
|
+
unanswered
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
function systemPathRows(reading) {
|
|
859
|
+
const s = HEAT_PUMP_PATHS.system;
|
|
802
860
|
return [
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
861
|
+
{
|
|
862
|
+
path: s.silent,
|
|
863
|
+
present: reading.silent !== null
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
path: s.away,
|
|
867
|
+
present: reading.away !== null
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
path: s.additionalHeater,
|
|
871
|
+
present: reading.additionalHeater !== null
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
path: s.holiday,
|
|
875
|
+
present: reading.holiday !== null
|
|
876
|
+
}
|
|
877
|
+
];
|
|
878
|
+
}
|
|
879
|
+
function readingPathRows(reading) {
|
|
880
|
+
const r = HEAT_PUMP_PATHS.readings;
|
|
881
|
+
return [
|
|
882
|
+
{
|
|
883
|
+
path: r.thermalW,
|
|
884
|
+
present: reading.thermalW !== null
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
path: r.electricalW,
|
|
888
|
+
present: reading.electricalW !== null
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
path: r.compressorPct,
|
|
892
|
+
present: reading.compressorPct !== null
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
path: r.supply,
|
|
896
|
+
present: reading.supply !== null
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
path: r.return,
|
|
900
|
+
present: reading.return !== null
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
path: r.outdoor,
|
|
904
|
+
present: reading.outdoor !== null
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
path: r.thermalKwh,
|
|
908
|
+
present: reading.thermalKwh !== null
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
path: r.electricalKwh,
|
|
912
|
+
present: reading.electricalKwh !== null
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
path: r.pressureBar,
|
|
916
|
+
present: reading.pressureBar !== null
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
path: r.starts,
|
|
920
|
+
present: reading.starts !== null
|
|
921
|
+
}
|
|
922
|
+
];
|
|
923
|
+
}
|
|
924
|
+
function circuitPathRows(circuit) {
|
|
925
|
+
if (circuit.kind === "dhw") return dhwPathRows(circuit);
|
|
926
|
+
return hcPathRows(circuit);
|
|
927
|
+
}
|
|
928
|
+
function hcPathRows(circuit) {
|
|
929
|
+
const base = `/heatingCircuits/${circuit.id}`;
|
|
930
|
+
const p = HEAT_PUMP_PATHS.circuit;
|
|
931
|
+
return [
|
|
932
|
+
{
|
|
933
|
+
path: `${base}/${p.operationMode}`,
|
|
934
|
+
present: circuit.operationMode !== null
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
path: `${base}/${p.heatCoolMode}`,
|
|
938
|
+
present: circuit.heatCoolMode !== null
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
path: `${base}/${p.summerWinter}`,
|
|
942
|
+
present: circuit.summerWinter !== null
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
path: `${base}/${p.comfort}`,
|
|
946
|
+
present: circuit.comfort !== null
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
path: `${base}/${p.eco}`,
|
|
950
|
+
present: circuit.eco !== null
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
path: `${base}/${p.maxFlow}`,
|
|
954
|
+
present: circuit.maxFlow !== null
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
path: `${base}/${p.current}`,
|
|
958
|
+
present: circuit.currentTemp !== null
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
path: `${base}/${p.target}`,
|
|
962
|
+
present: circuit.targetTemp !== null
|
|
963
|
+
}
|
|
964
|
+
];
|
|
965
|
+
}
|
|
966
|
+
function dhwPathRows(circuit) {
|
|
967
|
+
const base = `/dhwCircuits/${circuit.id}`;
|
|
968
|
+
const p = HEAT_PUMP_PATHS.dhw;
|
|
969
|
+
return [
|
|
970
|
+
{
|
|
971
|
+
path: `${base}/${p.operationMode}`,
|
|
972
|
+
present: circuit.operationMode !== null
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
path: `${base}/${p.level}`,
|
|
976
|
+
present: circuit.level !== null
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
path: `${base}/${p.chargeDuration}`,
|
|
980
|
+
present: circuit.chargeDuration !== null
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
path: `${base}/${p.charge}`,
|
|
984
|
+
present: circuit.charge !== null
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
path: `${base}/${p.current}`,
|
|
988
|
+
present: circuit.currentTemp !== null
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
path: `${base}/${p.target}`,
|
|
992
|
+
present: circuit.targetTemp !== null
|
|
993
|
+
}
|
|
994
|
+
];
|
|
809
995
|
}
|
|
810
996
|
async function readHc(get, id) {
|
|
811
997
|
const base = `/heatingCircuits/${id}`;
|
|
@@ -910,7 +1096,6 @@ function heatPumpStatusFrom(reading, now) {
|
|
|
910
1096
|
}
|
|
911
1097
|
function circuitOf(c) {
|
|
912
1098
|
const kind = kindOf(c);
|
|
913
|
-
const mode = textOf$1(c.operationMode);
|
|
914
1099
|
return {
|
|
915
1100
|
id: c.id,
|
|
916
1101
|
kind,
|
|
@@ -918,7 +1103,7 @@ function circuitOf(c) {
|
|
|
918
1103
|
icon: kind === "dhw" ? "droplets" : kind === "cooling" ? "snowflake" : "flame",
|
|
919
1104
|
currentTemp: c.currentTemp,
|
|
920
1105
|
targetTemp: c.targetTemp,
|
|
921
|
-
mode,
|
|
1106
|
+
mode: modeValue(c.operationMode),
|
|
922
1107
|
active: null,
|
|
923
1108
|
operationMode: optionOf(c.operationMode),
|
|
924
1109
|
heatCoolMode: optionOf(c.heatCoolMode),
|
|
@@ -938,15 +1123,24 @@ function kindOf(c) {
|
|
|
938
1123
|
function labelOf(c) {
|
|
939
1124
|
const n = c.id.replace(/^\D+/, "");
|
|
940
1125
|
if (c.kind === "dhw") return n === "1" || n === "" ? "Hot water" : `Hot water ${n}`;
|
|
941
|
-
return n === "" ? "
|
|
1126
|
+
return n === "" ? "Heating circuit" : `Heating circuit ${n}`;
|
|
942
1127
|
}
|
|
943
1128
|
function textOf$1(s) {
|
|
944
1129
|
return s !== null && typeof s.value === "string" && s.value.length > 0 ? s.value : null;
|
|
945
1130
|
}
|
|
1131
|
+
/**
|
|
1132
|
+
* A mode the gateway answered with an empty value is off (or "none" when the
|
|
1133
|
+
* gateway did not list off). A mode it did not answer at all stays null.
|
|
1134
|
+
*/
|
|
1135
|
+
function modeValue(s) {
|
|
1136
|
+
if (s === null || typeof s.value !== "string") return null;
|
|
1137
|
+
if (s.value.length > 0) return s.value;
|
|
1138
|
+
return s.allowed.includes("off") ? "off" : "none";
|
|
1139
|
+
}
|
|
946
1140
|
function optionOf(s) {
|
|
947
1141
|
if (s === null) return null;
|
|
948
1142
|
return {
|
|
949
|
-
value:
|
|
1143
|
+
value: modeValue(s),
|
|
950
1144
|
allowed: [...s.allowed]
|
|
951
1145
|
};
|
|
952
1146
|
}
|
|
@@ -1113,6 +1307,7 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
|
|
|
1113
1307
|
let last = null;
|
|
1114
1308
|
let refreshTail = Promise.resolve();
|
|
1115
1309
|
const dropped = /* @__PURE__ */ new Set();
|
|
1310
|
+
let heatPumpPathsLogged = false;
|
|
1116
1311
|
const base = homeComApiBase(config.region);
|
|
1117
1312
|
function authHeaders() {
|
|
1118
1313
|
return {
|
|
@@ -1267,11 +1462,24 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
|
|
|
1267
1462
|
dhw: dhwId === void 0 ? [] : [dhwId]
|
|
1268
1463
|
}
|
|
1269
1464
|
});
|
|
1270
|
-
|
|
1465
|
+
logHeatPumpPaths(heatPump);
|
|
1466
|
+
return hasSystemDevice(heatPump) ? {
|
|
1271
1467
|
...snapshot,
|
|
1272
1468
|
heatPump
|
|
1273
1469
|
} : snapshot;
|
|
1274
1470
|
}
|
|
1471
|
+
function logHeatPumpPaths(reading) {
|
|
1472
|
+
if (heatPumpPathsLogged) return;
|
|
1473
|
+
heatPumpPathsLogged = true;
|
|
1474
|
+
const census = heatPumpPathCensus(reading);
|
|
1475
|
+
logger?.info("HomeCom heat-pump paths", {
|
|
1476
|
+
tags: { gatewayId },
|
|
1477
|
+
meta: {
|
|
1478
|
+
answered: census.answered,
|
|
1479
|
+
unanswered: census.unanswered
|
|
1480
|
+
}
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1275
1483
|
function logHeatPumpFailure(path, error) {
|
|
1276
1484
|
const key = `heat-pump-read:${path}`;
|
|
1277
1485
|
if (dropped.has(key)) return;
|
|
@@ -1599,6 +1807,129 @@ function buildHomeComGatewayPersister(deps) {
|
|
|
1599
1807
|
return (integrationId, gatewayId) => patch(integrationId, { gatewayId });
|
|
1600
1808
|
}
|
|
1601
1809
|
//#endregion
|
|
1810
|
+
//#region src/homecom/homecom-gateway.ts
|
|
1811
|
+
/** Stable id of one HomeCom device. Adoption matches an existing row on this. */
|
|
1812
|
+
function homeComStableId(integrationId, nativeId) {
|
|
1813
|
+
return `homecom:${integrationId}:${nativeId}`;
|
|
1814
|
+
}
|
|
1815
|
+
/** The hot-water child of a heat pump. A boiler's own row keeps its longer name. */
|
|
1816
|
+
var HOMECOM_HOT_WATER_CHILD_NAME = "Hot water";
|
|
1817
|
+
var HomeComConnectionResolver = class {
|
|
1818
|
+
#surfaces = /* @__PURE__ */ new Map();
|
|
1819
|
+
set(integrationId, surface) {
|
|
1820
|
+
if (surface === null) {
|
|
1821
|
+
this.#surfaces.delete(integrationId);
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
this.#surfaces.set(integrationId, surface);
|
|
1825
|
+
}
|
|
1826
|
+
has(integrationId) {
|
|
1827
|
+
return this.#surfaces.has(integrationId);
|
|
1828
|
+
}
|
|
1829
|
+
list() {
|
|
1830
|
+
return Array.from(this.#surfaces.keys()).map((id) => ({ id }));
|
|
1831
|
+
}
|
|
1832
|
+
discoverables(integrationId) {
|
|
1833
|
+
return this.#surfaces.get(integrationId)?.discoverables ?? [];
|
|
1834
|
+
}
|
|
1835
|
+
getClient(integrationId) {
|
|
1836
|
+
return this.#surfaces.get(integrationId)?.client ?? null;
|
|
1837
|
+
}
|
|
1838
|
+
getSnapshot(integrationId) {
|
|
1839
|
+
return this.#surfaces.get(integrationId)?.snapshot ?? null;
|
|
1840
|
+
}
|
|
1841
|
+
updateSnapshot(integrationId, snapshot) {
|
|
1842
|
+
const existing = this.#surfaces.get(integrationId);
|
|
1843
|
+
if (!existing) return;
|
|
1844
|
+
this.#surfaces.set(integrationId, {
|
|
1845
|
+
...existing,
|
|
1846
|
+
snapshot,
|
|
1847
|
+
discoverables: buildDiscoverables(snapshot)
|
|
1848
|
+
});
|
|
1849
|
+
}
|
|
1850
|
+
clear() {
|
|
1851
|
+
this.#surfaces.clear();
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
var BOILER_DHW_NAME = "Domestic hot water";
|
|
1855
|
+
function circuitDiscoverable(circuit) {
|
|
1856
|
+
return {
|
|
1857
|
+
nativeId: `hc:${circuit.id}`,
|
|
1858
|
+
name: circuit.name,
|
|
1859
|
+
kind: "heating-circuit",
|
|
1860
|
+
circuitId: circuit.id
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
function dhwDiscoverable(name) {
|
|
1864
|
+
return {
|
|
1865
|
+
nativeId: "dhw:0",
|
|
1866
|
+
name,
|
|
1867
|
+
kind: "dhw"
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* A gateway with a heat pump is one pick. A boiler still offers each circuit
|
|
1872
|
+
* and the hot-water row on its own.
|
|
1873
|
+
*/
|
|
1874
|
+
function buildDiscoverables(snapshot) {
|
|
1875
|
+
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)]];
|
|
1883
|
+
}
|
|
1884
|
+
var homeComConnections = new HomeComConnectionResolver();
|
|
1885
|
+
//#endregion
|
|
1886
|
+
//#region src/homecom/homecom-adoption-plan.ts
|
|
1887
|
+
function createOf(device, parentNativeId) {
|
|
1888
|
+
return {
|
|
1889
|
+
nativeId: device.nativeId,
|
|
1890
|
+
name: device.name,
|
|
1891
|
+
kind: device.kind,
|
|
1892
|
+
circuitId: device.circuitId,
|
|
1893
|
+
parentNativeId
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* The devices one pick creates. A heat pump also creates its children, except
|
|
1898
|
+
* a circuit or hot-water row that already exists: a top-level one is reported
|
|
1899
|
+
* so the caller can log why it stayed there, and any existing row is not
|
|
1900
|
+
* created again.
|
|
1901
|
+
*/
|
|
1902
|
+
function planHomeComAdoption(device, integrationId, adopted) {
|
|
1903
|
+
const root = createOf(device, null);
|
|
1904
|
+
if (device.kind !== "heat-pump") return {
|
|
1905
|
+
create: [root],
|
|
1906
|
+
stayedTopLevel: [],
|
|
1907
|
+
alreadyPresent: []
|
|
1908
|
+
};
|
|
1909
|
+
const byStableId = new Map(adopted.map((row) => [row.stableId, row]));
|
|
1910
|
+
const create = [root];
|
|
1911
|
+
const stayedTopLevel = [];
|
|
1912
|
+
const alreadyPresent = [];
|
|
1913
|
+
for (const child of device.children ?? []) {
|
|
1914
|
+
const existing = byStableId.get(homeComStableId(integrationId, child.nativeId));
|
|
1915
|
+
if (existing === void 0) {
|
|
1916
|
+
create.push(createOf(child, device.nativeId));
|
|
1917
|
+
continue;
|
|
1918
|
+
}
|
|
1919
|
+
const skip = {
|
|
1920
|
+
nativeId: child.nativeId,
|
|
1921
|
+
deviceId: existing.id
|
|
1922
|
+
};
|
|
1923
|
+
if (existing.parentDeviceId === null) stayedTopLevel.push(skip);
|
|
1924
|
+
else alreadyPresent.push(skip);
|
|
1925
|
+
}
|
|
1926
|
+
return {
|
|
1927
|
+
create,
|
|
1928
|
+
stayedTopLevel,
|
|
1929
|
+
alreadyPresent
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
//#endregion
|
|
1602
1933
|
//#region src/homecom/homecom-discovery.ts
|
|
1603
1934
|
function buildHomeComCandidates(input) {
|
|
1604
1935
|
const out = [];
|
|
@@ -1618,6 +1949,9 @@ function buildHomeComCandidates(input) {
|
|
|
1618
1949
|
}
|
|
1619
1950
|
return out;
|
|
1620
1951
|
}
|
|
1952
|
+
function homeComDeviceType(kind) {
|
|
1953
|
+
return shapeOf(kind).type;
|
|
1954
|
+
}
|
|
1621
1955
|
function shapeOf(kind) {
|
|
1622
1956
|
switch (kind) {
|
|
1623
1957
|
case "heating-circuit": return {
|
|
@@ -1641,6 +1975,62 @@ var DEVICES_FILTER = {
|
|
|
1641
1975
|
label: "Devices",
|
|
1642
1976
|
isDefault: true
|
|
1643
1977
|
};
|
|
1978
|
+
var STAYED_TOP_LEVEL = "HomeCom device stayed top-level: it was already adopted, so it was not created again under the heat pump";
|
|
1979
|
+
var ALREADY_PRESENT = "HomeCom device was not created again: a device with this stable id already exists";
|
|
1980
|
+
async function adoptOne(input) {
|
|
1981
|
+
const { dev, integrationId, name, known, adoptDevice, logger, logOnce } = input;
|
|
1982
|
+
const plan = planHomeComAdoption(dev, integrationId, known);
|
|
1983
|
+
for (const skip of plan.stayedTopLevel) logOnce(skip.deviceId, STAYED_TOP_LEVEL);
|
|
1984
|
+
for (const skip of plan.alreadyPresent) logOnce(skip.deviceId, ALREADY_PRESENT);
|
|
1985
|
+
const root = plan.create[0];
|
|
1986
|
+
if (root === void 0) return { ok: false };
|
|
1987
|
+
let parentDeviceId;
|
|
1988
|
+
try {
|
|
1989
|
+
parentDeviceId = (await adoptDevice({
|
|
1990
|
+
nativeId: root.nativeId,
|
|
1991
|
+
integrationId,
|
|
1992
|
+
type: homeComDeviceType(root.kind),
|
|
1993
|
+
name,
|
|
1994
|
+
kind: root.kind,
|
|
1995
|
+
...root.circuitId === void 0 ? {} : { circuitId: root.circuitId },
|
|
1996
|
+
parentDeviceId: null
|
|
1997
|
+
})).deviceId;
|
|
1998
|
+
} catch (err) {
|
|
1999
|
+
logger.warn("homecom adopt failed", { meta: {
|
|
2000
|
+
nativeId: root.nativeId,
|
|
2001
|
+
integrationId,
|
|
2002
|
+
error: require_bosch_instances.errMsg(err)
|
|
2003
|
+
} });
|
|
2004
|
+
return { ok: false };
|
|
2005
|
+
}
|
|
2006
|
+
const accessoryDeviceIds = [];
|
|
2007
|
+
for (const child of plan.create.slice(1)) try {
|
|
2008
|
+
const created = await adoptDevice({
|
|
2009
|
+
nativeId: child.nativeId,
|
|
2010
|
+
integrationId,
|
|
2011
|
+
type: homeComDeviceType(child.kind),
|
|
2012
|
+
name: child.name,
|
|
2013
|
+
kind: child.kind,
|
|
2014
|
+
...child.circuitId === void 0 ? {} : { circuitId: child.circuitId },
|
|
2015
|
+
parentDeviceId
|
|
2016
|
+
});
|
|
2017
|
+
accessoryDeviceIds.push(created.deviceId);
|
|
2018
|
+
} catch (err) {
|
|
2019
|
+
logger.warn("homecom adopt child failed", {
|
|
2020
|
+
tags: { deviceId: parentDeviceId },
|
|
2021
|
+
meta: {
|
|
2022
|
+
nativeId: child.nativeId,
|
|
2023
|
+
integrationId,
|
|
2024
|
+
error: require_bosch_instances.errMsg(err)
|
|
2025
|
+
}
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
2028
|
+
return {
|
|
2029
|
+
ok: true,
|
|
2030
|
+
parentDeviceId,
|
|
2031
|
+
accessoryDeviceIds
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
1644
2034
|
async function adoptedMapForIntegration(integrationId, listAdopted) {
|
|
1645
2035
|
const all = await listAdopted();
|
|
1646
2036
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -1652,6 +2042,12 @@ async function adoptedMapForIntegration(integrationId, listAdopted) {
|
|
|
1652
2042
|
}
|
|
1653
2043
|
function buildHomeComAdoptionProvider(deps) {
|
|
1654
2044
|
const { registry, hasIntegration, listIntegrations, listAdopted, adoptDevice, removeDevice, findDeviceConfig, logger } = deps;
|
|
2045
|
+
const logged = /* @__PURE__ */ new Set();
|
|
2046
|
+
const logOnce = (deviceId, message) => {
|
|
2047
|
+
if (logged.has(deviceId)) return;
|
|
2048
|
+
logged.add(deviceId);
|
|
2049
|
+
logger.info(message, { tags: { deviceId } });
|
|
2050
|
+
};
|
|
1655
2051
|
async function allCandidatesForIntegration(integrationId) {
|
|
1656
2052
|
return buildHomeComCandidates({
|
|
1657
2053
|
devices: registry.discoverables(integrationId),
|
|
@@ -1706,48 +2102,36 @@ function buildHomeComAdoptionProvider(deps) {
|
|
|
1706
2102
|
adopt: async ({ integrationId, childNativeIds, perCandidate }) => {
|
|
1707
2103
|
if (!registry.has(integrationId)) throw new Error(`homecom adopt: integration ${integrationId} not connected`);
|
|
1708
2104
|
const devices = registry.discoverables(integrationId);
|
|
2105
|
+
const known = await listAdopted();
|
|
1709
2106
|
const adopted = [];
|
|
1710
2107
|
let failures = 0;
|
|
1711
|
-
for (const nativeId of childNativeIds)
|
|
2108
|
+
for (const nativeId of childNativeIds) {
|
|
1712
2109
|
const dev = devices.find((d) => d.nativeId === nativeId);
|
|
1713
2110
|
if (!dev) {
|
|
1714
2111
|
failures++;
|
|
1715
2112
|
continue;
|
|
1716
2113
|
}
|
|
1717
|
-
const
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
2114
|
+
const outcome = await adoptOne({
|
|
2115
|
+
dev,
|
|
2116
|
+
integrationId,
|
|
2117
|
+
name: perCandidate?.[nativeId]?.name ?? dev.name,
|
|
2118
|
+
known,
|
|
2119
|
+
adoptDevice,
|
|
2120
|
+
logger,
|
|
2121
|
+
logOnce
|
|
2122
|
+
});
|
|
2123
|
+
if (!outcome.ok) {
|
|
1722
2124
|
failures++;
|
|
1723
2125
|
continue;
|
|
1724
2126
|
}
|
|
1725
|
-
const name = perCandidate?.[nativeId]?.name ?? dev.name;
|
|
1726
|
-
const { deviceId } = await adoptDevice({
|
|
1727
|
-
nativeId,
|
|
1728
|
-
integrationId,
|
|
1729
|
-
type: candidate.type,
|
|
1730
|
-
name,
|
|
1731
|
-
kind: dev.kind,
|
|
1732
|
-
circuitId: dev.circuitId
|
|
1733
|
-
});
|
|
1734
2127
|
adopted.push({
|
|
1735
2128
|
childNativeId: nativeId,
|
|
1736
|
-
parentDeviceId:
|
|
2129
|
+
parentDeviceId: outcome.parentDeviceId,
|
|
2130
|
+
accessoryDeviceIds: outcome.accessoryDeviceIds
|
|
1737
2131
|
});
|
|
1738
|
-
} catch (err) {
|
|
1739
|
-
logger.warn("homecom adopt failed", { meta: {
|
|
1740
|
-
nativeId,
|
|
1741
|
-
integrationId,
|
|
1742
|
-
error: require_bosch_instances.errMsg(err)
|
|
1743
|
-
} });
|
|
1744
|
-
failures++;
|
|
1745
2132
|
}
|
|
1746
2133
|
if (adopted.length === 0 && failures > 0) throw new Error(`homecom adopt: all ${failures} adopt(s) failed`);
|
|
1747
|
-
return { adopted
|
|
1748
|
-
...a,
|
|
1749
|
-
accessoryDeviceIds: []
|
|
1750
|
-
})) };
|
|
2134
|
+
return { adopted };
|
|
1751
2135
|
},
|
|
1752
2136
|
release: async ({ camDeviceId }) => {
|
|
1753
2137
|
await removeDevice(camDeviceId);
|
|
@@ -1767,66 +2151,6 @@ function buildHomeComAdoptionProvider(deps) {
|
|
|
1767
2151
|
};
|
|
1768
2152
|
}
|
|
1769
2153
|
//#endregion
|
|
1770
|
-
//#region src/homecom/homecom-gateway.ts
|
|
1771
|
-
var HomeComConnectionResolver = class {
|
|
1772
|
-
#surfaces = /* @__PURE__ */ new Map();
|
|
1773
|
-
set(integrationId, surface) {
|
|
1774
|
-
if (surface === null) {
|
|
1775
|
-
this.#surfaces.delete(integrationId);
|
|
1776
|
-
return;
|
|
1777
|
-
}
|
|
1778
|
-
this.#surfaces.set(integrationId, surface);
|
|
1779
|
-
}
|
|
1780
|
-
has(integrationId) {
|
|
1781
|
-
return this.#surfaces.has(integrationId);
|
|
1782
|
-
}
|
|
1783
|
-
list() {
|
|
1784
|
-
return Array.from(this.#surfaces.keys()).map((id) => ({ id }));
|
|
1785
|
-
}
|
|
1786
|
-
discoverables(integrationId) {
|
|
1787
|
-
return this.#surfaces.get(integrationId)?.discoverables ?? [];
|
|
1788
|
-
}
|
|
1789
|
-
getClient(integrationId) {
|
|
1790
|
-
return this.#surfaces.get(integrationId)?.client ?? null;
|
|
1791
|
-
}
|
|
1792
|
-
getSnapshot(integrationId) {
|
|
1793
|
-
return this.#surfaces.get(integrationId)?.snapshot ?? null;
|
|
1794
|
-
}
|
|
1795
|
-
updateSnapshot(integrationId, snapshot) {
|
|
1796
|
-
const existing = this.#surfaces.get(integrationId);
|
|
1797
|
-
if (!existing) return;
|
|
1798
|
-
this.#surfaces.set(integrationId, {
|
|
1799
|
-
...existing,
|
|
1800
|
-
snapshot,
|
|
1801
|
-
discoverables: buildDiscoverables(snapshot)
|
|
1802
|
-
});
|
|
1803
|
-
}
|
|
1804
|
-
clear() {
|
|
1805
|
-
this.#surfaces.clear();
|
|
1806
|
-
}
|
|
1807
|
-
};
|
|
1808
|
-
function buildDiscoverables(snapshot) {
|
|
1809
|
-
const out = [];
|
|
1810
|
-
for (const circuit of snapshot.circuits) out.push({
|
|
1811
|
-
nativeId: `hc:${circuit.id}`,
|
|
1812
|
-
name: circuit.name,
|
|
1813
|
-
kind: "heating-circuit",
|
|
1814
|
-
circuitId: circuit.id
|
|
1815
|
-
});
|
|
1816
|
-
if (snapshot.dhw) out.push({
|
|
1817
|
-
nativeId: "dhw:0",
|
|
1818
|
-
name: "Domestic hot water",
|
|
1819
|
-
kind: "dhw"
|
|
1820
|
-
});
|
|
1821
|
-
if (snapshot.heatPump !== void 0) out.push({
|
|
1822
|
-
nativeId: "hp:0",
|
|
1823
|
-
name: "Heat pump",
|
|
1824
|
-
kind: "heat-pump"
|
|
1825
|
-
});
|
|
1826
|
-
return out;
|
|
1827
|
-
}
|
|
1828
|
-
var homeComConnections = new HomeComConnectionResolver();
|
|
1829
|
-
//#endregion
|
|
1830
2154
|
//#region src/homecom/homecom-integration-manager.ts
|
|
1831
2155
|
var HomeComIntegrationManager = class {
|
|
1832
2156
|
#id;
|
|
@@ -2725,7 +3049,7 @@ var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDevice
|
|
|
2725
3049
|
if (typeof integrationId !== "string" || integrationId.length === 0) throw new Error("homecom generateStableId: brokerId required");
|
|
2726
3050
|
const nativeId = config?.["nativeId"];
|
|
2727
3051
|
if (typeof nativeId !== "string" || nativeId.length === 0) throw new Error("homecom generateStableId: nativeId required");
|
|
2728
|
-
return
|
|
3052
|
+
return homeComStableId(integrationId, nativeId);
|
|
2729
3053
|
}
|
|
2730
3054
|
async reconcileIntegrations() {
|
|
2731
3055
|
const reg = this.requireClients();
|
|
@@ -2787,12 +3111,14 @@ var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDevice
|
|
|
2787
3111
|
const config = await devices.loadConfig(d.id).catch(() => ({}));
|
|
2788
3112
|
out.push({
|
|
2789
3113
|
id: d.id,
|
|
3114
|
+
stableId: d.stableId,
|
|
3115
|
+
parentDeviceId: d.parentDeviceId,
|
|
2790
3116
|
config
|
|
2791
3117
|
});
|
|
2792
3118
|
}
|
|
2793
3119
|
return out;
|
|
2794
3120
|
},
|
|
2795
|
-
adoptDevice: async ({ nativeId, integrationId, type, name, kind, circuitId }) => {
|
|
3121
|
+
adoptDevice: async ({ nativeId, integrationId, type, name, kind, circuitId, parentDeviceId }) => {
|
|
2796
3122
|
const devices = this.ctx.kernel.devices;
|
|
2797
3123
|
if (!devices) throw new Error("homecom adopt: devices unavailable");
|
|
2798
3124
|
const config = {
|
|
@@ -2806,7 +3132,7 @@ var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDevice
|
|
|
2806
3132
|
const stableId = this.generateStableId(type, config);
|
|
2807
3133
|
const DeviceClass = this.deviceClasses[type];
|
|
2808
3134
|
if (!DeviceClass) throw new Error(`homecom adopt: unsupported type ${type}`);
|
|
2809
|
-
return { deviceId: (await devices.create(stableId, DeviceClass, config,
|
|
3135
|
+
return { deviceId: (await devices.create(stableId, DeviceClass, config, parentDeviceId, {
|
|
2810
3136
|
type,
|
|
2811
3137
|
name,
|
|
2812
3138
|
integrationId
|