@camstack/addon-provider-bosch 0.1.20 → 0.1.21
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/dist/{account-integration-offline-7DUtQrPy.js → account-integration-offline-CEg0JOfN.js} +55 -1
- package/dist/{account-integration-offline-DvuqXgc8.mjs → account-integration-offline-Ciq9CZa-.mjs} +44 -2
- package/dist/{dist-CYBUFKeW.js → dist-CFPbdUpa.js} +143 -88
- package/dist/{dist-B75tWQLF.mjs → dist-D9i3id5N.mjs} +132 -89
- package/dist/home-connect/addon.js +137 -106
- package/dist/home-connect/addon.mjs +137 -106
- package/dist/homecom/addon.js +557 -433
- package/dist/homecom/addon.mjs +557 -433
- package/dist/shc/addon.js +331 -38
- package/dist/shc/addon.mjs +331 -38
- package/i18n/en.json +23 -19
- package/i18n/it.json +23 -19
- package/package.json +4 -1
package/dist/homecom/addon.js
CHANGED
|
@@ -1,26 +1,211 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_dist = require("../dist-
|
|
3
|
-
const require_account_integration_offline = require("../account-integration-offline-
|
|
2
|
+
const require_dist = require("../dist-CFPbdUpa.js");
|
|
3
|
+
const require_account_integration_offline = require("../account-integration-offline-CEg0JOfN.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
|
+
//#region src/homecom/config.ts
|
|
6
|
+
var HOMECOM_BRANDS = ["bosch", "buderus"];
|
|
7
|
+
var HomeComBrandSchema = require_dist._enum(HOMECOM_BRANDS);
|
|
8
|
+
var HOMECOM_REGIONS = ["eu", "us"];
|
|
9
|
+
var HomeComRegionSchema = require_dist._enum(HOMECOM_REGIONS);
|
|
10
|
+
var homeComConfigSchema = require_dist.object({
|
|
11
|
+
/** Account email; filled by the sign-in, informational only. */
|
|
12
|
+
username: require_dist.string().optional().describe("HomeCom / PointT account email").meta({ secret: true }),
|
|
13
|
+
token: require_dist.string().min(1).describe("OAuth access token").meta({ secret: true }),
|
|
14
|
+
refresh: require_dist.string().min(1).describe("OAuth refresh token").meta({ secret: true }),
|
|
15
|
+
brand: HomeComBrandSchema.default("bosch"),
|
|
16
|
+
region: HomeComRegionSchema.default("eu"),
|
|
17
|
+
/** The gateway this integration controls; chosen once from the account's gateways. */
|
|
18
|
+
gatewayId: require_dist.string().min(1).optional(),
|
|
19
|
+
/** Shown on the sign-in field. Not a credential. */
|
|
20
|
+
accountLabel: require_dist.string().optional(),
|
|
21
|
+
/** How many gateways the last sign-in listed. */
|
|
22
|
+
gatewayCount: require_dist.number().optional()
|
|
23
|
+
});
|
|
24
|
+
function connectionFromSettings(settings) {
|
|
25
|
+
const parsed = homeComConfigSchema.safeParse(settings);
|
|
26
|
+
return parsed.success ? parsed.data : null;
|
|
27
|
+
}
|
|
28
|
+
function sameHomeComConnection(a, b) {
|
|
29
|
+
if (a === null || b === null) return false;
|
|
30
|
+
return a.username === b.username && a.token === b.token && a.refresh === b.refresh && a.brand === b.brand && a.region === b.region && a.gatewayId === b.gatewayId;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* PointT REST origin (matches homecom_alt / HomeCom Easy). US and EU accounts use
|
|
34
|
+
* the same host; `region` is retained for Bacon MQTT (`euc1` / `use1`) when wired.
|
|
35
|
+
*/
|
|
36
|
+
var HOMECOM_POINTT_API_ORIGIN = "https://pointt-api.bosch-thermotechnology.com";
|
|
37
|
+
function homeComApiBase(_region) {
|
|
38
|
+
return HOMECOM_POINTT_API_ORIGIN;
|
|
39
|
+
}
|
|
40
|
+
var HOMECOM_SECRET_KEYS = [
|
|
41
|
+
"username",
|
|
42
|
+
"token",
|
|
43
|
+
"refresh"
|
|
44
|
+
];
|
|
45
|
+
var HIDDEN_UNTIL_ADVANCED = {
|
|
46
|
+
field: "__hidden__",
|
|
47
|
+
equals: "yes"
|
|
48
|
+
};
|
|
49
|
+
function buildHomeComFormSchema() {
|
|
50
|
+
return { sections: [{
|
|
51
|
+
id: "account",
|
|
52
|
+
title: "HomeCom account",
|
|
53
|
+
titleKey: "provider-bosch:homecom.s_account.title",
|
|
54
|
+
description: "Sign in with your SingleKey ID. Bosch opens its app address; paste that address here.",
|
|
55
|
+
descriptionKey: "provider-bosch:homecom.s_account.description",
|
|
56
|
+
columns: 1,
|
|
57
|
+
fields: [
|
|
58
|
+
{
|
|
59
|
+
type: "account-sign-in",
|
|
60
|
+
addonId: "bosch-homecom",
|
|
61
|
+
key: "__homecom-sign-in",
|
|
62
|
+
label: "HomeCom",
|
|
63
|
+
labelKey: "provider-bosch:homecom.s_account.__homecom-sign-in.label",
|
|
64
|
+
providerLabel: "SingleKey ID",
|
|
65
|
+
beginAction: "beginSignIn",
|
|
66
|
+
completeAction: "finishSignIn",
|
|
67
|
+
signOutAction: "signOut",
|
|
68
|
+
accountKey: "accountLabel",
|
|
69
|
+
countKey: "gatewayCount",
|
|
70
|
+
countNoun: "gateway",
|
|
71
|
+
choiceKey: "gatewayId",
|
|
72
|
+
choiceLabel: "Gateway",
|
|
73
|
+
pastePlaceholder: "com.bosch.tt.dashtt.pointt://app/login?code=…&state=…",
|
|
74
|
+
secretKeys: [
|
|
75
|
+
"token",
|
|
76
|
+
"refresh",
|
|
77
|
+
"username"
|
|
78
|
+
],
|
|
79
|
+
assignResult: {
|
|
80
|
+
token: "token",
|
|
81
|
+
refresh: "refresh",
|
|
82
|
+
username: "username",
|
|
83
|
+
accountLabel: "accountLabel",
|
|
84
|
+
gatewayId: "gatewayId",
|
|
85
|
+
brand: "brand",
|
|
86
|
+
region: "region",
|
|
87
|
+
gatewayCount: "gatewayCount"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: "text",
|
|
92
|
+
key: "accountLabel",
|
|
93
|
+
label: "Account",
|
|
94
|
+
labelKey: "provider-bosch:homecom.s_account.accountLabel.label",
|
|
95
|
+
required: false,
|
|
96
|
+
showWhen: HIDDEN_UNTIL_ADVANCED
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
type: "number",
|
|
100
|
+
key: "gatewayCount",
|
|
101
|
+
label: "Gateways",
|
|
102
|
+
labelKey: "provider-bosch:homecom.s_account.gatewayCount.label",
|
|
103
|
+
required: false,
|
|
104
|
+
showWhen: HIDDEN_UNTIL_ADVANCED
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: "password",
|
|
108
|
+
key: "username",
|
|
109
|
+
label: "Account email",
|
|
110
|
+
labelKey: "provider-bosch:homecom.s_account.username.label",
|
|
111
|
+
required: false,
|
|
112
|
+
showWhen: HIDDEN_UNTIL_ADVANCED
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
type: "group",
|
|
116
|
+
key: "__homecom-advanced",
|
|
117
|
+
label: "Advanced",
|
|
118
|
+
labelKey: "provider-bosch:homecom.s_account.__homecom-advanced.label",
|
|
119
|
+
style: "accordion",
|
|
120
|
+
defaultCollapsed: true,
|
|
121
|
+
fields: [
|
|
122
|
+
{
|
|
123
|
+
type: "password",
|
|
124
|
+
key: "token",
|
|
125
|
+
label: "Access token",
|
|
126
|
+
labelKey: "provider-bosch:homecom.s_account.token.label",
|
|
127
|
+
required: false,
|
|
128
|
+
showToggle: false
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
type: "password",
|
|
132
|
+
key: "refresh",
|
|
133
|
+
label: "Refresh token",
|
|
134
|
+
labelKey: "provider-bosch:homecom.s_account.refresh.label",
|
|
135
|
+
required: false,
|
|
136
|
+
showToggle: false
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: "text",
|
|
140
|
+
key: "gatewayId",
|
|
141
|
+
label: "Gateway id",
|
|
142
|
+
labelKey: "provider-bosch:homecom.s_account.gatewayId.label",
|
|
143
|
+
required: false
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: "select",
|
|
147
|
+
key: "brand",
|
|
148
|
+
label: "Brand",
|
|
149
|
+
labelKey: "provider-bosch:homecom.s_account.brand.label",
|
|
150
|
+
default: "bosch",
|
|
151
|
+
options: HOMECOM_BRANDS.map((b) => ({
|
|
152
|
+
value: b,
|
|
153
|
+
label: b
|
|
154
|
+
}))
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
type: "select",
|
|
158
|
+
key: "region",
|
|
159
|
+
label: "Region",
|
|
160
|
+
labelKey: "provider-bosch:homecom.s_account.region.label",
|
|
161
|
+
default: "eu",
|
|
162
|
+
options: HOMECOM_REGIONS.map((r) => ({
|
|
163
|
+
value: r,
|
|
164
|
+
label: r.toUpperCase()
|
|
165
|
+
}))
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}] };
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
5
173
|
//#region src/homecom/custom-actions.ts
|
|
174
|
+
var gatewayChoice = require_dist.object({
|
|
175
|
+
value: require_dist.string(),
|
|
176
|
+
label: require_dist.string(),
|
|
177
|
+
brand: HomeComBrandSchema,
|
|
178
|
+
region: HomeComRegionSchema
|
|
179
|
+
});
|
|
6
180
|
var homeComActions = require_dist.defineCustomActions({
|
|
7
181
|
beginSignIn: require_dist.customAction(require_dist.object({}), require_dist.object({
|
|
182
|
+
attemptId: require_dist.string().min(1),
|
|
8
183
|
authorizeUrl: require_dist.string().min(1),
|
|
9
|
-
verifier: require_dist.string().min(1),
|
|
10
|
-
state: require_dist.string().min(1),
|
|
11
184
|
summary: require_dist.string()
|
|
12
185
|
}), {
|
|
13
186
|
kind: "mutation",
|
|
14
187
|
auth: "admin"
|
|
15
188
|
}),
|
|
16
189
|
finishSignIn: require_dist.customAction(require_dist.object({
|
|
17
|
-
|
|
18
|
-
callback: require_dist.string().min(1)
|
|
19
|
-
state: require_dist.string().min(1)
|
|
190
|
+
attemptId: require_dist.string().min(1),
|
|
191
|
+
callback: require_dist.string().min(1)
|
|
20
192
|
}), require_dist.object({
|
|
21
193
|
token: require_dist.string().min(1).meta({ secret: true }),
|
|
22
194
|
refresh: require_dist.string().min(1).meta({ secret: true }),
|
|
23
195
|
username: require_dist.string().meta({ secret: true }),
|
|
196
|
+
accountLabel: require_dist.string(),
|
|
197
|
+
gatewayCount: require_dist.number(),
|
|
198
|
+
summary: require_dist.string(),
|
|
199
|
+
gatewayId: require_dist.string().optional(),
|
|
200
|
+
brand: HomeComBrandSchema.optional(),
|
|
201
|
+
region: HomeComRegionSchema.optional(),
|
|
202
|
+
choices: require_dist.array(gatewayChoice).optional()
|
|
203
|
+
}), {
|
|
204
|
+
kind: "mutation",
|
|
205
|
+
auth: "admin"
|
|
206
|
+
}),
|
|
207
|
+
signOut: require_dist.customAction(require_dist.object({}), require_dist.object({
|
|
208
|
+
signedOut: require_dist.literal(true),
|
|
24
209
|
summary: require_dist.string()
|
|
25
210
|
}), {
|
|
26
211
|
kind: "mutation",
|
|
@@ -28,6 +213,113 @@ var homeComActions = require_dist.defineCustomActions({
|
|
|
28
213
|
})
|
|
29
214
|
});
|
|
30
215
|
//#endregion
|
|
216
|
+
//#region src/homecom/homecom-domain-mapping.ts
|
|
217
|
+
var MODE_MAP = {
|
|
218
|
+
Off: "off",
|
|
219
|
+
off: "off",
|
|
220
|
+
Heat: "heat",
|
|
221
|
+
heat: "heat",
|
|
222
|
+
Cool: "cool",
|
|
223
|
+
cool: "cool",
|
|
224
|
+
Auto: "auto",
|
|
225
|
+
auto: "auto"
|
|
226
|
+
};
|
|
227
|
+
function mapHvacMode(raw) {
|
|
228
|
+
return MODE_MAP[raw] ?? "off";
|
|
229
|
+
}
|
|
230
|
+
function climateSliceFromCircuit(circuit, nowMs) {
|
|
231
|
+
return {
|
|
232
|
+
mode: mapHvacMode(circuit.operationMode),
|
|
233
|
+
availableModes: [
|
|
234
|
+
"off",
|
|
235
|
+
"heat",
|
|
236
|
+
"auto"
|
|
237
|
+
],
|
|
238
|
+
fanMode: "",
|
|
239
|
+
availableFanModes: [],
|
|
240
|
+
preset: "",
|
|
241
|
+
availablePresets: [],
|
|
242
|
+
target: circuit.setpoint,
|
|
243
|
+
targetHigh: null,
|
|
244
|
+
targetLow: null,
|
|
245
|
+
targetHumidity: null,
|
|
246
|
+
currentTemp: circuit.currentRoomTemp,
|
|
247
|
+
currentHumidity: null,
|
|
248
|
+
swingVertical: null,
|
|
249
|
+
swingHorizontal: null,
|
|
250
|
+
minTemp: circuit.minTemp,
|
|
251
|
+
maxTemp: circuit.maxTemp,
|
|
252
|
+
tempStep: .5,
|
|
253
|
+
lastFetchedAt: nowMs
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function waterHeaterSliceFromDhw(dhw, nowMs) {
|
|
257
|
+
return {
|
|
258
|
+
currentTemp: dhw.currentTemp,
|
|
259
|
+
targetTemp: dhw.targetTemp,
|
|
260
|
+
operationMode: dhw.operationMode,
|
|
261
|
+
availableModes: [...dhw.availableModes],
|
|
262
|
+
away: null,
|
|
263
|
+
minTemp: dhw.minTemp,
|
|
264
|
+
maxTemp: dhw.maxTemp,
|
|
265
|
+
lastChangedAt: nowMs
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/** Parse K40-style gateway JSON into heating circuits + DHW. Pure. */
|
|
269
|
+
function parseHomeComSystemPayload(payload) {
|
|
270
|
+
if (typeof payload !== "object" || payload === null) return {
|
|
271
|
+
circuits: [],
|
|
272
|
+
dhw: null
|
|
273
|
+
};
|
|
274
|
+
const root = payload;
|
|
275
|
+
const circuits = [];
|
|
276
|
+
const rawCircuits = root["heatingCircuits"];
|
|
277
|
+
if (Array.isArray(rawCircuits)) for (const item of rawCircuits) {
|
|
278
|
+
if (typeof item !== "object" || item === null) continue;
|
|
279
|
+
const row = item;
|
|
280
|
+
const id = row["id"];
|
|
281
|
+
if (typeof id !== "string") continue;
|
|
282
|
+
circuits.push({
|
|
283
|
+
id,
|
|
284
|
+
name: typeof row["name"] === "string" ? row["name"] : id,
|
|
285
|
+
operationMode: typeof row["operationMode"] === "string" ? row["operationMode"] : "off",
|
|
286
|
+
setpoint: readNum(row["setpoint"]),
|
|
287
|
+
currentRoomTemp: readNum(row["currentRoomTemperature"]),
|
|
288
|
+
minTemp: readNum(row["minTemp"]),
|
|
289
|
+
maxTemp: readNum(row["maxTemp"])
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
let dhw = null;
|
|
293
|
+
const rawDhw = root["domesticHotWater"];
|
|
294
|
+
if (typeof rawDhw === "object" && rawDhw !== null) {
|
|
295
|
+
const row = rawDhw;
|
|
296
|
+
const modes = [];
|
|
297
|
+
if (Array.isArray(row["operationModes"])) {
|
|
298
|
+
for (const m of row["operationModes"]) if (typeof m === "string") modes.push(m);
|
|
299
|
+
}
|
|
300
|
+
dhw = {
|
|
301
|
+
currentTemp: readNum(row["currentTemperature"]),
|
|
302
|
+
targetTemp: readNum(row["setpoint"]),
|
|
303
|
+
operationMode: typeof row["operationMode"] === "string" ? row["operationMode"] : null,
|
|
304
|
+
availableModes: modes.length > 0 ? modes : [
|
|
305
|
+
"off",
|
|
306
|
+
"eco",
|
|
307
|
+
"performance"
|
|
308
|
+
],
|
|
309
|
+
minTemp: readNum(row["minTemp"]),
|
|
310
|
+
maxTemp: readNum(row["maxTemp"])
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
circuits,
|
|
315
|
+
dhw
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function readNum(raw) {
|
|
319
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return raw;
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
31
323
|
//#region src/homecom/homecom-oauth.ts
|
|
32
324
|
/**
|
|
33
325
|
* Public OAuth client of the HomeCom Easy / PointT mobile app, signed in through SingleKey ID.
|
|
@@ -55,6 +347,179 @@ var HOMECOM_SCOPE = [
|
|
|
55
347
|
"hcc.tariff.read"
|
|
56
348
|
].join(" ");
|
|
57
349
|
//#endregion
|
|
350
|
+
//#region src/homecom/homecom-api-client.ts
|
|
351
|
+
var HomeComAuthError = class extends Error {
|
|
352
|
+
name = "HomeComAuthError";
|
|
353
|
+
};
|
|
354
|
+
function isRecord$1(value) {
|
|
355
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
356
|
+
}
|
|
357
|
+
function rowId(row) {
|
|
358
|
+
for (const key of [
|
|
359
|
+
"deviceId",
|
|
360
|
+
"gatewayId",
|
|
361
|
+
"id"
|
|
362
|
+
]) {
|
|
363
|
+
const value = row[key];
|
|
364
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
365
|
+
if (typeof value === "number") return String(value);
|
|
366
|
+
}
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
/** Gateways from the account listing: a bare array or `{ gateways: [...] }`. */
|
|
370
|
+
function parseHomeComGatewayList(payload) {
|
|
371
|
+
const rows = isRecord$1(payload) ? payload["gateways"] : payload;
|
|
372
|
+
if (!Array.isArray(rows)) return [];
|
|
373
|
+
const out = [];
|
|
374
|
+
for (const row of rows) {
|
|
375
|
+
if (!isRecord$1(row)) continue;
|
|
376
|
+
const gatewayId = rowId(row);
|
|
377
|
+
if (gatewayId === null) continue;
|
|
378
|
+
const name = row["name"];
|
|
379
|
+
const brand = row["brand"];
|
|
380
|
+
const region = row["region"];
|
|
381
|
+
out.push({
|
|
382
|
+
gatewayId,
|
|
383
|
+
name: typeof name === "string" && name.length > 0 ? name : gatewayId,
|
|
384
|
+
...typeof brand === "string" && brand.length > 0 ? { brand } : {},
|
|
385
|
+
...typeof region === "string" && region.length > 0 ? { region } : {}
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
return out;
|
|
389
|
+
}
|
|
390
|
+
function createHomeComApiClient(config, gatewayId, deps = {}) {
|
|
391
|
+
const fetchFn = deps.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
392
|
+
let token = config.token;
|
|
393
|
+
let refresh = config.refresh;
|
|
394
|
+
const base = homeComApiBase(config.region);
|
|
395
|
+
async function authHeaders() {
|
|
396
|
+
return {
|
|
397
|
+
Authorization: `Bearer ${token}`,
|
|
398
|
+
Accept: "application/json",
|
|
399
|
+
"Content-Type": "application/json"
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
async function request(path, init = {}) {
|
|
403
|
+
const res = await fetchFn(`${base}${path}`, {
|
|
404
|
+
method: init.method ?? "GET",
|
|
405
|
+
headers: await authHeaders(),
|
|
406
|
+
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
407
|
+
});
|
|
408
|
+
if (res.status === 401) throw new HomeComAuthError("token expired");
|
|
409
|
+
if (!res.ok) throw new Error(`HomeCom API ${path} failed: HTTP ${res.status}`);
|
|
410
|
+
return res.json();
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
async refreshTokens() {
|
|
414
|
+
const res = await fetchFn(HOMECOM_TOKEN_URL, {
|
|
415
|
+
method: "POST",
|
|
416
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
417
|
+
body: new URLSearchParams({
|
|
418
|
+
grant_type: "refresh_token",
|
|
419
|
+
refresh_token: refresh,
|
|
420
|
+
client_id: HOMECOM_CLIENT_ID
|
|
421
|
+
}).toString()
|
|
422
|
+
});
|
|
423
|
+
if (!res.ok) {
|
|
424
|
+
if (res.status === 401 || res.status === 403) throw new HomeComAuthError("refresh refused");
|
|
425
|
+
throw new Error(`HomeCom token refresh failed: HTTP ${res.status}`);
|
|
426
|
+
}
|
|
427
|
+
const body = await res.json();
|
|
428
|
+
if (typeof body !== "object" || body === null) throw new Error("HomeCom token refresh: bad body");
|
|
429
|
+
const row = body;
|
|
430
|
+
const newToken = row["access_token"];
|
|
431
|
+
const newRefresh = row["refresh_token"];
|
|
432
|
+
if (typeof newToken !== "string" || typeof newRefresh !== "string") throw new Error("HomeCom token refresh: missing tokens");
|
|
433
|
+
token = newToken;
|
|
434
|
+
refresh = newRefresh;
|
|
435
|
+
await deps.onTokensRotated?.({
|
|
436
|
+
token,
|
|
437
|
+
refresh
|
|
438
|
+
});
|
|
439
|
+
return {
|
|
440
|
+
token,
|
|
441
|
+
refresh
|
|
442
|
+
};
|
|
443
|
+
},
|
|
444
|
+
async listGateways() {
|
|
445
|
+
return parseHomeComGatewayList(await request("/v2/gateways"));
|
|
446
|
+
},
|
|
447
|
+
async fetchSystem() {
|
|
448
|
+
const parsed = parseHomeComSystemPayload(await request(`/v2/gateways/${gatewayId}/system`));
|
|
449
|
+
return {
|
|
450
|
+
gateway: {
|
|
451
|
+
gatewayId,
|
|
452
|
+
name: "HomeCom heat pump",
|
|
453
|
+
model: "K40"
|
|
454
|
+
},
|
|
455
|
+
circuits: parsed.circuits,
|
|
456
|
+
dhw: parsed.dhw
|
|
457
|
+
};
|
|
458
|
+
},
|
|
459
|
+
async setCircuitSetpoint(circuitId, temp) {
|
|
460
|
+
await request(`/v2/gateways/${gatewayId}/heatingCircuits/${circuitId}/setpoint`, {
|
|
461
|
+
method: "PUT",
|
|
462
|
+
body: { value: temp }
|
|
463
|
+
});
|
|
464
|
+
},
|
|
465
|
+
async setCircuitMode(circuitId, mode) {
|
|
466
|
+
await request(`/v2/gateways/${gatewayId}/heatingCircuits/${circuitId}/operationMode`, {
|
|
467
|
+
method: "PUT",
|
|
468
|
+
body: { value: mode }
|
|
469
|
+
});
|
|
470
|
+
},
|
|
471
|
+
async setDhwTarget(temp) {
|
|
472
|
+
await request(`/v2/gateways/${gatewayId}/domesticHotWater/setpoint`, {
|
|
473
|
+
method: "PUT",
|
|
474
|
+
body: { value: temp }
|
|
475
|
+
});
|
|
476
|
+
},
|
|
477
|
+
async setDhwMode(mode) {
|
|
478
|
+
await request(`/v2/gateways/${gatewayId}/domesticHotWater/operationMode`, {
|
|
479
|
+
method: "PUT",
|
|
480
|
+
body: { value: mode }
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
//#endregion
|
|
486
|
+
//#region src/homecom/homecom-gateway-pick.ts
|
|
487
|
+
function brandOf(value) {
|
|
488
|
+
for (const brand of HOMECOM_BRANDS) if (value === brand) return brand;
|
|
489
|
+
return "bosch";
|
|
490
|
+
}
|
|
491
|
+
function regionOf(value) {
|
|
492
|
+
for (const region of HOMECOM_REGIONS) if (value === region) return region;
|
|
493
|
+
return "eu";
|
|
494
|
+
}
|
|
495
|
+
function choiceOf(gateway) {
|
|
496
|
+
return {
|
|
497
|
+
value: gateway.gatewayId,
|
|
498
|
+
label: gateway.name,
|
|
499
|
+
brand: brandOf(gateway.brand),
|
|
500
|
+
region: regionOf(gateway.region)
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* One gateway is filled in. Several stay a choice. None leaves the id empty
|
|
505
|
+
* for Advanced.
|
|
506
|
+
*/
|
|
507
|
+
function resolveHomeComGateway(gateways) {
|
|
508
|
+
if (gateways.length === 0) return { kind: "none" };
|
|
509
|
+
const options = gateways.map(choiceOf);
|
|
510
|
+
const only = options[0];
|
|
511
|
+
if (gateways.length === 1 && only !== void 0) return {
|
|
512
|
+
kind: "one",
|
|
513
|
+
gatewayId: only.value,
|
|
514
|
+
brand: only.brand,
|
|
515
|
+
region: only.region
|
|
516
|
+
};
|
|
517
|
+
return {
|
|
518
|
+
kind: "many",
|
|
519
|
+
options
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
//#endregion
|
|
58
523
|
//#region src/homecom/homecom-login.ts
|
|
59
524
|
var MESSAGES = {
|
|
60
525
|
empty: "Nothing was pasted. Paste the address the browser tried to open after you signed in.",
|
|
@@ -107,7 +572,7 @@ function parseHomeComCallback(input, expectedState) {
|
|
|
107
572
|
if (!parsed.ok) throw new HomeComLoginError(parsed.reason);
|
|
108
573
|
return parsed.code;
|
|
109
574
|
}
|
|
110
|
-
function isRecord
|
|
575
|
+
function isRecord(value) {
|
|
111
576
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
112
577
|
}
|
|
113
578
|
function emailFromIdToken(idToken) {
|
|
@@ -116,7 +581,7 @@ function emailFromIdToken(idToken) {
|
|
|
116
581
|
if (!payload) return "";
|
|
117
582
|
try {
|
|
118
583
|
const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
119
|
-
return isRecord
|
|
584
|
+
return isRecord(parsed) && typeof parsed["email"] === "string" ? parsed["email"] : "";
|
|
120
585
|
} catch {
|
|
121
586
|
return "";
|
|
122
587
|
}
|
|
@@ -135,189 +600,85 @@ async function completeHomeComSignIn(input, fetchFn = globalThis.fetch.bind(glob
|
|
|
135
600
|
}).toString()
|
|
136
601
|
});
|
|
137
602
|
const body = await res.json().catch(() => null);
|
|
138
|
-
if (!res.ok) throw new HomeComLoginError((isRecord
|
|
139
|
-
if (!isRecord
|
|
603
|
+
if (!res.ok) throw new HomeComLoginError((isRecord(body) ? body["error"] : void 0) === "invalid_grant" ? "code-rejected" : "rejected");
|
|
604
|
+
if (!isRecord(body) || typeof body["access_token"] !== "string" || body["access_token"] === "") throw new HomeComLoginError("rejected");
|
|
140
605
|
const refresh = body["refresh_token"];
|
|
141
606
|
if (typeof refresh !== "string" || refresh === "") throw new HomeComLoginError("no-refresh");
|
|
142
607
|
return {
|
|
143
608
|
token: body["access_token"],
|
|
144
609
|
refresh,
|
|
145
|
-
username: emailFromIdToken(body["id_token"])
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
//#endregion
|
|
149
|
-
//#region src/homecom/
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
function
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
required: false,
|
|
216
|
-
omitFromSettings: true
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
type: "textarea",
|
|
220
|
-
key: "callback",
|
|
221
|
-
label: "Redirect address or code",
|
|
222
|
-
labelKey: "provider-bosch:homecom.s_account.callback.label",
|
|
223
|
-
required: false,
|
|
224
|
-
placeholder: "com.bosch.tt.dashtt.pointt://app/login?code=…&state=…",
|
|
225
|
-
omitFromSettings: true
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
type: "password",
|
|
229
|
-
key: "state",
|
|
230
|
-
label: "Login state",
|
|
231
|
-
labelKey: "provider-bosch:homecom.s_account.state.label",
|
|
232
|
-
required: false,
|
|
233
|
-
omitFromSettings: true
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
type: "password",
|
|
237
|
-
key: "verifier",
|
|
238
|
-
label: "Login verifier",
|
|
239
|
-
labelKey: "provider-bosch:homecom.s_account.verifier.label",
|
|
240
|
-
required: false,
|
|
241
|
-
omitFromSettings: true
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
type: "addon-action-button",
|
|
245
|
-
addonId: "bosch-homecom",
|
|
246
|
-
key: "__homecom-finish",
|
|
247
|
-
label: "Finish",
|
|
248
|
-
labelKey: "provider-bosch:homecom.s_account.__homecom-finish.label",
|
|
249
|
-
buttonLabel: "Complete sign-in",
|
|
250
|
-
action: "finishSignIn",
|
|
251
|
-
variant: "primary",
|
|
252
|
-
paramsFromForm: {
|
|
253
|
-
verifier: "verifier",
|
|
254
|
-
callback: "callback",
|
|
255
|
-
state: "state"
|
|
256
|
-
},
|
|
257
|
-
assignResult: {
|
|
258
|
-
token: "token",
|
|
259
|
-
refresh: "refresh",
|
|
260
|
-
username: "username"
|
|
261
|
-
},
|
|
262
|
-
successMessage: "{summary}"
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
type: "text",
|
|
266
|
-
key: "username",
|
|
267
|
-
label: "Account",
|
|
268
|
-
labelKey: "provider-bosch:homecom.s_account.username.label",
|
|
269
|
-
required: false
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
type: "password",
|
|
273
|
-
key: "token",
|
|
274
|
-
label: "Access token (filled by sign-in, or paste one)",
|
|
275
|
-
labelKey: "provider-bosch:homecom.s_account.token.label",
|
|
276
|
-
required: true,
|
|
277
|
-
showToggle: true
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
type: "password",
|
|
281
|
-
key: "refresh",
|
|
282
|
-
label: "Refresh token (filled by sign-in, or paste one)",
|
|
283
|
-
labelKey: "provider-bosch:homecom.s_account.refresh.label",
|
|
284
|
-
required: true,
|
|
285
|
-
showToggle: true
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
type: "text",
|
|
289
|
-
key: "gatewayId",
|
|
290
|
-
label: "Gateway id",
|
|
291
|
-
labelKey: "provider-bosch:homecom.s_account.gatewayId.label",
|
|
292
|
-
description: "Leave empty to use the first gateway of the account; the chosen one is stored here. For a second gateway add another HomeCom account entry and enter its id.",
|
|
293
|
-
descriptionKey: "provider-bosch:homecom.s_account.gatewayId.description",
|
|
294
|
-
required: false
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
type: "select",
|
|
298
|
-
key: "brand",
|
|
299
|
-
label: "Brand",
|
|
300
|
-
labelKey: "provider-bosch:homecom.s_account.brand.label",
|
|
301
|
-
default: "bosch",
|
|
302
|
-
options: HOMECOM_BRANDS.map((b) => ({
|
|
303
|
-
value: b,
|
|
304
|
-
label: b
|
|
305
|
-
}))
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
type: "select",
|
|
309
|
-
key: "region",
|
|
310
|
-
label: "Region",
|
|
311
|
-
labelKey: "provider-bosch:homecom.s_account.region.label",
|
|
312
|
-
default: "eu",
|
|
313
|
-
options: HOMECOM_REGIONS.map((r) => ({
|
|
314
|
-
value: r,
|
|
315
|
-
label: r.toUpperCase()
|
|
316
|
-
}))
|
|
317
|
-
}
|
|
318
|
-
]
|
|
319
|
-
}] };
|
|
610
|
+
username: emailFromIdToken(body["id_token"])
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
//#endregion
|
|
614
|
+
//#region src/homecom/homecom-sign-in.ts
|
|
615
|
+
/** What the form is allowed to see: the URL and an attempt id. Not the verifier. */
|
|
616
|
+
function publishHomeComSignIn(store, nowMs) {
|
|
617
|
+
const started = beginHomeComSignIn();
|
|
618
|
+
return {
|
|
619
|
+
attemptId: store.put(started.verifier, started.state, nowMs),
|
|
620
|
+
authorizeUrl: started.authorizeUrl
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
async function listGatewaysDefault(tokens, fetchFn) {
|
|
624
|
+
const res = await fetchFn(`${HOMECOM_POINTT_API_ORIGIN}/v2/gateways`, { headers: {
|
|
625
|
+
Authorization: `Bearer ${tokens.token}`,
|
|
626
|
+
Accept: "application/json"
|
|
627
|
+
} });
|
|
628
|
+
if (!res.ok) return [];
|
|
629
|
+
return parseHomeComGatewayList(await res.json());
|
|
630
|
+
}
|
|
631
|
+
async function finishHomeComSignIn(input, store, deps = {}) {
|
|
632
|
+
const taken = store.take(input.attemptId, input.nowMs);
|
|
633
|
+
if (!taken.ok) throw new HomeComLoginError(require_account_integration_offline.loginFailureForAttempt(taken.reason));
|
|
634
|
+
const fetchFn = deps.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
635
|
+
const tokens = await completeHomeComSignIn({
|
|
636
|
+
verifier: taken.attempt.verifier,
|
|
637
|
+
callback: input.callback,
|
|
638
|
+
state: taken.attempt.state
|
|
639
|
+
}, fetchFn);
|
|
640
|
+
let gateways = [];
|
|
641
|
+
let listFailed = false;
|
|
642
|
+
try {
|
|
643
|
+
gateways = await (deps.listGateways ?? ((next) => listGatewaysDefault(next, fetchFn)))(tokens);
|
|
644
|
+
} catch {
|
|
645
|
+
listFailed = true;
|
|
646
|
+
gateways = [];
|
|
647
|
+
}
|
|
648
|
+
return finishedFromGateways(tokens, resolveHomeComGateway(gateways), listFailed);
|
|
649
|
+
}
|
|
650
|
+
function finishedFromGateways(tokens, resolved, listFailed) {
|
|
651
|
+
const who = tokens.username.length > 0 ? `Signed in as ${tokens.username}` : "Signed in";
|
|
652
|
+
const base = {
|
|
653
|
+
...tokens,
|
|
654
|
+
accountLabel: tokens.username
|
|
655
|
+
};
|
|
656
|
+
if (listFailed) return {
|
|
657
|
+
...base,
|
|
658
|
+
gatewayCount: 0,
|
|
659
|
+
summary: `${who}. The gateway list could not be read. Enter the gateway id under Advanced.`
|
|
660
|
+
};
|
|
661
|
+
if (resolved.kind === "one") return {
|
|
662
|
+
...base,
|
|
663
|
+
gatewayCount: 1,
|
|
664
|
+
gatewayId: resolved.gatewayId,
|
|
665
|
+
brand: resolved.brand,
|
|
666
|
+
region: resolved.region,
|
|
667
|
+
summary: `${who}. Gateway ${resolved.gatewayId} selected.`
|
|
668
|
+
};
|
|
669
|
+
if (resolved.kind === "many") return {
|
|
670
|
+
...base,
|
|
671
|
+
gatewayCount: resolved.options.length,
|
|
672
|
+
choices: resolved.options.map((row) => ({ ...row })),
|
|
673
|
+
summary: `${who}. Choose a gateway.`
|
|
674
|
+
};
|
|
675
|
+
return {
|
|
676
|
+
...base,
|
|
677
|
+
gatewayCount: 0,
|
|
678
|
+
summary: `${who}. No gateway was listed. Enter the gateway id under Advanced.`
|
|
679
|
+
};
|
|
320
680
|
}
|
|
681
|
+
var homeComSignInAttempts = require_account_integration_offline.createSignInAttemptStore();
|
|
321
682
|
//#endregion
|
|
322
683
|
//#region src/homecom/homecom-token-persist.ts
|
|
323
684
|
function unreadable(value) {
|
|
@@ -591,245 +952,6 @@ function buildDiscoverables(snapshot) {
|
|
|
591
952
|
}
|
|
592
953
|
var homeComConnections = new HomeComConnectionResolver();
|
|
593
954
|
//#endregion
|
|
594
|
-
//#region src/homecom/homecom-domain-mapping.ts
|
|
595
|
-
var MODE_MAP = {
|
|
596
|
-
Off: "off",
|
|
597
|
-
off: "off",
|
|
598
|
-
Heat: "heat",
|
|
599
|
-
heat: "heat",
|
|
600
|
-
Cool: "cool",
|
|
601
|
-
cool: "cool",
|
|
602
|
-
Auto: "auto",
|
|
603
|
-
auto: "auto"
|
|
604
|
-
};
|
|
605
|
-
function mapHvacMode(raw) {
|
|
606
|
-
return MODE_MAP[raw] ?? "off";
|
|
607
|
-
}
|
|
608
|
-
function climateSliceFromCircuit(circuit, nowMs) {
|
|
609
|
-
return {
|
|
610
|
-
mode: mapHvacMode(circuit.operationMode),
|
|
611
|
-
availableModes: [
|
|
612
|
-
"off",
|
|
613
|
-
"heat",
|
|
614
|
-
"auto"
|
|
615
|
-
],
|
|
616
|
-
fanMode: "",
|
|
617
|
-
availableFanModes: [],
|
|
618
|
-
preset: "",
|
|
619
|
-
availablePresets: [],
|
|
620
|
-
target: circuit.setpoint,
|
|
621
|
-
targetHigh: null,
|
|
622
|
-
targetLow: null,
|
|
623
|
-
targetHumidity: null,
|
|
624
|
-
currentTemp: circuit.currentRoomTemp,
|
|
625
|
-
currentHumidity: null,
|
|
626
|
-
swingVertical: null,
|
|
627
|
-
swingHorizontal: null,
|
|
628
|
-
minTemp: circuit.minTemp,
|
|
629
|
-
maxTemp: circuit.maxTemp,
|
|
630
|
-
tempStep: .5,
|
|
631
|
-
lastFetchedAt: nowMs
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
function waterHeaterSliceFromDhw(dhw, nowMs) {
|
|
635
|
-
return {
|
|
636
|
-
currentTemp: dhw.currentTemp,
|
|
637
|
-
targetTemp: dhw.targetTemp,
|
|
638
|
-
operationMode: dhw.operationMode,
|
|
639
|
-
availableModes: [...dhw.availableModes],
|
|
640
|
-
away: null,
|
|
641
|
-
minTemp: dhw.minTemp,
|
|
642
|
-
maxTemp: dhw.maxTemp,
|
|
643
|
-
lastChangedAt: nowMs
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
/** Parse K40-style gateway JSON into heating circuits + DHW. Pure. */
|
|
647
|
-
function parseHomeComSystemPayload(payload) {
|
|
648
|
-
if (typeof payload !== "object" || payload === null) return {
|
|
649
|
-
circuits: [],
|
|
650
|
-
dhw: null
|
|
651
|
-
};
|
|
652
|
-
const root = payload;
|
|
653
|
-
const circuits = [];
|
|
654
|
-
const rawCircuits = root["heatingCircuits"];
|
|
655
|
-
if (Array.isArray(rawCircuits)) for (const item of rawCircuits) {
|
|
656
|
-
if (typeof item !== "object" || item === null) continue;
|
|
657
|
-
const row = item;
|
|
658
|
-
const id = row["id"];
|
|
659
|
-
if (typeof id !== "string") continue;
|
|
660
|
-
circuits.push({
|
|
661
|
-
id,
|
|
662
|
-
name: typeof row["name"] === "string" ? row["name"] : id,
|
|
663
|
-
operationMode: typeof row["operationMode"] === "string" ? row["operationMode"] : "off",
|
|
664
|
-
setpoint: readNum(row["setpoint"]),
|
|
665
|
-
currentRoomTemp: readNum(row["currentRoomTemperature"]),
|
|
666
|
-
minTemp: readNum(row["minTemp"]),
|
|
667
|
-
maxTemp: readNum(row["maxTemp"])
|
|
668
|
-
});
|
|
669
|
-
}
|
|
670
|
-
let dhw = null;
|
|
671
|
-
const rawDhw = root["domesticHotWater"];
|
|
672
|
-
if (typeof rawDhw === "object" && rawDhw !== null) {
|
|
673
|
-
const row = rawDhw;
|
|
674
|
-
const modes = [];
|
|
675
|
-
if (Array.isArray(row["operationModes"])) {
|
|
676
|
-
for (const m of row["operationModes"]) if (typeof m === "string") modes.push(m);
|
|
677
|
-
}
|
|
678
|
-
dhw = {
|
|
679
|
-
currentTemp: readNum(row["currentTemperature"]),
|
|
680
|
-
targetTemp: readNum(row["setpoint"]),
|
|
681
|
-
operationMode: typeof row["operationMode"] === "string" ? row["operationMode"] : null,
|
|
682
|
-
availableModes: modes.length > 0 ? modes : [
|
|
683
|
-
"off",
|
|
684
|
-
"eco",
|
|
685
|
-
"performance"
|
|
686
|
-
],
|
|
687
|
-
minTemp: readNum(row["minTemp"]),
|
|
688
|
-
maxTemp: readNum(row["maxTemp"])
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
return {
|
|
692
|
-
circuits,
|
|
693
|
-
dhw
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
function readNum(raw) {
|
|
697
|
-
if (typeof raw === "number" && Number.isFinite(raw)) return raw;
|
|
698
|
-
return null;
|
|
699
|
-
}
|
|
700
|
-
//#endregion
|
|
701
|
-
//#region src/homecom/homecom-api-client.ts
|
|
702
|
-
var HomeComAuthError = class extends Error {
|
|
703
|
-
name = "HomeComAuthError";
|
|
704
|
-
};
|
|
705
|
-
function isRecord(value) {
|
|
706
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
707
|
-
}
|
|
708
|
-
function rowId(row) {
|
|
709
|
-
for (const key of [
|
|
710
|
-
"deviceId",
|
|
711
|
-
"gatewayId",
|
|
712
|
-
"id"
|
|
713
|
-
]) {
|
|
714
|
-
const value = row[key];
|
|
715
|
-
if (typeof value === "string" && value.length > 0) return value;
|
|
716
|
-
if (typeof value === "number") return String(value);
|
|
717
|
-
}
|
|
718
|
-
return null;
|
|
719
|
-
}
|
|
720
|
-
/** Gateways from the account listing: a bare array or `{ gateways: [...] }`. */
|
|
721
|
-
function parseHomeComGatewayList(payload) {
|
|
722
|
-
const rows = isRecord(payload) ? payload["gateways"] : payload;
|
|
723
|
-
if (!Array.isArray(rows)) return [];
|
|
724
|
-
const out = [];
|
|
725
|
-
for (const row of rows) {
|
|
726
|
-
if (!isRecord(row)) continue;
|
|
727
|
-
const gatewayId = rowId(row);
|
|
728
|
-
if (gatewayId === null) continue;
|
|
729
|
-
const name = row["name"];
|
|
730
|
-
out.push({
|
|
731
|
-
gatewayId,
|
|
732
|
-
name: typeof name === "string" && name.length > 0 ? name : gatewayId
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
return out;
|
|
736
|
-
}
|
|
737
|
-
function createHomeComApiClient(config, gatewayId, deps = {}) {
|
|
738
|
-
const fetchFn = deps.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
739
|
-
let token = config.token;
|
|
740
|
-
let refresh = config.refresh;
|
|
741
|
-
const base = homeComApiBase(config.region);
|
|
742
|
-
async function authHeaders() {
|
|
743
|
-
return {
|
|
744
|
-
Authorization: `Bearer ${token}`,
|
|
745
|
-
Accept: "application/json",
|
|
746
|
-
"Content-Type": "application/json"
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
async function request(path, init = {}) {
|
|
750
|
-
const res = await fetchFn(`${base}${path}`, {
|
|
751
|
-
method: init.method ?? "GET",
|
|
752
|
-
headers: await authHeaders(),
|
|
753
|
-
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
754
|
-
});
|
|
755
|
-
if (res.status === 401) throw new HomeComAuthError("token expired");
|
|
756
|
-
if (!res.ok) throw new Error(`HomeCom API ${path} failed: HTTP ${res.status}`);
|
|
757
|
-
return res.json();
|
|
758
|
-
}
|
|
759
|
-
return {
|
|
760
|
-
async refreshTokens() {
|
|
761
|
-
const res = await fetchFn(HOMECOM_TOKEN_URL, {
|
|
762
|
-
method: "POST",
|
|
763
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
764
|
-
body: new URLSearchParams({
|
|
765
|
-
grant_type: "refresh_token",
|
|
766
|
-
refresh_token: refresh,
|
|
767
|
-
client_id: HOMECOM_CLIENT_ID
|
|
768
|
-
}).toString()
|
|
769
|
-
});
|
|
770
|
-
if (!res.ok) {
|
|
771
|
-
if (res.status === 401 || res.status === 403) throw new HomeComAuthError("refresh refused");
|
|
772
|
-
throw new Error(`HomeCom token refresh failed: HTTP ${res.status}`);
|
|
773
|
-
}
|
|
774
|
-
const body = await res.json();
|
|
775
|
-
if (typeof body !== "object" || body === null) throw new Error("HomeCom token refresh: bad body");
|
|
776
|
-
const row = body;
|
|
777
|
-
const newToken = row["access_token"];
|
|
778
|
-
const newRefresh = row["refresh_token"];
|
|
779
|
-
if (typeof newToken !== "string" || typeof newRefresh !== "string") throw new Error("HomeCom token refresh: missing tokens");
|
|
780
|
-
token = newToken;
|
|
781
|
-
refresh = newRefresh;
|
|
782
|
-
await deps.onTokensRotated?.({
|
|
783
|
-
token,
|
|
784
|
-
refresh
|
|
785
|
-
});
|
|
786
|
-
return {
|
|
787
|
-
token,
|
|
788
|
-
refresh
|
|
789
|
-
};
|
|
790
|
-
},
|
|
791
|
-
async listGateways() {
|
|
792
|
-
return parseHomeComGatewayList(await request("/v2/gateways"));
|
|
793
|
-
},
|
|
794
|
-
async fetchSystem() {
|
|
795
|
-
const parsed = parseHomeComSystemPayload(await request(`/v2/gateways/${gatewayId}/system`));
|
|
796
|
-
return {
|
|
797
|
-
gateway: {
|
|
798
|
-
gatewayId,
|
|
799
|
-
name: "HomeCom heat pump",
|
|
800
|
-
model: "K40"
|
|
801
|
-
},
|
|
802
|
-
circuits: parsed.circuits,
|
|
803
|
-
dhw: parsed.dhw
|
|
804
|
-
};
|
|
805
|
-
},
|
|
806
|
-
async setCircuitSetpoint(circuitId, temp) {
|
|
807
|
-
await request(`/v2/gateways/${gatewayId}/heatingCircuits/${circuitId}/setpoint`, {
|
|
808
|
-
method: "PUT",
|
|
809
|
-
body: { value: temp }
|
|
810
|
-
});
|
|
811
|
-
},
|
|
812
|
-
async setCircuitMode(circuitId, mode) {
|
|
813
|
-
await request(`/v2/gateways/${gatewayId}/heatingCircuits/${circuitId}/operationMode`, {
|
|
814
|
-
method: "PUT",
|
|
815
|
-
body: { value: mode }
|
|
816
|
-
});
|
|
817
|
-
},
|
|
818
|
-
async setDhwTarget(temp) {
|
|
819
|
-
await request(`/v2/gateways/${gatewayId}/domesticHotWater/setpoint`, {
|
|
820
|
-
method: "PUT",
|
|
821
|
-
body: { value: temp }
|
|
822
|
-
});
|
|
823
|
-
},
|
|
824
|
-
async setDhwMode(mode) {
|
|
825
|
-
await request(`/v2/gateways/${gatewayId}/domesticHotWater/operationMode`, {
|
|
826
|
-
method: "PUT",
|
|
827
|
-
body: { value: mode }
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
};
|
|
831
|
-
}
|
|
832
|
-
//#endregion
|
|
833
955
|
//#region src/homecom/homecom-integration-manager.ts
|
|
834
956
|
var HomeComIntegrationManager = class {
|
|
835
957
|
#id;
|
|
@@ -1390,16 +1512,18 @@ var BoschHomeComProviderAddon = class extends require_dist.BaseDeviceProvider {
|
|
|
1390
1512
|
customActions: homeComActions,
|
|
1391
1513
|
actionHandlers: {
|
|
1392
1514
|
beginSignIn: async () => ({
|
|
1393
|
-
...
|
|
1515
|
+
...publishHomeComSignIn(homeComSignInAttempts, Date.now()),
|
|
1394
1516
|
summary: "Open the link in a browser and sign in."
|
|
1395
1517
|
}),
|
|
1396
|
-
finishSignIn: async (input) => {
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1518
|
+
finishSignIn: async (input) => finishHomeComSignIn({
|
|
1519
|
+
attemptId: input.attemptId,
|
|
1520
|
+
callback: input.callback,
|
|
1521
|
+
nowMs: Date.now()
|
|
1522
|
+
}, homeComSignInAttempts),
|
|
1523
|
+
signOut: async () => ({
|
|
1524
|
+
signedOut: true,
|
|
1525
|
+
summary: "Signed out."
|
|
1526
|
+
})
|
|
1403
1527
|
}
|
|
1404
1528
|
};
|
|
1405
1529
|
}
|