iobroker.homewizard 0.1.0
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/LICENSE +21 -0
- package/README.md +190 -0
- package/admin/homewizard.svg +6 -0
- package/admin/i18n/de/translations.json +16 -0
- package/admin/i18n/en/translations.json +16 -0
- package/admin/i18n/es/translations.json +16 -0
- package/admin/i18n/fr/translations.json +16 -0
- package/admin/i18n/it/translations.json +16 -0
- package/admin/i18n/nl/translations.json +16 -0
- package/admin/i18n/pl/translations.json +16 -0
- package/admin/i18n/pt/translations.json +16 -0
- package/admin/i18n/ru/translations.json +16 -0
- package/admin/i18n/uk/translations.json +16 -0
- package/admin/i18n/zh-cn/translations.json +16 -0
- package/admin/jsonConfig.json +99 -0
- package/build/lib/discovery.js +103 -0
- package/build/lib/discovery.js.map +7 -0
- package/build/lib/homewizard-client.js +193 -0
- package/build/lib/homewizard-client.js.map +7 -0
- package/build/lib/state-manager.js +749 -0
- package/build/lib/state-manager.js.map +7 -0
- package/build/lib/types.js +17 -0
- package/build/lib/types.js.map +7 -0
- package/build/lib/websocket-client.js +151 -0
- package/build/lib/websocket-client.js.map +7 -0
- package/build/main.js +441 -0
- package/build/main.js.map +7 -0
- package/io-package.json +136 -0
- package/package.json +77 -0
package/build/main.js
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
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 __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
var utils = __toESM(require("@iobroker/adapter-core"));
|
|
25
|
+
var import_discovery = require("./lib/discovery");
|
|
26
|
+
var import_homewizard_client = require("./lib/homewizard-client");
|
|
27
|
+
var import_state_manager = require("./lib/state-manager");
|
|
28
|
+
var import_websocket_client = require("./lib/websocket-client");
|
|
29
|
+
const PAIRING_TIMEOUT_MS = 6e4;
|
|
30
|
+
const PAIRING_POLL_MS = 2e3;
|
|
31
|
+
const WS_RECONNECT_BASE_MS = 5e3;
|
|
32
|
+
const WS_RECONNECT_MAX_MS = 3e5;
|
|
33
|
+
const REST_POLL_MS = 1e4;
|
|
34
|
+
const SYSTEM_POLL_MS = 6e4;
|
|
35
|
+
class HomeWizard extends utils.Adapter {
|
|
36
|
+
stateManager;
|
|
37
|
+
discovery = null;
|
|
38
|
+
connections = /* @__PURE__ */ new Map();
|
|
39
|
+
pairingTimer = void 0;
|
|
40
|
+
pairingPollTimer = void 0;
|
|
41
|
+
systemPollTimer = void 0;
|
|
42
|
+
isPairing = false;
|
|
43
|
+
discoveredDuringPairing = [];
|
|
44
|
+
/** @param options Adapter options */
|
|
45
|
+
constructor(options = {}) {
|
|
46
|
+
super({ ...options, name: "homewizard" });
|
|
47
|
+
this.on("ready", () => this.onReady());
|
|
48
|
+
this.on("stateChange", (id, state) => this.onStateChange(id, state));
|
|
49
|
+
this.on("unload", (callback) => this.onUnload(callback));
|
|
50
|
+
}
|
|
51
|
+
/** Adapter started */
|
|
52
|
+
async onReady() {
|
|
53
|
+
this.stateManager = new import_state_manager.StateManager(this);
|
|
54
|
+
await this.subscribeStatesAsync("startPairing");
|
|
55
|
+
await this.subscribeStatesAsync("*.system.reboot");
|
|
56
|
+
await this.subscribeStatesAsync("*.system.identify");
|
|
57
|
+
await this.subscribeStatesAsync("*.system.cloud_enabled");
|
|
58
|
+
await this.subscribeStatesAsync("*.system.status_led_brightness_pct");
|
|
59
|
+
await this.subscribeStatesAsync("*.system.api_v1_enabled");
|
|
60
|
+
await this.subscribeStatesAsync("*.battery.mode");
|
|
61
|
+
await this.subscribeStatesAsync("*.battery.permissions");
|
|
62
|
+
const devices = this.config.devices || [];
|
|
63
|
+
if (devices.length === 0) {
|
|
64
|
+
this.log.info(
|
|
65
|
+
"No devices configured \u2014 press 'Start Pairing' to add a HomeWizard device"
|
|
66
|
+
);
|
|
67
|
+
await this.setStateAsync("info.connection", { val: false, ack: true });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.log.info(`Connecting to ${devices.length} device(s)`);
|
|
71
|
+
for (const device of devices) {
|
|
72
|
+
await this.connectDevice(device);
|
|
73
|
+
}
|
|
74
|
+
this.systemPollTimer = this.setInterval(() => {
|
|
75
|
+
void this.pollAllSystemInfo();
|
|
76
|
+
}, SYSTEM_POLL_MS);
|
|
77
|
+
this.updateGlobalConnection();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Adapter stopping — MUST be synchronous
|
|
81
|
+
*
|
|
82
|
+
* @param callback Completion callback
|
|
83
|
+
*/
|
|
84
|
+
onUnload(callback) {
|
|
85
|
+
var _a, _b;
|
|
86
|
+
if (this.pairingTimer) {
|
|
87
|
+
this.clearTimeout(this.pairingTimer);
|
|
88
|
+
}
|
|
89
|
+
if (this.pairingPollTimer) {
|
|
90
|
+
this.clearInterval(this.pairingPollTimer);
|
|
91
|
+
}
|
|
92
|
+
if (this.systemPollTimer) {
|
|
93
|
+
this.clearInterval(this.systemPollTimer);
|
|
94
|
+
}
|
|
95
|
+
(_a = this.discovery) == null ? void 0 : _a.stop();
|
|
96
|
+
for (const conn of this.connections.values()) {
|
|
97
|
+
(_b = conn.wsClient) == null ? void 0 : _b.close();
|
|
98
|
+
if (conn.pollTimer) {
|
|
99
|
+
this.clearInterval(conn.pollTimer);
|
|
100
|
+
}
|
|
101
|
+
if (conn.reconnectTimer) {
|
|
102
|
+
this.clearTimeout(conn.reconnectTimer);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
this.connections.clear();
|
|
106
|
+
void this.setState("info.connection", { val: false, ack: true });
|
|
107
|
+
callback();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Handle state changes
|
|
111
|
+
*
|
|
112
|
+
* @param id State ID
|
|
113
|
+
* @param state State value
|
|
114
|
+
*/
|
|
115
|
+
async onStateChange(id, state) {
|
|
116
|
+
if (!state || state.ack) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (id.endsWith(".startPairing")) {
|
|
120
|
+
if (state.val) {
|
|
121
|
+
this.startPairing();
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const device = this.findDeviceForState(id);
|
|
126
|
+
if (!device) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const client = new import_homewizard_client.HomeWizardClient(device.ip, device.token);
|
|
130
|
+
try {
|
|
131
|
+
if (id.endsWith(".system.reboot")) {
|
|
132
|
+
this.log.info(`Rebooting ${device.productName} (${device.ip})`);
|
|
133
|
+
await client.reboot();
|
|
134
|
+
} else if (id.endsWith(".system.identify")) {
|
|
135
|
+
await client.identify();
|
|
136
|
+
} else if (id.endsWith(".system.cloud_enabled")) {
|
|
137
|
+
await client.setSystem({ cloud_enabled: !!state.val });
|
|
138
|
+
await this.setStateAsync(id, { val: state.val, ack: true });
|
|
139
|
+
} else if (id.endsWith(".system.status_led_brightness_pct")) {
|
|
140
|
+
await client.setSystem({
|
|
141
|
+
status_led_brightness_pct: Number(state.val)
|
|
142
|
+
});
|
|
143
|
+
await this.setStateAsync(id, { val: state.val, ack: true });
|
|
144
|
+
} else if (id.endsWith(".system.api_v1_enabled")) {
|
|
145
|
+
await client.setSystem({ api_v1_enabled: !!state.val });
|
|
146
|
+
await this.setStateAsync(id, { val: state.val, ack: true });
|
|
147
|
+
} else if (id.endsWith(".battery.mode")) {
|
|
148
|
+
await client.setBatteries({
|
|
149
|
+
mode: String(state.val)
|
|
150
|
+
});
|
|
151
|
+
await this.setStateAsync(id, { val: state.val, ack: true });
|
|
152
|
+
} else if (id.endsWith(".battery.permissions")) {
|
|
153
|
+
const perms = JSON.parse(String(state.val));
|
|
154
|
+
await client.setBatteries({ permissions: perms });
|
|
155
|
+
await this.setStateAsync(id, { val: state.val, ack: true });
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
this.log.warn(
|
|
159
|
+
`Failed to set ${id}: ${err instanceof Error ? err.message : String(err)}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** Start pairing mode — discover devices and attempt to pair */
|
|
164
|
+
startPairing() {
|
|
165
|
+
if (this.isPairing) {
|
|
166
|
+
this.log.debug("Pairing already active");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.isPairing = true;
|
|
170
|
+
this.discoveredDuringPairing = [];
|
|
171
|
+
this.log.info(
|
|
172
|
+
"Pairing mode started \u2014 press the button on your HomeWizard device within 60 seconds!"
|
|
173
|
+
);
|
|
174
|
+
this.discovery = new import_discovery.HomeWizardDiscovery(this.log);
|
|
175
|
+
this.discovery.start((device) => {
|
|
176
|
+
const existing = (this.config.devices || []).find(
|
|
177
|
+
(d) => d.serial === device.serial
|
|
178
|
+
);
|
|
179
|
+
if (existing) {
|
|
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
|
+
});
|
|
190
|
+
this.pairingPollTimer = this.setInterval(() => {
|
|
191
|
+
void this.pollPairing();
|
|
192
|
+
}, PAIRING_POLL_MS);
|
|
193
|
+
this.pairingTimer = this.setTimeout(() => {
|
|
194
|
+
this.stopPairing();
|
|
195
|
+
this.log.info("Pairing mode timed out");
|
|
196
|
+
}, PAIRING_TIMEOUT_MS);
|
|
197
|
+
}
|
|
198
|
+
/** Poll all discovered devices to attempt pairing */
|
|
199
|
+
async pollPairing() {
|
|
200
|
+
for (const device of this.discoveredDuringPairing) {
|
|
201
|
+
try {
|
|
202
|
+
const client = new import_homewizard_client.HomeWizardClient(device.ip);
|
|
203
|
+
const result = await client.requestPairing();
|
|
204
|
+
this.log.info(
|
|
205
|
+
`Paired with ${device.name} (${device.productType}) at ${device.ip}`
|
|
206
|
+
);
|
|
207
|
+
const authedClient = new import_homewizard_client.HomeWizardClient(device.ip, result.token);
|
|
208
|
+
const info = await authedClient.getDeviceInfo();
|
|
209
|
+
const deviceConfig = {
|
|
210
|
+
ip: device.ip,
|
|
211
|
+
token: result.token,
|
|
212
|
+
productType: info.product_type,
|
|
213
|
+
serial: info.serial,
|
|
214
|
+
productName: info.product_name
|
|
215
|
+
};
|
|
216
|
+
const devices = [...this.config.devices || [], deviceConfig];
|
|
217
|
+
await this.extendForeignObjectAsync(
|
|
218
|
+
`system.adapter.${this.namespace}`,
|
|
219
|
+
{
|
|
220
|
+
native: { devices }
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
await this.stateManager.createDeviceStates(deviceConfig);
|
|
224
|
+
await this.connectDevice(deviceConfig);
|
|
225
|
+
this.discoveredDuringPairing = this.discoveredDuringPairing.filter(
|
|
226
|
+
(d) => d.serial !== device.serial
|
|
227
|
+
);
|
|
228
|
+
this.updateGlobalConnection();
|
|
229
|
+
} catch (err) {
|
|
230
|
+
if (err instanceof import_homewizard_client.HomeWizardApiError && err.statusCode === 403) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
this.log.debug(
|
|
234
|
+
`Pairing poll error for ${device.ip}: ${err instanceof Error ? err.message : String(err)}`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** Stop pairing mode */
|
|
240
|
+
stopPairing() {
|
|
241
|
+
var _a;
|
|
242
|
+
this.isPairing = false;
|
|
243
|
+
this.discoveredDuringPairing = [];
|
|
244
|
+
if (this.pairingPollTimer) {
|
|
245
|
+
this.clearInterval(this.pairingPollTimer);
|
|
246
|
+
this.pairingPollTimer = void 0;
|
|
247
|
+
}
|
|
248
|
+
if (this.pairingTimer) {
|
|
249
|
+
this.clearTimeout(this.pairingTimer);
|
|
250
|
+
this.pairingTimer = void 0;
|
|
251
|
+
}
|
|
252
|
+
(_a = this.discovery) == null ? void 0 : _a.stop();
|
|
253
|
+
this.discovery = null;
|
|
254
|
+
void this.setStateAsync("startPairing", { val: false, ack: true });
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Connect to a device via WebSocket
|
|
258
|
+
*
|
|
259
|
+
* @param config Device configuration
|
|
260
|
+
*/
|
|
261
|
+
async connectDevice(config) {
|
|
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);
|
|
274
|
+
try {
|
|
275
|
+
const client = new import_homewizard_client.HomeWizardClient(config.ip, config.token);
|
|
276
|
+
const info = await client.getDeviceInfo();
|
|
277
|
+
await this.setStateAsync(`${key}.info.firmware`, {
|
|
278
|
+
val: info.firmware_version,
|
|
279
|
+
ack: true
|
|
280
|
+
});
|
|
281
|
+
} catch (err) {
|
|
282
|
+
this.logDeviceError(conn, "init", err);
|
|
283
|
+
}
|
|
284
|
+
this.connectWebSocket(conn);
|
|
285
|
+
void this.pollSystemInfo(conn);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Connect WebSocket for a device
|
|
289
|
+
*
|
|
290
|
+
* @param conn Device connection
|
|
291
|
+
*/
|
|
292
|
+
connectWebSocket(conn) {
|
|
293
|
+
const key = `${conn.config.productType}_${conn.config.serial}`;
|
|
294
|
+
const wsClient = new import_websocket_client.HomeWizardWebSocket(
|
|
295
|
+
conn.config.ip,
|
|
296
|
+
conn.config.token,
|
|
297
|
+
{
|
|
298
|
+
onMeasurement: (data) => {
|
|
299
|
+
void this.stateManager.updateMeasurement(conn.config, data);
|
|
300
|
+
},
|
|
301
|
+
onConnected: () => {
|
|
302
|
+
conn.wsAuthenticated = true;
|
|
303
|
+
conn.wsFailCount = 0;
|
|
304
|
+
conn.lastErrorCode = "";
|
|
305
|
+
void this.stateManager.setDeviceConnected(conn.config, true);
|
|
306
|
+
this.updateGlobalConnection();
|
|
307
|
+
if (conn.pollTimer) {
|
|
308
|
+
this.clearInterval(conn.pollTimer);
|
|
309
|
+
conn.pollTimer = void 0;
|
|
310
|
+
}
|
|
311
|
+
this.log.debug(
|
|
312
|
+
`WebSocket connected to ${conn.config.productName} (${conn.config.ip})`
|
|
313
|
+
);
|
|
314
|
+
},
|
|
315
|
+
onDisconnected: (error) => {
|
|
316
|
+
conn.wsAuthenticated = false;
|
|
317
|
+
void this.stateManager.setDeviceConnected(conn.config, false);
|
|
318
|
+
this.updateGlobalConnection();
|
|
319
|
+
if (error) {
|
|
320
|
+
this.logDeviceError(conn, "ws", error);
|
|
321
|
+
}
|
|
322
|
+
this.startRestFallback(conn);
|
|
323
|
+
conn.wsFailCount++;
|
|
324
|
+
const delay = Math.min(
|
|
325
|
+
WS_RECONNECT_BASE_MS * Math.pow(2, conn.wsFailCount - 1),
|
|
326
|
+
WS_RECONNECT_MAX_MS
|
|
327
|
+
);
|
|
328
|
+
this.log.debug(
|
|
329
|
+
`${key}: WS reconnect in ${delay / 1e3}s (attempt ${conn.wsFailCount})`
|
|
330
|
+
);
|
|
331
|
+
conn.reconnectTimer = this.setTimeout(() => {
|
|
332
|
+
conn.reconnectTimer = void 0;
|
|
333
|
+
this.connectWebSocket(conn);
|
|
334
|
+
}, delay);
|
|
335
|
+
},
|
|
336
|
+
log: this.log
|
|
337
|
+
}
|
|
338
|
+
);
|
|
339
|
+
conn.wsClient = wsClient;
|
|
340
|
+
wsClient.connect();
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Start REST polling as fallback when WebSocket is down
|
|
344
|
+
*
|
|
345
|
+
* @param conn Device connection
|
|
346
|
+
*/
|
|
347
|
+
startRestFallback(conn) {
|
|
348
|
+
if (conn.pollTimer) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const client = new import_homewizard_client.HomeWizardClient(conn.config.ip, conn.config.token);
|
|
352
|
+
conn.pollTimer = this.setInterval(async () => {
|
|
353
|
+
try {
|
|
354
|
+
const data = await client.getMeasurement();
|
|
355
|
+
await this.stateManager.updateMeasurement(conn.config, data);
|
|
356
|
+
await this.stateManager.setDeviceConnected(conn.config, true);
|
|
357
|
+
} catch (err) {
|
|
358
|
+
this.logDeviceError(conn, "rest", err);
|
|
359
|
+
await this.stateManager.setDeviceConnected(conn.config, false);
|
|
360
|
+
}
|
|
361
|
+
this.updateGlobalConnection();
|
|
362
|
+
}, REST_POLL_MS);
|
|
363
|
+
}
|
|
364
|
+
/** Poll system info for all connected devices */
|
|
365
|
+
async pollAllSystemInfo() {
|
|
366
|
+
for (const conn of this.connections.values()) {
|
|
367
|
+
await this.pollSystemInfo(conn);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Poll system info for a single device
|
|
372
|
+
*
|
|
373
|
+
* @param conn Device connection
|
|
374
|
+
*/
|
|
375
|
+
async pollSystemInfo(conn) {
|
|
376
|
+
try {
|
|
377
|
+
const client = new import_homewizard_client.HomeWizardClient(conn.config.ip, conn.config.token);
|
|
378
|
+
const system = await client.getSystem();
|
|
379
|
+
await this.stateManager.updateSystem(conn.config, system);
|
|
380
|
+
try {
|
|
381
|
+
const battery = await client.getBatteries();
|
|
382
|
+
await this.stateManager.updateBattery(conn.config, battery);
|
|
383
|
+
} catch {
|
|
384
|
+
}
|
|
385
|
+
} catch (err) {
|
|
386
|
+
this.logDeviceError(conn, "system", err);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/** Update global info.connection based on all device states */
|
|
390
|
+
updateGlobalConnection() {
|
|
391
|
+
const anyConnected = Array.from(this.connections.values()).some(
|
|
392
|
+
(c) => c.wsAuthenticated
|
|
393
|
+
);
|
|
394
|
+
void this.setStateAsync("info.connection", {
|
|
395
|
+
val: anyConnected,
|
|
396
|
+
ack: true
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Find device config for a state ID
|
|
401
|
+
*
|
|
402
|
+
* @param stateId Full state ID
|
|
403
|
+
*/
|
|
404
|
+
findDeviceForState(stateId) {
|
|
405
|
+
const localId = stateId.replace(`${this.namespace}.`, "");
|
|
406
|
+
for (const conn of this.connections.values()) {
|
|
407
|
+
const prefix = this.stateManager.devicePrefix(conn.config);
|
|
408
|
+
if (localId.startsWith(`${prefix}.`)) {
|
|
409
|
+
return conn.config;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return void 0;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Log device error with deduplication
|
|
416
|
+
*
|
|
417
|
+
* @param conn Device connection
|
|
418
|
+
* @param context Error context
|
|
419
|
+
* @param err Error object
|
|
420
|
+
*/
|
|
421
|
+
logDeviceError(conn, context, err) {
|
|
422
|
+
const code = err instanceof import_homewizard_client.HomeWizardApiError ? err.errorCode : err instanceof Error ? err.message : "unknown";
|
|
423
|
+
const key = `${context}:${code}`;
|
|
424
|
+
if (conn.lastErrorCode === key) {
|
|
425
|
+
this.log.debug(
|
|
426
|
+
`${conn.config.productName} (${conn.config.ip}) ${context}: ${code}`
|
|
427
|
+
);
|
|
428
|
+
} else {
|
|
429
|
+
conn.lastErrorCode = key;
|
|
430
|
+
this.log.warn(
|
|
431
|
+
`${conn.config.productName} (${conn.config.ip}) ${context}: ${err instanceof Error ? err.message : String(err)}`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (require.main !== module) {
|
|
437
|
+
module.exports = (options) => new HomeWizard(options);
|
|
438
|
+
} else {
|
|
439
|
+
(() => new HomeWizard())();
|
|
440
|
+
}
|
|
441
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/io-package.json
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"name": "homewizard",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"news": {
|
|
6
|
+
"0.1.0": {
|
|
7
|
+
"en": "Initial release with API v2, WebSocket push, mDNS discovery, multi-device pairing",
|
|
8
|
+
"de": "Erstveröffentlichung mit API v2, WebSocket-Push, mDNS-Discovery, Multi-Geräte-Pairing",
|
|
9
|
+
"ru": "Первый выпуск с API v2, WebSocket push, mDNS discovery, сопряжение нескольких устройств",
|
|
10
|
+
"pt": "Lançamento inicial com API v2, WebSocket push, descoberta mDNS, emparelhamento multi-dispositivo",
|
|
11
|
+
"nl": "Eerste release met API v2, WebSocket push, mDNS discovery, multi-device pairing",
|
|
12
|
+
"fr": "Version initiale avec API v2, WebSocket push, découverte mDNS, appairage multi-appareils",
|
|
13
|
+
"it": "Rilascio iniziale con API v2, WebSocket push, scoperta mDNS, associazione multi-dispositivo",
|
|
14
|
+
"es": "Lanzamiento inicial con API v2, WebSocket push, descubrimiento mDNS, emparejamiento multi-dispositivo",
|
|
15
|
+
"pl": "Pierwsze wydanie z API v2, WebSocket push, odkrywanie mDNS, parowanie wielu urządzeń",
|
|
16
|
+
"uk": "Початковий випуск з API v2, WebSocket push, mDNS виявлення, з'єднання кількох пристроїв",
|
|
17
|
+
"zh-cn": "初始版本,支持 API v2、WebSocket 推送、mDNS 发现、多设备配对"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"titleLang": {
|
|
21
|
+
"en": "HomeWizard Energy",
|
|
22
|
+
"de": "HomeWizard Energy",
|
|
23
|
+
"ru": "HomeWizard Energy",
|
|
24
|
+
"pt": "HomeWizard Energy",
|
|
25
|
+
"nl": "HomeWizard Energy",
|
|
26
|
+
"fr": "HomeWizard Energy",
|
|
27
|
+
"it": "HomeWizard Energy",
|
|
28
|
+
"es": "HomeWizard Energy",
|
|
29
|
+
"pl": "HomeWizard Energy",
|
|
30
|
+
"uk": "HomeWizard Energy",
|
|
31
|
+
"zh-cn": "HomeWizard Energy"
|
|
32
|
+
},
|
|
33
|
+
"desc": {
|
|
34
|
+
"en": "Real-time energy monitoring from HomeWizard devices (P1 Meter, kWh Meter, Battery) via API v2 with WebSocket push",
|
|
35
|
+
"de": "Echtzeit-Energieüberwachung von HomeWizard-Geräten (P1 Meter, kWh Meter, Batterie) via API v2 mit WebSocket-Push",
|
|
36
|
+
"ru": "Мониторинг энергии в реальном времени от устройств HomeWizard (P1 Meter, kWh Meter, Battery) через API v2 с WebSocket push",
|
|
37
|
+
"pt": "Monitoramento de energia em tempo real de dispositivos HomeWizard (P1 Meter, kWh Meter, Battery) via API v2 com WebSocket push",
|
|
38
|
+
"nl": "Realtime energiemonitoring van HomeWizard-apparaten (P1 Meter, kWh Meter, Battery) via API v2 met WebSocket push",
|
|
39
|
+
"fr": "Surveillance énergétique en temps réel des appareils HomeWizard (P1 Meter, kWh Meter, Battery) via API v2 avec WebSocket push",
|
|
40
|
+
"it": "Monitoraggio energetico in tempo reale da dispositivi HomeWizard (P1 Meter, kWh Meter, Battery) via API v2 con WebSocket push",
|
|
41
|
+
"es": "Monitoreo de energía en tiempo real de dispositivos HomeWizard (P1 Meter, kWh Meter, Battery) vía API v2 con WebSocket push",
|
|
42
|
+
"pl": "Monitorowanie energii w czasie rzeczywistym z urządzeń HomeWizard (P1 Meter, kWh Meter, Battery) przez API v2 z WebSocket push",
|
|
43
|
+
"uk": "Моніторинг енергії в реальному часі від пристроїв HomeWizard (P1 Meter, kWh Meter, Battery) через API v2 з WebSocket push",
|
|
44
|
+
"zh-cn": "通过 API v2 和 WebSocket 推送实时监控 HomeWizard 设备(P1 Meter、kWh Meter、Battery)的能源数据"
|
|
45
|
+
},
|
|
46
|
+
"authors": [
|
|
47
|
+
"krobi <krobi@power-dreams.com>"
|
|
48
|
+
],
|
|
49
|
+
"keywords": [
|
|
50
|
+
"homewizard",
|
|
51
|
+
"energy",
|
|
52
|
+
"p1",
|
|
53
|
+
"meter",
|
|
54
|
+
"smartmeter",
|
|
55
|
+
"websocket",
|
|
56
|
+
"power"
|
|
57
|
+
],
|
|
58
|
+
"licenseInformation": {
|
|
59
|
+
"license": "MIT",
|
|
60
|
+
"type": "free"
|
|
61
|
+
},
|
|
62
|
+
"platform": "Javascript/Node.js",
|
|
63
|
+
"icon": "homewizard.svg",
|
|
64
|
+
"extIcon": "https://raw.githubusercontent.com/krobipd/ioBroker.homewizard/main/admin/homewizard.svg",
|
|
65
|
+
"readme": "https://github.com/krobipd/ioBroker.homewizard/blob/main/README.md",
|
|
66
|
+
"enabled": true,
|
|
67
|
+
"tier": 3,
|
|
68
|
+
"loglevel": "info",
|
|
69
|
+
"mode": "daemon",
|
|
70
|
+
"type": "energy",
|
|
71
|
+
"compact": true,
|
|
72
|
+
"connectionType": "local",
|
|
73
|
+
"dataSource": "push",
|
|
74
|
+
"adminUI": {
|
|
75
|
+
"config": "json"
|
|
76
|
+
},
|
|
77
|
+
"supportedMessages": {
|
|
78
|
+
"stopInstance": true
|
|
79
|
+
},
|
|
80
|
+
"dependencies": [
|
|
81
|
+
{
|
|
82
|
+
"js-controller": ">=7.0.0"
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"globalDependencies": [
|
|
86
|
+
{
|
|
87
|
+
"admin": ">=7.6.20"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
"encryptedNative": [
|
|
92
|
+
"devices"
|
|
93
|
+
],
|
|
94
|
+
"protectedNative": [
|
|
95
|
+
"devices"
|
|
96
|
+
],
|
|
97
|
+
"native": {
|
|
98
|
+
"devices": []
|
|
99
|
+
},
|
|
100
|
+
"instanceObjects": [
|
|
101
|
+
{
|
|
102
|
+
"_id": "info",
|
|
103
|
+
"type": "channel",
|
|
104
|
+
"common": {
|
|
105
|
+
"name": "Adapter Information"
|
|
106
|
+
},
|
|
107
|
+
"native": {}
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"_id": "info.connection",
|
|
111
|
+
"type": "state",
|
|
112
|
+
"common": {
|
|
113
|
+
"name": "Connection status",
|
|
114
|
+
"type": "boolean",
|
|
115
|
+
"role": "indicator.connected",
|
|
116
|
+
"read": true,
|
|
117
|
+
"write": false,
|
|
118
|
+
"def": false
|
|
119
|
+
},
|
|
120
|
+
"native": {}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"_id": "startPairing",
|
|
124
|
+
"type": "state",
|
|
125
|
+
"common": {
|
|
126
|
+
"name": "Start pairing mode",
|
|
127
|
+
"type": "boolean",
|
|
128
|
+
"role": "button",
|
|
129
|
+
"read": false,
|
|
130
|
+
"write": true,
|
|
131
|
+
"def": false
|
|
132
|
+
},
|
|
133
|
+
"native": {}
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
}
|