homebridge-gree-ac 2.2.2-beta.5 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +8 -4
  2. package/LICENSE +175 -175
  3. package/README.md +511 -500
  4. package/config.schema.json +520 -509
  5. package/dist/commands.d.ts +35 -160
  6. package/dist/commands.js +160 -162
  7. package/dist/commands.js.map +1 -1
  8. package/dist/crypto.d.ts +10 -11
  9. package/dist/crypto.js +28 -33
  10. package/dist/crypto.js.map +1 -1
  11. package/dist/index.d.ts +6 -7
  12. package/dist/index.js +8 -6
  13. package/dist/index.js.map +1 -1
  14. package/dist/platform.d.ts +47 -43
  15. package/dist/platform.js +585 -549
  16. package/dist/platform.js.map +1 -1
  17. package/dist/platformAccessory.d.ts +108 -102
  18. package/dist/platformAccessory.js +1763 -1726
  19. package/dist/platformAccessory.js.map +1 -1
  20. package/dist/settings.d.ts +72 -113
  21. package/dist/settings.js +97 -103
  22. package/dist/settings.js.map +1 -1
  23. package/dist/tsAccessory.d.ts +32 -34
  24. package/dist/tsAccessory.js +69 -71
  25. package/dist/tsAccessory.js.map +1 -1
  26. package/dist/version.d.ts +1 -2
  27. package/dist/version.js +2 -5
  28. package/dist/version.js.map +1 -1
  29. package/greedevice.jpg +0 -0
  30. package/greedevinfo.jpg +0 -0
  31. package/greemac.jpg +0 -0
  32. package/ha_fan.jpg +0 -0
  33. package/ha_settings.jpg +0 -0
  34. package/package.json +56 -52
  35. package/uiconfig.jpg +0 -0
  36. package/uiconfigcustdef.jpg +0 -0
  37. package/uiconfigmin.jpg +0 -0
  38. package/dist/commands.d.ts.map +0 -1
  39. package/dist/crypto.d.ts.map +0 -1
  40. package/dist/index.d.ts.map +0 -1
  41. package/dist/platform.d.ts.map +0 -1
  42. package/dist/platformAccessory.d.ts.map +0 -1
  43. package/dist/settings.d.ts.map +0 -1
  44. package/dist/tsAccessory.d.ts.map +0 -1
  45. package/dist/version.d.ts.map +0 -1
package/dist/platform.js CHANGED
@@ -1,550 +1,586 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.GreeACPlatform = void 0;
7
- const dgram_1 = __importDefault(require("dgram"));
8
- const crypto_1 = __importDefault(require("./crypto"));
9
- const os_1 = require("os");
10
- const fs_1 = require("fs");
11
- const settings_1 = require("./settings");
12
- const platformAccessory_1 = require("./platformAccessory");
13
- const commands_1 = __importDefault(require("./commands"));
14
- const version_1 = require("./version");
15
- /**
16
- * HomebridgePlatform
17
- * This class is the main constructor for your plugin, this is where you should
18
- * parse the user config and discover/register accessories with Homebridge.
19
- */
20
- class GreeACPlatform {
21
- constructor(log, config, api) {
22
- var _a;
23
- this.log = log;
24
- this.config = config;
25
- this.api = api;
26
- this.Service = this.api.hap.Service;
27
- this.Characteristic = this.api.hap.Characteristic;
28
- this.pluginAddresses = {};
29
- this.ports = [];
30
- this.handleMessage = (msg, rinfo) => {
31
- this.log.debug('handleMessage -> %s', msg.toString());
32
- try {
33
- let message;
34
- try {
35
- message = JSON.parse(msg.toString());
36
- }
37
- catch (e) {
38
- this.log.debug('handleMessage - unknown message from %s - BASE64 encoded message: %s', rinfo.address.toString(), Buffer.from(msg).toString('base64'));
39
- return;
40
- }
41
- if (message.i !== 1 || message.t !== 'pack') {
42
- this.log.debug('handleMessage - unknown response from %s: %j', rinfo.address.toString(), message);
43
- return;
44
- }
45
- let pack, encryptionVersion;
46
- if (message.tag === undefined) {
47
- this.log.debug('handleMessage -> Encryption version: 1');
48
- pack = crypto_1.default.decrypt_v1(message.pack);
49
- encryptionVersion = 1;
50
- }
51
- else {
52
- this.log.debug('handleMessage -> Encryption version: 2');
53
- pack = crypto_1.default.decrypt_v2(message.pack, message.tag);
54
- encryptionVersion = 2;
55
- }
56
- this.log.debug('handleMessage - Package -> %j', pack);
57
- if (encryptionVersion === 1 && pack.t === 'dev' && pack.ver && !pack.ver.toString().startsWith('V1.')) {
58
- // some devices respond to scan command with V1 encryption but binding requires V2 encryption
59
- // we set encryption to V2 if device version is not V1.x
60
- encryptionVersion = 2;
61
- }
62
- if (pack.t === 'dev') {
63
- this.registerDevice({
64
- ...pack,
65
- address: rinfo.address,
66
- port: rinfo.port,
67
- encryptionVersion,
68
- });
69
- }
70
- else {
71
- this.log.debug('handleMessage - unknown package from %s: %j', rinfo.address.toString(), pack);
72
- }
73
- }
74
- catch (err) {
75
- const msg = err.message;
76
- this.log.error('handleMessage (%s) - Error: %s', rinfo.address.toString(), msg);
77
- }
78
- };
79
- this.registerDevice = (deviceInfo) => {
80
- var _a, _b, _c, _d, _e, _f, _g, _h;
81
- this.log.debug('registerDevice - deviceInfo:', JSON.stringify(deviceInfo));
82
- const devcfg = ((_a = this.config.devices) === null || _a === void 0 ? void 0 : _a.find((item) => item.mac === deviceInfo.mac)) || { mac: deviceInfo.mac };
83
- if (!devcfg.disabled && deviceInfo.subCnt !== undefined) {
84
- // this is a bridge and bridge is enabled
85
- if (!this.skippedDevices[deviceInfo.mac]) {
86
- this.log.info(`Accessory ${deviceInfo.mac} (${(_b = devcfg === null || devcfg === void 0 ? void 0 : devcfg.name) !== null && _b !== void 0 ? _b : (deviceInfo.name || deviceInfo.mac)}) is a bridge`);
87
- // skip bridge, only subdevices are AC units
88
- this.skippedDevices[deviceInfo.mac] = true;
89
- if (!deviceInfo.subCnt || deviceInfo.subCnt <= 0) {
90
- this.log.warn(`Warning: No device is attached to bridge '${(_c = devcfg === null || devcfg === void 0 ? void 0 : devcfg.name) !== null && _c !== void 0 ? _c : (deviceInfo.name || deviceInfo.mac)}'`);
91
- return;
92
- }
93
- // read subdevice parameters from configuration
94
- const subDevices = ((_d = this.config.devices) === null || _d === void 0 ? void 0 : _d.filter((cfg) => cfg.mac.endsWith(`@${deviceInfo.mac}`) &&
95
- cfg.mac.length > deviceInfo.mac.length + 1)) || [];
96
- // if (subDevices.length === 0) {
97
- // this.log.warn(`Warning: No configuration entry found for devices attached to bridge '${devcfg?.name ??
98
- // (deviceInfo.name || deviceInfo.mac)}'`);
99
- // return;
100
- // }
101
- // register subdevices
102
- subDevices.forEach((cfg) => {
103
- const subDeviceInfo = { ...deviceInfo };
104
- subDeviceInfo.mac = cfg['mac'];
105
- subDeviceInfo.uid = cfg['mac'].substring(0, cfg['mac'].indexOf('@'));
106
- if (subDeviceInfo.subCnt !== undefined) {
107
- delete subDeviceInfo.subCnt;
108
- }
109
- if (subDeviceInfo.name) {
110
- subDeviceInfo.name = `${subDeviceInfo.uid}@${subDeviceInfo.name}`;
111
- }
112
- this.log.debug('registerDevice - sub device:', subDeviceInfo.mac);
113
- this.registerDevice(subDeviceInfo);
114
- });
115
- // try to register all subdevices -- this part may be removed
116
- for (let i = 1; i <= deviceInfo.subCnt; i++) {
117
- const subDeviceInfo = { ...deviceInfo };
118
- subDeviceInfo.mac = `${i.toString()}@${deviceInfo.mac}`;
119
- subDeviceInfo.uid = i;
120
- if (subDeviceInfo.subCnt !== undefined) {
121
- delete subDeviceInfo.subCnt;
122
- }
123
- if (subDeviceInfo.name) {
124
- subDeviceInfo.name = `${subDeviceInfo.uid.toString()}@${subDeviceInfo.name}`;
125
- }
126
- this.log.debug('registerDevice - sub device:', subDeviceInfo.mac);
127
- this.registerDevice(subDeviceInfo);
128
- }
129
- }
130
- else {
131
- this.log.debug('registerDevice - already processed:', (_e = devcfg === null || devcfg === void 0 ? void 0 : devcfg.name) !== null && _e !== void 0 ? _e : (deviceInfo.name || deviceInfo.mac), deviceInfo.mac);
132
- }
133
- return;
134
- }
135
- const deviceConfig = {
136
- // parameters read from config
137
- ...devcfg,
138
- // fix incorrect values read from config but do not add any value if parameter is missing
139
- ...((devcfg.speedSteps && devcfg.speedSteps !== 3 && devcfg.speedSteps !== 5) || devcfg.speedSteps === 0 ?
140
- { speedSteps: settings_1.DEFAULT_DEVICE_CONFIG.speedSteps } : {}),
141
- ...(devcfg.temperatureSensor && Object.values(settings_1.TS_TYPE).includes(devcfg.temperatureSensor.toLowerCase()) ?
142
- { temperatureSensor: devcfg.temperatureSensor.toLowerCase() }
143
- : (devcfg.temperatureSensor ? { temperatureSensor: settings_1.DEFAULT_DEVICE_CONFIG.temperatureSensor } : {})),
144
- ...(devcfg.minimumTargetTemperature &&
145
- (devcfg.minimumTargetTemperature < Math.min(settings_1.TEMPERATURE_LIMITS.coolingMinimum, settings_1.TEMPERATURE_LIMITS.heatingMinimum) ||
146
- devcfg.minimumTargetTemperature > Math.max(settings_1.TEMPERATURE_LIMITS.coolingMaximum, settings_1.TEMPERATURE_LIMITS.heatingMaximum)) ?
147
- { minimumTargetTemperature: settings_1.DEFAULT_DEVICE_CONFIG.minimumTargetTemperature } : {}),
148
- ...(devcfg.maximumTargetTemperature &&
149
- (devcfg.maximumTargetTemperature < Math.min(settings_1.TEMPERATURE_LIMITS.coolingMinimum, settings_1.TEMPERATURE_LIMITS.heatingMinimum) ||
150
- devcfg.maximumTargetTemperature > Math.max(settings_1.TEMPERATURE_LIMITS.coolingMaximum, settings_1.TEMPERATURE_LIMITS.heatingMaximum)) ?
151
- { maximumTargetTemperature: settings_1.DEFAULT_DEVICE_CONFIG.maximumTargetTemperature } : {}),
152
- ...(devcfg.defaultVerticalSwing && ![commands_1.default.swingVertical.value.default, commands_1.default.swingVertical.value.fixedHighest,
153
- commands_1.default.swingVertical.value.fixedHigher, commands_1.default.swingVertical.value.fixedMiddle, commands_1.default.swingVertical.value.fixedLower,
154
- commands_1.default.swingVertical.value.fixedLowest].includes(devcfg.defaultVerticalSwing) ?
155
- { defaultVerticalSwing: settings_1.DEFAULT_DEVICE_CONFIG.defaultVerticalSwing } : {}),
156
- ...(devcfg.defaultFanVerticalSwing && ![commands_1.default.swingVertical.value.default, commands_1.default.swingVertical.value.fixedHighest,
157
- commands_1.default.swingVertical.value.fixedHigher, commands_1.default.swingVertical.value.fixedMiddle, commands_1.default.swingVertical.value.fixedLower,
158
- commands_1.default.swingVertical.value.fixedLowest].includes(devcfg.defaultFanVerticalSwing) ?
159
- { defaultFanVerticalSwing: settings_1.DEFAULT_DEVICE_CONFIG.defaultFanVerticalSwing } : {}),
160
- // overrideDefaultVerticalSwing remains here for compatibility reasons
161
- ...(devcfg.overrideDefaultVerticalSwing &&
162
- !Object.values(settings_1.MODIFY_VERTICAL_SWING_POSITION).includes(devcfg.overrideDefaultVerticalSwing) ?
163
- { overrideDefaultVerticalSwing: settings_1.DEFAULT_DEVICE_CONFIG.modifyVerticalSwingPosition } : {}),
164
- ...(devcfg.modifyVerticalSwingPosition &&
165
- !Object.values(settings_1.MODIFY_VERTICAL_SWING_POSITION).includes(devcfg.modifyVerticalSwingPosition) ?
166
- { modifyVerticalSwingPosition: settings_1.DEFAULT_DEVICE_CONFIG.modifyVerticalSwingPosition } : {}),
167
- ...(devcfg.encryptionVersion && !Object.values(settings_1.ENCRYPTION_VERSION).includes(devcfg.encryptionVersion) ?
168
- { encryptionVersion: settings_1.DEFAULT_DEVICE_CONFIG.encryptionVersion } : {}),
169
- };
170
- // assign customized default to missing parameters
171
- Object.entries(((_f = this.config.devices) === null || _f === void 0 ? void 0 : _f.find((item) => { var _a; return ((_a = item.mac) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'default' && !item.disabled; })) || {})
172
- .forEach(([key, value]) => {
173
- if (!['mac', 'name', 'ip', 'port', 'disabled'].includes(key) && deviceConfig[key] === undefined) {
174
- deviceConfig[key] = value;
175
- }
176
- });
177
- // try to assign temperatureStepSize from Homebridge UI if missing in configuration
178
- if (deviceConfig.temperatureStepSize === undefined && this.tempUnit === 'c') {
179
- deviceConfig.temperatureStepSize = settings_1.TEMPERATURE_STEPS.celsius;
180
- }
181
- if (deviceConfig.temperatureStepSize === undefined && this.tempUnit === 'f') {
182
- deviceConfig.temperatureStepSize = settings_1.TEMPERATURE_STEPS.fahrenheit;
183
- }
184
- if (deviceConfig.temperatureStepSize !== undefined && !Object.values(settings_1.TEMPERATURE_STEPS).includes(deviceConfig.temperatureStepSize)) {
185
- this.log.warn(`Warning: Invalid temperature step size detected: ${deviceConfig.temperatureStepSize} ->`, `Accessory ${deviceInfo.mac} is using default value (0.5) instead of the configured one`);
186
- delete deviceConfig.temperatureStepSize;
187
- }
188
- // assign plugin default to missing parameters
189
- Object.entries(settings_1.DEFAULT_DEVICE_CONFIG).forEach(([key, value]) => {
190
- if (deviceConfig[key] === undefined) {
191
- deviceConfig[key] = value;
192
- }
193
- });
194
- // check parameters and fix incorrect values (some of them are repeated checks because default device may also be incorrect)
195
- if ((deviceConfig.speedSteps && deviceConfig.speedSteps !== 3 && deviceConfig.speedSteps !== 5) || deviceConfig.speedSteps === 0) {
196
- deviceConfig.speedSteps = settings_1.DEFAULT_DEVICE_CONFIG.speedSteps;
197
- }
198
- if (deviceConfig.temperatureSensor && Object.values(settings_1.TS_TYPE).includes(deviceConfig.temperatureSensor.toLowerCase())) {
199
- deviceConfig.temperatureSensor = deviceConfig.temperatureSensor.toLowerCase();
200
- }
201
- else {
202
- deviceConfig.temperatureSensor = settings_1.DEFAULT_DEVICE_CONFIG.temperatureSensor;
203
- }
204
- if (deviceConfig.minimumTargetTemperature &&
205
- (deviceConfig.minimumTargetTemperature < Math.min(settings_1.TEMPERATURE_LIMITS.coolingMinimum, settings_1.TEMPERATURE_LIMITS.heatingMinimum) ||
206
- deviceConfig.minimumTargetTemperature > Math.max(settings_1.TEMPERATURE_LIMITS.coolingMaximum, settings_1.TEMPERATURE_LIMITS.heatingMaximum))) {
207
- deviceConfig.minimumTargetTemperature = settings_1.DEFAULT_DEVICE_CONFIG.minimumTargetTemperature;
208
- }
209
- if (deviceConfig.maximumTargetTemperature &&
210
- (deviceConfig.maximumTargetTemperature < Math.min(settings_1.TEMPERATURE_LIMITS.coolingMinimum, settings_1.TEMPERATURE_LIMITS.heatingMinimum) ||
211
- deviceConfig.maximumTargetTemperature > Math.max(settings_1.TEMPERATURE_LIMITS.coolingMaximum, settings_1.TEMPERATURE_LIMITS.heatingMaximum))) {
212
- deviceConfig.maximumTargetTemperature = settings_1.DEFAULT_DEVICE_CONFIG.maximumTargetTemperature;
213
- }
214
- if (deviceConfig.minimumTargetTemperature && deviceConfig.maximumTargetTemperature &&
215
- deviceConfig.minimumTargetTemperature > deviceConfig.maximumTargetTemperature) {
216
- deviceConfig.minimumTargetTemperature =
217
- Math.min(settings_1.DEFAULT_DEVICE_CONFIG.minimumTargetTemperature, settings_1.DEFAULT_DEVICE_CONFIG.maximumTargetTemperature);
218
- deviceConfig.maximumTargetTemperature =
219
- Math.max(settings_1.DEFAULT_DEVICE_CONFIG.minimumTargetTemperature, settings_1.DEFAULT_DEVICE_CONFIG.maximumTargetTemperature);
220
- this.log.warn('Warning: Invalid minimum and maximum target temperature values detected ->', `Accessory ${deviceInfo.mac} is using default values instead of the configured ones`);
221
- }
222
- if (deviceConfig.defaultVerticalSwing && ![commands_1.default.swingVertical.value.default, commands_1.default.swingVertical.value.fixedHighest,
223
- commands_1.default.swingVertical.value.fixedHigher, commands_1.default.swingVertical.value.fixedMiddle, commands_1.default.swingVertical.value.fixedLower,
224
- commands_1.default.swingVertical.value.fixedLowest].includes(deviceConfig.defaultVerticalSwing)) {
225
- deviceConfig.defaultVerticalSwing = settings_1.DEFAULT_DEVICE_CONFIG.defaultVerticalSwing;
226
- this.log.warn('Warning: Invalid vertical position detected ->', `Accessory ${deviceInfo.mac} is using default value instead of the configured one`);
227
- }
228
- if (deviceConfig.defaultFanVerticalSwing && ![commands_1.default.swingVertical.value.default, commands_1.default.swingVertical.value.fixedHighest,
229
- commands_1.default.swingVertical.value.fixedHigher, commands_1.default.swingVertical.value.fixedMiddle, commands_1.default.swingVertical.value.fixedLower,
230
- commands_1.default.swingVertical.value.fixedLowest].includes(deviceConfig.defaultFanVerticalSwing)) {
231
- deviceConfig.defaultFanVerticalSwing = settings_1.DEFAULT_DEVICE_CONFIG.defaultFanVerticalSwing;
232
- this.log.warn('Warning: Invalid vertical fan position detected ->', `Accessory ${deviceInfo.mac} is using default value instead of the configured one`);
233
- }
234
- // overrideDefaultVerticalSwing remains here for compatibility reasons
235
- if (deviceConfig.overrideDefaultVerticalSwing &&
236
- !Object.values(settings_1.MODIFY_VERTICAL_SWING_POSITION).includes(deviceConfig.overrideDefaultVerticalSwing)) {
237
- deviceConfig.overrideDefaultVerticalSwing = settings_1.DEFAULT_DEVICE_CONFIG.modifyVerticalSwingPosition;
238
- }
239
- if (deviceConfig.modifyVerticalSwingPosition &&
240
- !Object.values(settings_1.MODIFY_VERTICAL_SWING_POSITION).includes(deviceConfig.modifyVerticalSwingPosition)) {
241
- deviceConfig.modifyVerticalSwingPosition = settings_1.DEFAULT_DEVICE_CONFIG.modifyVerticalSwingPosition;
242
- }
243
- if (deviceConfig.encryptionVersion && !Object.values(settings_1.ENCRYPTION_VERSION).includes(deviceConfig.encryptionVersion)) {
244
- deviceConfig.encryptionVersion = settings_1.DEFAULT_DEVICE_CONFIG.encryptionVersion;
245
- }
246
- if (deviceConfig.port !== undefined && (typeof deviceConfig.port !== 'number' || deviceConfig.port !== deviceConfig.port ||
247
- (typeof deviceConfig.port === 'number' && (deviceConfig.port < 1025 || deviceConfig.port > 65535)))) {
248
- this.log.warn('Warning: Port is misconfigured (Valid port values: 1025~65535 or leave port empty to auto select) - ' +
249
- `Accessory ${deviceInfo.mac} listening port overridden: ${deviceConfig.port} -> auto`);
250
- delete deviceConfig.port;
251
- }
252
- // replace deprecated overrideDefaultVerticalSwing with new modifyVerticalSwingPosition
253
- if (deviceConfig.overrideDefaultVerticalSwing !== undefined && deviceConfig.modifyVerticalSwingPosition === undefined) {
254
- // found deprecated but missing new
255
- if (!this.warningShown[`${deviceInfo.mac}_overrideDefaultVerticalSwing`]) {
256
- this.log.warn('Deprecated configuration parameter found: overrideDefaultVerticalSwing - ' +
257
- `Accessory ${deviceInfo.mac} parameter value: ${deviceConfig.overrideDefaultVerticalSwing} -> use modifyVerticalSwingPosition`);
258
- this.warningShown[`${deviceInfo.mac}_overrideDefaultVerticalSwing`] = true;
259
- }
260
- deviceConfig.modifyVerticalSwingPosition = deviceConfig.overrideDefaultVerticalSwing;
261
- delete deviceConfig.overrideDefaultVerticalSwing;
262
- }
263
- else if (deviceConfig.overrideDefaultVerticalSwing !== undefined && deviceConfig.modifyVerticalSwingPosition !== undefined) {
264
- // found both deprecated and new -> keep new only
265
- if (!this.warningShown[`${deviceInfo.mac}_overrideDefaultVerticalSwing`]) {
266
- this.log.warn('Deprecated configuration parameter found: overrideDefaultVerticalSwing - ' +
267
- `Accessory ${deviceInfo.mac} parameter value: ${deviceConfig.overrideDefaultVerticalSwing} -> ignoring`);
268
- this.warningShown[`${deviceInfo.mac}_overrideDefaultVerticalSwing`] = true;
269
- }
270
- delete deviceConfig.overrideDefaultVerticalSwing;
271
- }
272
- // ignore invalid silentTimeRange
273
- if (deviceConfig.silentTimeRange) {
274
- const match = deviceConfig.silentTimeRange.match(/^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]-(((0[0-9]|1[0-9]|2[0-3]):[0-5][0-9])|24:00)$/);
275
- if (!match || (match && deviceConfig.silentTimeRange !== match[0])) {
276
- // invalid parameter value (not in HH:MM-HH:MM format)
277
- if (!this.warningShown[`${deviceInfo.mac}_silentTimeRange`]) {
278
- this.log.warn('Invalid configuration parameter value found: silentTimeRange - ' +
279
- `Accessory ${deviceInfo.mac} parameter value: ${deviceConfig.silentTimeRange} -> ignoring`);
280
- this.warningShown[`${deviceInfo.mac}_silentTimeRange`] = true;
281
- }
282
- delete deviceConfig.silentTimeRange;
283
- }
284
- }
285
- // force encryption version if set in config
286
- if (deviceConfig.encryptionVersion !== settings_1.ENCRYPTION_VERSION.auto) {
287
- deviceInfo.encryptionVersion = deviceConfig.encryptionVersion;
288
- this.log.debug(`Accessory ${deviceInfo.mac} encryption version forced:`, deviceInfo.encryptionVersion);
289
- }
290
- let accessory = this.devices[deviceInfo.mac];
291
- let accessory_ts = this.devices[deviceInfo.mac + '_ts'];
292
- if ((deviceConfig === null || deviceConfig === void 0 ? void 0 : deviceConfig.disabled) || !/^[a-f0-9]{12}$/.test(deviceConfig === null || deviceConfig === void 0 ? void 0 : deviceConfig.mac.substring((deviceConfig === null || deviceConfig === void 0 ? void 0 : deviceConfig.mac.indexOf('@')) + 1))) {
293
- if (!devcfg || Object.keys(devcfg).length === 0) {
294
- this.log.debug('14 DEBUG:', deviceConfig);
295
- }
296
- //do not skip unconfigured devices
297
- if (!this.skippedDevices[deviceInfo.mac]) {
298
- this.log.info(`Accessory ${deviceInfo.mac} skipped`);
299
- this.skippedDevices[deviceInfo.mac] = true;
300
- }
301
- else {
302
- this.log.debug(`Accessory ${deviceInfo.mac} skipped`);
303
- }
304
- if (accessory) {
305
- delete this.devices[accessory.context.device.mac];
306
- this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
307
- this.log.debug(`registerDevice - unregister (${devcfg.mac === undefined ? 'not configured' : 'disabled'}):`, accessory.displayName, accessory.UUID);
308
- accessory = undefined;
309
- }
310
- if (accessory_ts) {
311
- delete this.devices[accessory_ts.context.device.mac];
312
- this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory_ts]);
313
- this.log.debug(`registerDevice - unregister (${devcfg.mac === undefined ? 'not configured' : 'disabled'}):`, accessory_ts.displayName, accessory_ts.UUID);
314
- accessory_ts = undefined;
315
- }
316
- return;
317
- }
318
- // check device address change
319
- if (accessory && deviceInfo.address !== accessory.context.device.address) {
320
- this.log.info(`Device [${accessory.displayName} - ${accessory.context.device.mac}] address has changed: %s -> %s`, accessory.context.device.address, deviceInfo.address);
321
- accessory.context.device.address = deviceInfo.address;
322
- }
323
- if (accessory_ts && deviceInfo.address !== accessory_ts.context.device.address) {
324
- accessory_ts.context.device.address = deviceInfo.address;
325
- }
326
- if (accessory && this.processedDevices[accessory.UUID]) {
327
- // already initalized
328
- this.log.debug('registerDevice - already processed:', accessory.displayName, accessory.context.device.mac, accessory.UUID);
329
- return;
330
- }
331
- // create heatercooler accessory if not loaded from cache
332
- const deviceName = (_g = deviceConfig === null || deviceConfig === void 0 ? void 0 : deviceConfig.name) !== null && _g !== void 0 ? _g : (deviceInfo.name || deviceInfo.mac);
333
- if (!accessory) {
334
- this.log.debug(`Creating new accessory ${deviceInfo.mac} with name ${deviceName} ...`);
335
- const uuid = this.api.hap.uuid.generate(deviceInfo.mac);
336
- accessory = new this.api.platformAccessory(deviceName, uuid, 21 /* Categories.AIR_CONDITIONER */);
337
- accessory.bound = false;
338
- accessory.registered = false;
339
- this.devices[deviceInfo.mac] = accessory;
340
- }
341
- // create temperaturesensor accessory if configured as separate and not loaded from cache
342
- const tsDeviceName = 'Temperature Sensor ' + ((_h = deviceConfig === null || deviceConfig === void 0 ? void 0 : deviceConfig.name) !== null && _h !== void 0 ? _h : (deviceInfo.name || deviceInfo.mac));
343
- if (!accessory_ts && deviceConfig.temperatureSensor === settings_1.TS_TYPE.separate) {
344
- this.log.debug(`Creating new accessory ${deviceInfo.mac}_ts with name ${tsDeviceName} ...`);
345
- const uuid = this.api.hap.uuid.generate(deviceInfo.mac + '_ts');
346
- accessory_ts = new this.api.platformAccessory(tsDeviceName, uuid, 10 /* Categories.SENSOR */);
347
- accessory_ts.registered = false;
348
- this.devices[deviceInfo.mac + '_ts'] = accessory_ts;
349
- }
350
- // unregister temperaturesensor accessory if configuration has changed from separate to any other
351
- if (accessory_ts && deviceConfig.temperatureSensor !== settings_1.TS_TYPE.separate) {
352
- this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory_ts]);
353
- delete this.devices[deviceInfo.mac + '_ts'];
354
- this.log.debug('registerDevice - unregister:', accessory_ts.displayName, accessory_ts.UUID);
355
- accessory_ts = undefined;
356
- }
357
- if (accessory_ts && deviceConfig.temperatureSensor === settings_1.TS_TYPE.separate) {
358
- // mark temperature sensor device as initialized
359
- accessory_ts.context.device = { ...deviceInfo };
360
- accessory_ts.context.device.mac = deviceInfo.mac + '_ts';
361
- accessory_ts.context.deviceType = 'TemperatureSensor';
362
- if (deviceConfig.model) {
363
- accessory_ts.context.device.model = deviceConfig.model;
364
- }
365
- this.processedDevices[accessory_ts.UUID] = true;
366
- this.log.debug(`registerDevice - ${accessory_ts.context.deviceType} created:`, accessory_ts.displayName, accessory_ts.context.device.mac, accessory_ts.UUID);
367
- // do not load temperature sensor accessory here (it will be loaded from heatercooler accessory)
368
- }
369
- if (accessory) {
370
- // mark heatercooler device as processed
371
- accessory.context.device = deviceInfo;
372
- accessory.context.deviceType = 'HeaterCooler';
373
- this.processedDevices[accessory.UUID] = true;
374
- this.log.debug(`registerDevice - ${accessory.context.deviceType} created:`, accessory.displayName, accessory.context.device.mac, accessory.UUID);
375
- // load heatercooler accessory
376
- new platformAccessory_1.GreeAirConditioner(this, accessory, deviceConfig, accessory_ts === null || accessory_ts === void 0 ? void 0 : accessory_ts.context.device.mac);
377
- }
378
- };
379
- this.devices = {};
380
- this.processedDevices = {};
381
- this.skippedDevices = {};
382
- this.warningShown = {};
383
- this.pluginAddresses = this.getNetworkAddresses();
384
- if (Object.entries(this.pluginAddresses).length > 0) {
385
- this.log.debug('Device detection address list {(address : netmask) pairs}:', this.pluginAddresses);
386
- }
387
- else {
388
- this.log.error('Error: Homebridge host has no IPv4 address');
389
- }
390
- // if no IPv4 address found we create socket for IPv6
391
- this.socket = dgram_1.default.createSocket({ type: (Object.entries(this.pluginAddresses).length > 0) ? 'udp4' : 'udp6', reuseAddr: true });
392
- this.socket.on('error', (err) => {
393
- this.log.error('Network - Error:', err.message);
394
- });
395
- this.socket.on('close', () => {
396
- this.log.debug('Network - Connection closed');
397
- });
398
- // get temperature unit from Homebridge UI config
399
- const configPath = this.api.user.configPath();
400
- const cfg = JSON.parse((0, fs_1.readFileSync)(configPath).toString());
401
- const configPlatform = ((_a = cfg === null || cfg === void 0 ? void 0 : cfg.platforms) === null || _a === void 0 ? void 0 : _a.find((item) => item.platform === 'config')) || {};
402
- this.tempUnit = (configPlatform === null || configPlatform === void 0 ? void 0 : configPlatform.tempUnits) || 'f';
403
- if (!['f', 'c'].includes(this.tempUnit)) {
404
- this.tempUnit = 'f';
405
- }
406
- this.log.debug(`Temperature display unit is ${this.tempUnit === 'f' ? 'Fahrenheit (°F)' : 'Celsius (°C)'}`);
407
- this.log.debug('Finished initializing platform');
408
- // When this event is fired it means Homebridge has restored all cached accessories from disk.
409
- // Dynamic Platform plugins should only register new accessories after this event was fired,
410
- // in order to ensure they weren't added to homebridge already. This event can also be used
411
- // to start discovery of new accessories.
412
- this.api.on('didFinishLaunching', () => {
413
- log.debug('Executed didFinishLaunching callback');
414
- if (Object.entries(this.pluginAddresses).length === 0) {
415
- this.socket.close();
416
- this.log.error('Network - Error: No IPv4 host address found');
417
- }
418
- else {
419
- this.socket.on('message', this.handleMessage);
420
- // run the method to discover / register your devices as accessories
421
- this.discoverDevices();
422
- }
423
- });
424
- }
425
- /**
426
- * This function is invoked when homebridge restores cached accessories from disk at startup.
427
- * It should be used to setup event handlers for characteristics and update respective values.
428
- */
429
- configureAccessory(accessory) {
430
- var _a, _b, _c;
431
- this.log.debug('Loading accessory from cache:', accessory.displayName, JSON.stringify(accessory.context.device));
432
- // add the restored accessory to the accessories cache so we can track if it has already been registered
433
- if ((_b = (_a = accessory.context) === null || _a === void 0 ? void 0 : _a.device) === null || _b === void 0 ? void 0 : _b.mac) {
434
- if (!accessory.context.deviceType || accessory.context.deviceType === 'HeaterCooler') {
435
- accessory.bound = false;
436
- }
437
- accessory.registered = true;
438
- this.devices[accessory.context.device.mac] = accessory;
439
- }
440
- // clean all invalid accessories found in cache
441
- if (!accessory.context) {
442
- this.log.debug('Invalid accessory found in cache - deleting:', accessory.displayName, accessory.UUID);
443
- this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
444
- }
445
- // remove deprecated properties from cached accessory
446
- if (((_c = accessory.context) === null || _c === void 0 ? void 0 : _c.bound) !== undefined) {
447
- delete accessory.context.bound;
448
- }
449
- }
450
- /**
451
- * Accessories must only be registered once, previously created accessories
452
- * must not be registered again to prevent "duplicate UUID" errors.
453
- */
454
- bindCallback() {
455
- this.log.success(`${settings_1.PLATFORM_NAME} (${settings_1.PLUGIN_NAME}) v%s is running on UDP port %d`, version_1.version, this.socket.address().port);
456
- this.ports.push(this.socket.address().port);
457
- this.socket.setBroadcast(true);
458
- this.sendScan();
459
- setInterval(() => {
460
- this.sendScan();
461
- }, (this.config.scanInterval || settings_1.DEF_SCAN_INTERVAL) * 1000); // scanInterval in seconds (default = 60 sec)
462
- }
463
- discoverDevices() {
464
- if (this.config.port === undefined || (this.config.port !== undefined && typeof this.config.port === 'number' &&
465
- this.config.port === this.config.port && this.config.port >= 1025 && this.config.port <= 65535)) {
466
- this.socket.bind(this.config.port, undefined, () => this.bindCallback());
467
- }
468
- else {
469
- this.log.error('Error: Port is misconfigured (Valid port values: 1025~65535 or leave port empty to auto select)');
470
- this.socket.close();
471
- }
472
- }
473
- sendScan() {
474
- const message = Buffer.from(JSON.stringify({ t: 'scan' }));
475
- Object.entries(this.pluginAddresses).forEach((value) => {
476
- const addr = value[0];
477
- this.socket.send(message, 0, message.length, settings_1.UDP_SCAN_PORT, addr, (error) => {
478
- if (this.pluginAddresses[addr] === '255.255.255.255') {
479
- this.log.debug(`Scanning for device (unicast) '${message}' ${addr}:${settings_1.UDP_SCAN_PORT}`);
480
- }
481
- else {
482
- this.log.debug(`Scanning for devices (broadcast) '${message}' ${addr}:${settings_1.UDP_SCAN_PORT}`);
483
- }
484
- if (error) {
485
- this.log.error('Device scan - Error:', error.message);
486
- }
487
- });
488
- });
489
- }
490
- getNetworkAddresses() {
491
- var _a;
492
- this.log.debug('Checking network interfaces');
493
- const pluginAddresses = {};
494
- const allInterfaces = (0, os_1.networkInterfaces)();
495
- for (const name of Object.keys(allInterfaces)) {
496
- const nets = allInterfaces[name];
497
- if (nets) {
498
- for (const iface of nets) {
499
- // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
500
- const familyV4Value = typeof iface.family === 'string' ? 'IPv4' : 4;
501
- if (iface.family === familyV4Value && !iface.internal) {
502
- const addrParts = iface.address.split('.');
503
- const netmaskParts = iface.netmask.split('.');
504
- const broadcast = addrParts.map((e, i) => ((~Number(netmaskParts[i]) & 0xFF) | Number(e)).toString()).join('.');
505
- this.log.debug('Interface: \'%s\' Address: %s Netmask: %s Broadcast: %s', name, iface.address, iface.netmask, broadcast);
506
- if (pluginAddresses[broadcast] === undefined) {
507
- pluginAddresses[broadcast] = iface.netmask;
508
- }
509
- }
510
- }
511
- }
512
- }
513
- // Add IPs from configuration but only if at least one host address found (any only for valid mac addresses)
514
- if (Object.keys(pluginAddresses).length > 0) {
515
- const devcfgs = ((_a = this.config.devices) === null || _a === void 0 ? void 0 : _a.filter((item) => item.ip && !item.disabled && /^[a-f0-9]{12}$/.test(item.mac))) || [];
516
- devcfgs.forEach((value) => {
517
- const ip = value['ip'];
518
- const ipv4Pattern = /^(25[0-5]|2[0-4]\d|1\d{2}|\d{1,2})(\.(25[0-5]|2[0-4]\d|1\d{2}|\d{1,2})){3}$/;
519
- if (ipv4Pattern.test(ip)) {
520
- this.log.debug('Found AC Unit address in configuration:', ip);
521
- const addrParts = ip.split('.');
522
- const addresses = {};
523
- Object.keys(pluginAddresses).forEach((addr) => {
524
- const netmaskParts = pluginAddresses[addr].split('.');
525
- const broadcast = addrParts.map((e, i) => ((~Number(netmaskParts[i]) & 0xFF) | Number(e)).toString()).join('.');
526
- if (addr === broadcast) {
527
- addresses[ip] = true;
528
- }
529
- });
530
- const skipAddress = Object.keys(addresses).find((addr) => addr === ip);
531
- if (skipAddress === undefined) {
532
- pluginAddresses[ip] = '255.255.255.255';
533
- }
534
- else {
535
- this.log.debug('AC Unit (%s) is already on broadcast list - skipping', skipAddress);
536
- }
537
- }
538
- else {
539
- this.log.warn('Warning: Invalid IP address found in configuration: %s - skipping', ip);
540
- }
541
- });
542
- }
543
- return pluginAddresses;
544
- }
545
- getAccessory(mac) {
546
- return this.devices[mac];
547
- }
548
- }
549
- exports.GreeACPlatform = GreeACPlatform;
1
+ import dgram from 'dgram';
2
+ import crypto from './crypto.js';
3
+ import { networkInterfaces } from 'os';
4
+ import { readFileSync } from 'fs';
5
+ import { GreeAirConditioner } from './platformAccessory.js';
6
+ import { PLATFORM_NAME, PLUGIN_NAME, UDP_SCAN_PORT, DEFAULT_DEVICE_CONFIG, MODIFY_VERTICAL_SWING_POSITION, ENCRYPTION_VERSION, TS_TYPE, DEF_SCAN_INTERVAL, TEMPERATURE_LIMITS, TEMPERATURE_STEPS } from './settings.js';
7
+ import commands from './commands.js';
8
+ import { version } from './version.js';
9
+ /**
10
+ * HomebridgePlatform
11
+ * This class is the main constructor for your plugin, this is where you should
12
+ * parse the user config and discover/register accessories with Homebridge.
13
+ */
14
+ export class GreeACPlatform {
15
+ log;
16
+ config;
17
+ api;
18
+ Service;
19
+ Characteristic;
20
+ // this is used to track restored cached accessories
21
+ devices;
22
+ processedDevices;
23
+ skippedDevices;
24
+ warningShown;
25
+ socket;
26
+ pluginAddresses = {};
27
+ ports = [];
28
+ tempUnit;
29
+ // This is only required when using Custom Services and Characteristics not support by HomeKit
30
+ //public readonly CustomServices: any;
31
+ //public readonly CustomCharacteristics: any;
32
+ constructor(log, config, api) {
33
+ this.log = log;
34
+ this.config = config;
35
+ this.api = api;
36
+ this.Service = api.hap.Service;
37
+ this.Characteristic = api.hap.Characteristic;
38
+ // This is only required when using Custom Services and Characteristics not support by HomeKit
39
+ //this.CustomServices = new EveHomeKitTypes(this.api).Services;
40
+ //this.CustomCharacteristics = new EveHomeKitTypes(this.api).Characteristics;
41
+ this.devices = {};
42
+ this.processedDevices = {};
43
+ this.skippedDevices = {};
44
+ this.warningShown = {};
45
+ // get temperature unit from Homebridge UI config
46
+ const configPath = this.api.user.configPath();
47
+ const cfg = JSON.parse(readFileSync(configPath).toString());
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ const configPlatform = cfg?.platforms?.find((item) => item.platform === 'config') || {};
50
+ this.tempUnit = configPlatform?.tempUnits || 'f';
51
+ if (!['f', 'c'].includes(this.tempUnit)) {
52
+ this.tempUnit = 'f';
53
+ }
54
+ this.log.debug(`Temperature display unit is ${this.tempUnit === 'f' ? 'Fahrenheit (°F)' : 'Celsius (°C)'}`);
55
+ // log auto detection parameter
56
+ if (this.config.disableAutoDetection === true) {
57
+ this.log.debug('Auto detection disabled');
58
+ }
59
+ // network initialization
60
+ this.pluginAddresses = this.getNetworkAddresses(cfg?.bridge?.bind);
61
+ if (Object.entries(this.pluginAddresses).length > 0) {
62
+ this.log.debug('Device detection address list {(address : netmask) pairs}:', this.pluginAddresses);
63
+ }
64
+ else {
65
+ this.log.error('Error: Homebridge host has no IPv4 address');
66
+ }
67
+ // if no IPv4 address found we create socket for IPv6
68
+ this.socket = dgram.createSocket({ type: (Object.entries(this.pluginAddresses).length > 0) ? 'udp4' : 'udp6', reuseAddr: true });
69
+ this.socket.on('error', (err) => {
70
+ this.log.error('Network - Error:', err.message);
71
+ });
72
+ this.socket.on('close', () => {
73
+ this.log.debug('Network - Connection closed');
74
+ });
75
+ this.log.debug('Finished initializing platform');
76
+ // When this event is fired it means Homebridge has restored all cached accessories from disk.
77
+ // Dynamic Platform plugins should only register new accessories after this event was fired,
78
+ // in order to ensure they weren't added to homebridge already. This event can also be used
79
+ // to start discovery of new accessories.
80
+ this.api.on('didFinishLaunching', () => {
81
+ log.debug('Executing didFinishLaunching callback');
82
+ if (Object.entries(this.pluginAddresses).length === 0) {
83
+ this.socket.close();
84
+ this.log.error('Network - Error: No IPv4 host address found');
85
+ }
86
+ else {
87
+ this.socket.on('message', this.handleMessage);
88
+ // run the method to discover / register your devices as accessories
89
+ this.discoverDevices();
90
+ }
91
+ });
92
+ }
93
+ /**
94
+ * This function is invoked when homebridge restores cached accessories from disk at startup.
95
+ * It should be used to set up event handlers for characteristics and update respective values.
96
+ */
97
+ configureAccessory(accessory) {
98
+ this.log.debug('Loading accessory from cache:', accessory.displayName, JSON.stringify(accessory.context.device));
99
+ // add the restored accessory to the accessories cache, so we can track if it has already been registered
100
+ if (accessory.context?.device?.mac) {
101
+ if (!accessory.context.deviceType || accessory.context.deviceType === 'HeaterCooler') {
102
+ accessory.bound = false;
103
+ }
104
+ accessory.registered = true;
105
+ this.devices[accessory.context.device.mac] = accessory;
106
+ }
107
+ // clean all invalid accessories found in cache
108
+ if (!accessory.context) {
109
+ this.log.debug('Invalid accessory found in cache - deleting:', accessory.displayName, accessory.UUID);
110
+ this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
111
+ }
112
+ // remove deprecated properties from cached accessory
113
+ if (accessory.context?.bound !== undefined) {
114
+ delete accessory.context.bound;
115
+ }
116
+ }
117
+ /**
118
+ * Accessories must only be registered once, previously created accessories
119
+ * must not be registered again to prevent "duplicate UUID" errors.
120
+ */
121
+ bindCallback() {
122
+ this.log.success(`${PLATFORM_NAME} (${PLUGIN_NAME}) v%s is running on UDP port %d`, version, this.socket.address().port);
123
+ this.ports.push(this.socket.address().port);
124
+ this.socket.setBroadcast(true);
125
+ this.sendScan();
126
+ setInterval(() => {
127
+ this.sendScan();
128
+ }, (this.config.scanInterval || DEF_SCAN_INTERVAL) * 1000); // scanInterval in seconds (default = 60 sec)
129
+ }
130
+ discoverDevices() {
131
+ if (this.config.port === undefined || (this.config.port !== undefined && typeof this.config.port === 'number' &&
132
+ this.config.port === this.config.port && this.config.port >= 1025 && this.config.port <= 65535)) {
133
+ this.socket.bind(this.config.port, undefined, () => this.bindCallback());
134
+ }
135
+ else {
136
+ this.log.error('Error: Port is misconfigured (Valid port values: 1025~65535 or leave port empty to auto select)');
137
+ this.socket.close();
138
+ }
139
+ }
140
+ handleMessage = (msg, rinfo) => {
141
+ this.log.debug('handleMessage -> %s', msg.toString());
142
+ try {
143
+ let message;
144
+ try {
145
+ message = JSON.parse(msg.toString());
146
+ }
147
+ catch (e) {
148
+ this.log.debug('handleMessage - unknown message from %s - BASE64 encoded message: %s', rinfo.address, Buffer.from(msg).toString('base64'));
149
+ return;
150
+ }
151
+ if (message.i !== 1 || message.t !== 'pack') {
152
+ this.log.debug('handleMessage - unknown response from %s: %j', rinfo.address, message);
153
+ return;
154
+ }
155
+ let pack, encryptionVersion;
156
+ if (message.tag === undefined) {
157
+ this.log.debug('handleMessage -> Encryption version: 1');
158
+ pack = crypto.decrypt_v1(message.pack);
159
+ encryptionVersion = 1;
160
+ }
161
+ else {
162
+ this.log.debug('handleMessage -> Encryption version: 2');
163
+ pack = crypto.decrypt_v2(message.pack, message.tag);
164
+ encryptionVersion = 2;
165
+ }
166
+ this.log.debug('handleMessage - Package -> %j', pack);
167
+ if (encryptionVersion === 1 && pack.t === 'dev' && pack.ver && !pack.ver.toString().startsWith('V1.')) {
168
+ // some devices respond to scan command with V1 encryption but binding requires V2 encryption
169
+ // we set encryption to V2 if device version is not V1.x
170
+ encryptionVersion = 2;
171
+ }
172
+ if (pack.t === 'dev') {
173
+ if (this.config.disableAutoDetection !== true || this.config.devices?.find((item) => item.mac === pack.mac) !==
174
+ undefined) {
175
+ this.registerDevice({
176
+ ...pack,
177
+ address: rinfo.address,
178
+ port: rinfo.port,
179
+ encryptionVersion,
180
+ });
181
+ }
182
+ else {
183
+ if (this.config.disableAutoDetection === true && this.config.devices?.find((item) => item.mac === pack.mac) ===
184
+ undefined) {
185
+ if (this.skippedDevices[pack.mac] !== true) {
186
+ this.log.debug(`Accessory ${pack.mac} skipped`);
187
+ this.skippedDevices[pack.mac] = true;
188
+ }
189
+ }
190
+ }
191
+ }
192
+ else {
193
+ this.log.debug('handleMessage - unknown package from %s: %j', rinfo.address.toString(), pack);
194
+ }
195
+ }
196
+ catch (err) {
197
+ const msg = err.message;
198
+ this.log.error('handleMessage (%s) - Error: %s', rinfo.address.toString(), msg);
199
+ }
200
+ };
201
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
202
+ registerDevice = (deviceInfo) => {
203
+ this.log.debug('registerDevice - deviceInfo:', JSON.stringify(deviceInfo));
204
+ const devcfg = this.config.devices?.find((item) => item.mac === deviceInfo.mac) || { mac: deviceInfo.mac };
205
+ if (!devcfg.disabled && deviceInfo.subCnt !== undefined) {
206
+ // this is a bridge and bridge is enabled
207
+ if (!this.skippedDevices[deviceInfo.mac]) {
208
+ this.log.warn(`Accessory ${deviceInfo.mac} (${devcfg?.name ?? (deviceInfo.name || deviceInfo.mac)}) is a bridge.` +
209
+ ' Bridge accessories and devices attached to a bridge are not supported. If you are ready to help plugin development then' +
210
+ 'you can create an issue and post detailed debug log about your environment.');
211
+ // skip bridge, only subdevices are AC units
212
+ this.skippedDevices[deviceInfo.mac] = true;
213
+ if (!deviceInfo.subCnt || deviceInfo.subCnt <= 0) {
214
+ this.log.warn(`Warning: No device is attached to bridge '${devcfg?.name ?? (deviceInfo.name || deviceInfo.mac)}'`);
215
+ return;
216
+ }
217
+ // read subdevice parameters from configuration
218
+ const subDevices = this.config.devices?.filter((cfg) => cfg.mac?.endsWith(`@${deviceInfo.mac}`) &&
219
+ cfg.mac.length > deviceInfo.mac.length + 1) || [];
220
+ // register subdevices
221
+ subDevices.forEach((cfg) => {
222
+ const subDeviceInfo = { ...deviceInfo };
223
+ subDeviceInfo.mac = cfg.mac;
224
+ subDeviceInfo.uid = cfg.mac?.substring(0, cfg.mac?.indexOf('@'));
225
+ if (subDeviceInfo.subCnt !== undefined) {
226
+ delete subDeviceInfo.subCnt;
227
+ }
228
+ if (subDeviceInfo.name) {
229
+ subDeviceInfo.name = `${subDeviceInfo.uid}@${subDeviceInfo.name}`;
230
+ }
231
+ this.log.debug('registerDevice - sub device:', subDeviceInfo.mac);
232
+ this.registerDevice(subDeviceInfo);
233
+ });
234
+ // try to register all subdevices -- this part may be removed
235
+ for (let i = 1; i <= deviceInfo.subCnt; i++) {
236
+ const subDeviceInfo = { ...deviceInfo };
237
+ subDeviceInfo.mac = `${i.toString()}@${deviceInfo.mac}`;
238
+ subDeviceInfo.uid = i;
239
+ if (subDeviceInfo.subCnt !== undefined) {
240
+ delete subDeviceInfo.subCnt;
241
+ }
242
+ if (subDeviceInfo.name) {
243
+ subDeviceInfo.name = `${subDeviceInfo.uid.toString()}@${subDeviceInfo.name}`;
244
+ }
245
+ this.log.debug('registerDevice - sub device:', subDeviceInfo.mac);
246
+ this.registerDevice(subDeviceInfo);
247
+ }
248
+ }
249
+ else {
250
+ this.log.debug('registerDevice - already processed:', devcfg?.name ?? (deviceInfo.name || deviceInfo.mac), deviceInfo.mac);
251
+ }
252
+ return;
253
+ }
254
+ const deviceConfig = {
255
+ // parameters read from config
256
+ ...devcfg,
257
+ // fix incorrect values read from config but do not add any value if parameter is missing
258
+ ...((devcfg.speedSteps && devcfg.speedSteps !== 3 && devcfg.speedSteps !== 5) || devcfg.speedSteps === 0 ?
259
+ { speedSteps: DEFAULT_DEVICE_CONFIG.speedSteps } : {}),
260
+ ...(devcfg.temperatureSensor && Object.values(TS_TYPE).includes(devcfg.temperatureSensor.toLowerCase()) ?
261
+ { temperatureSensor: devcfg.temperatureSensor.toLowerCase() }
262
+ : (devcfg.temperatureSensor ? { temperatureSensor: DEFAULT_DEVICE_CONFIG.temperatureSensor } : {})),
263
+ ...(devcfg.minimumTargetTemperature &&
264
+ (devcfg.minimumTargetTemperature < Math.min(TEMPERATURE_LIMITS.coolingMinimum, TEMPERATURE_LIMITS.heatingMinimum) ||
265
+ devcfg.minimumTargetTemperature > Math.max(TEMPERATURE_LIMITS.coolingMaximum, TEMPERATURE_LIMITS.heatingMaximum)) ?
266
+ { minimumTargetTemperature: DEFAULT_DEVICE_CONFIG.minimumTargetTemperature } : {}),
267
+ ...(devcfg.maximumTargetTemperature &&
268
+ (devcfg.maximumTargetTemperature < Math.min(TEMPERATURE_LIMITS.coolingMinimum, TEMPERATURE_LIMITS.heatingMinimum) ||
269
+ devcfg.maximumTargetTemperature > Math.max(TEMPERATURE_LIMITS.coolingMaximum, TEMPERATURE_LIMITS.heatingMaximum)) ?
270
+ { maximumTargetTemperature: DEFAULT_DEVICE_CONFIG.maximumTargetTemperature } : {}),
271
+ ...(devcfg.defaultVerticalSwing && ![commands.swingVertical.value.default, commands.swingVertical.value.fixedHighest,
272
+ commands.swingVertical.value.fixedHigher, commands.swingVertical.value.fixedMiddle, commands.swingVertical.value.fixedLower,
273
+ commands.swingVertical.value.fixedLowest].includes(devcfg.defaultVerticalSwing) ?
274
+ { defaultVerticalSwing: DEFAULT_DEVICE_CONFIG.defaultVerticalSwing } : {}),
275
+ ...(devcfg.defaultFanVerticalSwing && ![commands.swingVertical.value.default, commands.swingVertical.value.fixedHighest,
276
+ commands.swingVertical.value.fixedHigher, commands.swingVertical.value.fixedMiddle, commands.swingVertical.value.fixedLower,
277
+ commands.swingVertical.value.fixedLowest].includes(devcfg.defaultFanVerticalSwing) ?
278
+ { defaultFanVerticalSwing: DEFAULT_DEVICE_CONFIG.defaultFanVerticalSwing } : {}),
279
+ // overrideDefaultVerticalSwing remains here for compatibility reasons
280
+ ...(devcfg.overrideDefaultVerticalSwing &&
281
+ !Object.values(MODIFY_VERTICAL_SWING_POSITION).includes(devcfg.overrideDefaultVerticalSwing) ?
282
+ { overrideDefaultVerticalSwing: DEFAULT_DEVICE_CONFIG.modifyVerticalSwingPosition } : {}),
283
+ ...(devcfg.modifyVerticalSwingPosition &&
284
+ !Object.values(MODIFY_VERTICAL_SWING_POSITION).includes(devcfg.modifyVerticalSwingPosition) ?
285
+ { modifyVerticalSwingPosition: DEFAULT_DEVICE_CONFIG.modifyVerticalSwingPosition } : {}),
286
+ ...(devcfg.encryptionVersion && !Object.values(ENCRYPTION_VERSION).includes(devcfg.encryptionVersion) ?
287
+ { encryptionVersion: DEFAULT_DEVICE_CONFIG.encryptionVersion } : {}),
288
+ };
289
+ // assign customized default to missing parameters
290
+ Object.entries(this.config.devices?.find((item) => item.mac?.toLowerCase() === 'default' &&
291
+ !item?.disabled) || {})
292
+ .forEach(([key, value]) => {
293
+ if (!['mac', 'name', 'ip', 'port', 'disabled'].includes(key) && deviceConfig[key] === undefined) {
294
+ deviceConfig[key] = value;
295
+ }
296
+ });
297
+ // try to assign temperatureStepSize from Homebridge UI if missing in configuration
298
+ if (deviceConfig.temperatureStepSize === undefined && this.tempUnit === 'c') {
299
+ deviceConfig.temperatureStepSize = TEMPERATURE_STEPS.celsius;
300
+ }
301
+ if (deviceConfig.temperatureStepSize === undefined && this.tempUnit === 'f') {
302
+ deviceConfig.temperatureStepSize = TEMPERATURE_STEPS.fahrenheit;
303
+ }
304
+ if (deviceConfig.temperatureStepSize !== undefined && !Object.values(TEMPERATURE_STEPS).includes(deviceConfig.temperatureStepSize)) {
305
+ this.log.warn(`Warning: Invalid temperature step size detected: ${deviceConfig.temperatureStepSize} ->`, `Accessory ${deviceInfo.mac} is using default value (0.5) instead of the configured one`);
306
+ delete deviceConfig.temperatureStepSize;
307
+ }
308
+ // assign plugin default to missing parameters
309
+ Object.entries(DEFAULT_DEVICE_CONFIG).forEach(([key, value]) => {
310
+ if (deviceConfig[key] === undefined) {
311
+ deviceConfig[key] = value;
312
+ }
313
+ });
314
+ // check parameters and fix incorrect values (some of them are repeated checks because default device may also be incorrect)
315
+ if ((deviceConfig.speedSteps && deviceConfig.speedSteps !== 3 && deviceConfig.speedSteps !== 5) || deviceConfig.speedSteps === 0) {
316
+ deviceConfig.speedSteps = DEFAULT_DEVICE_CONFIG.speedSteps;
317
+ }
318
+ if (deviceConfig.temperatureSensor && Object.values(TS_TYPE).includes(deviceConfig.temperatureSensor.toLowerCase())) {
319
+ deviceConfig.temperatureSensor = deviceConfig.temperatureSensor.toLowerCase();
320
+ }
321
+ else {
322
+ deviceConfig.temperatureSensor = DEFAULT_DEVICE_CONFIG.temperatureSensor;
323
+ }
324
+ if (deviceConfig.minimumTargetTemperature &&
325
+ (deviceConfig.minimumTargetTemperature < Math.min(TEMPERATURE_LIMITS.coolingMinimum, TEMPERATURE_LIMITS.heatingMinimum) ||
326
+ deviceConfig.minimumTargetTemperature > Math.max(TEMPERATURE_LIMITS.coolingMaximum, TEMPERATURE_LIMITS.heatingMaximum))) {
327
+ deviceConfig.minimumTargetTemperature = DEFAULT_DEVICE_CONFIG.minimumTargetTemperature;
328
+ }
329
+ if (deviceConfig.maximumTargetTemperature &&
330
+ (deviceConfig.maximumTargetTemperature < Math.min(TEMPERATURE_LIMITS.coolingMinimum, TEMPERATURE_LIMITS.heatingMinimum) ||
331
+ deviceConfig.maximumTargetTemperature > Math.max(TEMPERATURE_LIMITS.coolingMaximum, TEMPERATURE_LIMITS.heatingMaximum))) {
332
+ deviceConfig.maximumTargetTemperature = DEFAULT_DEVICE_CONFIG.maximumTargetTemperature;
333
+ }
334
+ if (deviceConfig.minimumTargetTemperature && deviceConfig.maximumTargetTemperature &&
335
+ deviceConfig.minimumTargetTemperature > deviceConfig.maximumTargetTemperature) {
336
+ deviceConfig.minimumTargetTemperature =
337
+ Math.min(DEFAULT_DEVICE_CONFIG.minimumTargetTemperature, DEFAULT_DEVICE_CONFIG.maximumTargetTemperature);
338
+ deviceConfig.maximumTargetTemperature =
339
+ Math.max(DEFAULT_DEVICE_CONFIG.minimumTargetTemperature, DEFAULT_DEVICE_CONFIG.maximumTargetTemperature);
340
+ this.log.warn('Warning: Invalid minimum and maximum target temperature values detected ->', `Accessory ${deviceInfo.mac} is using default values instead of the configured ones`);
341
+ }
342
+ if (deviceConfig.defaultVerticalSwing && ![commands.swingVertical.value.default, commands.swingVertical.value.fixedHighest,
343
+ commands.swingVertical.value.fixedHigher, commands.swingVertical.value.fixedMiddle, commands.swingVertical.value.fixedLower,
344
+ commands.swingVertical.value.fixedLowest].includes(deviceConfig.defaultVerticalSwing)) {
345
+ deviceConfig.defaultVerticalSwing = DEFAULT_DEVICE_CONFIG.defaultVerticalSwing;
346
+ this.log.warn('Warning: Invalid vertical position detected ->', `Accessory ${deviceInfo.mac} is using default value instead of the configured one`);
347
+ }
348
+ if (deviceConfig.defaultFanVerticalSwing && ![commands.swingVertical.value.default, commands.swingVertical.value.fixedHighest,
349
+ commands.swingVertical.value.fixedHigher, commands.swingVertical.value.fixedMiddle, commands.swingVertical.value.fixedLower,
350
+ commands.swingVertical.value.fixedLowest].includes(deviceConfig.defaultFanVerticalSwing)) {
351
+ deviceConfig.defaultFanVerticalSwing = DEFAULT_DEVICE_CONFIG.defaultFanVerticalSwing;
352
+ this.log.warn('Warning: Invalid vertical fan position detected ->', `Accessory ${deviceInfo.mac} is using default value instead of the configured one`);
353
+ }
354
+ // overrideDefaultVerticalSwing remains here for compatibility reasons
355
+ if (deviceConfig.overrideDefaultVerticalSwing &&
356
+ !Object.values(MODIFY_VERTICAL_SWING_POSITION).includes(deviceConfig.overrideDefaultVerticalSwing)) {
357
+ deviceConfig.overrideDefaultVerticalSwing = DEFAULT_DEVICE_CONFIG.modifyVerticalSwingPosition;
358
+ }
359
+ if (deviceConfig.modifyVerticalSwingPosition &&
360
+ !Object.values(MODIFY_VERTICAL_SWING_POSITION).includes(deviceConfig.modifyVerticalSwingPosition)) {
361
+ deviceConfig.modifyVerticalSwingPosition = DEFAULT_DEVICE_CONFIG.modifyVerticalSwingPosition;
362
+ }
363
+ if (deviceConfig.encryptionVersion && !Object.values(ENCRYPTION_VERSION).includes(deviceConfig.encryptionVersion)) {
364
+ deviceConfig.encryptionVersion = DEFAULT_DEVICE_CONFIG.encryptionVersion;
365
+ }
366
+ if (deviceConfig.port !== undefined && (typeof deviceConfig.port !== 'number' || deviceConfig.port !== deviceConfig.port ||
367
+ (typeof deviceConfig.port === 'number' && (deviceConfig.port < 1025 || deviceConfig.port > 65535)))) {
368
+ this.log.warn('Warning: Port is misconfigured (Valid port values: 1025~65535 or leave port empty to auto select) - ' +
369
+ `Accessory ${deviceInfo.mac} listening port overridden: ${deviceConfig.port} -> auto`);
370
+ delete deviceConfig.port;
371
+ }
372
+ // replace deprecated overrideDefaultVerticalSwing with new modifyVerticalSwingPosition
373
+ if (deviceConfig.overrideDefaultVerticalSwing !== undefined && deviceConfig.modifyVerticalSwingPosition === undefined) {
374
+ // found deprecated but missing new
375
+ if (!this.warningShown[`${deviceInfo.mac}_overrideDefaultVerticalSwing`]) {
376
+ this.log.warn('Deprecated configuration parameter found: overrideDefaultVerticalSwing - ' +
377
+ `Accessory ${deviceInfo.mac} parameter value: ${deviceConfig.overrideDefaultVerticalSwing} -> use modifyVerticalSwingPosition`);
378
+ this.warningShown[`${deviceInfo.mac}_overrideDefaultVerticalSwing`] = true;
379
+ }
380
+ deviceConfig.modifyVerticalSwingPosition = deviceConfig.overrideDefaultVerticalSwing;
381
+ delete deviceConfig.overrideDefaultVerticalSwing;
382
+ }
383
+ else if (deviceConfig.overrideDefaultVerticalSwing !== undefined && deviceConfig.modifyVerticalSwingPosition !== undefined) {
384
+ // found both deprecated and new -> keep new only
385
+ if (!this.warningShown[`${deviceInfo.mac}_overrideDefaultVerticalSwing`]) {
386
+ this.log.warn('Deprecated configuration parameter found: overrideDefaultVerticalSwing - ' +
387
+ `Accessory ${deviceInfo.mac} parameter value: ${deviceConfig.overrideDefaultVerticalSwing} -> ignoring`);
388
+ this.warningShown[`${deviceInfo.mac}_overrideDefaultVerticalSwing`] = true;
389
+ }
390
+ delete deviceConfig.overrideDefaultVerticalSwing;
391
+ }
392
+ // ignore invalid silentTimeRange
393
+ if (deviceConfig.silentTimeRange) {
394
+ const match = deviceConfig.silentTimeRange.match(/^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]-(((0[0-9]|1[0-9]|2[0-3]):[0-5][0-9])|24:00)$/);
395
+ if (!match || (match && deviceConfig.silentTimeRange !== match[0])) {
396
+ // invalid parameter value (not in HH:MM-HH:MM format)
397
+ if (!this.warningShown[`${deviceInfo.mac}_silentTimeRange`]) {
398
+ this.log.warn('Invalid configuration parameter value found: silentTimeRange - ' +
399
+ `Accessory ${deviceInfo.mac} parameter value: ${deviceConfig.silentTimeRange} -> ignoring`);
400
+ this.warningShown[`${deviceInfo.mac}_silentTimeRange`] = true;
401
+ }
402
+ delete deviceConfig.silentTimeRange;
403
+ }
404
+ }
405
+ // force encryption version if set in config
406
+ if (deviceConfig.encryptionVersion !== ENCRYPTION_VERSION.auto) {
407
+ deviceInfo.encryptionVersion = deviceConfig.encryptionVersion;
408
+ this.log.debug(`Accessory ${deviceInfo.mac} encryption version forced:`, deviceInfo.encryptionVersion);
409
+ }
410
+ let accessory = this.devices[deviceInfo.mac];
411
+ let accessory_ts = this.devices[deviceInfo.mac + '_ts'];
412
+ if (deviceConfig?.disabled || !/^[a-f0-9]{12}$/.test(deviceConfig?.mac.substring(deviceConfig?.mac.indexOf('@') + 1))) {
413
+ if (!devcfg || Object.keys(devcfg).length === 0) {
414
+ this.log.debug('14 DEBUG:', deviceConfig);
415
+ }
416
+ //do not skip unconfigured devices
417
+ if (!this.skippedDevices[deviceInfo.mac]) {
418
+ this.log.info(`Accessory ${deviceInfo.mac} skipped`);
419
+ this.skippedDevices[deviceInfo.mac] = true;
420
+ }
421
+ else {
422
+ this.log.debug(`Accessory ${deviceInfo.mac} skipped`);
423
+ }
424
+ if (accessory) {
425
+ delete this.devices[accessory.context.device.mac];
426
+ this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
427
+ this.log.debug(`registerDevice - unregister (${devcfg.mac === undefined ? 'not configured' : 'disabled'}):`, accessory.displayName, accessory.UUID);
428
+ accessory = undefined;
429
+ }
430
+ if (accessory_ts) {
431
+ delete this.devices[accessory_ts.context.device.mac];
432
+ this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory_ts]);
433
+ this.log.debug(`registerDevice - unregister (${devcfg.mac === undefined ? 'not configured' : 'disabled'}):`, accessory_ts.displayName, accessory_ts.UUID);
434
+ accessory_ts = undefined;
435
+ }
436
+ return;
437
+ }
438
+ // check device address change
439
+ if (accessory && deviceInfo.address !== accessory.context.device.address) {
440
+ this.log.info(`Device [${accessory.displayName} - ${accessory.context.device.mac}] address has changed: %s -> %s`, accessory.context.device.address, deviceInfo.address);
441
+ accessory.context.device.address = deviceInfo.address;
442
+ }
443
+ if (accessory_ts && deviceInfo.address !== accessory_ts.context.device.address) {
444
+ accessory_ts.context.device.address = deviceInfo.address;
445
+ }
446
+ if (accessory && this.processedDevices[accessory.UUID]) {
447
+ // already initalized
448
+ this.log.debug('registerDevice - already processed:', accessory.displayName, accessory.context.device.mac, accessory.UUID);
449
+ return;
450
+ }
451
+ // create heatercooler accessory if not loaded from cache
452
+ const deviceName = deviceConfig?.name ?? (deviceInfo.name || deviceInfo.mac);
453
+ if (!accessory) {
454
+ this.log.debug(`Creating new accessory ${deviceInfo.mac} with name ${deviceName} ...`);
455
+ const uuid = this.api.hap.uuid.generate(deviceInfo.mac);
456
+ accessory = new this.api.platformAccessory(deviceName, uuid, 21 /* Categories.AIR_CONDITIONER */);
457
+ accessory.bound = false;
458
+ accessory.registered = false;
459
+ this.devices[deviceInfo.mac] = accessory;
460
+ }
461
+ // create temperaturesensor accessory if configured as separate and not loaded from cache
462
+ const tsDeviceName = 'Temperature Sensor ' + (deviceConfig?.name ?? (deviceInfo.name || deviceInfo.mac));
463
+ if (!accessory_ts && deviceConfig.temperatureSensor === TS_TYPE.separate) {
464
+ this.log.debug(`Creating new accessory ${deviceInfo.mac}_ts with name ${tsDeviceName} ...`);
465
+ const uuid = this.api.hap.uuid.generate(deviceInfo.mac + '_ts');
466
+ accessory_ts = new this.api.platformAccessory(tsDeviceName, uuid, 10 /* Categories.SENSOR */);
467
+ accessory_ts.registered = false;
468
+ this.devices[deviceInfo.mac + '_ts'] = accessory_ts;
469
+ }
470
+ // unregister temperaturesensor accessory if configuration has changed from separate to any other
471
+ if (accessory_ts && deviceConfig.temperatureSensor !== TS_TYPE.separate) {
472
+ this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory_ts]);
473
+ delete this.devices[deviceInfo.mac + '_ts'];
474
+ this.log.debug('registerDevice - unregister:', accessory_ts.displayName, accessory_ts.UUID);
475
+ accessory_ts = undefined;
476
+ }
477
+ if (accessory_ts && deviceConfig.temperatureSensor === TS_TYPE.separate) {
478
+ // mark temperature sensor device as initialized
479
+ accessory_ts.context.device = { ...deviceInfo };
480
+ accessory_ts.context.device.mac = deviceInfo.mac + '_ts';
481
+ accessory_ts.context.deviceType = 'TemperatureSensor';
482
+ if (deviceConfig.model) {
483
+ accessory_ts.context.device.model = deviceConfig.model;
484
+ }
485
+ this.processedDevices[accessory_ts.UUID] = true;
486
+ this.log.debug(`registerDevice - ${accessory_ts.context.deviceType} created:`, accessory_ts.displayName, accessory_ts.context.device.mac, accessory_ts.UUID);
487
+ // do not load temperature sensor accessory here (it will be loaded from heatercooler accessory)
488
+ }
489
+ if (accessory) {
490
+ // mark heatercooler device as processed
491
+ accessory.context.device = deviceInfo;
492
+ accessory.context.deviceType = 'HeaterCooler';
493
+ this.processedDevices[accessory.UUID] = true;
494
+ this.log.debug(`registerDevice - ${accessory.context.deviceType} created:`, accessory.displayName, accessory.context.device.mac, accessory.UUID);
495
+ // load heatercooler accessory
496
+ new GreeAirConditioner(this, accessory, deviceConfig, accessory_ts?.context.device.mac);
497
+ }
498
+ };
499
+ sendScan() {
500
+ const message = Buffer.from(JSON.stringify({ t: 'scan' }));
501
+ Object.entries(this.pluginAddresses).forEach((value) => {
502
+ const addr = value[0];
503
+ this.socket.send(message, 0, message.length, UDP_SCAN_PORT, addr, (error) => {
504
+ if (this.pluginAddresses[addr] === '255.255.255.255') {
505
+ this.log.debug(`Scanning for device (unicast) '${message}' ${addr}:${UDP_SCAN_PORT}`);
506
+ }
507
+ else {
508
+ this.log.debug(`Scanning for devices (broadcast) '${message}' ${addr}:${UDP_SCAN_PORT}`);
509
+ }
510
+ if (error) {
511
+ this.log.error('Device scan - Error:', error.message);
512
+ }
513
+ });
514
+ });
515
+ }
516
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
517
+ getNetworkAddresses(bindInterfaces) {
518
+ this.log.debug('Checking network interfaces');
519
+ const pluginAddresses = {};
520
+ let allInterfaces;
521
+ if (bindInterfaces !== null && bindInterfaces !== undefined && bindInterfaces.length > 0) {
522
+ this.log.debug('Homebridge bound to:', bindInterfaces);
523
+ const filteredEntries = Object.entries(networkInterfaces()).filter(([key]) => {
524
+ return bindInterfaces.includes(key);
525
+ });
526
+ allInterfaces = Object.fromEntries(filteredEntries);
527
+ }
528
+ else {
529
+ allInterfaces = networkInterfaces();
530
+ }
531
+ for (const name of Object.keys(allInterfaces)) {
532
+ const nets = allInterfaces[name];
533
+ if (nets) {
534
+ for (const iface of nets) {
535
+ // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
536
+ const familyV4Value = typeof iface.family === 'string' ? 'IPv4' : 4;
537
+ if (iface.family === familyV4Value && !iface.internal) {
538
+ const addrParts = iface.address.split('.');
539
+ const netmaskParts = iface.netmask.split('.');
540
+ const broadcast = addrParts.map((e, i) => ((~Number(netmaskParts[i]) & 0xFF) | Number(e)).toString()).join('.');
541
+ this.log.debug('Interface: \'%s\' Address: %s Netmask: %s Broadcast: %s', name, iface.address, iface.netmask, broadcast);
542
+ if (pluginAddresses[broadcast] === undefined) {
543
+ pluginAddresses[broadcast] = iface.netmask;
544
+ }
545
+ }
546
+ }
547
+ }
548
+ }
549
+ // Add IPs from configuration but only if at least one host address found (add only for valid mac addresses)
550
+ if (Object.keys(pluginAddresses).length > 0) {
551
+ const devcfgs = this.config.devices?.filter((item) => item.ip && !item.disabled && /^[a-f0-9]{12}$/.test(item.mac || '')) || [];
552
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
553
+ devcfgs.forEach((value) => {
554
+ const ip = value.ip;
555
+ const ipv4Pattern = /^(25[0-5]|2[0-4]\d|1\d{2}|\d{1,2})(\.(25[0-5]|2[0-4]\d|1\d{2}|\d{1,2})){3}$/;
556
+ if (ipv4Pattern.test(ip)) {
557
+ this.log.debug('Found AC Unit address in configuration:', ip);
558
+ const addrParts = ip.split('.');
559
+ const addresses = {};
560
+ Object.keys(pluginAddresses).forEach((addr) => {
561
+ const netmaskParts = pluginAddresses[addr].split('.');
562
+ const broadcast = addrParts.map((e, i) => ((~Number(netmaskParts[i]) & 0xFF) | Number(e)).toString()).join('.');
563
+ if (addr === broadcast) {
564
+ addresses[ip] = true;
565
+ }
566
+ });
567
+ const skipAddress = Object.keys(addresses).find((addr) => addr === ip);
568
+ if (skipAddress === undefined) {
569
+ pluginAddresses[ip] = '255.255.255.255';
570
+ }
571
+ else {
572
+ this.log.debug('AC Unit (%s) is already on broadcast list - skipping', skipAddress);
573
+ }
574
+ }
575
+ else {
576
+ this.log.warn('Warning: Invalid IP address found in configuration: %s - skipping', ip);
577
+ }
578
+ });
579
+ }
580
+ return pluginAddresses;
581
+ }
582
+ getAccessory(mac) {
583
+ return this.devices[mac];
584
+ }
585
+ }
550
586
  //# sourceMappingURL=platform.js.map