homebridge-nest-accfactory 0.2.3 → 0.2.9
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 +15 -0
- package/README.md +23 -25
- package/config.schema.json +8 -1
- package/dist/HomeKitHistory.js +3 -3
- package/dist/camera.js +12 -8
- package/dist/doorbell.js +3 -7
- package/dist/protect.js +20 -11
- package/dist/protobuf/root.proto +3 -2
- package/dist/streamer.js +5 -5
- package/dist/system.js +139 -33
- package/dist/webrtc.js +167 -164
- package/package.json +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
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
|
+
## v0.2.8 (2025/03/23)
|
|
6
|
+
|
|
7
|
+
- General code cleanup and bug fixes
|
|
8
|
+
- Support for Nest Protect(s) in Google Home app
|
|
9
|
+
- Default location to check for ffmpeg binary is now /usr/local/bin
|
|
10
|
+
- Logs Nest Protect(s) self testing status
|
|
11
|
+
|
|
12
|
+
## v0.2.5 (2024/12/10)
|
|
13
|
+
|
|
14
|
+
- Fix for dropped sub modules.. Do not know why!
|
|
15
|
+
|
|
16
|
+
## v0.2.4 (2024/12/10)
|
|
17
|
+
|
|
18
|
+
- Fix for camera video stream when audio disabled
|
|
19
|
+
|
|
5
20
|
## v0.2.3 (2024/12/06)
|
|
6
21
|
|
|
7
22
|
- General code cleanup and bug fixes
|
package/README.md
CHANGED
|
@@ -95,38 +95,36 @@ Sample config.json entries below
|
|
|
95
95
|
|
|
96
96
|
The following options are available in the config.json options object. These apply to all discovered devices.
|
|
97
97
|
|
|
98
|
-
| Name | Description | Default
|
|
99
|
-
|
|
100
|
-
| elevation | Height above sea level for the weather station | 0
|
|
101
|
-
| eveHistory | Provide history in EveHome application where applicable | true
|
|
102
|
-
| ffmegDebug | Turns on specific debugging output for when ffmpeg is envoked | false
|
|
103
|
-
| ffmegPath | Path to an ffmpeg binary for us to use
|
|
104
|
-
| hksv | Enable HomeKit Secure Video for supported camera(s) and doorbell(s) | false
|
|
105
|
-
| maxStreams | Maximum number of concurrent video streams in HomeKit for supported camera(s) and doorbell(s) | 2
|
|
106
|
-
| weather | Virtual weather station for each Nest/Google home we discover | false
|
|
98
|
+
| Name | Description | Default |
|
|
99
|
+
|-------------------|-----------------------------------------------------------------------------------------------|----------------|
|
|
100
|
+
| elevation | Height above sea level for the weather station | 0 |
|
|
101
|
+
| eveHistory | Provide history in EveHome application where applicable | true |
|
|
102
|
+
| ffmegDebug | Turns on specific debugging output for when ffmpeg is envoked | false |
|
|
103
|
+
| ffmegPath | Path to an ffmpeg binary for us to use | /usr/local/bin |
|
|
104
|
+
| hksv | Enable HomeKit Secure Video for supported camera(s) and doorbell(s) | false |
|
|
105
|
+
| maxStreams | Maximum number of concurrent video streams in HomeKit for supported camera(s) and doorbell(s) | 2 |
|
|
106
|
+
| weather | Virtual weather station for each Nest/Google home we discover | false |
|
|
107
107
|
|
|
108
108
|
#### devices
|
|
109
109
|
|
|
110
110
|
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)
|
|
111
111
|
|
|
112
|
-
| Name | Description | Default
|
|
113
|
-
|
|
114
|
-
| chimeSwitch | Create a switch for supported doorbell(s) which allows the indoor chime to be turned on/off | false
|
|
115
|
-
| doorbellCooldown | Time in seconds between doorbell press events | 60
|
|
116
|
-
| elevation | Height above sea level for the specific weather station | 0
|
|
117
|
-
| eveHistory | Provide history in EveHome application where applicable for the specific device | true
|
|
118
|
-
| exclude | Exclude the device | false
|
|
119
|
-
| ffmegDebug | Turns on specific debugging output for when ffmpeg is envoked | false
|
|
120
|
-
| hksv | Enable HomeKit Secure Video for supported camera(s) and doorbell(s) | false
|
|
121
|
-
| humiditySensor | Create a seperate humidity sensor for supported thermostat(s) | false
|
|
122
|
-
| localAccess | Use direct access to supported camera(s) and doorbell(s) for video streaming and recording | false
|
|
123
|
-
| motionCooldown | Time in seconds between detected motion events | 60
|
|
124
|
-
| personCooldown | Time in seconds between detected person events | 120
|
|
112
|
+
| Name | Description | Default |
|
|
113
|
+
|-------------------|-----------------------------------------------------------------------------------------------|----------------|
|
|
114
|
+
| chimeSwitch | Create a switch for supported doorbell(s) which allows the indoor chime to be turned on/off | false |
|
|
115
|
+
| doorbellCooldown | Time in seconds between doorbell press events | 60 |
|
|
116
|
+
| elevation | Height above sea level for the specific weather station | 0 |
|
|
117
|
+
| eveHistory | Provide history in EveHome application where applicable for the specific device | true |
|
|
118
|
+
| exclude | Exclude the device | false |
|
|
119
|
+
| ffmegDebug | Turns on specific debugging output for when ffmpeg is envoked | false |
|
|
120
|
+
| hksv | Enable HomeKit Secure Video for supported camera(s) and doorbell(s) | false |
|
|
121
|
+
| humiditySensor | Create a seperate humidity sensor for supported thermostat(s) | false |
|
|
122
|
+
| localAccess | Use direct access to supported camera(s) and doorbell(s) for video streaming and recording | false |
|
|
123
|
+
| motionCooldown | Time in seconds between detected motion events | 60 |
|
|
124
|
+
| personCooldown | Time in seconds between detected person events | 120 |
|
|
125
125
|
|
|
126
126
|
## ffmpeg
|
|
127
127
|
|
|
128
|
-
**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**
|
|
129
|
-
|
|
130
128
|
To support streaming and recording from cameras, an ffmpeg binary needs to be present. We have specific requirements, which are:
|
|
131
129
|
- version 6.0 or later
|
|
132
130
|
- compiled with:
|
|
@@ -135,7 +133,7 @@ To support streaming and recording from cameras, an ffmpeg binary needs to be pr
|
|
|
135
133
|
- libspeex
|
|
136
134
|
- libopus
|
|
137
135
|
|
|
138
|
-
By default, we look in
|
|
136
|
+
By default, we look in /usr/local/bin for an ffmpeg binary, however, you can specify a specific ffmpeg binary to use via the configuration option 'ffmpegPath'
|
|
139
137
|
|
|
140
138
|
## Disclaimer
|
|
141
139
|
|
package/config.schema.json
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
"schema": {
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
8
|
+
"name": {
|
|
9
|
+
"title": "Name",
|
|
10
|
+
"type": "string",
|
|
11
|
+
"default": "NestAccfactory",
|
|
12
|
+
"condition": "1=2"
|
|
13
|
+
},
|
|
8
14
|
"nest": {
|
|
9
15
|
"title": "Nest Account",
|
|
10
16
|
"type": "object",
|
|
@@ -90,7 +96,8 @@
|
|
|
90
96
|
"ffmpegPath": {
|
|
91
97
|
"title": "Path to ffmpeg binary",
|
|
92
98
|
"type": "string",
|
|
93
|
-
"placeholder": "Path to an ffmpeg binary
|
|
99
|
+
"placeholder": "Path to an ffmpeg binary",
|
|
100
|
+
"default": "/usr/local/bin/ffmpeg"
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
103
|
}
|
package/dist/HomeKitHistory.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Credit to https://github.com/simont77/fakegato-history for the work on starting the EveHome comms protocol decoding
|
|
12
12
|
//
|
|
13
|
-
// Version
|
|
13
|
+
// Version 2025/18/01
|
|
14
14
|
// Mark Hulskamp
|
|
15
15
|
|
|
16
16
|
// Define nodejs module requirements
|
|
@@ -1169,7 +1169,7 @@ export default class HomeKitHistory {
|
|
|
1169
1169
|
firmware: typeof options?.EveSmoke_firmware === 'number' ? options.EveSmoke_firmware : 1208, // Firmware version
|
|
1170
1170
|
lastalarmtest: typeof options?.EveSmoke_lastalarmtest === 'number' ? options.EveSmoke_lastalarmtest : 0, // Seconds of alarm test
|
|
1171
1171
|
alarmtest: options?.EveSmoke_alarmtest === true, // Is alarmtest running
|
|
1172
|
-
heatstatus:
|
|
1172
|
+
heatstatus: options.EveSmoke_heatstatus === true, // Heat sensor status
|
|
1173
1173
|
statusled: options?.EveSmoke_statusled === false, // Status LED flash/enabled
|
|
1174
1174
|
smoketestpassed: options?.EveSmoke_smoketestpassed === false, // Passed smoke test?
|
|
1175
1175
|
heattestpassed: options?.EveSmoke_heattestpassed === false, // Passed smoke test?
|
|
@@ -2009,7 +2009,7 @@ export default class HomeKitHistory {
|
|
|
2009
2009
|
) {
|
|
2010
2010
|
value |= 1 << 0; // 1st bit, smoke detected
|
|
2011
2011
|
}
|
|
2012
|
-
if (this.EveSmokePersist.heatstatus
|
|
2012
|
+
if (this.EveSmokePersist.heatstatus === true) {
|
|
2013
2013
|
value |= 1 << 1; // 2th bit - heat detected
|
|
2014
2014
|
}
|
|
2015
2015
|
if (this.EveSmokePersist.alarmtest === true) {
|
package/dist/camera.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Nest Cameras
|
|
2
2
|
// Part of homebridge-nest-accfactory
|
|
3
3
|
//
|
|
4
|
-
// Code version
|
|
4
|
+
// Code version 2025/03/19
|
|
5
5
|
// Mark Hulskamp
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
@@ -71,15 +71,19 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// Class functions
|
|
74
|
-
addServices() {
|
|
74
|
+
addServices(hapController = this.hap.CameraController) {
|
|
75
75
|
// Setup motion services
|
|
76
76
|
if (this.motionServices === undefined) {
|
|
77
77
|
this.createCameraMotionServices();
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
// Setup HomeKit camera controller
|
|
81
|
-
if (this.controller === undefined) {
|
|
82
|
-
|
|
80
|
+
// Setup HomeKit camera/doorbell controller
|
|
81
|
+
if (this.controller === undefined && typeof hapController === 'function') {
|
|
82
|
+
// Need to cleanup the CameraOperatingMode service. This is to allow seamless configuration
|
|
83
|
+
// switching between enabling hksv or not
|
|
84
|
+
// Thanks to @bcullman (Brad Ullman) for catching this
|
|
85
|
+
this.accessory.removeService(this.accessory.getService(this.hap.Service.CameraOperatingMode));
|
|
86
|
+
this.controller = new hapController(this.generateControllerOptions());
|
|
83
87
|
this.accessory.configureController(this.controller);
|
|
84
88
|
}
|
|
85
89
|
|
|
@@ -87,7 +91,7 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
87
91
|
this.operatingModeService = this.controller?.recordingManagement?.operatingModeService;
|
|
88
92
|
if (this.operatingModeService === undefined) {
|
|
89
93
|
// Add in operating mode service for a non-hksv camera/doorbell
|
|
90
|
-
// Allow us to change things such as night vision, camera indicator etc within HomeKit for those also:-)
|
|
94
|
+
// Allow us to change things such as night vision, camera indicator etc within HomeKit for those also :-)
|
|
91
95
|
this.operatingModeService = this.accessory.getService(this.hap.Service.CameraOperatingMode);
|
|
92
96
|
if (this.operatingModeService === undefined) {
|
|
93
97
|
this.operatingModeService = this.accessory.addService(this.hap.Service.CameraOperatingMode, '', 1);
|
|
@@ -414,7 +418,7 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
414
418
|
);
|
|
415
419
|
let ffmpegRecording = child_process.spawn(this.deviceData.ffmpeg.binary, commandLine.join(' ').split(' '), {
|
|
416
420
|
env: process.env,
|
|
417
|
-
stdio: ['pipe', 'pipe', 'pipe',
|
|
421
|
+
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
|
418
422
|
});
|
|
419
423
|
|
|
420
424
|
// Process FFmpeg output and parse out the fMP4 stream it's generating for HomeKit Secure Video.
|
|
@@ -806,7 +810,7 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
806
810
|
);
|
|
807
811
|
let ffmpegStreaming = child_process.spawn(this.deviceData.ffmpeg.binary, commandLine.join(' ').split(' '), {
|
|
808
812
|
env: process.env,
|
|
809
|
-
stdio: ['pipe', 'pipe', 'pipe',
|
|
813
|
+
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
|
810
814
|
});
|
|
811
815
|
|
|
812
816
|
ffmpegStreaming.on('exit', (code, signal) => {
|
package/dist/doorbell.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Nest Doorbell(s)
|
|
2
2
|
// Part of homebridge-nest-accfactory
|
|
3
3
|
//
|
|
4
|
-
// Code version
|
|
4
|
+
// Code version 2025/03/19
|
|
5
5
|
// Mark Hulskamp
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
@@ -21,13 +21,9 @@ export default class NestDoorbell extends NestCamera {
|
|
|
21
21
|
|
|
22
22
|
// Class functions
|
|
23
23
|
addServices() {
|
|
24
|
-
// Setup some details around the doorbell BEFORE will call out parent addServices function
|
|
25
|
-
this.createCameraMotionServices();
|
|
26
|
-
this.controller = new this.hap.DoorbellController(this.generateControllerOptions());
|
|
27
|
-
this.accessory.configureController(this.controller);
|
|
28
|
-
|
|
29
24
|
// Call parent to setup the common camera things. Once we return, we can add in the specifics for our doorbell
|
|
30
|
-
|
|
25
|
+
// We pass in the HAP Doorbell controller constructor function here also
|
|
26
|
+
let postSetupDetails = super.addServices(this.hap.DoorbellController);
|
|
31
27
|
|
|
32
28
|
this.switchService = this.accessory.getService(this.hap.Service.Switch);
|
|
33
29
|
if (this.deviceData.has_indoor_chime === true && this.deviceData.chimeSwitch === true) {
|
package/dist/protect.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Nest Protect
|
|
2
2
|
// Part of homebridge-nest-accfactory
|
|
3
3
|
//
|
|
4
|
-
// Code version
|
|
4
|
+
// Code version 2025/01/17
|
|
5
5
|
// Mark Hulskamp
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
@@ -74,7 +74,7 @@ export default class NestProtect extends HomeKitDevice {
|
|
|
74
74
|
EveSmoke_alarmtest: this.deviceData.self_test_in_progress,
|
|
75
75
|
EveSmoke_heatstatus: this.deviceData.heat_status,
|
|
76
76
|
EveSmoke_hushedstate: this.deviceData.hushed_state,
|
|
77
|
-
|
|
77
|
+
Evesmoke_statusled: this.deviceData.ntp_green_led_enable,
|
|
78
78
|
EveSmoke_smoketestpassed: this.deviceData.smoke_test_passed,
|
|
79
79
|
EveSmoke_heattestpassed: this.deviceData.heat_test_passed,
|
|
80
80
|
});
|
|
@@ -103,50 +103,59 @@ export default class NestProtect extends HomeKitDevice {
|
|
|
103
103
|
);
|
|
104
104
|
|
|
105
105
|
// Update smoke details
|
|
106
|
-
// If protect isn't online,
|
|
106
|
+
// If protect isn't online, replacement date past, report in HomeKit
|
|
107
107
|
this.smokeService.updateCharacteristic(
|
|
108
108
|
this.hap.Characteristic.StatusActive,
|
|
109
|
-
deviceData.online === true &&
|
|
109
|
+
deviceData.online === true && Math.floor(Date.now() / 1000) <= deviceData.replacement_date,
|
|
110
110
|
);
|
|
111
111
|
|
|
112
112
|
this.smokeService.updateCharacteristic(
|
|
113
113
|
this.hap.Characteristic.StatusFault,
|
|
114
|
-
deviceData.online === true &&
|
|
114
|
+
deviceData.online === true && Math.floor(Date.now() / 1000) <= deviceData.replacement_date
|
|
115
115
|
? this.hap.Characteristic.StatusFault.NO_FAULT
|
|
116
116
|
: this.hap.Characteristic.StatusFault.GENERAL_FAULT,
|
|
117
117
|
);
|
|
118
118
|
|
|
119
119
|
this.smokeService.updateCharacteristic(
|
|
120
120
|
this.hap.Characteristic.SmokeDetected,
|
|
121
|
-
deviceData.smoke_status ===
|
|
121
|
+
deviceData.smoke_status === true
|
|
122
122
|
? this.hap.Characteristic.SmokeDetected.SMOKE_DETECTED
|
|
123
123
|
: this.hap.Characteristic.SmokeDetected.SMOKE_NOT_DETECTED,
|
|
124
124
|
);
|
|
125
125
|
|
|
126
|
-
if (deviceData.smoke_status
|
|
126
|
+
if (deviceData.smoke_status === true && this.deviceData.smoke_status === false) {
|
|
127
127
|
this?.log?.warn && this.log.warn('Smoke detected in "%s"', deviceData.description);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
if (deviceData.smoke_status ===
|
|
130
|
+
if (deviceData.smoke_status === false && this.deviceData.smoke_status === true) {
|
|
131
131
|
this?.log?.info && this.log.info('Smoke is nolonger detected in "%s"', deviceData.description);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// Update carbon monoxide details
|
|
135
135
|
this.carbonMonoxideService.updateCharacteristic(
|
|
136
136
|
this.hap.Characteristic.CarbonMonoxideDetected,
|
|
137
|
-
deviceData.co_status
|
|
137
|
+
deviceData.co_status === true
|
|
138
138
|
? this.hap.Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL
|
|
139
139
|
: this.hap.Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL,
|
|
140
140
|
);
|
|
141
141
|
|
|
142
|
-
if (deviceData.co_status
|
|
142
|
+
if (deviceData.co_status === true && this.deviceData.co_status === false) {
|
|
143
143
|
this?.log?.warn && this.log.warn('Abnormal carbon monoxide levels detected in "%s"', deviceData.description);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
if (deviceData.co_status ===
|
|
146
|
+
if (deviceData.co_status === false && this.deviceData.co_status === true) {
|
|
147
147
|
this?.log?.info && this.log.info('Carbon monoxide levels have returned to normal in "%s"', deviceData.description);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
// Update self testing details
|
|
151
|
+
if (deviceData.self_test_in_progress === true && this.deviceData.self_test_in_progress === false) {
|
|
152
|
+
this?.log?.warn && this.log.info('Smoke and Carbon monoxide sensor testing has started in "%s"', deviceData.description);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (deviceData.self_test_in_progress === false && this.deviceData.self_test_in_progress === true) {
|
|
156
|
+
this?.log?.info && this.log.info('Smoke and Carbon monoxide sensor testing completed in "%s"', deviceData.description);
|
|
157
|
+
}
|
|
158
|
+
|
|
150
159
|
// Update motion service if present
|
|
151
160
|
if (this.motionService !== undefined) {
|
|
152
161
|
this.motionService.updateCharacteristic(this.hap.Characteristic.MotionDetected, deviceData.detected_motion === true);
|
package/dist/protobuf/root.proto
CHANGED
|
@@ -3,11 +3,11 @@ syntax = "proto3";
|
|
|
3
3
|
import "google/trait/product/camera.proto";
|
|
4
4
|
import "nest/trait/audio.proto";
|
|
5
5
|
import "nest/trait/detector.proto";
|
|
6
|
-
import "nest/trait/hvac.proto";
|
|
7
6
|
import "nest/trait/humanlibrary.proto";
|
|
8
7
|
import "nest/trait/history.proto";
|
|
9
|
-
import "nest/trait/
|
|
8
|
+
import "nest/trait/hvac.proto";
|
|
10
9
|
import "nest/trait/input.proto";
|
|
10
|
+
import "nest/trait/lighting.proto";
|
|
11
11
|
import "nest/trait/located.proto";
|
|
12
12
|
import "nest/trait/media.proto";
|
|
13
13
|
import "nest/trait/network.proto";
|
|
@@ -22,6 +22,7 @@ import "nest/trait/sensor.proto";
|
|
|
22
22
|
import "nest/trait/service.proto";
|
|
23
23
|
import "nest/trait/structure.proto";
|
|
24
24
|
import "nest/trait/ui.proto";
|
|
25
|
+
import "nest/trait/user.proto";
|
|
25
26
|
import "weave/common.proto";
|
|
26
27
|
import "weave/trait/actuator.proto";
|
|
27
28
|
import "weave/trait/audio.proto";
|
package/dist/streamer.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
//
|
|
18
18
|
// blankAudio - Buffer containing a blank audio segment for the type of audio being used
|
|
19
19
|
//
|
|
20
|
-
// Code version
|
|
20
|
+
// Code version 2025/03/16
|
|
21
21
|
// Mark Hulskamp
|
|
22
22
|
'use strict';
|
|
23
23
|
|
|
@@ -162,7 +162,7 @@ export default class Streamer {
|
|
|
162
162
|
startBuffering() {
|
|
163
163
|
if (this.#outputs?.buffer === undefined) {
|
|
164
164
|
// No active buffer session, start connection to streamer
|
|
165
|
-
if (this.connected === undefined && typeof this.connect === 'function') {
|
|
165
|
+
if (this.online === true && this.videoEnabled === true && this.connected === undefined && typeof this.connect === 'function') {
|
|
166
166
|
this?.log?.debug && this.log.debug('Started buffering for uuid "%s"', this.uuid);
|
|
167
167
|
this.connect();
|
|
168
168
|
}
|
|
@@ -209,7 +209,7 @@ export default class Streamer {
|
|
|
209
209
|
});
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
if (this.connected === undefined && typeof this.connect === 'function') {
|
|
212
|
+
if (this.online === true && this.videoEnabled === true && this.connected === undefined && typeof this.connect === 'function') {
|
|
213
213
|
// We do not have an active connection, so startup connection
|
|
214
214
|
this.connect();
|
|
215
215
|
}
|
|
@@ -247,7 +247,7 @@ export default class Streamer {
|
|
|
247
247
|
});
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
if (this.connected === undefined && typeof this.connect === 'function') {
|
|
250
|
+
if (this.connected === undefined && typeof this.connect === 'function' && this.online === true && this.videoEnabled === true) {
|
|
251
251
|
// We do not have an active connection, so startup connection
|
|
252
252
|
this.connect();
|
|
253
253
|
}
|
|
@@ -328,7 +328,7 @@ export default class Streamer {
|
|
|
328
328
|
if (typeof this.close === 'function') {
|
|
329
329
|
this.close();
|
|
330
330
|
}
|
|
331
|
-
if (typeof this.connect === 'function') {
|
|
331
|
+
if (this.online === true && this.videoEnabled === true && this.connected === undefined && typeof this.connect === 'function') {
|
|
332
332
|
this.connect();
|
|
333
333
|
}
|
|
334
334
|
}
|
package/dist/system.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Nest System communications
|
|
2
2
|
// Part of homebridge-nest-accfactory
|
|
3
3
|
//
|
|
4
|
-
// Code version
|
|
4
|
+
// Code version 2025/03/20
|
|
5
5
|
// Mark Hulskamp
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
@@ -129,6 +129,10 @@ export default class NestAccfactory {
|
|
|
129
129
|
this.config.options.weather = this.config.options?.weather === true;
|
|
130
130
|
this.config.options.hksv = this.config.options?.hksv === true;
|
|
131
131
|
|
|
132
|
+
// Controls what APIs we use, default is to use both REST and protobuf APIs
|
|
133
|
+
this.config.options.restAPI = this.config.options?.restAPI === true || this.config.options?.restAPI === undefined;
|
|
134
|
+
this.config.options.protobufAPI = this.config.options?.protobufAPI === true || this.config.options?.protobufAPI === undefined;
|
|
135
|
+
|
|
132
136
|
// Get configuration for max number of concurrent 'live view' streams. For HomeKit Secure Video, this will always be 1
|
|
133
137
|
this.config.options.maxStreams =
|
|
134
138
|
isNaN(this.config.options?.maxStreams) === false && this.deviceData?.hksv === false
|
|
@@ -137,16 +141,13 @@ export default class NestAccfactory {
|
|
|
137
141
|
? 1
|
|
138
142
|
: 2;
|
|
139
143
|
|
|
140
|
-
// Check if a ffmpeg binary
|
|
141
|
-
// If using HomeBridge, the default path will be where the Homebridge user folder is, otherwise the current directory
|
|
144
|
+
// Check if a ffmpeg binary exist via a specific path in configuration OR /usr/local/bin
|
|
142
145
|
this.config.options.ffmpeg = {};
|
|
143
146
|
this.config.options.ffmpeg.debug = this.config.options?.ffmpegDebug === true;
|
|
144
147
|
this.config.options.ffmpeg.binary = path.resolve(
|
|
145
148
|
typeof this.config.options?.ffmpegPath === 'string' && this.config.options.ffmpegPath !== ''
|
|
146
149
|
? this.config.options.ffmpegPath
|
|
147
|
-
:
|
|
148
|
-
? api.user.storagePath()
|
|
149
|
-
: __dirname,
|
|
150
|
+
: '/usr/local/bin',
|
|
150
151
|
);
|
|
151
152
|
|
|
152
153
|
// If the path doesn't include 'ffmpeg' on the end, we'll add it here
|
|
@@ -170,6 +171,7 @@ export default class NestAccfactory {
|
|
|
170
171
|
this.config.options.ffmpeg.binary = undefined;
|
|
171
172
|
}
|
|
172
173
|
|
|
174
|
+
// Process ffmpeg binary to see if we can use it
|
|
173
175
|
if (fs.existsSync(this.config.options.ffmpeg.binary) === true) {
|
|
174
176
|
let ffmpegProcess = child_process.spawnSync(this.config.options.ffmpeg.binary, ['-version'], {
|
|
175
177
|
env: process.env,
|
|
@@ -186,7 +188,11 @@ export default class NestAccfactory {
|
|
|
186
188
|
this.config.options.ffmpeg.libx264 = ffmpegProcess.stdout.toString().includes('--enable-libx264') === true;
|
|
187
189
|
this.config.options.ffmpeg.libfdk_aac = ffmpegProcess.stdout.toString().includes('--enable-libfdk-aac') === true;
|
|
188
190
|
if (
|
|
189
|
-
this.config.options.ffmpeg.version.
|
|
191
|
+
this.config.options.ffmpeg.version.localeCompare(FFMPEGVERSION, undefined, {
|
|
192
|
+
numeric: true,
|
|
193
|
+
sensitivity: 'case',
|
|
194
|
+
caseFirst: 'upper',
|
|
195
|
+
}) === -1 ||
|
|
190
196
|
this.config.options.ffmpeg.libspeex === false ||
|
|
191
197
|
this.config.options.ffmpeg.libopus === false ||
|
|
192
198
|
this.config.options.ffmpeg.libx264 === false ||
|
|
@@ -194,7 +200,13 @@ export default class NestAccfactory {
|
|
|
194
200
|
) {
|
|
195
201
|
this?.log?.warn &&
|
|
196
202
|
this.log.warn('ffmpeg binary "%s" does not meet the minimum support requirements', this.config.options.ffmpeg.binary);
|
|
197
|
-
if (
|
|
203
|
+
if (
|
|
204
|
+
this.config.options.ffmpeg.version.localeCompare(FFMPEGVERSION, undefined, {
|
|
205
|
+
numeric: true,
|
|
206
|
+
sensitivity: 'case',
|
|
207
|
+
caseFirst: 'upper',
|
|
208
|
+
}) === -1
|
|
209
|
+
) {
|
|
198
210
|
this?.log?.warn &&
|
|
199
211
|
this.log.warn(
|
|
200
212
|
'Minimum binary version is "%s", however the installed version is "%s"',
|
|
@@ -243,6 +255,10 @@ export default class NestAccfactory {
|
|
|
243
255
|
}
|
|
244
256
|
}
|
|
245
257
|
|
|
258
|
+
if (this.config.options.ffmpeg.binary !== undefined) {
|
|
259
|
+
this?.log?.success && this.log.success('Found valid ffmpeg binary in %s', this.config.options.ffmpeg.binary);
|
|
260
|
+
}
|
|
261
|
+
|
|
246
262
|
if (this.api instanceof EventEmitter === true) {
|
|
247
263
|
this.api.on('didFinishLaunching', async () => {
|
|
248
264
|
// We got notified that Homebridge has finished loading, so we are ready to process
|
|
@@ -287,7 +303,7 @@ export default class NestAccfactory {
|
|
|
287
303
|
}
|
|
288
304
|
|
|
289
305
|
configureAccessory(accessory) {
|
|
290
|
-
// This gets called from
|
|
306
|
+
// This gets called from Homebridge each time it restores an accessory from its cache
|
|
291
307
|
this?.log?.info && this.log.info('Loading accessory from cache:', accessory.displayName);
|
|
292
308
|
|
|
293
309
|
// add the restored accessory to the accessories cache, so we can track if it has already been registered
|
|
@@ -298,8 +314,10 @@ export default class NestAccfactory {
|
|
|
298
314
|
Object.keys(this.#connections).forEach((uuid) => {
|
|
299
315
|
if (this.#connections[uuid].authorised === false) {
|
|
300
316
|
this.#connect(uuid).then(() => {
|
|
301
|
-
if (this.#connections[uuid].authorised === true) {
|
|
317
|
+
if (this.#connections[uuid].authorised === true && this.config.options?.restAPI === true) {
|
|
302
318
|
this.#subscribeREST(uuid, true);
|
|
319
|
+
}
|
|
320
|
+
if (this.#connections[uuid].authorised === true && this.config.options?.protobufAPI === true) {
|
|
303
321
|
this.#subscribeProtobuf(uuid, true);
|
|
304
322
|
}
|
|
305
323
|
});
|
|
@@ -701,11 +719,13 @@ export default class NestAccfactory {
|
|
|
701
719
|
});
|
|
702
720
|
|
|
703
721
|
// Send removed notice onto HomeKit device for it to process
|
|
704
|
-
this.#eventEmitter
|
|
705
|
-
this.#
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
722
|
+
if (this.#eventEmitter !== undefined) {
|
|
723
|
+
this.#eventEmitter.emit(
|
|
724
|
+
this.#trackedDevices[this.#rawData[object_key].value.serial_number].uuid,
|
|
725
|
+
HomeKitDevice.REMOVE,
|
|
726
|
+
{},
|
|
727
|
+
);
|
|
728
|
+
}
|
|
709
729
|
|
|
710
730
|
// Finally, remove from tracked devices
|
|
711
731
|
delete this.#trackedDevices[this.#rawData[object_key].value.serial_number];
|
|
@@ -909,11 +929,13 @@ export default class NestAccfactory {
|
|
|
909
929
|
}
|
|
910
930
|
|
|
911
931
|
// Send removed notice onto HomeKit device for it to process
|
|
912
|
-
this.#eventEmitter
|
|
913
|
-
this.#
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
932
|
+
if (this.#eventEmitter !== undefined) {
|
|
933
|
+
this.#eventEmitter.emit(
|
|
934
|
+
this.#trackedDevices[this.#rawData[resource.resourceId].value.device_identity.serialNumber].uuid,
|
|
935
|
+
HomeKitDevice.REMOVE,
|
|
936
|
+
{},
|
|
937
|
+
);
|
|
938
|
+
}
|
|
917
939
|
|
|
918
940
|
// Finally, remove from tracked devices
|
|
919
941
|
delete this.#trackedDevices[this.#rawData[resource.resourceId].value.device_identity.serialNumber];
|
|
@@ -972,6 +994,7 @@ export default class NestAccfactory {
|
|
|
972
994
|
this.log.debug('Error was "%s"', error);
|
|
973
995
|
}
|
|
974
996
|
})
|
|
997
|
+
|
|
975
998
|
.finally(() => {
|
|
976
999
|
this?.log?.debug && this.log.debug('Restarting Protobuf API trait observe for connection uuid "%s"', connectionUUID);
|
|
977
1000
|
setTimeout(this.#subscribeProtobuf.bind(this, connectionUUID, false), 1000);
|
|
@@ -983,7 +1006,16 @@ export default class NestAccfactory {
|
|
|
983
1006
|
if (this.#trackedDevices?.[deviceData?.serialNumber] === undefined && deviceData?.excluded === true) {
|
|
984
1007
|
// We haven't tracked this device before (ie: should be a new one) and but its excluded
|
|
985
1008
|
this?.log?.warn && this.log.warn('Device "%s" is ignored due to it being marked as excluded', deviceData.description);
|
|
1009
|
+
|
|
1010
|
+
// Track this device even though its excluded
|
|
1011
|
+
this.#trackedDevices[deviceData.serialNumber] = {
|
|
1012
|
+
uuid: undefined,
|
|
1013
|
+
rawDataUuid: deviceData.nest_google_uuid,
|
|
1014
|
+
source: undefined,
|
|
1015
|
+
exclude: true,
|
|
1016
|
+
};
|
|
986
1017
|
}
|
|
1018
|
+
|
|
987
1019
|
if (this.#trackedDevices?.[deviceData?.serialNumber] === undefined && deviceData?.excluded === false) {
|
|
988
1020
|
// We haven't tracked this device before (ie: should be a new one) and its not excluded
|
|
989
1021
|
// so create the required HomeKit accessories based upon the device data
|
|
@@ -1097,7 +1129,7 @@ export default class NestAccfactory {
|
|
|
1097
1129
|
this.#rawData[nest_google_uuid].value.activity_zones = zones;
|
|
1098
1130
|
|
|
1099
1131
|
// Send updated data onto HomeKit device for it to process
|
|
1100
|
-
if (this.#trackedDevices?.[deviceData?.serialNumber]?.uuid !== undefined) {
|
|
1132
|
+
if (this.#eventEmitter !== undefined && this.#trackedDevices?.[deviceData?.serialNumber]?.uuid !== undefined) {
|
|
1101
1133
|
this.#eventEmitter.emit(this.#trackedDevices[deviceData.serialNumber].uuid, HomeKitDevice.UPDATE, {
|
|
1102
1134
|
activity_zones: zones,
|
|
1103
1135
|
});
|
|
@@ -1258,7 +1290,7 @@ export default class NestAccfactory {
|
|
|
1258
1290
|
this.#rawData[nest_google_uuid].value.alerts = alerts;
|
|
1259
1291
|
|
|
1260
1292
|
// Send updated alerts onto HomeKit device for it to process
|
|
1261
|
-
if (this.#trackedDevices?.[deviceData?.serialNumber]?.uuid !== undefined) {
|
|
1293
|
+
if (this.#eventEmitter !== undefined && this.#trackedDevices?.[deviceData?.serialNumber]?.uuid !== undefined) {
|
|
1262
1294
|
this.#eventEmitter.emit(this.#trackedDevices[deviceData.serialNumber].uuid, HomeKitDevice.UPDATE, {
|
|
1263
1295
|
alerts: alerts,
|
|
1264
1296
|
});
|
|
@@ -1292,7 +1324,7 @@ export default class NestAccfactory {
|
|
|
1292
1324
|
);
|
|
1293
1325
|
|
|
1294
1326
|
// Send updated weather data onto HomeKit device for it to process
|
|
1295
|
-
if (this.#trackedDevices?.[deviceData?.serialNumber]?.uuid !== undefined) {
|
|
1327
|
+
if (this.#eventEmitter !== undefined && this.#trackedDevices?.[deviceData?.serialNumber]?.uuid !== undefined) {
|
|
1296
1328
|
this.#eventEmitter.emit(
|
|
1297
1329
|
this.#trackedDevices[deviceData.serialNumber].uuid,
|
|
1298
1330
|
HomeKitDevice.UPDATE,
|
|
@@ -1323,7 +1355,9 @@ export default class NestAccfactory {
|
|
|
1323
1355
|
this.#trackedDevices[deviceData.serialNumber].rawDataUuid = deviceData.nest_google_uuid;
|
|
1324
1356
|
}
|
|
1325
1357
|
|
|
1326
|
-
this.#eventEmitter
|
|
1358
|
+
if (this.#eventEmitter !== undefined) {
|
|
1359
|
+
this.#eventEmitter.emit(this.#trackedDevices[deviceData.serialNumber].uuid, HomeKitDevice.UPDATE, deviceData);
|
|
1360
|
+
}
|
|
1327
1361
|
}
|
|
1328
1362
|
});
|
|
1329
1363
|
}
|
|
@@ -1458,7 +1492,11 @@ export default class NestAccfactory {
|
|
|
1458
1492
|
.forEach(([object_key, value]) => {
|
|
1459
1493
|
let tempDevice = {};
|
|
1460
1494
|
try {
|
|
1461
|
-
if (
|
|
1495
|
+
if (
|
|
1496
|
+
value?.source === NestAccfactory.DataSource.PROTOBUF &&
|
|
1497
|
+
this.config.options?.protobufAPI === true &&
|
|
1498
|
+
value.value?.configuration_done?.deviceReady === true
|
|
1499
|
+
) {
|
|
1462
1500
|
let RESTTypeData = {};
|
|
1463
1501
|
RESTTypeData.serialNumber = value.value.device_identity.serialNumber;
|
|
1464
1502
|
RESTTypeData.softwareVersion =
|
|
@@ -1688,7 +1726,11 @@ export default class NestAccfactory {
|
|
|
1688
1726
|
tempDevice = process_thermostat_data(object_key, RESTTypeData);
|
|
1689
1727
|
}
|
|
1690
1728
|
|
|
1691
|
-
if (
|
|
1729
|
+
if (
|
|
1730
|
+
value?.source === NestAccfactory.DataSource.REST &&
|
|
1731
|
+
this.config.options?.restAPI === true &&
|
|
1732
|
+
value.value?.where_id !== undefined
|
|
1733
|
+
) {
|
|
1692
1734
|
let RESTTypeData = {};
|
|
1693
1735
|
RESTTypeData.serialNumber = value.value.serial_number;
|
|
1694
1736
|
RESTTypeData.softwareVersion = value.value.current_version;
|
|
@@ -1953,6 +1995,7 @@ export default class NestAccfactory {
|
|
|
1953
1995
|
try {
|
|
1954
1996
|
if (
|
|
1955
1997
|
value?.source === NestAccfactory.DataSource.PROTOBUF &&
|
|
1998
|
+
this.config.options?.protobufAPI === true &&
|
|
1956
1999
|
value.value?.configuration_done?.deviceReady === true &&
|
|
1957
2000
|
typeof value?.value?.associated_thermostat === 'string' &&
|
|
1958
2001
|
value?.value?.associated_thermostat !== ''
|
|
@@ -1977,6 +2020,7 @@ export default class NestAccfactory {
|
|
|
1977
2020
|
}
|
|
1978
2021
|
if (
|
|
1979
2022
|
value?.source === NestAccfactory.DataSource.REST &&
|
|
2023
|
+
this.config.options?.restAPI === true &&
|
|
1980
2024
|
value.value?.where_id !== undefined &&
|
|
1981
2025
|
value.value?.structure_id !== undefined &&
|
|
1982
2026
|
typeof value?.value?.associated_thermostat === 'string' &&
|
|
@@ -2014,7 +2058,6 @@ export default class NestAccfactory {
|
|
|
2014
2058
|
// Fix up data we need to
|
|
2015
2059
|
data = process_common_data(object_key, data);
|
|
2016
2060
|
data.device_type = NestAccfactory.DeviceType.SMOKESENSOR; // Nest Protect
|
|
2017
|
-
data.battery_level = scaleValue(data.battery_level, 0, 5400, 0, 100);
|
|
2018
2061
|
data.model = 'Protect';
|
|
2019
2062
|
if (data.wired_or_battery === 0) {
|
|
2020
2063
|
data.model = data.model + ' (wired'; // Mains powered
|
|
@@ -2046,8 +2089,68 @@ export default class NestAccfactory {
|
|
|
2046
2089
|
.forEach(([object_key, value]) => {
|
|
2047
2090
|
let tempDevice = {};
|
|
2048
2091
|
try {
|
|
2092
|
+
if (
|
|
2093
|
+
value?.source === NestAccfactory.DataSource.PROTOBUF &&
|
|
2094
|
+
this.config.options?.protobufAPI === true &&
|
|
2095
|
+
value.value?.configuration_done?.deviceReady === true
|
|
2096
|
+
) {
|
|
2097
|
+
let RESTTypeData = {};
|
|
2098
|
+
RESTTypeData.serialNumber = value.value.device_identity.serialNumber;
|
|
2099
|
+
RESTTypeData.softwareVersion =
|
|
2100
|
+
value.value.device_identity.softwareVersion.split(/\s+/)?.[3] !== undefined
|
|
2101
|
+
? value.value.device_identity.softwareVersion.split(/\s+/)?.[3]
|
|
2102
|
+
: value.value.device_identity.softwareVersion;
|
|
2103
|
+
RESTTypeData.online = value.value?.liveness?.status === 'LIVENESS_DEVICE_STATUS_ONLINE';
|
|
2104
|
+
RESTTypeData.line_power_present = value.value?.wall_power?.status === 'POWER_SOURCE_STATUS_ACTIVE';
|
|
2105
|
+
RESTTypeData.wired_or_battery = typeof value.value?.wall_power?.status === 'string' ? 0 : 1;
|
|
2106
|
+
RESTTypeData.battery_level =
|
|
2107
|
+
isNaN(value.value?.battery_voltage_bank1?.batteryValue?.batteryVoltage?.value) === false
|
|
2108
|
+
? scaleValue(Number(value.value.battery_voltage_bank1.batteryValue.batteryVoltage.value), 0, 5.4, 0, 100)
|
|
2109
|
+
: 0;
|
|
2110
|
+
RESTTypeData.battery_health_state =
|
|
2111
|
+
value.value?.battery_voltage_bank0?.faultInformation === undefined &&
|
|
2112
|
+
value.value?.battery_voltage_bank1?.faultInformation === undefined
|
|
2113
|
+
? 0
|
|
2114
|
+
: 1;
|
|
2115
|
+
RESTTypeData.smoke_status = value.value?.safety_alarm_smoke?.alarmState === 'ALARM_STATE_ALARM';
|
|
2116
|
+
RESTTypeData.co_status = value.value?.safety_alarm_co?.alarmState === 'ALARM_STATE_ALARM';
|
|
2117
|
+
RESTTypeData.heat_status = false; // To find in protobuf
|
|
2118
|
+
RESTTypeData.hushed_state =
|
|
2119
|
+
value.value?.safety_alarm_smoke?.silenceState === 'SILENCE_STATE_SILENCED' ||
|
|
2120
|
+
value.value?.safety_alarm_co?.silenceState === 'SILENCE_STATE_SILENCED';
|
|
2121
|
+
RESTTypeData.ntp_green_led = value.value?.night_time_promise_settings?.greenLedEnabled === true;
|
|
2122
|
+
RESTTypeData.smoke_test_passed =
|
|
2123
|
+
typeof value.value.safety_summary?.warningDevices?.failures === 'object'
|
|
2124
|
+
? value.value.safety_summary?.warningDevices?.failures.includes('FAILURE_TYPE_SMOKE') === false
|
|
2125
|
+
: true;
|
|
2126
|
+
RESTTypeData.heat_test_passed =
|
|
2127
|
+
typeof value.value.safety_summary?.warningDevices?.failures === 'object'
|
|
2128
|
+
? value.value.safety_summary?.warningDevices?.failures.includes('FAILURE_TYPE_TEMP') === false
|
|
2129
|
+
: true;
|
|
2130
|
+
RESTTypeData.latest_alarm_test =
|
|
2131
|
+
isNaN(value.value.self_test?.lastMstEnd?.seconds) === false ? Number(value.value.self_test.lastMstEnd.seconds) : 0;
|
|
2132
|
+
RESTTypeData.self_test_in_progress =
|
|
2133
|
+
value.value?.legacy_structure_self_test?.mstInProgress === true ||
|
|
2134
|
+
value.value?.legacy_structure_self_test?.astInProgress === true;
|
|
2135
|
+
RESTTypeData.replacement_date =
|
|
2136
|
+
isNaN(value.value.legacy_protect_device_settings?.replaceByDate?.seconds) === false
|
|
2137
|
+
? Number(value.value.legacy_protect_device_settings.replaceByDate.seconds)
|
|
2138
|
+
: 0;
|
|
2139
|
+
RESTTypeData.topaz_hush_key =
|
|
2140
|
+
typeof value.value?.safety_structure_settings?.structureHushKey === 'string'
|
|
2141
|
+
? value.value.safety_structure_settings.structureHushKey
|
|
2142
|
+
: '';
|
|
2143
|
+
RESTTypeData.detected_motion = value.value?.legacy_protect_device_info?.autoAway !== true; // undefined or false = motion
|
|
2144
|
+
RESTTypeData.description = typeof value.value?.label?.label === 'string' ? value.value.label.label : '';
|
|
2145
|
+
RESTTypeData.location = get_location_name(
|
|
2146
|
+
value.value?.device_info?.pairerId?.resourceId,
|
|
2147
|
+
value.value?.device_located_settings?.whereAnnotationRid?.resourceId,
|
|
2148
|
+
);
|
|
2149
|
+
tempDevice = process_protect_data(object_key, RESTTypeData);
|
|
2150
|
+
}
|
|
2049
2151
|
if (
|
|
2050
2152
|
value?.source === NestAccfactory.DataSource.REST &&
|
|
2153
|
+
this.config.options?.restAPI === true &&
|
|
2051
2154
|
value.value?.where_id !== undefined &&
|
|
2052
2155
|
value.value?.structure_id !== undefined
|
|
2053
2156
|
) {
|
|
@@ -2060,11 +2163,11 @@ export default class NestAccfactory {
|
|
|
2060
2163
|
: false;
|
|
2061
2164
|
RESTTypeData.line_power_present = value.value.line_power_present === true;
|
|
2062
2165
|
RESTTypeData.wired_or_battery = value.value.wired_or_battery;
|
|
2063
|
-
RESTTypeData.battery_level = value.value.battery_level;
|
|
2166
|
+
RESTTypeData.battery_level = scaleValue(value.value.battery_level, 0, 5400, 0, 100);
|
|
2064
2167
|
RESTTypeData.battery_health_state = value.value.battery_health_state;
|
|
2065
|
-
RESTTypeData.smoke_status = value.value.smoke_status;
|
|
2066
|
-
RESTTypeData.co_status = value.value.co_status;
|
|
2067
|
-
RESTTypeData.heat_status = value.value.heat_status;
|
|
2168
|
+
RESTTypeData.smoke_status = value.value.smoke_status !== 0;
|
|
2169
|
+
RESTTypeData.co_status = value.value.co_status !== 0;
|
|
2170
|
+
RESTTypeData.heat_status = value.value.heat_status !== 0;
|
|
2068
2171
|
RESTTypeData.hushed_state = value.value.hushed_state === true;
|
|
2069
2172
|
RESTTypeData.ntp_green_led_enable = value.value.ntp_green_led_enable === true;
|
|
2070
2173
|
RESTTypeData.smoke_test_passed = value.value.component_smoke_test_passed === true;
|
|
@@ -2073,7 +2176,6 @@ export default class NestAccfactory {
|
|
|
2073
2176
|
RESTTypeData.self_test_in_progress =
|
|
2074
2177
|
this.#rawData?.['safety.' + value.value.structure_id]?.value?.manual_self_test_in_progress === true;
|
|
2075
2178
|
RESTTypeData.replacement_date = value.value.replace_by_date_utc_secs;
|
|
2076
|
-
RESTTypeData.removed_from_base = value.value.removed_from_base === true;
|
|
2077
2179
|
RESTTypeData.topaz_hush_key =
|
|
2078
2180
|
typeof this.#rawData?.['structure.' + value.value.structure_id]?.value?.topaz_hush_key === 'string'
|
|
2079
2181
|
? this.#rawData?.['structure.' + value.value.structure_id]?.value?.topaz_hush_key
|
|
@@ -2141,6 +2243,7 @@ export default class NestAccfactory {
|
|
|
2141
2243
|
try {
|
|
2142
2244
|
if (
|
|
2143
2245
|
value?.source === NestAccfactory.DataSource.PROTOBUF &&
|
|
2246
|
+
this.config.options?.protobufAPI === true &&
|
|
2144
2247
|
Array.isArray(value.value?.streaming_protocol?.supportedProtocols) === true &&
|
|
2145
2248
|
value.value.streaming_protocol.supportedProtocols.includes('PROTOCOL_WEBRTC') === true &&
|
|
2146
2249
|
(value.value?.configuration_done?.deviceReady === true ||
|
|
@@ -2255,6 +2358,7 @@ export default class NestAccfactory {
|
|
|
2255
2358
|
|
|
2256
2359
|
if (
|
|
2257
2360
|
value?.source === NestAccfactory.DataSource.REST &&
|
|
2361
|
+
this.config.options?.restAPI === true &&
|
|
2258
2362
|
value.value?.where_id !== undefined &&
|
|
2259
2363
|
value.value?.structure_id !== undefined &&
|
|
2260
2364
|
value.value?.nexus_api_http_server_url !== undefined &&
|
|
@@ -2388,6 +2492,7 @@ export default class NestAccfactory {
|
|
|
2388
2492
|
try {
|
|
2389
2493
|
if (
|
|
2390
2494
|
value?.source === NestAccfactory.DataSource.PROTOBUF &&
|
|
2495
|
+
this.config.options?.protobufAPI === true &&
|
|
2391
2496
|
value.value?.structure_location?.geoCoordinate?.latitude !== undefined &&
|
|
2392
2497
|
value.value?.structure_location?.geoCoordinate?.longitude !== undefined
|
|
2393
2498
|
) {
|
|
@@ -2412,6 +2517,7 @@ export default class NestAccfactory {
|
|
|
2412
2517
|
|
|
2413
2518
|
if (
|
|
2414
2519
|
value?.source === NestAccfactory.DataSource.REST &&
|
|
2520
|
+
this.config.options?.restAPI === true &&
|
|
2415
2521
|
value.value?.latitude !== undefined &&
|
|
2416
2522
|
value.value?.longitude !== undefined
|
|
2417
2523
|
) {
|
|
@@ -3130,7 +3236,7 @@ function makeHomeKitName(nameToMakeValid) {
|
|
|
3130
3236
|
// Matches against uni-code characters
|
|
3131
3237
|
return typeof nameToMakeValid === 'string'
|
|
3132
3238
|
? nameToMakeValid
|
|
3133
|
-
.replace(/[^\p{L}\p{N}\p{Z}\u2019.,-]/gu, '')
|
|
3239
|
+
.replace(/[^\p{L}\p{N}\p{Z}\u2019 '.,-]/gu, '')
|
|
3134
3240
|
.replace(/^[^\p{L}\p{N}]*/gu, '')
|
|
3135
3241
|
.replace(/[^\p{L}\p{N}]+$/gu, '')
|
|
3136
3242
|
: nameToMakeValid;
|
package/dist/webrtc.js
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
// Part of homebridge-nest-accfactory
|
|
3
3
|
//
|
|
4
4
|
// Handles connection and data from Google WebRTC systems
|
|
5
|
+
// Currently a "work in progress"
|
|
5
6
|
//
|
|
6
|
-
// Code version
|
|
7
|
+
// Code version 2025/03/16
|
|
7
8
|
// Mark Hulskamp
|
|
8
9
|
'use strict';
|
|
9
10
|
|
|
10
11
|
// Define external library requirements
|
|
11
12
|
import protobuf from 'protobufjs';
|
|
12
|
-
import werift from 'werift';
|
|
13
|
+
import * as werift from 'werift';
|
|
13
14
|
|
|
14
15
|
// Define nodejs module requirements
|
|
15
16
|
import EventEmitter from 'node:events';
|
|
@@ -98,194 +99,196 @@ export default class WebRTC extends Streamer {
|
|
|
98
99
|
this.stalledTimer = undefined;
|
|
99
100
|
this.#id = undefined;
|
|
100
101
|
|
|
101
|
-
if (this
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// Translate our uuid (DEVICE_xxxxxxxxxx) into the associated 'google id' from the Google Home Foyer
|
|
108
|
-
// We need this id for SOME calls to Google Home Foyer services. Gotta love consistancy :-)
|
|
109
|
-
if (homeFoyerResponse?.data?.[0]?.homes !== undefined) {
|
|
110
|
-
Object.values(homeFoyerResponse?.data?.[0]?.homes).forEach((home) => {
|
|
111
|
-
Object.values(home.devices).forEach((device) => {
|
|
112
|
-
if (device?.id?.googleUuid !== undefined && device?.otherIds?.otherThirdPartyId !== undefined) {
|
|
113
|
-
// Test to see if our uuid matches here
|
|
114
|
-
let currentGoogleUuid = device?.id?.googleUuid;
|
|
115
|
-
Object.values(device.otherIds.otherThirdPartyId).forEach((other) => {
|
|
116
|
-
if (other?.id === this.uuid) {
|
|
117
|
-
this.#googleHomeDeviceUUID = currentGoogleUuid;
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
});
|
|
102
|
+
if (this.online === true && this.videoEnabled === true) {
|
|
103
|
+
if (this.#googleHomeDeviceUUID === undefined) {
|
|
104
|
+
// We don't have the 'google id' yet for this device, so obtain
|
|
105
|
+
let homeFoyerResponse = await this.#googleHomeFoyerCommand('StructuresService', 'GetHomeGraph', {
|
|
106
|
+
requestId: crypto.randomUUID(),
|
|
122
107
|
});
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (this.#googleHomeDeviceUUID !== undefined) {
|
|
127
|
-
// Start setting up connection to camera stream
|
|
128
|
-
this.connected = false; // Starting connection
|
|
129
|
-
let homeFoyerResponse = await this.#googleHomeFoyerCommand('CameraService', 'SendCameraViewIntent', {
|
|
130
|
-
request: {
|
|
131
|
-
googleDeviceId: {
|
|
132
|
-
value: this.#googleHomeDeviceUUID,
|
|
133
|
-
},
|
|
134
|
-
command: 'VIEW_INTENT_START',
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
108
|
|
|
138
|
-
|
|
139
|
-
this.
|
|
140
|
-
|
|
109
|
+
// Translate our uuid (DEVICE_xxxxxxxxxx) into the associated 'google id' from the Google Home Foyer
|
|
110
|
+
// We need this id for SOME calls to Google Home Foyer services. Gotta love consistancy :-)
|
|
111
|
+
if (homeFoyerResponse?.data?.[0]?.homes !== undefined) {
|
|
112
|
+
Object.values(homeFoyerResponse?.data?.[0]?.homes).forEach((home) => {
|
|
113
|
+
Object.values(home.devices).forEach((device) => {
|
|
114
|
+
if (device?.id?.googleUuid !== undefined && device?.otherIds?.otherThirdPartyId !== undefined) {
|
|
115
|
+
// Test to see if our uuid matches here
|
|
116
|
+
let currentGoogleUuid = device?.id?.googleUuid;
|
|
117
|
+
Object.values(device.otherIds.otherThirdPartyId).forEach((other) => {
|
|
118
|
+
if (other?.id === this.uuid) {
|
|
119
|
+
this.#googleHomeDeviceUUID = currentGoogleUuid;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
141
126
|
}
|
|
142
127
|
|
|
143
|
-
if (
|
|
144
|
-
//
|
|
145
|
-
this
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
mimeType: 'audio/opus',
|
|
153
|
-
clockRate: 48000,
|
|
154
|
-
channels: 2,
|
|
155
|
-
rtcpFeedback: [{ type: 'transport-cc' }, { type: 'nack' }],
|
|
156
|
-
parameters: 'minptime=10;useinbandfec=1',
|
|
157
|
-
payloadType: RTP_AUDIO_PAYLOAD_TYPE,
|
|
158
|
-
}),
|
|
159
|
-
],
|
|
160
|
-
video: [
|
|
161
|
-
// H264 Main profile, level 4.0
|
|
162
|
-
new werift.RTCRtpCodecParameters({
|
|
163
|
-
mimeType: 'video/H264',
|
|
164
|
-
clockRate: 90000,
|
|
165
|
-
rtcpFeedback: [
|
|
166
|
-
{ type: 'transport-cc' },
|
|
167
|
-
{ type: 'ccm', parameter: 'fir' },
|
|
168
|
-
{ type: 'nack' },
|
|
169
|
-
{ type: 'nack', parameter: 'pli' },
|
|
170
|
-
{ type: 'goog-remb' },
|
|
171
|
-
],
|
|
172
|
-
parameters: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
|
|
173
|
-
payloadType: RTP_VIDEO_PAYLOAD_TYPE,
|
|
174
|
-
}),
|
|
175
|
-
],
|
|
176
|
-
},
|
|
177
|
-
headerExtensions: {
|
|
178
|
-
audio: [werift.useTransportWideCC(), werift.useAudioLevelIndication()],
|
|
128
|
+
if (this.#googleHomeDeviceUUID !== undefined) {
|
|
129
|
+
// Start setting up connection to camera stream
|
|
130
|
+
this.connected = false; // Starting connection
|
|
131
|
+
let homeFoyerResponse = await this.#googleHomeFoyerCommand('CameraService', 'SendCameraViewIntent', {
|
|
132
|
+
request: {
|
|
133
|
+
googleDeviceId: {
|
|
134
|
+
value: this.#googleHomeDeviceUUID,
|
|
135
|
+
},
|
|
136
|
+
command: 'VIEW_INTENT_START',
|
|
179
137
|
},
|
|
180
138
|
});
|
|
181
139
|
|
|
182
|
-
|
|
140
|
+
if (homeFoyerResponse.status !== 0) {
|
|
141
|
+
this.connected = undefined;
|
|
142
|
+
this?.log?.debug && this.log.debug('Request to start camera viewing was not accepted for uuid "%s"', this.uuid);
|
|
143
|
+
}
|
|
183
144
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
145
|
+
if (homeFoyerResponse.status === 0) {
|
|
146
|
+
// Setup our WebWRTC peer connection for this device
|
|
147
|
+
this.#peerConnection = new werift.RTCPeerConnection({
|
|
148
|
+
iceUseIpv4: true,
|
|
149
|
+
iceUseIpv6: false,
|
|
150
|
+
bundlePolicy: 'max-bundle',
|
|
151
|
+
codecs: {
|
|
152
|
+
audio: [
|
|
153
|
+
new werift.RTCRtpCodecParameters({
|
|
154
|
+
mimeType: 'audio/opus',
|
|
155
|
+
clockRate: 48000,
|
|
156
|
+
channels: 2,
|
|
157
|
+
rtcpFeedback: [{ type: 'transport-cc' }, { type: 'nack' }],
|
|
158
|
+
parameters: 'minptime=10;useinbandfec=1',
|
|
159
|
+
payloadType: RTP_AUDIO_PAYLOAD_TYPE,
|
|
160
|
+
}),
|
|
161
|
+
],
|
|
162
|
+
video: [
|
|
163
|
+
// H264 Main profile, level 4.0
|
|
164
|
+
new werift.RTCRtpCodecParameters({
|
|
165
|
+
mimeType: 'video/H264',
|
|
166
|
+
clockRate: 90000,
|
|
167
|
+
rtcpFeedback: [
|
|
168
|
+
{ type: 'transport-cc' },
|
|
169
|
+
{ type: 'ccm', parameter: 'fir' },
|
|
170
|
+
{ type: 'nack' },
|
|
171
|
+
{ type: 'nack', parameter: 'pli' },
|
|
172
|
+
{ type: 'goog-remb' },
|
|
173
|
+
],
|
|
174
|
+
parameters: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
|
|
175
|
+
payloadType: RTP_VIDEO_PAYLOAD_TYPE,
|
|
176
|
+
}),
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
headerExtensions: {
|
|
180
|
+
audio: [werift.useTransportWideCC(), werift.useAudioLevelIndication()],
|
|
181
|
+
},
|
|
182
|
+
});
|
|
187
183
|
|
|
188
|
-
|
|
189
|
-
direction: 'recvonly',
|
|
190
|
-
});
|
|
184
|
+
this.#peerConnection.createDataChannel('webrtc-datachannel');
|
|
191
185
|
|
|
192
|
-
|
|
193
|
-
|
|
186
|
+
this.#audioTransceiver = this.#peerConnection.addTransceiver('audio', {
|
|
187
|
+
direction: 'sendrecv',
|
|
188
|
+
});
|
|
194
189
|
|
|
195
|
-
|
|
190
|
+
this.#videoTransceiver = this.#peerConnection.addTransceiver('video', {
|
|
191
|
+
direction: 'recvonly',
|
|
192
|
+
});
|
|
196
193
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
deviceId: this.uuid,
|
|
200
|
-
local: this.localAccess,
|
|
201
|
-
streamContext: 'STREAM_CONTEXT_DEFAULT',
|
|
202
|
-
requestedVideoResolution: 'VIDEO_RESOLUTION_FULL_HIGH',
|
|
203
|
-
sdp: webRTCOffer.sdp,
|
|
204
|
-
});
|
|
194
|
+
let webRTCOffer = await this.#peerConnection.createOffer();
|
|
195
|
+
await this.#peerConnection.setLocalDescription(webRTCOffer);
|
|
205
196
|
|
|
206
|
-
|
|
207
|
-
this.connected = undefined;
|
|
208
|
-
this?.log?.debug && this.log.debug('WebRTC offer was not agreed with remote for uuid "%s"', this.uuid);
|
|
209
|
-
}
|
|
197
|
+
this?.log?.debug && this.log.debug('Sending WebRTC offer for uuid "%s"', this.uuid);
|
|
210
198
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
199
|
+
homeFoyerResponse = await this.#googleHomeFoyerCommand('CameraService', 'JoinStream', {
|
|
200
|
+
command: 'offer',
|
|
201
|
+
deviceId: this.uuid,
|
|
202
|
+
local: this.localAccess,
|
|
203
|
+
streamContext: 'STREAM_CONTEXT_DEFAULT',
|
|
204
|
+
requestedVideoResolution: 'VIDEO_RESOLUTION_FULL_HIGH',
|
|
205
|
+
sdp: webRTCOffer.sdp,
|
|
206
|
+
});
|
|
217
207
|
|
|
218
|
-
|
|
219
|
-
this
|
|
220
|
-
|
|
208
|
+
if (homeFoyerResponse.status !== 0) {
|
|
209
|
+
this.connected = undefined;
|
|
210
|
+
this?.log?.debug && this.log.debug('WebRTC offer was not agreed with remote for uuid "%s"', this.uuid);
|
|
211
|
+
}
|
|
221
212
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
213
|
+
if (
|
|
214
|
+
homeFoyerResponse.status === 0 &&
|
|
215
|
+
homeFoyerResponse.data?.[0]?.responseType === 'answer' &&
|
|
216
|
+
homeFoyerResponse.data?.[0]?.streamId !== undefined
|
|
217
|
+
) {
|
|
218
|
+
this?.log?.debug && this.log.debug('WebRTC offer agreed with remote for uuid "%s"', this.uuid);
|
|
226
219
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
220
|
+
this.#audioTransceiver?.onTrack &&
|
|
221
|
+
this.#audioTransceiver.onTrack.subscribe((track) => {
|
|
222
|
+
this.#handlePlaybackBegin(track);
|
|
230
223
|
|
|
231
|
-
|
|
232
|
-
|
|
224
|
+
track.onReceiveRtp.subscribe((rtp) => {
|
|
225
|
+
this.#handlePlaybackPacket(rtp);
|
|
226
|
+
});
|
|
233
227
|
});
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
228
|
+
|
|
229
|
+
this.#videoTransceiver?.onTrack &&
|
|
230
|
+
this.#videoTransceiver.onTrack.subscribe((track) => {
|
|
231
|
+
this.#handlePlaybackBegin(track);
|
|
232
|
+
|
|
233
|
+
track.onReceiveRtp.subscribe((rtp) => {
|
|
234
|
+
this.#handlePlaybackPacket(rtp);
|
|
235
|
+
});
|
|
236
|
+
track.onReceiveRtcp.once(() => {
|
|
237
|
+
setInterval(() => {
|
|
238
|
+
if (this.#videoTransceiver?.receiver !== undefined) {
|
|
239
|
+
this.#videoTransceiver.receiver.sendRtcpPLI(track.ssrc);
|
|
240
|
+
}
|
|
241
|
+
}, 2000);
|
|
242
|
+
});
|
|
240
243
|
});
|
|
241
|
-
});
|
|
242
244
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
245
|
+
this.#id = homeFoyerResponse.data[0].streamId;
|
|
246
|
+
this.#peerConnection &&
|
|
247
|
+
(await this.#peerConnection.setRemoteDescription({
|
|
248
|
+
type: 'answer',
|
|
249
|
+
sdp: homeFoyerResponse.data[0].sdp,
|
|
250
|
+
}));
|
|
251
|
+
|
|
252
|
+
this?.log?.debug && this.log.debug('Playback started from WebRTC for uuid "%s" with session ID "%s"', this.uuid, this.#id);
|
|
253
|
+
this.connected = true;
|
|
254
|
+
|
|
255
|
+
// Monitor connection status. If closed and there are still output streams, re-connect
|
|
256
|
+
// Never seem to get a 'connected' status. Could use that for something?
|
|
257
|
+
this.#peerConnection &&
|
|
258
|
+
this.#peerConnection.connectionStateChange.subscribe((state) => {
|
|
259
|
+
if (state !== 'connected' && state !== 'connecting') {
|
|
260
|
+
this?.log?.debug && this.log.debug('Connection closed to WebRTC for uuid "%s"', this.uuid);
|
|
261
|
+
this.connected = undefined;
|
|
262
|
+
if (this.haveOutputs() === true) {
|
|
263
|
+
this.connect();
|
|
264
|
+
}
|
|
262
265
|
}
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// Create a timer to extend the active stream every period as defined
|
|
267
|
-
this.extendTimer = setInterval(async () => {
|
|
268
|
-
if (
|
|
269
|
-
this.#googleHomeFoyer !== undefined &&
|
|
270
|
-
this.connected === true &&
|
|
271
|
-
this.#id !== undefined &&
|
|
272
|
-
this.#googleHomeDeviceUUID !== undefined
|
|
273
|
-
) {
|
|
274
|
-
let homeFoyerResponse = await this.#googleHomeFoyerCommand('CameraService', 'JoinStream', {
|
|
275
|
-
command: 'extend',
|
|
276
|
-
deviceId: this.uuid,
|
|
277
|
-
streamId: this.#id,
|
|
278
266
|
});
|
|
279
267
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
268
|
+
// Create a timer to extend the active stream every period as defined
|
|
269
|
+
this.extendTimer = setInterval(async () => {
|
|
270
|
+
if (
|
|
271
|
+
this.#googleHomeFoyer !== undefined &&
|
|
272
|
+
this.connected === true &&
|
|
273
|
+
this.#id !== undefined &&
|
|
274
|
+
this.#googleHomeDeviceUUID !== undefined
|
|
275
|
+
) {
|
|
276
|
+
let homeFoyerResponse = await this.#googleHomeFoyerCommand('CameraService', 'JoinStream', {
|
|
277
|
+
command: 'extend',
|
|
278
|
+
deviceId: this.uuid,
|
|
279
|
+
streamId: this.#id,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (homeFoyerResponse?.data?.[0]?.streamExtensionStatus !== 'STATUS_STREAM_EXTENDED') {
|
|
283
|
+
this?.log?.debug && this.log.debug('Error occurred while requested stream extension for uuid "%s"', this.uuid);
|
|
284
|
+
|
|
285
|
+
if (typeof this.#peerConnection?.close === 'function') {
|
|
286
|
+
await this.#peerConnection.close();
|
|
287
|
+
}
|
|
285
288
|
}
|
|
286
289
|
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
290
|
+
}, EXTENDINTERVAL);
|
|
291
|
+
}
|
|
289
292
|
}
|
|
290
293
|
}
|
|
291
294
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "homebridge-nest-accfactory",
|
|
3
3
|
"displayName": "Nest Accfactory",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.9",
|
|
6
6
|
"description": "Homebridge support for Nest/Google devices including HomeKit Secure Video (HKSV) support for doorbells and cameras",
|
|
7
7
|
"author": "n0rt0nthec4t",
|
|
8
8
|
"license": "Apache-2.0",
|
|
@@ -45,26 +45,26 @@
|
|
|
45
45
|
],
|
|
46
46
|
"scripts": {
|
|
47
47
|
"clean": "rimraf ./dist*",
|
|
48
|
-
"format": "prettier --write src/*.js src/**/*.js",
|
|
49
|
-
"lint": "eslint src/*.js src/**/*.js --max-warnings=20",
|
|
48
|
+
"format": "prettier --write src/*.js src/**/*.js src/**/*.mjs",
|
|
49
|
+
"lint": "eslint src/*.js src/**/*.js src/**/*.mjs --max-warnings=20",
|
|
50
50
|
"build": "npm run clean && copyfiles -u 1 src/*.js dist && copyfiles -u 2 src/HomeKitDevice/*.js dist && copyfiles -u 2 src/HomeKitHistory/*.js dist && copyfiles -u 1 src/res/*.h264 dist && copyfiles -u 1 src/res/*.jpg dist && copyfiles -u 1 'src/protobuf/**/*.proto' dist",
|
|
51
51
|
"prepublishOnly": "npm run lint && npm run build"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@eslint/js": "^9.
|
|
55
|
-
"@stylistic/eslint-plugin": "^2.
|
|
56
|
-
"@types/node": "^22.
|
|
57
|
-
"@typescript-eslint/parser": "^8.
|
|
58
|
-
"homebridge": "^2.0.0-beta.0",
|
|
54
|
+
"@eslint/js": "^9.23.0",
|
|
55
|
+
"@stylistic/eslint-plugin": "^4.2.0",
|
|
56
|
+
"@types/node": "^22.13.11",
|
|
57
|
+
"@typescript-eslint/parser": "^8.27.0",
|
|
59
58
|
"copyfiles": "^2.4.1",
|
|
60
|
-
"eslint": "^9.
|
|
61
|
-
"
|
|
59
|
+
"eslint": "^9.23.0",
|
|
60
|
+
"homebridge": "^2.0.0-beta.0",
|
|
61
|
+
"prettier": "^3.5.3",
|
|
62
62
|
"prettier-eslint": "^16.3.0",
|
|
63
63
|
"rimraf": "^6.0.1"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"protobufjs": "^7.4.0",
|
|
67
|
-
"
|
|
68
|
-
"
|
|
67
|
+
"werift": "^0.22.1",
|
|
68
|
+
"ws": "^8.18.1"
|
|
69
69
|
}
|
|
70
70
|
}
|