iobroker.homewizard 0.11.0 → 0.12.1
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 +24 -22
- package/build/lib/websocket-client.js +0 -5
- package/build/lib/websocket-client.js.map +2 -2
- package/io-package.json +33 -28
- package/package.json +5 -6
package/README.md
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
# ioBroker.homewizard
|
|
1
|
+
# <img src="https://cdn.jsdelivr.net/gh/krobipd/ioBroker.homewizard@main/admin/homewizard.svg" width="48" align="top" /> ioBroker.homewizard
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/iobroker.homewizard)
|
|
4
|
-

|
|
5
|
-

|
|
6
|
-
[](LICENSE)
|
|
7
|
-
[](https://www.npmjs.com/package/iobroker.homewizard)
|
|
8
|
-

|
|
9
|
-
[](https://ko-fi.com/krobipd)
|
|
10
|
-
[](https://paypal.me/krobipd)
|
|
3
|
+
**Release:** [](https://www.npmjs.com/package/iobroker.homewizard)   [](https://www.npmjs.com/package/iobroker.homewizard)
|
|
11
4
|
|
|
12
|
-
|
|
5
|
+
**Build:** [](https://github.com/krobipd/ioBroker.homewizard/actions/workflows/test-and-release.yml)   [](LICENSE) [](https://github.com/ioBroker/plugin-sentry#plugin-sentry)
|
|
6
|
+
|
|
7
|
+
**Support:** [](https://ko-fi.com/krobipd) [](https://paypal.me/krobipd)
|
|
13
8
|
|
|
14
9
|
Real-time energy monitoring for [HomeWizard](https://www.homewizard.com) Energy devices with API v2.
|
|
15
10
|
|
|
@@ -26,10 +21,18 @@ Real-time energy monitoring for [HomeWizard](https://www.homewizard.com) Energy
|
|
|
26
21
|
|
|
27
22
|
---
|
|
28
23
|
|
|
24
|
+
## Sentry / Error reporting
|
|
25
|
+
|
|
26
|
+
**This adapter uses Sentry libraries to automatically report exceptions and code errors to the developers.** Reporting only happens if you have enabled error reporting in the ioBroker diagnostics (**System settings → Diagnostics and error reporting**). Only an anonymous installation ID is transmitted — no name, e-mail address or IP address.
|
|
27
|
+
|
|
28
|
+
For details and how to disable it, see the [Sentry plugin documentation](https://github.com/ioBroker/plugin-sentry#plugin-sentry). Error reporting requires js-controller 3.0 or newer.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
29
32
|
## Requirements
|
|
30
33
|
|
|
31
34
|
- **Node.js >= 22**
|
|
32
|
-
- **ioBroker js-controller >= 7.
|
|
35
|
+
- **ioBroker js-controller >= 7.1.2**
|
|
33
36
|
- **ioBroker Admin >= 7.8.23**
|
|
34
37
|
- **HomeWizard device with API v2 support** (firmware 4.x+ with local API enabled)
|
|
35
38
|
|
|
@@ -117,7 +120,6 @@ homewizard.0.
|
|
|
117
120
|
│ ├── monthly_power_peak_timestamp — Monthly peak timestamp (string)
|
|
118
121
|
│ ├── meter_model — Meter model identifier (string)
|
|
119
122
|
│ ├── timestamp — Measurement timestamp (string)
|
|
120
|
-
│ ├── telegram — Raw P1 telegram text (string, P1 only)
|
|
121
123
|
│ ├── quality/ — Power quality counters
|
|
122
124
|
│ │ ├── voltage_sag_l1..l3_count
|
|
123
125
|
│ │ ├── voltage_swell_l1..l3_count
|
|
@@ -177,6 +179,14 @@ homewizard.0.
|
|
|
177
179
|
Placeholder for the next version (at the beginning of the line):
|
|
178
180
|
### **WORK IN PROGRESS**
|
|
179
181
|
-->
|
|
182
|
+
### 0.12.1 (2026-06-09)
|
|
183
|
+
|
|
184
|
+
- Internal refactoring. No user-facing changes.
|
|
185
|
+
|
|
186
|
+
### 0.12.0 (2026-06-07)
|
|
187
|
+
|
|
188
|
+
- Added optional Sentry error reporting: crashes are sent to the developer so issues get fixed faster. Active only with ioBroker diagnostics enabled; anonymous.
|
|
189
|
+
|
|
180
190
|
### 0.11.0 (2026-06-03)
|
|
181
191
|
|
|
182
192
|
- Removed the raw P1 telegram datapoint — its data is already available as parsed measurement states; the leftover state is cleaned up automatically on existing P1 meters.
|
|
@@ -193,15 +203,7 @@ homewizard.0.
|
|
|
193
203
|
- User-modified device names are no longer overwritten on adapter restart or IP recovery.
|
|
194
204
|
- Improved timer management for ioBroker compact mode.
|
|
195
205
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
- Changelog rewritten in user-centric style across all versions.
|
|
199
|
-
|
|
200
|
-
### 0.9.1 (2026-05-23)
|
|
201
|
-
|
|
202
|
-
- Internal cleanup. No user-facing changes.
|
|
203
|
-
|
|
204
|
-
Older entries are in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
206
|
+
[Older changelogs can be found there](CHANGELOG_OLD.md)
|
|
205
207
|
|
|
206
208
|
## Support Development
|
|
207
209
|
|
|
@@ -238,4 +240,4 @@ SOFTWARE.
|
|
|
238
240
|
|
|
239
241
|
---
|
|
240
242
|
|
|
241
|
-
|
|
243
|
+
_Developed with assistance from Claude.ai_
|
|
@@ -117,11 +117,6 @@ class HomeWizardWebSocket {
|
|
|
117
117
|
this.destroyed = true;
|
|
118
118
|
this.cleanup();
|
|
119
119
|
}
|
|
120
|
-
/** Whether the WebSocket is currently open */
|
|
121
|
-
get isConnected() {
|
|
122
|
-
var _a;
|
|
123
|
-
return ((_a = this.ws) == null ? void 0 : _a.readyState) === import_ws.default.OPEN;
|
|
124
|
-
}
|
|
125
120
|
/**
|
|
126
121
|
* Handle incoming WebSocket message
|
|
127
122
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/websocket-client.ts"],
|
|
4
|
-
"sourcesContent": ["import type * as https from \"node:https\";\nimport WebSocket from \"ws\";\nimport { HW_AGENT } from \"./cacert\";\nimport { isPlainObject } from \"./coerce\";\nimport type { BatteryControl, Measurement, SystemInfo } from \"./types\";\n\n/** Auth handshake must complete within this window (Doku says 40s, +5s slack). */\nexport const AUTH_TIMEOUT_MS = 45_000;\n/** WS-layer ping interval after `authorized`. */\nexport const PING_INTERVAL_MS = 30_000;\n/** Max time to wait for a pong reply before declaring the link dead. */\nexport const PONG_TIMEOUT_MS = 10_000;\n\n/** Timer dependency injection \u2014 allows adapter-managed timers instead of native ones. */\nexport interface TimerDeps {\n /** Schedule a one-shot callback */\n schedule(cb: () => void, ms: number): unknown;\n /** Cancel a one-shot timer */\n cancel(handle: unknown): void;\n /** Schedule a recurring callback */\n scheduleRepeating(cb: () => void, ms: number): unknown;\n /** Cancel a recurring timer */\n cancelRepeating(handle: unknown): void;\n}\n\n/** Callback interface for WebSocket events */\nexport interface WsCallbacks {\n /** Called when measurement data is received */\n onMeasurement: (data: Measurement) => void;\n /** Called when a real-time system push is received (cloud/led changes etc.). Optional. */\n onSystem?: (data: SystemInfo) => void;\n /** Called when a real-time battery-group push is received (mode/permissions/target power). Optional. */\n onBattery?: (data: BatteryControl) => void;\n /** Called when connection is established and authenticated */\n onConnected: () => void;\n /** Called when connection is lost */\n onDisconnected: (error?: Error) => void;\n /** Log functions */\n log: {\n debug: (msg: string) => void;\n warn: (msg: string) => void;\n };\n}\n\n/**\n * WebSocket client for HomeWizard real-time measurement push.\n * Handles auth handshake, subscription, heartbeat (WS-layer ping/pong),\n * and termination of half-dead connections (TCP open, no traffic).\n *\n * The push is event-driven (P1 Power ~1/s, Gas ~5min, Battery undocumented),\n * so we cannot rely on measurement frames as a liveness signal. Instead we\n * use the WS-layer ping/pong frames, which the device must answer regardless\n * of data activity.\n */\nexport class HomeWizardWebSocket {\n private readonly ip: string;\n private readonly token: string;\n private readonly callbacks: WsCallbacks;\n private readonly timers: TimerDeps;\n private readonly agent: https.Agent;\n /** Override target port \u2014 only used by tests against a local wss stub-server. */\n private readonly port: number;\n private ws: WebSocket | null = null;\n private destroyed = false;\n private authTimer: unknown = null;\n private pingInterval: unknown = null;\n private pongTimer: unknown = null;\n\n /**\n * @param ip Device IP address\n * @param token Bearer token\n * @param callbacks Event callbacks\n * @param timers Timer functions (use adapter-managed timers in production)\n * @param options Optional overrides \u2014 primarily for unit tests against a local wss stub.\n * @param options.agent HTTPS agent to use; defaults to {@link HW_AGENT} (with HomeWizard CA pinning).\n * @param options.port Target port; defaults to 443.\n */\n constructor(\n ip: string,\n token: string,\n callbacks: WsCallbacks,\n timers: TimerDeps,\n options: { agent?: https.Agent; port?: number } = {},\n ) {\n this.ip = ip;\n this.token = token;\n this.callbacks = callbacks;\n this.timers = timers;\n this.agent = options.agent ?? HW_AGENT;\n this.port = options.port ?? 443;\n }\n\n /** Connect to WebSocket and start auth handshake */\n connect(): void {\n if (this.destroyed) {\n return;\n }\n\n this.cleanup();\n\n const portSeg = this.port !== 443 ? `:${this.port}` : \"\";\n const url = `wss://${this.ip}${portSeg}/api/ws`;\n this.callbacks.log.debug(`WS connecting to ${url}`);\n\n this.ws = new WebSocket(url, {\n agent: this.agent,\n handshakeTimeout: 10_000,\n });\n\n // Auth-watchdog: server must finish the auth handshake within\n // AUTH_TIMEOUT_MS or we declare the link dead. Doku timeout is 40s.\n this.authTimer = this.timers.schedule(() => {\n this.callbacks.log.debug(`WS auth-timeout (${AUTH_TIMEOUT_MS}ms) \u2014 terminating`);\n this.forceDisconnect();\n }, AUTH_TIMEOUT_MS);\n\n this.ws.on(\"open\", () => {\n this.callbacks.log.debug(`WS open to ${this.ip}`);\n });\n\n this.ws.on(\"message\", (raw: WebSocket.RawData) => {\n this.handleMessage(raw);\n });\n\n this.ws.on(\"pong\", () => {\n // Pong arrived in time \u2014 clear pending pong-timer.\n if (this.pongTimer != null) {\n this.timers.cancel(this.pongTimer);\n this.pongTimer = null;\n }\n });\n\n this.ws.on(\"close\", (code: number, reason: Buffer) => {\n this.callbacks.log.debug(`WS closed: ${code} ${reason.toString()}`);\n this.clearTimers();\n this.ws = null;\n if (!this.destroyed) {\n this.callbacks.onDisconnected();\n }\n });\n\n this.ws.on(\"error\", (err: Error) => {\n this.callbacks.log.debug(`WS error: ${err.message}`);\n // close event will follow\n });\n }\n\n /** Gracefully close connection */\n close(): void {\n this.destroyed = true;\n this.cleanup();\n }\n\n /** Whether the WebSocket is currently open */\n get isConnected(): boolean {\n return this.ws?.readyState === WebSocket.OPEN;\n }\n\n /**\n * Handle incoming WebSocket message\n *\n * @param raw Raw message data\n */\n private handleMessage(raw: WebSocket.RawData): void {\n const text = Buffer.isBuffer(raw)\n ? raw.toString(\"utf8\")\n : raw instanceof ArrayBuffer\n ? Buffer.from(raw).toString(\"utf8\")\n : Array.isArray(raw)\n ? Buffer.concat(raw).toString(\"utf8\")\n : \"\";\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n this.callbacks.log.warn(`WS invalid JSON: ${text.substring(0, 200)}`);\n return;\n }\n\n if (!isPlainObject(parsed)) {\n this.callbacks.log.warn(`WS non-object message: ${text.substring(0, 200)}`);\n return;\n }\n\n const type = parsed.type;\n if (typeof type !== \"string\") {\n this.callbacks.log.warn(`WS message without string type`);\n return;\n }\n\n switch (type) {\n case \"authorization_requested\":\n this.callbacks.log.debug(\"WS auth requested, sending token\");\n this.sendRaw({ type: \"authorization\", data: this.token });\n break;\n\n case \"authorized\":\n // Subscribe to the three real-time topics this adapter consumes (explicit, not \"*\",\n // to avoid device/user-topic noise). system/batteries push control-state changes;\n // measurement is the ~1/s data feed.\n this.callbacks.log.debug(\"WS authorized, subscribing to measurement + system + batteries\");\n this.sendRaw({ type: \"subscribe\", data: \"measurement\" });\n this.sendRaw({ type: \"subscribe\", data: \"system\" });\n this.sendRaw({ type: \"subscribe\", data: \"batteries\" });\n // Auth complete \u2014 clear auth-watchdog and start the heartbeat.\n if (this.authTimer != null) {\n this.timers.cancel(this.authTimer);\n this.authTimer = null;\n }\n this.startHeartbeat();\n this.callbacks.onConnected();\n break;\n\n case \"measurement\":\n if (isPlainObject(parsed.data)) {\n this.callbacks.onMeasurement(parsed.data);\n } else {\n this.callbacks.log.warn(`WS measurement without object payload`);\n }\n break;\n\n case \"system\":\n // isPlainObject-guarded WS payload; updateSystem re-validates every field, so the\n // boundary cast is safe (the typed shape is aspirational, not trusted).\n if (isPlainObject(parsed.data)) {\n this.callbacks.onSystem?.(parsed.data as unknown as SystemInfo);\n }\n break;\n\n case \"batteries\":\n if (isPlainObject(parsed.data)) {\n this.callbacks.onBattery?.(parsed.data as unknown as BatteryControl);\n }\n break;\n\n case \"error\": {\n const detail =\n isPlainObject(parsed.data) && typeof parsed.data.message === \"string\"\n ? parsed.data.message\n : text.substring(0, 200);\n this.callbacks.log.warn(`WS error: ${detail}`);\n break;\n }\n\n default:\n this.callbacks.log.debug(`WS message type: ${type}`);\n break;\n }\n }\n\n /**\n * Send a message over WebSocket\n *\n * @param msg Message envelope\n * @param msg.type Message type identifier\n * @param msg.data Optional payload\n */\n private sendRaw(msg: { type: string; data?: unknown }): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n }\n\n /**\n * Start the ping/pong heartbeat. Sends a WS-layer ping every\n * PING_INTERVAL_MS and arms a pong-timer; a missing pong terminates.\n * This catches half-dead links where the TCP stream is buffered but the\n * device has stopped responding (the documented \"API-Lockup\" mode).\n */\n private startHeartbeat(): void {\n this.pingInterval = this.timers.scheduleRepeating(() => {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n return;\n }\n // Arm the pong-timer first, then ping. If pong arrives, the pong\n // handler clears it; if it doesn't, we terminate.\n this.pongTimer = this.timers.schedule(() => {\n this.callbacks.log.debug(`WS pong-timeout (${PONG_TIMEOUT_MS}ms) \u2014 terminating`);\n this.forceDisconnect();\n }, PONG_TIMEOUT_MS);\n try {\n this.ws.ping();\n } catch (err) {\n this.callbacks.log.debug(`WS ping send failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }, PING_INTERVAL_MS);\n }\n\n /** Terminate the socket \u2014 triggers close-event \u2192 onDisconnected \u2192 reconnect. */\n private forceDisconnect(): void {\n if (!this.ws) {\n return;\n }\n try {\n this.ws.terminate();\n } catch {\n // ignore \u2014 already closed\n }\n }\n\n /** Clear all timers. Called on close, cleanup, and from the close-event. */\n private clearTimers(): void {\n if (this.authTimer != null) {\n this.timers.cancel(this.authTimer);\n this.authTimer = null;\n }\n if (this.pingInterval != null) {\n this.timers.cancelRepeating(this.pingInterval);\n this.pingInterval = null;\n }\n if (this.pongTimer != null) {\n this.timers.cancel(this.pongTimer);\n this.pongTimer = null;\n }\n }\n\n /** Close WebSocket without triggering reconnect */\n private cleanup(): void {\n this.clearTimers();\n if (this.ws) {\n this.ws.removeAllListeners();\n // Prevent uncaught errors from frames received during close\n this.ws.on(\"error\", () => {});\n this.ws.terminate();\n this.ws = null;\n }\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,gBAAsB;AACtB,oBAAyB;AACzB,oBAA8B;AAIvB,MAAM,kBAAkB;AAExB,MAAM,mBAAmB;AAEzB,MAAM,kBAAkB;AA2CxB,MAAM,oBAAoB;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACT,KAAuB;AAAA,EACvB,YAAY;AAAA,EACZ,YAAqB;AAAA,EACrB,eAAwB;AAAA,EACxB,YAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAW7B,YACE,IACA,OACA,WACA,QACA,UAAkD,CAAC,GACnD;AAnFJ;AAoFI,SAAK,KAAK;AACV,SAAK,QAAQ;AACb,SAAK,YAAY;AACjB,SAAK,SAAS;AACd,SAAK,SAAQ,aAAQ,UAAR,YAAiB;AAC9B,SAAK,QAAO,aAAQ,SAAR,YAAgB;AAAA,EAC9B;AAAA;AAAA,EAGA,UAAgB;AACd,QAAI,KAAK,WAAW;AAClB;AAAA,IACF;AAEA,SAAK,QAAQ;AAEb,UAAM,UAAU,KAAK,SAAS,MAAM,IAAI,KAAK,IAAI,KAAK;AACtD,UAAM,MAAM,SAAS,KAAK,EAAE,GAAG,OAAO;AACtC,SAAK,UAAU,IAAI,MAAM,oBAAoB,GAAG,EAAE;AAElD,SAAK,KAAK,IAAI,UAAAA,QAAU,KAAK;AAAA,MAC3B,OAAO,KAAK;AAAA,MACZ,kBAAkB;AAAA,IACpB,CAAC;AAID,SAAK,YAAY,KAAK,OAAO,SAAS,MAAM;AAC1C,WAAK,UAAU,IAAI,MAAM,oBAAoB,eAAe,wBAAmB;AAC/E,WAAK,gBAAgB;AAAA,IACvB,GAAG,eAAe;AAElB,SAAK,GAAG,GAAG,QAAQ,MAAM;AACvB,WAAK,UAAU,IAAI,MAAM,cAAc,KAAK,EAAE,EAAE;AAAA,IAClD,CAAC;AAED,SAAK,GAAG,GAAG,WAAW,CAAC,QAA2B;AAChD,WAAK,cAAc,GAAG;AAAA,IACxB,CAAC;AAED,SAAK,GAAG,GAAG,QAAQ,MAAM;AAEvB,UAAI,KAAK,aAAa,MAAM;AAC1B,aAAK,OAAO,OAAO,KAAK,SAAS;AACjC,aAAK,YAAY;AAAA,MACnB;AAAA,IACF,CAAC;AAED,SAAK,GAAG,GAAG,SAAS,CAAC,MAAc,WAAmB;AACpD,WAAK,UAAU,IAAI,MAAM,cAAc,IAAI,IAAI,OAAO,SAAS,CAAC,EAAE;AAClE,WAAK,YAAY;AACjB,WAAK,KAAK;AACV,UAAI,CAAC,KAAK,WAAW;AACnB,aAAK,UAAU,eAAe;AAAA,MAChC;AAAA,IACF,CAAC;AAED,SAAK,GAAG,GAAG,SAAS,CAAC,QAAe;AAClC,WAAK,UAAU,IAAI,MAAM,aAAa,IAAI,OAAO,EAAE;AAAA,IAErD,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,YAAY;AACjB,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA
|
|
4
|
+
"sourcesContent": ["import type * as https from \"node:https\";\nimport WebSocket from \"ws\";\nimport { HW_AGENT } from \"./cacert\";\nimport { isPlainObject } from \"./coerce\";\nimport type { BatteryControl, Measurement, SystemInfo } from \"./types\";\n\n/** Auth handshake must complete within this window (Doku says 40s, +5s slack). */\nexport const AUTH_TIMEOUT_MS = 45_000;\n/** WS-layer ping interval after `authorized`. */\nexport const PING_INTERVAL_MS = 30_000;\n/** Max time to wait for a pong reply before declaring the link dead. */\nexport const PONG_TIMEOUT_MS = 10_000;\n\n/** Timer dependency injection \u2014 allows adapter-managed timers instead of native ones. */\nexport interface TimerDeps {\n /** Schedule a one-shot callback */\n schedule(cb: () => void, ms: number): unknown;\n /** Cancel a one-shot timer */\n cancel(handle: unknown): void;\n /** Schedule a recurring callback */\n scheduleRepeating(cb: () => void, ms: number): unknown;\n /** Cancel a recurring timer */\n cancelRepeating(handle: unknown): void;\n}\n\n/** Callback interface for WebSocket events */\nexport interface WsCallbacks {\n /** Called when measurement data is received */\n onMeasurement: (data: Measurement) => void;\n /** Called when a real-time system push is received (cloud/led changes etc.). Optional. */\n onSystem?: (data: SystemInfo) => void;\n /** Called when a real-time battery-group push is received (mode/permissions/target power). Optional. */\n onBattery?: (data: BatteryControl) => void;\n /** Called when connection is established and authenticated */\n onConnected: () => void;\n /** Called when connection is lost */\n onDisconnected: (error?: Error) => void;\n /** Log functions */\n log: {\n debug: (msg: string) => void;\n warn: (msg: string) => void;\n };\n}\n\n/**\n * WebSocket client for HomeWizard real-time measurement push.\n * Handles auth handshake, subscription, heartbeat (WS-layer ping/pong),\n * and termination of half-dead connections (TCP open, no traffic).\n *\n * The push is event-driven (P1 Power ~1/s, Gas ~5min, Battery undocumented),\n * so we cannot rely on measurement frames as a liveness signal. Instead we\n * use the WS-layer ping/pong frames, which the device must answer regardless\n * of data activity.\n */\nexport class HomeWizardWebSocket {\n private readonly ip: string;\n private readonly token: string;\n private readonly callbacks: WsCallbacks;\n private readonly timers: TimerDeps;\n private readonly agent: https.Agent;\n /** Override target port \u2014 only used by tests against a local wss stub-server. */\n private readonly port: number;\n private ws: WebSocket | null = null;\n private destroyed = false;\n private authTimer: unknown = null;\n private pingInterval: unknown = null;\n private pongTimer: unknown = null;\n\n /**\n * @param ip Device IP address\n * @param token Bearer token\n * @param callbacks Event callbacks\n * @param timers Timer functions (use adapter-managed timers in production)\n * @param options Optional overrides \u2014 primarily for unit tests against a local wss stub.\n * @param options.agent HTTPS agent to use; defaults to {@link HW_AGENT} (with HomeWizard CA pinning).\n * @param options.port Target port; defaults to 443.\n */\n constructor(\n ip: string,\n token: string,\n callbacks: WsCallbacks,\n timers: TimerDeps,\n options: { agent?: https.Agent; port?: number } = {},\n ) {\n this.ip = ip;\n this.token = token;\n this.callbacks = callbacks;\n this.timers = timers;\n this.agent = options.agent ?? HW_AGENT;\n this.port = options.port ?? 443;\n }\n\n /** Connect to WebSocket and start auth handshake */\n connect(): void {\n if (this.destroyed) {\n return;\n }\n\n this.cleanup();\n\n const portSeg = this.port !== 443 ? `:${this.port}` : \"\";\n const url = `wss://${this.ip}${portSeg}/api/ws`;\n this.callbacks.log.debug(`WS connecting to ${url}`);\n\n this.ws = new WebSocket(url, {\n agent: this.agent,\n handshakeTimeout: 10_000,\n });\n\n // Auth-watchdog: server must finish the auth handshake within\n // AUTH_TIMEOUT_MS or we declare the link dead. Doku timeout is 40s.\n this.authTimer = this.timers.schedule(() => {\n this.callbacks.log.debug(`WS auth-timeout (${AUTH_TIMEOUT_MS}ms) \u2014 terminating`);\n this.forceDisconnect();\n }, AUTH_TIMEOUT_MS);\n\n this.ws.on(\"open\", () => {\n this.callbacks.log.debug(`WS open to ${this.ip}`);\n });\n\n this.ws.on(\"message\", (raw: WebSocket.RawData) => {\n this.handleMessage(raw);\n });\n\n this.ws.on(\"pong\", () => {\n // Pong arrived in time \u2014 clear pending pong-timer.\n if (this.pongTimer != null) {\n this.timers.cancel(this.pongTimer);\n this.pongTimer = null;\n }\n });\n\n this.ws.on(\"close\", (code: number, reason: Buffer) => {\n this.callbacks.log.debug(`WS closed: ${code} ${reason.toString()}`);\n this.clearTimers();\n this.ws = null;\n if (!this.destroyed) {\n this.callbacks.onDisconnected();\n }\n });\n\n this.ws.on(\"error\", (err: Error) => {\n this.callbacks.log.debug(`WS error: ${err.message}`);\n // close event will follow\n });\n }\n\n /** Gracefully close connection */\n close(): void {\n this.destroyed = true;\n this.cleanup();\n }\n\n /**\n * Handle incoming WebSocket message\n *\n * @param raw Raw message data\n */\n private handleMessage(raw: WebSocket.RawData): void {\n const text = Buffer.isBuffer(raw)\n ? raw.toString(\"utf8\")\n : raw instanceof ArrayBuffer\n ? Buffer.from(raw).toString(\"utf8\")\n : Array.isArray(raw)\n ? Buffer.concat(raw).toString(\"utf8\")\n : \"\";\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n this.callbacks.log.warn(`WS invalid JSON: ${text.substring(0, 200)}`);\n return;\n }\n\n if (!isPlainObject(parsed)) {\n this.callbacks.log.warn(`WS non-object message: ${text.substring(0, 200)}`);\n return;\n }\n\n const type = parsed.type;\n if (typeof type !== \"string\") {\n this.callbacks.log.warn(`WS message without string type`);\n return;\n }\n\n switch (type) {\n case \"authorization_requested\":\n this.callbacks.log.debug(\"WS auth requested, sending token\");\n this.sendRaw({ type: \"authorization\", data: this.token });\n break;\n\n case \"authorized\":\n // Subscribe to the three real-time topics this adapter consumes (explicit, not \"*\",\n // to avoid device/user-topic noise). system/batteries push control-state changes;\n // measurement is the ~1/s data feed.\n this.callbacks.log.debug(\"WS authorized, subscribing to measurement + system + batteries\");\n this.sendRaw({ type: \"subscribe\", data: \"measurement\" });\n this.sendRaw({ type: \"subscribe\", data: \"system\" });\n this.sendRaw({ type: \"subscribe\", data: \"batteries\" });\n // Auth complete \u2014 clear auth-watchdog and start the heartbeat.\n if (this.authTimer != null) {\n this.timers.cancel(this.authTimer);\n this.authTimer = null;\n }\n this.startHeartbeat();\n this.callbacks.onConnected();\n break;\n\n case \"measurement\":\n if (isPlainObject(parsed.data)) {\n this.callbacks.onMeasurement(parsed.data);\n } else {\n this.callbacks.log.warn(`WS measurement without object payload`);\n }\n break;\n\n case \"system\":\n // isPlainObject-guarded WS payload; updateSystem re-validates every field, so the\n // boundary cast is safe (the typed shape is aspirational, not trusted).\n if (isPlainObject(parsed.data)) {\n this.callbacks.onSystem?.(parsed.data as unknown as SystemInfo);\n }\n break;\n\n case \"batteries\":\n if (isPlainObject(parsed.data)) {\n this.callbacks.onBattery?.(parsed.data as unknown as BatteryControl);\n }\n break;\n\n case \"error\": {\n const detail =\n isPlainObject(parsed.data) && typeof parsed.data.message === \"string\"\n ? parsed.data.message\n : text.substring(0, 200);\n this.callbacks.log.warn(`WS error: ${detail}`);\n break;\n }\n\n default:\n this.callbacks.log.debug(`WS message type: ${type}`);\n break;\n }\n }\n\n /**\n * Send a message over WebSocket\n *\n * @param msg Message envelope\n * @param msg.type Message type identifier\n * @param msg.data Optional payload\n */\n private sendRaw(msg: { type: string; data?: unknown }): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n }\n\n /**\n * Start the ping/pong heartbeat. Sends a WS-layer ping every\n * PING_INTERVAL_MS and arms a pong-timer; a missing pong terminates.\n * This catches half-dead links where the TCP stream is buffered but the\n * device has stopped responding (the documented \"API-Lockup\" mode).\n */\n private startHeartbeat(): void {\n this.pingInterval = this.timers.scheduleRepeating(() => {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n return;\n }\n // Arm the pong-timer first, then ping. If pong arrives, the pong\n // handler clears it; if it doesn't, we terminate.\n this.pongTimer = this.timers.schedule(() => {\n this.callbacks.log.debug(`WS pong-timeout (${PONG_TIMEOUT_MS}ms) \u2014 terminating`);\n this.forceDisconnect();\n }, PONG_TIMEOUT_MS);\n try {\n this.ws.ping();\n } catch (err) {\n this.callbacks.log.debug(`WS ping send failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }, PING_INTERVAL_MS);\n }\n\n /** Terminate the socket \u2014 triggers close-event \u2192 onDisconnected \u2192 reconnect. */\n private forceDisconnect(): void {\n if (!this.ws) {\n return;\n }\n try {\n this.ws.terminate();\n } catch {\n // ignore \u2014 already closed\n }\n }\n\n /** Clear all timers. Called on close, cleanup, and from the close-event. */\n private clearTimers(): void {\n if (this.authTimer != null) {\n this.timers.cancel(this.authTimer);\n this.authTimer = null;\n }\n if (this.pingInterval != null) {\n this.timers.cancelRepeating(this.pingInterval);\n this.pingInterval = null;\n }\n if (this.pongTimer != null) {\n this.timers.cancel(this.pongTimer);\n this.pongTimer = null;\n }\n }\n\n /** Close WebSocket without triggering reconnect */\n private cleanup(): void {\n this.clearTimers();\n if (this.ws) {\n this.ws.removeAllListeners();\n // Prevent uncaught errors from frames received during close\n this.ws.on(\"error\", () => {});\n this.ws.terminate();\n this.ws = null;\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["WebSocket"]
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "homewizard",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.12.1": {
|
|
7
|
+
"en": "Internal refactoring. No user-facing changes.",
|
|
8
|
+
"de": "Interne Überarbeitung. Keine für Nutzer sichtbaren Änderungen.",
|
|
9
|
+
"ru": "Внутренняя переработка. Никаких изменений для пользователей.",
|
|
10
|
+
"pt": "Refatoração interna. Sem alterações visíveis para o utilizador.",
|
|
11
|
+
"nl": "Interne herstructurering. Geen zichtbare wijzigingen voor gebruikers.",
|
|
12
|
+
"fr": "Refactorisation interne. Aucun changement visible pour l'utilisateur.",
|
|
13
|
+
"it": "Refactoring interno. Nessuna modifica visibile per l'utente.",
|
|
14
|
+
"es": "Refactorización interna. Sin cambios visibles para el usuario.",
|
|
15
|
+
"pl": "Wewnętrzna refaktoryzacja. Brak zmian widocznych dla użytkownika.",
|
|
16
|
+
"uk": "Внутрішня переробка. Жодних змін для користувачів.",
|
|
17
|
+
"zh-cn": "内部重构。对用户无可见变化。"
|
|
18
|
+
},
|
|
19
|
+
"0.12.0": {
|
|
20
|
+
"en": "Added optional Sentry error reporting: crashes are sent to the developer so issues get fixed faster. Active only with ioBroker diagnostics enabled; anonymous.",
|
|
21
|
+
"de": "Optionale Fehlermeldung über Sentry hinzugefügt: Abstürze werden an den Entwickler gesendet, damit Probleme schneller behoben werden. Nur aktiv bei eingeschalteter ioBroker-Diagnose; anonym.",
|
|
22
|
+
"ru": "Добавлена необязательная отправка ошибок через Sentry: сбои отправляются разработчику, чтобы быстрее их устранять. Работает только при включённой диагностике ioBroker; анонимно.",
|
|
23
|
+
"pt": "Adicionado relatório de erros opcional via Sentry: as falhas são enviadas ao programador para corrigir problemas mais rápido. Ativo apenas com o diagnóstico do ioBroker ligado; anónimo.",
|
|
24
|
+
"nl": "Optionele foutrapportage via Sentry toegevoegd: crashes worden naar de ontwikkelaar gestuurd om problemen sneller op te lossen. Alleen actief met ioBroker-diagnostiek ingeschakeld; anoniem.",
|
|
25
|
+
"fr": "Rapport d'erreurs optionnel via Sentry ajouté : les plantages sont envoyés au développeur pour corriger plus vite. Actif seulement si le diagnostic ioBroker est activé ; anonyme.",
|
|
26
|
+
"it": "Aggiunta segnalazione errori opzionale via Sentry: gli arresti vengono inviati allo sviluppatore per risolvere prima i problemi. Attivo solo con la diagnostica ioBroker attiva; anonimo.",
|
|
27
|
+
"es": "Añadido informe de errores opcional mediante Sentry: los fallos se envían al desarrollador para solucionar problemas más rápido. Activo solo con el diagnóstico de ioBroker activado; anónimo.",
|
|
28
|
+
"pl": "Dodano opcjonalne raportowanie błędów przez Sentry: awarie są wysyłane do dewelopera, aby szybciej rozwiązywać problemy. Aktywne tylko przy włączonej diagnostyce ioBroker; anonimowo.",
|
|
29
|
+
"uk": "Додано необов'язкову відправку помилок через Sentry: збої надсилаються розробнику, щоб швидше їх виправляти. Працює лише з увімкненою діагностикою ioBroker; анонімно.",
|
|
30
|
+
"zh-cn": "新增可选的 Sentry 错误上报:崩溃信息会发送给开发者以更快修复问题。仅在启用 ioBroker 诊断时生效;匿名。"
|
|
31
|
+
},
|
|
6
32
|
"0.11.0": {
|
|
7
33
|
"en": "Removed the raw P1 telegram datapoint — its data is already available as parsed measurement states; the leftover state is cleaned up automatically on existing P1 meters.",
|
|
8
34
|
"de": "Roher P1-Telegramm-Datenpunkt entfernt — seine Werte stehen ohnehin als geparste Messdaten-States bereit; der verbliebene State wird auf bestehenden P1-Zählern automatisch aufgeräumt.",
|
|
@@ -67,32 +93,11 @@
|
|
|
67
93
|
"pl": "Wewnętrzne porządki. Brak widocznych zmian dla użytkownika.",
|
|
68
94
|
"uk": "Внутрішнє прибирання. Без видимих змін для користувача.",
|
|
69
95
|
"zh-cn": "内部清理。无用户可见的更改。"
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"
|
|
75
|
-
"pt": "Nomes de estados modificados pelo utilizador já não são substituídos ao reiniciar o adaptador.",
|
|
76
|
-
"nl": "Door gebruiker gewijzigde statusnamen worden niet meer overschreven bij herstart van de adapter.",
|
|
77
|
-
"fr": "Les noms d états modifiés par l utilisateur ne sont plus écrasés au redémarrage de l adaptateur.",
|
|
78
|
-
"it": "I nomi degli stati modificati dall utente non vengono più sovrascritti al riavvio dell adattatore.",
|
|
79
|
-
"es": "Los nombres de estados modificados por el usuario ya no se sobrescriben al reiniciar el adaptador.",
|
|
80
|
-
"pl": "Nazwy stanów zmienione przez użytkownika nie są już nadpisywane przy restarcie adaptera.",
|
|
81
|
-
"uk": "Змінені користувачем назви станів більше не перезаписуються при перезапуску адаптера.",
|
|
82
|
-
"zh-cn": "用户修改的状态名称在适配器重启时不再被覆盖。"
|
|
83
|
-
},
|
|
84
|
-
"0.8.3": {
|
|
85
|
-
"en": "Improved error handling and stability.",
|
|
86
|
-
"de": "Verbesserte Fehlerbehandlung und Stabilität.",
|
|
87
|
-
"ru": "Улучшена обработка ошибок и стабильность.",
|
|
88
|
-
"pt": "Tratamento de erros e estabilidade melhorados.",
|
|
89
|
-
"nl": "Verbeterde foutafhandeling en stabiliteit.",
|
|
90
|
-
"fr": "Gestion des erreurs et stabilité améliorées.",
|
|
91
|
-
"it": "Gestione degli errori e stabilità migliorate.",
|
|
92
|
-
"es": "Mejora del manejo de errores y la estabilidad.",
|
|
93
|
-
"pl": "Poprawiona obsługa błędów i stabilność.",
|
|
94
|
-
"uk": "Покращено обробку помилок та стабільність.",
|
|
95
|
-
"zh-cn": "改进了错误处理和稳定性。"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"plugins": {
|
|
99
|
+
"sentry": {
|
|
100
|
+
"dsn": "https://66f27ceff38c1ce1e6cdcc3095ba06d3@o4511524771266560.ingest.de.sentry.io/4511524773560400"
|
|
96
101
|
}
|
|
97
102
|
},
|
|
98
103
|
"titleLang": {
|
|
@@ -157,7 +162,7 @@
|
|
|
157
162
|
},
|
|
158
163
|
"dependencies": [
|
|
159
164
|
{
|
|
160
|
-
"js-controller": ">=7.
|
|
165
|
+
"js-controller": ">=7.1.2"
|
|
161
166
|
}
|
|
162
167
|
],
|
|
163
168
|
"globalDependencies": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.homewizard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "ioBroker adapter for HomeWizard Energy devices with API v2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "krobi",
|
|
@@ -28,8 +28,7 @@
|
|
|
28
28
|
"url": "https://github.com/krobipd/ioBroker.homewizard"
|
|
29
29
|
},
|
|
30
30
|
"engines": {
|
|
31
|
-
"node": ">=22"
|
|
32
|
-
"npm": ">=10"
|
|
31
|
+
"node": ">=22"
|
|
33
32
|
},
|
|
34
33
|
"dependencies": {
|
|
35
34
|
"@iobroker/adapter-core": "^3.3.2",
|
|
@@ -37,7 +36,7 @@
|
|
|
37
36
|
"ws": "^8.21.0"
|
|
38
37
|
},
|
|
39
38
|
"devDependencies": {
|
|
40
|
-
"@alcalzone/release-script": "^5.2.
|
|
39
|
+
"@alcalzone/release-script": "^5.2.1",
|
|
41
40
|
"@alcalzone/release-script-plugin-iobroker": "^5.2.0",
|
|
42
41
|
"@alcalzone/release-script-plugin-license": "^5.2.0",
|
|
43
42
|
"@iobroker/adapter-dev": "^1.5.0",
|
|
@@ -47,10 +46,10 @@
|
|
|
47
46
|
"@types/iobroker": "npm:@iobroker/types@^7.1.2",
|
|
48
47
|
"@types/node": "^22.19.17",
|
|
49
48
|
"@types/ws": "^8.18.1",
|
|
50
|
-
"@vitest/coverage-v8": "^4.1.
|
|
49
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
51
50
|
"rimraf": "^6.1.3",
|
|
52
51
|
"typescript": "~6.0.3",
|
|
53
|
-
"vitest": "^4.1.
|
|
52
|
+
"vitest": "^4.1.8"
|
|
54
53
|
},
|
|
55
54
|
"files": [
|
|
56
55
|
"admin",
|