homebridge-nest-accfactory 0.2.11 → 0.3.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 +24 -0
- package/README.md +14 -7
- package/config.schema.json +118 -0
- package/dist/HomeKitDevice.js +194 -77
- package/dist/HomeKitHistory.js +1 -1
- package/dist/config.js +207 -0
- package/dist/devices.js +113 -0
- package/dist/index.js +2 -1
- package/dist/nexustalk.js +19 -21
- package/dist/{camera.js → plugins/camera.js} +212 -239
- package/dist/{doorbell.js → plugins/doorbell.js} +32 -30
- package/dist/plugins/floodlight.js +91 -0
- package/dist/plugins/heatlink.js +17 -0
- package/dist/{protect.js → plugins/protect.js} +24 -41
- package/dist/{tempsensor.js → plugins/tempsensor.js} +13 -17
- package/dist/{thermostat.js → plugins/thermostat.js} +424 -381
- package/dist/{weather.js → plugins/weather.js} +26 -60
- package/dist/protobuf/nest/services/apigateway.proto +31 -1
- package/dist/protobuf/nest/trait/firmware.proto +207 -89
- package/dist/protobuf/nest/trait/hvac.proto +1052 -312
- package/dist/protobuf/nest/trait/located.proto +51 -8
- package/dist/protobuf/nest/trait/network.proto +366 -36
- package/dist/protobuf/nest/trait/occupancy.proto +145 -17
- package/dist/protobuf/nest/trait/product/protect.proto +57 -43
- package/dist/protobuf/nest/trait/resourcedirectory.proto +8 -0
- package/dist/protobuf/nest/trait/sensor.proto +7 -1
- package/dist/protobuf/nest/trait/service.proto +3 -1
- package/dist/protobuf/nest/trait/structure.proto +60 -14
- package/dist/protobuf/nest/trait/ui.proto +41 -1
- package/dist/protobuf/nest/trait/user.proto +6 -1
- package/dist/protobuf/nest/trait/voiceassistant.proto +2 -1
- package/dist/protobuf/nestlabs/eventingapi/v1.proto +20 -1
- package/dist/protobuf/root.proto +1 -0
- package/dist/protobuf/wdl.proto +18 -2
- package/dist/protobuf/weave/common.proto +2 -1
- package/dist/protobuf/weave/trait/heartbeat.proto +41 -1
- package/dist/protobuf/weave/trait/power.proto +1 -0
- package/dist/protobuf/weave/trait/security.proto +10 -1
- package/dist/streamer.js +68 -72
- package/dist/system.js +1208 -1245
- package/dist/webrtc.js +28 -23
- package/package.json +12 -12
- package/dist/floodlight.js +0 -97
package/dist/webrtc.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Handles connection and data from Google WebRTC systems
|
|
5
5
|
// Currently a "work in progress"
|
|
6
6
|
//
|
|
7
|
-
// Code version 2025
|
|
7
|
+
// Code version 2025.06.10
|
|
8
8
|
// Mark Hulskamp
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
@@ -58,6 +58,7 @@ export default class WebRTC extends Streamer {
|
|
|
58
58
|
// Internal data only for this class
|
|
59
59
|
#protobufFoyer = undefined; // Protobuf for Google Home Foyer
|
|
60
60
|
#googleHomeFoyer = undefined; // HTTP/2 connection to Google Home Foyer APIs
|
|
61
|
+
#googleHomeFoyerAPIHost = 'https://googlehomefoyer-pa.googleapis.com'; // Default API endpoint for Google Home Foyer
|
|
61
62
|
#id = undefined; // Session ID
|
|
62
63
|
#googleHomeDeviceUUID = undefined; // Normal Nest/Google protobuf device ID translated to a Google Foyer device ID
|
|
63
64
|
#peerConnection = undefined;
|
|
@@ -78,6 +79,11 @@ export default class WebRTC extends Streamer {
|
|
|
78
79
|
this.token = deviceData?.apiAccess?.oauth2;
|
|
79
80
|
this.localAccess = deviceData?.localAccess === true;
|
|
80
81
|
|
|
82
|
+
// Update Google Home Foyer api host if using field test
|
|
83
|
+
if (deviceData?.apiAccess?.fieldTest === true) {
|
|
84
|
+
this.#googleHomeFoyerAPIHost = 'https://preprod-googlehomefoyer-pa.sandbox.googleapis.com';
|
|
85
|
+
}
|
|
86
|
+
|
|
81
87
|
// Set our streamer codec types
|
|
82
88
|
this.codecs = {
|
|
83
89
|
video: 'h264',
|
|
@@ -139,7 +145,7 @@ export default class WebRTC extends Streamer {
|
|
|
139
145
|
|
|
140
146
|
if (homeFoyerResponse.status !== 0) {
|
|
141
147
|
this.connected = undefined;
|
|
142
|
-
this?.log?.debug
|
|
148
|
+
this?.log?.debug?.('Request to start camera viewing was not accepted for uuid "%s"', this.uuid);
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
if (homeFoyerResponse.status === 0) {
|
|
@@ -194,7 +200,7 @@ export default class WebRTC extends Streamer {
|
|
|
194
200
|
let webRTCOffer = await this.#peerConnection.createOffer();
|
|
195
201
|
await this.#peerConnection.setLocalDescription(webRTCOffer);
|
|
196
202
|
|
|
197
|
-
this?.log?.debug
|
|
203
|
+
this?.log?.debug?.('Sending WebRTC offer for uuid "%s"', this.uuid);
|
|
198
204
|
|
|
199
205
|
homeFoyerResponse = await this.#googleHomeFoyerCommand('CameraService', 'JoinStream', {
|
|
200
206
|
command: 'offer',
|
|
@@ -207,7 +213,7 @@ export default class WebRTC extends Streamer {
|
|
|
207
213
|
|
|
208
214
|
if (homeFoyerResponse.status !== 0) {
|
|
209
215
|
this.connected = undefined;
|
|
210
|
-
this?.log?.debug
|
|
216
|
+
this?.log?.debug?.('WebRTC offer was not agreed with remote for uuid "%s"', this.uuid);
|
|
211
217
|
}
|
|
212
218
|
|
|
213
219
|
if (
|
|
@@ -215,7 +221,7 @@ export default class WebRTC extends Streamer {
|
|
|
215
221
|
homeFoyerResponse.data?.[0]?.responseType === 'answer' &&
|
|
216
222
|
homeFoyerResponse.data?.[0]?.streamId !== undefined
|
|
217
223
|
) {
|
|
218
|
-
this?.log?.debug
|
|
224
|
+
this?.log?.debug?.('WebRTC offer agreed with remote for uuid "%s"', this.uuid);
|
|
219
225
|
|
|
220
226
|
this.#audioTransceiver?.onTrack &&
|
|
221
227
|
this.#audioTransceiver.onTrack.subscribe((track) => {
|
|
@@ -249,7 +255,7 @@ export default class WebRTC extends Streamer {
|
|
|
249
255
|
sdp: homeFoyerResponse.data[0].sdp,
|
|
250
256
|
}));
|
|
251
257
|
|
|
252
|
-
this?.log?.debug
|
|
258
|
+
this?.log?.debug?.('Playback started from WebRTC for uuid "%s" with session ID "%s"', this.uuid, this.#id);
|
|
253
259
|
this.connected = true;
|
|
254
260
|
|
|
255
261
|
// Monitor connection status. If closed and there are still output streams, re-connect
|
|
@@ -257,7 +263,7 @@ export default class WebRTC extends Streamer {
|
|
|
257
263
|
this.#peerConnection &&
|
|
258
264
|
this.#peerConnection.connectionStateChange.subscribe((state) => {
|
|
259
265
|
if (state !== 'connected' && state !== 'connecting') {
|
|
260
|
-
this?.log?.debug
|
|
266
|
+
this?.log?.debug?.('Connection closed to WebRTC for uuid "%s"', this.uuid);
|
|
261
267
|
this.connected = undefined;
|
|
262
268
|
if (this.haveOutputs() === true) {
|
|
263
269
|
this.connect();
|
|
@@ -280,7 +286,7 @@ export default class WebRTC extends Streamer {
|
|
|
280
286
|
});
|
|
281
287
|
|
|
282
288
|
if (homeFoyerResponse?.data?.[0]?.streamExtensionStatus !== 'STATUS_STREAM_EXTENDED') {
|
|
283
|
-
this?.log?.debug
|
|
289
|
+
this?.log?.debug?.('Error occurred while requested stream extension for uuid "%s"', this.uuid);
|
|
284
290
|
|
|
285
291
|
if (typeof this.#peerConnection?.close === 'function') {
|
|
286
292
|
await this.#peerConnection.close();
|
|
@@ -307,7 +313,7 @@ export default class WebRTC extends Streamer {
|
|
|
307
313
|
});
|
|
308
314
|
}
|
|
309
315
|
|
|
310
|
-
this?.log?.debug
|
|
316
|
+
this?.log?.debug?.('Notifying remote about closing connection for uuid "%s"', this.uuid);
|
|
311
317
|
await this.#googleHomeFoyerCommand('CameraService', 'JoinStream', {
|
|
312
318
|
command: 'end',
|
|
313
319
|
deviceId: this.uuid,
|
|
@@ -375,11 +381,11 @@ export default class WebRTC extends Streamer {
|
|
|
375
381
|
|
|
376
382
|
if (homeFoyerResponse?.status !== 0) {
|
|
377
383
|
this.audio.talking = undefined;
|
|
378
|
-
this?.log?.debug
|
|
384
|
+
this?.log?.debug?.('Error occurred while requesting talkback to start for uuid "%s"', this.uuid);
|
|
379
385
|
}
|
|
380
386
|
if (homeFoyerResponse?.status === 0) {
|
|
381
387
|
this.audio.talking = true;
|
|
382
|
-
this?.log?.debug
|
|
388
|
+
this?.log?.debug?.('Talking start on uuid "%s"', this.uuid);
|
|
383
389
|
}
|
|
384
390
|
}
|
|
385
391
|
|
|
@@ -407,10 +413,10 @@ export default class WebRTC extends Streamer {
|
|
|
407
413
|
command: 'COMMAND_STOP',
|
|
408
414
|
});
|
|
409
415
|
if (homeFoyerResponse?.status !== 0) {
|
|
410
|
-
this?.log?.debug
|
|
416
|
+
this?.log?.debug?.('Error occurred while requesting talkback to stop for uuid "%s"', this.uuid);
|
|
411
417
|
}
|
|
412
418
|
if (homeFoyerResponse?.status === 0) {
|
|
413
|
-
this?.log?.debug
|
|
419
|
+
this?.log?.debug?.('Talking ended on uuid "%s"', this.uuid);
|
|
414
420
|
}
|
|
415
421
|
this.audio.talking = undefined;
|
|
416
422
|
}
|
|
@@ -453,12 +459,11 @@ export default class WebRTC extends Streamer {
|
|
|
453
459
|
// If its trigger, we'll attempt to restart the stream and/or connection
|
|
454
460
|
clearTimeout(this.stalledTimer);
|
|
455
461
|
this.stalledTimer = setTimeout(async () => {
|
|
456
|
-
this?.log?.debug
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
);
|
|
462
|
+
this?.log?.debug?.(
|
|
463
|
+
'We have not received any data from webrtc in the past "%s" seconds for uuid "%s". Attempting restart',
|
|
464
|
+
10,
|
|
465
|
+
this.uuid,
|
|
466
|
+
);
|
|
462
467
|
|
|
463
468
|
if (typeof this.#peerConnection?.close === 'function') {
|
|
464
469
|
await this.#peerConnection.close();
|
|
@@ -510,11 +515,11 @@ export default class WebRTC extends Streamer {
|
|
|
510
515
|
if (TraitMapRequest !== null && TraitMapResponse !== null && this.token !== undefined) {
|
|
511
516
|
if (this.#googleHomeFoyer === undefined || (this.#googleHomeFoyer?.connected === false && this.#googleHomeFoyer?.closed === true)) {
|
|
512
517
|
// No current HTTP/2 connection or current session is closed
|
|
513
|
-
this?.log?.debug
|
|
514
|
-
this.#googleHomeFoyer = http2.connect(
|
|
518
|
+
this?.log?.debug?.('Connection started to Google Home Foyer "%s"', this.#googleHomeFoyerAPIHost);
|
|
519
|
+
this.#googleHomeFoyer = http2.connect(this.#googleHomeFoyerAPIHost);
|
|
515
520
|
|
|
516
521
|
this.#googleHomeFoyer.on('connect', () => {
|
|
517
|
-
this?.log?.debug
|
|
522
|
+
this?.log?.debug?.('Connection established to Google Home Foyer "%s"', this.#googleHomeFoyerAPIHost);
|
|
518
523
|
|
|
519
524
|
clearInterval(this.pingTimer);
|
|
520
525
|
this.pingTimer = setInterval(() => {
|
|
@@ -542,7 +547,7 @@ export default class WebRTC extends Streamer {
|
|
|
542
547
|
clearInterval(this.pingTimer);
|
|
543
548
|
this.pingTimer = undefined;
|
|
544
549
|
this.#googleHomeFoyer = undefined;
|
|
545
|
-
this?.log?.debug
|
|
550
|
+
this?.log?.debug?.('Connection closed to Google Home Foyer "%s"', this.#googleHomeFoyerAPIHost);
|
|
546
551
|
});
|
|
547
552
|
}
|
|
548
553
|
|
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.
|
|
5
|
+
"version": "0.3.0",
|
|
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
|
|
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",
|
|
48
|
+
"format": "prettier --write src/*.js src/**/*.js",
|
|
49
|
+
"lint": "eslint src/*.js src/**/*.js --fix --max-warnings=20",
|
|
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 && copyfiles -u 1 src/plugins/*.js dist",
|
|
51
51
|
"prepublishOnly": "npm run lint && npm run build"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@eslint/js": "^9.
|
|
55
|
-
"@stylistic/eslint-plugin": "^4.
|
|
56
|
-
"@types/node": "^
|
|
57
|
-
"@typescript-eslint/parser": "^8.
|
|
54
|
+
"@eslint/js": "^9.28.0",
|
|
55
|
+
"@stylistic/eslint-plugin": "^4.4.1",
|
|
56
|
+
"@types/node": "^24.0.1",
|
|
57
|
+
"@typescript-eslint/parser": "^8.34.0",
|
|
58
58
|
"copyfiles": "^2.4.1",
|
|
59
|
-
"eslint": "^9.
|
|
59
|
+
"eslint": "^9.28.0",
|
|
60
60
|
"homebridge": "^2.0.0-beta.0",
|
|
61
61
|
"prettier": "^3.5.3",
|
|
62
|
-
"prettier-eslint": "^16.
|
|
62
|
+
"prettier-eslint": "^16.4.2",
|
|
63
63
|
"rimraf": "^6.0.1"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"protobufjs": "^7.5.
|
|
66
|
+
"protobufjs": "^7.5.3",
|
|
67
67
|
"werift": "^0.22.1",
|
|
68
|
-
"ws": "^8.18.
|
|
68
|
+
"ws": "^8.18.2"
|
|
69
69
|
}
|
|
70
70
|
}
|
package/dist/floodlight.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
// Nest Cam with Floodlight
|
|
2
|
-
// Part of homebridge-nest-accfactory
|
|
3
|
-
//
|
|
4
|
-
// Code version 27/9/2024
|
|
5
|
-
// Mark Hulskamp
|
|
6
|
-
'use strict';
|
|
7
|
-
|
|
8
|
-
// Define external module requirements
|
|
9
|
-
import NestCamera from './camera.js';
|
|
10
|
-
|
|
11
|
-
export default class NestFloodlight extends NestCamera {
|
|
12
|
-
lightService = undefined; // HomeKit light
|
|
13
|
-
|
|
14
|
-
constructor(accessory, api, log, eventEmitter, deviceData) {
|
|
15
|
-
super(accessory, api, log, eventEmitter, deviceData);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Class functions
|
|
19
|
-
addServices() {
|
|
20
|
-
// Call parent to setup the common camera things. Once we return, we can add in the specifics for our floodlight
|
|
21
|
-
let postSetupDetails = super.addServices();
|
|
22
|
-
|
|
23
|
-
this.lightService = this.accessory.getService(this.hap.Service.Switch);
|
|
24
|
-
if (this.deviceData.has_light === true) {
|
|
25
|
-
// Add service to for a light, including brightness control
|
|
26
|
-
if (this.lightService === undefined) {
|
|
27
|
-
this.lightService = this.accessory.addService(this.hap.Service.Lightbulb, '', 1);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (this.lightService.testCharacteristic(this.hap.Characteristic.Brightness) === false) {
|
|
31
|
-
this.lightService.addCharacteristic(this.hap.Characteristic.Brightness);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
this.lightService.getCharacteristic(this.hap.Characteristic.Brightness).setProps({
|
|
35
|
-
minStep: 10, // Light only goes in 10% increments
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Setup set callback for this light service
|
|
39
|
-
this.lightService.getCharacteristic(this.hap.Characteristic.On).onSet((value) => {
|
|
40
|
-
if (value !== this.deviceData.light_enabled) {
|
|
41
|
-
this.set({ uuid: this.deviceData.nest_google_uuid, light_enabled: value });
|
|
42
|
-
|
|
43
|
-
this?.log?.info && this.log.info('Floodlight on "%s" was turned', this.deviceData.description, value === true ? 'on' : 'off');
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
this.lightService.getCharacteristic(this.hap.Characteristic.Brightness).onSet((value) => {
|
|
48
|
-
if (value !== this.deviceData.light_brightness) {
|
|
49
|
-
this.set({ uuid: this.deviceData.nest_google_uuid, light_brightness: value });
|
|
50
|
-
|
|
51
|
-
this?.log?.info && this.log.info('Floodlight brightness on "%s" was set to "%s %"', this.deviceData.description, value);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
this.lightService.getCharacteristic(this.hap.Characteristic.On).onGet(() => {
|
|
56
|
-
return this.deviceData.light_enabled === true;
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
this.lightService.getCharacteristic(this.hap.Characteristic.Brightness).onGet(() => {
|
|
60
|
-
return this.deviceData.light_brightness;
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
if (this.lightService !== undefined && this.deviceData.has_light !== true) {
|
|
64
|
-
// No longer required to have the light service
|
|
65
|
-
this.accessory.removeService(this.lightService);
|
|
66
|
-
this.lightService === undefined;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Create extra details for output
|
|
70
|
-
this.lightService !== undefined && postSetupDetails.push('Light support');
|
|
71
|
-
return postSetupDetails;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
removeServices() {
|
|
75
|
-
super.removeServices();
|
|
76
|
-
|
|
77
|
-
if (this.lightService !== undefined) {
|
|
78
|
-
this.accessory.removeService(this.lightService);
|
|
79
|
-
}
|
|
80
|
-
this.lightService = undefined;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
updateServices(deviceData) {
|
|
84
|
-
if (typeof deviceData !== 'object' || this.controller === undefined) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Get the camera class todo all its updates first, then we'll handle the doorbell specific stuff
|
|
89
|
-
super.updateServices(deviceData);
|
|
90
|
-
|
|
91
|
-
if (this.lightService !== undefined) {
|
|
92
|
-
// Update status of light, including brightness
|
|
93
|
-
this.lightService.updateCharacteristic(this.hap.Characteristic.On, deviceData.light_enabled);
|
|
94
|
-
this.lightService.updateCharacteristic(this.hap.Characteristic.Brightness, deviceData.light_brightness);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|