iobroker.homewizard 0.6.1 → 0.6.3
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/README.md +21 -9
- package/build/lib/coerce.js +59 -0
- package/build/lib/coerce.js.map +7 -0
- package/build/lib/homewizard-client.js +1 -0
- package/build/lib/homewizard-client.js.map +2 -2
- package/build/lib/state-manager.js +193 -138
- package/build/lib/state-manager.js.map +2 -2
- package/build/lib/websocket-client.js +26 -10
- package/build/lib/websocket-client.js.map +2 -2
- package/build/main.js +45 -31
- package/build/main.js.map +2 -2
- package/io-package.json +27 -27
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -45,8 +45,6 @@ Real-time energy monitoring from [HomeWizard](https://www.homewizard.com) Energy
|
|
|
45
45
|
| kWh Meter 3-Phase | HWE-KWH3 / SDM630 | Yes | Yes (as controller) |
|
|
46
46
|
| Plug-In Battery | HWE-BAT | Yes | Controlled via P1/kWh |
|
|
47
47
|
|
|
48
|
-
> **Note:** Energy Socket (HWE-SKT) and Watermeter (HWE-WTR) only support API v1 and are not yet supported. Support will be added when HomeWizard releases API v2 for these devices.
|
|
49
|
-
|
|
50
48
|
---
|
|
51
49
|
|
|
52
50
|
## Configuration
|
|
@@ -100,7 +98,9 @@ homewizard.0.
|
|
|
100
98
|
├── measurement/ — Measurement data
|
|
101
99
|
│ ├── power_w — Total power (number, W)
|
|
102
100
|
│ ├── power_l1_w .. l3_w — Power per phase (number, W)
|
|
101
|
+
│ ├── voltage_v — Voltage single-phase (number, V)
|
|
103
102
|
│ ├── voltage_l1_v .. l3_v — Voltage per phase (number, V)
|
|
103
|
+
│ ├── current_a — Current single-phase (number, A)
|
|
104
104
|
│ ├── current_l1_a .. l3_a — Current per phase (number, A)
|
|
105
105
|
│ ├── frequency_hz — Grid frequency (number, Hz)
|
|
106
106
|
│ ├── energy_import_kwh — Total import (number, kWh)
|
|
@@ -108,6 +108,13 @@ homewizard.0.
|
|
|
108
108
|
│ ├── energy_export_kwh — Total export (number, kWh)
|
|
109
109
|
│ ├── energy_export_t1..t4_kwh — Export per tariff (number, kWh)
|
|
110
110
|
│ ├── tariff — Active tariff (number)
|
|
111
|
+
│ ├── state_of_charge_pct — Battery charge level (number, %)
|
|
112
|
+
│ ├── cycles — Battery charge cycles (number)
|
|
113
|
+
│ ├── average_power_15m_w — 15-min average power (number, W, Belgium)
|
|
114
|
+
│ ├── monthly_power_peak_w — Monthly power peak (number, W, Belgium)
|
|
115
|
+
│ ├── monthly_power_peak_timestamp — Monthly peak timestamp (string)
|
|
116
|
+
│ ├── meter_model — Meter model identifier (string)
|
|
117
|
+
│ ├── timestamp — Measurement timestamp (string)
|
|
111
118
|
│ ├── quality/ — Power quality counters
|
|
112
119
|
│ │ ├── voltage_sag_l1..l3_count
|
|
113
120
|
│ │ ├── voltage_swell_l1..l3_count
|
|
@@ -135,7 +142,7 @@ homewizard.0.
|
|
|
135
142
|
└── identify — Blink LED (button)
|
|
136
143
|
```
|
|
137
144
|
|
|
138
|
-
> States are created dynamically based on what the device reports. Not all devices have all states.
|
|
145
|
+
> States are created dynamically based on what the device reports. Not all devices have all states. kWh meters additionally provide apparent/reactive current, apparent/reactive power, and power factor states.
|
|
139
146
|
|
|
140
147
|
---
|
|
141
148
|
|
|
@@ -158,6 +165,17 @@ homewizard.0.
|
|
|
158
165
|
|
|
159
166
|
## Changelog
|
|
160
167
|
|
|
168
|
+
### 0.6.3 (2026-04-18)
|
|
169
|
+
- Harden WebSocket and REST input handling against unexpected API responses
|
|
170
|
+
- Stop endless reconnect when the device token is invalid (fires once after 3 failed auth attempts)
|
|
171
|
+
- Avoid creating an empty `external/` channel when a device reports no external meters
|
|
172
|
+
|
|
173
|
+
### 0.6.2 (2026-04-13)
|
|
174
|
+
- Fix hanging promise when response stream errors mid-transfer (`res.on("error")`)
|
|
175
|
+
- Fix onUnload: wrap in try/finally so callback always fires (prevents adapter hang on shutdown)
|
|
176
|
+
- Optimize state creation hot path: use `setObjectNotExistsAsync` instead of `extendObjectAsync` (~50 fewer object writes per second per device)
|
|
177
|
+
- Remove unnecessary `removeDeviceFromObject` wrapper (DRY)
|
|
178
|
+
|
|
161
179
|
### 0.6.1 (2026-04-12)
|
|
162
180
|
- Code cleanup: extract testable connection-utils module (classifyError, createDeviceConnection)
|
|
163
181
|
- Add 20 unit tests for error classification, connection factory, and unstable threshold
|
|
@@ -180,12 +198,6 @@ homewizard.0.
|
|
|
180
198
|
### 0.4.2 (2026-04-05)
|
|
181
199
|
- Consistent donation labels and about text across all adapters
|
|
182
200
|
|
|
183
|
-
### 0.4.1 (2026-04-05)
|
|
184
|
-
- Move measurement data into `measurement/` channel for cleaner object tree
|
|
185
|
-
|
|
186
|
-
### 0.4.0 (2026-04-05)
|
|
187
|
-
- Add online/offline status icon for devices (`statusStates`)
|
|
188
|
-
|
|
189
201
|
Older entries have been moved to [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
190
202
|
|
|
191
203
|
---
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var coerce_exports = {};
|
|
20
|
+
__export(coerce_exports, {
|
|
21
|
+
coerceBoolean: () => coerceBoolean,
|
|
22
|
+
coerceFiniteNumber: () => coerceFiniteNumber,
|
|
23
|
+
coerceString: () => coerceString,
|
|
24
|
+
isPlainObject: () => isPlainObject
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(coerce_exports);
|
|
27
|
+
function coerceFiniteNumber(value) {
|
|
28
|
+
if (typeof value === "number") {
|
|
29
|
+
return Number.isFinite(value) ? value : null;
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === "string" && value.length > 0) {
|
|
32
|
+
const n = Number(value);
|
|
33
|
+
return Number.isFinite(n) ? n : null;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function coerceString(value) {
|
|
38
|
+
if (typeof value === "string" && value.length > 0) {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function coerceBoolean(value) {
|
|
44
|
+
if (typeof value === "boolean") {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function isPlainObject(value) {
|
|
50
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
53
|
+
0 && (module.exports = {
|
|
54
|
+
coerceBoolean,
|
|
55
|
+
coerceFiniteNumber,
|
|
56
|
+
coerceString,
|
|
57
|
+
isPlainObject
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=coerce.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/coerce.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Boundary coercion helpers for external API data (REST + WebSocket).\n * HomeWizard API v2 is well-documented but field types still drift in practice\n * (firmware bugs, future additions, null values). These helpers guard against\n * NaN/Infinity/non-string values reaching ioBroker states.\n */\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses numeric strings; rejects NaN/Infinity/other.\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && value.length > 0) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce to a non-empty string, or null.\n *\n * @param value Unknown external value\n */\nexport function coerceString(value: unknown): string | null {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n return null;\n}\n\n/**\n * Coerce to a boolean (only `true`/`false` accepted \u2014 no truthy/falsy JS rules).\n *\n * @param value Unknown external value\n */\nexport function coerceBoolean(value: unknown): boolean | null {\n if (typeof value === \"boolean\") {\n return value;\n }\n return null;\n}\n\n/**\n * Guard for plain objects (not arrays, not null).\n *\n * @param value Unknown external value\n */\nexport function isPlainObject(\n value: unknown,\n): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaO,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAOO,SAAS,aAAa,OAA+B;AAC1D,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cAAc,OAAgC;AAC5D,MAAI,OAAO,UAAU,WAAW;AAC9B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,cACd,OACkC;AAClC,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/homewizard-client.ts"],
|
|
4
|
-
"sourcesContent": ["import * as https from \"node:https\";\nimport { HW_AGENT } from \"./cacert\";\nimport type {\n BatteryControl,\n DeviceInfo,\n Measurement,\n PairingResponse,\n SystemInfo,\n} from \"./types\";\n\n/** HTTPS client for HomeWizard API v2 */\nexport class HomeWizardClient {\n private readonly ip: string;\n private readonly token: string;\n\n /**\n * @param ip Device IP address\n * @param token Bearer token (empty string for pairing requests)\n */\n constructor(ip: string, token: string = \"\") {\n this.ip = ip;\n this.token = token;\n }\n\n /** Get device info (GET /api) */\n async getDeviceInfo(): Promise<DeviceInfo> {\n return this.request<DeviceInfo>(\"GET\", \"/api\");\n }\n\n /** Request pairing token (POST /api/user) \u2014 403 until button pressed */\n async requestPairing(): Promise<PairingResponse> {\n return this.request<PairingResponse>(\"POST\", \"/api/user\", {\n name: \"local/iobroker\",\n });\n }\n\n /** Get current measurement (REST fallback) */\n async getMeasurement(): Promise<Measurement> {\n return this.request<Measurement>(\"GET\", \"/api/measurement\");\n }\n\n /** Get system info */\n async getSystem(): Promise<SystemInfo> {\n return this.request<SystemInfo>(\"GET\", \"/api/system\");\n }\n\n /**\n * Update system settings\n *\n * @param settings System settings to update\n */\n async setSystem(settings: Partial<SystemInfo>): Promise<SystemInfo> {\n return this.request<SystemInfo>(\"PUT\", \"/api/system\", settings);\n }\n\n /** Reboot device */\n async reboot(): Promise<void> {\n await this.request(\"PUT\", \"/api/system/reboot\");\n }\n\n /** Identify device (blink LED) */\n async identify(): Promise<void> {\n await this.request(\"PUT\", \"/api/system/identify\");\n }\n\n /** Get battery control status */\n async getBatteries(): Promise<BatteryControl> {\n return this.request<BatteryControl>(\"GET\", \"/api/batteries\");\n }\n\n /**\n * Set battery control\n *\n * @param settings Battery control settings to update\n */\n async setBatteries(\n settings: Partial<BatteryControl>,\n ): Promise<BatteryControl> {\n return this.request<BatteryControl>(\"PUT\", \"/api/batteries\", settings);\n }\n\n /**\n * @param method HTTP method\n * @param path API path\n * @param body Optional request body\n */\n private request<T>(method: string, path: string, body?: unknown): Promise<T> {\n return new Promise((resolve, reject) => {\n const bodyStr = body ? JSON.stringify(body) : undefined;\n const headers: Record<string, string> = {\n \"X-Api-Version\": \"2\",\n };\n\n if (this.token) {\n headers.Authorization = `Bearer ${this.token}`;\n }\n if (bodyStr) {\n headers[\"Content-Type\"] = \"application/json\";\n headers[\"Content-Length\"] = Buffer.byteLength(bodyStr).toString();\n }\n\n const req = https.request(\n {\n hostname: this.ip,\n port: 443,\n path,\n method,\n headers,\n agent: HW_AGENT,\n timeout: 10_000,\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n res.on(\"end\", () => {\n const data = Buffer.concat(chunks).toString();\n if (!res.statusCode || res.statusCode >= 400) {\n const error = new HomeWizardApiError(\n res.statusCode ?? 0,\n data,\n `${method} ${path}`,\n );\n reject(error);\n return;\n }\n if (!data) {\n resolve(undefined as T);\n return;\n }\n try {\n resolve(JSON.parse(data) as T);\n } catch {\n reject(\n new Error(\n `Invalid JSON from ${method} ${path}: ${data.substring(0, 200)}`,\n ),\n );\n }\n });\n },\n );\n\n req.on(\"error\", reject);\n req.on(\"timeout\", () => {\n req.destroy(new Error(`Timeout: ${method} ${path}`));\n });\n\n if (bodyStr) {\n req.write(bodyStr);\n }\n req.end();\n });\n }\n}\n\n/** API error with status code and parsed error body */\nexport class HomeWizardApiError extends Error {\n readonly statusCode: number;\n readonly errorCode: string;\n\n /**\n * @param statusCode HTTP status code\n * @param body Response body\n * @param context Request context for error message\n */\n constructor(statusCode: number, body: string, context: string) {\n let errorCode = \"unknown\";\n let description = body;\n try {\n const parsed = JSON.parse(body);\n errorCode = parsed.error?.code ?? parsed.error ?? \"unknown\";\n description = parsed.error?.description ?? parsed.error?.code ?? body;\n } catch {\n // body is not JSON\n }\n super(`${context}: HTTP ${statusCode} \u2014 ${description}`);\n this.name = \"HomeWizardApiError\";\n this.statusCode = statusCode;\n this.errorCode = errorCode;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAAuB;AACvB,oBAAyB;AAUlB,MAAM,iBAAiB;AAAA,EACX;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjB,YAAY,IAAY,QAAgB,IAAI;AAC1C,SAAK,KAAK;AACV,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAGA,MAAM,gBAAqC;AACzC,WAAO,KAAK,QAAoB,OAAO,MAAM;AAAA,EAC/C;AAAA;AAAA,EAGA,MAAM,iBAA2C;AAC/C,WAAO,KAAK,QAAyB,QAAQ,aAAa;AAAA,MACxD,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,iBAAuC;AAC3C,WAAO,KAAK,QAAqB,OAAO,kBAAkB;AAAA,EAC5D;AAAA;AAAA,EAGA,MAAM,YAAiC;AACrC,WAAO,KAAK,QAAoB,OAAO,aAAa;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAU,UAAoD;AAClE,WAAO,KAAK,QAAoB,OAAO,eAAe,QAAQ;AAAA,EAChE;AAAA;AAAA,EAGA,MAAM,SAAwB;AAC5B,UAAM,KAAK,QAAQ,OAAO,oBAAoB;AAAA,EAChD;AAAA;AAAA,EAGA,MAAM,WAA0B;AAC9B,UAAM,KAAK,QAAQ,OAAO,sBAAsB;AAAA,EAClD;AAAA;AAAA,EAGA,MAAM,eAAwC;AAC5C,WAAO,KAAK,QAAwB,OAAO,gBAAgB;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aACJ,UACyB;AACzB,WAAO,KAAK,QAAwB,OAAO,kBAAkB,QAAQ;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,QAAW,QAAgB,MAAc,MAA4B;AAC3E,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,UAAU,OAAO,KAAK,UAAU,IAAI,IAAI;AAC9C,YAAM,UAAkC;AAAA,QACtC,iBAAiB;AAAA,MACnB;AAEA,UAAI,KAAK,OAAO;AACd,gBAAQ,gBAAgB,UAAU,KAAK,KAAK;AAAA,MAC9C;AACA,UAAI,SAAS;AACX,gBAAQ,cAAc,IAAI;AAC1B,gBAAQ,gBAAgB,IAAI,OAAO,WAAW,OAAO,EAAE,SAAS;AAAA,MAClE;AAEA,YAAM,MAAM,MAAM;AAAA,QAChB;AAAA,UACE,UAAU,KAAK;AAAA,UACf,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA;AAAA,UACA,OAAO;AAAA,UACP,SAAS;AAAA,QACX;AAAA,QACA,CAAC,QAAQ;AACP,gBAAM,SAAmB,CAAC;AAC1B,cAAI,GAAG,QAAQ,CAAC,UAAkB,OAAO,KAAK,KAAK,CAAC;AACpD,cAAI,GAAG,OAAO,MAAM;
|
|
4
|
+
"sourcesContent": ["import * as https from \"node:https\";\nimport { HW_AGENT } from \"./cacert\";\nimport type {\n BatteryControl,\n DeviceInfo,\n Measurement,\n PairingResponse,\n SystemInfo,\n} from \"./types\";\n\n/** HTTPS client for HomeWizard API v2 */\nexport class HomeWizardClient {\n private readonly ip: string;\n private readonly token: string;\n\n /**\n * @param ip Device IP address\n * @param token Bearer token (empty string for pairing requests)\n */\n constructor(ip: string, token: string = \"\") {\n this.ip = ip;\n this.token = token;\n }\n\n /** Get device info (GET /api) */\n async getDeviceInfo(): Promise<DeviceInfo> {\n return this.request<DeviceInfo>(\"GET\", \"/api\");\n }\n\n /** Request pairing token (POST /api/user) \u2014 403 until button pressed */\n async requestPairing(): Promise<PairingResponse> {\n return this.request<PairingResponse>(\"POST\", \"/api/user\", {\n name: \"local/iobroker\",\n });\n }\n\n /** Get current measurement (REST fallback) */\n async getMeasurement(): Promise<Measurement> {\n return this.request<Measurement>(\"GET\", \"/api/measurement\");\n }\n\n /** Get system info */\n async getSystem(): Promise<SystemInfo> {\n return this.request<SystemInfo>(\"GET\", \"/api/system\");\n }\n\n /**\n * Update system settings\n *\n * @param settings System settings to update\n */\n async setSystem(settings: Partial<SystemInfo>): Promise<SystemInfo> {\n return this.request<SystemInfo>(\"PUT\", \"/api/system\", settings);\n }\n\n /** Reboot device */\n async reboot(): Promise<void> {\n await this.request(\"PUT\", \"/api/system/reboot\");\n }\n\n /** Identify device (blink LED) */\n async identify(): Promise<void> {\n await this.request(\"PUT\", \"/api/system/identify\");\n }\n\n /** Get battery control status */\n async getBatteries(): Promise<BatteryControl> {\n return this.request<BatteryControl>(\"GET\", \"/api/batteries\");\n }\n\n /**\n * Set battery control\n *\n * @param settings Battery control settings to update\n */\n async setBatteries(\n settings: Partial<BatteryControl>,\n ): Promise<BatteryControl> {\n return this.request<BatteryControl>(\"PUT\", \"/api/batteries\", settings);\n }\n\n /**\n * @param method HTTP method\n * @param path API path\n * @param body Optional request body\n */\n private request<T>(method: string, path: string, body?: unknown): Promise<T> {\n return new Promise((resolve, reject) => {\n const bodyStr = body ? JSON.stringify(body) : undefined;\n const headers: Record<string, string> = {\n \"X-Api-Version\": \"2\",\n };\n\n if (this.token) {\n headers.Authorization = `Bearer ${this.token}`;\n }\n if (bodyStr) {\n headers[\"Content-Type\"] = \"application/json\";\n headers[\"Content-Length\"] = Buffer.byteLength(bodyStr).toString();\n }\n\n const req = https.request(\n {\n hostname: this.ip,\n port: 443,\n path,\n method,\n headers,\n agent: HW_AGENT,\n timeout: 10_000,\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on(\"error\", reject);\n res.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n res.on(\"end\", () => {\n const data = Buffer.concat(chunks).toString();\n if (!res.statusCode || res.statusCode >= 400) {\n const error = new HomeWizardApiError(\n res.statusCode ?? 0,\n data,\n `${method} ${path}`,\n );\n reject(error);\n return;\n }\n if (!data) {\n resolve(undefined as T);\n return;\n }\n try {\n resolve(JSON.parse(data) as T);\n } catch {\n reject(\n new Error(\n `Invalid JSON from ${method} ${path}: ${data.substring(0, 200)}`,\n ),\n );\n }\n });\n },\n );\n\n req.on(\"error\", reject);\n req.on(\"timeout\", () => {\n req.destroy(new Error(`Timeout: ${method} ${path}`));\n });\n\n if (bodyStr) {\n req.write(bodyStr);\n }\n req.end();\n });\n }\n}\n\n/** API error with status code and parsed error body */\nexport class HomeWizardApiError extends Error {\n readonly statusCode: number;\n readonly errorCode: string;\n\n /**\n * @param statusCode HTTP status code\n * @param body Response body\n * @param context Request context for error message\n */\n constructor(statusCode: number, body: string, context: string) {\n let errorCode = \"unknown\";\n let description = body;\n try {\n const parsed = JSON.parse(body);\n errorCode = parsed.error?.code ?? parsed.error ?? \"unknown\";\n description = parsed.error?.description ?? parsed.error?.code ?? body;\n } catch {\n // body is not JSON\n }\n super(`${context}: HTTP ${statusCode} \u2014 ${description}`);\n this.name = \"HomeWizardApiError\";\n this.statusCode = statusCode;\n this.errorCode = errorCode;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -21,6 +21,7 @@ __export(state_manager_exports, {
|
|
|
21
21
|
StateManager: () => StateManager
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(state_manager_exports);
|
|
24
|
+
var import_coerce = require("./coerce");
|
|
24
25
|
function sanitize(str) {
|
|
25
26
|
return str.replace(/[^a-zA-Z0-9_-]/g, "_").toLowerCase();
|
|
26
27
|
}
|
|
@@ -582,63 +583,93 @@ class StateManager {
|
|
|
582
583
|
* @param data Measurement data
|
|
583
584
|
*/
|
|
584
585
|
async updateMeasurement(config, data) {
|
|
585
|
-
|
|
586
|
+
if (!(0, import_coerce.isPlainObject)(data)) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
586
589
|
const prefix = this.devicePrefix(config);
|
|
587
590
|
const mPrefix = `${prefix}.measurement`;
|
|
588
|
-
await this.adapter.
|
|
591
|
+
await this.adapter.setObjectNotExistsAsync(mPrefix, {
|
|
589
592
|
type: "channel",
|
|
590
593
|
common: { name: "Measurement" },
|
|
591
594
|
native: {}
|
|
592
595
|
});
|
|
593
|
-
const
|
|
594
|
-
for (const def of
|
|
595
|
-
const
|
|
596
|
-
|
|
596
|
+
const record = data;
|
|
597
|
+
for (const def of MEASUREMENT_STATE_DEFS) {
|
|
598
|
+
const raw = record[def.key];
|
|
599
|
+
let coerced = null;
|
|
600
|
+
if (def.type === "number") {
|
|
601
|
+
coerced = (0, import_coerce.coerceFiniteNumber)(raw);
|
|
602
|
+
} else if (def.type === "string") {
|
|
603
|
+
coerced = (0, import_coerce.coerceString)(raw);
|
|
604
|
+
}
|
|
605
|
+
if (coerced !== null) {
|
|
597
606
|
await this.ensureAndSet(
|
|
598
607
|
`${mPrefix}.${def.id}`,
|
|
599
608
|
def.name,
|
|
600
609
|
def.type,
|
|
601
610
|
def.role,
|
|
602
|
-
|
|
611
|
+
coerced,
|
|
603
612
|
def.unit
|
|
604
613
|
);
|
|
605
614
|
}
|
|
606
615
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
+
const external = record.external;
|
|
617
|
+
if (Array.isArray(external) && external.length > 0) {
|
|
618
|
+
let extChannelEnsured = false;
|
|
619
|
+
for (const rawExt of external) {
|
|
620
|
+
if (!(0, import_coerce.isPlainObject)(rawExt)) {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
const type = (0, import_coerce.coerceString)(rawExt.type);
|
|
624
|
+
const uniqueId = (0, import_coerce.coerceString)(rawExt.unique_id);
|
|
625
|
+
if (!type || !uniqueId) {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
const value = (0, import_coerce.coerceFiniteNumber)(rawExt.value);
|
|
629
|
+
const unit = (0, import_coerce.coerceString)(rawExt.unit);
|
|
630
|
+
const timestamp = (0, import_coerce.coerceString)(rawExt.timestamp);
|
|
631
|
+
if (!extChannelEnsured) {
|
|
632
|
+
await this.adapter.setObjectNotExistsAsync(`${mPrefix}.external`, {
|
|
633
|
+
type: "channel",
|
|
634
|
+
common: { name: "External Meters" },
|
|
635
|
+
native: {}
|
|
636
|
+
});
|
|
637
|
+
extChannelEnsured = true;
|
|
638
|
+
}
|
|
639
|
+
const extId = `${mPrefix}.external.${sanitize(type)}_${sanitize(uniqueId)}`;
|
|
640
|
+
await this.adapter.setObjectNotExistsAsync(extId, {
|
|
616
641
|
type: "channel",
|
|
617
|
-
common: { name:
|
|
642
|
+
common: { name: type },
|
|
618
643
|
native: {}
|
|
619
644
|
});
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
645
|
+
if (value !== null) {
|
|
646
|
+
await this.ensureAndSet(
|
|
647
|
+
`${extId}.value`,
|
|
648
|
+
"Value",
|
|
649
|
+
"number",
|
|
650
|
+
"value",
|
|
651
|
+
value,
|
|
652
|
+
unit != null ? unit : void 0
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
if (unit) {
|
|
656
|
+
await this.ensureAndSet(
|
|
657
|
+
`${extId}.unit`,
|
|
658
|
+
"Unit",
|
|
659
|
+
"string",
|
|
660
|
+
"text",
|
|
661
|
+
unit
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
if (timestamp) {
|
|
665
|
+
await this.ensureAndSet(
|
|
666
|
+
`${extId}.timestamp`,
|
|
667
|
+
"Timestamp",
|
|
668
|
+
"string",
|
|
669
|
+
"date",
|
|
670
|
+
timestamp
|
|
671
|
+
);
|
|
672
|
+
}
|
|
642
673
|
}
|
|
643
674
|
}
|
|
644
675
|
}
|
|
@@ -649,53 +680,70 @@ class StateManager {
|
|
|
649
680
|
* @param system System info data
|
|
650
681
|
*/
|
|
651
682
|
async updateSystem(config, system) {
|
|
683
|
+
if (!(0, import_coerce.isPlainObject)(system)) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
652
686
|
const prefix = this.devicePrefix(config);
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
687
|
+
const record = system;
|
|
688
|
+
const rssi = (0, import_coerce.coerceFiniteNumber)(record.wifi_rssi_db);
|
|
689
|
+
if (rssi !== null) {
|
|
690
|
+
await this.ensureAndSet(
|
|
691
|
+
`${prefix}.info.wifi_rssi_db`,
|
|
692
|
+
"WiFi signal strength",
|
|
693
|
+
"number",
|
|
694
|
+
"value",
|
|
695
|
+
rssi,
|
|
696
|
+
"dB"
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
const uptime = (0, import_coerce.coerceFiniteNumber)(record.uptime_s);
|
|
700
|
+
if (uptime !== null) {
|
|
701
|
+
await this.ensureAndSet(
|
|
702
|
+
`${prefix}.info.uptime_s`,
|
|
703
|
+
"Uptime",
|
|
704
|
+
"number",
|
|
705
|
+
"value",
|
|
706
|
+
uptime,
|
|
707
|
+
"s"
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
await this.adapter.setObjectNotExistsAsync(`${prefix}.system`, {
|
|
670
711
|
type: "channel",
|
|
671
712
|
common: { name: "System Settings" },
|
|
672
713
|
native: {}
|
|
673
714
|
});
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
715
|
+
const cloudEnabled = (0, import_coerce.coerceBoolean)(record.cloud_enabled);
|
|
716
|
+
if (cloudEnabled !== null) {
|
|
717
|
+
await this.ensureAndSet(
|
|
718
|
+
`${prefix}.system.cloud_enabled`,
|
|
719
|
+
"Cloud enabled",
|
|
720
|
+
"boolean",
|
|
721
|
+
"switch",
|
|
722
|
+
cloudEnabled,
|
|
723
|
+
void 0,
|
|
724
|
+
true
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
const ledPct = (0, import_coerce.coerceFiniteNumber)(record.status_led_brightness_pct);
|
|
728
|
+
if (ledPct !== null) {
|
|
729
|
+
await this.ensureAndSet(
|
|
730
|
+
`${prefix}.system.status_led_brightness_pct`,
|
|
731
|
+
"LED brightness",
|
|
732
|
+
"number",
|
|
733
|
+
"level",
|
|
734
|
+
ledPct,
|
|
735
|
+
"%",
|
|
736
|
+
true
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
const apiV1 = (0, import_coerce.coerceBoolean)(record.api_v1_enabled);
|
|
740
|
+
if (apiV1 !== null) {
|
|
693
741
|
await this.ensureAndSet(
|
|
694
742
|
`${prefix}.system.api_v1_enabled`,
|
|
695
743
|
"API v1 enabled",
|
|
696
744
|
"boolean",
|
|
697
745
|
"switch",
|
|
698
|
-
|
|
746
|
+
apiV1,
|
|
699
747
|
void 0,
|
|
700
748
|
true
|
|
701
749
|
);
|
|
@@ -713,80 +761,87 @@ class StateManager {
|
|
|
713
761
|
* @param battery Battery control data
|
|
714
762
|
*/
|
|
715
763
|
async updateBattery(config, battery) {
|
|
764
|
+
if (!(0, import_coerce.isPlainObject)(battery)) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
716
767
|
const prefix = this.devicePrefix(config);
|
|
717
|
-
|
|
768
|
+
const record = battery;
|
|
769
|
+
await this.adapter.setObjectNotExistsAsync(`${prefix}.battery`, {
|
|
718
770
|
type: "channel",
|
|
719
771
|
common: { name: "Battery Control" },
|
|
720
772
|
native: {}
|
|
721
773
|
});
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
"Battery mode",
|
|
725
|
-
"string",
|
|
726
|
-
"text",
|
|
727
|
-
battery.mode,
|
|
728
|
-
void 0,
|
|
729
|
-
true
|
|
730
|
-
);
|
|
731
|
-
if (battery.permissions !== void 0) {
|
|
774
|
+
const mode = (0, import_coerce.coerceString)(record.mode);
|
|
775
|
+
if (mode) {
|
|
732
776
|
await this.ensureAndSet(
|
|
733
|
-
`${prefix}.battery.
|
|
734
|
-
"Battery
|
|
777
|
+
`${prefix}.battery.mode`,
|
|
778
|
+
"Battery mode",
|
|
735
779
|
"string",
|
|
736
|
-
"
|
|
737
|
-
|
|
780
|
+
"text",
|
|
781
|
+
mode,
|
|
738
782
|
void 0,
|
|
739
783
|
true
|
|
740
784
|
);
|
|
741
785
|
}
|
|
742
|
-
if (
|
|
743
|
-
await this.ensureAndSet(
|
|
744
|
-
`${prefix}.battery.battery_count`,
|
|
745
|
-
"Connected batteries",
|
|
746
|
-
"number",
|
|
747
|
-
"value",
|
|
748
|
-
battery.battery_count
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
if (battery.power_w !== void 0) {
|
|
752
|
-
await this.ensureAndSet(
|
|
753
|
-
`${prefix}.battery.power_w`,
|
|
754
|
-
"Battery power",
|
|
755
|
-
"number",
|
|
756
|
-
"value.power",
|
|
757
|
-
battery.power_w,
|
|
758
|
-
"W"
|
|
759
|
-
);
|
|
760
|
-
}
|
|
761
|
-
if (battery.target_power_w !== void 0) {
|
|
786
|
+
if (Array.isArray(record.permissions)) {
|
|
762
787
|
await this.ensureAndSet(
|
|
763
|
-
`${prefix}.battery.
|
|
764
|
-
"
|
|
765
|
-
"
|
|
766
|
-
"
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
771
|
-
if (battery.max_consumption_w !== void 0) {
|
|
772
|
-
await this.ensureAndSet(
|
|
773
|
-
`${prefix}.battery.max_consumption_w`,
|
|
774
|
-
"Max consumption",
|
|
775
|
-
"number",
|
|
776
|
-
"value.power",
|
|
777
|
-
battery.max_consumption_w,
|
|
778
|
-
"W"
|
|
788
|
+
`${prefix}.battery.permissions`,
|
|
789
|
+
"Battery permissions",
|
|
790
|
+
"string",
|
|
791
|
+
"json",
|
|
792
|
+
JSON.stringify(record.permissions),
|
|
793
|
+
void 0,
|
|
794
|
+
true
|
|
779
795
|
);
|
|
780
796
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
"
|
|
786
|
-
"value
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
797
|
+
const numberFields = [
|
|
798
|
+
{
|
|
799
|
+
key: "battery_count",
|
|
800
|
+
id: "battery_count",
|
|
801
|
+
name: "Connected batteries",
|
|
802
|
+
role: "value"
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
key: "power_w",
|
|
806
|
+
id: "power_w",
|
|
807
|
+
name: "Battery power",
|
|
808
|
+
role: "value.power",
|
|
809
|
+
unit: "W"
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
key: "target_power_w",
|
|
813
|
+
id: "target_power_w",
|
|
814
|
+
name: "Target power",
|
|
815
|
+
role: "value.power",
|
|
816
|
+
unit: "W"
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
key: "max_consumption_w",
|
|
820
|
+
id: "max_consumption_w",
|
|
821
|
+
name: "Max consumption",
|
|
822
|
+
role: "value.power",
|
|
823
|
+
unit: "W"
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
key: "max_production_w",
|
|
827
|
+
id: "max_production_w",
|
|
828
|
+
name: "Max production",
|
|
829
|
+
role: "value.power",
|
|
830
|
+
unit: "W"
|
|
831
|
+
}
|
|
832
|
+
];
|
|
833
|
+
for (const field of numberFields) {
|
|
834
|
+
const coerced = (0, import_coerce.coerceFiniteNumber)(record[field.key]);
|
|
835
|
+
if (coerced !== null) {
|
|
836
|
+
await this.ensureAndSet(
|
|
837
|
+
`${prefix}.battery.${field.id}`,
|
|
838
|
+
field.name,
|
|
839
|
+
"number",
|
|
840
|
+
field.role,
|
|
841
|
+
coerced,
|
|
842
|
+
field.unit
|
|
843
|
+
);
|
|
844
|
+
}
|
|
790
845
|
}
|
|
791
846
|
}
|
|
792
847
|
/**
|
|
@@ -859,7 +914,7 @@ class StateManager {
|
|
|
859
914
|
if (unit) {
|
|
860
915
|
common.unit = unit;
|
|
861
916
|
}
|
|
862
|
-
await this.adapter.
|
|
917
|
+
await this.adapter.setObjectNotExistsAsync(id, {
|
|
863
918
|
type: "state",
|
|
864
919
|
common,
|
|
865
920
|
native: {}
|
|
@@ -872,7 +927,7 @@ class StateManager {
|
|
|
872
927
|
* @param name State name
|
|
873
928
|
*/
|
|
874
929
|
async createButton(id, name) {
|
|
875
|
-
await this.adapter.
|
|
930
|
+
await this.adapter.setObjectNotExistsAsync(id, {
|
|
876
931
|
type: "state",
|
|
877
932
|
common: {
|
|
878
933
|
name,
|