@camstack/addon-provider-bosch 0.1.44 → 0.1.45
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 +6 -0
- package/dist/{account-integration-offline-CFyuKmsG.js → account-integration-offline-BiXbzZsg.js} +1 -1
- package/dist/{account-integration-offline-IHodWmhD.mjs → account-integration-offline-DAEciV_Q.mjs} +1 -1
- package/dist/{bosch-instances-C8FRjQzC.mjs → bosch-instances-DXID-jMD.mjs} +1196 -174
- package/dist/{bosch-instances-CXMUquuA.js → bosch-instances-DdklwoKS.js} +1207 -173
- package/dist/home-connect/addon.js +2 -2
- package/dist/home-connect/addon.mjs +2 -2
- package/dist/homecom/addon.js +557 -46
- package/dist/homecom/addon.mjs +557 -46
- package/dist/shc/addon.js +1 -1
- package/dist/shc/addon.mjs +1 -1
- package/package.json +8 -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-DdklwoKS.js");
|
|
3
|
+
const require_account_integration_offline = require("../account-integration-offline-BiXbzZsg.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
//#region src/homecom/config.ts
|
|
6
6
|
var HOMECOM_BRANDS = ["bosch", "buderus"];
|
|
@@ -267,36 +267,6 @@ function unsupportedDeviceTypeMessage(deviceType) {
|
|
|
267
267
|
return new HomeComUnknownDeviceTypeError(deviceType ?? "(missing)").message;
|
|
268
268
|
}
|
|
269
269
|
//#endregion
|
|
270
|
-
//#region src/homecom/homecom-oauth.ts
|
|
271
|
-
/**
|
|
272
|
-
* Public OAuth client of the HomeCom Easy app, signed in through SingleKey ID.
|
|
273
|
-
*
|
|
274
|
-
* A PKCE client has no secret. The values are the ones SingleKey accepts for this client id
|
|
275
|
-
* today: the same authorize query the MIT-licensed `homecom_alt` client records
|
|
276
|
-
* (`OAUTH_LOGIN_PARAMS` — parameters, not copied code) and that the Home Assistant integration
|
|
277
|
-
* opens. `pointt.gateway.resource.dopair` is not a scope this client may request; SingleKey
|
|
278
|
-
* answers `misconfigured-application` and never shows the login page. The scope it does accept
|
|
279
|
-
* names `pointt.gateway.resource.dashapp`, and the Bosch app sends `style_id=tt_bsch`.
|
|
280
|
-
*/
|
|
281
|
-
var HOMECOM_CLIENT_ID = "762162C0-FA2D-4540-AE66-6489F189FADC";
|
|
282
|
-
var HOMECOM_AUTHORIZE_URL = "https://singlekey-id.com/auth/connect/authorize";
|
|
283
|
-
var HOMECOM_TOKEN_URL = "https://singlekey-id.com/auth/connect/token";
|
|
284
|
-
var HOMECOM_STYLE_ID = "tt_bsch";
|
|
285
|
-
var HOMECOM_SCOPE = [
|
|
286
|
-
"openid",
|
|
287
|
-
"email",
|
|
288
|
-
"profile",
|
|
289
|
-
"offline_access",
|
|
290
|
-
"pointt.gateway.claiming",
|
|
291
|
-
"pointt.gateway.removal",
|
|
292
|
-
"pointt.gateway.list",
|
|
293
|
-
"pointt.gateway.users",
|
|
294
|
-
"pointt.gateway.resource.dashapp",
|
|
295
|
-
"pointt.castt.flow.token-exchange",
|
|
296
|
-
"bacon",
|
|
297
|
-
"hcc.tariff.read"
|
|
298
|
-
].join(" ");
|
|
299
|
-
//#endregion
|
|
300
270
|
//#region src/homecom/homecom-domain-mapping.ts
|
|
301
271
|
var MODE_MAP = {
|
|
302
272
|
Off: "off",
|
|
@@ -717,6 +687,352 @@ function isRecord$2(value) {
|
|
|
717
687
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
718
688
|
}
|
|
719
689
|
//#endregion
|
|
690
|
+
//#region src/homecom/homecom-heat-pump.ts
|
|
691
|
+
/**
|
|
692
|
+
* Heat-pump resources of a hydronic gateway (D1026).
|
|
693
|
+
*
|
|
694
|
+
* UNVERIFIED: the paths below come from the layout of the public HomeCom Easy
|
|
695
|
+
* tree as the MIT `homecom_alt` library lists it, and were not checked against a
|
|
696
|
+
* live heat pump. They live in this one table so a wrong path is a one-line
|
|
697
|
+
* fix, and a resource the gateway does not answer reads as `null` (never 0),
|
|
698
|
+
* which the hero then simply does not draw.
|
|
699
|
+
*/
|
|
700
|
+
var HEAT_PUMP_PATHS = {
|
|
701
|
+
system: {
|
|
702
|
+
silent: "/heatSources/hs1/silentMode",
|
|
703
|
+
away: "/system/awayMode",
|
|
704
|
+
additionalHeater: "/heatSources/hs1/additionalHeaterMode",
|
|
705
|
+
holiday: "/system/holidayMode"
|
|
706
|
+
},
|
|
707
|
+
readings: {
|
|
708
|
+
thermalW: "/heatSources/hs1/heatPumpPower",
|
|
709
|
+
electricalW: "/heatSources/hs1/electricalPower",
|
|
710
|
+
compressorPct: "/heatSources/hs1/compressorModulation",
|
|
711
|
+
supply: "/heatSources/hs1/supplyTemperature",
|
|
712
|
+
return: "/heatSources/hs1/returnTemperature",
|
|
713
|
+
outdoor: "/system/sensors/temperatures/outdoor_t1",
|
|
714
|
+
thermalKwh: "/recordings/heatSources/total/thermalEnergy",
|
|
715
|
+
electricalKwh: "/recordings/heatSources/total/electricalEnergy",
|
|
716
|
+
pressureBar: "/heatSources/hs1/waterPressure",
|
|
717
|
+
starts: "/heatSources/hs1/numberOfStarts"
|
|
718
|
+
},
|
|
719
|
+
circuit: {
|
|
720
|
+
operationMode: "operationMode",
|
|
721
|
+
heatCoolMode: "heatCoolMode",
|
|
722
|
+
summerWinter: "suWiSwitchMode",
|
|
723
|
+
comfort: "temperatureLevels/comfort2",
|
|
724
|
+
eco: "temperatureLevels/eco",
|
|
725
|
+
maxFlow: "maxSupplyTemperature",
|
|
726
|
+
current: "roomtemperature",
|
|
727
|
+
target: "currentRoomSetpoint"
|
|
728
|
+
},
|
|
729
|
+
dhw: {
|
|
730
|
+
operationMode: "operationMode",
|
|
731
|
+
level: "currentTemperatureLevel",
|
|
732
|
+
chargeDuration: "charge/duration",
|
|
733
|
+
charge: "charge",
|
|
734
|
+
current: "actualTemp",
|
|
735
|
+
target: "currentSetpoint"
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
/** A write the gateway would refuse, caught before it is sent. */
|
|
739
|
+
var HomeComHeatPumpRefusalError = class extends Error {
|
|
740
|
+
name = "HomeComHeatPumpRefusalError";
|
|
741
|
+
};
|
|
742
|
+
async function readHeatPump(input) {
|
|
743
|
+
const get = safeGet(input.get, input.onFailure);
|
|
744
|
+
const circuits = await Promise.all([...input.circuitIds.hc.map((id) => readHc(get, id)), ...input.circuitIds.dhw.map((id) => readDhwCircuit(get, id))]);
|
|
745
|
+
const r = HEAT_PUMP_PATHS.readings;
|
|
746
|
+
const s = HEAT_PUMP_PATHS.system;
|
|
747
|
+
const [silent, away, additionalHeater, holiday] = await Promise.all([
|
|
748
|
+
setting(get, s.silent),
|
|
749
|
+
setting(get, s.away),
|
|
750
|
+
setting(get, s.additionalHeater),
|
|
751
|
+
setting(get, s.holiday)
|
|
752
|
+
]);
|
|
753
|
+
const [thermalW, electricalW, compressorPct, supply, ret, outdoor] = await Promise.all([
|
|
754
|
+
num(get, r.thermalW),
|
|
755
|
+
num(get, r.electricalW),
|
|
756
|
+
num(get, r.compressorPct),
|
|
757
|
+
num(get, r.supply),
|
|
758
|
+
num(get, r.return),
|
|
759
|
+
num(get, r.outdoor)
|
|
760
|
+
]);
|
|
761
|
+
const [thermalKwh, electricalKwh, pressureBar, starts] = await Promise.all([
|
|
762
|
+
num(get, r.thermalKwh),
|
|
763
|
+
num(get, r.electricalKwh),
|
|
764
|
+
num(get, r.pressureBar),
|
|
765
|
+
num(get, r.starts)
|
|
766
|
+
]);
|
|
767
|
+
return {
|
|
768
|
+
circuits,
|
|
769
|
+
silent,
|
|
770
|
+
away,
|
|
771
|
+
additionalHeater,
|
|
772
|
+
holiday,
|
|
773
|
+
thermalW,
|
|
774
|
+
electricalW,
|
|
775
|
+
compressorPct,
|
|
776
|
+
supply,
|
|
777
|
+
return: ret,
|
|
778
|
+
outdoor,
|
|
779
|
+
thermalKwh,
|
|
780
|
+
electricalKwh,
|
|
781
|
+
pressureBar,
|
|
782
|
+
starts
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
/** Any failure of a heat-pump read (HTTP 4xx/5xx, a throw) is "not answered", never an error. */
|
|
786
|
+
function safeGet(get, onFailure) {
|
|
787
|
+
return async (path) => {
|
|
788
|
+
try {
|
|
789
|
+
return await get(path);
|
|
790
|
+
} catch (error) {
|
|
791
|
+
onFailure?.(path, error);
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* True when a HEAT PUMP answered: silent mode, the auxiliary heater, heat-pump
|
|
798
|
+
* power or compressor modulation. Flow temperatures, pressure and starts are left
|
|
799
|
+
* out on purpose: a gas boiler's heat source answers those too, and it is no heat pump.
|
|
800
|
+
*/
|
|
801
|
+
function hasHeatSource(reading) {
|
|
802
|
+
return [
|
|
803
|
+
reading.silent,
|
|
804
|
+
reading.additionalHeater,
|
|
805
|
+
reading.thermalW,
|
|
806
|
+
reading.electricalW,
|
|
807
|
+
reading.compressorPct
|
|
808
|
+
].some((v) => v !== null);
|
|
809
|
+
}
|
|
810
|
+
async function readHc(get, id) {
|
|
811
|
+
const base = `/heatingCircuits/${id}`;
|
|
812
|
+
const p = HEAT_PUMP_PATHS.circuit;
|
|
813
|
+
const [operationMode, heatCoolMode, summerWinter, comfort, eco, maxFlow, current, target] = await Promise.all([
|
|
814
|
+
setting(get, `${base}/${p.operationMode}`),
|
|
815
|
+
setting(get, `${base}/${p.heatCoolMode}`),
|
|
816
|
+
setting(get, `${base}/${p.summerWinter}`),
|
|
817
|
+
setting(get, `${base}/${p.comfort}`),
|
|
818
|
+
setting(get, `${base}/${p.eco}`),
|
|
819
|
+
setting(get, `${base}/${p.maxFlow}`),
|
|
820
|
+
num(get, `${base}/${p.current}`),
|
|
821
|
+
num(get, `${base}/${p.target}`)
|
|
822
|
+
]);
|
|
823
|
+
return {
|
|
824
|
+
id,
|
|
825
|
+
kind: "hc",
|
|
826
|
+
currentTemp: current,
|
|
827
|
+
targetTemp: target,
|
|
828
|
+
operationMode,
|
|
829
|
+
heatCoolMode,
|
|
830
|
+
summerWinter,
|
|
831
|
+
comfort,
|
|
832
|
+
eco,
|
|
833
|
+
maxFlow,
|
|
834
|
+
level: null,
|
|
835
|
+
chargeDuration: null,
|
|
836
|
+
charge: null
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
async function readDhwCircuit(get, id) {
|
|
840
|
+
const base = `/dhwCircuits/${id}`;
|
|
841
|
+
const p = HEAT_PUMP_PATHS.dhw;
|
|
842
|
+
const [operationMode, level, chargeDuration, charge, current, target] = await Promise.all([
|
|
843
|
+
setting(get, `${base}/${p.operationMode}`),
|
|
844
|
+
setting(get, `${base}/${p.level}`),
|
|
845
|
+
setting(get, `${base}/${p.chargeDuration}`),
|
|
846
|
+
setting(get, `${base}/${p.charge}`),
|
|
847
|
+
num(get, `${base}/${p.current}`),
|
|
848
|
+
num(get, `${base}/${p.target}`)
|
|
849
|
+
]);
|
|
850
|
+
return {
|
|
851
|
+
id,
|
|
852
|
+
kind: "dhw",
|
|
853
|
+
currentTemp: current,
|
|
854
|
+
targetTemp: target,
|
|
855
|
+
operationMode,
|
|
856
|
+
heatCoolMode: null,
|
|
857
|
+
summerWinter: null,
|
|
858
|
+
comfort: null,
|
|
859
|
+
eco: null,
|
|
860
|
+
maxFlow: null,
|
|
861
|
+
level,
|
|
862
|
+
chargeDuration,
|
|
863
|
+
charge
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
async function setting(get, path) {
|
|
867
|
+
const envelope = readEnvelope(await get(path));
|
|
868
|
+
return envelope === null ? null : settingOf(path, envelope);
|
|
869
|
+
}
|
|
870
|
+
async function num(get, path) {
|
|
871
|
+
const envelope = readEnvelope(await get(path));
|
|
872
|
+
return envelope !== null && typeof envelope.value === "number" && Number.isFinite(envelope.value) ? envelope.value : null;
|
|
873
|
+
}
|
|
874
|
+
function settingOf(path, envelope) {
|
|
875
|
+
return {
|
|
876
|
+
path,
|
|
877
|
+
value: envelope.value,
|
|
878
|
+
min: envelope.min,
|
|
879
|
+
max: envelope.max,
|
|
880
|
+
allowed: envelope.allowed
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
function heatPumpStatusFrom(reading, now) {
|
|
884
|
+
return {
|
|
885
|
+
circuits: reading.circuits.map(circuitOf),
|
|
886
|
+
system: {
|
|
887
|
+
silent: optionOf(reading.silent),
|
|
888
|
+
away: optionOf(reading.away),
|
|
889
|
+
additionalHeater: optionOf(reading.additionalHeater),
|
|
890
|
+
holiday: optionOf(reading.holiday)
|
|
891
|
+
},
|
|
892
|
+
power: {
|
|
893
|
+
thermalW: reading.thermalW,
|
|
894
|
+
electricalW: reading.electricalW,
|
|
895
|
+
compressorPct: reading.compressorPct
|
|
896
|
+
},
|
|
897
|
+
temperatures: {
|
|
898
|
+
supply: reading.supply,
|
|
899
|
+
return: reading.return,
|
|
900
|
+
outdoor: reading.outdoor
|
|
901
|
+
},
|
|
902
|
+
energy: {
|
|
903
|
+
thermalKwh: reading.thermalKwh,
|
|
904
|
+
electricalKwh: reading.electricalKwh
|
|
905
|
+
},
|
|
906
|
+
waterPressureBar: reading.pressureBar,
|
|
907
|
+
starts: reading.starts,
|
|
908
|
+
lastFetchedAt: now
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
function circuitOf(c) {
|
|
912
|
+
const kind = kindOf(c);
|
|
913
|
+
const mode = textOf$1(c.operationMode);
|
|
914
|
+
return {
|
|
915
|
+
id: c.id,
|
|
916
|
+
kind,
|
|
917
|
+
label: labelOf(c),
|
|
918
|
+
icon: kind === "dhw" ? "droplets" : kind === "cooling" ? "snowflake" : "flame",
|
|
919
|
+
currentTemp: c.currentTemp,
|
|
920
|
+
targetTemp: c.targetTemp,
|
|
921
|
+
mode,
|
|
922
|
+
active: null,
|
|
923
|
+
operationMode: optionOf(c.operationMode),
|
|
924
|
+
heatCoolMode: optionOf(c.heatCoolMode),
|
|
925
|
+
summerWinter: optionOf(c.summerWinter),
|
|
926
|
+
comfortSetpoint: rangeOf(c.comfort),
|
|
927
|
+
ecoSetpoint: rangeOf(c.eco),
|
|
928
|
+
maxFlowTemp: rangeOf(c.maxFlow),
|
|
929
|
+
temperatureLevel: optionOf(c.level),
|
|
930
|
+
chargeDuration: rangeOf(c.chargeDuration),
|
|
931
|
+
extraHotWater: c.charge === null ? null : textOf$1(c.charge) === "start"
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function kindOf(c) {
|
|
935
|
+
if (c.kind === "dhw") return "dhw";
|
|
936
|
+
return textOf$1(c.heatCoolMode) === "cooling" ? "cooling" : "heating";
|
|
937
|
+
}
|
|
938
|
+
function labelOf(c) {
|
|
939
|
+
const n = c.id.replace(/^\D+/, "");
|
|
940
|
+
if (c.kind === "dhw") return n === "1" || n === "" ? "Hot water" : `Hot water ${n}`;
|
|
941
|
+
return n === "" ? "Circuit" : `Circuit ${n}`;
|
|
942
|
+
}
|
|
943
|
+
function textOf$1(s) {
|
|
944
|
+
return s !== null && typeof s.value === "string" && s.value.length > 0 ? s.value : null;
|
|
945
|
+
}
|
|
946
|
+
function optionOf(s) {
|
|
947
|
+
if (s === null) return null;
|
|
948
|
+
return {
|
|
949
|
+
value: textOf$1(s),
|
|
950
|
+
allowed: [...s.allowed]
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
function rangeOf(s) {
|
|
954
|
+
if (s === null) return null;
|
|
955
|
+
return {
|
|
956
|
+
value: typeof s.value === "number" && Number.isFinite(s.value) ? s.value : null,
|
|
957
|
+
min: s.min,
|
|
958
|
+
max: s.max,
|
|
959
|
+
step: null
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
function requireCircuit(reading, circuitId, want) {
|
|
963
|
+
const found = reading.circuits.find((c) => c.id === circuitId && c.kind === want);
|
|
964
|
+
if (found === void 0) throw new HomeComHeatPumpRefusalError(`HomeCom heat pump has no ${want} circuit ${circuitId}`);
|
|
965
|
+
return found;
|
|
966
|
+
}
|
|
967
|
+
function optionWrite(s, what, mode) {
|
|
968
|
+
if (s === null) throw new HomeComHeatPumpRefusalError(`HomeCom heat pump: ${what} is not available`);
|
|
969
|
+
if (s.allowed.length === 0) throw new HomeComHeatPumpRefusalError(`HomeCom heat pump: ${what} is read-only on this gateway`);
|
|
970
|
+
if (!s.allowed.includes(mode)) throw new HomeComHeatPumpRefusalError(`HomeCom heat pump: "${mode}" is not an accepted value of ${what} (${s.allowed.join(", ")})`);
|
|
971
|
+
return {
|
|
972
|
+
path: s.path,
|
|
973
|
+
value: mode
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
function rangeWrite(s, what, value) {
|
|
977
|
+
if (s === null) throw new HomeComHeatPumpRefusalError(`HomeCom heat pump: ${what} is not available`);
|
|
978
|
+
if (!Number.isFinite(value)) throw new HomeComHeatPumpRefusalError(`HomeCom heat pump: ${what} needs a finite number`);
|
|
979
|
+
if (s.min === null || s.max === null) throw new HomeComHeatPumpRefusalError(`HomeCom heat pump: ${what} has no limits from the gateway`);
|
|
980
|
+
if (value < s.min || value > s.max) throw new HomeComHeatPumpRefusalError(`HomeCom heat pump: ${value} is outside ${what} limits ${s.min}..${s.max}`);
|
|
981
|
+
return {
|
|
982
|
+
path: s.path,
|
|
983
|
+
value
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
function systemModeWrite(reading, which, mode) {
|
|
987
|
+
return optionWrite(reading[which], `${which} mode`, mode);
|
|
988
|
+
}
|
|
989
|
+
function circuitOptionWrite(reading, circuitId, which, mode) {
|
|
990
|
+
return optionWrite(requireCircuit(reading, circuitId, "hc")[which], `${circuitId} ${which}`, mode);
|
|
991
|
+
}
|
|
992
|
+
function dhwOptionWrite(reading, circuitId, which, mode) {
|
|
993
|
+
return optionWrite(requireCircuit(reading, circuitId, "dhw")[which], `${circuitId} ${which}`, mode);
|
|
994
|
+
}
|
|
995
|
+
function circuitRangeWrite(reading, circuitId, which, value) {
|
|
996
|
+
return rangeWrite(requireCircuit(reading, circuitId, "hc")[which], `${circuitId} ${which}`, value);
|
|
997
|
+
}
|
|
998
|
+
function dhwChargeDurationWrite(reading, circuitId, minutes) {
|
|
999
|
+
return rangeWrite(requireCircuit(reading, circuitId, "dhw").chargeDuration, `${circuitId} charge duration`, minutes);
|
|
1000
|
+
}
|
|
1001
|
+
function extraHotWaterWrite(reading, circuitId) {
|
|
1002
|
+
const charge = requireCircuit(reading, circuitId, "dhw").charge;
|
|
1003
|
+
return optionWrite(charge, `${circuitId} extra hot water`, "start");
|
|
1004
|
+
}
|
|
1005
|
+
//#endregion
|
|
1006
|
+
//#region src/homecom/homecom-oauth.ts
|
|
1007
|
+
/**
|
|
1008
|
+
* Public OAuth client of the HomeCom Easy app, signed in through SingleKey ID.
|
|
1009
|
+
*
|
|
1010
|
+
* A PKCE client has no secret. The values are the ones SingleKey accepts for this client id
|
|
1011
|
+
* today: the same authorize query the MIT-licensed `homecom_alt` client records
|
|
1012
|
+
* (`OAUTH_LOGIN_PARAMS` — parameters, not copied code) and that the Home Assistant integration
|
|
1013
|
+
* opens. `pointt.gateway.resource.dopair` is not a scope this client may request; SingleKey
|
|
1014
|
+
* answers `misconfigured-application` and never shows the login page. The scope it does accept
|
|
1015
|
+
* names `pointt.gateway.resource.dashapp`, and the Bosch app sends `style_id=tt_bsch`.
|
|
1016
|
+
*/
|
|
1017
|
+
var HOMECOM_CLIENT_ID = "762162C0-FA2D-4540-AE66-6489F189FADC";
|
|
1018
|
+
var HOMECOM_AUTHORIZE_URL = "https://singlekey-id.com/auth/connect/authorize";
|
|
1019
|
+
var HOMECOM_TOKEN_URL = "https://singlekey-id.com/auth/connect/token";
|
|
1020
|
+
var HOMECOM_STYLE_ID = "tt_bsch";
|
|
1021
|
+
var HOMECOM_SCOPE = [
|
|
1022
|
+
"openid",
|
|
1023
|
+
"email",
|
|
1024
|
+
"profile",
|
|
1025
|
+
"offline_access",
|
|
1026
|
+
"pointt.gateway.claiming",
|
|
1027
|
+
"pointt.gateway.removal",
|
|
1028
|
+
"pointt.gateway.list",
|
|
1029
|
+
"pointt.gateway.users",
|
|
1030
|
+
"pointt.gateway.resource.dashapp",
|
|
1031
|
+
"pointt.castt.flow.token-exchange",
|
|
1032
|
+
"bacon",
|
|
1033
|
+
"hcc.tariff.read"
|
|
1034
|
+
].join(" ");
|
|
1035
|
+
//#endregion
|
|
720
1036
|
//#region src/homecom/homecom-api-client.ts
|
|
721
1037
|
var HomeComAuthError = class extends Error {
|
|
722
1038
|
name = "HomeComAuthError";
|
|
@@ -930,15 +1246,43 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
|
|
|
930
1246
|
}
|
|
931
1247
|
async function fetchSystem() {
|
|
932
1248
|
const resolved = await resolveDeviceType();
|
|
933
|
-
const
|
|
1249
|
+
const withHeatPump = await attachHeatPump(await readPointtSnapshot({
|
|
934
1250
|
deviceType: resolved.deviceType,
|
|
935
1251
|
family: resolved.family,
|
|
936
1252
|
gatewayId,
|
|
937
1253
|
get: getResource,
|
|
938
1254
|
logDrop
|
|
1255
|
+
}));
|
|
1256
|
+
last = withHeatPump;
|
|
1257
|
+
return withHeatPump;
|
|
1258
|
+
}
|
|
1259
|
+
async function attachHeatPump(snapshot) {
|
|
1260
|
+
if (snapshot.family !== "hydronic" || snapshot.deviceType === "rrc2") return snapshot;
|
|
1261
|
+
const dhwId = snapshot.dhw?.circuitId;
|
|
1262
|
+
const heatPump = await readHeatPump({
|
|
1263
|
+
get: getResource,
|
|
1264
|
+
onFailure: logHeatPumpFailure,
|
|
1265
|
+
circuitIds: {
|
|
1266
|
+
hc: snapshot.circuits.map((circuit) => circuit.id),
|
|
1267
|
+
dhw: dhwId === void 0 ? [] : [dhwId]
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
return hasHeatSource(heatPump) ? {
|
|
1271
|
+
...snapshot,
|
|
1272
|
+
heatPump
|
|
1273
|
+
} : snapshot;
|
|
1274
|
+
}
|
|
1275
|
+
function logHeatPumpFailure(path, error) {
|
|
1276
|
+
const key = `heat-pump-read:${path}`;
|
|
1277
|
+
if (dropped.has(key)) return;
|
|
1278
|
+
dropped.add(key);
|
|
1279
|
+
logger?.warn("HomeCom heat-pump resource could not be read; it is skipped", {
|
|
1280
|
+
tags: { gatewayId },
|
|
1281
|
+
meta: {
|
|
1282
|
+
path,
|
|
1283
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1284
|
+
}
|
|
939
1285
|
});
|
|
940
|
-
last = snapshot;
|
|
941
|
-
return snapshot;
|
|
942
1286
|
}
|
|
943
1287
|
async function probePath(path) {
|
|
944
1288
|
const res = await authed(homeComResourceUrl(gatewayId, path), {
|
|
@@ -992,6 +1336,9 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
|
|
|
992
1336
|
const snapshot = await requireSnapshot();
|
|
993
1337
|
if (snapshot.family !== "rac") logDrop("swing is not a resource on this device type");
|
|
994
1338
|
await putWrite(swingWrite(snapshot, "horizontal", on));
|
|
1339
|
+
},
|
|
1340
|
+
async writeHeatPump(writes) {
|
|
1341
|
+
for (const write of writes) await putWrite(write);
|
|
995
1342
|
}
|
|
996
1343
|
};
|
|
997
1344
|
}
|
|
@@ -1256,8 +1603,7 @@ function buildHomeComGatewayPersister(deps) {
|
|
|
1256
1603
|
function buildHomeComCandidates(input) {
|
|
1257
1604
|
const out = [];
|
|
1258
1605
|
for (const dev of input.devices) {
|
|
1259
|
-
const type = dev.kind
|
|
1260
|
-
const caps = dev.kind === "heating-circuit" ? ["climate-control"] : ["water-heater"];
|
|
1606
|
+
const { type, caps } = shapeOf(dev.kind);
|
|
1261
1607
|
const adoptedId = input.adopted.get(dev.nativeId) ?? null;
|
|
1262
1608
|
out.push({
|
|
1263
1609
|
childNativeId: dev.nativeId,
|
|
@@ -1272,6 +1618,22 @@ function buildHomeComCandidates(input) {
|
|
|
1272
1618
|
}
|
|
1273
1619
|
return out;
|
|
1274
1620
|
}
|
|
1621
|
+
function shapeOf(kind) {
|
|
1622
|
+
switch (kind) {
|
|
1623
|
+
case "heating-circuit": return {
|
|
1624
|
+
type: require_bosch_instances.DeviceType.Climate,
|
|
1625
|
+
caps: ["climate-control"]
|
|
1626
|
+
};
|
|
1627
|
+
case "dhw": return {
|
|
1628
|
+
type: require_bosch_instances.DeviceType.WaterHeater,
|
|
1629
|
+
caps: ["water-heater"]
|
|
1630
|
+
};
|
|
1631
|
+
case "heat-pump": return {
|
|
1632
|
+
type: require_bosch_instances.DeviceType.HeatPump,
|
|
1633
|
+
caps: ["heat-pump-status", "heat-pump-control"]
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1275
1637
|
//#endregion
|
|
1276
1638
|
//#region src/homecom/homecom-adoption-provider.ts
|
|
1277
1639
|
var DEVICES_FILTER = {
|
|
@@ -1456,6 +1818,11 @@ function buildDiscoverables(snapshot) {
|
|
|
1456
1818
|
name: "Domestic hot water",
|
|
1457
1819
|
kind: "dhw"
|
|
1458
1820
|
});
|
|
1821
|
+
if (snapshot.heatPump !== void 0) out.push({
|
|
1822
|
+
nativeId: "hp:0",
|
|
1823
|
+
name: "Heat pump",
|
|
1824
|
+
kind: "heat-pump"
|
|
1825
|
+
});
|
|
1459
1826
|
return out;
|
|
1460
1827
|
}
|
|
1461
1828
|
var homeComConnections = new HomeComConnectionResolver();
|
|
@@ -1942,7 +2309,7 @@ var homeComClimateSchema = require_bosch_instances.object({
|
|
|
1942
2309
|
integrationId: require_bosch_instances.string().optional()
|
|
1943
2310
|
});
|
|
1944
2311
|
var CLIMATE_CAP = "climate-control";
|
|
1945
|
-
var COLD_START$
|
|
2312
|
+
var COLD_START$2 = {
|
|
1946
2313
|
mode: "off",
|
|
1947
2314
|
availableModes: [],
|
|
1948
2315
|
fanMode: "",
|
|
@@ -1977,14 +2344,14 @@ var HomeComClimateDevice = class extends require_bosch_instances.BaseDevice {
|
|
|
1977
2344
|
async onActivate() {
|
|
1978
2345
|
await super.onActivate();
|
|
1979
2346
|
this.registerClimateCap();
|
|
1980
|
-
this.runtimeState.setCapState(CLIMATE_CAP, COLD_START$
|
|
2347
|
+
this.runtimeState.setCapState(CLIMATE_CAP, COLD_START$2);
|
|
1981
2348
|
this.refreshFromSnapshot();
|
|
1982
2349
|
}
|
|
1983
2350
|
refreshFromSnapshot() {
|
|
1984
2351
|
const circuit = homeComConnections.getSnapshot(this.#brokerId)?.circuits.find((c) => c.id === this.#circuitId);
|
|
1985
2352
|
if (!circuit) {
|
|
1986
2353
|
this.runtimeState.setCapState(CLIMATE_CAP, {
|
|
1987
|
-
...COLD_START$
|
|
2354
|
+
...COLD_START$2,
|
|
1988
2355
|
lastFetchedAt: Date.now()
|
|
1989
2356
|
});
|
|
1990
2357
|
return;
|
|
@@ -2000,7 +2367,7 @@ var HomeComClimateDevice = class extends require_bosch_instances.BaseDevice {
|
|
|
2000
2367
|
this.ctx.registerNativeCap(require_bosch_instances.climateControlCapability, {
|
|
2001
2368
|
getStatus: async () => {
|
|
2002
2369
|
this.refreshFromSnapshot();
|
|
2003
|
-
return this.runtimeState.getCapState(CLIMATE_CAP) ?? COLD_START$
|
|
2370
|
+
return this.runtimeState.getCapState(CLIMATE_CAP) ?? COLD_START$2;
|
|
2004
2371
|
},
|
|
2005
2372
|
setMode: async ({ mode }) => {
|
|
2006
2373
|
const apiMode = hvacModeToHomeCom(mode);
|
|
@@ -2052,7 +2419,7 @@ var homeComWaterHeaterSchema = require_bosch_instances.object({
|
|
|
2052
2419
|
integrationId: require_bosch_instances.string().optional()
|
|
2053
2420
|
});
|
|
2054
2421
|
var WH_CAP = "water-heater";
|
|
2055
|
-
var COLD_START = {
|
|
2422
|
+
var COLD_START$1 = {
|
|
2056
2423
|
currentTemp: null,
|
|
2057
2424
|
targetTemp: null,
|
|
2058
2425
|
operationMode: null,
|
|
@@ -2078,14 +2445,14 @@ var HomeComWaterHeaterDevice = class extends require_bosch_instances.BaseDevice
|
|
|
2078
2445
|
async onActivate() {
|
|
2079
2446
|
await super.onActivate();
|
|
2080
2447
|
this.registerWaterHeaterCap();
|
|
2081
|
-
this.runtimeState.setCapState(WH_CAP, COLD_START);
|
|
2448
|
+
this.runtimeState.setCapState(WH_CAP, COLD_START$1);
|
|
2082
2449
|
this.refreshFromSnapshot();
|
|
2083
2450
|
}
|
|
2084
2451
|
refreshFromSnapshot() {
|
|
2085
2452
|
const dhw = homeComConnections.getSnapshot(this.#brokerId)?.dhw;
|
|
2086
2453
|
if (!dhw) {
|
|
2087
2454
|
this.runtimeState.setCapState(WH_CAP, {
|
|
2088
|
-
...COLD_START,
|
|
2455
|
+
...COLD_START$1,
|
|
2089
2456
|
lastChangedAt: Date.now()
|
|
2090
2457
|
});
|
|
2091
2458
|
return;
|
|
@@ -2101,7 +2468,7 @@ var HomeComWaterHeaterDevice = class extends require_bosch_instances.BaseDevice
|
|
|
2101
2468
|
this.ctx.registerNativeCap(require_bosch_instances.waterHeaterCapability, {
|
|
2102
2469
|
getStatus: async () => {
|
|
2103
2470
|
this.refreshFromSnapshot();
|
|
2104
|
-
return this.runtimeState.getCapState(WH_CAP) ?? COLD_START;
|
|
2471
|
+
return this.runtimeState.getCapState(WH_CAP) ?? COLD_START$1;
|
|
2105
2472
|
},
|
|
2106
2473
|
setTargetTemp: async ({ temp }) => {
|
|
2107
2474
|
await this.requireClient().setDhwTarget(temp);
|
|
@@ -2118,13 +2485,157 @@ var HomeComWaterHeaterDevice = class extends require_bosch_instances.BaseDevice
|
|
|
2118
2485
|
}
|
|
2119
2486
|
};
|
|
2120
2487
|
//#endregion
|
|
2488
|
+
//#region src/homecom/devices/homecom-heat-pump-device.ts
|
|
2489
|
+
var homeComHeatPumpSchema = require_bosch_instances.object({
|
|
2490
|
+
nativeId: require_bosch_instances.string().min(1),
|
|
2491
|
+
brokerId: require_bosch_instances.string().min(1),
|
|
2492
|
+
system: require_bosch_instances.literal("homecom").optional(),
|
|
2493
|
+
integrationId: require_bosch_instances.string().optional()
|
|
2494
|
+
});
|
|
2495
|
+
var STATUS_CAP = "heat-pump-status";
|
|
2496
|
+
var COLD_START = {
|
|
2497
|
+
circuits: [],
|
|
2498
|
+
system: {
|
|
2499
|
+
silent: null,
|
|
2500
|
+
away: null,
|
|
2501
|
+
additionalHeater: null,
|
|
2502
|
+
holiday: null
|
|
2503
|
+
},
|
|
2504
|
+
power: {
|
|
2505
|
+
thermalW: null,
|
|
2506
|
+
electricalW: null,
|
|
2507
|
+
compressorPct: null
|
|
2508
|
+
},
|
|
2509
|
+
temperatures: {
|
|
2510
|
+
supply: null,
|
|
2511
|
+
return: null,
|
|
2512
|
+
outdoor: null
|
|
2513
|
+
},
|
|
2514
|
+
energy: {
|
|
2515
|
+
thermalKwh: null,
|
|
2516
|
+
electricalKwh: null
|
|
2517
|
+
},
|
|
2518
|
+
waterPressureBar: null,
|
|
2519
|
+
starts: null,
|
|
2520
|
+
lastFetchedAt: 0
|
|
2521
|
+
};
|
|
2522
|
+
/**
|
|
2523
|
+
* A heat pump on a hydronic HomeCom gateway (D1026). The slice and every limit
|
|
2524
|
+
* come from the gateway reading the integration poll already holds; a write is
|
|
2525
|
+
* checked against those limits first and the reading is refreshed after it.
|
|
2526
|
+
*/
|
|
2527
|
+
var HomeComHeatPumpDevice = class extends require_bosch_instances.BaseDevice {
|
|
2528
|
+
features = [];
|
|
2529
|
+
#brokerId;
|
|
2530
|
+
/** True while the gateway has no heat-pump reading, so the log marks the transitions only. */
|
|
2531
|
+
#noReading = false;
|
|
2532
|
+
constructor(ctx) {
|
|
2533
|
+
const persisted = homeComHeatPumpSchema.parse(ctx.persistedConfig ?? {});
|
|
2534
|
+
super(ctx, homeComHeatPumpSchema, { type: ctx.deviceMeta.type });
|
|
2535
|
+
this.#brokerId = persisted.brokerId;
|
|
2536
|
+
this.online = true;
|
|
2537
|
+
this.updateSourceInfo({
|
|
2538
|
+
id: persisted.nativeId,
|
|
2539
|
+
system: "homecom"
|
|
2540
|
+
});
|
|
2541
|
+
}
|
|
2542
|
+
async onActivate() {
|
|
2543
|
+
await super.onActivate();
|
|
2544
|
+
this.registerStatusCap();
|
|
2545
|
+
this.registerControlCap();
|
|
2546
|
+
this.runtimeState.setCapState(STATUS_CAP, COLD_START);
|
|
2547
|
+
this.refreshFromSnapshot();
|
|
2548
|
+
}
|
|
2549
|
+
reading() {
|
|
2550
|
+
return homeComConnections.getSnapshot(this.#brokerId)?.heatPump ?? null;
|
|
2551
|
+
}
|
|
2552
|
+
refreshFromSnapshot() {
|
|
2553
|
+
const reading = this.reading();
|
|
2554
|
+
if (reading === null) {
|
|
2555
|
+
if (!this.#noReading) {
|
|
2556
|
+
this.#noReading = true;
|
|
2557
|
+
this.ctx.logger.warn("HomeCom heat pump: the gateway returned no heat-pump reading", { tags: { deviceId: this.ctx.id } });
|
|
2558
|
+
}
|
|
2559
|
+
this.runtimeState.setCapState(STATUS_CAP, {
|
|
2560
|
+
...COLD_START,
|
|
2561
|
+
lastFetchedAt: Date.now()
|
|
2562
|
+
});
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
if (this.#noReading) {
|
|
2566
|
+
this.#noReading = false;
|
|
2567
|
+
this.ctx.logger.info("HomeCom heat pump: the gateway answers with a heat-pump reading again", { tags: { deviceId: this.ctx.id } });
|
|
2568
|
+
}
|
|
2569
|
+
this.runtimeState.setCapState(STATUS_CAP, heatPumpStatusFrom(reading, Date.now()));
|
|
2570
|
+
}
|
|
2571
|
+
requireClient() {
|
|
2572
|
+
const client = homeComConnections.getClient(this.#brokerId);
|
|
2573
|
+
if (!client) throw new Error("HomeCom client unavailable (heat pump)");
|
|
2574
|
+
return client;
|
|
2575
|
+
}
|
|
2576
|
+
requireReading() {
|
|
2577
|
+
const reading = this.reading();
|
|
2578
|
+
if (reading === null) throw new Error("HomeCom heat pump: no reading from the gateway yet");
|
|
2579
|
+
return reading;
|
|
2580
|
+
}
|
|
2581
|
+
/** Validate against the reading, send, then re-read so the slice shows what the gateway holds. */
|
|
2582
|
+
async apply(build) {
|
|
2583
|
+
const write = build(this.requireReading());
|
|
2584
|
+
const client = this.requireClient();
|
|
2585
|
+
await client.writeHeatPump([write]);
|
|
2586
|
+
this.ctx.logger.info("HomeCom heat pump: value written", {
|
|
2587
|
+
tags: { deviceId: this.ctx.id },
|
|
2588
|
+
meta: { path: write.path }
|
|
2589
|
+
});
|
|
2590
|
+
try {
|
|
2591
|
+
const snapshot = await client.fetchSystem();
|
|
2592
|
+
homeComConnections.updateSnapshot(this.#brokerId, snapshot);
|
|
2593
|
+
} catch (error) {
|
|
2594
|
+
this.ctx.logger.warn("HomeCom heat pump: value written, the refresh failed", {
|
|
2595
|
+
tags: { deviceId: this.ctx.id },
|
|
2596
|
+
meta: {
|
|
2597
|
+
path: write.path,
|
|
2598
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2599
|
+
}
|
|
2600
|
+
});
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2603
|
+
this.refreshFromSnapshot();
|
|
2604
|
+
}
|
|
2605
|
+
registerStatusCap() {
|
|
2606
|
+
this.ctx.registerNativeCap(require_bosch_instances.heatPumpStatusCapability, { getStatus: async () => {
|
|
2607
|
+
this.refreshFromSnapshot();
|
|
2608
|
+
return this.runtimeState.getCapState(STATUS_CAP) ?? COLD_START;
|
|
2609
|
+
} });
|
|
2610
|
+
}
|
|
2611
|
+
registerControlCap() {
|
|
2612
|
+
this.ctx.registerNativeCap(require_bosch_instances.heatPumpControlCapability, {
|
|
2613
|
+
setSilentMode: ({ mode }) => this.apply((r) => systemModeWrite(r, "silent", mode)),
|
|
2614
|
+
setAwayMode: ({ mode }) => this.apply((r) => systemModeWrite(r, "away", mode)),
|
|
2615
|
+
setAdditionalHeaterMode: ({ mode }) => this.apply((r) => systemModeWrite(r, "additionalHeater", mode)),
|
|
2616
|
+
setHolidayMode: ({ mode }) => this.apply((r) => systemModeWrite(r, "holiday", mode)),
|
|
2617
|
+
setCircuitOperationMode: ({ circuitId, mode }) => this.apply((r) => circuitOptionWrite(r, circuitId, "operationMode", mode)),
|
|
2618
|
+
setCircuitHeatCoolMode: ({ circuitId, mode }) => this.apply((r) => circuitOptionWrite(r, circuitId, "heatCoolMode", mode)),
|
|
2619
|
+
setCircuitSummerWinter: ({ circuitId, mode }) => this.apply((r) => circuitOptionWrite(r, circuitId, "summerWinter", mode)),
|
|
2620
|
+
setCircuitComfortSetpoint: ({ circuitId, temp }) => this.apply((r) => circuitRangeWrite(r, circuitId, "comfort", temp)),
|
|
2621
|
+
setCircuitEcoSetpoint: ({ circuitId, temp }) => this.apply((r) => circuitRangeWrite(r, circuitId, "eco", temp)),
|
|
2622
|
+
setCircuitMaxFlowTemp: ({ circuitId, temp }) => this.apply((r) => circuitRangeWrite(r, circuitId, "maxFlow", temp)),
|
|
2623
|
+
setDhwOperationMode: ({ circuitId, mode }) => this.apply((r) => dhwOptionWrite(r, circuitId, "operationMode", mode)),
|
|
2624
|
+
setDhwTemperatureLevel: ({ circuitId, level }) => this.apply((r) => dhwOptionWrite(r, circuitId, "level", level)),
|
|
2625
|
+
setDhwChargeDuration: ({ circuitId, minutes }) => this.apply((r) => dhwChargeDurationWrite(r, circuitId, minutes)),
|
|
2626
|
+
startExtraHotWater: ({ circuitId }) => this.apply((r) => extraHotWaterWrite(r, circuitId))
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2630
|
+
//#endregion
|
|
2121
2631
|
//#region src/homecom/addon.ts
|
|
2122
2632
|
var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDeviceProvider {
|
|
2123
2633
|
addonId = "bosch-homecom";
|
|
2124
2634
|
providerName = "Bosch HomeCom";
|
|
2125
2635
|
deviceClasses = {
|
|
2126
2636
|
[require_bosch_instances.DeviceType.Climate]: HomeComClimateDevice,
|
|
2127
|
-
[require_bosch_instances.DeviceType.WaterHeater]: HomeComWaterHeaterDevice
|
|
2637
|
+
[require_bosch_instances.DeviceType.WaterHeater]: HomeComWaterHeaterDevice,
|
|
2638
|
+
[require_bosch_instances.DeviceType.HeatPump]: HomeComHeatPumpDevice
|
|
2128
2639
|
};
|
|
2129
2640
|
clients = null;
|
|
2130
2641
|
instances = null;
|