homebridge-gree-ac 2.1.2 → 2.1.4

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/CHANGELOG.md CHANGED
@@ -1,10 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.1.4] - 2024-11-11
4
+
5
+ **<ins>Reminder:</ins> New (v2) network encryption protocol supported since v2.1.0**
6
+
7
+ When upgrading from v2.0.0 - v2.1.1 to v2.1.2 or later, configuration settings should be updated.
8
+
9
+ ### New features
10
+ - Added support of devices with hardware version V3.x which use a mix of V1 and V2 network encryption
11
+ - Reorganized device registration with binding error detection
12
+
13
+ ### Fixes
14
+ - Fixed error on disabling device after successful registration
15
+ - After homebridge startup, don't wait before scanning devices on the network
16
+ - Handling conflicting port numbers
17
+
18
+ ## [2.1.3] - 2024-11-05
19
+
20
+ **<ins>Reminder:</ins> New (v2) network encryption protocol supported since v2.1.0**
21
+
22
+ When upgrading from v2.0.0 - v2.1.1 to v2.1.2 or later, configuration settings should be updated.
23
+
24
+ #### Updated dependencies
25
+ - added Node.js v22 to supported versions (18.15.0 or later, 20.7.0 or later and 22.0.0 or later are supported)
26
+
27
+ ### New features
28
+ - new optional IP address parameter to support devices on different subnets
29
+ - new optional device port parameter to support cases when automatic port assigment is not appropriate
30
+
31
+ ### Fixes
32
+ - consistent xFan default setting in configuration UI and in plugin behaviour
33
+
3
34
  ## [2.1.2] - 2024-10-01
4
35
 
5
36
  **<ins>Reminder:</ins> New (v2) network encryption protocol supported since v2.1.0**
6
37
 
7
- When upgrading from v2.0.0 - v2.1.1 to v2.1.2 configuration settings should be updated.
38
+ When upgrading from v2.0.0 - v2.1.1 to v2.1.2, configuration settings should be updated.
8
39
 
9
40
  #### Configuration update steps
10
41
 
package/README.md CHANGED
@@ -31,10 +31,10 @@ This plugin is designed to be as simple and clear as possible and supports prima
31
31
 
32
32
  ## Requirements
33
33
 
34
- * Node.js (>= 18.15.0 || >= 20.8.0) with NPM
34
+ * Node.js (>= 18.15.0 || >= 20.7.0 || >= 22.0.0) with NPM
35
35
  * Homebridge (>= 1.8.0 || >= 2.0.0-beta.0)
36
36
 
37
- Homebridge and all AC units have to be on the same subnet. The plugin finds all supported units automatically but controls only those which MAC address is added to configuration.
37
+ The plugin finds all supported units automatically if they are located on the same subnet but controls only those which MAC address is added to the configuration. AC units on different subnets are also supported if the unit's IP address is set in the configuration. (MAC address have to be set correctly in this case also.)
38
38
 
39
39
  IPv4 address is required. GREE Air Conditioners do not support IPv6 nor other network protocols.
40
40
 
@@ -48,7 +48,7 @@ This is not plugin dependency but its good to know that Homebridge server host a
48
48
 
49
49
  > If you get _"error:1C80006B:Provider routines::wrong final block length"_ error message then your device is not supported.
50
50
  >
51
- > If you don't get _"Device is bound ..."_ message within a few minutes after Homebridge startup and the correct MAC address is added to the configuration and the AC unit is accessible on the same subnet then your device is not supported.
51
+ > If you get _"Error: Device is not bound ..."_ error message then your device may not be supported. (The same error occures when device is malfunctioning but if turning the power supply off and on does not solve the problem then your device is not supported.)
52
52
 
53
53
  By default this plugin tries to auto detect the network protocol encryption version. If not the right version is selected there can get errors and the AC device will not correctly work. It is possible to force a network protocol encryption version in configuration file. If auto detection does not work then it is recommended to try all possible values to check if the device is compatible or not.
54
54
 
@@ -64,7 +64,6 @@ This plugin was designed to support the Home App's Heater Cooler functionality u
64
64
  * Not all half a degree values are supported in °C mode (GREE AC units are designed to support only integer °C and °F values). Unsupported values are automatically updated to the nearest supported values.
65
65
  * There is no way to get current heating-cooling state from the AC unit in auto mode, so displayed state in the Home App is based on temperature measurement, but internal sensor is not precise enough to always display the correct state.
66
66
  * Cooling / Heating temperature threshold limits (minimum and maximum values) can only be set in active cooling / heating mode. So the gauge in Home App may show invalid minimum and maximum values for the first use of cooling and heating modes. If so please restart Home App. Next time the correct values will be displayed.
67
- * Homebridge and AC unit on different subnets is a not supported configuration.
68
67
  * Devices without a built-in temperature sensor display the target temperature as current temperature not the measured one. (Some AC firmware versions do not report the measured temperature but the unit has a built-in sensor. They are handled by the plugin as devices without a sensor.)
69
68
 
70
69
  ## Installation instructions
@@ -126,15 +125,17 @@ _Only the relevant part of the configuration file is displayed:_
126
125
  {
127
126
  "mac": "502cc6000000",
128
127
  "name": "Living room AC",
128
+ "ip": "192.168.1.2",
129
+ "port": 7003
130
+ "statusUpdateInterval": 10,
131
+ "encryptionVersion": 0,
129
132
  "model": "Pulse 3.2kW GWH12AGB-K6DNA1A/I",
130
133
  "speedSteps": 5,
131
- "encryptionVersion": 0,
132
- "statusUpdateInterval": 10,
133
- "sensorOffset": 40,
134
134
  "minimumTargetTemperature": 16,
135
135
  "maximumTargetTemperature": 30,
136
- "xFanEnabled": true,
136
+ "sensorOffset": 40,
137
137
  "temperatureSensor": "disabled",
138
+ "xFanEnabled": true,
138
139
  "overrideDefaultVerticalSwing": 0,
139
140
  "defaultVerticalSwing": 0,
140
141
  "disabled": false
@@ -143,28 +144,32 @@ _Only the relevant part of the configuration file is displayed:_
143
144
  }
144
145
  ]
145
146
  ```
146
- _It's not recommended to add the port parameter. The above example contains it but only for showing all optional parameters also._
147
+ _It's not recommended to add the port and ip parameters. The above example contains them but only for showing all optional parameters also._
147
148
 
148
149
  * name - Unique name of the platform plugin
149
150
  * platform - **GREEAirConditioner** (fixed name, it identifies the plugin)
150
- * port - free UDP port (optional) (homebridge will use this port for network communication; select a port which is not used and the next 256 ports are also available because devices will be bound to a separate port based on the last part of the device's IPv4 address and the port specified in the configuration; valid values: 1025 - 65279); **Do not specify a port unless you have trouble with automatic port assignment!**
151
+ * port - free UDP port (optional) (plugin will use this port for network communication; valid values: 1025 - 65535) **Do not specify a port unless you have trouble with automatic port assignment!**
151
152
  * scanInterval - time period in seconds between device query retries (defaults to 60 sec if missing)
152
153
  * devices - devices should be listed in this block (specify as many devices as you have on your network)
153
154
  * mac - MAC address (Serial Number) of the device
154
155
  * name - custom name of the device (optional) Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis.
156
+ * ip - device IP address (optional) Address is auto detected if this parameter is missing. **Specify only if device is located on a different subnet then homebridge!**
157
+ * port - free UDP port (optional) (plugin will listen on this port for data received from the device; valid values: 1025 - 65535) **Do not specify a port unless you have trouble with automatic port assignment!**
158
+ * statusUpdateInterval - device status will be refreshed based on this interval (in seconds, default is 10 seconds)
159
+ * encryptionVersion - Auto (0) is fine for most AC units. If auto does not work then you can force v1 (1) or v2 (2) encryption version to use in network communication
155
160
  * model - model name, information only (optional)
156
161
  * speedSteps - fan speed steps of the unit (valid values are: 3 and 5)
157
- * encryptionVersion - Auto (0) is fine for most AC units. If auto does not work then you can force v1 (1) or v2 (2) encryption version to use in network communication
158
- * statusUpdateInterval - device status will be refreshed based on this interval (in seconds)
159
- * sensorOffset - device temperature sensor offset value for current temperature calibration (default is 40 °C, must be specified in °C)
160
162
  * minimumTargetTemperature - minimum target temperature accepted by the device (default is 16 °C, must be specified in °C, valid values: 16-30)
161
163
  * maximumTargetTemperature - maximum target temperature accepted by the device (default is 30 °C, must be specified in °C, valid values: 16-30)
162
- * xFanEnabled - automatically turn on xFan functionality in supported device modes (xFan actual setting is not modified by the Home App if disabled)
164
+ * sensorOffset - device temperature sensor offset value for current temperature calibration (default is 40 °C, must be specified in °C)
163
165
  * temperatureSensor - control additional temperature sensor accessory in Home App (disabled = do not add to Home App / child = add as a child accessory / separate = add as a separate (independent) accessory)
166
+ * xFanEnabled - automatically turn on xFan functionality in supported device modes (xFan actual setting is not modified by the Home App if disabled)
164
167
  * overrideDefaultVerticalSwing - by default this plugin does not change the vertical swing position of the AC unit but some devices do not keep the original vertical position set by the remote control if controlled from Homebridge and return back to device default position; this setting allows to override the default position -> if AC unit is set to default vertical swing position Homebridge modifies it to a predefined position (set by defaultVerticalSwing) (Never (0) = turn off override, let device use default / After power on (1) = override default position on each power on / After power on and swing disable (2) = override default position on each power on and each time when swing is switched to disabled)
165
168
  * defaultVerticalSwing - specify the vertical swing position to be used instead of device default when overriding is enabled (Device default (0) = use device default, same position as used by device by default without overriding / one of the following 5 positions: fixed Highest (2), fixed Higher (3), fixed Middle (4), fixed Lower (5), fixed Lowest (6))
166
169
  * disabled - set to true if you do not want to control this device in the Home App _(can be used also to temporarily remove the device from Home App but not if the device is not responding any more on the network)_
167
170
 
171
+ Recommended configuration:
172
+
168
173
  ![Homebridge UI](./uiconfig.jpg)
169
174
 
170
175
  ## Tips
@@ -199,6 +204,19 @@ Some settings are initialized by Home App only once (when enabling the device).
199
204
 
200
205
  All other settings are applied when starting up Homebridge. You have to restart Homebridge to apply changes in configuration settings.
201
206
 
207
+ ### IP address
208
+
209
+ IP addresses of the AC units are determined automatically by the plugin. However this auto detection works only if the AC unit is on the same subnet as homebridge. There is an optional IP address parameter which can be used to specifiy the unit's IP address if it is on a different subnet. (Routing should be correctly set up to communicate with units on different subnets.)
210
+
211
+ ### Port
212
+
213
+ Network communication uses UDP ports. There are two kind of ports:
214
+
215
+ - Plugin port. This port is used by the plugin to communicate on the network.
216
+ - Device specific port. The plugin is listening for data received from the device using this port.
217
+
218
+ All ports are set up automatically by default. In some cases auto detection is not appropriate. (E.g. when firewall rules should be set up) It is possible to overwrite the default ports by optional port parameters (for the plugin and also for each devices). _Note that the ports must be unique._
219
+
202
220
  ### Temperature display units
203
221
 
204
222
  Home App allows to set the device temperature display units but it is independent from the temperature units shown in Home App. Home App always displays temperature values as specified by iOS/MacOS (can be changed in Preferences / Regional settings section). Display unit conversion is made by the Home App device (e.g. iPhone).
@@ -209,7 +227,7 @@ Temperature measurement is not perfect if using the built-in sensor. It is highl
209
227
 
210
228
  ### Invalid room temperature
211
229
 
212
- Some AC units have a built-in temperature sensor but the actual room temperature is not displayed in Home App. This is an AC firmware problem. Older firware versions do not report temperature values at all and there are some firmware versions which report a fixed value (e.g. zero) instead of the measured one. This plugin replaces the missing value and the fixed zero value by the desired target temperature. To get the correct measured temperature please try to upgrade or downgrade the AC firmware.
230
+ Some AC units have a built-in temperature sensor but the actual room temperature is not displayed in Home App. This is an AC firmware problem. Older firmware versions do not report temperature values at all and there are some firmware versions which report a fixed value (e.g. zero) instead of the measured one. This plugin replaces the missing value and the fixed zero value by the desired target temperature. To get the correct measured temperature please try to upgrade or downgrade the AC firmware.
213
231
 
214
232
  ### Fan speed
215
233
 
@@ -62,28 +62,28 @@
62
62
  "type": "integer",
63
63
  "required": false,
64
64
  "minimum": 1,
65
- "default": 10
65
+ "placeholder": 10
66
66
  },
67
67
  "sensorOffset": {
68
68
  "type": "integer",
69
69
  "required": false,
70
- "default": 40
70
+ "placeholder": 40
71
71
  },
72
72
  "minimumTargetTemperature": {
73
73
  "type": "integer",
74
74
  "required": false,
75
75
  "minimum": 16,
76
- "default": 16
76
+ "placeholder": 16
77
77
  },
78
78
  "maximumTargetTemperature": {
79
79
  "type": "integer",
80
80
  "required": false,
81
81
  "maximum": 30,
82
- "default": 30
82
+ "placeholder": 30
83
83
  },
84
84
  "xFanEnabled": {
85
85
  "type": "boolean",
86
- "default": false
86
+ "default": true
87
87
  },
88
88
  "temperatureSensor": {
89
89
  "type": "string",
@@ -205,6 +205,16 @@
205
205
  ]
206
206
  }
207
207
  ]
208
+ },
209
+ "port": {
210
+ "type": "integer",
211
+ "required": false,
212
+ "minimum": 1025
213
+ },
214
+ "ip": {
215
+ "type": "string",
216
+ "required": false,
217
+ "format": "ipv4"
208
218
  }
209
219
  }
210
220
  }
@@ -224,7 +234,7 @@
224
234
  "key": "port",
225
235
  "flex": "1 1 50%",
226
236
  "title": "Port:",
227
- "description": "Requested UDP port (auto if empty)"
237
+ "description": "Platform level UDP port (auto if empty)"
228
238
  },
229
239
  {
230
240
  "key": "scanInterval",
@@ -269,17 +279,19 @@
269
279
  }
270
280
  },
271
281
  {
272
- "key": "devices[].model",
282
+ "key": "devices[].ip",
273
283
  "flex": "1 1 50%",
274
- "title": "Device model:",
284
+ "title": "IP address:",
285
+ "description": "Device IP address (auto if empty)",
275
286
  "condition": {
276
287
  "functionBody": "return (model.devices && model.devices[arrayIndices] && model.devices[arrayIndices].mac && /^[a-f0-9]{12}$/.test(model.devices[arrayIndices].mac) && model.devices[arrayIndices].disabled !== true);"
277
288
  }
278
289
  },
279
290
  {
280
- "key": "devices[].speedSteps",
291
+ "key": "devices[].port",
281
292
  "flex": "1 1 50%",
282
- "title": "Fan speed steps:",
293
+ "title": "Port:",
294
+ "description": "Device specific UDP port (auto if empty)",
283
295
  "condition": {
284
296
  "functionBody": "return (model.devices && model.devices[arrayIndices] && model.devices[arrayIndices].mac && /^[a-f0-9]{12}$/.test(model.devices[arrayIndices].mac) && model.devices[arrayIndices].disabled !== true);"
285
297
  }
@@ -302,6 +314,22 @@
302
314
  "functionBody": "return (model.devices && model.devices[arrayIndices] && model.devices[arrayIndices].mac && /^[a-f0-9]{12}$/.test(model.devices[arrayIndices].mac) && model.devices[arrayIndices].disabled !== true);"
303
315
  }
304
316
  },
317
+ {
318
+ "key": "devices[].model",
319
+ "flex": "1 1 50%",
320
+ "title": "Device model:",
321
+ "condition": {
322
+ "functionBody": "return (model.devices && model.devices[arrayIndices] && model.devices[arrayIndices].mac && /^[a-f0-9]{12}$/.test(model.devices[arrayIndices].mac) && model.devices[arrayIndices].disabled !== true);"
323
+ }
324
+ },
325
+ {
326
+ "key": "devices[].speedSteps",
327
+ "flex": "1 1 50%",
328
+ "title": "Fan speed steps:",
329
+ "condition": {
330
+ "functionBody": "return (model.devices && model.devices[arrayIndices] && model.devices[arrayIndices].mac && /^[a-f0-9]{12}$/.test(model.devices[arrayIndices].mac) && model.devices[arrayIndices].disabled !== true);"
331
+ }
332
+ },
305
333
  {
306
334
  "key": "devices[].minimumTargetTemperature",
307
335
  "flex": "1 1 50%",
@@ -329,15 +357,6 @@
329
357
  "functionBody": "return (model.devices && model.devices[arrayIndices] && model.devices[arrayIndices].mac && /^[a-f0-9]{12}$/.test(model.devices[arrayIndices].mac) && model.devices[arrayIndices].disabled !== true);"
330
358
  }
331
359
  },
332
- {
333
- "key": "devices[].xFanEnabled",
334
- "flex": "1 1 50%",
335
- "title": "xFan enabled",
336
- "description": "If enabled, then xFan functionality is turned on automatically on the device",
337
- "condition": {
338
- "functionBody": "return (model.devices && model.devices[arrayIndices] && model.devices[arrayIndices].mac && /^[a-f0-9]{12}$/.test(model.devices[arrayIndices].mac) && model.devices[arrayIndices].disabled !== true);"
339
- }
340
- },
341
360
  {
342
361
  "key": "devices[].temperatureSensor",
343
362
  "flex": "1 1 50%",
@@ -348,7 +367,10 @@
348
367
  }
349
368
  },
350
369
  {
351
- "flex": "1 1 50%",
370
+ "key": "devices[].xFanEnabled",
371
+ "flex": "1 1 100%",
372
+ "title": "xFan enabled",
373
+ "description": "If enabled, then xFan functionality is turned on automatically on the device",
352
374
  "condition": {
353
375
  "functionBody": "return (model.devices && model.devices[arrayIndices] && model.devices[arrayIndices].mac && /^[a-f0-9]{12}$/.test(model.devices[arrayIndices].mac) && model.devices[arrayIndices].disabled !== true);"
354
376
  }
@@ -1,4 +1,8 @@
1
1
  import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge';
2
+ export interface MyPlatformAccessory extends PlatformAccessory {
3
+ bound?: boolean;
4
+ registered?: boolean;
5
+ }
2
6
  /**
3
7
  * HomebridgePlatform
4
8
  * This class is the main constructor for your plugin, this is where you should
@@ -11,16 +15,17 @@ export declare class GreeACPlatform implements DynamicPlatformPlugin {
11
15
  readonly Service: typeof Service;
12
16
  readonly Characteristic: typeof Characteristic;
13
17
  private devices;
14
- private initializedDevices;
18
+ private processedDevices;
15
19
  private skippedDevices;
16
20
  private socket;
17
21
  private pluginAddresses;
22
+ ports: number[];
18
23
  constructor(log: Logger, config: PlatformConfig, api: API);
19
24
  /**
20
25
  * This function is invoked when homebridge restores cached accessories from disk at startup.
21
26
  * It should be used to setup event handlers for characteristics and update respective values.
22
27
  */
23
- configureAccessory(accessory: PlatformAccessory): void;
28
+ configureAccessory(accessory: MyPlatformAccessory): void;
24
29
  /**
25
30
  * Accessories must only be registered once, previously created accessories
26
31
  * must not be registered again to prevent "duplicate UUID" errors.
@@ -29,7 +34,8 @@ export declare class GreeACPlatform implements DynamicPlatformPlugin {
29
34
  discoverDevices(): void;
30
35
  handleMessage: (msg: any, rinfo: any) => void;
31
36
  registerDevice: (deviceInfo: any) => void;
32
- broadcastScan(): void;
37
+ sendScan(): void;
33
38
  getNetworkAddresses(): {};
39
+ getAccessory(mac: string): MyPlatformAccessory;
34
40
  }
35
41
  //# sourceMappingURL=platform.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,iBAAiB,EAAE,cAAc,EAAE,OAAO,EAAE,cAAc,EAAc,MAAM,YAAY,CAAC;AAUxI;;;;GAIG;AACH,qBAAa,cAAe,YAAW,qBAAqB;aAYxC,GAAG,EAAE,MAAM;aACX,MAAM,EAAE,cAAc;aACtB,GAAG,EAAE,GAAG;IAb1B,SAAgB,OAAO,EAAE,OAAO,OAAO,CAAwB;IAC/D,SAAgB,cAAc,EAAE,OAAO,cAAc,CAA+B;IAGpF,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,kBAAkB,CAA0B;IACpD,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,eAAe,CAA8B;gBAGnC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,GAAG;IAsC1B;;;OAGG;IACH,kBAAkB,CAAC,SAAS,EAAE,iBAAiB;IAe/C;;;OAGG;IACH,YAAY;IAQZ,eAAe;IAUf,aAAa,iCA4CX;IAEF,cAAc,4BAyIZ;IAEF,aAAa;IAYb,mBAAmB;CA8BpB"}
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,iBAAiB,EAAE,cAAc,EAAE,OAAO,EAAE,cAAc,EAAc,MAAM,YAAY,CAAC;AAUxI,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;GAIG;AACH,qBAAa,cAAe,YAAW,qBAAqB;aAaxC,GAAG,EAAE,MAAM;aACX,MAAM,EAAE,cAAc;aACtB,GAAG,EAAE,GAAG;IAd1B,SAAgB,OAAO,EAAE,OAAO,OAAO,CAAwB;IAC/D,SAAgB,cAAc,EAAE,OAAO,cAAc,CAA+B;IAGpF,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,eAAe,CAA8B;IAC9C,KAAK,EAAE,MAAM,EAAE,CAAM;gBAGV,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,GAAG;IAsC1B;;;OAGG;IACH,kBAAkB,CAAC,SAAS,EAAE,mBAAmB;IAsBjD;;;OAGG;IACH,YAAY;IAUZ,eAAe;IAUf,aAAa,iCA6CX;IAEF,cAAc,4BA4IZ;IAEF,QAAQ;IAiBR,mBAAmB;IAqDZ,YAAY,CAAC,GAAG,EAAE,MAAM;CAGhC"}
package/dist/platform.js CHANGED
@@ -9,7 +9,7 @@ const crypto_1 = __importDefault(require("./crypto"));
9
9
  const os_1 = require("os");
10
10
  const settings_1 = require("./settings");
11
11
  const platformAccessory_1 = require("./platformAccessory");
12
- const tsAccessory_1 = require("./tsAccessory");
12
+ //import { GreeAirConditionerTS } from './tsAccessory';
13
13
  const commands_1 = __importDefault(require("./commands"));
14
14
  const version_1 = require("./version");
15
15
  /**
@@ -25,6 +25,7 @@ class GreeACPlatform {
25
25
  this.Service = this.api.hap.Service;
26
26
  this.Characteristic = this.api.hap.Characteristic;
27
27
  this.pluginAddresses = {};
28
+ this.ports = [];
28
29
  this.handleMessage = (msg, rinfo) => {
29
30
  this.log.debug('handleMessage -> %s', msg.toString());
30
31
  try {
@@ -52,8 +53,9 @@ class GreeACPlatform {
52
53
  encryptionVersion = 2;
53
54
  }
54
55
  this.log.debug('handleMessage - Package -> %j', pack);
55
- if (encryptionVersion === 1 && pack.t === 'dev' && pack.ver && pack.ver.toString().startsWith('V2.')) {
56
- // first V2 version responded to scan command with V1 encryption but binding requires V2 encryption
56
+ if (encryptionVersion === 1 && pack.t === 'dev' && pack.ver && !pack.ver.toString().startsWith('V1.')) {
57
+ // some devices respond to scan command with V1 encryption but binding requires V2 encryption
58
+ // we set encryption to V2 if device version is not V1.x
57
59
  encryptionVersion = 2;
58
60
  }
59
61
  if (pack.t === 'dev') {
@@ -103,6 +105,12 @@ class GreeACPlatform {
103
105
  deviceConfig[key] = value;
104
106
  }
105
107
  });
108
+ if (deviceConfig.port !== undefined && (typeof deviceConfig.port !== 'number' || deviceConfig.port !== deviceConfig.port ||
109
+ (typeof deviceConfig.port === 'number' && (deviceConfig.port < 1025 || deviceConfig.port > 65535)))) {
110
+ this.log.warn('Warning: Port is misconfigured (Valid port values: 1025~65535 or leave port empty to auto select) - ' +
111
+ `Accessory ${deviceInfo.mac} listening port overridden: ${deviceConfig.port} -> auto`);
112
+ deviceConfig.port = undefined;
113
+ }
106
114
  // force encryption version if set in config
107
115
  if (deviceConfig.encryptionVersion !== settings_1.ENCRYPTION_VERSION.auto) {
108
116
  deviceInfo.encryptionVersion = deviceConfig.encryptionVersion;
@@ -119,13 +127,13 @@ class GreeACPlatform {
119
127
  this.log.debug(`Accessory ${deviceInfo.mac}${devcfg.mac === undefined ? ' not configured -' : ''} skipped`);
120
128
  }
121
129
  if (accessory) {
122
- delete this.devices[accessory.context.deviceInfo.mac];
130
+ delete this.devices[accessory.context.device.mac];
123
131
  this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
124
132
  this.log.debug(`registerDevice - unregister (${devcfg.mac === undefined ? 'not configured' : 'disabled'}):`, accessory.displayName, accessory.UUID);
125
133
  accessory = undefined;
126
134
  }
127
135
  if (accessory_ts) {
128
- delete this.devices[accessory_ts.context.deviceInfo.mac];
136
+ delete this.devices[accessory_ts.context.device.mac];
129
137
  this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory_ts]);
130
138
  this.log.debug(`registerDevice - unregister (${devcfg.mac === undefined ? 'not configured' : 'disabled'}):`, accessory_ts.displayName, accessory_ts.UUID);
131
139
  accessory_ts = undefined;
@@ -140,28 +148,29 @@ class GreeACPlatform {
140
148
  if (accessory_ts && deviceInfo.address !== accessory_ts.context.device.address) {
141
149
  accessory_ts.context.device.address = deviceInfo.address;
142
150
  }
143
- if (accessory && this.initializedDevices[accessory.UUID]) {
151
+ if (accessory && this.processedDevices[accessory.UUID]) {
144
152
  // already initalized
145
- this.log.debug('registerDevice - already initalized:', accessory.displayName, accessory.UUID, accessory.context.device.mac);
153
+ this.log.debug('registerDevice - already processed:', accessory.displayName, accessory.context.device.mac, accessory.UUID);
146
154
  return;
147
155
  }
148
- // register heatercooler accessory
156
+ // create heatercooler accessory if not loaded from cache
149
157
  const deviceName = (_a = deviceConfig === null || deviceConfig === void 0 ? void 0 : deviceConfig.name) !== null && _a !== void 0 ? _a : (deviceInfo.name || deviceInfo.mac);
150
158
  if (!accessory) {
151
- this.log.debug(`Initializing new accessory ${deviceInfo.mac} with name ${deviceName} ...`);
159
+ this.log.debug(`Creating new accessory ${deviceInfo.mac} with name ${deviceName} ...`);
152
160
  const uuid = this.api.hap.uuid.generate(deviceInfo.mac);
153
161
  accessory = new this.api.platformAccessory(deviceName, uuid, 21 /* Categories.AIR_CONDITIONER */);
162
+ accessory.bound = false;
163
+ accessory.registered = false;
154
164
  this.devices[deviceInfo.mac] = accessory;
155
- this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
156
165
  }
157
- // register temperaturesensor accessory if configured as separate
166
+ // create temperaturesensor accessory if configured as separate and not loaded from cache
158
167
  const tsDeviceName = 'Temperature Sensor - ' + ((_b = deviceConfig === null || deviceConfig === void 0 ? void 0 : deviceConfig.name) !== null && _b !== void 0 ? _b : (deviceInfo.name || deviceInfo.mac));
159
168
  if (!accessory_ts && deviceConfig.temperatureSensor === settings_1.TS_TYPE.separate) {
160
- this.log.debug(`Initializing new accessory ${deviceInfo.mac}_ts with name ${tsDeviceName} ...`);
169
+ this.log.debug(`Creating new accessory ${deviceInfo.mac}_ts with name ${tsDeviceName} ...`);
161
170
  const uuid = this.api.hap.uuid.generate(deviceInfo.mac + '_ts');
162
171
  accessory_ts = new this.api.platformAccessory(tsDeviceName, uuid, 10 /* Categories.SENSOR */);
172
+ accessory_ts.registered = false;
163
173
  this.devices[deviceInfo.mac + '_ts'] = accessory_ts;
164
- this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory_ts]);
165
174
  }
166
175
  // unregister temperaturesensor accessory if configuration has changed from separate to any other
167
176
  if (accessory_ts && deviceConfig.temperatureSensor !== settings_1.TS_TYPE.separate) {
@@ -170,39 +179,36 @@ class GreeACPlatform {
170
179
  this.log.debug('registerDevice - unregister:', accessory_ts.displayName, accessory_ts.UUID);
171
180
  accessory_ts = undefined;
172
181
  }
173
- let tsService = null;
174
182
  if (accessory_ts && deviceConfig.temperatureSensor === settings_1.TS_TYPE.separate) {
175
183
  // mark temperature sensor device as initialized
176
184
  accessory_ts.context.device = { ...deviceInfo };
177
185
  accessory_ts.context.device.mac = deviceInfo.mac + '_ts';
178
186
  accessory_ts.context.deviceType = 'TemperatureSensor';
187
+ if (deviceConfig.model) {
188
+ accessory_ts.context.device.model = deviceConfig.model;
189
+ }
179
190
  accessory_ts.displayName = tsDeviceName;
180
- this.initializedDevices[accessory_ts.UUID] = true;
181
- tsService = new tsAccessory_1.GreeAirConditionerTS(this, accessory_ts, deviceConfig);
182
- this.log.debug(`registerDevice - ${accessory_ts.context.deviceType} initialized:`, accessory_ts.displayName, accessory_ts.context.device.mac, accessory_ts.UUID);
191
+ this.processedDevices[accessory_ts.UUID] = true;
192
+ this.log.debug(`registerDevice - ${accessory_ts.context.deviceType} created:`, accessory_ts.displayName, accessory_ts.context.device.mac, accessory_ts.UUID);
193
+ // do not load temperature sensor accessory here (it will be loaded from heatercooler accessory)
183
194
  }
184
195
  if (accessory) {
185
196
  // mark heatercooler device as initialized
186
197
  accessory.context.device = deviceInfo;
187
198
  accessory.context.deviceType = 'HeaterCooler';
188
199
  accessory.displayName = deviceName;
189
- this.initializedDevices[accessory.UUID] = true;
190
- this.log.debug(`registerDevice - ${accessory.context.deviceType} initialized:`, accessory.displayName, accessory.context.device.mac, accessory.UUID);
191
- new platformAccessory_1.GreeAirConditioner(this, accessory, deviceConfig, this.config.port, tsService);
192
- }
193
- // update registered accessories at the end of initialization
194
- const accessories = [accessory];
195
- if (accessory_ts) {
196
- accessories.push(accessory_ts);
200
+ this.processedDevices[accessory.UUID] = true;
201
+ this.log.debug(`registerDevice - ${accessory.context.deviceType} created:`, accessory.displayName, accessory.context.device.mac, accessory.UUID);
202
+ // load heatercooler accessory
203
+ new platformAccessory_1.GreeAirConditioner(this, accessory, deviceConfig, accessory_ts === null || accessory_ts === void 0 ? void 0 : accessory_ts.context.device.mac);
197
204
  }
198
- this.api.updatePlatformAccessories(accessories);
199
205
  };
200
206
  this.devices = {};
201
- this.initializedDevices = {};
207
+ this.processedDevices = {};
202
208
  this.skippedDevices = {};
203
209
  this.pluginAddresses = this.getNetworkAddresses();
204
210
  if (Object.entries(this.pluginAddresses).length > 0) {
205
- this.log.debug('Homebridge host addresses: ', this.pluginAddresses);
211
+ this.log.debug('Device detection address list {(address : netmask) pairs}:', this.pluginAddresses);
206
212
  }
207
213
  else {
208
214
  this.log.error('Error: Homebridge host has no IPv4 address');
@@ -238,11 +244,14 @@ class GreeACPlatform {
238
244
  * It should be used to setup event handlers for characteristics and update respective values.
239
245
  */
240
246
  configureAccessory(accessory) {
241
- var _a;
247
+ var _a, _b, _c;
242
248
  this.log.debug('Loading accessory from cache:', accessory.displayName, JSON.stringify(accessory.context.device));
243
249
  // add the restored accessory to the accessories cache so we can track if it has already been registered
244
- if ((_a = accessory.context.device) === null || _a === void 0 ? void 0 : _a.mac) {
245
- accessory.context.bound = false;
250
+ if ((_b = (_a = accessory.context) === null || _a === void 0 ? void 0 : _a.device) === null || _b === void 0 ? void 0 : _b.mac) {
251
+ if (!accessory.context.deviceType || accessory.context.deviceType === 'HeaterCooler') {
252
+ accessory.bound = false;
253
+ }
254
+ accessory.registered = true;
246
255
  this.devices[accessory.context.device.mac] = accessory;
247
256
  }
248
257
  // clean all invalid accessories found in cache
@@ -250,6 +259,10 @@ class GreeACPlatform {
250
259
  this.log.debug('Invalid accessory found in cache - deleting:', accessory.displayName, accessory.UUID);
251
260
  this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
252
261
  }
262
+ // remove deprecated properties from cached accessory
263
+ if (((_c = accessory.context) === null || _c === void 0 ? void 0 : _c.bound) !== undefined) {
264
+ delete accessory.context.bound;
265
+ }
253
266
  }
254
267
  /**
255
268
  * Accessories must only be registered once, previously created accessories
@@ -257,28 +270,36 @@ class GreeACPlatform {
257
270
  */
258
271
  bindCallback() {
259
272
  this.log.info(`${settings_1.PLATFORM_NAME} (${settings_1.PLUGIN_NAME}) v%s is running on UDP port %d`, version_1.version, this.socket.address().port);
273
+ this.ports.push(this.socket.address().port);
260
274
  this.socket.setBroadcast(true);
275
+ this.sendScan();
261
276
  setInterval(() => {
262
- this.broadcastScan();
277
+ this.sendScan();
263
278
  }, (this.config.scanInterval || settings_1.DEF_SCAN_INTERVAL) * 1000); // scanInterval in seconds (default = 60 sec)
264
279
  }
265
280
  discoverDevices() {
266
281
  if (this.config.port === undefined || (this.config.port !== undefined && typeof this.config.port === 'number' &&
267
- this.config.port === this.config.port && this.config.port >= 0 && this.config.port <= 65279)) {
282
+ this.config.port === this.config.port && this.config.port >= 1025 && this.config.port <= 65535)) {
268
283
  this.socket.bind(this.config.port, undefined, () => this.bindCallback());
269
284
  }
270
285
  else {
271
- this.log.error('Error: Port is misconfigured (Valid port values: 1025~65279 or leave port empty to auto select)');
286
+ this.log.error('Error: Port is misconfigured (Valid port values: 1025~65535 or leave port empty to auto select)');
272
287
  this.socket.close();
273
288
  }
274
289
  }
275
- broadcastScan() {
290
+ sendScan() {
276
291
  const message = Buffer.from(JSON.stringify({ t: 'scan' }));
277
292
  Object.entries(this.pluginAddresses).forEach((value) => {
278
- this.socket.send(message, 0, message.length, settings_1.UDP_SCAN_PORT, value[0], (error) => {
279
- this.log.debug(`Broadcast '${message}' ${value[0]}:${settings_1.UDP_SCAN_PORT}`);
293
+ const addr = value[0];
294
+ this.socket.send(message, 0, message.length, settings_1.UDP_SCAN_PORT, addr, (error) => {
295
+ if (this.pluginAddresses[addr] === '255.255.255.255') {
296
+ this.log.debug(`Scanning for device (unicast) '${message}' ${addr}:${settings_1.UDP_SCAN_PORT}`);
297
+ }
298
+ else {
299
+ this.log.debug(`Scanning for devices (broadcast) '${message}' ${addr}:${settings_1.UDP_SCAN_PORT}`);
300
+ }
280
301
  if (error) {
281
- this.log.error('broadcastScan - Error:', error.message);
302
+ this.log.error('Device scan - Error:', error.message);
282
303
  }
283
304
  });
284
305
  });
@@ -298,21 +319,48 @@ class GreeACPlatform {
298
319
  const netmaskParts = iface.netmask.split('.');
299
320
  const broadcast = addrParts.map((e, i) => ((~Number(netmaskParts[i]) & 0xFF) | Number(e)).toString()).join('.');
300
321
  this.log.debug('Interface: \'%s\' Address: %s Netmask: %s Broadcast: %s', name, iface.address, iface.netmask, broadcast);
301
- pluginAddresses[broadcast] = pluginAddresses[broadcast] === undefined ? iface.address :
302
- pluginAddresses[broadcast] + ';' + iface.address;
322
+ if (pluginAddresses[broadcast] === undefined) {
323
+ pluginAddresses[broadcast] = iface.netmask;
324
+ }
303
325
  }
304
326
  }
305
327
  }
306
328
  }
307
- // Sort IP addresses for consistent comparison
308
- for (const bcast of Object.keys(pluginAddresses)) {
309
- // Keep only unique IPs
310
- const ips = Array.from(new Set(pluginAddresses[bcast].split(';')));
311
- ips.sort((a, b) => (a < b ? -1 : 1));
312
- pluginAddresses[bcast] = ips.join(';');
329
+ // Add IPs from configuration but only if at least one host address found
330
+ if (Object.keys(pluginAddresses).length > 0) {
331
+ const devcfgs = this.config.devices.filter((item) => item.ip && !item.disabled) || [];
332
+ devcfgs.forEach((value) => {
333
+ const ip = value['ip'];
334
+ 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}$/;
335
+ if (ipv4Pattern.test(ip)) {
336
+ this.log.debug('Found AC Unit address in configuration:', ip);
337
+ const addrParts = ip.split('.');
338
+ const addresses = {};
339
+ Object.keys(pluginAddresses).forEach((addr) => {
340
+ const netmaskParts = pluginAddresses[addr].split('.');
341
+ const broadcast = addrParts.map((e, i) => ((~Number(netmaskParts[i]) & 0xFF) | Number(e)).toString()).join('.');
342
+ if (addr === broadcast) {
343
+ addresses[ip] = true;
344
+ }
345
+ });
346
+ const skipAddress = Object.keys(addresses).find((addr) => addr === ip);
347
+ if (skipAddress === undefined) {
348
+ pluginAddresses[ip] = '255.255.255.255';
349
+ }
350
+ else {
351
+ this.log.debug('AC Unit (%s) is already on broadcast list - skipping', skipAddress);
352
+ }
353
+ }
354
+ else {
355
+ this.log.warn('Warning: Invalid IP address found in configuration: %s - skipping', ip);
356
+ }
357
+ });
313
358
  }
314
359
  return pluginAddresses;
315
360
  }
361
+ getAccessory(mac) {
362
+ return this.devices[mac];
363
+ }
316
364
  }
317
365
  exports.GreeACPlatform = GreeACPlatform;
318
366
  //# sourceMappingURL=platform.js.map