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.
Files changed (43) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +14 -7
  3. package/config.schema.json +118 -0
  4. package/dist/HomeKitDevice.js +194 -77
  5. package/dist/HomeKitHistory.js +1 -1
  6. package/dist/config.js +207 -0
  7. package/dist/devices.js +113 -0
  8. package/dist/index.js +2 -1
  9. package/dist/nexustalk.js +19 -21
  10. package/dist/{camera.js → plugins/camera.js} +212 -239
  11. package/dist/{doorbell.js → plugins/doorbell.js} +32 -30
  12. package/dist/plugins/floodlight.js +91 -0
  13. package/dist/plugins/heatlink.js +17 -0
  14. package/dist/{protect.js → plugins/protect.js} +24 -41
  15. package/dist/{tempsensor.js → plugins/tempsensor.js} +13 -17
  16. package/dist/{thermostat.js → plugins/thermostat.js} +424 -381
  17. package/dist/{weather.js → plugins/weather.js} +26 -60
  18. package/dist/protobuf/nest/services/apigateway.proto +31 -1
  19. package/dist/protobuf/nest/trait/firmware.proto +207 -89
  20. package/dist/protobuf/nest/trait/hvac.proto +1052 -312
  21. package/dist/protobuf/nest/trait/located.proto +51 -8
  22. package/dist/protobuf/nest/trait/network.proto +366 -36
  23. package/dist/protobuf/nest/trait/occupancy.proto +145 -17
  24. package/dist/protobuf/nest/trait/product/protect.proto +57 -43
  25. package/dist/protobuf/nest/trait/resourcedirectory.proto +8 -0
  26. package/dist/protobuf/nest/trait/sensor.proto +7 -1
  27. package/dist/protobuf/nest/trait/service.proto +3 -1
  28. package/dist/protobuf/nest/trait/structure.proto +60 -14
  29. package/dist/protobuf/nest/trait/ui.proto +41 -1
  30. package/dist/protobuf/nest/trait/user.proto +6 -1
  31. package/dist/protobuf/nest/trait/voiceassistant.proto +2 -1
  32. package/dist/protobuf/nestlabs/eventingapi/v1.proto +20 -1
  33. package/dist/protobuf/root.proto +1 -0
  34. package/dist/protobuf/wdl.proto +18 -2
  35. package/dist/protobuf/weave/common.proto +2 -1
  36. package/dist/protobuf/weave/trait/heartbeat.proto +41 -1
  37. package/dist/protobuf/weave/trait/power.proto +1 -0
  38. package/dist/protobuf/weave/trait/security.proto +10 -1
  39. package/dist/streamer.js +68 -72
  40. package/dist/system.js +1208 -1245
  41. package/dist/webrtc.js +28 -23
  42. package/package.json +12 -12
  43. 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/03/16
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 && this.log.debug('Request to start camera viewing was not accepted for uuid "%s"', this.uuid);
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 && this.log.debug('Sending WebRTC offer for uuid "%s"', this.uuid);
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 && this.log.debug('WebRTC offer was not agreed with remote for uuid "%s"', this.uuid);
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 && this.log.debug('WebRTC offer agreed with remote for uuid "%s"', this.uuid);
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 && this.log.debug('Playback started from WebRTC for uuid "%s" with session ID "%s"', this.uuid, this.#id);
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 && this.log.debug('Connection closed to WebRTC for uuid "%s"', this.uuid);
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 && this.log.debug('Error occurred while requested stream extension for uuid "%s"', this.uuid);
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 && this.log.debug('Notifying remote about closing connection for uuid "%s"', this.uuid);
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 && this.log.debug('Error occurred while requesting talkback to start for uuid "%s"', this.uuid);
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 && this.log.debug('Talking start on uuid "%s"', this.uuid);
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 && this.log.debug('Error occurred while requesting talkback to stop for uuid "%s"', this.uuid);
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 && this.log.debug('Talking ended on uuid "%s"', this.uuid);
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
- this.log.debug(
458
- 'We have not received any data from webrtc in the past "%s" seconds for uuid "%s". Attempting restart',
459
- 10,
460
- this.uuid,
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 && this.log.debug('Connection started to Google Home Foyer');
514
- this.#googleHomeFoyer = http2.connect('https://googlehomefoyer-pa.googleapis.com');
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 && this.log.debug('Connection established to Google Home Foyer');
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 && this.log.debug('Connection closed to Google Home Foyer');
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.2.11",
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 src/**/*.mjs",
49
- "lint": "eslint src/*.js src/**/*.js src/**/*.mjs --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",
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.24.0",
55
- "@stylistic/eslint-plugin": "^4.2.0",
56
- "@types/node": "^22.14.1",
57
- "@typescript-eslint/parser": "^8.30.1",
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.24.0",
59
+ "eslint": "^9.28.0",
60
60
  "homebridge": "^2.0.0-beta.0",
61
61
  "prettier": "^3.5.3",
62
- "prettier-eslint": "^16.3.2",
62
+ "prettier-eslint": "^16.4.2",
63
63
  "rimraf": "^6.0.1"
64
64
  },
65
65
  "dependencies": {
66
- "protobufjs": "^7.5.0",
66
+ "protobufjs": "^7.5.3",
67
67
  "werift": "^0.22.1",
68
- "ws": "^8.18.1"
68
+ "ws": "^8.18.2"
69
69
  }
70
70
  }
@@ -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
- }