homebridge-nest-accfactory 0.0.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 12/9/2024
4
+ // Code version 27/9/2024
5
5
  // Mark Hulskamp
6
6
  'use strict';
7
7
 
@@ -41,7 +41,7 @@ export default class NestDoorbell extends NestCamera {
41
41
  this.switchService.getCharacteristic(this.hap.Characteristic.On).onSet((value) => {
42
42
  if (value !== this.deviceData.indoor_chime_enabled) {
43
43
  // only change indoor chime status value if different than on-device
44
- this.set({ indoor_chime_enabled: value });
44
+ this.set({ uuid: this.deviceData.nest_google_uuid, indoor_chime_enabled: value });
45
45
 
46
46
  this?.log?.info && this.log.info('Indoor chime on "%s" was turned', this.deviceData.description, value === true ? 'on' : 'off');
47
47
  }
@@ -66,7 +66,9 @@ export default class NestDoorbell extends NestCamera {
66
66
  removeServices() {
67
67
  super.removeServices();
68
68
 
69
- this.doorbellTimer = clearTimeout(this.doorbellTimer);
69
+ clearTimeout(this.doorbellTimer);
70
+ this.doorbellTimer = undefined;
71
+
70
72
  if (this.switchService !== undefined) {
71
73
  this.accessory.removeService(this.switchService);
72
74
  }
@@ -1,7 +1,7 @@
1
1
  // Nest Cam with Floodlight
2
2
  // Part of homebridge-nest-accfactory
3
3
  //
4
- // Code version 12/9/2024
4
+ // Code version 27/9/2024
5
5
  // Mark Hulskamp
6
6
  'use strict';
7
7
 
@@ -38,7 +38,7 @@ export default class NestFloodlight extends NestCamera {
38
38
  // Setup set callback for this light service
39
39
  this.lightService.getCharacteristic(this.hap.Characteristic.On).onSet((value) => {
40
40
  if (value !== this.deviceData.light_enabled) {
41
- this.set({ light_enabled: value });
41
+ this.set({ uuid: this.deviceData.nest_google_uuid, light_enabled: value });
42
42
 
43
43
  this?.log?.info && this.log.info('Floodlight on "%s" was turned', this.deviceData.description, value === true ? 'on' : 'off');
44
44
  }
@@ -46,9 +46,9 @@ export default class NestFloodlight extends NestCamera {
46
46
 
47
47
  this.lightService.getCharacteristic(this.hap.Characteristic.Brightness).onSet((value) => {
48
48
  if (value !== this.deviceData.light_brightness) {
49
- this.set({ light_brightness: value });
49
+ this.set({ uuid: this.deviceData.nest_google_uuid, light_brightness: value });
50
50
 
51
- this?.log?.info && this.log.info('Floodlight brightness on "%s" was set to "%s %"', this.deviceData.description);
51
+ this?.log?.info && this.log.info('Floodlight brightness on "%s" was set to "%s %"', this.deviceData.description, value);
52
52
  }
53
53
  });
54
54
 
package/dist/nexustalk.js CHANGED
@@ -5,7 +5,7 @@
5
5
  //
6
6
  // Credit to https://github.com/Brandawg93/homebridge-nest-cam for the work on the Nest Camera comms code on which this is based
7
7
  //
8
- // Code version 11/9/2024
8
+ // Code version 29/9/2024
9
9
  // Mark Hulskamp
10
10
  'use strict';
11
11
 
@@ -55,15 +55,22 @@ const PacketType = {
55
55
  AUTHORIZE_REQUEST: 212,
56
56
  };
57
57
 
58
+ // Blank audio in AAC format, mono channel @48000
59
+ const AACMONO48000BLANK = Buffer.from([
60
+ 0xff, 0xf1, 0x4c, 0x40, 0x03, 0x9f, 0xfc, 0xde, 0x02, 0x00, 0x4c, 0x61, 0x76, 0x63, 0x35, 0x39, 0x2e, 0x31, 0x38, 0x2e, 0x31, 0x30, 0x30,
61
+ 0x00, 0x02, 0x30, 0x40, 0x0e,
62
+ ]);
63
+
58
64
  // nexusTalk object
59
65
  export default class NexusTalk extends Streamer {
60
66
  token = undefined;
61
67
  tokenType = undefined;
62
- id = undefined; // Session ID
63
68
  pingTimer = undefined; // Timer object for ping interval
64
69
  stalledTimer = undefined; // Timer object for no received data
65
- video = {}; // Video stream details
66
- audio = {}; // Audio stream details
70
+ host = ''; // Host to connect to or connected too
71
+ blankAudio = AACMONO48000BLANK;
72
+ video = {}; // Video stream details once connected
73
+ audio = {}; // Audio stream details once connected
67
74
 
68
75
  // Internal data only for this class
69
76
  #protobufNexusTalk = undefined; // Protobuf for NexusTalk
@@ -71,6 +78,7 @@ export default class NexusTalk extends Streamer {
71
78
  #packets = []; // Incoming packets
72
79
  #messages = []; // Incoming messages
73
80
  #authorised = false; // Have we been authorised
81
+ #id = undefined; // Session ID
74
82
 
75
83
  constructor(deviceData, options) {
76
84
  super(deviceData, options);
@@ -86,8 +94,15 @@ export default class NexusTalk extends Streamer {
86
94
  this.tokenType = deviceData?.apiAccess?.oauth2 !== undefined ? 'google' : 'nest';
87
95
  this.host = deviceData?.streaming_host; // Host we'll connect to
88
96
 
97
+ // Set our streamer codec types
98
+ this.codecs = {
99
+ video: 'h264',
100
+ audio: 'aac',
101
+ talk: 'speex',
102
+ };
103
+
89
104
  // If specified option to start buffering, kick off
90
- if (typeof options?.buffer === 'boolean' && options.buffer === true) {
105
+ if (options?.buffer === true) {
91
106
  this.startBuffering();
92
107
  }
93
108
  }
@@ -95,10 +110,11 @@ export default class NexusTalk extends Streamer {
95
110
  // Class functions
96
111
  connect(host) {
97
112
  // Clear any timers we have running
98
- this.pingTimer = clearInterval(this.pingTimer);
99
- this.stalledTimer = clearInterval(this.stalledTimer);
100
-
101
- this.id = undefined; // No session ID yet
113
+ clearInterval(this.pingTimer);
114
+ clearTimeout(this.stalledTimer);
115
+ this.pingTimer = undefined;
116
+ this.stalledTimer = undefined;
117
+ this.#id = undefined; // No session ID yet
102
118
 
103
119
  if (this.online === true && this.videoEnabled === true) {
104
120
  if (typeof host === 'undefined' || host === null) {
@@ -106,7 +122,8 @@ export default class NexusTalk extends Streamer {
106
122
  host = this.host;
107
123
  }
108
124
 
109
- this?.log?.debug && this.log.debug('Starting connection to "%s"', host);
125
+ this.connected = false; // Starting connection
126
+ this?.log?.debug && this.log.debug('Connection started to "%s"', host);
110
127
 
111
128
  this.#socket = tls.connect({ host: host, port: 1443 }, () => {
112
129
  // Opened connection to Nexus server, so now need to authenticate ourselves
@@ -129,12 +146,14 @@ export default class NexusTalk extends Streamer {
129
146
  this.#socket.on('close', (hadError) => {
130
147
  this?.log?.debug && this.log.debug('Connection closed to "%s"', host);
131
148
 
132
- this.stalledTimer = clearTimeout(this.stalledTimer); // Clear stalled timer
133
- this.pingTimer = clearInterval(this.pingTimer); // Clear ping timer
149
+ clearInterval(this.pingTimer);
150
+ clearTimeout(this.stalledTimer);
151
+ this.pingTimer = undefined;
152
+ this.stalledTimer = undefined;
134
153
  this.#authorised = false; // Since connection close, we can't be authorised anymore
135
154
  this.#socket = undefined; // Clear socket object
136
- this.connected = false;
137
- this.id = undefined; // Not an active session anymore
155
+ this.connected = undefined;
156
+ this.#id = undefined; // Not an active session anymore
138
157
 
139
158
  if (hadError === true && this.haveOutputs() === true) {
140
159
  // We still have either active buffering occuring or output streams running
@@ -147,7 +166,7 @@ export default class NexusTalk extends Streamer {
147
166
 
148
167
  close(stopStreamFirst) {
149
168
  // Close an authenicated socket stream gracefully
150
- if (this.#socket !== null) {
169
+ if (this.#socket !== undefined) {
151
170
  if (stopStreamFirst === true) {
152
171
  // Send a notifcation to nexus we're finished playback
153
172
  this.#stopNexusData();
@@ -155,11 +174,13 @@ export default class NexusTalk extends Streamer {
155
174
  this.#socket.destroy();
156
175
  }
157
176
 
158
- this.connected = false;
177
+ this.connected = undefined;
159
178
  this.#socket = undefined;
160
- this.id = undefined; // Not an active session anymore
179
+ this.#id = undefined; // Not an active session anymore
161
180
  this.#packets = [];
162
181
  this.#messages = [];
182
+ this.video = {};
183
+ this.audio = {};
163
184
  }
164
185
 
165
186
  update(deviceData) {
@@ -171,11 +192,16 @@ export default class NexusTalk extends Streamer {
171
192
  // access token has changed so re-authorise
172
193
  this.token = deviceData.apiAccess.token;
173
194
 
174
- if (this.#socket !== null) {
195
+ if (this.#socket !== undefined) {
175
196
  this.#Authenticate(true); // Update authorisation only if connected
176
197
  }
177
198
  }
178
199
 
200
+ if (this.host !== deviceData.streaming_host) {
201
+ this.host = deviceData.streaming_host;
202
+ this?.log?.debug && this.log.debug('New host has been requested for connection. Host requested is "%s"', this.host);
203
+ }
204
+
179
205
  // Let our parent handle the remaining updates
180
206
  super.update(deviceData);
181
207
  }
@@ -183,13 +209,13 @@ export default class NexusTalk extends Streamer {
183
209
  talkingAudio(talkingData) {
184
210
  // Encode audio packet for sending to camera
185
211
  if (typeof talkingData === 'object' && this.#protobufNexusTalk !== undefined) {
186
- let TraitMap = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.StartPlayback');
212
+ let TraitMap = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.AudioPayload');
187
213
  if (TraitMap !== null) {
188
214
  let encodedData = TraitMap.encode(
189
215
  TraitMap.fromObject({
190
216
  payload: talkingData,
191
- sessionId: this.id,
192
- codec: 'SPEEX',
217
+ sessionId: this.#id,
218
+ codec: this.codecs.talk.toUpperCase(),
193
219
  sampleRate: 16000,
194
220
  }),
195
221
  ).finish();
@@ -227,12 +253,12 @@ export default class NexusTalk extends Streamer {
227
253
  }
228
254
 
229
255
  #stopNexusData() {
230
- if (this.id !== undefined && this.#protobufNexusTalk !== undefined) {
256
+ if (this.#id !== undefined && this.#protobufNexusTalk !== undefined) {
231
257
  let TraitMap = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.StopPlayback');
232
258
  if (TraitMap !== null) {
233
259
  let encodedData = TraitMap.encode(
234
260
  TraitMap.fromObject({
235
- sessionId: this.id,
261
+ sessionId: this.#id,
236
262
  }),
237
263
  ).finish();
238
264
  this.#sendMessage(PacketType.STOP_PLAYBACK, encodedData);
@@ -241,7 +267,7 @@ export default class NexusTalk extends Streamer {
241
267
  }
242
268
 
243
269
  #sendMessage(type, data) {
244
- if (this.#socket === null || this.#socket.readyState !== 'open' || (type !== PacketType.HELLO && this.#authorised === false)) {
270
+ if (this.#socket?.readyState !== 'open' || (type !== PacketType.HELLO && this.#authorised === false)) {
245
271
  // We're not connect and/or authorised yet, so 'cache' message for processing once this occurs
246
272
  this.#messages.push({ type: type, data: data });
247
273
  return;
@@ -339,30 +365,31 @@ export default class NexusTalk extends Streamer {
339
365
  let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.PlaybackBegin').decode(payload).toJSON();
340
366
  decodedMessage.channels.forEach((stream) => {
341
367
  // Find which channels match our video and audio streams
342
- if (stream.codecType === 'H264') {
368
+ if (stream.codecType === this.codecs.video.toUpperCase()) {
343
369
  this.video = {
344
- channel_id: stream.channelId,
345
- start_time: Date.now() + stream.startTime,
346
- sample_rate: stream.sampleRate,
347
- timestamp_delta: 0,
370
+ id: stream.channelId,
371
+ startTime: Date.now() + stream.startTime,
372
+ sampleRate: stream.sampleRate,
373
+ timeStamp: 0,
348
374
  };
349
375
  }
350
- if (stream.codecType === 'AAC' || stream.codecType === 'OPUS' || stream.codecType === 'SPEEX') {
376
+ if (stream.codecType === this.codecs.audio.toUpperCase()) {
351
377
  this.audio = {
352
- channel_id: stream.channelId,
353
- start_time: Date.now() + stream.startTime,
354
- sample_rate: stream.sampleRate,
355
- timestamp_delta: 0,
378
+ id: stream.channelId,
379
+ startTime: Date.now() + stream.startTime,
380
+ sampleRate: stream.sampleRate,
381
+ timeStamp: 0,
382
+ talking: false,
356
383
  };
357
384
  }
358
385
  });
359
386
 
360
387
  // Since this is the beginning of playback, clear any active buffers contents
361
- this.id = decodedMessage.sessionId;
388
+ this.#id = decodedMessage.sessionId;
362
389
  this.#packets = [];
363
390
  this.#messages = [];
364
391
 
365
- this?.log?.debug && this.log.debug('Playback started from "%s" with session ID "%s"', this.host, this.id);
392
+ this?.log?.debug && this.log.debug('Playback started from "%s" with session ID "%s"', this.host, this.#id);
366
393
  }
367
394
  }
368
395
 
@@ -374,28 +401,31 @@ export default class NexusTalk extends Streamer {
374
401
  // Setup up a timeout to monitor for no packets recieved in a certain period
375
402
  // If its trigger, we'll attempt to restart the stream and/or connection
376
403
  // <-- testing to see how often this occurs first
377
- this.stalledTimer = clearTimeout(this.stalledTimer);
404
+ clearTimeout(this.stalledTimer);
378
405
  this.stalledTimer = setTimeout(() => {
379
- this?.log?.debug && this.log.debug('We have not received any data from nexus in the past "%s" seconds. Attempting restart', 8);
406
+ this?.log?.debug &&
407
+ this.log.debug(
408
+ 'We have not received any data from nexus in the past "%s" seconds for uuid "%s". Attempting restart',
409
+ 10,
410
+ this.uuid,
411
+ );
380
412
 
381
413
  // Setup listener for socket close event. Once socket is closed, we'll perform the re-connection
382
414
  this.#socket &&
383
415
  this.#socket.on('close', () => {
384
- this.connect(this.host); // try reconnection
416
+ this.connect(); // try reconnection
385
417
  });
386
418
  this.close(false); // Close existing socket
387
419
  }, 8000);
388
420
 
389
421
  // Handle video packet
390
- if (decodedMessage.channelId === this.video.channel_id) {
391
- this.video.timestamp_delta += decodedMessage.timestampDelta;
392
- this.addToOutput('video', this.video.start_time + this.video.timestamp_delta, Buffer.from(decodedMessage.payload, 'base64'));
422
+ if (decodedMessage?.channelId !== undefined && decodedMessage.channelId === this.video?.id) {
423
+ this.addToOutput('video', Buffer.from(decodedMessage.payload, 'base64'));
393
424
  }
394
425
 
395
426
  // Handle audio packet
396
- if (decodedMessage.channelId === this.audio.channel_id) {
397
- this.audio.timestamp_delta += decodedMessage.timestampDelta;
398
- this.addToOutput('audio', this.audio.start_time + this.audio.timestamp_delta, Buffer.from(decodedMessage.payload, 'base64'));
427
+ if (decodedMessage?.channelId !== undefined && decodedMessage.channelId === this.audio?.id) {
428
+ this.addToOutput('audio', Buffer.from(decodedMessage.payload, 'base64'));
399
429
  }
400
430
  }
401
431
  }
@@ -405,7 +435,7 @@ export default class NexusTalk extends Streamer {
405
435
  if (typeof payload === 'object' && this.#protobufNexusTalk !== undefined) {
406
436
  let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.PlaybackEnd').decode(payload).toJSON();
407
437
 
408
- if (this.id !== null && decodedMessage.reason === 'USER_ENDED_SESSION') {
438
+ if (this.#id !== undefined && decodedMessage.reason === 'USER_ENDED_SESSION') {
409
439
  // Normal playback ended ie: when we stopped playback
410
440
  this?.log?.debug && this.log.debug('Playback ended on "%s"', this.host);
411
441
  }
@@ -418,7 +448,7 @@ export default class NexusTalk extends Streamer {
418
448
  // Setup listener for socket close event. Once socket is closed, we'll perform the re-connection
419
449
  this.#socket &&
420
450
  this.#socket.on('close', () => {
421
- this.connect(this.host); // try reconnection to existing host
451
+ this.connect(); // try reconnection to existing host
422
452
  });
423
453
  this.close(false); // Close existing socket
424
454
  }
@@ -442,16 +472,18 @@ export default class NexusTalk extends Streamer {
442
472
  #handleTalkbackBegin(payload) {
443
473
  // Decode talk begin packet
444
474
  if (typeof payload === 'object' && this.#protobufNexusTalk !== undefined) {
445
- let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.TalkbackBegin').decode(payload).toJSON();
446
- this?.log?.debug && this.log.debug('Talkback started on "%s"', decodedMessage.deviceId);
475
+ //let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.TalkbackBegin').decode(payload).toJSON();
476
+ this.audio.talking = true;
477
+ this?.log?.debug && this.log.debug(Streamer.TALKINGSTART, this.uuid);
447
478
  }
448
479
  }
449
480
 
450
481
  #handleTalkbackEnd(payload) {
451
482
  // Decode talk end packet
452
483
  if (typeof payload === 'object' && this.#protobufNexusTalk !== undefined) {
453
- let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.TalkbackEnd').decode(payload).toJSON();
454
- this?.log?.debug && this.log.debug('Talkback ended on "%s"', decodedMessage.device_id);
484
+ //let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.TalkbackEnd').decode(payload).toJSON();
485
+ this.audio.talking = false;
486
+ this?.log?.debug && this.log.debug('Talking ended on uuid "%s"', this.uuid);
455
487
  }
456
488
  }
457
489
 
@@ -491,7 +523,7 @@ export default class NexusTalk extends Streamer {
491
523
  }
492
524
 
493
525
  // Periodically send PING message to keep stream alive
494
- this.pingTimer = clearInterval(this.pingTimer);
526
+ clearInterval(this.pingTimer);
495
527
  this.pingTimer = setInterval(() => {
496
528
  this.#sendMessage(PacketType.PING, Buffer.alloc(0));
497
529
  }, PINGINTERVAL);
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 12/9/2024
4
+ // Code version 27/9/2024
5
5
  // Mark Hulskamp
6
6
  'use strict';
7
7
 
@@ -233,7 +233,7 @@ export default class NestProtect extends HomeKitDevice {
233
233
  //this.log.info('Eve Smoke Alarm test', (EveHomeSetData.alarmtest === true ? 'start' : 'stop'));
234
234
  }
235
235
  if (typeof EveHomeSetData?.statusled === 'boolean') {
236
- this.set({ ntp_green_led_enable: EveHomeSetData.statusled });
236
+ this.set({ uuid: this.deviceData.nest_google_uuid, ntp_green_led_enable: EveHomeSetData.statusled });
237
237
  }
238
238
  }
239
239
  }