homebridge-nest-accfactory 0.0.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -4
- package/README.md +37 -19
- package/dist/HomeKitDevice.js +132 -109
- package/dist/camera.js +344 -262
- package/dist/doorbell.js +5 -3
- package/dist/floodlight.js +4 -4
- package/dist/nexustalk.js +84 -52
- package/dist/protect.js +2 -2
- package/dist/protobuf/googlehome/foyer.proto +216 -160
- package/dist/res/Nest_camera_connecting.h264 +0 -0
- package/dist/res/Nest_camera_off.h264 +0 -0
- package/dist/res/Nest_camera_offline.h264 +0 -0
- package/dist/res/Nest_camera_transfer.h264 +0 -0
- package/dist/streamer.js +100 -71
- package/dist/system.js +1321 -1263
- package/dist/thermostat.js +73 -27
- package/dist/webrtc.js +582 -0
- package/package.json +31 -29
package/CHANGELOG.md
CHANGED
|
@@ -2,11 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `homebridge-nest-accfactory` will be documented in this file. This project tries to adhere to [Semantic Versioning](http://semver.org/).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Breaking changes v0.2.0+
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Unfornunately, from version **0.2.0**, I've made some breaking changes in the code to help move forward with the project.
|
|
8
|
+
So, what does this mean for you, the end user.
|
|
9
|
+
1) You'll need to remove all previously discovered devices from HomeKit before adding them back in after upgrading to this version
|
|
10
|
+
2) If using Homebridge version, remove any cached acccesory data associated with this plug-in
|
|
11
|
+
3) If using docker/standalone version, remove the 'persist' folder
|
|
12
|
+
4) Re-add devices to HomeKit once version upgraded
|
|
13
|
+
5) Any HomeKit Secure Video recordings will be lost
|
|
14
|
+
6) Will need to re-configure camera streaming and notification options
|
|
15
|
+
6) Any automations will need to be re-created in HomeKit
|
|
16
|
+
7) History in EveHome app will be lost
|
|
8
17
|
|
|
9
|
-
|
|
18
|
+
Appologies for this change, as I can understand what an inconvience and frustration it will be :-(
|
|
19
|
+
|
|
20
|
+
## Known Issues
|
|
21
|
+
|
|
22
|
+
- Audio from newer Nest/Google camera/doorbell devices is still blank
|
|
23
|
+
- npm package [ip](https://github.com/advisories/GHSA-2p57-rm9w-gvfp) has severity issue. This is being used in external library (werift)
|
|
24
|
+
|
|
25
|
+
## v0.2.0 (2024/10/04)
|
|
26
|
+
|
|
27
|
+
- General code cleanup and bug fixes
|
|
28
|
+
- Common configuration between Homebridge plug-in and docker/standalone versions
|
|
29
|
+
- Seemlessly allow Nest/Google devices to be migrated between Nest <-> Google Home apps
|
|
30
|
+
|
|
31
|
+
## v0.1.9 (alpha)
|
|
32
|
+
|
|
33
|
+
- General code cleanup and bug fixes
|
|
34
|
+
- Aligned version numbering to old Nest_accfactory repo
|
|
35
|
+
- Audio talkback support for newer Nest/Google camera/doorbell devices
|
|
36
|
+
|
|
37
|
+
## v0.0.7 (alpha)
|
|
38
|
+
|
|
39
|
+
- General code cleanup and bug fixes
|
|
40
|
+
- Updated streaming/recording support for newer Nest/Google camera/doorbell devices
|
|
41
|
+
- No incoming audio, just video stream
|
|
42
|
+
|
|
43
|
+
## v0.0.6 (2024-09-14)
|
|
44
|
+
|
|
45
|
+
- Fix for two/way audio starting on non-enabled HKSV camera/doorbells
|
|
46
|
+
|
|
47
|
+
## v0.0.5 (2024-09-13)
|
|
10
48
|
|
|
11
49
|
- General code cleanup and bug fixes
|
|
12
50
|
- External dependancy reductions, dropped pbf and axios libraries
|
|
@@ -18,7 +56,7 @@ Currently all releases are considered 'alpha' status, where things may or may no
|
|
|
18
56
|
- Camera/Doorbell support for snapshots and live video re-introduced
|
|
19
57
|
- HomeKit Secure Video recording support re-introduced
|
|
20
58
|
- Should support Nest Thermostat 4th Gen (untested)
|
|
21
|
-
- *might* have finally resolved audio sync issues
|
|
59
|
+
- *might* have finally resolved audio sync issues with live video and recording
|
|
22
60
|
- Depending on the libraries present in ffmpeg binary, we limit functionality for camera/doorbells
|
|
23
61
|
- missing libspeex = no two-way audio
|
|
24
62
|
- missing libfdk_aac = no audio
|
package/README.md
CHANGED
|
@@ -82,6 +82,9 @@ Sample config.json entries below
|
|
|
82
82
|
"XXXXXXXX": {
|
|
83
83
|
"exclude": false
|
|
84
84
|
},
|
|
85
|
+
"YYYYYYYY" : {
|
|
86
|
+
"hksv" : true
|
|
87
|
+
}
|
|
85
88
|
},
|
|
86
89
|
"platform": "NestAccfactory"
|
|
87
90
|
}
|
|
@@ -91,30 +94,45 @@ Sample config.json entries below
|
|
|
91
94
|
|
|
92
95
|
The following options are available in the config.json options object. These apply to all discovered devices.
|
|
93
96
|
|
|
94
|
-
| Name | Description | Default
|
|
95
|
-
|
|
96
|
-
| elevation | Height above sea level for the weather station | 0
|
|
97
|
-
| eveHistory | Provide history in EveHome application where applicable | true
|
|
98
|
-
| ffmegPath | Path to an ffmpeg binary for us to use. Will look in current directory by default |
|
|
99
|
-
| hksv | Enable HomeKit Secure Video for supported camera(s) and doorbell(s) | false
|
|
100
|
-
| maxStreams | Maximum number of concurrent video streams in HomeKit for supported camera(s) and doorbell(s) | 2
|
|
101
|
-
| weather | Virtual weather station for each Nest/Google home we discover | false
|
|
97
|
+
| Name | Description | Default |
|
|
98
|
+
|-------------------|-----------------------------------------------------------------------------------------------|------------|
|
|
99
|
+
| elevation | Height above sea level for the weather station | 0 |
|
|
100
|
+
| eveHistory | Provide history in EveHome application where applicable | true |
|
|
101
|
+
| ffmegPath | Path to an ffmpeg binary for us to use. Will look in current directory by default | |
|
|
102
|
+
| hksv | Enable HomeKit Secure Video for supported camera(s) and doorbell(s) | false |
|
|
103
|
+
| maxStreams | Maximum number of concurrent video streams in HomeKit for supported camera(s) and doorbell(s) | 2 |
|
|
104
|
+
| weather | Virtual weather station for each Nest/Google home we discover | false |
|
|
102
105
|
|
|
103
106
|
#### devices
|
|
104
107
|
|
|
105
108
|
The following options are available on a per-device level in the config.json devices object. The device is specified by using its serial number (in uppercase)
|
|
106
109
|
|
|
107
|
-
| Name | Description | Default
|
|
108
|
-
|
|
109
|
-
| chimeSwitch | Create a switch for supported doorbell(s) which allows the indoor chime to be turned on/off | false
|
|
110
|
-
| doorbellCooldown | Time in seconds between doorbell press events | 60
|
|
111
|
-
| elevation | Height above sea level for the specific weather station | 0
|
|
112
|
-
| eveHistory | Provide history in EveHome application where applicable for the specific device | true
|
|
113
|
-
| exclude | Exclude the device | false
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
110
|
+
| Name | Description | Default |
|
|
111
|
+
|-------------------|-----------------------------------------------------------------------------------------------|------------|
|
|
112
|
+
| chimeSwitch | Create a switch for supported doorbell(s) which allows the indoor chime to be turned on/off | false |
|
|
113
|
+
| doorbellCooldown | Time in seconds between doorbell press events | 60 |
|
|
114
|
+
| elevation | Height above sea level for the specific weather station | 0 |
|
|
115
|
+
| eveHistory | Provide history in EveHome application where applicable for the specific device | true |
|
|
116
|
+
| exclude | Exclude the device | false |
|
|
117
|
+
| hksv | Enable HomeKit Secure Video for supported camera(s) and doorbell(s) | false |
|
|
118
|
+
| humiditySensor | Create a seperate humidity sensor for supported thermostat(s) | false |
|
|
119
|
+
| localAccess | Use direct access to supported camera(s) and doorbell(s) for video streaming and recording | false |
|
|
120
|
+
| motionCooldown | Time in seconds between detected motion events | 60 |
|
|
121
|
+
| personCooldown | Time in seconds between detected person events | 120 |
|
|
122
|
+
|
|
123
|
+
## ffmpeg
|
|
124
|
+
|
|
125
|
+
**As of 3/10/2024, the [Homebridge Docker Image](https://hub.docker.com/r/homebridge/homebridge) includes an ffmpeg binary meeting our requirements. Its located in /usr/local/bin/ffmpeg**
|
|
126
|
+
|
|
127
|
+
To support streaming and recording from cameras, an ffmpeg binary needs to be present. We have specific requirements, which are:
|
|
128
|
+
- version 6.0 or later
|
|
129
|
+
- compiled with:
|
|
130
|
+
- libx264
|
|
131
|
+
- libfdk-aac
|
|
132
|
+
- libspeex
|
|
133
|
+
- libopus
|
|
134
|
+
|
|
135
|
+
By default, we look in the current directory where the plug-in excutes for an ffmpeg binary, however, you can specify a specific ffmpeg binary to use va the configuration option 'ffmpegPath'
|
|
118
136
|
|
|
119
137
|
## Caveats
|
|
120
138
|
|
package/dist/HomeKitDevice.js
CHANGED
|
@@ -6,22 +6,19 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Homebridge Plugin:
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// software_version
|
|
9
|
+
// serialNumber
|
|
10
|
+
// softwareVersion
|
|
12
11
|
// description
|
|
13
12
|
// manufacturer
|
|
14
13
|
// model
|
|
15
14
|
//
|
|
16
15
|
// HAP-NodeJS Library Accessory:
|
|
17
16
|
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// software_version
|
|
17
|
+
// serialNumber
|
|
18
|
+
// softwareVersion
|
|
21
19
|
// description
|
|
22
20
|
// manufacturer
|
|
23
21
|
// model
|
|
24
|
-
// hkUsername
|
|
25
22
|
// hkPairingCode
|
|
26
23
|
//
|
|
27
24
|
// Following constants should be overridden in the module loading this class file
|
|
@@ -37,12 +34,14 @@
|
|
|
37
34
|
// HomeKitDevice.updateServices(deviceData)
|
|
38
35
|
// HomeKitDevice.messageServices(type, message)
|
|
39
36
|
//
|
|
40
|
-
// Code version
|
|
37
|
+
// Code version 28/9/2024
|
|
41
38
|
// Mark Hulskamp
|
|
42
39
|
'use strict';
|
|
43
40
|
|
|
44
41
|
// Define nodejs module requirements
|
|
42
|
+
import crypto from 'crypto';
|
|
45
43
|
import EventEmitter from 'node:events';
|
|
44
|
+
import { Buffer } from 'node:buffer';
|
|
46
45
|
|
|
47
46
|
// Define our HomeKit device class
|
|
48
47
|
export default class HomeKitDevice {
|
|
@@ -60,6 +59,7 @@ export default class HomeKitDevice {
|
|
|
60
59
|
accessory = undefined; // Accessory service for this device
|
|
61
60
|
hap = undefined; // HomeKit Accessory Protocol API stub
|
|
62
61
|
log = undefined; // Logging function object
|
|
62
|
+
uuid = undefined; // UUID for this instance
|
|
63
63
|
|
|
64
64
|
// Internal data only for this class
|
|
65
65
|
#platform = undefined; // Homebridge platform api
|
|
@@ -78,7 +78,7 @@ export default class HomeKitDevice {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// Workout if we're running under HomeBridge or HAP-NodeJS library
|
|
81
|
-
if (
|
|
81
|
+
if (isNaN(api?.version) === false && typeof api?.hap === 'object' && api?.HAPLibraryVersion === undefined) {
|
|
82
82
|
// We have the HomeBridge version number and hap API object
|
|
83
83
|
this.hap = api.hap;
|
|
84
84
|
this.#platform = api;
|
|
@@ -86,65 +86,68 @@ export default class HomeKitDevice {
|
|
|
86
86
|
this?.log?.debug && this.log.debug('HomeKitDevice module using Homebridge backend for "%s"', deviceData?.description);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
if (typeof api?.HAPLibraryVersion === 'function' &&
|
|
89
|
+
if (typeof api?.HAPLibraryVersion === 'function' && api?.version === undefined && api?.hap === undefined) {
|
|
90
90
|
// As we're missing the HomeBridge entry points but have the HAP library version
|
|
91
91
|
this.hap = api;
|
|
92
92
|
|
|
93
93
|
this?.log?.debug && this.log.debug('HomeKitDevice module using HAP-NodeJS library for "%s"', deviceData?.description);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
96
|
+
// Generate UUID for this device instance
|
|
97
|
+
// Will either be a random generated one or HAP generated one
|
|
98
|
+
// HAP is based upon defined plugin name and devices serial number
|
|
99
|
+
this.uuid = crypto.randomUUID();
|
|
100
|
+
if (
|
|
101
|
+
typeof HomeKitDevice.PLUGIN_NAME === 'string' &&
|
|
102
|
+
HomeKitDevice.PLUGIN_NAME !== '' &&
|
|
103
|
+
typeof deviceData.serialNumber === 'string' &&
|
|
104
|
+
deviceData.serialNumber !== '' &&
|
|
105
|
+
typeof this?.hap?.uuid?.generate === 'function'
|
|
106
|
+
) {
|
|
107
|
+
this.uuid = this.hap.uuid.generate(HomeKitDevice.PLUGIN_NAME + '_' + deviceData.serialNumber.toUpperCase());
|
|
105
108
|
}
|
|
106
109
|
|
|
107
|
-
// Make a clone of current data and store in this object
|
|
108
|
-
// Important that we done have a 'linked' cope of the object data
|
|
109
|
-
// eslint-disable-next-line no-undef
|
|
110
|
-
this.deviceData = structuredClone(deviceData);
|
|
111
|
-
|
|
112
110
|
// See if we were passed in an existing accessory object or array of accessory objects
|
|
113
111
|
// Mainly used to restore a HomeBridge cached accessory
|
|
114
|
-
if (
|
|
115
|
-
typeof accessory === 'object' &&
|
|
116
|
-
typeof this?.hap?.uuid?.generate === 'function' &&
|
|
117
|
-
typeof deviceData.uuid === 'string' &&
|
|
118
|
-
this.#platform !== undefined
|
|
119
|
-
) {
|
|
120
|
-
let uuid = this.hap.uuid.generate(HomeKitDevice.PLUGIN_NAME + '_' + deviceData.uuid);
|
|
121
|
-
|
|
112
|
+
if (typeof accessory === 'object' && this.#platform !== undefined) {
|
|
122
113
|
if (Array.isArray(accessory) === true) {
|
|
123
|
-
this.accessory = accessory.find((accessory) => accessory
|
|
114
|
+
this.accessory = accessory.find((accessory) => accessory?.UUID === this.uuid);
|
|
124
115
|
}
|
|
125
|
-
if (Array.isArray(accessory) === false &&
|
|
116
|
+
if (Array.isArray(accessory) === false && accessory?.UUID === this.uuid) {
|
|
126
117
|
this.accessory = accessory;
|
|
127
118
|
}
|
|
128
119
|
}
|
|
120
|
+
|
|
121
|
+
// Validate if eventEmitter object passed to us is an instance of EventEmitter
|
|
122
|
+
// If valid, setup an event listener for messages to this device using our generated uuid
|
|
123
|
+
if (eventEmitter instanceof EventEmitter === true) {
|
|
124
|
+
this.#eventEmitter = eventEmitter;
|
|
125
|
+
this.#eventEmitter.addListener(this.uuid, this.#message.bind(this));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Make a clone of current data and store in this object
|
|
129
|
+
// Important that we done have a 'linked' copy of the object data
|
|
130
|
+
// eslint-disable-next-line no-undef
|
|
131
|
+
this.deviceData = structuredClone(deviceData);
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
// Class functions
|
|
132
135
|
async add(accessoryName, accessoryCategory, useHistoryService) {
|
|
133
136
|
if (
|
|
134
137
|
this.hap === undefined ||
|
|
135
|
-
HomeKitDevice.PLUGIN_NAME
|
|
136
|
-
HomeKitDevice.
|
|
138
|
+
typeof HomeKitDevice.PLUGIN_NAME !== 'string' ||
|
|
139
|
+
HomeKitDevice.PLUGIN_NAME === '' ||
|
|
140
|
+
typeof HomeKitDevice.PLATFORM_NAME !== 'string' ||
|
|
141
|
+
HomeKitDevice.PLATFORM_NAME === '' ||
|
|
137
142
|
typeof accessoryName !== 'string' ||
|
|
138
143
|
accessoryName === '' ||
|
|
139
144
|
typeof this.hap.Categories[accessoryCategory] === 'undefined' ||
|
|
140
145
|
typeof useHistoryService !== 'boolean' ||
|
|
141
146
|
typeof this.deviceData !== 'object' ||
|
|
142
|
-
typeof this.deviceData?.
|
|
143
|
-
this.deviceData.
|
|
144
|
-
typeof this.deviceData?.
|
|
145
|
-
this.deviceData.
|
|
146
|
-
typeof this.deviceData?.software_version !== 'string' ||
|
|
147
|
-
this.deviceData.software_version === '' ||
|
|
147
|
+
typeof this.deviceData?.serialNumber !== 'string' ||
|
|
148
|
+
this.deviceData.serialNumber === '' ||
|
|
149
|
+
typeof this.deviceData?.softwareVersion !== 'string' ||
|
|
150
|
+
this.deviceData.softwareVersion === '' ||
|
|
148
151
|
(typeof this.deviceData?.description !== 'string' && this.deviceData.description === '') ||
|
|
149
152
|
typeof this.deviceData?.model !== 'string' ||
|
|
150
153
|
this.deviceData.model === '' ||
|
|
@@ -153,10 +156,7 @@ export default class HomeKitDevice {
|
|
|
153
156
|
(this.#platform === undefined &&
|
|
154
157
|
typeof this.deviceData?.hkPairingCode !== 'string' &&
|
|
155
158
|
(new RegExp(/^([0-9]{3}-[0-9]{2}-[0-9]{3})$/).test(this.deviceData.hkPairingCode) === true ||
|
|
156
|
-
new RegExp(/^([0-9]{4}-[0-9]{4})$/).test(this.deviceData.hkPairingCode) === true))
|
|
157
|
-
(this.#platform === undefined &&
|
|
158
|
-
typeof this.deviceData?.hkUsername !== 'string' &&
|
|
159
|
-
new RegExp(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/).test(this.deviceData.hkUsername) === false)
|
|
159
|
+
new RegExp(/^([0-9]{4}-[0-9]{4})$/).test(this.deviceData.hkPairingCode) === true))
|
|
160
160
|
) {
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
@@ -164,23 +164,21 @@ export default class HomeKitDevice {
|
|
|
164
164
|
// If we do not have an existing accessory object, create a new one
|
|
165
165
|
if (this.accessory === undefined && this.#platform !== undefined) {
|
|
166
166
|
// Create HomeBridge platform accessory
|
|
167
|
-
this.accessory = new this.#platform.platformAccessory(
|
|
168
|
-
this.deviceData.description,
|
|
169
|
-
this.hap.uuid.generate(HomeKitDevice.PLUGIN_NAME + '_' + this.deviceData.uuid),
|
|
170
|
-
);
|
|
167
|
+
this.accessory = new this.#platform.platformAccessory(this.deviceData.description, this.uuid);
|
|
171
168
|
this.#platform.registerPlatformAccessories(HomeKitDevice.PLUGIN_NAME, HomeKitDevice.PLATFORM_NAME, [this.accessory]);
|
|
172
169
|
}
|
|
173
170
|
|
|
174
171
|
if (this.accessory === undefined && this.#platform === undefined) {
|
|
175
172
|
// Create HAP-NodeJS libray accessory
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
|
|
173
|
+
this.accessory = new this.hap.Accessory(accessoryName, this.uuid);
|
|
174
|
+
|
|
175
|
+
// Create a HomeKit username for the device in format of xx:xx:xx:xx:xx:xx
|
|
176
|
+
// Use a Nest Labs prefix for first 6 digits, followed by a CRC24 based off serial number for last 6 digits.
|
|
177
|
+
this.accessory.username = ('18B430' + crc24(this.deviceData.serialNumber.toUpperCase()))
|
|
178
|
+
.toString('hex')
|
|
179
|
+
.split(/(..)/)
|
|
180
|
+
.filter((s) => s)
|
|
181
|
+
.join(':');
|
|
184
182
|
this.accessory.pincode = this.deviceData.hkPairingCode;
|
|
185
183
|
this.accessory.category = accessoryCategory;
|
|
186
184
|
}
|
|
@@ -190,10 +188,10 @@ export default class HomeKitDevice {
|
|
|
190
188
|
if (informationService !== undefined) {
|
|
191
189
|
informationService.updateCharacteristic(this.hap.Characteristic.Manufacturer, this.deviceData.manufacturer);
|
|
192
190
|
informationService.updateCharacteristic(this.hap.Characteristic.Model, this.deviceData.model);
|
|
193
|
-
informationService.updateCharacteristic(this.hap.Characteristic.SerialNumber, this.deviceData.
|
|
194
|
-
informationService.updateCharacteristic(this.hap.Characteristic.FirmwareRevision, this.deviceData.
|
|
191
|
+
informationService.updateCharacteristic(this.hap.Characteristic.SerialNumber, this.deviceData.serialNumber);
|
|
192
|
+
informationService.updateCharacteristic(this.hap.Characteristic.FirmwareRevision, this.deviceData.softwareVersion);
|
|
193
|
+
informationService.updateCharacteristic(this.hap.Characteristic.Name, this.deviceData.description);
|
|
195
194
|
}
|
|
196
|
-
informationService.updateCharacteristic(this.hap.Characteristic.Name, this.deviceData.description);
|
|
197
195
|
|
|
198
196
|
// Setup our history service if module has been defined and requested to be active for this device
|
|
199
197
|
if (typeof HomeKitDevice?.HISTORY === 'function' && this.historyService === undefined && useHistoryService === true) {
|
|
@@ -221,34 +219,31 @@ export default class HomeKitDevice {
|
|
|
221
219
|
}
|
|
222
220
|
}
|
|
223
221
|
|
|
224
|
-
// if we have a valid EventEmitter and we have not previously setup an message event handler, do so now
|
|
225
|
-
if (this.#eventEmitter !== undefined && this.#eventEmitter.listenerCount(this.deviceData.uuid) === 0) {
|
|
226
|
-
this.#eventEmitter.addListener(this.deviceData.uuid, this.#message.bind(this));
|
|
227
|
-
}
|
|
228
|
-
|
|
229
222
|
// Perform an initial update using current data
|
|
230
223
|
this.update(this.deviceData, true);
|
|
231
224
|
|
|
232
225
|
// If using HAP-NodeJS library, publish accessory on local network
|
|
233
226
|
if (this.#platform === undefined && this.accessory !== undefined) {
|
|
234
|
-
if (this?.log?.info) {
|
|
235
|
-
this.log.info(' += Advertising as "%s"', accessoryName);
|
|
236
|
-
this.log.info(' += Pairing code is "%s"', this.accessory.pincode);
|
|
237
|
-
}
|
|
238
227
|
this.accessory.publish({
|
|
239
228
|
username: this.accessory.username,
|
|
240
229
|
pincode: this.accessory.pincode,
|
|
241
230
|
category: this.accessory.category,
|
|
242
231
|
});
|
|
232
|
+
if (this?.log?.info) {
|
|
233
|
+
this.log.info(' += Advertising as "%s"', this.accessory.displayName);
|
|
234
|
+
this.log.info(' += Pairing code is "%s"', this.accessory.pincode);
|
|
235
|
+
}
|
|
243
236
|
}
|
|
237
|
+
|
|
238
|
+
return this.accessory; // Return our HomeKit accessory
|
|
244
239
|
}
|
|
245
240
|
|
|
246
241
|
remove() {
|
|
247
242
|
this?.log?.warn && this.log.warn('Device "%s" has been removed', this.deviceData.description);
|
|
248
243
|
|
|
249
|
-
if (this.#eventEmitter
|
|
244
|
+
if (this.#eventEmitter !== undefined) {
|
|
250
245
|
// Remove listener for 'messages'
|
|
251
|
-
this.#eventEmitter.removeAllListeners(this.
|
|
246
|
+
this.#eventEmitter.removeAllListeners(this.uuid);
|
|
252
247
|
}
|
|
253
248
|
|
|
254
249
|
if (typeof this.removeServices === 'function') {
|
|
@@ -260,12 +255,12 @@ export default class HomeKitDevice {
|
|
|
260
255
|
}
|
|
261
256
|
|
|
262
257
|
if (this.accessory !== undefined && this.#platform !== undefined) {
|
|
263
|
-
// Unregister the accessory from Homebridge
|
|
258
|
+
// Unregister the accessory from Homebridge platform
|
|
264
259
|
this.#platform.unregisterPlatformAccessories(HomeKitDevice.PLUGIN_NAME, HomeKitDevice.PLATFORM_NAME, [this.accessory]);
|
|
265
260
|
}
|
|
266
261
|
|
|
267
262
|
if (this.accessory !== undefined && this.#platform === undefined) {
|
|
268
|
-
// Unpublish the accessory from HAP
|
|
263
|
+
// Unpublish the accessory from HAP-NodeJS library
|
|
269
264
|
this.accessory.unpublish();
|
|
270
265
|
}
|
|
271
266
|
|
|
@@ -274,6 +269,7 @@ export default class HomeKitDevice {
|
|
|
274
269
|
this.historyService = undefined;
|
|
275
270
|
this.hap = undefined;
|
|
276
271
|
this.log = undefined;
|
|
272
|
+
this.uuid = undefined;
|
|
277
273
|
this.#platform = undefined;
|
|
278
274
|
this.#eventEmitter = undefined;
|
|
279
275
|
|
|
@@ -320,42 +316,46 @@ export default class HomeKitDevice {
|
|
|
320
316
|
deviceData.manufacturer !== '' &&
|
|
321
317
|
deviceData.manufacturer !== this.deviceData.manufacturer
|
|
322
318
|
) {
|
|
323
|
-
// Update
|
|
324
|
-
informationService.updateCharacteristic(this.hap.Characteristic.Manufacturer,
|
|
319
|
+
// Update manufacturer number on the HomeKit accessory
|
|
320
|
+
informationService.updateCharacteristic(this.hap.Characteristic.Manufacturer, deviceData.manufacturer);
|
|
325
321
|
}
|
|
326
322
|
|
|
327
323
|
if (typeof deviceData?.model === 'string' && deviceData.model !== '' && deviceData.model !== this.deviceData.model) {
|
|
328
|
-
// Update
|
|
329
|
-
informationService.updateCharacteristic(this.hap.Characteristic.Model,
|
|
324
|
+
// Update model on the HomeKit accessory
|
|
325
|
+
informationService.updateCharacteristic(this.hap.Characteristic.Model, deviceData.model);
|
|
330
326
|
}
|
|
331
327
|
|
|
332
328
|
if (
|
|
333
|
-
typeof deviceData?.
|
|
334
|
-
deviceData.
|
|
335
|
-
deviceData.
|
|
329
|
+
typeof deviceData?.softwareVersion === 'string' &&
|
|
330
|
+
deviceData.softwareVersion !== '' &&
|
|
331
|
+
deviceData.softwareVersion !== this.deviceData.softwareVersion
|
|
336
332
|
) {
|
|
337
|
-
// Update
|
|
338
|
-
informationService.updateCharacteristic(this.hap.Characteristic.
|
|
333
|
+
// Update software version on the HomeKit accessory
|
|
334
|
+
informationService.updateCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceData.softwareVersion);
|
|
339
335
|
}
|
|
340
336
|
|
|
337
|
+
// Check for devices serial number changing. Really shouldn't occur, but handle case anyway
|
|
341
338
|
if (
|
|
342
|
-
typeof deviceData?.
|
|
343
|
-
deviceData.
|
|
344
|
-
deviceData.
|
|
339
|
+
typeof deviceData?.serialNumber === 'string' &&
|
|
340
|
+
deviceData.serialNumber !== '' &&
|
|
341
|
+
deviceData.serialNumber.toUpperCase() !== this.deviceData.serialNumber.toUpperCase()
|
|
345
342
|
) {
|
|
343
|
+
this?.log?.warn && this.log.warn('Serial number on "%s" has changed', deviceData.description);
|
|
344
|
+
this?.log?.warn && this.log.warn('This may cause the device to become unresponsive in HomeKit');
|
|
345
|
+
|
|
346
346
|
// Update software version on the HomeKit accessory
|
|
347
|
-
informationService.updateCharacteristic(this.hap.Characteristic.
|
|
347
|
+
informationService.updateCharacteristic(this.hap.Characteristic.SerialNumber, deviceData.serialNumber);
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
350
|
|
|
351
351
|
if (typeof deviceData?.online === 'boolean' && deviceData.online !== this.deviceData.online) {
|
|
352
352
|
// Output device online/offline status
|
|
353
|
-
if (deviceData.online === false
|
|
354
|
-
this.log.warn('Device "%s" is offline',
|
|
353
|
+
if (deviceData.online === false) {
|
|
354
|
+
this?.log?.warn && this.log.warn('Device "%s" is offline', deviceData.description);
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
if (deviceData.online === true
|
|
358
|
-
this.log.success('Device "%s" is online',
|
|
357
|
+
if (deviceData.online === true) {
|
|
358
|
+
this?.log?.success && this.log.success('Device "%s" is online', deviceData.description);
|
|
359
359
|
}
|
|
360
360
|
}
|
|
361
361
|
|
|
@@ -363,7 +363,7 @@ export default class HomeKitDevice {
|
|
|
363
363
|
try {
|
|
364
364
|
this.updateServices(deviceData); // Pass updated data on for accessory to process as it needs
|
|
365
365
|
} catch (error) {
|
|
366
|
-
this?.log?.error && this.log.error('updateServices call for device "%s" failed. Error was',
|
|
366
|
+
this?.log?.error && this.log.error('updateServices call for device "%s" failed. Error was', deviceData.description, error);
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
|
|
@@ -374,17 +374,12 @@ export default class HomeKitDevice {
|
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
async set(values) {
|
|
377
|
-
if (
|
|
378
|
-
typeof values !== 'object' ||
|
|
379
|
-
this.#eventEmitter === undefined ||
|
|
380
|
-
typeof this.deviceData?.uuid !== 'string' ||
|
|
381
|
-
this.deviceData.uuid === ''
|
|
382
|
-
) {
|
|
377
|
+
if (typeof values !== 'object' || this.#eventEmitter === undefined) {
|
|
383
378
|
return;
|
|
384
379
|
}
|
|
385
380
|
|
|
386
381
|
// Send event with data to set
|
|
387
|
-
this.#eventEmitter.emit(HomeKitDevice.SET, this.
|
|
382
|
+
this.#eventEmitter.emit(HomeKitDevice.SET, this.uuid, values);
|
|
388
383
|
|
|
389
384
|
// Update the internal data for the set values, as could take sometime once we emit the event
|
|
390
385
|
Object.entries(values).forEach(([key, value]) => {
|
|
@@ -395,21 +390,16 @@ export default class HomeKitDevice {
|
|
|
395
390
|
}
|
|
396
391
|
|
|
397
392
|
async get(values) {
|
|
398
|
-
if (
|
|
399
|
-
typeof values !== 'object' ||
|
|
400
|
-
this.#eventEmitter === undefined ||
|
|
401
|
-
typeof this.deviceData?.uuid !== 'string' ||
|
|
402
|
-
this.deviceData.uuid === ''
|
|
403
|
-
) {
|
|
393
|
+
if (typeof values !== 'object' || this.#eventEmitter === undefined) {
|
|
404
394
|
return;
|
|
405
395
|
}
|
|
406
396
|
|
|
407
397
|
// Send event with data to get
|
|
408
398
|
// Once get has completed, we'll get an event back with the requested data
|
|
409
|
-
this.#eventEmitter.emit(HomeKitDevice.GET, this.
|
|
399
|
+
this.#eventEmitter.emit(HomeKitDevice.GET, this.uuid, values);
|
|
410
400
|
|
|
411
401
|
// This should always return, but we probably should put in a timeout?
|
|
412
|
-
let results = await EventEmitter.once(this.#eventEmitter, HomeKitDevice.GET + '->' + this.
|
|
402
|
+
let results = await EventEmitter.once(this.#eventEmitter, HomeKitDevice.GET + '->' + this.uuid);
|
|
413
403
|
return results?.[0];
|
|
414
404
|
}
|
|
415
405
|
|
|
@@ -417,8 +407,8 @@ export default class HomeKitDevice {
|
|
|
417
407
|
switch (type) {
|
|
418
408
|
case HomeKitDevice.ADD: {
|
|
419
409
|
// Got message for device add
|
|
420
|
-
if (typeof message?.name === 'string' &&
|
|
421
|
-
this.add(message.name, message.category, message.history);
|
|
410
|
+
if (typeof message?.name === 'string' && isNaN(message?.category) === false && typeof message?.history === 'boolean') {
|
|
411
|
+
this.add(message.name, Number(message.category), message.history);
|
|
422
412
|
}
|
|
423
413
|
break;
|
|
424
414
|
}
|
|
@@ -450,3 +440,36 @@ export default class HomeKitDevice {
|
|
|
450
440
|
}
|
|
451
441
|
}
|
|
452
442
|
}
|
|
443
|
+
|
|
444
|
+
// General helper functions which don't need to be part of an object class
|
|
445
|
+
function crc24(valueToHash) {
|
|
446
|
+
const crc24HashTable = [
|
|
447
|
+
0x000000, 0x864cfb, 0x8ad50d, 0x0c99f6, 0x93e6e1, 0x15aa1a, 0x1933ec, 0x9f7f17, 0xa18139, 0x27cdc2, 0x2b5434, 0xad18cf, 0x3267d8,
|
|
448
|
+
0xb42b23, 0xb8b2d5, 0x3efe2e, 0xc54e89, 0x430272, 0x4f9b84, 0xc9d77f, 0x56a868, 0xd0e493, 0xdc7d65, 0x5a319e, 0x64cfb0, 0xe2834b,
|
|
449
|
+
0xee1abd, 0x685646, 0xf72951, 0x7165aa, 0x7dfc5c, 0xfbb0a7, 0x0cd1e9, 0x8a9d12, 0x8604e4, 0x00481f, 0x9f3708, 0x197bf3, 0x15e205,
|
|
450
|
+
0x93aefe, 0xad50d0, 0x2b1c2b, 0x2785dd, 0xa1c926, 0x3eb631, 0xb8faca, 0xb4633c, 0x322fc7, 0xc99f60, 0x4fd39b, 0x434a6d, 0xc50696,
|
|
451
|
+
0x5a7981, 0xdc357a, 0xd0ac8c, 0x56e077, 0x681e59, 0xee52a2, 0xe2cb54, 0x6487af, 0xfbf8b8, 0x7db443, 0x712db5, 0xf7614e, 0x19a3d2,
|
|
452
|
+
0x9fef29, 0x9376df, 0x153a24, 0x8a4533, 0x0c09c8, 0x00903e, 0x86dcc5, 0xb822eb, 0x3e6e10, 0x32f7e6, 0xb4bb1d, 0x2bc40a, 0xad88f1,
|
|
453
|
+
0xa11107, 0x275dfc, 0xdced5b, 0x5aa1a0, 0x563856, 0xd074ad, 0x4f0bba, 0xc94741, 0xc5deb7, 0x43924c, 0x7d6c62, 0xfb2099, 0xf7b96f,
|
|
454
|
+
0x71f594, 0xee8a83, 0x68c678, 0x645f8e, 0xe21375, 0x15723b, 0x933ec0, 0x9fa736, 0x19ebcd, 0x8694da, 0x00d821, 0x0c41d7, 0x8a0d2c,
|
|
455
|
+
0xb4f302, 0x32bff9, 0x3e260f, 0xb86af4, 0x2715e3, 0xa15918, 0xadc0ee, 0x2b8c15, 0xd03cb2, 0x567049, 0x5ae9bf, 0xdca544, 0x43da53,
|
|
456
|
+
0xc596a8, 0xc90f5e, 0x4f43a5, 0x71bd8b, 0xf7f170, 0xfb6886, 0x7d247d, 0xe25b6a, 0x641791, 0x688e67, 0xeec29c, 0x3347a4, 0xb50b5f,
|
|
457
|
+
0xb992a9, 0x3fde52, 0xa0a145, 0x26edbe, 0x2a7448, 0xac38b3, 0x92c69d, 0x148a66, 0x181390, 0x9e5f6b, 0x01207c, 0x876c87, 0x8bf571,
|
|
458
|
+
0x0db98a, 0xf6092d, 0x7045d6, 0x7cdc20, 0xfa90db, 0x65efcc, 0xe3a337, 0xef3ac1, 0x69763a, 0x578814, 0xd1c4ef, 0xdd5d19, 0x5b11e2,
|
|
459
|
+
0xc46ef5, 0x42220e, 0x4ebbf8, 0xc8f703, 0x3f964d, 0xb9dab6, 0xb54340, 0x330fbb, 0xac70ac, 0x2a3c57, 0x26a5a1, 0xa0e95a, 0x9e1774,
|
|
460
|
+
0x185b8f, 0x14c279, 0x928e82, 0x0df195, 0x8bbd6e, 0x872498, 0x016863, 0xfad8c4, 0x7c943f, 0x700dc9, 0xf64132, 0x693e25, 0xef72de,
|
|
461
|
+
0xe3eb28, 0x65a7d3, 0x5b59fd, 0xdd1506, 0xd18cf0, 0x57c00b, 0xc8bf1c, 0x4ef3e7, 0x426a11, 0xc426ea, 0x2ae476, 0xaca88d, 0xa0317b,
|
|
462
|
+
0x267d80, 0xb90297, 0x3f4e6c, 0x33d79a, 0xb59b61, 0x8b654f, 0x0d29b4, 0x01b042, 0x87fcb9, 0x1883ae, 0x9ecf55, 0x9256a3, 0x141a58,
|
|
463
|
+
0xefaaff, 0x69e604, 0x657ff2, 0xe33309, 0x7c4c1e, 0xfa00e5, 0xf69913, 0x70d5e8, 0x4e2bc6, 0xc8673d, 0xc4fecb, 0x42b230, 0xddcd27,
|
|
464
|
+
0x5b81dc, 0x57182a, 0xd154d1, 0x26359f, 0xa07964, 0xace092, 0x2aac69, 0xb5d37e, 0x339f85, 0x3f0673, 0xb94a88, 0x87b4a6, 0x01f85d,
|
|
465
|
+
0x0d61ab, 0x8b2d50, 0x145247, 0x921ebc, 0x9e874a, 0x18cbb1, 0xe37b16, 0x6537ed, 0x69ae1b, 0xefe2e0, 0x709df7, 0xf6d10c, 0xfa48fa,
|
|
466
|
+
0x7c0401, 0x42fa2f, 0xc4b6d4, 0xc82f22, 0x4e63d9, 0xd11cce, 0x575035, 0x5bc9c3, 0xdd8538,
|
|
467
|
+
];
|
|
468
|
+
|
|
469
|
+
let crc24 = 0xb704ce; // init crc24 hash;
|
|
470
|
+
valueToHash = Buffer.from(valueToHash); // convert value into buffer for processing
|
|
471
|
+
for (let index = 0; index < valueToHash.length; index++) {
|
|
472
|
+
crc24 = (crc24HashTable[((crc24 >> 16) ^ valueToHash[index]) & 0xff] ^ (crc24 << 8)) & 0xffffff;
|
|
473
|
+
}
|
|
474
|
+
return crc24.toString(16); // return crc24 as hex string
|
|
475
|
+
}
|