homebridge-nest-accfactory 0.2.9 → 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 +29 -1
- package/README.md +14 -7
- package/config.schema.json +126 -1
- 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/google/trait/product/camera.proto +1 -1
- package/dist/protobuf/googlehome/foyer.proto +0 -46
- 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 +80 -80
- 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/devices.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Device support loader
|
|
2
|
+
// Part of homebridge-nest-accfactory
|
|
3
|
+
//
|
|
4
|
+
// Code version 2025.06.12
|
|
5
|
+
// Mark Hulskamp
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
// Define nodejs module requirements
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import fs from 'node:fs/promises';
|
|
11
|
+
import url from 'node:url';
|
|
12
|
+
|
|
13
|
+
// Import our modules
|
|
14
|
+
import HomeKitDevice from './HomeKitDevice.js';
|
|
15
|
+
|
|
16
|
+
// Define constants
|
|
17
|
+
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
18
|
+
const DeviceType = Object.freeze({
|
|
19
|
+
THERMOSTAT: 'Thermostat',
|
|
20
|
+
TEMPSENSOR: 'TemperatureSensor',
|
|
21
|
+
SMOKESENSOR: 'Protect',
|
|
22
|
+
CAMERA: 'Camera',
|
|
23
|
+
DOORBELL: 'Doorbell',
|
|
24
|
+
FLOODLIGHT: 'FloodlightCamera',
|
|
25
|
+
WEATHER: 'Weather',
|
|
26
|
+
HEATLINK: 'Heatlink',
|
|
27
|
+
LOCK: 'Lock',
|
|
28
|
+
ALARM: 'Alarm',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
async function loadDeviceModules(log, pluginDir = '') {
|
|
32
|
+
let baseDir = path.join(__dirname, pluginDir);
|
|
33
|
+
let deviceMap = new Map();
|
|
34
|
+
let files = (await fs.readdir(baseDir)).sort();
|
|
35
|
+
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
if (file.endsWith('.js') === false) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
let module = await import(url.pathToFileURL(path.join(baseDir, file)).href);
|
|
43
|
+
let exportsToCheck = Object.values(module);
|
|
44
|
+
|
|
45
|
+
for (const exported of exportsToCheck) {
|
|
46
|
+
if (typeof exported !== 'function') {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let proto = Object.getPrototypeOf(exported);
|
|
51
|
+
while (proto !== undefined && proto.name !== '') {
|
|
52
|
+
if (proto === HomeKitDevice) {
|
|
53
|
+
if (
|
|
54
|
+
typeof exported.TYPE !== 'string' ||
|
|
55
|
+
exported.TYPE === '' ||
|
|
56
|
+
typeof exported.VERSION !== 'string' ||
|
|
57
|
+
exported.VERSION === ''
|
|
58
|
+
) {
|
|
59
|
+
log?.warn?.('Skipping device module %s (missing TYPE or VERSION)', file);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (deviceMap.has(exported.TYPE) === false) {
|
|
64
|
+
deviceMap.set(exported.TYPE, exported);
|
|
65
|
+
log?.info?.('Loaded device module "%s" (v%s)', exported.TYPE, exported.VERSION);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
proto = Object.getPrototypeOf(proto);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
log?.warn?.('Failed to load device support file "%s": %s', file, error.message);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return deviceMap;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getDeviceHKCategory(type) {
|
|
83
|
+
let category = 1; // Categories.OTHER
|
|
84
|
+
|
|
85
|
+
if (type === DeviceType.LOCK) {
|
|
86
|
+
category = 6; // Categories.DOOR_LOCK
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (type === DeviceType.THERMOSTAT) {
|
|
90
|
+
category = 9; // Categories.THERMOSTAT
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (type === DeviceType.TEMPSENSOR || type === DeviceType.HEATLINK || type === DeviceType.SMOKESENSOR || type === DeviceType.WEATHER) {
|
|
94
|
+
category = 10; // Categories.SENSOR
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (type === DeviceType.ALARM) {
|
|
98
|
+
category = 11; // Categories.SECURITY_SYSTEM
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (type === DeviceType.CAMERA || type === DeviceType.FLOODLIGHT) {
|
|
102
|
+
category = 17; // Categories.IP_CAMERA
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (type === DeviceType.DOORBELL) {
|
|
106
|
+
category = 18; // Categories.VIDEO_DOORBELL
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return category;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Define exports
|
|
113
|
+
export { DeviceType, loadDeviceModules, getDeviceHKCategory };
|
package/dist/index.js
CHANGED
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
// Nest Temp Sensors (1st gen)
|
|
11
11
|
// Nest Cameras (Cam Indoor, IQ Indoor, Outdoor, IQ Outdoor, Cam with Floodlight)
|
|
12
12
|
// Nest Doorbells (wired 1st gen)
|
|
13
|
+
// Nest HeatLink
|
|
13
14
|
//
|
|
14
15
|
// The accessory supports authentication to Nest/Google using either a Nest account OR Google (migrated Nest account) account.
|
|
15
16
|
// "preliminary" support for using FieldTest account types also.
|
|
16
17
|
//
|
|
17
18
|
// Supports both Nest REST and Protobuf APIs for communication
|
|
18
19
|
//
|
|
19
|
-
// Code version
|
|
20
|
+
// Code version 2025.06.05
|
|
20
21
|
// Mark Hulskamp
|
|
21
22
|
'use strict';
|
|
22
23
|
|
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
|
|
8
|
+
// Code version 2025.06.10
|
|
9
9
|
// Mark Hulskamp
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
@@ -123,11 +123,11 @@ export default class NexusTalk extends Streamer {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
this.connected = false; // Starting connection
|
|
126
|
-
this?.log?.debug
|
|
126
|
+
this?.log?.debug?.('Connection started to "%s"', host);
|
|
127
127
|
|
|
128
128
|
this.#socket = tls.connect({ host: host, port: 1443 }, () => {
|
|
129
129
|
// Opened connection to Nexus server, so now need to authenticate ourselves
|
|
130
|
-
this?.log?.debug
|
|
130
|
+
this?.log?.debug?.('Connection established to "%s"', host);
|
|
131
131
|
|
|
132
132
|
this.#socket.setKeepAlive(true); // Keep socket connection alive
|
|
133
133
|
this.host = host; // update internal host name since we've connected
|
|
@@ -144,7 +144,7 @@ export default class NexusTalk extends Streamer {
|
|
|
144
144
|
});
|
|
145
145
|
|
|
146
146
|
this.#socket.on('close', (hadError) => {
|
|
147
|
-
this?.log?.debug
|
|
147
|
+
this?.log?.debug?.('Connection closed to "%s"', host);
|
|
148
148
|
|
|
149
149
|
clearInterval(this.pingTimer);
|
|
150
150
|
clearTimeout(this.stalledTimer);
|
|
@@ -199,7 +199,7 @@ export default class NexusTalk extends Streamer {
|
|
|
199
199
|
|
|
200
200
|
if (this.host !== deviceData.streaming_host) {
|
|
201
201
|
this.host = deviceData.streaming_host;
|
|
202
|
-
this?.log?.debug
|
|
202
|
+
this?.log?.debug?.('New host has been requested for connection. Host requested is "%s"', this.host);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
// Let our parent handle the remaining updates
|
|
@@ -308,7 +308,7 @@ export default class NexusTalk extends Streamer {
|
|
|
308
308
|
|
|
309
309
|
if (reauthorise === true && authoriseRequest !== null) {
|
|
310
310
|
// Request to re-authorise only
|
|
311
|
-
this?.log?.debug
|
|
311
|
+
this?.log?.debug?.('Re-authentication requested to "%s"', this.host);
|
|
312
312
|
this.#sendMessage(PacketType.AUTHORIZE_REQUEST, authoriseRequest);
|
|
313
313
|
}
|
|
314
314
|
|
|
@@ -316,7 +316,7 @@ export default class NexusTalk extends Streamer {
|
|
|
316
316
|
// This isn't a re-authorise request, so perform 'Hello' packet
|
|
317
317
|
let TraitMap = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.Hello');
|
|
318
318
|
if (TraitMap !== null) {
|
|
319
|
-
this?.log?.debug
|
|
319
|
+
this?.log?.debug?.('Performing authentication to "%s"', this.host);
|
|
320
320
|
|
|
321
321
|
let encodedData = TraitMap.encode(
|
|
322
322
|
TraitMap.fromObject({
|
|
@@ -350,7 +350,7 @@ export default class NexusTalk extends Streamer {
|
|
|
350
350
|
return;
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
this?.log?.debug
|
|
353
|
+
this?.log?.debug?.('Redirect requested from "%s" to "%s"', this.host, redirectToHost);
|
|
354
354
|
|
|
355
355
|
// Setup listener for socket close event. Once socket is closed, we'll perform the redirect
|
|
356
356
|
this.#socket &&
|
|
@@ -389,7 +389,7 @@ export default class NexusTalk extends Streamer {
|
|
|
389
389
|
this.#packets = [];
|
|
390
390
|
this.#messages = [];
|
|
391
391
|
|
|
392
|
-
this?.log?.debug
|
|
392
|
+
this?.log?.debug?.('Playback started from "%s" with session ID "%s"', this.host, this.#id);
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
395
|
|
|
@@ -403,12 +403,11 @@ export default class NexusTalk extends Streamer {
|
|
|
403
403
|
// <-- testing to see how often this occurs first
|
|
404
404
|
clearTimeout(this.stalledTimer);
|
|
405
405
|
this.stalledTimer = setTimeout(() => {
|
|
406
|
-
this?.log?.debug
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
);
|
|
406
|
+
this?.log?.debug?.(
|
|
407
|
+
'We have not received any data from nexus in the past "%s" seconds for uuid "%s". Attempting restart',
|
|
408
|
+
10,
|
|
409
|
+
this.uuid,
|
|
410
|
+
);
|
|
412
411
|
|
|
413
412
|
// Setup listener for socket close event. Once socket is closed, we'll perform the re-connection
|
|
414
413
|
this.#socket &&
|
|
@@ -437,13 +436,12 @@ export default class NexusTalk extends Streamer {
|
|
|
437
436
|
|
|
438
437
|
if (this.#id !== undefined && decodedMessage.reason === 'USER_ENDED_SESSION') {
|
|
439
438
|
// Normal playback ended ie: when we stopped playback
|
|
440
|
-
this?.log?.debug
|
|
439
|
+
this?.log?.debug?.('Playback ended on "%s"', this.host);
|
|
441
440
|
}
|
|
442
441
|
|
|
443
442
|
if (decodedMessage.reason !== 'USER_ENDED_SESSION') {
|
|
444
443
|
// Error during playback, so we'll attempt to restart by reconnection to host
|
|
445
|
-
this?.log?.debug
|
|
446
|
-
this.log.debug('Playback ended on "%s" with error "%s". Attempting reconnection', this.host, decodedMessage.reason);
|
|
444
|
+
this?.log?.debug?.('Playback ended on "%s" with error "%s". Attempting reconnection', this.host, decodedMessage.reason);
|
|
447
445
|
|
|
448
446
|
// Setup listener for socket close event. Once socket is closed, we'll perform the re-connection
|
|
449
447
|
this.#socket &&
|
|
@@ -464,7 +462,7 @@ export default class NexusTalk extends Streamer {
|
|
|
464
462
|
this.#Authenticate(true); // Update authorisation only
|
|
465
463
|
} else {
|
|
466
464
|
// NexusStreamer Error, packet.message contains the message
|
|
467
|
-
this?.log?.debug
|
|
465
|
+
this?.log?.debug?.('Error', decodedMessage.message);
|
|
468
466
|
}
|
|
469
467
|
}
|
|
470
468
|
}
|
|
@@ -474,7 +472,7 @@ export default class NexusTalk extends Streamer {
|
|
|
474
472
|
if (typeof payload === 'object' && this.#protobufNexusTalk !== undefined) {
|
|
475
473
|
//let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.TalkbackBegin').decode(payload).toJSON();
|
|
476
474
|
this.audio.talking = true;
|
|
477
|
-
this?.log?.debug
|
|
475
|
+
this?.log?.debug?.('Talking started on uuid "%s"', this.uuid);
|
|
478
476
|
}
|
|
479
477
|
}
|
|
480
478
|
|
|
@@ -483,7 +481,7 @@ export default class NexusTalk extends Streamer {
|
|
|
483
481
|
if (typeof payload === 'object' && this.#protobufNexusTalk !== undefined) {
|
|
484
482
|
//let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.TalkbackEnd').decode(payload).toJSON();
|
|
485
483
|
this.audio.talking = false;
|
|
486
|
-
this?.log?.debug
|
|
484
|
+
this?.log?.debug?.('Talking ended on uuid "%s"', this.uuid);
|
|
487
485
|
}
|
|
488
486
|
}
|
|
489
487
|
|