iobroker.homewizard 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/admin/i18n/de/translations.json +0 -1
- package/admin/i18n/en/translations.json +0 -1
- package/admin/i18n/es/translations.json +0 -1
- package/admin/i18n/fr/translations.json +0 -1
- package/admin/i18n/it/translations.json +0 -1
- package/admin/i18n/nl/translations.json +0 -1
- package/admin/i18n/pl/translations.json +0 -1
- package/admin/i18n/pt/translations.json +0 -1
- package/admin/i18n/ru/translations.json +0 -1
- package/admin/i18n/uk/translations.json +0 -1
- package/admin/i18n/zh-cn/translations.json +0 -1
- package/admin/jsonConfig.json +4 -9
- package/build/lib/cacert.js +66 -0
- package/build/lib/cacert.js.map +7 -0
- package/build/lib/homewizard-client.js +2 -2
- package/build/lib/homewizard-client.js.map +2 -2
- package/build/lib/types.js.map +1 -1
- package/build/lib/websocket-client.js +2 -2
- package/build/lib/websocket-client.js.map +2 -2
- package/build/main.js +152 -96
- package/build/main.js.map +2 -2
- package/io-package.json +27 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -134,6 +134,14 @@ homewizard.0.
|
|
|
134
134
|
|
|
135
135
|
## Changelog
|
|
136
136
|
|
|
137
|
+
### 0.1.3 (2026-04-04)
|
|
138
|
+
- Remove IP from config — devices discovered via mDNS at runtime
|
|
139
|
+
- mDNS discovery runs permanently, automatic IP updates on DHCP changes
|
|
140
|
+
|
|
141
|
+
### 0.1.2 (2026-04-04)
|
|
142
|
+
- Bundle HomeWizard CA certificate for proper TLS validation
|
|
143
|
+
- Replace `rejectUnauthorized: false` with CA-based cert chain validation
|
|
144
|
+
|
|
137
145
|
### 0.1.1 (2026-04-04)
|
|
138
146
|
- Add unit tests (129 tests)
|
|
139
147
|
- Fix Dependabot config
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"colProductName": "Name",
|
|
6
6
|
"colProductType": "Typ",
|
|
7
7
|
"colSerial": "Seriennummer",
|
|
8
|
-
"colIP": "IP-Adresse",
|
|
9
8
|
"colToken": "Token",
|
|
10
9
|
"headerPairing": "Kopplung",
|
|
11
10
|
"pairingInfo": "Um ein neues Gerät hinzuzufügen: Setze den Datenpunkt 'startPairing' auf true und drücke dann innerhalb von 60 Sekunden den physischen Knopf am HomeWizard-Gerät. Das Gerät wird automatisch erkannt und zur Liste hinzugefügt.",
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"colProductName": "Name",
|
|
6
6
|
"colProductType": "Type",
|
|
7
7
|
"colSerial": "Serial",
|
|
8
|
-
"colIP": "IP Address",
|
|
9
8
|
"colToken": "Token",
|
|
10
9
|
"headerPairing": "Pairing",
|
|
11
10
|
"pairingInfo": "To add a new device: Set the 'startPairing' data point to true, then press the physical button on your HomeWizard device within 60 seconds. The device will be discovered automatically and added to the list above.",
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"colProductName": "Nombre",
|
|
6
6
|
"colProductType": "Tipo",
|
|
7
7
|
"colSerial": "Número de serie",
|
|
8
|
-
"colIP": "Dirección IP",
|
|
9
8
|
"colToken": "Token",
|
|
10
9
|
"headerPairing": "Emparejamiento",
|
|
11
10
|
"pairingInfo": "Para añadir un dispositivo: establece el punto de datos 'startPairing' en true y luego presiona el botón físico en tu dispositivo HomeWizard dentro de 60 segundos. El dispositivo se descubrirá automáticamente.",
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"colProductName": "Nom",
|
|
6
6
|
"colProductType": "Type",
|
|
7
7
|
"colSerial": "Numéro de série",
|
|
8
|
-
"colIP": "Adresse IP",
|
|
9
8
|
"colToken": "Jeton",
|
|
10
9
|
"headerPairing": "Appairage",
|
|
11
10
|
"pairingInfo": "Pour ajouter un appareil : définissez le point de données 'startPairing' sur true, puis appuyez sur le bouton physique de votre appareil HomeWizard dans les 60 secondes. L'appareil sera découvert automatiquement.",
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"colProductName": "Nome",
|
|
6
6
|
"colProductType": "Tipo",
|
|
7
7
|
"colSerial": "Numero di serie",
|
|
8
|
-
"colIP": "Indirizzo IP",
|
|
9
8
|
"colToken": "Token",
|
|
10
9
|
"headerPairing": "Associazione",
|
|
11
10
|
"pairingInfo": "Per aggiungere un dispositivo: imposta il punto dati 'startPairing' su true, poi premi il pulsante fisico sul dispositivo HomeWizard entro 60 secondi. Il dispositivo verrà scoperto automaticamente.",
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"colProductName": "Naam",
|
|
6
6
|
"colProductType": "Type",
|
|
7
7
|
"colSerial": "Serienummer",
|
|
8
|
-
"colIP": "IP-adres",
|
|
9
8
|
"colToken": "Token",
|
|
10
9
|
"headerPairing": "Koppelen",
|
|
11
10
|
"pairingInfo": "Om een apparaat toe te voegen: zet het datapunt 'startPairing' op true en druk vervolgens binnen 60 seconden op de fysieke knop op je HomeWizard-apparaat. Het apparaat wordt automatisch gevonden en toegevoegd.",
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"colProductName": "Nazwa",
|
|
6
6
|
"colProductType": "Typ",
|
|
7
7
|
"colSerial": "Numer seryjny",
|
|
8
|
-
"colIP": "Adres IP",
|
|
9
8
|
"colToken": "Token",
|
|
10
9
|
"headerPairing": "Parowanie",
|
|
11
10
|
"pairingInfo": "Aby dodać urządzenie: ustaw punkt danych 'startPairing' na true, a następnie naciśnij fizyczny przycisk na urządzeniu HomeWizard w ciągu 60 sekund. Urządzenie zostanie wykryte automatycznie.",
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"colProductName": "Nome",
|
|
6
6
|
"colProductType": "Tipo",
|
|
7
7
|
"colSerial": "Número de série",
|
|
8
|
-
"colIP": "Endereço IP",
|
|
9
8
|
"colToken": "Token",
|
|
10
9
|
"headerPairing": "Emparelhamento",
|
|
11
10
|
"pairingInfo": "Para adicionar um dispositivo: defina o ponto de dados 'startPairing' como true e pressione o botão físico no dispositivo HomeWizard dentro de 60 segundos. O dispositivo será descoberto automaticamente.",
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"colProductName": "Имя",
|
|
6
6
|
"colProductType": "Тип",
|
|
7
7
|
"colSerial": "Серийный номер",
|
|
8
|
-
"colIP": "IP-адрес",
|
|
9
8
|
"colToken": "Токен",
|
|
10
9
|
"headerPairing": "Сопряжение",
|
|
11
10
|
"pairingInfo": "Чтобы добавить устройство: установите точку данных 'startPairing' в true, затем нажмите физическую кнопку на устройстве HomeWizard в течение 60 секунд. Устройство будет обнаружено автоматически.",
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"colProductName": "Назва",
|
|
6
6
|
"colProductType": "Тип",
|
|
7
7
|
"colSerial": "Серійний номер",
|
|
8
|
-
"colIP": "IP-адреса",
|
|
9
8
|
"colToken": "Токен",
|
|
10
9
|
"headerPairing": "Сполучення",
|
|
11
10
|
"pairingInfo": "Щоб додати пристрій: встановіть точку даних 'startPairing' на true, потім натисніть фізичну кнопку на пристрої HomeWizard протягом 60 секунд. Пристрій буде виявлено автоматично.",
|
package/admin/jsonConfig.json
CHANGED
|
@@ -36,24 +36,19 @@
|
|
|
36
36
|
"type": "text",
|
|
37
37
|
"attr": "serial",
|
|
38
38
|
"title": "colSerial",
|
|
39
|
-
"width": "
|
|
39
|
+
"width": "25%",
|
|
40
40
|
"readOnly": true
|
|
41
41
|
},
|
|
42
|
-
{
|
|
43
|
-
"type": "text",
|
|
44
|
-
"attr": "ip",
|
|
45
|
-
"title": "colIP",
|
|
46
|
-
"width": "20%"
|
|
47
|
-
},
|
|
48
42
|
{
|
|
49
43
|
"type": "text",
|
|
50
44
|
"attr": "token",
|
|
51
45
|
"title": "colToken",
|
|
52
|
-
"width": "
|
|
46
|
+
"width": "20%",
|
|
53
47
|
"readOnly": true
|
|
54
48
|
}
|
|
55
49
|
],
|
|
56
|
-
"noDelete": false
|
|
50
|
+
"noDelete": false,
|
|
51
|
+
"noAdd": true
|
|
57
52
|
},
|
|
58
53
|
"_headerPairing": {
|
|
59
54
|
"newLine": true,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var cacert_exports = {};
|
|
30
|
+
__export(cacert_exports, {
|
|
31
|
+
HOMEWIZARD_CA_CERT: () => HOMEWIZARD_CA_CERT,
|
|
32
|
+
HW_AGENT: () => HW_AGENT
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(cacert_exports);
|
|
35
|
+
var https = __toESM(require("node:https"));
|
|
36
|
+
const HOMEWIZARD_CA_CERT = `-----BEGIN CERTIFICATE-----
|
|
37
|
+
MIIDITCCAgkCFDn7cwYLioTM3VxdAygLl/Px9ovFMA0GCSqGSIb3DQEBCwUAME0x
|
|
38
|
+
CzAJBgNVBAYTAk5MMQswCQYDVQQIDAJaSDETMBEGA1UECgwKSG9tZVdpemFyZDEc
|
|
39
|
+
MBoGA1UEAwwTQXBwbGlhbmNlIEFjY2VzcyBDQTAeFw0yMTEyMTgxOTEyMTJaFw0z
|
|
40
|
+
MTEyMTYxOTEyMTJaME0xCzAJBgNVBAYTAk5MMQswCQYDVQQIDAJaSDETMBEGA1UE
|
|
41
|
+
CgwKSG9tZVdpemFyZDEcMBoGA1UEAwwTQXBwbGlhbmNlIEFjY2VzcyBDQTCCASIw
|
|
42
|
+
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPBIvW8NRffqdvzHZY0M32fQHiGm
|
|
43
|
+
pJgNGhiaQmpJfRDhT9yihM0S/hYcN8IqnfrMqoCQb/56Ub0+dZizmtfcGsE+Lpm1
|
|
44
|
+
K1znkWqSDlpnuTNOb70TrsxBmbFuNOZQEi/xOjzT2j98wT0GSfxz1RVq6lZhDRRz
|
|
45
|
+
xoe08+Xo4+ttUGanfOggJi0BXygeFEVBpbctVVJ9EgqeEE9itjcMlcxMe1QN14f8
|
|
46
|
+
hCcOnId+9PSsdmyUCLrTB0FVYrbNfbJPk/vMU57fu6swBjWhYBxPx9ZhFy+7WnPR
|
|
47
|
+
9BFg4seHNVQIqZNrf1YwBXlmZQIL32SRPaiH/+AVNMrYGXBvncY0Km6ZHIMCAwEA
|
|
48
|
+
ATANBgkqhkiG9w0BAQsFAAOCAQEA6ybM8xm0PCXg8Rr/q0v1vPxQy44PmwXTDj0e
|
|
49
|
+
r2vW4ZMiEwXZCp0Kk2K16KJYz4iJyfiQk8ikAIMiRSbyXzmyQ7XmL1O4l4d8E1Pg
|
|
50
|
+
8EImvcyoBxFhd0Lq7VKriLc8Bw8SXbahPMGT+Y8Yz0uIsLAYVwlkLfgppVPmBaLD
|
|
51
|
+
QautcQnI8WxPvCIQf5anyzgAyJC5ac6/CkB+iyPcuWcG3RMYvXnC0QoTlRa5YMlE
|
|
52
|
+
FweVDlT2C/MdDyOxiAD/H1EP/eaySnU0zsxyD0yNFRKsQfQ+UJEPd2GS1AGA1lTy
|
|
53
|
+
CGdyYj/Gghrusw0hM4rYXQSERWGF0mpEnuJ+7bHDolHu0rzgTQ==
|
|
54
|
+
-----END CERTIFICATE-----`;
|
|
55
|
+
const HW_AGENT = new https.Agent({
|
|
56
|
+
ca: HOMEWIZARD_CA_CERT,
|
|
57
|
+
rejectUnauthorized: true,
|
|
58
|
+
// Device certs use appliance/type/serial as hostname — not matchable via IP
|
|
59
|
+
checkServerIdentity: () => void 0
|
|
60
|
+
});
|
|
61
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
62
|
+
0 && (module.exports = {
|
|
63
|
+
HOMEWIZARD_CA_CERT,
|
|
64
|
+
HW_AGENT
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=cacert.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/cacert.ts"],
|
|
4
|
+
"sourcesContent": ["import * as https from \"node:https\";\n\n/**\n * HomeWizard Appliance Access CA certificate.\n * Used to validate the self-signed TLS certificates of HomeWizard devices.\n *\n * Issuer: C=NL, ST=ZH, O=HomeWizard, CN=Appliance Access CA\n * Valid: 2021-12-18 to 2031-12-16\n *\n * Source: https://github.com/homewizard/python-homewizard-energy\n */\nexport const HOMEWIZARD_CA_CERT = `-----BEGIN CERTIFICATE-----\nMIIDITCCAgkCFDn7cwYLioTM3VxdAygLl/Px9ovFMA0GCSqGSIb3DQEBCwUAME0x\nCzAJBgNVBAYTAk5MMQswCQYDVQQIDAJaSDETMBEGA1UECgwKSG9tZVdpemFyZDEc\nMBoGA1UEAwwTQXBwbGlhbmNlIEFjY2VzcyBDQTAeFw0yMTEyMTgxOTEyMTJaFw0z\nMTEyMTYxOTEyMTJaME0xCzAJBgNVBAYTAk5MMQswCQYDVQQIDAJaSDETMBEGA1UE\nCgwKSG9tZVdpemFyZDEcMBoGA1UEAwwTQXBwbGlhbmNlIEFjY2VzcyBDQTCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPBIvW8NRffqdvzHZY0M32fQHiGm\npJgNGhiaQmpJfRDhT9yihM0S/hYcN8IqnfrMqoCQb/56Ub0+dZizmtfcGsE+Lpm1\nK1znkWqSDlpnuTNOb70TrsxBmbFuNOZQEi/xOjzT2j98wT0GSfxz1RVq6lZhDRRz\nxoe08+Xo4+ttUGanfOggJi0BXygeFEVBpbctVVJ9EgqeEE9itjcMlcxMe1QN14f8\nhCcOnId+9PSsdmyUCLrTB0FVYrbNfbJPk/vMU57fu6swBjWhYBxPx9ZhFy+7WnPR\n9BFg4seHNVQIqZNrf1YwBXlmZQIL32SRPaiH/+AVNMrYGXBvncY0Km6ZHIMCAwEA\nATANBgkqhkiG9w0BAQsFAAOCAQEA6ybM8xm0PCXg8Rr/q0v1vPxQy44PmwXTDj0e\nr2vW4ZMiEwXZCp0Kk2K16KJYz4iJyfiQk8ikAIMiRSbyXzmyQ7XmL1O4l4d8E1Pg\n8EImvcyoBxFhd0Lq7VKriLc8Bw8SXbahPMGT+Y8Yz0uIsLAYVwlkLfgppVPmBaLD\nQautcQnI8WxPvCIQf5anyzgAyJC5ac6/CkB+iyPcuWcG3RMYvXnC0QoTlRa5YMlE\nFweVDlT2C/MdDyOxiAD/H1EP/eaySnU0zsxyD0yNFRKsQfQ+UJEPd2GS1AGA1lTy\nCGdyYj/Gghrusw0hM4rYXQSERWGF0mpEnuJ+7bHDolHu0rzgTQ==\n-----END CERTIFICATE-----`;\n\n/**\n * Shared HTTPS agent for HomeWizard connections.\n * Validates cert chain against the HomeWizard CA certificate.\n * Hostname verification is skipped because device certs use\n * non-standard hostnames (appliance/type/serial).\n */\nexport const HW_AGENT = new https.Agent({\n ca: HOMEWIZARD_CA_CERT,\n rejectUnauthorized: true,\n // Device certs use appliance/type/serial as hostname \u2014 not matchable via IP\n checkServerIdentity: () => undefined,\n});\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAAuB;AAWhB,MAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0B3B,MAAM,WAAW,IAAI,MAAM,MAAM;AAAA,EACtC,IAAI;AAAA,EACJ,oBAAoB;AAAA;AAAA,EAEpB,qBAAqB,MAAM;AAC7B,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -33,6 +33,7 @@ __export(homewizard_client_exports, {
|
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(homewizard_client_exports);
|
|
35
35
|
var https = __toESM(require("node:https"));
|
|
36
|
+
var import_cacert = require("./cacert");
|
|
36
37
|
class HomeWizardClient {
|
|
37
38
|
ip;
|
|
38
39
|
token;
|
|
@@ -115,8 +116,7 @@ class HomeWizardClient {
|
|
|
115
116
|
path,
|
|
116
117
|
method,
|
|
117
118
|
headers,
|
|
118
|
-
|
|
119
|
-
// HomeWizard uses self-signed certs
|
|
119
|
+
agent: import_cacert.HW_AGENT,
|
|
120
120
|
timeout: 1e4
|
|
121
121
|
},
|
|
122
122
|
(res) => {
|
|
@@ -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 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
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAAuB;
|
|
4
|
+
"sourcesContent": ["import * as https from \"node:https\";\nimport { HW_AGENT } from \"./cacert\";\nimport type {\n BatteryControl,\n DeviceInfo,\n Measurement,\n PairingResponse,\n SystemInfo,\n} from \"./types\";\n\n/** HTTPS client for HomeWizard API v2 */\nexport class HomeWizardClient {\n private readonly ip: string;\n private readonly token: string;\n\n /**\n * @param ip Device IP address\n * @param token Bearer token (empty string for pairing requests)\n */\n constructor(ip: string, token: string = \"\") {\n this.ip = ip;\n this.token = token;\n }\n\n /** Get device info (GET /api) */\n async getDeviceInfo(): Promise<DeviceInfo> {\n return this.request<DeviceInfo>(\"GET\", \"/api\");\n }\n\n /** Request pairing token (POST /api/user) \u2014 403 until button pressed */\n async requestPairing(): Promise<PairingResponse> {\n return this.request<PairingResponse>(\"POST\", \"/api/user\", {\n name: \"local/iobroker\",\n });\n }\n\n /** Get current measurement (REST fallback) */\n async getMeasurement(): Promise<Measurement> {\n return this.request<Measurement>(\"GET\", \"/api/measurement\");\n }\n\n /** Get system info */\n async getSystem(): Promise<SystemInfo> {\n return this.request<SystemInfo>(\"GET\", \"/api/system\");\n }\n\n /**\n * Update system settings\n *\n * @param settings System settings to update\n */\n async setSystem(settings: Partial<SystemInfo>): Promise<SystemInfo> {\n return this.request<SystemInfo>(\"PUT\", \"/api/system\", settings);\n }\n\n /** Reboot device */\n async reboot(): Promise<void> {\n await this.request(\"PUT\", \"/api/system/reboot\");\n }\n\n /** Identify device (blink LED) */\n async identify(): Promise<void> {\n await this.request(\"PUT\", \"/api/system/identify\");\n }\n\n /** Get battery control status */\n async getBatteries(): Promise<BatteryControl> {\n return this.request<BatteryControl>(\"GET\", \"/api/batteries\");\n }\n\n /**\n * Set battery control\n *\n * @param settings Battery control settings to update\n */\n async setBatteries(\n settings: Partial<BatteryControl>,\n ): Promise<BatteryControl> {\n return this.request<BatteryControl>(\"PUT\", \"/api/batteries\", settings);\n }\n\n /**\n * @param method HTTP method\n * @param path API path\n * @param body Optional request body\n */\n private request<T>(method: string, path: string, body?: unknown): Promise<T> {\n return new Promise((resolve, reject) => {\n const bodyStr = body ? JSON.stringify(body) : undefined;\n const headers: Record<string, string> = {\n \"X-Api-Version\": \"2\",\n };\n\n if (this.token) {\n headers.Authorization = `Bearer ${this.token}`;\n }\n if (bodyStr) {\n headers[\"Content-Type\"] = \"application/json\";\n headers[\"Content-Length\"] = Buffer.byteLength(bodyStr).toString();\n }\n\n const req = https.request(\n {\n hostname: this.ip,\n port: 443,\n path,\n method,\n headers,\n agent: HW_AGENT,\n timeout: 10_000,\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n res.on(\"end\", () => {\n const data = Buffer.concat(chunks).toString();\n if (!res.statusCode || res.statusCode >= 400) {\n const error = new HomeWizardApiError(\n res.statusCode ?? 0,\n data,\n `${method} ${path}`,\n );\n reject(error);\n return;\n }\n if (!data) {\n resolve(undefined as T);\n return;\n }\n try {\n resolve(JSON.parse(data) as T);\n } catch {\n reject(\n new Error(\n `Invalid JSON from ${method} ${path}: ${data.substring(0, 200)}`,\n ),\n );\n }\n });\n },\n );\n\n req.on(\"error\", reject);\n req.on(\"timeout\", () => {\n req.destroy(new Error(`Timeout: ${method} ${path}`));\n });\n\n if (bodyStr) {\n req.write(bodyStr);\n }\n req.end();\n });\n }\n}\n\n/** API error with status code and parsed error body */\nexport class HomeWizardApiError extends Error {\n readonly statusCode: number;\n readonly errorCode: string;\n\n /**\n * @param statusCode HTTP status code\n * @param body Response body\n * @param context Request context for error message\n */\n constructor(statusCode: number, body: string, context: string) {\n let errorCode = \"unknown\";\n let description = body;\n try {\n const parsed = JSON.parse(body);\n errorCode = parsed.error?.code ?? parsed.error ?? \"unknown\";\n description = parsed.error?.description ?? parsed.error?.code ?? body;\n } catch {\n // body is not JSON\n }\n super(`${context}: HTTP ${statusCode} \u2014 ${description}`);\n this.name = \"HomeWizardApiError\";\n this.statusCode = statusCode;\n this.errorCode = errorCode;\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAAuB;AACvB,oBAAyB;AAUlB,MAAM,iBAAiB;AAAA,EACX;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjB,YAAY,IAAY,QAAgB,IAAI;AAC1C,SAAK,KAAK;AACV,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAGA,MAAM,gBAAqC;AACzC,WAAO,KAAK,QAAoB,OAAO,MAAM;AAAA,EAC/C;AAAA;AAAA,EAGA,MAAM,iBAA2C;AAC/C,WAAO,KAAK,QAAyB,QAAQ,aAAa;AAAA,MACxD,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,iBAAuC;AAC3C,WAAO,KAAK,QAAqB,OAAO,kBAAkB;AAAA,EAC5D;AAAA;AAAA,EAGA,MAAM,YAAiC;AACrC,WAAO,KAAK,QAAoB,OAAO,aAAa;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAU,UAAoD;AAClE,WAAO,KAAK,QAAoB,OAAO,eAAe,QAAQ;AAAA,EAChE;AAAA;AAAA,EAGA,MAAM,SAAwB;AAC5B,UAAM,KAAK,QAAQ,OAAO,oBAAoB;AAAA,EAChD;AAAA;AAAA,EAGA,MAAM,WAA0B;AAC9B,UAAM,KAAK,QAAQ,OAAO,sBAAsB;AAAA,EAClD;AAAA;AAAA,EAGA,MAAM,eAAwC;AAC5C,WAAO,KAAK,QAAwB,OAAO,gBAAgB;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aACJ,UACyB;AACzB,WAAO,KAAK,QAAwB,OAAO,kBAAkB,QAAQ;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,QAAW,QAAgB,MAAc,MAA4B;AAC3E,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,UAAU,OAAO,KAAK,UAAU,IAAI,IAAI;AAC9C,YAAM,UAAkC;AAAA,QACtC,iBAAiB;AAAA,MACnB;AAEA,UAAI,KAAK,OAAO;AACd,gBAAQ,gBAAgB,UAAU,KAAK,KAAK;AAAA,MAC9C;AACA,UAAI,SAAS;AACX,gBAAQ,cAAc,IAAI;AAC1B,gBAAQ,gBAAgB,IAAI,OAAO,WAAW,OAAO,EAAE,SAAS;AAAA,MAClE;AAEA,YAAM,MAAM,MAAM;AAAA,QAChB;AAAA,UACE,UAAU,KAAK;AAAA,UACf,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA;AAAA,UACA,OAAO;AAAA,UACP,SAAS;AAAA,QACX;AAAA,QACA,CAAC,QAAQ;AACP,gBAAM,SAAmB,CAAC;AAC1B,cAAI,GAAG,QAAQ,CAAC,UAAkB,OAAO,KAAK,KAAK,CAAC;AACpD,cAAI,GAAG,OAAO,MAAM;AAlH9B;AAmHY,kBAAM,OAAO,OAAO,OAAO,MAAM,EAAE,SAAS;AAC5C,gBAAI,CAAC,IAAI,cAAc,IAAI,cAAc,KAAK;AAC5C,oBAAM,QAAQ,IAAI;AAAA,iBAChB,SAAI,eAAJ,YAAkB;AAAA,gBAClB;AAAA,gBACA,GAAG,MAAM,IAAI,IAAI;AAAA,cACnB;AACA,qBAAO,KAAK;AACZ;AAAA,YACF;AACA,gBAAI,CAAC,MAAM;AACT,sBAAQ,MAAc;AACtB;AAAA,YACF;AACA,gBAAI;AACF,sBAAQ,KAAK,MAAM,IAAI,CAAM;AAAA,YAC/B,QAAQ;AACN;AAAA,gBACE,IAAI;AAAA,kBACF,qBAAqB,MAAM,IAAI,IAAI,KAAK,KAAK,UAAU,GAAG,GAAG,CAAC;AAAA,gBAChE;AAAA,cACF;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAEA,UAAI,GAAG,SAAS,MAAM;AACtB,UAAI,GAAG,WAAW,MAAM;AACtB,YAAI,QAAQ,IAAI,MAAM,YAAY,MAAM,IAAI,IAAI,EAAE,CAAC;AAAA,MACrD,CAAC;AAED,UAAI,SAAS;AACX,YAAI,MAAM,OAAO;AAAA,MACnB;AACA,UAAI,IAAI;AAAA,IACV,CAAC;AAAA,EACH;AACF;AAGO,MAAM,2BAA2B,MAAM;AAAA,EACnC;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT,YAAY,YAAoB,MAAc,SAAiB;AArKjE;AAsKI,QAAI,YAAY;AAChB,QAAI,cAAc;AAClB,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,mBAAY,wBAAO,UAAP,mBAAc,SAAd,YAAsB,OAAO,UAA7B,YAAsC;AAClD,qBAAc,wBAAO,UAAP,mBAAc,gBAAd,aAA6B,YAAO,UAAP,mBAAc,SAA3C,YAAmD;AAAA,IACnE,QAAQ;AAAA,IAER;AACA,UAAM,GAAG,OAAO,UAAU,UAAU,WAAM,WAAW,EAAE;AACvD,SAAK,OAAO;AACZ,SAAK,aAAa;AAClB,SAAK,YAAY;AAAA,EACnB;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/lib/types.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/types.ts"],
|
|
4
|
-
"sourcesContent": ["import type { HomeWizardWebSocket } from \"./websocket-client\";\n\n/** HomeWizard adapter configuration stored in native */\nexport interface AdapterConfig {\n /** Array of paired device configs (tokens encrypted via encryptedNative) */\n devices: DeviceConfig[];\n}\n\n/** Persisted config for a single paired device */\nexport interface DeviceConfig {\n /**
|
|
4
|
+
"sourcesContent": ["import type { HomeWizardWebSocket } from \"./websocket-client\";\n\n/** HomeWizard adapter configuration stored in native */\nexport interface AdapterConfig {\n /** Array of paired device configs (tokens encrypted via encryptedNative) */\n devices: DeviceConfig[];\n}\n\n/** Persisted config for a single paired device */\nexport interface DeviceConfig {\n /** Bearer token from pairing */\n token: string;\n /** Product type (e.g. HWE-P1) */\n productType: string;\n /** Device serial number */\n serial: string;\n /** Human-readable product name */\n productName: string;\n}\n\n/** Response from GET /api */\nexport interface DeviceInfo {\n /** Product name */\n product_name: string;\n /** Product type identifier */\n product_type: string;\n /** Device serial number */\n serial: string;\n /** Firmware version string */\n firmware_version: string;\n /** API version string */\n api_version: string;\n}\n\n/** Response from POST /api/user (pairing) */\nexport interface PairingResponse {\n /** Bearer token for API access */\n token: string;\n}\n\n/** Measurement data from GET /api/measurement or WebSocket push */\nexport interface Measurement {\n /** Unique meter identifier */\n unique_id?: string;\n /** Protocol version */\n protocol_version?: string;\n /** Meter model */\n meter_model?: string;\n /** Measurement timestamp */\n timestamp?: string;\n /** Active tariff number */\n tariff?: number;\n\n /** Total energy import in kWh */\n energy_import_kwh?: number;\n /** Energy import tariff 1 */\n energy_import_t1_kwh?: number;\n /** Energy import tariff 2 */\n energy_import_t2_kwh?: number;\n /** Energy import tariff 3 */\n energy_import_t3_kwh?: number;\n /** Energy import tariff 4 */\n energy_import_t4_kwh?: number;\n /** Total energy export in kWh */\n energy_export_kwh?: number;\n /** Energy export tariff 1 */\n energy_export_t1_kwh?: number;\n /** Energy export tariff 2 */\n energy_export_t2_kwh?: number;\n /** Energy export tariff 3 */\n energy_export_t3_kwh?: number;\n /** Energy export tariff 4 */\n energy_export_t4_kwh?: number;\n\n /** Total active power in W */\n power_w?: number;\n /** Active power phase 1 */\n power_l1_w?: number;\n /** Active power phase 2 */\n power_l2_w?: number;\n /** Active power phase 3 */\n power_l3_w?: number;\n\n /** Voltage (single phase) */\n voltage_v?: number;\n /** Voltage phase 1 */\n voltage_l1_v?: number;\n /** Voltage phase 2 */\n voltage_l2_v?: number;\n /** Voltage phase 3 */\n voltage_l3_v?: number;\n\n /** Current (single phase) */\n current_a?: number;\n /** Current phase 1 */\n current_l1_a?: number;\n /** Current phase 2 */\n current_l2_a?: number;\n /** Current phase 3 */\n current_l3_a?: number;\n\n /** Grid frequency in Hz */\n frequency_hz?: number;\n\n /** Voltage sag count phase 1 */\n voltage_sag_l1_count?: number;\n /** Voltage sag count phase 2 */\n voltage_sag_l2_count?: number;\n /** Voltage sag count phase 3 */\n voltage_sag_l3_count?: number;\n /** Voltage swell count phase 1 */\n voltage_swell_l1_count?: number;\n /** Voltage swell count phase 2 */\n voltage_swell_l2_count?: number;\n /** Voltage swell count phase 3 */\n voltage_swell_l3_count?: number;\n /** Any power fail count */\n any_power_fail_count?: number;\n /** Long power fail count */\n long_power_fail_count?: number;\n\n /** Average power over 15 min (Belgium) */\n average_power_15m_w?: number;\n /** Monthly power peak (Belgium) */\n monthly_power_peak_w?: number;\n /** Monthly power peak timestamp (Belgium) */\n monthly_power_peak_timestamp?: string;\n\n /** Apparent current */\n apparent_current_a?: number;\n /** Reactive current */\n reactive_current_a?: number;\n /** Apparent power in VA */\n apparent_power_va?: number;\n /** Reactive power in var */\n reactive_power_var?: number;\n /** Power factor */\n power_factor?: number;\n /** Apparent current phase 1 */\n apparent_current_l1_a?: number;\n /** Apparent current phase 2 */\n apparent_current_l2_a?: number;\n /** Apparent current phase 3 */\n apparent_current_l3_a?: number;\n /** Reactive current phase 1 */\n reactive_current_l1_a?: number;\n /** Reactive current phase 2 */\n reactive_current_l2_a?: number;\n /** Reactive current phase 3 */\n reactive_current_l3_a?: number;\n /** Apparent power phase 1 */\n apparent_power_l1_va?: number;\n /** Apparent power phase 2 */\n apparent_power_l2_va?: number;\n /** Apparent power phase 3 */\n apparent_power_l3_va?: number;\n /** Reactive power phase 1 */\n reactive_power_l1_var?: number;\n /** Reactive power phase 2 */\n reactive_power_l2_var?: number;\n /** Reactive power phase 3 */\n reactive_power_l3_var?: number;\n /** Power factor phase 1 */\n power_factor_l1?: number;\n /** Power factor phase 2 */\n power_factor_l2?: number;\n /** Power factor phase 3 */\n power_factor_l3?: number;\n\n /** Battery state of charge in percent */\n state_of_charge_pct?: number;\n /** Battery charge cycles */\n cycles?: number;\n\n /** External meters (gas, water, heat) */\n external?: ExternalMeter[];\n}\n\n/** External meter attached to P1 (gas, water, heat) */\nexport interface ExternalMeter {\n /** Unique meter identifier */\n unique_id: string;\n /** Meter type */\n type:\n | \"gas_meter\"\n | \"heat_meter\"\n | \"warm_water_meter\"\n | \"water_meter\"\n | \"inlet_heat_meter\";\n /** Last reading timestamp */\n timestamp: string;\n /** Meter reading value */\n value: number;\n /** Measurement unit */\n unit: string;\n}\n\n/** System info from GET /api/system */\nexport interface SystemInfo {\n /** WiFi SSID */\n wifi_ssid: string;\n /** WiFi signal strength in dB */\n wifi_rssi_db: number;\n /** Uptime in seconds */\n uptime_s: number;\n /** Cloud communication enabled */\n cloud_enabled: boolean;\n /** Status LED brightness 0-100% */\n status_led_brightness_pct: number;\n /** Legacy API v1 enabled */\n api_v1_enabled?: boolean;\n}\n\n/** Battery control from GET /api/batteries */\nexport interface BatteryControl {\n /** Battery mode */\n mode: \"zero\" | \"to_full\" | \"standby\";\n /** Battery permissions */\n permissions?: string[];\n /** Number of connected batteries */\n battery_count?: number;\n /** Current combined power in W */\n power_w?: number;\n /** Target power in W */\n target_power_w?: number;\n /** Maximum consumption in W */\n max_consumption_w?: number;\n /** Maximum production in W */\n max_production_w?: number;\n}\n\n/** WebSocket message envelope */\nexport interface WsMessage {\n /** Message type */\n type: string;\n /** Message data payload */\n data?: Record<string, unknown>;\n}\n\n/** Device discovered via mDNS */\nexport interface DiscoveredDevice {\n /** Device IP address */\n ip: string;\n /** Product type from mDNS TXT record or device info */\n productType: string;\n /** Serial number from mDNS name */\n serial: string;\n /** Human-readable name */\n name: string;\n}\n\n/** Connection state for a single device */\nexport interface DeviceConnection {\n /** Device config */\n config: DeviceConfig;\n /** Current IP address (from mDNS, not persisted) */\n ip: string;\n /** WebSocket client instance (if connected) */\n wsClient: HomeWizardWebSocket | null;\n /** Whether WS is authenticated */\n wsAuthenticated: boolean;\n /** REST fallback polling timer */\n pollTimer: ioBroker.Interval | undefined;\n /** Reconnect timer */\n reconnectTimer: ioBroker.Timeout | undefined;\n /** Consecutive WS failures for backoff */\n wsFailCount: number;\n /** Last error code for dedup */\n lastErrorCode: string;\n}\n\n// Augment the ioBroker global namespace\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace ioBroker {\n interface AdapterConfig {\n /** Array of paired device configs (tokens encrypted via encryptedNative) */\n devices: DeviceConfig[];\n }\n }\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;AAAA;AAAA;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -32,6 +32,7 @@ __export(websocket_client_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(websocket_client_exports);
|
|
34
34
|
var import_ws = __toESM(require("ws"));
|
|
35
|
+
var import_cacert = require("./cacert");
|
|
35
36
|
class HomeWizardWebSocket {
|
|
36
37
|
ip;
|
|
37
38
|
token;
|
|
@@ -57,8 +58,7 @@ class HomeWizardWebSocket {
|
|
|
57
58
|
const url = `wss://${this.ip}/api/ws`;
|
|
58
59
|
this.callbacks.log.debug(`WS connecting to ${url}`);
|
|
59
60
|
this.ws = new import_ws.default(url, {
|
|
60
|
-
|
|
61
|
-
// HomeWizard uses self-signed certs
|
|
61
|
+
agent: import_cacert.HW_AGENT,
|
|
62
62
|
handshakeTimeout: 1e4
|
|
63
63
|
});
|
|
64
64
|
this.ws.on("open", () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/websocket-client.ts"],
|
|
4
|
-
"sourcesContent": ["import WebSocket from \"ws\";\nimport type { Measurement, WsMessage } from \"./types\";\n\n/** Callback interface for WebSocket events */\nexport interface WsCallbacks {\n /** Called when measurement data is received */\n onMeasurement: (data: Measurement) => void;\n /** Called when connection is established and authenticated */\n onConnected: () => void;\n /** Called when connection is lost */\n onDisconnected: (error?: Error) => void;\n /** Log functions */\n log: {\n debug: (msg: string) => void;\n warn: (msg: string) => void;\n };\n}\n\n/**\n * WebSocket client for HomeWizard real-time measurement push.\n * Handles auth handshake, subscription, and auto-reconnect.\n */\nexport class HomeWizardWebSocket {\n private readonly ip: string;\n private readonly token: string;\n private readonly callbacks: WsCallbacks;\n private ws: WebSocket | null = null;\n private destroyed = false;\n\n /**\n * @param ip Device IP address\n * @param token Bearer token\n * @param callbacks Event callbacks\n */\n constructor(ip: string, token: string, callbacks: WsCallbacks) {\n this.ip = ip;\n this.token = token;\n this.callbacks = callbacks;\n }\n\n /** Connect to WebSocket and start auth handshake */\n connect(): void {\n if (this.destroyed) {\n return;\n }\n\n this.cleanup();\n\n const url = `wss://${this.ip}/api/ws`;\n this.callbacks.log.debug(`WS connecting to ${url}`);\n\n this.ws = new WebSocket(url, {\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAAsB;
|
|
4
|
+
"sourcesContent": ["import WebSocket from \"ws\";\nimport { HW_AGENT } from \"./cacert\";\nimport type { Measurement, WsMessage } from \"./types\";\n\n/** Callback interface for WebSocket events */\nexport interface WsCallbacks {\n /** Called when measurement data is received */\n onMeasurement: (data: Measurement) => void;\n /** Called when connection is established and authenticated */\n onConnected: () => void;\n /** Called when connection is lost */\n onDisconnected: (error?: Error) => void;\n /** Log functions */\n log: {\n debug: (msg: string) => void;\n warn: (msg: string) => void;\n };\n}\n\n/**\n * WebSocket client for HomeWizard real-time measurement push.\n * Handles auth handshake, subscription, and auto-reconnect.\n */\nexport class HomeWizardWebSocket {\n private readonly ip: string;\n private readonly token: string;\n private readonly callbacks: WsCallbacks;\n private ws: WebSocket | null = null;\n private destroyed = false;\n\n /**\n * @param ip Device IP address\n * @param token Bearer token\n * @param callbacks Event callbacks\n */\n constructor(ip: string, token: string, callbacks: WsCallbacks) {\n this.ip = ip;\n this.token = token;\n this.callbacks = callbacks;\n }\n\n /** Connect to WebSocket and start auth handshake */\n connect(): void {\n if (this.destroyed) {\n return;\n }\n\n this.cleanup();\n\n const url = `wss://${this.ip}/api/ws`;\n this.callbacks.log.debug(`WS connecting to ${url}`);\n\n this.ws = new WebSocket(url, {\n agent: HW_AGENT,\n handshakeTimeout: 10_000,\n });\n\n this.ws.on(\"open\", () => {\n this.callbacks.log.debug(`WS open to ${this.ip}`);\n });\n\n this.ws.on(\"message\", (raw: WebSocket.RawData) => {\n this.handleMessage(raw);\n });\n\n this.ws.on(\"close\", (code: number, reason: Buffer) => {\n this.callbacks.log.debug(`WS closed: ${code} ${reason.toString()}`);\n this.ws = null;\n if (!this.destroyed) {\n this.callbacks.onDisconnected();\n }\n });\n\n this.ws.on(\"error\", (err: Error) => {\n this.callbacks.log.debug(`WS error: ${err.message}`);\n // close event will follow\n });\n }\n\n /** Gracefully close connection */\n close(): void {\n this.destroyed = true;\n this.cleanup();\n }\n\n /** Whether the WebSocket is currently open */\n get isConnected(): boolean {\n return this.ws?.readyState === WebSocket.OPEN;\n }\n\n /**\n * Handle incoming WebSocket message\n *\n * @param raw Raw message data\n */\n private handleMessage(raw: WebSocket.RawData): void {\n const text = Buffer.isBuffer(raw)\n ? raw.toString(\"utf8\")\n : raw instanceof ArrayBuffer\n ? Buffer.from(raw).toString(\"utf8\")\n : Array.isArray(raw)\n ? Buffer.concat(raw).toString(\"utf8\")\n : \"\";\n let msg: WsMessage;\n try {\n msg = JSON.parse(text) as WsMessage;\n } catch {\n this.callbacks.log.warn(`WS invalid JSON: ${text.substring(0, 200)}`);\n return;\n }\n\n switch (msg.type) {\n case \"authorization_requested\":\n this.callbacks.log.debug(\"WS auth requested, sending token\");\n this.send({ type: \"authorization\", data: { token: this.token } });\n break;\n\n case \"authorized\":\n this.callbacks.log.debug(\"WS authorized, subscribing to measurement\");\n this.send({ type: \"subscribe\", data: { topic: \"measurement\" } });\n this.callbacks.onConnected();\n break;\n\n case \"measurement\":\n if (msg.data) {\n this.callbacks.onMeasurement(msg.data as unknown as Measurement);\n }\n break;\n\n default:\n this.callbacks.log.debug(`WS message type: ${msg.type}`);\n break;\n }\n }\n\n /**\n * Send a message over WebSocket\n *\n * @param msg Message to send\n */\n private send(msg: WsMessage): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n }\n\n /** Close WebSocket without triggering reconnect */\n private cleanup(): void {\n if (this.ws) {\n this.ws.removeAllListeners();\n if (\n this.ws.readyState === WebSocket.OPEN ||\n this.ws.readyState === WebSocket.CONNECTING\n ) {\n this.ws.close();\n }\n this.ws = null;\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["WebSocket"]
|
|
7
7
|
}
|
package/build/main.js
CHANGED
|
@@ -67,15 +67,75 @@ class HomeWizard extends utils.Adapter {
|
|
|
67
67
|
await this.setStateAsync("info.connection", { val: false, ack: true });
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
|
-
this.log.info(`Connecting to ${devices.length} device(s)`);
|
|
71
70
|
for (const device of devices) {
|
|
72
|
-
|
|
71
|
+
const key = `${device.productType}_${device.serial}`;
|
|
72
|
+
await this.stateManager.createDeviceStates(device);
|
|
73
|
+
this.connections.set(key, {
|
|
74
|
+
config: device,
|
|
75
|
+
ip: "",
|
|
76
|
+
wsClient: null,
|
|
77
|
+
wsAuthenticated: false,
|
|
78
|
+
pollTimer: void 0,
|
|
79
|
+
reconnectTimer: void 0,
|
|
80
|
+
wsFailCount: 0,
|
|
81
|
+
lastErrorCode: ""
|
|
82
|
+
});
|
|
73
83
|
}
|
|
84
|
+
this.log.info(
|
|
85
|
+
`Waiting for ${devices.length} device(s) to announce via mDNS`
|
|
86
|
+
);
|
|
87
|
+
this.discovery = new import_discovery.HomeWizardDiscovery(this.log);
|
|
88
|
+
this.discovery.start((discovered) => {
|
|
89
|
+
this.onDeviceDiscovered(discovered);
|
|
90
|
+
});
|
|
74
91
|
this.systemPollTimer = this.setInterval(() => {
|
|
75
92
|
void this.pollAllSystemInfo();
|
|
76
93
|
}, SYSTEM_POLL_MS);
|
|
77
94
|
this.updateGlobalConnection();
|
|
78
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Handle a discovered device from mDNS
|
|
98
|
+
*
|
|
99
|
+
* @param discovered Discovered device info
|
|
100
|
+
*/
|
|
101
|
+
onDeviceDiscovered(discovered) {
|
|
102
|
+
if (this.isPairing) {
|
|
103
|
+
const existing = (this.config.devices || []).find(
|
|
104
|
+
(d) => d.serial === discovered.serial
|
|
105
|
+
);
|
|
106
|
+
if (!existing) {
|
|
107
|
+
if (!this.discoveredDuringPairing.find(
|
|
108
|
+
(d) => d.serial === discovered.serial
|
|
109
|
+
)) {
|
|
110
|
+
this.discoveredDuringPairing.push(discovered);
|
|
111
|
+
this.log.info(
|
|
112
|
+
`Discovered: ${discovered.name} (${discovered.productType}) at ${discovered.ip} \u2014 waiting for button press...`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const [, conn] of this.connections) {
|
|
119
|
+
if (conn.config.serial !== discovered.serial) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (conn.ip !== discovered.ip) {
|
|
123
|
+
if (conn.ip) {
|
|
124
|
+
this.log.debug(
|
|
125
|
+
`${conn.config.productName}: IP changed ${conn.ip} \u2192 ${discovered.ip}`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
conn.ip = discovered.ip;
|
|
129
|
+
}
|
|
130
|
+
if (!conn.wsClient && !conn.reconnectTimer) {
|
|
131
|
+
this.log.debug(
|
|
132
|
+
`mDNS: found ${conn.config.productName} at ${discovered.ip}`
|
|
133
|
+
);
|
|
134
|
+
void this.initDevice(conn);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
79
139
|
/**
|
|
80
140
|
* Adapter stopping — MUST be synchronous
|
|
81
141
|
*
|
|
@@ -122,14 +182,14 @@ class HomeWizard extends utils.Adapter {
|
|
|
122
182
|
}
|
|
123
183
|
return;
|
|
124
184
|
}
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
185
|
+
const conn = this.findConnectionForState(id);
|
|
186
|
+
if (!conn || !conn.ip) {
|
|
127
187
|
return;
|
|
128
188
|
}
|
|
129
|
-
const client = new import_homewizard_client.HomeWizardClient(
|
|
189
|
+
const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token);
|
|
130
190
|
try {
|
|
131
191
|
if (id.endsWith(".system.reboot")) {
|
|
132
|
-
this.log.info(`Rebooting ${
|
|
192
|
+
this.log.info(`Rebooting ${conn.config.productName} (${conn.ip})`);
|
|
133
193
|
await client.reboot();
|
|
134
194
|
} else if (id.endsWith(".system.identify")) {
|
|
135
195
|
await client.identify();
|
|
@@ -171,22 +231,12 @@ class HomeWizard extends utils.Adapter {
|
|
|
171
231
|
this.log.info(
|
|
172
232
|
"Pairing mode started \u2014 press the button on your HomeWizard device within 60 seconds!"
|
|
173
233
|
);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
(
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
this.log.debug(`Pairing: ${device.name} already configured, skipping`);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
if (!this.discoveredDuringPairing.find((d) => d.serial === device.serial)) {
|
|
184
|
-
this.discoveredDuringPairing.push(device);
|
|
185
|
-
this.log.info(
|
|
186
|
-
`Discovered: ${device.name} (${device.productType}) at ${device.ip} \u2014 waiting for button press...`
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
234
|
+
if (!this.discovery) {
|
|
235
|
+
this.discovery = new import_discovery.HomeWizardDiscovery(this.log);
|
|
236
|
+
this.discovery.start((discovered) => {
|
|
237
|
+
this.onDeviceDiscovered(discovered);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
190
240
|
this.pairingPollTimer = this.setInterval(() => {
|
|
191
241
|
void this.pollPairing();
|
|
192
242
|
}, PAIRING_POLL_MS);
|
|
@@ -207,7 +257,6 @@ class HomeWizard extends utils.Adapter {
|
|
|
207
257
|
const authedClient = new import_homewizard_client.HomeWizardClient(device.ip, result.token);
|
|
208
258
|
const info = await authedClient.getDeviceInfo();
|
|
209
259
|
const deviceConfig = {
|
|
210
|
-
ip: device.ip,
|
|
211
260
|
token: result.token,
|
|
212
261
|
productType: info.product_type,
|
|
213
262
|
serial: info.serial,
|
|
@@ -220,8 +269,20 @@ class HomeWizard extends utils.Adapter {
|
|
|
220
269
|
native: { devices }
|
|
221
270
|
}
|
|
222
271
|
);
|
|
272
|
+
const key = `${deviceConfig.productType}_${deviceConfig.serial}`;
|
|
223
273
|
await this.stateManager.createDeviceStates(deviceConfig);
|
|
224
|
-
|
|
274
|
+
const conn = {
|
|
275
|
+
config: deviceConfig,
|
|
276
|
+
ip: device.ip,
|
|
277
|
+
wsClient: null,
|
|
278
|
+
wsAuthenticated: false,
|
|
279
|
+
pollTimer: void 0,
|
|
280
|
+
reconnectTimer: void 0,
|
|
281
|
+
wsFailCount: 0,
|
|
282
|
+
lastErrorCode: ""
|
|
283
|
+
};
|
|
284
|
+
this.connections.set(key, conn);
|
|
285
|
+
void this.initDevice(conn);
|
|
225
286
|
this.discoveredDuringPairing = this.discoveredDuringPairing.filter(
|
|
226
287
|
(d) => d.serial !== device.serial
|
|
227
288
|
);
|
|
@@ -249,31 +310,22 @@ class HomeWizard extends utils.Adapter {
|
|
|
249
310
|
this.clearTimeout(this.pairingTimer);
|
|
250
311
|
this.pairingTimer = void 0;
|
|
251
312
|
}
|
|
252
|
-
(
|
|
253
|
-
|
|
313
|
+
if ((this.config.devices || []).length === 0) {
|
|
314
|
+
(_a = this.discovery) == null ? void 0 : _a.stop();
|
|
315
|
+
this.discovery = null;
|
|
316
|
+
}
|
|
254
317
|
void this.setStateAsync("startPairing", { val: false, ack: true });
|
|
255
318
|
}
|
|
256
319
|
/**
|
|
257
|
-
*
|
|
320
|
+
* Initialize a newly discovered device — fetch info and connect WebSocket
|
|
258
321
|
*
|
|
259
|
-
* @param
|
|
322
|
+
* @param conn Device connection with IP set
|
|
260
323
|
*/
|
|
261
|
-
async
|
|
262
|
-
const key = `${config.productType}_${config.serial}`;
|
|
263
|
-
await this.stateManager.createDeviceStates(config);
|
|
264
|
-
const conn = {
|
|
265
|
-
config,
|
|
266
|
-
wsClient: null,
|
|
267
|
-
wsAuthenticated: false,
|
|
268
|
-
pollTimer: void 0,
|
|
269
|
-
reconnectTimer: void 0,
|
|
270
|
-
wsFailCount: 0,
|
|
271
|
-
lastErrorCode: ""
|
|
272
|
-
};
|
|
273
|
-
this.connections.set(key, conn);
|
|
324
|
+
async initDevice(conn) {
|
|
274
325
|
try {
|
|
275
|
-
const client = new import_homewizard_client.HomeWizardClient(
|
|
326
|
+
const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token);
|
|
276
327
|
const info = await client.getDeviceInfo();
|
|
328
|
+
const key = this.stateManager.devicePrefix(conn.config);
|
|
277
329
|
await this.setStateAsync(`${key}.info.firmware`, {
|
|
278
330
|
val: info.firmware_version,
|
|
279
331
|
ack: true
|
|
@@ -290,52 +342,51 @@ class HomeWizard extends utils.Adapter {
|
|
|
290
342
|
* @param conn Device connection
|
|
291
343
|
*/
|
|
292
344
|
connectWebSocket(conn) {
|
|
345
|
+
if (!conn.ip) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
293
348
|
const key = `${conn.config.productType}_${conn.config.serial}`;
|
|
294
|
-
const wsClient = new import_websocket_client.HomeWizardWebSocket(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
},
|
|
336
|
-
log: this.log
|
|
337
|
-
}
|
|
338
|
-
);
|
|
349
|
+
const wsClient = new import_websocket_client.HomeWizardWebSocket(conn.ip, conn.config.token, {
|
|
350
|
+
onMeasurement: (data) => {
|
|
351
|
+
void this.stateManager.updateMeasurement(conn.config, data);
|
|
352
|
+
},
|
|
353
|
+
onConnected: () => {
|
|
354
|
+
conn.wsAuthenticated = true;
|
|
355
|
+
conn.wsFailCount = 0;
|
|
356
|
+
conn.lastErrorCode = "";
|
|
357
|
+
void this.stateManager.setDeviceConnected(conn.config, true);
|
|
358
|
+
this.updateGlobalConnection();
|
|
359
|
+
if (conn.pollTimer) {
|
|
360
|
+
this.clearInterval(conn.pollTimer);
|
|
361
|
+
conn.pollTimer = void 0;
|
|
362
|
+
}
|
|
363
|
+
this.log.debug(
|
|
364
|
+
`WebSocket connected to ${conn.config.productName} (${conn.ip})`
|
|
365
|
+
);
|
|
366
|
+
},
|
|
367
|
+
onDisconnected: (error) => {
|
|
368
|
+
conn.wsAuthenticated = false;
|
|
369
|
+
void this.stateManager.setDeviceConnected(conn.config, false);
|
|
370
|
+
this.updateGlobalConnection();
|
|
371
|
+
if (error) {
|
|
372
|
+
this.logDeviceError(conn, "ws", error);
|
|
373
|
+
}
|
|
374
|
+
this.startRestFallback(conn);
|
|
375
|
+
conn.wsFailCount++;
|
|
376
|
+
const delay = Math.min(
|
|
377
|
+
WS_RECONNECT_BASE_MS * Math.pow(2, conn.wsFailCount - 1),
|
|
378
|
+
WS_RECONNECT_MAX_MS
|
|
379
|
+
);
|
|
380
|
+
this.log.debug(
|
|
381
|
+
`${key}: WS reconnect in ${delay / 1e3}s (attempt ${conn.wsFailCount})`
|
|
382
|
+
);
|
|
383
|
+
conn.reconnectTimer = this.setTimeout(() => {
|
|
384
|
+
conn.reconnectTimer = void 0;
|
|
385
|
+
this.connectWebSocket(conn);
|
|
386
|
+
}, delay);
|
|
387
|
+
},
|
|
388
|
+
log: this.log
|
|
389
|
+
});
|
|
339
390
|
conn.wsClient = wsClient;
|
|
340
391
|
wsClient.connect();
|
|
341
392
|
}
|
|
@@ -345,10 +396,10 @@ class HomeWizard extends utils.Adapter {
|
|
|
345
396
|
* @param conn Device connection
|
|
346
397
|
*/
|
|
347
398
|
startRestFallback(conn) {
|
|
348
|
-
if (conn.pollTimer) {
|
|
399
|
+
if (conn.pollTimer || !conn.ip) {
|
|
349
400
|
return;
|
|
350
401
|
}
|
|
351
|
-
const client = new import_homewizard_client.HomeWizardClient(conn.
|
|
402
|
+
const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token);
|
|
352
403
|
conn.pollTimer = this.setInterval(async () => {
|
|
353
404
|
try {
|
|
354
405
|
const data = await client.getMeasurement();
|
|
@@ -364,7 +415,9 @@ class HomeWizard extends utils.Adapter {
|
|
|
364
415
|
/** Poll system info for all connected devices */
|
|
365
416
|
async pollAllSystemInfo() {
|
|
366
417
|
for (const conn of this.connections.values()) {
|
|
367
|
-
|
|
418
|
+
if (conn.ip) {
|
|
419
|
+
await this.pollSystemInfo(conn);
|
|
420
|
+
}
|
|
368
421
|
}
|
|
369
422
|
}
|
|
370
423
|
/**
|
|
@@ -373,8 +426,11 @@ class HomeWizard extends utils.Adapter {
|
|
|
373
426
|
* @param conn Device connection
|
|
374
427
|
*/
|
|
375
428
|
async pollSystemInfo(conn) {
|
|
429
|
+
if (!conn.ip) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
376
432
|
try {
|
|
377
|
-
const client = new import_homewizard_client.HomeWizardClient(conn.
|
|
433
|
+
const client = new import_homewizard_client.HomeWizardClient(conn.ip, conn.config.token);
|
|
378
434
|
const system = await client.getSystem();
|
|
379
435
|
await this.stateManager.updateSystem(conn.config, system);
|
|
380
436
|
try {
|
|
@@ -397,16 +453,16 @@ class HomeWizard extends utils.Adapter {
|
|
|
397
453
|
});
|
|
398
454
|
}
|
|
399
455
|
/**
|
|
400
|
-
* Find
|
|
456
|
+
* Find connection for a state ID
|
|
401
457
|
*
|
|
402
458
|
* @param stateId Full state ID
|
|
403
459
|
*/
|
|
404
|
-
|
|
460
|
+
findConnectionForState(stateId) {
|
|
405
461
|
const localId = stateId.replace(`${this.namespace}.`, "");
|
|
406
462
|
for (const conn of this.connections.values()) {
|
|
407
463
|
const prefix = this.stateManager.devicePrefix(conn.config);
|
|
408
464
|
if (localId.startsWith(`${prefix}.`)) {
|
|
409
|
-
return conn
|
|
465
|
+
return conn;
|
|
410
466
|
}
|
|
411
467
|
}
|
|
412
468
|
return void 0;
|
|
@@ -423,12 +479,12 @@ class HomeWizard extends utils.Adapter {
|
|
|
423
479
|
const key = `${context}:${code}`;
|
|
424
480
|
if (conn.lastErrorCode === key) {
|
|
425
481
|
this.log.debug(
|
|
426
|
-
`${conn.config.productName} (${conn.
|
|
482
|
+
`${conn.config.productName} (${conn.ip}) ${context}: ${code}`
|
|
427
483
|
);
|
|
428
484
|
} else {
|
|
429
485
|
conn.lastErrorCode = key;
|
|
430
486
|
this.log.warn(
|
|
431
|
-
`${conn.config.productName} (${conn.
|
|
487
|
+
`${conn.config.productName} (${conn.ip}) ${context}: ${err instanceof Error ? err.message : String(err)}`
|
|
432
488
|
);
|
|
433
489
|
}
|
|
434
490
|
}
|
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 { 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\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 isPairing = false;\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 // 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\n // Connect to all configured devices\n const devices: DeviceConfig[] = this.config.devices || [];\n if (devices.length === 0) {\n this.log.info(\n \"No devices configured \u2014 press 'Start Pairing' to add a HomeWizard device\",\n );\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n return;\n }\n\n this.log.info(`Connecting to ${devices.length} device(s)`);\n for (const device of devices) {\n await this.connectDevice(device);\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 * Adapter stopping \u2014 MUST be synchronous\n *\n * @param callback Completion callback\n */\n private onUnload(callback: () => void): void {\n // Stop pairing\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\n // Stop discovery\n this.discovery?.stop();\n\n // Close all device connections\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 this.startPairing();\n }\n return;\n }\n\n // Find which device this state belongs to\n const device = this.findDeviceForState(id);\n if (!device) {\n return;\n }\n\n const client = new HomeWizardClient(device.ip, device.token);\n\n try {\n if (id.endsWith(\".system.reboot\")) {\n this.log.info(`Rebooting ${device.productName} (${device.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 startPairing(): void {\n if (this.isPairing) {\n this.log.debug(\"Pairing already active\");\n return;\n }\n\n this.isPairing = true;\n this.discoveredDuringPairing = [];\n this.log.info(\n \"Pairing mode started \u2014 press the button on your HomeWizard device within 60 seconds!\",\n );\n\n // Start mDNS discovery\n this.discovery = new HomeWizardDiscovery(this.log);\n this.discovery.start((device) => {\n // Skip already configured devices\n const existing = (this.config.devices || []).find(\n (d) => d.serial === device.serial,\n );\n if (existing) {\n this.log.debug(`Pairing: ${device.name} already configured, skipping`);\n return;\n }\n\n // Add to list if not already discovered\n if (\n !this.discoveredDuringPairing.find((d) => d.serial === device.serial)\n ) {\n this.discoveredDuringPairing.push(device);\n this.log.info(\n `Discovered: ${device.name} (${device.productType}) at ${device.ip} \u2014 waiting for button press...`,\n );\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(\"Pairing mode timed out\");\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 `Paired with ${device.name} (${device.productType}) at ${device.ip}`,\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 ip: device.ip,\n token: result.token,\n productType: info.product_type,\n serial: info.serial,\n productName: info.product_name,\n };\n\n // Save to config\n const devices = [...(this.config.devices || []), deviceConfig];\n await this.extendForeignObjectAsync(\n `system.adapter.${this.namespace}`,\n {\n native: { devices },\n },\n );\n\n // Create states and connect\n await this.stateManager.createDeviceStates(deviceConfig);\n await this.connectDevice(deviceConfig);\n\n // Remove from discovery list\n this.discoveredDuringPairing = this.discoveredDuringPairing.filter(\n (d) => d.serial !== device.serial,\n );\n\n this.updateGlobalConnection();\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.discoveredDuringPairing = [];\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 this.discovery?.stop();\n this.discovery = null;\n\n // Reset pairing button\n void this.setStateAsync(\"startPairing\", { val: false, ack: true });\n }\n\n /**\n * Connect to a device via WebSocket\n *\n * @param config Device configuration\n */\n private async connectDevice(config: DeviceConfig): Promise<void> {\n const key = `${config.productType}_${config.serial}`;\n\n // Create states if they don't exist yet\n await this.stateManager.createDeviceStates(config);\n\n const conn: DeviceConnection = {\n config,\n wsClient: null,\n wsAuthenticated: false,\n pollTimer: undefined,\n reconnectTimer: undefined,\n wsFailCount: 0,\n lastErrorCode: \"\",\n };\n this.connections.set(key, conn);\n\n // Get initial device info\n try {\n const client = new HomeWizardClient(config.ip, config.token);\n const info = await client.getDeviceInfo();\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 // Start WebSocket connection\n this.connectWebSocket(conn);\n\n // Fetch initial system info\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 const key = `${conn.config.productType}_${conn.config.serial}`;\n\n const wsClient = new HomeWizardWebSocket(\n conn.config.ip,\n conn.config.token,\n {\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.lastErrorCode = \"\";\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 this.log.debug(\n `WebSocket connected to ${conn.config.productName} (${conn.config.ip})`,\n );\n },\n onDisconnected: (error?: Error) => {\n conn.wsAuthenticated = false;\n void this.stateManager.setDeviceConnected(conn.config, false);\n this.updateGlobalConnection();\n\n if (error) {\n this.logDeviceError(conn, \"ws\", error);\n }\n\n // Start REST fallback\n this.startRestFallback(conn);\n\n // Schedule reconnect with exponential backoff\n conn.wsFailCount++;\n const delay = Math.min(\n WS_RECONNECT_BASE_MS * Math.pow(2, conn.wsFailCount - 1),\n WS_RECONNECT_MAX_MS,\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\n conn.wsClient = wsClient;\n wsClient.connect();\n }\n\n /**\n * Start REST polling as fallback when WebSocket is down\n *\n * @param conn Device connection\n */\n private startRestFallback(conn: DeviceConnection): void {\n if (conn.pollTimer) {\n return; // Already polling\n }\n\n const client = new HomeWizardClient(conn.config.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 await this.stateManager.setDeviceConnected(conn.config, true);\n } catch (err) {\n this.logDeviceError(conn, \"rest\", err);\n await this.stateManager.setDeviceConnected(conn.config, false);\n }\n this.updateGlobalConnection();\n }, REST_POLL_MS);\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 await this.pollSystemInfo(conn);\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 try {\n const client = new HomeWizardClient(conn.config.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 await this.stateManager.updateBattery(conn.config, battery);\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 * Find device config for a state ID\n *\n * @param stateId Full state ID\n */\n private findDeviceForState(stateId: string): DeviceConfig | 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.config;\n }\n }\n return undefined;\n }\n\n /**\n * Log device error with deduplication\n *\n * @param conn Device connection\n * @param context Error context\n * @param err Error object\n */\n private logDeviceError(\n conn: DeviceConnection,\n context: string,\n err: unknown,\n ): void {\n const code =\n err instanceof HomeWizardApiError\n ? err.errorCode\n : err instanceof Error\n ? err.message\n : \"unknown\";\n const key = `${context}:${code}`;\n\n if (conn.lastErrorCode === key) {\n // Same error as last time \u2014 debug only\n this.log.debug(\n `${conn.config.productName} (${conn.config.ip}) ${context}: ${code}`,\n );\n } else {\n conn.lastErrorCode = key;\n this.log.warn(\n `${conn.config.productName} (${conn.config.ip}) ${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 { 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\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 isPairing = false;\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 // 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\n const devices: DeviceConfig[] = this.config.devices || [];\n if (devices.length === 0) {\n this.log.info(\n \"No devices configured \u2014 press 'Start Pairing' to add a HomeWizard device\",\n );\n await this.setStateAsync(\"info.connection\", { val: false, ack: true });\n return;\n }\n\n // Create connection entries for all configured devices (without IP yet)\n for (const device of devices) {\n const key = `${device.productType}_${device.serial}`;\n await this.stateManager.createDeviceStates(device);\n this.connections.set(key, {\n config: device,\n ip: \"\",\n wsClient: null,\n wsAuthenticated: false,\n pollTimer: undefined,\n reconnectTimer: undefined,\n wsFailCount: 0,\n lastErrorCode: \"\",\n });\n }\n\n // Start mDNS discovery to find configured devices\n this.log.info(\n `Waiting for ${devices.length} device(s) to announce via mDNS`,\n );\n this.discovery = new HomeWizardDiscovery(this.log);\n this.discovery.start((discovered) => {\n this.onDeviceDiscovered(discovered);\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 * Handle a discovered device from mDNS\n *\n * @param discovered Discovered device info\n */\n private onDeviceDiscovered(discovered: DiscoveredDevice): void {\n // During pairing, collect new devices\n if (this.isPairing) {\n const existing = (this.config.devices || []).find(\n (d) => d.serial === discovered.serial,\n );\n if (!existing) {\n if (\n !this.discoveredDuringPairing.find(\n (d) => d.serial === discovered.serial,\n )\n ) {\n this.discoveredDuringPairing.push(discovered);\n this.log.info(\n `Discovered: ${discovered.name} (${discovered.productType}) at ${discovered.ip} \u2014 waiting for button press...`,\n );\n }\n return;\n }\n }\n\n // Match against configured devices\n for (const [, conn] of this.connections) {\n if (conn.config.serial !== discovered.serial) {\n continue;\n }\n\n // Update IP if changed\n if (conn.ip !== discovered.ip) {\n if (conn.ip) {\n this.log.debug(\n `${conn.config.productName}: IP changed ${conn.ip} \u2192 ${discovered.ip}`,\n );\n }\n conn.ip = discovered.ip;\n }\n\n // Connect if not already connected\n if (!conn.wsClient && !conn.reconnectTimer) {\n this.log.debug(\n `mDNS: found ${conn.config.productName} at ${discovered.ip}`,\n );\n void this.initDevice(conn);\n }\n\n return;\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 // Stop pairing\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\n // Stop discovery\n this.discovery?.stop();\n\n // Close all device connections\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 this.startPairing();\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 startPairing(): void {\n if (this.isPairing) {\n this.log.debug(\"Pairing already active\");\n return;\n }\n\n this.isPairing = true;\n this.discoveredDuringPairing = [];\n this.log.info(\n \"Pairing mode started \u2014 press the button on your HomeWizard device within 60 seconds!\",\n );\n\n // If discovery isn't running yet (no devices configured), start it\n if (!this.discovery) {\n this.discovery = new HomeWizardDiscovery(this.log);\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(\"Pairing mode timed out\");\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 `Paired with ${device.name} (${device.productType}) at ${device.ip}`,\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 };\n\n // Save to config\n const devices = [...(this.config.devices || []), deviceConfig];\n await this.extendForeignObjectAsync(\n `system.adapter.${this.namespace}`,\n {\n native: { devices },\n },\n );\n\n // Create states and connect\n const key = `${deviceConfig.productType}_${deviceConfig.serial}`;\n await this.stateManager.createDeviceStates(deviceConfig);\n const conn: DeviceConnection = {\n config: deviceConfig,\n ip: device.ip,\n wsClient: null,\n wsAuthenticated: false,\n pollTimer: undefined,\n reconnectTimer: undefined,\n wsFailCount: 0,\n lastErrorCode: \"\",\n };\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 !== device.serial,\n );\n\n this.updateGlobalConnection();\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.discoveredDuringPairing = [];\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 // Stop discovery only if no devices are configured (nothing to listen for)\n if ((this.config.devices || []).length === 0) {\n this.discovery?.stop();\n this.discovery = null;\n }\n\n // Reset pairing button\n void this.setStateAsync(\"startPairing\", { val: false, ack: true });\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 const key = `${conn.config.productType}_${conn.config.serial}`;\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.lastErrorCode = \"\";\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 this.log.debug(\n `WebSocket connected to ${conn.config.productName} (${conn.ip})`,\n );\n },\n onDisconnected: (error?: Error) => {\n conn.wsAuthenticated = false;\n void this.stateManager.setDeviceConnected(conn.config, false);\n this.updateGlobalConnection();\n\n if (error) {\n this.logDeviceError(conn, \"ws\", error);\n }\n\n // Start REST fallback\n this.startRestFallback(conn);\n\n // Schedule reconnect with exponential backoff\n conn.wsFailCount++;\n const delay = Math.min(\n WS_RECONNECT_BASE_MS * Math.pow(2, conn.wsFailCount - 1),\n WS_RECONNECT_MAX_MS,\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 *\n * @param conn Device connection\n */\n private startRestFallback(conn: DeviceConnection): void {\n if (conn.pollTimer || !conn.ip) {\n return;\n }\n\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 await this.stateManager.setDeviceConnected(conn.config, true);\n } catch (err) {\n this.logDeviceError(conn, \"rest\", err);\n await this.stateManager.setDeviceConnected(conn.config, false);\n }\n this.updateGlobalConnection();\n }, REST_POLL_MS);\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) {\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 await this.stateManager.updateBattery(conn.config, battery);\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 * 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 * Log device error with deduplication\n *\n * @param conn Device connection\n * @param context Error context\n * @param err Error object\n */\n private logDeviceError(\n conn: DeviceConnection,\n context: string,\n err: unknown,\n ): void {\n const code =\n err instanceof HomeWizardApiError\n ? err.errorCode\n : err instanceof Error\n ? err.message\n : \"unknown\";\n const key = `${context}:${code}`;\n\n if (conn.lastErrorCode === key) {\n // Same error as last time \u2014 debug only\n this.log.debug(\n `${conn.config.productName} (${conn.ip}) ${context}: ${code}`,\n );\n } else {\n conn.lastErrorCode = key;\n this.log.warn(\n `${conn.config.productName} (${conn.ip}) ${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,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "homewizard",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.1.3": {
|
|
7
|
+
"en": "Remove IP from config — devices are now discovered via mDNS at runtime",
|
|
8
|
+
"de": "IP aus Konfiguration entfernt — Geräte werden jetzt per mDNS zur Laufzeit gefunden",
|
|
9
|
+
"ru": "IP удалён из конфигурации — устройства теперь обнаруживаются через mDNS во время работы",
|
|
10
|
+
"pt": "IP removido da configuração — dispositivos agora são descobertos via mDNS em tempo de execução",
|
|
11
|
+
"nl": "IP uit configuratie verwijderd — apparaten worden nu via mDNS tijdens runtime gevonden",
|
|
12
|
+
"fr": "IP supprimée de la configuration — les appareils sont maintenant découverts via mDNS à l exécution",
|
|
13
|
+
"it": "IP rimosso dalla configurazione — i dispositivi vengono ora scoperti tramite mDNS a runtime",
|
|
14
|
+
"es": "IP eliminada de la configuración — los dispositivos se descubren ahora vía mDNS en tiempo de ejecución",
|
|
15
|
+
"pl": "IP usunięto z konfiguracji — urządzenia są teraz wykrywane przez mDNS w czasie działania",
|
|
16
|
+
"uk": "IP видалено з конфігурації — пристрої тепер виявляються через mDNS під час роботи",
|
|
17
|
+
"zh-cn": "从配置中移除 IP — 设备现在通过 mDNS 在运行时发现"
|
|
18
|
+
},
|
|
19
|
+
"0.1.2": {
|
|
20
|
+
"en": "Bundle HomeWizard CA certificate for proper TLS validation instead of skipping verification",
|
|
21
|
+
"de": "HomeWizard CA-Zertifikat für korrekte TLS-Validierung gebündelt statt Überprüfung zu überspringen",
|
|
22
|
+
"ru": "Встроен CA-сертификат HomeWizard для корректной проверки TLS вместо пропуска верификации",
|
|
23
|
+
"pt": "Certificado CA HomeWizard incluído para validação TLS adequada em vez de ignorar verificação",
|
|
24
|
+
"nl": "HomeWizard CA-certificaat gebundeld voor correcte TLS-validatie in plaats van verificatie over te slaan",
|
|
25
|
+
"fr": "Certificat CA HomeWizard intégré pour une validation TLS correcte au lieu d'ignorer la vérification",
|
|
26
|
+
"it": "Certificato CA HomeWizard incluso per una corretta validazione TLS invece di saltare la verifica",
|
|
27
|
+
"es": "Certificado CA HomeWizard incluido para validación TLS correcta en lugar de omitir verificación",
|
|
28
|
+
"pl": "Dołączono certyfikat CA HomeWizard dla prawidłowej walidacji TLS zamiast pomijania weryfikacji",
|
|
29
|
+
"uk": "Додано CA-сертифікат HomeWizard для належної перевірки TLS замість пропуску верифікації",
|
|
30
|
+
"zh-cn": "捆绑 HomeWizard CA 证书进行正确的 TLS 验证,而非跳过验证"
|
|
31
|
+
},
|
|
6
32
|
"0.1.1": {
|
|
7
33
|
"en": "Add unit tests (129 tests), fix Dependabot config",
|
|
8
34
|
"de": "Unit-Tests hinzugefügt (129 Tests), Dependabot-Konfiguration korrigiert",
|
package/package.json
CHANGED