iobroker.homewizard 0.6.1 → 0.6.2

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 CHANGED
@@ -100,7 +100,9 @@ homewizard.0.
100
100
  ├── measurement/ — Measurement data
101
101
  │ ├── power_w — Total power (number, W)
102
102
  │ ├── power_l1_w .. l3_w — Power per phase (number, W)
103
+ │ ├── voltage_v — Voltage single-phase (number, V)
103
104
  │ ├── voltage_l1_v .. l3_v — Voltage per phase (number, V)
105
+ │ ├── current_a — Current single-phase (number, A)
104
106
  │ ├── current_l1_a .. l3_a — Current per phase (number, A)
105
107
  │ ├── frequency_hz — Grid frequency (number, Hz)
106
108
  │ ├── energy_import_kwh — Total import (number, kWh)
@@ -108,6 +110,12 @@ homewizard.0.
108
110
  │ ├── energy_export_kwh — Total export (number, kWh)
109
111
  │ ├── energy_export_t1..t4_kwh — Export per tariff (number, kWh)
110
112
  │ ├── tariff — Active tariff (number)
113
+ │ ├── state_of_charge_pct — Battery charge level (number, %)
114
+ │ ├── cycles — Battery charge cycles (number)
115
+ │ ├── average_power_15m_w — 15-min average power (number, W, Belgium)
116
+ │ ├── monthly_power_peak_w — Monthly power peak (number, W, Belgium)
117
+ │ ├── meter_model — Meter model identifier (string)
118
+ │ ├── timestamp — Measurement timestamp (string)
111
119
  │ ├── quality/ — Power quality counters
112
120
  │ │ ├── voltage_sag_l1..l3_count
113
121
  │ │ ├── voltage_swell_l1..l3_count
@@ -135,7 +143,7 @@ homewizard.0.
135
143
  └── identify — Blink LED (button)
136
144
  ```
137
145
 
138
- > States are created dynamically based on what the device reports. Not all devices have all states.
146
+ > 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
147
 
140
148
  ---
141
149
 
@@ -158,6 +166,12 @@ homewizard.0.
158
166
 
159
167
  ## Changelog
160
168
 
169
+ ### 0.6.2 (2026-04-13)
170
+ - Fix hanging promise when response stream errors mid-transfer (`res.on("error")`)
171
+ - Fix onUnload: wrap in try/finally so callback always fires (prevents adapter hang on shutdown)
172
+ - Optimize state creation hot path: use `setObjectNotExistsAsync` instead of `extendObjectAsync` (~50 fewer object writes per second per device)
173
+ - Remove unnecessary `removeDeviceFromObject` wrapper (DRY)
174
+
161
175
  ### 0.6.1 (2026-04-12)
162
176
  - Code cleanup: extract testable connection-utils module (classifyError, createDeviceConnection)
163
177
  - Add 20 unit tests for error classification, connection factory, and unstable threshold
@@ -183,9 +197,6 @@ homewizard.0.
183
197
  ### 0.4.1 (2026-04-05)
184
198
  - Move measurement data into `measurement/` channel for cleaner object tree
185
199
 
186
- ### 0.4.0 (2026-04-05)
187
- - Add online/offline status icon for devices (`statusStates`)
188
-
189
200
  Older entries have been moved to [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
190
201
 
191
202
  ---
@@ -121,6 +121,7 @@ class HomeWizardClient {
121
121
  },
122
122
  (res) => {
123
123
  const chunks = [];
124
+ res.on("error", reject);
124
125
  res.on("data", (chunk) => chunks.push(chunk));
125
126
  res.on("end", () => {
126
127
  var _a;
@@ -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": "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",
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
  }
@@ -585,7 +585,7 @@ class StateManager {
585
585
  var _a;
586
586
  const prefix = this.devicePrefix(config);
587
587
  const mPrefix = `${prefix}.measurement`;
588
- await this.adapter.extendObjectAsync(mPrefix, {
588
+ await this.adapter.setObjectNotExistsAsync(mPrefix, {
589
589
  type: "channel",
590
590
  common: { name: "Measurement" },
591
591
  native: {}
@@ -605,14 +605,14 @@ class StateManager {
605
605
  }
606
606
  }
607
607
  if ((_a = data.external) == null ? void 0 : _a.length) {
608
- await this.adapter.extendObjectAsync(`${mPrefix}.external`, {
608
+ await this.adapter.setObjectNotExistsAsync(`${mPrefix}.external`, {
609
609
  type: "channel",
610
610
  common: { name: "External Meters" },
611
611
  native: {}
612
612
  });
613
613
  for (const ext of data.external) {
614
614
  const extId = `${mPrefix}.external.${sanitize(ext.type)}_${sanitize(ext.unique_id)}`;
615
- await this.adapter.extendObjectAsync(extId, {
615
+ await this.adapter.setObjectNotExistsAsync(extId, {
616
616
  type: "channel",
617
617
  common: { name: ext.type },
618
618
  native: {}
@@ -666,7 +666,7 @@ class StateManager {
666
666
  system.uptime_s,
667
667
  "s"
668
668
  );
669
- await this.adapter.extendObjectAsync(`${prefix}.system`, {
669
+ await this.adapter.setObjectNotExistsAsync(`${prefix}.system`, {
670
670
  type: "channel",
671
671
  common: { name: "System Settings" },
672
672
  native: {}
@@ -714,7 +714,7 @@ class StateManager {
714
714
  */
715
715
  async updateBattery(config, battery) {
716
716
  const prefix = this.devicePrefix(config);
717
- await this.adapter.extendObjectAsync(`${prefix}.battery`, {
717
+ await this.adapter.setObjectNotExistsAsync(`${prefix}.battery`, {
718
718
  type: "channel",
719
719
  common: { name: "Battery Control" },
720
720
  native: {}
@@ -859,7 +859,7 @@ class StateManager {
859
859
  if (unit) {
860
860
  common.unit = unit;
861
861
  }
862
- await this.adapter.extendObjectAsync(id, {
862
+ await this.adapter.setObjectNotExistsAsync(id, {
863
863
  type: "state",
864
864
  common,
865
865
  native: {}
@@ -872,7 +872,7 @@ class StateManager {
872
872
  * @param name State name
873
873
  */
874
874
  async createButton(id, name) {
875
- await this.adapter.extendObjectAsync(id, {
875
+ await this.adapter.setObjectNotExistsAsync(id, {
876
876
  type: "state",
877
877
  common: {
878
878
  name,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/state-manager.ts"],
4
- "sourcesContent": ["import type * as utils from \"@iobroker/adapter-core\";\nimport type {\n BatteryControl,\n DeviceConfig,\n Measurement,\n SystemInfo,\n} from \"./types\";\n\n/** Measurement field to state definition mapping */\ninterface MeasurementStateDef {\n /** Measurement field key */\n key: string;\n /** ioBroker state ID suffix */\n id: string;\n /** State display name */\n name: string;\n /** State value type */\n type: ioBroker.CommonType;\n /** ioBroker role */\n role: string;\n /** Unit string */\n unit?: string;\n}\n\n/**\n * Sanitize a string for use as ioBroker object ID (see adapter.FORBIDDEN_CHARS).\n *\n * @param str Raw string to sanitize\n */\nfunction sanitize(str: string): string {\n return str.replace(/[^a-zA-Z0-9_-]/g, \"_\").toLowerCase();\n}\n\nconst MEASUREMENT_STATE_DEFS: MeasurementStateDef[] = [\n // Power\n {\n key: \"power_w\",\n id: \"power_w\",\n name: \"Total power\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"power_l1_w\",\n id: \"power_l1_w\",\n name: \"Power L1\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"power_l2_w\",\n id: \"power_l2_w\",\n name: \"Power L2\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"power_l3_w\",\n id: \"power_l3_w\",\n name: \"Power L3\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n // Voltage\n {\n key: \"voltage_v\",\n id: \"voltage_v\",\n name: \"Voltage\",\n type: \"number\",\n role: \"value.voltage\",\n unit: \"V\",\n },\n {\n key: \"voltage_l1_v\",\n id: \"voltage_l1_v\",\n name: \"Voltage L1\",\n type: \"number\",\n role: \"value.voltage\",\n unit: \"V\",\n },\n {\n key: \"voltage_l2_v\",\n id: \"voltage_l2_v\",\n name: \"Voltage L2\",\n type: \"number\",\n role: \"value.voltage\",\n unit: \"V\",\n },\n {\n key: \"voltage_l3_v\",\n id: \"voltage_l3_v\",\n name: \"Voltage L3\",\n type: \"number\",\n role: \"value.voltage\",\n unit: \"V\",\n },\n // Current\n {\n key: \"current_a\",\n id: \"current_a\",\n name: \"Current\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"current_l1_a\",\n id: \"current_l1_a\",\n name: \"Current L1\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"current_l2_a\",\n id: \"current_l2_a\",\n name: \"Current L2\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"current_l3_a\",\n id: \"current_l3_a\",\n name: \"Current L3\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n // Frequency\n {\n key: \"frequency_hz\",\n id: \"frequency_hz\",\n name: \"Frequency\",\n type: \"number\",\n role: \"value\",\n unit: \"Hz\",\n },\n // Energy import\n {\n key: \"energy_import_kwh\",\n id: \"energy_import_kwh\",\n name: \"Energy import total\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t1_kwh\",\n id: \"energy_import_t1_kwh\",\n name: \"Energy import T1\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t2_kwh\",\n id: \"energy_import_t2_kwh\",\n name: \"Energy import T2\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t3_kwh\",\n id: \"energy_import_t3_kwh\",\n name: \"Energy import T3\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t4_kwh\",\n id: \"energy_import_t4_kwh\",\n name: \"Energy import T4\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n // Energy export\n {\n key: \"energy_export_kwh\",\n id: \"energy_export_kwh\",\n name: \"Energy export total\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t1_kwh\",\n id: \"energy_export_t1_kwh\",\n name: \"Energy export T1\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t2_kwh\",\n id: \"energy_export_t2_kwh\",\n name: \"Energy export T2\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t3_kwh\",\n id: \"energy_export_t3_kwh\",\n name: \"Energy export T3\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t4_kwh\",\n id: \"energy_export_t4_kwh\",\n name: \"Energy export T4\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n // Tariff\n {\n key: \"tariff\",\n id: \"tariff\",\n name: \"Active tariff\",\n type: \"number\",\n role: \"value\",\n },\n // Power quality\n {\n key: \"voltage_sag_l1_count\",\n id: \"quality.voltage_sag_l1_count\",\n name: \"Voltage sag L1\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_sag_l2_count\",\n id: \"quality.voltage_sag_l2_count\",\n name: \"Voltage sag L2\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_sag_l3_count\",\n id: \"quality.voltage_sag_l3_count\",\n name: \"Voltage sag L3\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_swell_l1_count\",\n id: \"quality.voltage_swell_l1_count\",\n name: \"Voltage swell L1\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_swell_l2_count\",\n id: \"quality.voltage_swell_l2_count\",\n name: \"Voltage swell L2\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_swell_l3_count\",\n id: \"quality.voltage_swell_l3_count\",\n name: \"Voltage swell L3\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"any_power_fail_count\",\n id: \"quality.power_fail_count\",\n name: \"Power fail count\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"long_power_fail_count\",\n id: \"quality.long_power_fail_count\",\n name: \"Long power fail count\",\n type: \"number\",\n role: \"value\",\n },\n // Capacity tariff (Belgium)\n {\n key: \"average_power_15m_w\",\n id: \"average_power_15m_w\",\n name: \"Average power 15min\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"monthly_power_peak_w\",\n id: \"monthly_power_peak_w\",\n name: \"Monthly power peak\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"monthly_power_peak_timestamp\",\n id: \"monthly_power_peak_timestamp\",\n name: \"Monthly power peak time\",\n type: \"string\",\n role: \"date\",\n },\n // kWh meter specifics\n {\n key: \"apparent_current_a\",\n id: \"apparent_current_a\",\n name: \"Apparent current\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_current_l1_a\",\n id: \"apparent_current_l1_a\",\n name: \"Apparent current L1\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_current_l2_a\",\n id: \"apparent_current_l2_a\",\n name: \"Apparent current L2\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_current_l3_a\",\n id: \"apparent_current_l3_a\",\n name: \"Apparent current L3\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_a\",\n id: \"reactive_current_a\",\n name: \"Reactive current\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_l1_a\",\n id: \"reactive_current_l1_a\",\n name: \"Reactive current L1\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_l2_a\",\n id: \"reactive_current_l2_a\",\n name: \"Reactive current L2\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_l3_a\",\n id: \"reactive_current_l3_a\",\n name: \"Reactive current L3\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_power_va\",\n id: \"apparent_power_va\",\n name: \"Apparent power\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"apparent_power_l1_va\",\n id: \"apparent_power_l1_va\",\n name: \"Apparent power L1\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"apparent_power_l2_va\",\n id: \"apparent_power_l2_va\",\n name: \"Apparent power L2\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"apparent_power_l3_va\",\n id: \"apparent_power_l3_va\",\n name: \"Apparent power L3\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"reactive_power_var\",\n id: \"reactive_power_var\",\n name: \"Reactive power\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"reactive_power_l1_var\",\n id: \"reactive_power_l1_var\",\n name: \"Reactive power L1\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"reactive_power_l2_var\",\n id: \"reactive_power_l2_var\",\n name: \"Reactive power L2\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"reactive_power_l3_var\",\n id: \"reactive_power_l3_var\",\n name: \"Reactive power L3\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"power_factor\",\n id: \"power_factor\",\n name: \"Power factor\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"power_factor_l1\",\n id: \"power_factor_l1\",\n name: \"Power factor L1\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"power_factor_l2\",\n id: \"power_factor_l2\",\n name: \"Power factor L2\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"power_factor_l3\",\n id: \"power_factor_l3\",\n name: \"Power factor L3\",\n type: \"number\",\n role: \"value\",\n },\n // Battery specifics\n {\n key: \"state_of_charge_pct\",\n id: \"state_of_charge_pct\",\n name: \"State of charge\",\n type: \"number\",\n role: \"value.battery\",\n unit: \"%\",\n },\n {\n key: \"cycles\",\n id: \"cycles\",\n name: \"Charge cycles\",\n type: \"number\",\n role: \"value\",\n },\n // Metadata\n {\n key: \"meter_model\",\n id: \"meter_model\",\n name: \"Meter model\",\n type: \"string\",\n role: \"text\",\n },\n {\n key: \"timestamp\",\n id: \"timestamp\",\n name: \"Measurement timestamp\",\n type: \"string\",\n role: \"date\",\n },\n];\n\n/** Manages ioBroker state creation and updates for HomeWizard devices */\nexport class StateManager {\n private readonly adapter: utils.AdapterInstance;\n\n /** @param adapter The ioBroker adapter instance */\n constructor(adapter: utils.AdapterInstance) {\n this.adapter = adapter;\n }\n\n /**\n * Create device channel and info states\n *\n * @param config Device configuration\n */\n async createDeviceStates(config: DeviceConfig): Promise<void> {\n const prefix = this.devicePrefix(config);\n\n await this.adapter.extendObjectAsync(prefix, {\n type: \"device\",\n common: {\n name: config.productName || config.productType,\n statusStates: {\n onlineId: `${this.adapter.namespace}.${prefix}.info.connected`,\n },\n } as ioBroker.DeviceCommon,\n native: {},\n });\n\n await this.adapter.extendObjectAsync(`${prefix}.info`, {\n type: \"channel\",\n common: { name: \"Device Information\" },\n native: {},\n });\n\n await this.createState(\n `${prefix}.info.productName`,\n \"Product name\",\n \"string\",\n \"text\",\n false,\n );\n await this.createState(\n `${prefix}.info.productType`,\n \"Product type\",\n \"string\",\n \"text\",\n false,\n );\n await this.createState(\n `${prefix}.info.firmware`,\n \"Firmware version\",\n \"string\",\n \"text\",\n false,\n );\n await this.createState(\n `${prefix}.info.connected`,\n \"Device connected\",\n \"boolean\",\n \"indicator.reachable\",\n false,\n );\n await this.createState(\n `${prefix}.info.wifi_rssi_db`,\n \"WiFi signal strength\",\n \"number\",\n \"value\",\n false,\n \"dB\",\n );\n await this.createState(\n `${prefix}.info.uptime_s`,\n \"Uptime\",\n \"number\",\n \"value\",\n false,\n \"s\",\n );\n\n // Remove device button\n await this.createButton(`${prefix}.remove`, \"Remove device\");\n\n // Set initial info values\n await this.adapter.setStateAsync(`${prefix}.info.productName`, {\n val: config.productName,\n ack: true,\n });\n await this.adapter.setStateAsync(`${prefix}.info.productType`, {\n val: config.productType,\n ack: true,\n });\n }\n\n /**\n * Update measurement states \u2014 only creates states that have values\n *\n * @param config Device configuration\n * @param data Measurement data\n */\n async updateMeasurement(\n config: DeviceConfig,\n data: Measurement,\n ): Promise<void> {\n const prefix = this.devicePrefix(config);\n const mPrefix = `${prefix}.measurement`;\n\n // Ensure measurement channel exists\n await this.adapter.extendObjectAsync(mPrefix, {\n type: \"channel\",\n common: { name: \"Measurement\" },\n native: {},\n });\n\n // Main measurement values\n const fields = MEASUREMENT_STATE_DEFS;\n for (const def of fields) {\n const rawValue = data[def.key as keyof Measurement];\n if (\n rawValue !== undefined &&\n rawValue !== null &&\n !Array.isArray(rawValue)\n ) {\n await this.ensureAndSet(\n `${mPrefix}.${def.id}`,\n def.name,\n def.type,\n def.role,\n rawValue as ioBroker.StateValue,\n def.unit,\n );\n }\n }\n\n // External meters (P1 gas/water/heat)\n if (data.external?.length) {\n await this.adapter.extendObjectAsync(`${mPrefix}.external`, {\n type: \"channel\",\n common: { name: \"External Meters\" },\n native: {},\n });\n\n for (const ext of data.external) {\n const extId = `${mPrefix}.external.${sanitize(ext.type)}_${sanitize(ext.unique_id)}`;\n await this.adapter.extendObjectAsync(extId, {\n type: \"channel\",\n common: { name: ext.type },\n native: {},\n });\n await this.ensureAndSet(\n `${extId}.value`,\n \"Value\",\n \"number\",\n \"value\",\n ext.value,\n ext.unit,\n );\n await this.ensureAndSet(\n `${extId}.unit`,\n \"Unit\",\n \"string\",\n \"text\",\n ext.unit,\n );\n await this.ensureAndSet(\n `${extId}.timestamp`,\n \"Timestamp\",\n \"string\",\n \"date\",\n ext.timestamp,\n );\n }\n }\n }\n\n /**\n * Update system states\n *\n * @param config Device configuration\n * @param system System info data\n */\n async updateSystem(config: DeviceConfig, system: SystemInfo): Promise<void> {\n const prefix = this.devicePrefix(config);\n\n // WiFi/uptime in info channel\n await this.ensureAndSet(\n `${prefix}.info.wifi_rssi_db`,\n \"WiFi signal strength\",\n \"number\",\n \"value\",\n system.wifi_rssi_db,\n \"dB\",\n );\n await this.ensureAndSet(\n `${prefix}.info.uptime_s`,\n \"Uptime\",\n \"number\",\n \"value\",\n system.uptime_s,\n \"s\",\n );\n\n // System control channel\n await this.adapter.extendObjectAsync(`${prefix}.system`, {\n type: \"channel\",\n common: { name: \"System Settings\" },\n native: {},\n });\n\n await this.ensureAndSet(\n `${prefix}.system.cloud_enabled`,\n \"Cloud enabled\",\n \"boolean\",\n \"switch\",\n system.cloud_enabled,\n undefined,\n true,\n );\n await this.ensureAndSet(\n `${prefix}.system.status_led_brightness_pct`,\n \"LED brightness\",\n \"number\",\n \"level\",\n system.status_led_brightness_pct,\n \"%\",\n true,\n );\n\n if (system.api_v1_enabled !== undefined) {\n await this.ensureAndSet(\n `${prefix}.system.api_v1_enabled`,\n \"API v1 enabled\",\n \"boolean\",\n \"switch\",\n system.api_v1_enabled,\n undefined,\n true,\n );\n }\n\n // Action buttons\n await this.createButton(`${prefix}.system.reboot`, \"Reboot device\");\n await this.createButton(\n `${prefix}.system.identify`,\n \"Identify (blink LED)\",\n );\n }\n\n /**\n * Update battery control states\n *\n * @param config Device configuration\n * @param battery Battery control data\n */\n async updateBattery(\n config: DeviceConfig,\n battery: BatteryControl,\n ): Promise<void> {\n const prefix = this.devicePrefix(config);\n\n await this.adapter.extendObjectAsync(`${prefix}.battery`, {\n type: \"channel\",\n common: { name: \"Battery Control\" },\n native: {},\n });\n\n await this.ensureAndSet(\n `${prefix}.battery.mode`,\n \"Battery mode\",\n \"string\",\n \"text\",\n battery.mode,\n undefined,\n true,\n );\n if (battery.permissions !== undefined) {\n await this.ensureAndSet(\n `${prefix}.battery.permissions`,\n \"Battery permissions\",\n \"string\",\n \"json\",\n JSON.stringify(battery.permissions),\n undefined,\n true,\n );\n }\n if (battery.battery_count !== undefined) {\n await this.ensureAndSet(\n `${prefix}.battery.battery_count`,\n \"Connected batteries\",\n \"number\",\n \"value\",\n battery.battery_count,\n );\n }\n if (battery.power_w !== undefined) {\n await this.ensureAndSet(\n `${prefix}.battery.power_w`,\n \"Battery power\",\n \"number\",\n \"value.power\",\n battery.power_w,\n \"W\",\n );\n }\n if (battery.target_power_w !== undefined) {\n await this.ensureAndSet(\n `${prefix}.battery.target_power_w`,\n \"Target power\",\n \"number\",\n \"value.power\",\n battery.target_power_w,\n \"W\",\n );\n }\n if (battery.max_consumption_w !== undefined) {\n await this.ensureAndSet(\n `${prefix}.battery.max_consumption_w`,\n \"Max consumption\",\n \"number\",\n \"value.power\",\n battery.max_consumption_w,\n \"W\",\n );\n }\n if (battery.max_production_w !== undefined) {\n await this.ensureAndSet(\n `${prefix}.battery.max_production_w`,\n \"Max production\",\n \"number\",\n \"value.power\",\n battery.max_production_w,\n \"W\",\n );\n }\n }\n\n /**\n * Set device connected state\n *\n * @param config Device configuration\n * @param connected Connection status\n */\n async setDeviceConnected(\n config: DeviceConfig,\n connected: boolean,\n ): Promise<void> {\n const prefix = this.devicePrefix(config);\n await this.adapter.setStateAsync(`${prefix}.info.connected`, {\n val: connected,\n ack: true,\n });\n }\n\n /**\n * Remove all states for a device\n *\n * @param config Device configuration\n */\n async removeDevice(config: DeviceConfig): Promise<void> {\n const prefix = this.devicePrefix(config);\n await this.adapter.delObjectAsync(prefix, { recursive: true });\n }\n\n /**\n * Remove measurement states from old locations (pre-v0.4.0: device root instead of measurement/ channel)\n *\n * @param config Device configuration\n */\n async cleanupMovedStates(config: DeviceConfig): Promise<void> {\n const prefix = this.devicePrefix(config);\n\n // Old paths: states were at device root, now under measurement/\n const oldIds: string[] = [];\n for (const def of MEASUREMENT_STATE_DEFS) {\n oldIds.push(`${prefix}.${def.id}`);\n }\n // External was at device root too\n oldIds.push(`${prefix}.external`);\n\n for (const id of oldIds) {\n if (await this.adapter.getObjectAsync(id)) {\n await this.adapter.delObjectAsync(id, { recursive: true });\n this.adapter.log.debug(`Removed obsolete state: ${id}`);\n }\n }\n }\n\n /**\n * Get device object ID prefix\n *\n * @param config Device configuration\n */\n devicePrefix(config: DeviceConfig): string {\n return `${sanitize(config.productType)}_${sanitize(config.serial)}`;\n }\n\n /**\n * Create a state if it doesn't exist\n *\n * @param id State ID\n * @param name State name\n * @param type Value type\n * @param role ioBroker role\n * @param write Whether state is writable\n * @param unit Optional unit\n */\n private async createState(\n id: string,\n name: string,\n type: ioBroker.CommonType,\n role: string,\n write: boolean,\n unit?: string,\n ): Promise<void> {\n const common: Partial<ioBroker.StateCommon> = {\n name,\n type,\n role,\n read: true,\n write,\n };\n if (unit) {\n common.unit = unit;\n }\n await this.adapter.extendObjectAsync(id, {\n type: \"state\",\n common: common as ioBroker.StateCommon,\n native: {},\n });\n }\n\n /**\n * Create a button state (read: false, write: true) with initial value false\n *\n * @param id State ID\n * @param name State name\n */\n private async createButton(id: string, name: string): Promise<void> {\n await this.adapter.extendObjectAsync(id, {\n type: \"state\",\n common: {\n name,\n type: \"boolean\",\n role: \"button\",\n read: false,\n write: true,\n } as ioBroker.StateCommon,\n native: {},\n });\n await this.adapter.setStateAsync(id, { val: false, ack: true });\n }\n\n /**\n * Ensure state exists and set value\n *\n * @param id State ID\n * @param name State name\n * @param type Value type\n * @param role ioBroker role\n * @param value State value\n * @param unit Optional unit\n * @param write Whether state is writable\n */\n private async ensureAndSet(\n id: string,\n name: string,\n type: ioBroker.CommonType,\n role: string,\n value: ioBroker.StateValue,\n unit?: string,\n write?: boolean,\n ): Promise<void> {\n await this.createState(id, name, type, role, write ?? false, unit);\n await this.adapter.setStateAsync(id, { val: value, ack: true });\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type * as utils from \"@iobroker/adapter-core\";\nimport type {\n BatteryControl,\n DeviceConfig,\n Measurement,\n SystemInfo,\n} from \"./types\";\n\n/** Measurement field to state definition mapping */\ninterface MeasurementStateDef {\n /** Measurement field key */\n key: string;\n /** ioBroker state ID suffix */\n id: string;\n /** State display name */\n name: string;\n /** State value type */\n type: ioBroker.CommonType;\n /** ioBroker role */\n role: string;\n /** Unit string */\n unit?: string;\n}\n\n/**\n * Sanitize a string for use as ioBroker object ID (see adapter.FORBIDDEN_CHARS).\n *\n * @param str Raw string to sanitize\n */\nfunction sanitize(str: string): string {\n return str.replace(/[^a-zA-Z0-9_-]/g, \"_\").toLowerCase();\n}\n\nconst MEASUREMENT_STATE_DEFS: MeasurementStateDef[] = [\n // Power\n {\n key: \"power_w\",\n id: \"power_w\",\n name: \"Total power\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"power_l1_w\",\n id: \"power_l1_w\",\n name: \"Power L1\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"power_l2_w\",\n id: \"power_l2_w\",\n name: \"Power L2\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"power_l3_w\",\n id: \"power_l3_w\",\n name: \"Power L3\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n // Voltage\n {\n key: \"voltage_v\",\n id: \"voltage_v\",\n name: \"Voltage\",\n type: \"number\",\n role: \"value.voltage\",\n unit: \"V\",\n },\n {\n key: \"voltage_l1_v\",\n id: \"voltage_l1_v\",\n name: \"Voltage L1\",\n type: \"number\",\n role: \"value.voltage\",\n unit: \"V\",\n },\n {\n key: \"voltage_l2_v\",\n id: \"voltage_l2_v\",\n name: \"Voltage L2\",\n type: \"number\",\n role: \"value.voltage\",\n unit: \"V\",\n },\n {\n key: \"voltage_l3_v\",\n id: \"voltage_l3_v\",\n name: \"Voltage L3\",\n type: \"number\",\n role: \"value.voltage\",\n unit: \"V\",\n },\n // Current\n {\n key: \"current_a\",\n id: \"current_a\",\n name: \"Current\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"current_l1_a\",\n id: \"current_l1_a\",\n name: \"Current L1\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"current_l2_a\",\n id: \"current_l2_a\",\n name: \"Current L2\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"current_l3_a\",\n id: \"current_l3_a\",\n name: \"Current L3\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n // Frequency\n {\n key: \"frequency_hz\",\n id: \"frequency_hz\",\n name: \"Frequency\",\n type: \"number\",\n role: \"value\",\n unit: \"Hz\",\n },\n // Energy import\n {\n key: \"energy_import_kwh\",\n id: \"energy_import_kwh\",\n name: \"Energy import total\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t1_kwh\",\n id: \"energy_import_t1_kwh\",\n name: \"Energy import T1\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t2_kwh\",\n id: \"energy_import_t2_kwh\",\n name: \"Energy import T2\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t3_kwh\",\n id: \"energy_import_t3_kwh\",\n name: \"Energy import T3\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_import_t4_kwh\",\n id: \"energy_import_t4_kwh\",\n name: \"Energy import T4\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n // Energy export\n {\n key: \"energy_export_kwh\",\n id: \"energy_export_kwh\",\n name: \"Energy export total\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t1_kwh\",\n id: \"energy_export_t1_kwh\",\n name: \"Energy export T1\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t2_kwh\",\n id: \"energy_export_t2_kwh\",\n name: \"Energy export T2\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t3_kwh\",\n id: \"energy_export_t3_kwh\",\n name: \"Energy export T3\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n {\n key: \"energy_export_t4_kwh\",\n id: \"energy_export_t4_kwh\",\n name: \"Energy export T4\",\n type: \"number\",\n role: \"value.energy\",\n unit: \"kWh\",\n },\n // Tariff\n {\n key: \"tariff\",\n id: \"tariff\",\n name: \"Active tariff\",\n type: \"number\",\n role: \"value\",\n },\n // Power quality\n {\n key: \"voltage_sag_l1_count\",\n id: \"quality.voltage_sag_l1_count\",\n name: \"Voltage sag L1\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_sag_l2_count\",\n id: \"quality.voltage_sag_l2_count\",\n name: \"Voltage sag L2\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_sag_l3_count\",\n id: \"quality.voltage_sag_l3_count\",\n name: \"Voltage sag L3\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_swell_l1_count\",\n id: \"quality.voltage_swell_l1_count\",\n name: \"Voltage swell L1\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_swell_l2_count\",\n id: \"quality.voltage_swell_l2_count\",\n name: \"Voltage swell L2\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"voltage_swell_l3_count\",\n id: \"quality.voltage_swell_l3_count\",\n name: \"Voltage swell L3\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"any_power_fail_count\",\n id: \"quality.power_fail_count\",\n name: \"Power fail count\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"long_power_fail_count\",\n id: \"quality.long_power_fail_count\",\n name: \"Long power fail count\",\n type: \"number\",\n role: \"value\",\n },\n // Capacity tariff (Belgium)\n {\n key: \"average_power_15m_w\",\n id: \"average_power_15m_w\",\n name: \"Average power 15min\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"monthly_power_peak_w\",\n id: \"monthly_power_peak_w\",\n name: \"Monthly power peak\",\n type: \"number\",\n role: \"value.power\",\n unit: \"W\",\n },\n {\n key: \"monthly_power_peak_timestamp\",\n id: \"monthly_power_peak_timestamp\",\n name: \"Monthly power peak time\",\n type: \"string\",\n role: \"date\",\n },\n // kWh meter specifics\n {\n key: \"apparent_current_a\",\n id: \"apparent_current_a\",\n name: \"Apparent current\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_current_l1_a\",\n id: \"apparent_current_l1_a\",\n name: \"Apparent current L1\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_current_l2_a\",\n id: \"apparent_current_l2_a\",\n name: \"Apparent current L2\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_current_l3_a\",\n id: \"apparent_current_l3_a\",\n name: \"Apparent current L3\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_a\",\n id: \"reactive_current_a\",\n name: \"Reactive current\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_l1_a\",\n id: \"reactive_current_l1_a\",\n name: \"Reactive current L1\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_l2_a\",\n id: \"reactive_current_l2_a\",\n name: \"Reactive current L2\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"reactive_current_l3_a\",\n id: \"reactive_current_l3_a\",\n name: \"Reactive current L3\",\n type: \"number\",\n role: \"value.current\",\n unit: \"A\",\n },\n {\n key: \"apparent_power_va\",\n id: \"apparent_power_va\",\n name: \"Apparent power\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"apparent_power_l1_va\",\n id: \"apparent_power_l1_va\",\n name: \"Apparent power L1\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"apparent_power_l2_va\",\n id: \"apparent_power_l2_va\",\n name: \"Apparent power L2\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"apparent_power_l3_va\",\n id: \"apparent_power_l3_va\",\n name: \"Apparent power L3\",\n type: \"number\",\n role: \"value.power\",\n unit: \"VA\",\n },\n {\n key: \"reactive_power_var\",\n id: \"reactive_power_var\",\n name: \"Reactive power\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"reactive_power_l1_var\",\n id: \"reactive_power_l1_var\",\n name: \"Reactive power L1\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"reactive_power_l2_var\",\n id: \"reactive_power_l2_var\",\n name: \"Reactive power L2\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"reactive_power_l3_var\",\n id: \"reactive_power_l3_var\",\n name: \"Reactive power L3\",\n type: \"number\",\n role: \"value.power\",\n unit: \"var\",\n },\n {\n key: \"power_factor\",\n id: \"power_factor\",\n name: \"Power factor\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"power_factor_l1\",\n id: \"power_factor_l1\",\n name: \"Power factor L1\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"power_factor_l2\",\n id: \"power_factor_l2\",\n name: \"Power factor L2\",\n type: \"number\",\n role: \"value\",\n },\n {\n key: \"power_factor_l3\",\n id: \"power_factor_l3\",\n name: \"Power factor L3\",\n type: \"number\",\n role: \"value\",\n },\n // Battery specifics\n {\n key: \"state_of_charge_pct\",\n id: \"state_of_charge_pct\",\n name: \"State of charge\",\n type: \"number\",\n role: \"value.battery\",\n unit: \"%\",\n },\n {\n key: \"cycles\",\n id: \"cycles\",\n name: \"Charge cycles\",\n type: \"number\",\n role: \"value\",\n },\n // Metadata\n {\n key: \"meter_model\",\n id: \"meter_model\",\n name: \"Meter model\",\n type: \"string\",\n role: \"text\",\n },\n {\n key: \"timestamp\",\n id: \"timestamp\",\n name: \"Measurement timestamp\",\n type: \"string\",\n role: \"date\",\n },\n];\n\n/** Manages ioBroker state creation and updates for HomeWizard devices */\nexport class StateManager {\n private readonly adapter: utils.AdapterInstance;\n\n /** @param adapter The ioBroker adapter instance */\n constructor(adapter: utils.AdapterInstance) {\n this.adapter = adapter;\n }\n\n /**\n * Create device channel and info states\n *\n * @param config Device configuration\n */\n async createDeviceStates(config: DeviceConfig): Promise<void> {\n const prefix = this.devicePrefix(config);\n\n await this.adapter.extendObjectAsync(prefix, {\n type: \"device\",\n common: {\n name: config.productName || config.productType,\n statusStates: {\n onlineId: `${this.adapter.namespace}.${prefix}.info.connected`,\n },\n } as ioBroker.DeviceCommon,\n native: {},\n });\n\n await this.adapter.extendObjectAsync(`${prefix}.info`, {\n type: \"channel\",\n common: { name: \"Device Information\" },\n native: {},\n });\n\n await this.createState(\n `${prefix}.info.productName`,\n \"Product name\",\n \"string\",\n \"text\",\n false,\n );\n await this.createState(\n `${prefix}.info.productType`,\n \"Product type\",\n \"string\",\n \"text\",\n false,\n );\n await this.createState(\n `${prefix}.info.firmware`,\n \"Firmware version\",\n \"string\",\n \"text\",\n false,\n );\n await this.createState(\n `${prefix}.info.connected`,\n \"Device connected\",\n \"boolean\",\n \"indicator.reachable\",\n false,\n );\n await this.createState(\n `${prefix}.info.wifi_rssi_db`,\n \"WiFi signal strength\",\n \"number\",\n \"value\",\n false,\n \"dB\",\n );\n await this.createState(\n `${prefix}.info.uptime_s`,\n \"Uptime\",\n \"number\",\n \"value\",\n false,\n \"s\",\n );\n\n // Remove device button\n await this.createButton(`${prefix}.remove`, \"Remove device\");\n\n // Set initial info values\n await this.adapter.setStateAsync(`${prefix}.info.productName`, {\n val: config.productName,\n ack: true,\n });\n await this.adapter.setStateAsync(`${prefix}.info.productType`, {\n val: config.productType,\n ack: true,\n });\n }\n\n /**\n * Update measurement states \u2014 only creates states that have values\n *\n * @param config Device configuration\n * @param data Measurement data\n */\n async updateMeasurement(\n config: DeviceConfig,\n data: Measurement,\n ): Promise<void> {\n const prefix = this.devicePrefix(config);\n const mPrefix = `${prefix}.measurement`;\n\n // Ensure measurement channel exists\n await this.adapter.setObjectNotExistsAsync(mPrefix, {\n type: \"channel\",\n common: { name: \"Measurement\" },\n native: {},\n });\n\n // Main measurement values\n const fields = MEASUREMENT_STATE_DEFS;\n for (const def of fields) {\n const rawValue = data[def.key as keyof Measurement];\n if (\n rawValue !== undefined &&\n rawValue !== null &&\n !Array.isArray(rawValue)\n ) {\n await this.ensureAndSet(\n `${mPrefix}.${def.id}`,\n def.name,\n def.type,\n def.role,\n rawValue as ioBroker.StateValue,\n def.unit,\n );\n }\n }\n\n // External meters (P1 gas/water/heat)\n if (data.external?.length) {\n await this.adapter.setObjectNotExistsAsync(`${mPrefix}.external`, {\n type: \"channel\",\n common: { name: \"External Meters\" },\n native: {},\n });\n\n for (const ext of data.external) {\n const extId = `${mPrefix}.external.${sanitize(ext.type)}_${sanitize(ext.unique_id)}`;\n await this.adapter.setObjectNotExistsAsync(extId, {\n type: \"channel\",\n common: { name: ext.type },\n native: {},\n });\n await this.ensureAndSet(\n `${extId}.value`,\n \"Value\",\n \"number\",\n \"value\",\n ext.value,\n ext.unit,\n );\n await this.ensureAndSet(\n `${extId}.unit`,\n \"Unit\",\n \"string\",\n \"text\",\n ext.unit,\n );\n await this.ensureAndSet(\n `${extId}.timestamp`,\n \"Timestamp\",\n \"string\",\n \"date\",\n ext.timestamp,\n );\n }\n }\n }\n\n /**\n * Update system states\n *\n * @param config Device configuration\n * @param system System info data\n */\n async updateSystem(config: DeviceConfig, system: SystemInfo): Promise<void> {\n const prefix = this.devicePrefix(config);\n\n // WiFi/uptime in info channel\n await this.ensureAndSet(\n `${prefix}.info.wifi_rssi_db`,\n \"WiFi signal strength\",\n \"number\",\n \"value\",\n system.wifi_rssi_db,\n \"dB\",\n );\n await this.ensureAndSet(\n `${prefix}.info.uptime_s`,\n \"Uptime\",\n \"number\",\n \"value\",\n system.uptime_s,\n \"s\",\n );\n\n // System control channel\n await this.adapter.setObjectNotExistsAsync(`${prefix}.system`, {\n type: \"channel\",\n common: { name: \"System Settings\" },\n native: {},\n });\n\n await this.ensureAndSet(\n `${prefix}.system.cloud_enabled`,\n \"Cloud enabled\",\n \"boolean\",\n \"switch\",\n system.cloud_enabled,\n undefined,\n true,\n );\n await this.ensureAndSet(\n `${prefix}.system.status_led_brightness_pct`,\n \"LED brightness\",\n \"number\",\n \"level\",\n system.status_led_brightness_pct,\n \"%\",\n true,\n );\n\n if (system.api_v1_enabled !== undefined) {\n await this.ensureAndSet(\n `${prefix}.system.api_v1_enabled`,\n \"API v1 enabled\",\n \"boolean\",\n \"switch\",\n system.api_v1_enabled,\n undefined,\n true,\n );\n }\n\n // Action buttons\n await this.createButton(`${prefix}.system.reboot`, \"Reboot device\");\n await this.createButton(\n `${prefix}.system.identify`,\n \"Identify (blink LED)\",\n );\n }\n\n /**\n * Update battery control states\n *\n * @param config Device configuration\n * @param battery Battery control data\n */\n async updateBattery(\n config: DeviceConfig,\n battery: BatteryControl,\n ): Promise<void> {\n const prefix = this.devicePrefix(config);\n\n await this.adapter.setObjectNotExistsAsync(`${prefix}.battery`, {\n type: \"channel\",\n common: { name: \"Battery Control\" },\n native: {},\n });\n\n await this.ensureAndSet(\n `${prefix}.battery.mode`,\n \"Battery mode\",\n \"string\",\n \"text\",\n battery.mode,\n undefined,\n true,\n );\n if (battery.permissions !== undefined) {\n await this.ensureAndSet(\n `${prefix}.battery.permissions`,\n \"Battery permissions\",\n \"string\",\n \"json\",\n JSON.stringify(battery.permissions),\n undefined,\n true,\n );\n }\n if (battery.battery_count !== undefined) {\n await this.ensureAndSet(\n `${prefix}.battery.battery_count`,\n \"Connected batteries\",\n \"number\",\n \"value\",\n battery.battery_count,\n );\n }\n if (battery.power_w !== undefined) {\n await this.ensureAndSet(\n `${prefix}.battery.power_w`,\n \"Battery power\",\n \"number\",\n \"value.power\",\n battery.power_w,\n \"W\",\n );\n }\n if (battery.target_power_w !== undefined) {\n await this.ensureAndSet(\n `${prefix}.battery.target_power_w`,\n \"Target power\",\n \"number\",\n \"value.power\",\n battery.target_power_w,\n \"W\",\n );\n }\n if (battery.max_consumption_w !== undefined) {\n await this.ensureAndSet(\n `${prefix}.battery.max_consumption_w`,\n \"Max consumption\",\n \"number\",\n \"value.power\",\n battery.max_consumption_w,\n \"W\",\n );\n }\n if (battery.max_production_w !== undefined) {\n await this.ensureAndSet(\n `${prefix}.battery.max_production_w`,\n \"Max production\",\n \"number\",\n \"value.power\",\n battery.max_production_w,\n \"W\",\n );\n }\n }\n\n /**\n * Set device connected state\n *\n * @param config Device configuration\n * @param connected Connection status\n */\n async setDeviceConnected(\n config: DeviceConfig,\n connected: boolean,\n ): Promise<void> {\n const prefix = this.devicePrefix(config);\n await this.adapter.setStateAsync(`${prefix}.info.connected`, {\n val: connected,\n ack: true,\n });\n }\n\n /**\n * Remove all states for a device\n *\n * @param config Device configuration\n */\n async removeDevice(config: DeviceConfig): Promise<void> {\n const prefix = this.devicePrefix(config);\n await this.adapter.delObjectAsync(prefix, { recursive: true });\n }\n\n /**\n * Remove measurement states from old locations (pre-v0.4.0: device root instead of measurement/ channel)\n *\n * @param config Device configuration\n */\n async cleanupMovedStates(config: DeviceConfig): Promise<void> {\n const prefix = this.devicePrefix(config);\n\n // Old paths: states were at device root, now under measurement/\n const oldIds: string[] = [];\n for (const def of MEASUREMENT_STATE_DEFS) {\n oldIds.push(`${prefix}.${def.id}`);\n }\n // External was at device root too\n oldIds.push(`${prefix}.external`);\n\n for (const id of oldIds) {\n if (await this.adapter.getObjectAsync(id)) {\n await this.adapter.delObjectAsync(id, { recursive: true });\n this.adapter.log.debug(`Removed obsolete state: ${id}`);\n }\n }\n }\n\n /**\n * Get device object ID prefix\n *\n * @param config Device configuration\n */\n devicePrefix(config: DeviceConfig): string {\n return `${sanitize(config.productType)}_${sanitize(config.serial)}`;\n }\n\n /**\n * Create a state if it doesn't exist\n *\n * @param id State ID\n * @param name State name\n * @param type Value type\n * @param role ioBroker role\n * @param write Whether state is writable\n * @param unit Optional unit\n */\n private async createState(\n id: string,\n name: string,\n type: ioBroker.CommonType,\n role: string,\n write: boolean,\n unit?: string,\n ): Promise<void> {\n const common: Partial<ioBroker.StateCommon> = {\n name,\n type,\n role,\n read: true,\n write,\n };\n if (unit) {\n common.unit = unit;\n }\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: common as ioBroker.StateCommon,\n native: {},\n });\n }\n\n /**\n * Create a button state (read: false, write: true) with initial value false\n *\n * @param id State ID\n * @param name State name\n */\n private async createButton(id: string, name: string): Promise<void> {\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: {\n name,\n type: \"boolean\",\n role: \"button\",\n read: false,\n write: true,\n } as ioBroker.StateCommon,\n native: {},\n });\n await this.adapter.setStateAsync(id, { val: false, ack: true });\n }\n\n /**\n * Ensure state exists and set value\n *\n * @param id State ID\n * @param name State name\n * @param type Value type\n * @param role ioBroker role\n * @param value State value\n * @param unit Optional unit\n * @param write Whether state is writable\n */\n private async ensureAndSet(\n id: string,\n name: string,\n type: ioBroker.CommonType,\n role: string,\n value: ioBroker.StateValue,\n unit?: string,\n write?: boolean,\n ): Promise<void> {\n await this.createState(id, name, type, role, write ?? false, unit);\n await this.adapter.setStateAsync(id, { val: value, ack: true });\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/build/main.js CHANGED
@@ -168,14 +168,6 @@ class HomeWizard extends utils.Adapter {
168
168
  }
169
169
  });
170
170
  }
171
- /**
172
- * Remove device config from its device object
173
- *
174
- * @param config Device configuration to remove
175
- */
176
- async removeDeviceFromObject(config) {
177
- await this.stateManager.removeDevice(config);
178
- }
179
171
  /**
180
172
  * Handle a discovered device from mDNS (only active during pairing)
181
173
  *
@@ -203,31 +195,34 @@ class HomeWizard extends utils.Adapter {
203
195
  */
204
196
  onUnload(callback) {
205
197
  var _a, _b;
206
- if (this.pairingTimer) {
207
- this.clearTimeout(this.pairingTimer);
208
- }
209
- if (this.pairingPollTimer) {
210
- this.clearInterval(this.pairingPollTimer);
211
- }
212
- if (this.systemPollTimer) {
213
- this.clearInterval(this.systemPollTimer);
214
- }
215
- if (this.ipRecoveryTimer) {
216
- this.clearTimeout(this.ipRecoveryTimer);
217
- }
218
- (_a = this.discovery) == null ? void 0 : _a.stop();
219
- for (const conn of this.connections.values()) {
220
- (_b = conn.wsClient) == null ? void 0 : _b.close();
221
- if (conn.pollTimer) {
222
- this.clearInterval(conn.pollTimer);
198
+ try {
199
+ if (this.pairingTimer) {
200
+ this.clearTimeout(this.pairingTimer);
201
+ }
202
+ if (this.pairingPollTimer) {
203
+ this.clearInterval(this.pairingPollTimer);
223
204
  }
224
- if (conn.reconnectTimer) {
225
- this.clearTimeout(conn.reconnectTimer);
205
+ if (this.systemPollTimer) {
206
+ this.clearInterval(this.systemPollTimer);
207
+ }
208
+ if (this.ipRecoveryTimer) {
209
+ this.clearTimeout(this.ipRecoveryTimer);
210
+ }
211
+ (_a = this.discovery) == null ? void 0 : _a.stop();
212
+ for (const conn of this.connections.values()) {
213
+ (_b = conn.wsClient) == null ? void 0 : _b.close();
214
+ if (conn.pollTimer) {
215
+ this.clearInterval(conn.pollTimer);
216
+ }
217
+ if (conn.reconnectTimer) {
218
+ this.clearTimeout(conn.reconnectTimer);
219
+ }
226
220
  }
221
+ this.connections.clear();
222
+ void this.setState("info.connection", { val: false, ack: true });
223
+ } finally {
224
+ callback();
227
225
  }
228
- this.connections.clear();
229
- void this.setState("info.connection", { val: false, ack: true });
230
- callback();
231
226
  }
232
227
  /**
233
228
  * Handle state changes
@@ -666,7 +661,7 @@ class HomeWizard extends utils.Adapter {
666
661
  this.clearTimeout(conn.reconnectTimer);
667
662
  }
668
663
  this.connections.delete(key);
669
- await this.removeDeviceFromObject(conn.config);
664
+ await this.stateManager.removeDevice(conn.config);
670
665
  this.updateGlobalConnection();
671
666
  }
672
667
  /**
package/build/main.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/main.ts"],
4
- "sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport {\n classifyError,\n createDeviceConnection,\n UNSTABLE_DISCONNECT_THRESHOLD,\n} from \"./lib/connection-utils\";\nimport { HomeWizardDiscovery } from \"./lib/discovery\";\nimport { HomeWizardApiError, HomeWizardClient } from \"./lib/homewizard-client\";\nimport { StateManager } from \"./lib/state-manager\";\nimport type {\n DeviceConfig,\n DeviceConnection,\n DiscoveredDevice,\n Measurement,\n} from \"./lib/types\";\nimport { HomeWizardWebSocket } from \"./lib/websocket-client\";\n\n/** Pairing timeout in milliseconds (60 seconds) */\nconst PAIRING_TIMEOUT_MS = 60_000;\n/** Pairing poll interval in milliseconds */\nconst PAIRING_POLL_MS = 2_000;\n/** WebSocket reconnect base delay in milliseconds */\nconst WS_RECONNECT_BASE_MS = 5_000;\n/** Maximum WebSocket reconnect delay in milliseconds */\nconst WS_RECONNECT_MAX_MS = 300_000;\n/** REST fallback poll interval in milliseconds */\nconst REST_POLL_MS = 10_000;\n/** System info poll interval in milliseconds */\nconst SYSTEM_POLL_MS = 60_000;\n/** Max auth failures before giving up */\nconst MAX_AUTH_FAILURES = 3;\n/** WS failures before starting mDNS IP recovery */\nconst WS_FAILURES_BEFORE_MDNS = 3;\n/** mDNS IP recovery timeout in milliseconds */\nconst IP_RECOVERY_TIMEOUT_MS = 60_000;\n/** Retry mDNS every N WS failures after first attempt (~1 hour at 5 min cap) */\nconst MDNS_RETRY_EVERY = 12;\n/** Connection must last this long to count as \"stable\" */\nconst STABLE_THRESHOLD_MS = 600_000;\n/** Max reconnect delay for unstable devices */\nconst WS_RECONNECT_MAX_UNSTABLE_MS = 60_000;\n/** REST fallback interval for unstable devices (slower, not stopped) */\nconst REST_POLL_UNSTABLE_MS = 30_000;\n\nclass HomeWizard extends utils.Adapter {\n private stateManager!: StateManager;\n private discovery: HomeWizardDiscovery | null = null;\n private readonly connections = new Map<string, DeviceConnection>();\n private pairingTimer: ioBroker.Timeout | undefined = undefined;\n private pairingPollTimer: ioBroker.Interval | undefined = undefined;\n private systemPollTimer: ioBroker.Interval | undefined = undefined;\n private ipRecoveryTimer: ioBroker.Timeout | undefined = undefined;\n private isPairing = false;\n private pairingManualIp = \"\";\n private discoveredDuringPairing: DiscoveredDevice[] = [];\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"homewizard\" });\n this.on(\"ready\", () => this.onReady());\n this.on(\"stateChange\", (id, state) => this.onStateChange(id, state));\n this.on(\"unload\", (callback) => this.onUnload(callback));\n }\n\n /** Adapter started */\n private async onReady(): Promise<void> {\n this.stateManager = new StateManager(this);\n\n // Create pairing states\n await this.extendObjectAsync(\"pairingIp\", {\n type: \"state\",\n common: {\n name: \"Device IP for manual pairing\",\n type: \"string\",\n role: \"text\",\n read: true,\n write: true,\n def: \"\",\n },\n native: {},\n });\n\n // Reset pairing states on start (in case previous run was killed mid-pairing)\n await this.setStateAsync(\"startPairing\", { val: false, ack: true });\n await this.setStateAsync(\"pairingIp\", { val: \"\", ack: true });\n\n // Subscribe to pairing button and writable device states\n await this.subscribeStatesAsync(\"startPairing\");\n await this.subscribeStatesAsync(\"*.system.reboot\");\n await this.subscribeStatesAsync(\"*.system.identify\");\n await this.subscribeStatesAsync(\"*.system.cloud_enabled\");\n await this.subscribeStatesAsync(\"*.system.status_led_brightness_pct\");\n await this.subscribeStatesAsync(\"*.system.api_v1_enabled\");\n await this.subscribeStatesAsync(\"*.battery.mode\");\n await this.subscribeStatesAsync(\"*.battery.permissions\");\n await this.subscribeStatesAsync(\"*.remove\");\n\n // Load devices from device objects (not from adapter config)\n const devices = await this.loadDevicesFromObjects();\n if (devices.length === 0) {\n this.log.info(\n \"No devices configured \u2014 set 'startPairing' to true to add a device\",\n );\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n }\n\n // Create connection entries for all configured devices\n for (const device of devices) {\n const key = this.stateManager.devicePrefix(device);\n await this.stateManager.cleanupMovedStates(device);\n await this.stateManager.createDeviceStates(device);\n const conn = createDeviceConnection(device, device.ip || \"\");\n this.connections.set(key, conn);\n\n // If we have a stored IP, connect immediately\n if (conn.ip) {\n this.log.debug(`Using stored IP ${conn.ip} for ${device.productName}`);\n void this.initDevice(conn);\n }\n }\n\n // Periodic system info poll\n this.systemPollTimer = this.setInterval(() => {\n void this.pollAllSystemInfo();\n }, SYSTEM_POLL_MS);\n\n this.updateGlobalConnection();\n }\n\n /**\n * Load device configs from existing device objects\n * Tokens are stored encrypted in device object native\n */\n private async loadDevicesFromObjects(): Promise<DeviceConfig[]> {\n const devices: DeviceConfig[] = [];\n\n // Also migrate from old adapter config if devices exist there\n const oldDevices: DeviceConfig[] =\n ((this.config as Record<string, unknown>).devices as DeviceConfig[]) ||\n [];\n if (oldDevices.length > 0) {\n this.log.debug(\n `Migrating ${oldDevices.length} device(s) from adapter config to device objects`,\n );\n for (const device of oldDevices) {\n await this.saveDeviceToObject(device);\n }\n // Clear old config (this triggers one restart, but only during migration)\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, {\n native: { devices: [] },\n });\n return oldDevices;\n }\n\n // Read device objects from our namespace\n const objects = await this.getAdapterObjectsAsync();\n for (const [id, obj] of Object.entries(objects)) {\n if (obj.type !== \"device\") {\n continue;\n }\n const native = obj.native as Record<string, string> | undefined;\n if (!native?.encryptedToken || !native.serial) {\n continue;\n }\n const localId = id.replace(`${this.namespace}.`, \"\");\n this.log.debug(`Loading device from object: ${localId}`);\n const token = this.decrypt(native.encryptedToken);\n devices.push({\n token,\n productType: native.productType || \"unknown\",\n serial: native.serial,\n productName: native.productName || native.productType || \"unknown\",\n ...(native.ip ? { ip: native.ip } : {}),\n });\n }\n\n return devices;\n }\n\n /**\n * Save device config to its device object native (encrypted token)\n *\n * @param config Device configuration to save\n */\n private async saveDeviceToObject(config: DeviceConfig): Promise<void> {\n const prefix = this.stateManager.devicePrefix(config);\n const encryptedToken = this.encrypt(config.token);\n await this.extendObjectAsync(prefix, {\n type: \"device\",\n common: { name: config.productName || config.productType },\n native: {\n encryptedToken,\n productType: config.productType,\n serial: config.serial,\n productName: config.productName,\n ...(config.ip ? { ip: config.ip } : {}),\n },\n });\n }\n\n /**\n * Remove device config from its device object\n *\n * @param config Device configuration to remove\n */\n private async removeDeviceFromObject(config: DeviceConfig): Promise<void> {\n await this.stateManager.removeDevice(config);\n }\n\n /**\n * Handle a discovered device from mDNS (only active during pairing)\n *\n * @param discovered Discovered device info\n */\n private onDeviceDiscovered(discovered: DiscoveredDevice): void {\n // Skip already paired devices\n const existing = Array.from(this.connections.values()).find(\n (c) => c.config.serial === discovered.serial,\n );\n if (existing) {\n return;\n }\n\n // Skip duplicates\n if (\n this.discoveredDuringPairing.find((d) => d.serial === discovered.serial)\n ) {\n return;\n }\n\n this.discoveredDuringPairing.push(discovered);\n this.log.info(\n `Found ${discovered.name} (${discovered.productType}) at ${discovered.ip} \u2014 press the button on the device to pair`,\n );\n }\n\n /**\n * Adapter stopping \u2014 MUST be synchronous\n *\n * @param callback Completion callback\n */\n private onUnload(callback: () => void): void {\n if (this.pairingTimer) {\n this.clearTimeout(this.pairingTimer);\n }\n if (this.pairingPollTimer) {\n this.clearInterval(this.pairingPollTimer);\n }\n if (this.systemPollTimer) {\n this.clearInterval(this.systemPollTimer);\n }\n if (this.ipRecoveryTimer) {\n this.clearTimeout(this.ipRecoveryTimer);\n }\n\n this.discovery?.stop();\n\n for (const conn of this.connections.values()) {\n conn.wsClient?.close();\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n }\n }\n this.connections.clear();\n\n void this.setState(\"info.connection\", { val: false, ack: true });\n callback();\n }\n\n /**\n * Handle state changes\n *\n * @param id State ID\n * @param state State value\n */\n private async onStateChange(\n id: string,\n state: ioBroker.State | null | undefined,\n ): Promise<void> {\n if (!state || state.ack) {\n return;\n }\n\n // Pairing button\n if (id.endsWith(\".startPairing\")) {\n if (state.val) {\n await this.startPairing();\n }\n return;\n }\n\n // Remove device button\n if (id.endsWith(\".remove\")) {\n if (state.val) {\n await this.removeDevice(id);\n }\n return;\n }\n\n // Find which device this state belongs to\n const conn = this.findConnectionForState(id);\n if (!conn || !conn.ip) {\n return;\n }\n\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n\n try {\n if (id.endsWith(\".system.reboot\")) {\n this.log.info(`Rebooting ${conn.config.productName} (${conn.ip})`);\n await client.reboot();\n } else if (id.endsWith(\".system.identify\")) {\n await client.identify();\n } else if (id.endsWith(\".system.cloud_enabled\")) {\n await client.setSystem({ cloud_enabled: !!state.val });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".system.status_led_brightness_pct\")) {\n await client.setSystem({\n status_led_brightness_pct: Number(state.val),\n });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".system.api_v1_enabled\")) {\n await client.setSystem({ api_v1_enabled: !!state.val });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".battery.mode\")) {\n await client.setBatteries({\n mode: String(state.val) as \"zero\" | \"to_full\" | \"standby\",\n });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".battery.permissions\")) {\n const perms = JSON.parse(String(state.val));\n await client.setBatteries({ permissions: perms });\n await this.setStateAsync(id, { val: state.val, ack: true });\n }\n } catch (err) {\n this.log.warn(\n `Failed to set ${id}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n /** Start pairing mode \u2014 discover devices and attempt to pair */\n private async startPairing(): Promise<void> {\n if (this.isPairing) {\n this.log.debug(\"Pairing already active\");\n return;\n }\n\n // Reset startPairing immediately so it doesn't survive a restart\n await this.setStateAsync(\"startPairing\", { val: false, ack: true });\n\n this.isPairing = true;\n this.discoveredDuringPairing = [];\n\n // Stop IP recovery if running \u2014 pairing takes priority\n this.stopIpRecovery();\n\n // Check if manual IP is set, then clear pairingIp immediately\n const ipState = await this.getStateAsync(\"pairingIp\");\n this.pairingManualIp = ipState?.val ? String(ipState.val).trim() : \"\";\n await this.setStateAsync(\"pairingIp\", { val: \"\", ack: true });\n\n if (this.pairingManualIp) {\n this.log.info(\n `Pairing mode enabled for ${this.pairingManualIp} \u2014 press the button on your HomeWizard device now (60 seconds timeout)`,\n );\n // Add as discovered device immediately\n this.discoveredDuringPairing.push({\n ip: this.pairingManualIp,\n productType: \"unknown\",\n serial: \"unknown\",\n name: this.pairingManualIp,\n });\n } else {\n this.log.info(\n \"Pairing mode enabled \u2014 searching for devices via mDNS, press the button on your HomeWizard device now (60 seconds timeout)\",\n );\n // Restart mDNS browser to trigger fresh query \u2014 already-cached devices\n // won't be re-announced otherwise and pairing would never find them\n if (!this.discovery) {\n this.discovery = new HomeWizardDiscovery(this.log);\n }\n this.discovery.start((discovered) => {\n this.onDeviceDiscovered(discovered);\n });\n }\n\n // Poll discovered devices for pairing\n this.pairingPollTimer = this.setInterval(() => {\n void this.pollPairing();\n }, PAIRING_POLL_MS);\n\n // Timeout pairing\n this.pairingTimer = this.setTimeout(() => {\n this.stopPairing();\n this.log.info(\n \"Pairing mode automatically disabled after 60 seconds timeout\",\n );\n }, PAIRING_TIMEOUT_MS);\n }\n\n /** Poll all discovered devices to attempt pairing */\n private async pollPairing(): Promise<void> {\n for (const device of this.discoveredDuringPairing) {\n try {\n const client = new HomeWizardClient(device.ip);\n const result = await client.requestPairing();\n\n // Success! Button was pressed\n this.log.info(\n `Successfully paired with ${device.name} (${device.productType}) at ${device.ip} \u2014 connecting...`,\n );\n\n // Get device info\n const authedClient = new HomeWizardClient(device.ip, result.token);\n const info = await authedClient.getDeviceInfo();\n\n const deviceConfig: DeviceConfig = {\n token: result.token,\n productType: info.product_type,\n serial: info.serial,\n productName: info.product_name,\n ip: device.ip,\n };\n\n // Save to device object (no adapter restart!)\n await this.saveDeviceToObject(deviceConfig);\n await this.stateManager.createDeviceStates(deviceConfig);\n\n // Create connection and connect\n const key = this.stateManager.devicePrefix(deviceConfig);\n const conn = createDeviceConnection(deviceConfig, device.ip);\n this.connections.set(key, conn);\n void this.initDevice(conn);\n\n // Remove from discovery list\n this.discoveredDuringPairing = this.discoveredDuringPairing.filter(\n (d) => d.serial !== info.serial,\n );\n\n this.stopPairing();\n this.updateGlobalConnection();\n return;\n } catch (err) {\n // 403 = button not pressed yet \u2014 expected, keep polling\n if (err instanceof HomeWizardApiError && err.statusCode === 403) {\n continue;\n }\n this.log.debug(\n `Pairing poll error for ${device.ip}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n }\n\n /** Stop pairing mode */\n private stopPairing(): void {\n this.isPairing = false;\n this.pairingManualIp = \"\";\n this.discoveredDuringPairing = [];\n\n // Stop mDNS \u2014 only needed during pairing\n if (this.discovery) {\n this.discovery.stop();\n this.discovery = null;\n }\n\n if (this.pairingPollTimer) {\n this.clearInterval(this.pairingPollTimer);\n this.pairingPollTimer = undefined;\n }\n if (this.pairingTimer) {\n this.clearTimeout(this.pairingTimer);\n this.pairingTimer = undefined;\n }\n }\n\n /** Start mDNS to find devices that changed IP */\n private startIpRecovery(): void {\n // Don't start if already running or pairing\n if (this.discovery || this.isPairing) {\n return;\n }\n\n this.log.info(\"Device unreachable \u2014 searching for new IP via mDNS\");\n\n this.discovery = new HomeWizardDiscovery(this.log);\n this.discovery.start((discovered) => {\n // Match against disconnected devices\n for (const conn of this.connections.values()) {\n if (conn.config.serial !== discovered.serial) {\n continue;\n }\n if (discovered.ip === conn.ip || conn.wsAuthenticated) {\n return; // Same IP or already connected\n }\n\n this.log.info(\n `${conn.config.productName}: found at new IP ${discovered.ip} (was ${conn.ip})`,\n );\n\n // Update IP and persist \u2014 reset stability (new network conditions)\n conn.ip = discovered.ip;\n conn.config.ip = discovered.ip;\n conn.wsFailCount = 0;\n conn.recentDisconnects = 0;\n void this.saveDeviceToObject(conn.config);\n\n // Cancel pending reconnect and connect immediately\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n conn.reconnectTimer = undefined;\n }\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n this.connectWebSocket(conn);\n return;\n }\n });\n\n // Stop mDNS after timeout \u2014 WS reconnect continues with exponential backoff\n this.ipRecoveryTimer = this.setTimeout(() => {\n this.ipRecoveryTimer = undefined;\n this.stopIpRecovery();\n\n for (const conn of this.connections.values()) {\n if (!conn.wsAuthenticated && conn.wsFailCount > 0) {\n this.log.warn(\n `${conn.config.productName}: device offline \u2014 will keep retrying every ${WS_RECONNECT_MAX_MS / 1000}s`,\n );\n }\n }\n }, IP_RECOVERY_TIMEOUT_MS);\n }\n\n /** Stop mDNS IP recovery */\n private stopIpRecovery(): void {\n if (this.ipRecoveryTimer) {\n this.clearTimeout(this.ipRecoveryTimer);\n this.ipRecoveryTimer = undefined;\n }\n if (this.discovery && !this.isPairing) {\n this.discovery.stop();\n this.discovery = null;\n }\n }\n\n /**\n * Initialize a newly discovered device \u2014 fetch info and connect WebSocket\n *\n * @param conn Device connection with IP set\n */\n private async initDevice(conn: DeviceConnection): Promise<void> {\n try {\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n const info = await client.getDeviceInfo();\n const key = this.stateManager.devicePrefix(conn.config);\n await this.setStateAsync(`${key}.info.firmware`, {\n val: info.firmware_version,\n ack: true,\n });\n } catch (err) {\n this.logDeviceError(conn, \"init\", err);\n }\n\n this.connectWebSocket(conn);\n void this.pollSystemInfo(conn);\n }\n\n /**\n * Connect WebSocket for a device\n *\n * @param conn Device connection\n */\n private connectWebSocket(conn: DeviceConnection): void {\n if (!conn.ip) {\n return; // No IP yet \u2014 wait for mDNS\n }\n\n // Stop reconnecting if auth keeps failing\n if (conn.authFailCount >= MAX_AUTH_FAILURES) {\n return;\n }\n\n // After repeated failures, try mDNS periodically to find a new IP\n if (\n conn.wsFailCount >= WS_FAILURES_BEFORE_MDNS &&\n (conn.wsFailCount - WS_FAILURES_BEFORE_MDNS) % MDNS_RETRY_EVERY === 0\n ) {\n this.startIpRecovery();\n }\n\n const key = this.stateManager.devicePrefix(conn.config);\n\n const wsClient = new HomeWizardWebSocket(conn.ip, conn.config.token, {\n onMeasurement: (data: Measurement) => {\n void this.stateManager.updateMeasurement(conn.config, data);\n },\n onConnected: () => {\n conn.wsAuthenticated = true;\n conn.wsFailCount = 0;\n conn.authFailCount = 0;\n conn.lastConnectedAt = Date.now();\n void this.stateManager.setDeviceConnected(conn.config, true);\n this.updateGlobalConnection();\n\n // Stop REST fallback if active\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n\n // Stop IP recovery if all devices are connected\n if (this.discovery && !this.isPairing) {\n const allConnected = Array.from(this.connections.values()).every(\n (c) => c.wsAuthenticated,\n );\n if (allConnected) {\n this.stopIpRecovery();\n }\n }\n\n // Log restoration if we had errors before\n if (conn.lastErrorCode) {\n const mode = this.isUnstable(conn) ? \" (unstable mode)\" : \"\";\n this.log.info(\n `${conn.config.productName}: connection restored${mode}`,\n );\n conn.lastErrorCode = \"\";\n }\n\n this.log.debug(\n `WebSocket connected to ${conn.config.productName} (${conn.ip})`,\n );\n },\n onDisconnected: (error?: Error) => {\n // Track connection stability\n if (conn.lastConnectedAt > 0) {\n const duration = Date.now() - conn.lastConnectedAt;\n if (duration < STABLE_THRESHOLD_MS) {\n conn.recentDisconnects++;\n if (conn.recentDisconnects === UNSTABLE_DISCONNECT_THRESHOLD) {\n this.log.info(\n `${conn.config.productName}: unstable connection detected \u2014 using faster reconnect`,\n );\n }\n } else {\n // Was stable \u2014 reset counter\n if (conn.recentDisconnects >= UNSTABLE_DISCONNECT_THRESHOLD) {\n this.log.info(\n `${conn.config.productName}: connection stabilized \u2014 using normal reconnect`,\n );\n }\n conn.recentDisconnects = 0;\n }\n }\n\n conn.wsAuthenticated = false;\n conn.wsClient = null;\n void this.stateManager.setDeviceConnected(conn.config, false);\n this.updateGlobalConnection();\n\n if (error) {\n this.logDeviceError(conn, \"ws\", error);\n }\n\n // Check if this was an auth failure\n if (\n error instanceof HomeWizardApiError &&\n error.errorCode === \"user:unauthorized\"\n ) {\n conn.authFailCount++;\n if (conn.authFailCount >= MAX_AUTH_FAILURES) {\n this.log.warn(\n `${conn.config.productName}: token invalid \u2014 re-pair device to fix`,\n );\n return;\n }\n }\n\n // Start REST fallback\n this.startRestFallback(conn);\n\n // Schedule reconnect with exponential backoff (faster for unstable devices)\n conn.wsFailCount++;\n const maxDelay = this.isUnstable(conn)\n ? WS_RECONNECT_MAX_UNSTABLE_MS\n : WS_RECONNECT_MAX_MS;\n const delay = Math.min(\n WS_RECONNECT_BASE_MS * Math.pow(2, conn.wsFailCount - 1),\n maxDelay,\n );\n this.log.debug(\n `${key}: WS reconnect in ${delay / 1000}s (attempt ${conn.wsFailCount})`,\n );\n\n conn.reconnectTimer = this.setTimeout(() => {\n conn.reconnectTimer = undefined;\n this.connectWebSocket(conn);\n }, delay);\n },\n log: this.log,\n });\n\n conn.wsClient = wsClient;\n wsClient.connect();\n }\n\n /**\n * Start REST polling as fallback when WebSocket is down.\n * For stable devices: stops on network errors (WS reconnect handles recovery).\n * For unstable devices: slows down instead of stopping to minimize data gaps.\n *\n * @param conn Device connection\n */\n private startRestFallback(conn: DeviceConnection): void {\n if (conn.pollTimer || !conn.ip) {\n return;\n }\n\n const unstable = this.isUnstable(conn);\n const interval = unstable ? REST_POLL_UNSTABLE_MS : REST_POLL_MS;\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n\n conn.pollTimer = this.setInterval(async () => {\n try {\n const data = await client.getMeasurement();\n await this.stateManager.updateMeasurement(conn.config, data);\n } catch (err) {\n this.logDeviceError(conn, \"rest\", err);\n\n // Stop REST polling on network errors for stable devices.\n // Unstable devices keep polling (slower) to minimize data gaps.\n if (!unstable && classifyError(err) === \"NETWORK\" && conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n }\n }, interval);\n }\n\n /** Poll system info for all connected devices */\n private async pollAllSystemInfo(): Promise<void> {\n for (const conn of this.connections.values()) {\n if (conn.ip && conn.wsAuthenticated) {\n await this.pollSystemInfo(conn);\n }\n }\n }\n\n /**\n * Poll system info for a single device\n *\n * @param conn Device connection\n */\n private async pollSystemInfo(conn: DeviceConnection): Promise<void> {\n if (!conn.ip) {\n return;\n }\n\n try {\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n const system = await client.getSystem();\n await this.stateManager.updateSystem(conn.config, system);\n\n // Also poll battery if device supports it\n try {\n const battery = await client.getBatteries();\n // Only create battery states if batteries are actually connected\n if (battery.battery_count && battery.battery_count > 0) {\n await this.stateManager.updateBattery(conn.config, battery);\n }\n } catch {\n // Device may not support batteries \u2014 that's fine\n }\n } catch (err) {\n this.logDeviceError(conn, \"system\", err);\n }\n }\n\n /** Update global info.connection based on all device states */\n private updateGlobalConnection(): void {\n const anyConnected = Array.from(this.connections.values()).some(\n (c) => c.wsAuthenticated,\n );\n void this.setStateAsync(\"info.connection\", {\n val: anyConnected,\n ack: true,\n });\n }\n\n /**\n * Remove a device \u2014 disconnect, delete states and object\n *\n * @param stateId The remove state ID\n */\n private async removeDevice(stateId: string): Promise<void> {\n const conn = this.findConnectionForState(stateId);\n if (!conn) {\n return;\n }\n\n const key = this.stateManager.devicePrefix(conn.config);\n this.log.info(\n `Removing device ${conn.config.productName} (${conn.config.serial})`,\n );\n\n // Disconnect\n conn.wsClient?.close();\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n }\n this.connections.delete(key);\n\n // Delete device object and all states (no adapter restart!)\n await this.removeDeviceFromObject(conn.config);\n\n this.updateGlobalConnection();\n }\n\n /**\n * Find connection for a state ID\n *\n * @param stateId Full state ID\n */\n private findConnectionForState(\n stateId: string,\n ): DeviceConnection | undefined {\n const localId = stateId.replace(`${this.namespace}.`, \"\");\n for (const conn of this.connections.values()) {\n const prefix = this.stateManager.devicePrefix(conn.config);\n if (localId.startsWith(`${prefix}.`)) {\n return conn;\n }\n }\n return undefined;\n }\n\n /**\n * Whether a device has unstable connectivity (frequent short-lived connections).\n * Unstable devices get faster reconnect and persistent REST fallback.\n *\n * @param conn Device connection\n */\n private isUnstable(conn: DeviceConnection): boolean {\n return conn.recentDisconnects >= UNSTABLE_DISCONNECT_THRESHOLD;\n }\n\n /**\n * Log device error with deduplication (based on error category, not context).\n * First occurrence of a new error category logs as warn, repeats as debug.\n *\n * @param conn Device connection\n * @param context Error context (for debug messages only)\n * @param err Error object\n */\n private logDeviceError(\n conn: DeviceConnection,\n context: string,\n err: unknown,\n ): void {\n const errorCode = classifyError(err);\n const isRepeat = errorCode === conn.lastErrorCode;\n conn.lastErrorCode = errorCode;\n\n if (isRepeat) {\n this.log.debug(\n `${conn.config.productName} ${context}: ${err instanceof Error ? err.message : String(err)}`,\n );\n } else if (errorCode === \"NETWORK\") {\n this.log.warn(\n `${conn.config.productName}: device unreachable \u2014 will keep retrying`,\n );\n } else {\n this.log.warn(\n `${conn.config.productName} ${context}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) =>\n new HomeWizard(options);\n} else {\n (() => new HomeWizard())();\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport {\n classifyError,\n createDeviceConnection,\n UNSTABLE_DISCONNECT_THRESHOLD,\n} from \"./lib/connection-utils\";\nimport { HomeWizardDiscovery } from \"./lib/discovery\";\nimport { HomeWizardApiError, HomeWizardClient } from \"./lib/homewizard-client\";\nimport { StateManager } from \"./lib/state-manager\";\nimport type {\n DeviceConfig,\n DeviceConnection,\n DiscoveredDevice,\n Measurement,\n} from \"./lib/types\";\nimport { HomeWizardWebSocket } from \"./lib/websocket-client\";\n\n/** Pairing timeout in milliseconds (60 seconds) */\nconst PAIRING_TIMEOUT_MS = 60_000;\n/** Pairing poll interval in milliseconds */\nconst PAIRING_POLL_MS = 2_000;\n/** WebSocket reconnect base delay in milliseconds */\nconst WS_RECONNECT_BASE_MS = 5_000;\n/** Maximum WebSocket reconnect delay in milliseconds */\nconst WS_RECONNECT_MAX_MS = 300_000;\n/** REST fallback poll interval in milliseconds */\nconst REST_POLL_MS = 10_000;\n/** System info poll interval in milliseconds */\nconst SYSTEM_POLL_MS = 60_000;\n/** Max auth failures before giving up */\nconst MAX_AUTH_FAILURES = 3;\n/** WS failures before starting mDNS IP recovery */\nconst WS_FAILURES_BEFORE_MDNS = 3;\n/** mDNS IP recovery timeout in milliseconds */\nconst IP_RECOVERY_TIMEOUT_MS = 60_000;\n/** Retry mDNS every N WS failures after first attempt (~1 hour at 5 min cap) */\nconst MDNS_RETRY_EVERY = 12;\n/** Connection must last this long to count as \"stable\" */\nconst STABLE_THRESHOLD_MS = 600_000;\n/** Max reconnect delay for unstable devices */\nconst WS_RECONNECT_MAX_UNSTABLE_MS = 60_000;\n/** REST fallback interval for unstable devices (slower, not stopped) */\nconst REST_POLL_UNSTABLE_MS = 30_000;\n\nclass HomeWizard extends utils.Adapter {\n private stateManager!: StateManager;\n private discovery: HomeWizardDiscovery | null = null;\n private readonly connections = new Map<string, DeviceConnection>();\n private pairingTimer: ioBroker.Timeout | undefined = undefined;\n private pairingPollTimer: ioBroker.Interval | undefined = undefined;\n private systemPollTimer: ioBroker.Interval | undefined = undefined;\n private ipRecoveryTimer: ioBroker.Timeout | undefined = undefined;\n private isPairing = false;\n private pairingManualIp = \"\";\n private discoveredDuringPairing: DiscoveredDevice[] = [];\n\n /** @param options Adapter options */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"homewizard\" });\n this.on(\"ready\", () => this.onReady());\n this.on(\"stateChange\", (id, state) => this.onStateChange(id, state));\n this.on(\"unload\", (callback) => this.onUnload(callback));\n }\n\n /** Adapter started */\n private async onReady(): Promise<void> {\n this.stateManager = new StateManager(this);\n\n // Create pairing states\n await this.extendObjectAsync(\"pairingIp\", {\n type: \"state\",\n common: {\n name: \"Device IP for manual pairing\",\n type: \"string\",\n role: \"text\",\n read: true,\n write: true,\n def: \"\",\n },\n native: {},\n });\n\n // Reset pairing states on start (in case previous run was killed mid-pairing)\n await this.setStateAsync(\"startPairing\", { val: false, ack: true });\n await this.setStateAsync(\"pairingIp\", { val: \"\", ack: true });\n\n // Subscribe to pairing button and writable device states\n await this.subscribeStatesAsync(\"startPairing\");\n await this.subscribeStatesAsync(\"*.system.reboot\");\n await this.subscribeStatesAsync(\"*.system.identify\");\n await this.subscribeStatesAsync(\"*.system.cloud_enabled\");\n await this.subscribeStatesAsync(\"*.system.status_led_brightness_pct\");\n await this.subscribeStatesAsync(\"*.system.api_v1_enabled\");\n await this.subscribeStatesAsync(\"*.battery.mode\");\n await this.subscribeStatesAsync(\"*.battery.permissions\");\n await this.subscribeStatesAsync(\"*.remove\");\n\n // Load devices from device objects (not from adapter config)\n const devices = await this.loadDevicesFromObjects();\n if (devices.length === 0) {\n this.log.info(\n \"No devices configured \u2014 set 'startPairing' to true to add a device\",\n );\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n }\n\n // Create connection entries for all configured devices\n for (const device of devices) {\n const key = this.stateManager.devicePrefix(device);\n await this.stateManager.cleanupMovedStates(device);\n await this.stateManager.createDeviceStates(device);\n const conn = createDeviceConnection(device, device.ip || \"\");\n this.connections.set(key, conn);\n\n // If we have a stored IP, connect immediately\n if (conn.ip) {\n this.log.debug(`Using stored IP ${conn.ip} for ${device.productName}`);\n void this.initDevice(conn);\n }\n }\n\n // Periodic system info poll\n this.systemPollTimer = this.setInterval(() => {\n void this.pollAllSystemInfo();\n }, SYSTEM_POLL_MS);\n\n this.updateGlobalConnection();\n }\n\n /**\n * Load device configs from existing device objects\n * Tokens are stored encrypted in device object native\n */\n private async loadDevicesFromObjects(): Promise<DeviceConfig[]> {\n const devices: DeviceConfig[] = [];\n\n // Also migrate from old adapter config if devices exist there\n const oldDevices: DeviceConfig[] =\n ((this.config as Record<string, unknown>).devices as DeviceConfig[]) ||\n [];\n if (oldDevices.length > 0) {\n this.log.debug(\n `Migrating ${oldDevices.length} device(s) from adapter config to device objects`,\n );\n for (const device of oldDevices) {\n await this.saveDeviceToObject(device);\n }\n // Clear old config (this triggers one restart, but only during migration)\n await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, {\n native: { devices: [] },\n });\n return oldDevices;\n }\n\n // Read device objects from our namespace\n const objects = await this.getAdapterObjectsAsync();\n for (const [id, obj] of Object.entries(objects)) {\n if (obj.type !== \"device\") {\n continue;\n }\n const native = obj.native as Record<string, string> | undefined;\n if (!native?.encryptedToken || !native.serial) {\n continue;\n }\n const localId = id.replace(`${this.namespace}.`, \"\");\n this.log.debug(`Loading device from object: ${localId}`);\n const token = this.decrypt(native.encryptedToken);\n devices.push({\n token,\n productType: native.productType || \"unknown\",\n serial: native.serial,\n productName: native.productName || native.productType || \"unknown\",\n ...(native.ip ? { ip: native.ip } : {}),\n });\n }\n\n return devices;\n }\n\n /**\n * Save device config to its device object native (encrypted token)\n *\n * @param config Device configuration to save\n */\n private async saveDeviceToObject(config: DeviceConfig): Promise<void> {\n const prefix = this.stateManager.devicePrefix(config);\n const encryptedToken = this.encrypt(config.token);\n await this.extendObjectAsync(prefix, {\n type: \"device\",\n common: { name: config.productName || config.productType },\n native: {\n encryptedToken,\n productType: config.productType,\n serial: config.serial,\n productName: config.productName,\n ...(config.ip ? { ip: config.ip } : {}),\n },\n });\n }\n\n /**\n * Handle a discovered device from mDNS (only active during pairing)\n *\n * @param discovered Discovered device info\n */\n private onDeviceDiscovered(discovered: DiscoveredDevice): void {\n // Skip already paired devices\n const existing = Array.from(this.connections.values()).find(\n (c) => c.config.serial === discovered.serial,\n );\n if (existing) {\n return;\n }\n\n // Skip duplicates\n if (\n this.discoveredDuringPairing.find((d) => d.serial === discovered.serial)\n ) {\n return;\n }\n\n this.discoveredDuringPairing.push(discovered);\n this.log.info(\n `Found ${discovered.name} (${discovered.productType}) at ${discovered.ip} \u2014 press the button on the device to pair`,\n );\n }\n\n /**\n * Adapter stopping \u2014 MUST be synchronous\n *\n * @param callback Completion callback\n */\n private onUnload(callback: () => void): void {\n try {\n if (this.pairingTimer) {\n this.clearTimeout(this.pairingTimer);\n }\n if (this.pairingPollTimer) {\n this.clearInterval(this.pairingPollTimer);\n }\n if (this.systemPollTimer) {\n this.clearInterval(this.systemPollTimer);\n }\n if (this.ipRecoveryTimer) {\n this.clearTimeout(this.ipRecoveryTimer);\n }\n\n this.discovery?.stop();\n\n for (const conn of this.connections.values()) {\n conn.wsClient?.close();\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n }\n }\n this.connections.clear();\n\n void this.setState(\"info.connection\", { val: false, ack: true });\n } finally {\n callback();\n }\n }\n\n /**\n * Handle state changes\n *\n * @param id State ID\n * @param state State value\n */\n private async onStateChange(\n id: string,\n state: ioBroker.State | null | undefined,\n ): Promise<void> {\n if (!state || state.ack) {\n return;\n }\n\n // Pairing button\n if (id.endsWith(\".startPairing\")) {\n if (state.val) {\n await this.startPairing();\n }\n return;\n }\n\n // Remove device button\n if (id.endsWith(\".remove\")) {\n if (state.val) {\n await this.removeDevice(id);\n }\n return;\n }\n\n // Find which device this state belongs to\n const conn = this.findConnectionForState(id);\n if (!conn || !conn.ip) {\n return;\n }\n\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n\n try {\n if (id.endsWith(\".system.reboot\")) {\n this.log.info(`Rebooting ${conn.config.productName} (${conn.ip})`);\n await client.reboot();\n } else if (id.endsWith(\".system.identify\")) {\n await client.identify();\n } else if (id.endsWith(\".system.cloud_enabled\")) {\n await client.setSystem({ cloud_enabled: !!state.val });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".system.status_led_brightness_pct\")) {\n await client.setSystem({\n status_led_brightness_pct: Number(state.val),\n });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".system.api_v1_enabled\")) {\n await client.setSystem({ api_v1_enabled: !!state.val });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".battery.mode\")) {\n await client.setBatteries({\n mode: String(state.val) as \"zero\" | \"to_full\" | \"standby\",\n });\n await this.setStateAsync(id, { val: state.val, ack: true });\n } else if (id.endsWith(\".battery.permissions\")) {\n const perms = JSON.parse(String(state.val));\n await client.setBatteries({ permissions: perms });\n await this.setStateAsync(id, { val: state.val, ack: true });\n }\n } catch (err) {\n this.log.warn(\n `Failed to set ${id}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n /** Start pairing mode \u2014 discover devices and attempt to pair */\n private async startPairing(): Promise<void> {\n if (this.isPairing) {\n this.log.debug(\"Pairing already active\");\n return;\n }\n\n // Reset startPairing immediately so it doesn't survive a restart\n await this.setStateAsync(\"startPairing\", { val: false, ack: true });\n\n this.isPairing = true;\n this.discoveredDuringPairing = [];\n\n // Stop IP recovery if running \u2014 pairing takes priority\n this.stopIpRecovery();\n\n // Check if manual IP is set, then clear pairingIp immediately\n const ipState = await this.getStateAsync(\"pairingIp\");\n this.pairingManualIp = ipState?.val ? String(ipState.val).trim() : \"\";\n await this.setStateAsync(\"pairingIp\", { val: \"\", ack: true });\n\n if (this.pairingManualIp) {\n this.log.info(\n `Pairing mode enabled for ${this.pairingManualIp} \u2014 press the button on your HomeWizard device now (60 seconds timeout)`,\n );\n // Add as discovered device immediately\n this.discoveredDuringPairing.push({\n ip: this.pairingManualIp,\n productType: \"unknown\",\n serial: \"unknown\",\n name: this.pairingManualIp,\n });\n } else {\n this.log.info(\n \"Pairing mode enabled \u2014 searching for devices via mDNS, press the button on your HomeWizard device now (60 seconds timeout)\",\n );\n // Restart mDNS browser to trigger fresh query \u2014 already-cached devices\n // won't be re-announced otherwise and pairing would never find them\n if (!this.discovery) {\n this.discovery = new HomeWizardDiscovery(this.log);\n }\n this.discovery.start((discovered) => {\n this.onDeviceDiscovered(discovered);\n });\n }\n\n // Poll discovered devices for pairing\n this.pairingPollTimer = this.setInterval(() => {\n void this.pollPairing();\n }, PAIRING_POLL_MS);\n\n // Timeout pairing\n this.pairingTimer = this.setTimeout(() => {\n this.stopPairing();\n this.log.info(\n \"Pairing mode automatically disabled after 60 seconds timeout\",\n );\n }, PAIRING_TIMEOUT_MS);\n }\n\n /** Poll all discovered devices to attempt pairing */\n private async pollPairing(): Promise<void> {\n for (const device of this.discoveredDuringPairing) {\n try {\n const client = new HomeWizardClient(device.ip);\n const result = await client.requestPairing();\n\n // Success! Button was pressed\n this.log.info(\n `Successfully paired with ${device.name} (${device.productType}) at ${device.ip} \u2014 connecting...`,\n );\n\n // Get device info\n const authedClient = new HomeWizardClient(device.ip, result.token);\n const info = await authedClient.getDeviceInfo();\n\n const deviceConfig: DeviceConfig = {\n token: result.token,\n productType: info.product_type,\n serial: info.serial,\n productName: info.product_name,\n ip: device.ip,\n };\n\n // Save to device object (no adapter restart!)\n await this.saveDeviceToObject(deviceConfig);\n await this.stateManager.createDeviceStates(deviceConfig);\n\n // Create connection and connect\n const key = this.stateManager.devicePrefix(deviceConfig);\n const conn = createDeviceConnection(deviceConfig, device.ip);\n this.connections.set(key, conn);\n void this.initDevice(conn);\n\n // Remove from discovery list\n this.discoveredDuringPairing = this.discoveredDuringPairing.filter(\n (d) => d.serial !== info.serial,\n );\n\n this.stopPairing();\n this.updateGlobalConnection();\n return;\n } catch (err) {\n // 403 = button not pressed yet \u2014 expected, keep polling\n if (err instanceof HomeWizardApiError && err.statusCode === 403) {\n continue;\n }\n this.log.debug(\n `Pairing poll error for ${device.ip}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n }\n\n /** Stop pairing mode */\n private stopPairing(): void {\n this.isPairing = false;\n this.pairingManualIp = \"\";\n this.discoveredDuringPairing = [];\n\n // Stop mDNS \u2014 only needed during pairing\n if (this.discovery) {\n this.discovery.stop();\n this.discovery = null;\n }\n\n if (this.pairingPollTimer) {\n this.clearInterval(this.pairingPollTimer);\n this.pairingPollTimer = undefined;\n }\n if (this.pairingTimer) {\n this.clearTimeout(this.pairingTimer);\n this.pairingTimer = undefined;\n }\n }\n\n /** Start mDNS to find devices that changed IP */\n private startIpRecovery(): void {\n // Don't start if already running or pairing\n if (this.discovery || this.isPairing) {\n return;\n }\n\n this.log.info(\"Device unreachable \u2014 searching for new IP via mDNS\");\n\n this.discovery = new HomeWizardDiscovery(this.log);\n this.discovery.start((discovered) => {\n // Match against disconnected devices\n for (const conn of this.connections.values()) {\n if (conn.config.serial !== discovered.serial) {\n continue;\n }\n if (discovered.ip === conn.ip || conn.wsAuthenticated) {\n return; // Same IP or already connected\n }\n\n this.log.info(\n `${conn.config.productName}: found at new IP ${discovered.ip} (was ${conn.ip})`,\n );\n\n // Update IP and persist \u2014 reset stability (new network conditions)\n conn.ip = discovered.ip;\n conn.config.ip = discovered.ip;\n conn.wsFailCount = 0;\n conn.recentDisconnects = 0;\n void this.saveDeviceToObject(conn.config);\n\n // Cancel pending reconnect and connect immediately\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n conn.reconnectTimer = undefined;\n }\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n this.connectWebSocket(conn);\n return;\n }\n });\n\n // Stop mDNS after timeout \u2014 WS reconnect continues with exponential backoff\n this.ipRecoveryTimer = this.setTimeout(() => {\n this.ipRecoveryTimer = undefined;\n this.stopIpRecovery();\n\n for (const conn of this.connections.values()) {\n if (!conn.wsAuthenticated && conn.wsFailCount > 0) {\n this.log.warn(\n `${conn.config.productName}: device offline \u2014 will keep retrying every ${WS_RECONNECT_MAX_MS / 1000}s`,\n );\n }\n }\n }, IP_RECOVERY_TIMEOUT_MS);\n }\n\n /** Stop mDNS IP recovery */\n private stopIpRecovery(): void {\n if (this.ipRecoveryTimer) {\n this.clearTimeout(this.ipRecoveryTimer);\n this.ipRecoveryTimer = undefined;\n }\n if (this.discovery && !this.isPairing) {\n this.discovery.stop();\n this.discovery = null;\n }\n }\n\n /**\n * Initialize a newly discovered device \u2014 fetch info and connect WebSocket\n *\n * @param conn Device connection with IP set\n */\n private async initDevice(conn: DeviceConnection): Promise<void> {\n try {\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n const info = await client.getDeviceInfo();\n const key = this.stateManager.devicePrefix(conn.config);\n await this.setStateAsync(`${key}.info.firmware`, {\n val: info.firmware_version,\n ack: true,\n });\n } catch (err) {\n this.logDeviceError(conn, \"init\", err);\n }\n\n this.connectWebSocket(conn);\n void this.pollSystemInfo(conn);\n }\n\n /**\n * Connect WebSocket for a device\n *\n * @param conn Device connection\n */\n private connectWebSocket(conn: DeviceConnection): void {\n if (!conn.ip) {\n return; // No IP yet \u2014 wait for mDNS\n }\n\n // Stop reconnecting if auth keeps failing\n if (conn.authFailCount >= MAX_AUTH_FAILURES) {\n return;\n }\n\n // After repeated failures, try mDNS periodically to find a new IP\n if (\n conn.wsFailCount >= WS_FAILURES_BEFORE_MDNS &&\n (conn.wsFailCount - WS_FAILURES_BEFORE_MDNS) % MDNS_RETRY_EVERY === 0\n ) {\n this.startIpRecovery();\n }\n\n const key = this.stateManager.devicePrefix(conn.config);\n\n const wsClient = new HomeWizardWebSocket(conn.ip, conn.config.token, {\n onMeasurement: (data: Measurement) => {\n void this.stateManager.updateMeasurement(conn.config, data);\n },\n onConnected: () => {\n conn.wsAuthenticated = true;\n conn.wsFailCount = 0;\n conn.authFailCount = 0;\n conn.lastConnectedAt = Date.now();\n void this.stateManager.setDeviceConnected(conn.config, true);\n this.updateGlobalConnection();\n\n // Stop REST fallback if active\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n\n // Stop IP recovery if all devices are connected\n if (this.discovery && !this.isPairing) {\n const allConnected = Array.from(this.connections.values()).every(\n (c) => c.wsAuthenticated,\n );\n if (allConnected) {\n this.stopIpRecovery();\n }\n }\n\n // Log restoration if we had errors before\n if (conn.lastErrorCode) {\n const mode = this.isUnstable(conn) ? \" (unstable mode)\" : \"\";\n this.log.info(\n `${conn.config.productName}: connection restored${mode}`,\n );\n conn.lastErrorCode = \"\";\n }\n\n this.log.debug(\n `WebSocket connected to ${conn.config.productName} (${conn.ip})`,\n );\n },\n onDisconnected: (error?: Error) => {\n // Track connection stability\n if (conn.lastConnectedAt > 0) {\n const duration = Date.now() - conn.lastConnectedAt;\n if (duration < STABLE_THRESHOLD_MS) {\n conn.recentDisconnects++;\n if (conn.recentDisconnects === UNSTABLE_DISCONNECT_THRESHOLD) {\n this.log.info(\n `${conn.config.productName}: unstable connection detected \u2014 using faster reconnect`,\n );\n }\n } else {\n // Was stable \u2014 reset counter\n if (conn.recentDisconnects >= UNSTABLE_DISCONNECT_THRESHOLD) {\n this.log.info(\n `${conn.config.productName}: connection stabilized \u2014 using normal reconnect`,\n );\n }\n conn.recentDisconnects = 0;\n }\n }\n\n conn.wsAuthenticated = false;\n conn.wsClient = null;\n void this.stateManager.setDeviceConnected(conn.config, false);\n this.updateGlobalConnection();\n\n if (error) {\n this.logDeviceError(conn, \"ws\", error);\n }\n\n // Check if this was an auth failure\n if (\n error instanceof HomeWizardApiError &&\n error.errorCode === \"user:unauthorized\"\n ) {\n conn.authFailCount++;\n if (conn.authFailCount >= MAX_AUTH_FAILURES) {\n this.log.warn(\n `${conn.config.productName}: token invalid \u2014 re-pair device to fix`,\n );\n return;\n }\n }\n\n // Start REST fallback\n this.startRestFallback(conn);\n\n // Schedule reconnect with exponential backoff (faster for unstable devices)\n conn.wsFailCount++;\n const maxDelay = this.isUnstable(conn)\n ? WS_RECONNECT_MAX_UNSTABLE_MS\n : WS_RECONNECT_MAX_MS;\n const delay = Math.min(\n WS_RECONNECT_BASE_MS * Math.pow(2, conn.wsFailCount - 1),\n maxDelay,\n );\n this.log.debug(\n `${key}: WS reconnect in ${delay / 1000}s (attempt ${conn.wsFailCount})`,\n );\n\n conn.reconnectTimer = this.setTimeout(() => {\n conn.reconnectTimer = undefined;\n this.connectWebSocket(conn);\n }, delay);\n },\n log: this.log,\n });\n\n conn.wsClient = wsClient;\n wsClient.connect();\n }\n\n /**\n * Start REST polling as fallback when WebSocket is down.\n * For stable devices: stops on network errors (WS reconnect handles recovery).\n * For unstable devices: slows down instead of stopping to minimize data gaps.\n *\n * @param conn Device connection\n */\n private startRestFallback(conn: DeviceConnection): void {\n if (conn.pollTimer || !conn.ip) {\n return;\n }\n\n const unstable = this.isUnstable(conn);\n const interval = unstable ? REST_POLL_UNSTABLE_MS : REST_POLL_MS;\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n\n conn.pollTimer = this.setInterval(async () => {\n try {\n const data = await client.getMeasurement();\n await this.stateManager.updateMeasurement(conn.config, data);\n } catch (err) {\n this.logDeviceError(conn, \"rest\", err);\n\n // Stop REST polling on network errors for stable devices.\n // Unstable devices keep polling (slower) to minimize data gaps.\n if (!unstable && classifyError(err) === \"NETWORK\" && conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n conn.pollTimer = undefined;\n }\n }\n }, interval);\n }\n\n /** Poll system info for all connected devices */\n private async pollAllSystemInfo(): Promise<void> {\n for (const conn of this.connections.values()) {\n if (conn.ip && conn.wsAuthenticated) {\n await this.pollSystemInfo(conn);\n }\n }\n }\n\n /**\n * Poll system info for a single device\n *\n * @param conn Device connection\n */\n private async pollSystemInfo(conn: DeviceConnection): Promise<void> {\n if (!conn.ip) {\n return;\n }\n\n try {\n const client = new HomeWizardClient(conn.ip, conn.config.token);\n const system = await client.getSystem();\n await this.stateManager.updateSystem(conn.config, system);\n\n // Also poll battery if device supports it\n try {\n const battery = await client.getBatteries();\n // Only create battery states if batteries are actually connected\n if (battery.battery_count && battery.battery_count > 0) {\n await this.stateManager.updateBattery(conn.config, battery);\n }\n } catch {\n // Device may not support batteries \u2014 that's fine\n }\n } catch (err) {\n this.logDeviceError(conn, \"system\", err);\n }\n }\n\n /** Update global info.connection based on all device states */\n private updateGlobalConnection(): void {\n const anyConnected = Array.from(this.connections.values()).some(\n (c) => c.wsAuthenticated,\n );\n void this.setStateAsync(\"info.connection\", {\n val: anyConnected,\n ack: true,\n });\n }\n\n /**\n * Remove a device \u2014 disconnect, delete states and object\n *\n * @param stateId The remove state ID\n */\n private async removeDevice(stateId: string): Promise<void> {\n const conn = this.findConnectionForState(stateId);\n if (!conn) {\n return;\n }\n\n const key = this.stateManager.devicePrefix(conn.config);\n this.log.info(\n `Removing device ${conn.config.productName} (${conn.config.serial})`,\n );\n\n // Disconnect\n conn.wsClient?.close();\n if (conn.pollTimer) {\n this.clearInterval(conn.pollTimer);\n }\n if (conn.reconnectTimer) {\n this.clearTimeout(conn.reconnectTimer);\n }\n this.connections.delete(key);\n\n // Delete device object and all states (no adapter restart!)\n await this.stateManager.removeDevice(conn.config);\n\n this.updateGlobalConnection();\n }\n\n /**\n * Find connection for a state ID\n *\n * @param stateId Full state ID\n */\n private findConnectionForState(\n stateId: string,\n ): DeviceConnection | undefined {\n const localId = stateId.replace(`${this.namespace}.`, \"\");\n for (const conn of this.connections.values()) {\n const prefix = this.stateManager.devicePrefix(conn.config);\n if (localId.startsWith(`${prefix}.`)) {\n return conn;\n }\n }\n return undefined;\n }\n\n /**\n * Whether a device has unstable connectivity (frequent short-lived connections).\n * Unstable devices get faster reconnect and persistent REST fallback.\n *\n * @param conn Device connection\n */\n private isUnstable(conn: DeviceConnection): boolean {\n return conn.recentDisconnects >= UNSTABLE_DISCONNECT_THRESHOLD;\n }\n\n /**\n * Log device error with deduplication (based on error category, not context).\n * First occurrence of a new error category logs as warn, repeats as debug.\n *\n * @param conn Device connection\n * @param context Error context (for debug messages only)\n * @param err Error object\n */\n private logDeviceError(\n conn: DeviceConnection,\n context: string,\n err: unknown,\n ): void {\n const errorCode = classifyError(err);\n const isRepeat = errorCode === conn.lastErrorCode;\n conn.lastErrorCode = errorCode;\n\n if (isRepeat) {\n this.log.debug(\n `${conn.config.productName} ${context}: ${err instanceof Error ? err.message : String(err)}`,\n );\n } else if (errorCode === \"NETWORK\") {\n this.log.warn(\n `${conn.config.productName}: device unreachable \u2014 will keep retrying`,\n );\n } else {\n this.log.warn(\n `${conn.config.productName} ${context}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n}\n\nif (require.main !== module) {\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) =>\n new HomeWizard(options);\n} else {\n (() => new HomeWizard())();\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/io-package.json CHANGED
@@ -1,8 +1,21 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "homewizard",
4
- "version": "0.6.1",
4
+ "version": "0.6.2",
5
5
  "news": {
6
+ "0.6.2": {
7
+ "en": "Fix hanging promise on response stream error, safe onUnload with try/finally, optimize state creation hot path (setObjectNotExistsAsync), remove dead code",
8
+ "de": "Hängendes Promise bei Response-Stream-Fehler behoben, sicheres onUnload mit try/finally, State-Erstellung im Hot Path optimiert (setObjectNotExistsAsync), toten Code entfernt",
9
+ "ru": "Исправлен зависший промис при ошибке потока ответа, безопасный onUnload с try/finally, оптимизация создания состояний (setObjectNotExistsAsync), удалён мёртвый код",
10
+ "pt": "Corrigida promessa pendente em erro de stream de resposta, onUnload seguro com try/finally, otimização da criação de estados (setObjectNotExistsAsync), código morto removido",
11
+ "nl": "Vastlopende promise bij response stream fout opgelost, veilige onUnload met try/finally, state-creatie hot path geoptimaliseerd (setObjectNotExistsAsync), dode code verwijderd",
12
+ "fr": "Correction de la promesse bloquée lors d'une erreur de flux de réponse, onUnload sécurisé avec try/finally, optimisation du chemin critique de création d'états (setObjectNotExistsAsync), code mort supprimé",
13
+ "it": "Corretta promessa bloccata su errore stream di risposta, onUnload sicuro con try/finally, ottimizzazione percorso critico creazione stati (setObjectNotExistsAsync), codice morto rimosso",
14
+ "es": "Corregida promesa colgada en error de flujo de respuesta, onUnload seguro con try/finally, optimización de ruta crítica de creación de estados (setObjectNotExistsAsync), código muerto eliminado",
15
+ "pl": "Naprawiono zawieszającą się obietnicę przy błędzie strumienia odpowiedzi, bezpieczne onUnload z try/finally, optymalizacja ścieżki krytycznej tworzenia stanów (setObjectNotExistsAsync), usunięto martwy kod",
16
+ "uk": "Виправлено завислий проміс при помилці потоку відповіді, безпечний onUnload з try/finally, оптимізація критичного шляху створення станів (setObjectNotExistsAsync), видалено мертвий код",
17
+ "zh-cn": "修复响应流错误时挂起的Promise,使用try/finally保护onUnload,优化状态创建热路径(setObjectNotExistsAsync),移除死代码"
18
+ },
6
19
  "0.6.1": {
7
20
  "en": "Code cleanup: extract testable connection-utils module, add 20 unit tests, fix ESLint warnings, remove unused dependencies",
8
21
  "de": "Code-Bereinigung: testbares connection-utils Modul extrahiert, 20 Unit-Tests hinzugefügt, ESLint-Warnungen behoben, ungenutzte Abhängigkeiten entfernt",
@@ -80,19 +93,6 @@
80
93
  "pl": "Dane pomiarowe przeniesione do kanalu measurement/, czyszczenie starych punktow danych przy aktualizacji",
81
94
  "uk": "Данi вимiрювань перемiщено в канал measurement/, очищення старих точок даних при оновленнi",
82
95
  "zh-cn": "测量数据移至 measurement/ 通道,升级时清理旧数据点"
83
- },
84
- "0.4.0": {
85
- "en": "Move measurement data into measurement/ channel, cleanup old datapoints, add online/offline status icon",
86
- "de": "Messdaten in measurement/ Channel verschoben, alte Datenpunkte aufgeraeumt, Online/Offline-Statusicon",
87
- "ru": "Данные измерений перемещены в канал measurement/, очистка старых точек данных, иконка онлайн/офлайн статуса",
88
- "pt": "Dados de medicao movidos para canal measurement/, limpeza de pontos de dados antigos, icone de status online/offline",
89
- "nl": "Meetgegevens verplaatst naar measurement/ kanaal, oude datapunten opgeruimd, online/offline statusicoon",
90
- "fr": "Donnees de mesure deplacees dans le canal measurement/, nettoyage des anciens points de donnees, icone de statut en ligne/hors ligne",
91
- "it": "Dati di misurazione spostati nel canale measurement/, pulizia vecchi punti dati, icona stato online/offline",
92
- "es": "Datos de medicion movidos al canal measurement/, limpieza de puntos de datos antiguos, icono de estado online/offline",
93
- "pl": "Dane pomiarowe przeniesione do kanalu measurement/, czyszczenie starych punktow danych, ikona statusu online/offline",
94
- "uk": "Данi вимiрювань перемiщено в канал measurement/, очищення старих точок даних, iконка статусу онлайн/офлайн",
95
- "zh-cn": "测量数据移至 measurement/ 通道,清理旧数据点,添加在线/离线状态图标"
96
96
  }
97
97
  },
98
98
  "titleLang": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.homewizard",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "ioBroker adapter for HomeWizard Energy devices — real-time energy monitoring via API v2 with WebSocket push",
5
5
  "author": {
6
6
  "name": "krobi",