homebridge-nest-accfactory 0.2.11 → 0.3.1
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 +28 -0
- package/README.md +14 -7
- package/config.schema.json +118 -0
- package/dist/HomeKitDevice.js +203 -77
- package/dist/HomeKitHistory.js +1 -1
- package/dist/config.js +207 -0
- package/dist/devices.js +118 -0
- package/dist/index.js +2 -1
- package/dist/nexustalk.js +46 -48
- package/dist/{camera.js → plugins/camera.js} +216 -241
- 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} +26 -43
- package/dist/{tempsensor.js → plugins/tempsensor.js} +15 -19
- package/dist/{thermostat.js → plugins/thermostat.js} +426 -383
- 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 +74 -78
- package/dist/system.js +1213 -1264
- package/dist/webrtc.js +39 -34
- package/package.json +11 -11
- package/dist/floodlight.js +0 -97
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Nest Cameras
|
|
2
2
|
// Part of homebridge-nest-accfactory
|
|
3
3
|
//
|
|
4
|
-
// Code version 2025/03/19
|
|
5
4
|
// Mark Hulskamp
|
|
6
5
|
'use strict';
|
|
7
6
|
|
|
@@ -18,18 +17,28 @@ import path from 'node:path';
|
|
|
18
17
|
import { fileURLToPath } from 'node:url';
|
|
19
18
|
|
|
20
19
|
// Define our modules
|
|
21
|
-
import HomeKitDevice from '
|
|
22
|
-
import NexusTalk from '
|
|
23
|
-
import WebRTC from '
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
import HomeKitDevice from '../HomeKitDevice.js';
|
|
21
|
+
import NexusTalk from '../nexustalk.js';
|
|
22
|
+
import WebRTC from '../webrtc.js';
|
|
23
|
+
|
|
24
|
+
const CAMERA_RESOURCE = {
|
|
25
|
+
OFFLINE: 'Nest_camera_offline.jpg',
|
|
26
|
+
OFF: 'Nest_camera_off.jpg',
|
|
27
|
+
TRANSFER: 'Nest_camera_transfer.jpg',
|
|
28
|
+
};
|
|
28
29
|
const MP4BOX = 'mp4box'; // MP4 box fragement event for HKSV recording
|
|
29
|
-
const
|
|
30
|
+
const SNAPSHOT_CACHE_TIMEOUT = 30000; // Timeout for retaining snapshot image (in milliseconds)
|
|
31
|
+
const STREAMING_PROTOCOL = {
|
|
32
|
+
WEBRTC: 'PROTOCOL_WEBRTC',
|
|
33
|
+
NEXUSTALK: 'PROTOCOL_NEXUSTALK',
|
|
34
|
+
};
|
|
35
|
+
const RESOURCE_PATH = '../res';
|
|
30
36
|
const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Make a defined for JS __dirname
|
|
31
37
|
|
|
32
38
|
export default class NestCamera extends HomeKitDevice {
|
|
39
|
+
static TYPE = 'Camera';
|
|
40
|
+
static VERSION = '2025.06.15';
|
|
41
|
+
|
|
33
42
|
controller = undefined; // HomeKit Camera/Doorbell controller service
|
|
34
43
|
streamer = undefined; // Streamer object for live/recording stream
|
|
35
44
|
motionServices = undefined; // Object of Camera/Doorbell motion sensor(s)
|
|
@@ -51,27 +60,25 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
51
60
|
constructor(accessory, api, log, eventEmitter, deviceData) {
|
|
52
61
|
super(accessory, api, log, eventEmitter, deviceData);
|
|
53
62
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
63
|
+
// Load supporrt image files as required
|
|
64
|
+
const loadImageIfExists = (filename, label) => {
|
|
65
|
+
let buffer = undefined;
|
|
66
|
+
let file = path.resolve(__dirname, RESOURCE_PATH, filename);
|
|
67
|
+
if (fs.existsSync(file) === true) {
|
|
68
|
+
buffer = fs.readFileSync(file);
|
|
69
|
+
} else {
|
|
70
|
+
this.log?.warn?.('Failed to load %s image resource for "%s"', label, this.deviceData.description);
|
|
71
|
+
}
|
|
72
|
+
return buffer;
|
|
73
|
+
};
|
|
65
74
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.#cameraTransferringImage = fs.readFileSync(imageFile);
|
|
70
|
-
}
|
|
75
|
+
this.#cameraOfflineImage = loadImageIfExists(CAMERA_RESOURCE.OFFLINE, 'offline');
|
|
76
|
+
this.#cameraVideoOffImage = loadImageIfExists(CAMERA_RESOURCE.OFF, 'video off');
|
|
77
|
+
this.#cameraTransferringImage = loadImageIfExists(CAMERA_RESOURCE.TRANSFER, 'transferring');
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
// Class functions
|
|
74
|
-
|
|
81
|
+
setupDevice(hapController = this.hap.CameraController) {
|
|
75
82
|
// Setup motion services
|
|
76
83
|
if (this.motionServices === undefined) {
|
|
77
84
|
this.createCameraMotionServices();
|
|
@@ -92,19 +99,13 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
92
99
|
if (this.operatingModeService === undefined) {
|
|
93
100
|
// Add in operating mode service for a non-hksv camera/doorbell
|
|
94
101
|
// Allow us to change things such as night vision, camera indicator etc within HomeKit for those also :-)
|
|
95
|
-
this.operatingModeService = this.
|
|
96
|
-
if (this.operatingModeService === undefined) {
|
|
97
|
-
this.operatingModeService = this.accessory.addService(this.hap.Service.CameraOperatingMode, '', 1);
|
|
98
|
-
}
|
|
102
|
+
this.operatingModeService = this.addHKService(this.hap.Service.CameraOperatingMode, '', 1);
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
// Setup set
|
|
102
|
-
if (this.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.operatingModeService.addOptionalCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator);
|
|
106
|
-
}
|
|
107
|
-
this.operatingModeService.getCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator).onSet((value) => {
|
|
105
|
+
// Setup set characteristics
|
|
106
|
+
if (this.deviceData?.has_statusled === true) {
|
|
107
|
+
this.addHKCharacteristic(this.operatingModeService, this.hap.Characteristic.CameraOperatingModeIndicator, {
|
|
108
|
+
onSet: (value) => {
|
|
108
109
|
// 0 = auto, 1 = low, 2 = high
|
|
109
110
|
// We'll use auto mode for led on and low for led off
|
|
110
111
|
if (
|
|
@@ -112,43 +113,33 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
112
113
|
(value === false && this.deviceData.statusled_brightness !== 1)
|
|
113
114
|
) {
|
|
114
115
|
this.set({ uuid: this.deviceData.nest_google_uuid, statusled_brightness: value === true ? 0 : 1 });
|
|
115
|
-
|
|
116
|
-
this.log.info('Recording status LED on "%s" was turned', this.deviceData.description, value === true ? 'on' : 'off');
|
|
117
|
-
}
|
|
116
|
+
this?.log?.info?.('Recording status LED on "%s" was turned', this.deviceData.description, value === true ? 'on' : 'off');
|
|
118
117
|
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
this.operatingModeService.getCharacteristic(this.hap.Characteristic.CameraOperatingModeIndicator).onGet(() => {
|
|
118
|
+
},
|
|
119
|
+
onGet: () => {
|
|
122
120
|
return this.deviceData.statusled_brightness !== 1;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (this.deviceData.has_irled === true) {
|
|
127
|
-
if (this.operatingModeService.testCharacteristic(this.hap.Characteristic.NightVision) === false) {
|
|
128
|
-
this.operatingModeService.addOptionalCharacteristic(this.hap.Characteristic.NightVision);
|
|
129
|
-
}
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
130
124
|
|
|
131
|
-
|
|
125
|
+
if (this.deviceData?.has_irled === true) {
|
|
126
|
+
this.addHKCharacteristic(this.operatingModeService, this.hap.Characteristic.NightVision, {
|
|
127
|
+
onSet: (value) => {
|
|
132
128
|
// only change IRLed status value if different than on-device
|
|
133
129
|
if ((value === false && this.deviceData.irled_enabled === true) || (value === true && this.deviceData.irled_enabled === false)) {
|
|
134
130
|
this.set({ uuid: this.deviceData.nest_google_uuid, irled_enabled: value === true ? 'auto_on' : 'always_off' });
|
|
135
131
|
|
|
136
|
-
|
|
137
|
-
this.log.info('Night vision on "%s" was turned', this.deviceData.description, value === true ? 'on' : 'off');
|
|
138
|
-
}
|
|
132
|
+
this?.log?.info?.('Night vision on "%s" was turned', this.deviceData.description, value === true ? 'on' : 'off');
|
|
139
133
|
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
this.operatingModeService.getCharacteristic(this.hap.Characteristic.NightVision).onGet(() => {
|
|
134
|
+
},
|
|
135
|
+
onGet: () => {
|
|
143
136
|
return this.deviceData.irled_enabled;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (this.operatingModeService.testCharacteristic(this.hap.Characteristic.ManuallyDisabled) === false) {
|
|
148
|
-
this.operatingModeService.addOptionalCharacteristic(this.hap.Characteristic.ManuallyDisabled);
|
|
149
|
-
}
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
150
140
|
|
|
151
|
-
|
|
141
|
+
this.addHKCharacteristic(this.operatingModeService, this.hap.Characteristic.ManuallyDisabled, {
|
|
142
|
+
onSet: (value) => {
|
|
152
143
|
if (value !== this.operatingModeService.getCharacteristic(this.hap.Characteristic.ManuallyDisabled).value) {
|
|
153
144
|
// Make sure only updating status if HomeKit value *actually changes*
|
|
154
145
|
if (
|
|
@@ -157,35 +148,31 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
157
148
|
) {
|
|
158
149
|
// Camera state does not reflect requested state, so fix
|
|
159
150
|
this.set({ uuid: this.deviceData.nest_google_uuid, streaming_enabled: value === false ? true : false });
|
|
160
|
-
|
|
161
|
-
this.log.info('Camera on "%s" was turned', this.deviceData.description, value === false ? 'on' : 'off');
|
|
162
|
-
}
|
|
151
|
+
this?.log?.info?.('Camera on "%s" was turned', this.deviceData.description, value === false ? 'on' : 'off');
|
|
163
152
|
}
|
|
164
153
|
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
this.operatingModeService.getCharacteristic(this.hap.Characteristic.ManuallyDisabled).onGet(() => {
|
|
154
|
+
},
|
|
155
|
+
onGet: () => {
|
|
168
156
|
return this.deviceData.streaming_enabled === false
|
|
169
157
|
? this.hap.Characteristic.ManuallyDisabled.DISABLED
|
|
170
158
|
: this.hap.Characteristic.ManuallyDisabled.ENABLED;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (this.deviceData.has_video_flip === true) {
|
|
174
|
-
if (this.operatingModeService.testCharacteristic(this.hap.Characteristic.ImageRotation) === false) {
|
|
175
|
-
this.operatingModeService.addOptionalCharacteristic(this.hap.Characteristic.ImageRotation);
|
|
176
|
-
}
|
|
159
|
+
},
|
|
160
|
+
});
|
|
177
161
|
|
|
178
|
-
|
|
162
|
+
if (this.deviceData?.has_video_flip === true) {
|
|
163
|
+
this.addHKCharacteristic(this.operatingModeService, this.hap.Characteristic.ImageRotation, {
|
|
164
|
+
onGet: () => {
|
|
179
165
|
return this.deviceData.video_flipped === true ? 180 : 0;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
166
|
+
},
|
|
167
|
+
});
|
|
182
168
|
}
|
|
183
169
|
|
|
184
|
-
if (this.controller?.recordingManagement?.recordingManagementService !== undefined) {
|
|
185
|
-
|
|
186
|
-
this.controller.recordingManagement.recordingManagementService
|
|
187
|
-
|
|
188
|
-
|
|
170
|
+
if (this.controller?.recordingManagement?.recordingManagementService !== undefined && this.deviceData.has_microphone === true) {
|
|
171
|
+
this.addHKCharacteristic(
|
|
172
|
+
this.controller.recordingManagement.recordingManagementService,
|
|
173
|
+
this.hap.Characteristic.RecordingAudioActive,
|
|
174
|
+
{
|
|
175
|
+
onSet: (value) => {
|
|
189
176
|
if (
|
|
190
177
|
(this.deviceData.audio_enabled === true && value === this.hap.Characteristic.RecordingAudioActive.DISABLE) ||
|
|
191
178
|
(this.deviceData.audio_enabled === false && value === this.hap.Characteristic.RecordingAudioActive.ENABLE)
|
|
@@ -194,42 +181,37 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
194
181
|
uuid: this.deviceData.nest_google_uuid,
|
|
195
182
|
audio_enabled: value === this.hap.Characteristic.RecordingAudioActive.ENABLE ? true : false,
|
|
196
183
|
});
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
);
|
|
203
|
-
}
|
|
184
|
+
this?.log?.info?.(
|
|
185
|
+
'Audio recording on "%s" was turned',
|
|
186
|
+
this.deviceData.description,
|
|
187
|
+
value === this.hap.Characteristic.RecordingAudioActive.ENABLE ? 'on' : 'off',
|
|
188
|
+
);
|
|
204
189
|
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
this.controller.recordingManagement.recordingManagementService
|
|
208
|
-
.getCharacteristic(this.hap.Characteristic.RecordingAudioActive)
|
|
209
|
-
.onGet(() => {
|
|
190
|
+
},
|
|
191
|
+
onGet: () => {
|
|
210
192
|
return this.deviceData.audio_enabled === true
|
|
211
193
|
? this.hap.Characteristic.RecordingAudioActive.ENABLE
|
|
212
194
|
: this.hap.Characteristic.RecordingAudioActive.DISABLE;
|
|
213
|
-
}
|
|
214
|
-
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
);
|
|
215
198
|
}
|
|
216
199
|
|
|
217
200
|
if (this.deviceData.migrating === true) {
|
|
218
201
|
// Migration happening between Nest <-> Google Home apps
|
|
219
|
-
this?.log?.warn
|
|
202
|
+
this?.log?.warn?.('Migration between Nest <-> Google Home apps is underway for "%s"', this.deviceData.description);
|
|
220
203
|
}
|
|
221
204
|
|
|
222
205
|
if (
|
|
223
|
-
(this.deviceData.streaming_protocols.includes(
|
|
224
|
-
this.deviceData.streaming_protocols.includes(
|
|
225
|
-
(this.deviceData.streaming_protocols.includes(
|
|
226
|
-
(this.deviceData.streaming_protocols.includes(
|
|
206
|
+
(this.deviceData.streaming_protocols.includes(STREAMING_PROTOCOL.WEBRTC) === false &&
|
|
207
|
+
this.deviceData.streaming_protocols.includes(STREAMING_PROTOCOL.NEXUSTALK) === false) ||
|
|
208
|
+
(this.deviceData.streaming_protocols.includes(STREAMING_PROTOCOL.WEBRTC) === true && WebRTC === undefined) ||
|
|
209
|
+
(this.deviceData.streaming_protocols.includes(STREAMING_PROTOCOL.NEXUSTALK) === true && NexusTalk === undefined)
|
|
227
210
|
) {
|
|
228
|
-
this?.log?.error
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
);
|
|
211
|
+
this?.log?.error?.(
|
|
212
|
+
'No suitable streaming protocol is present for "%s". Streaming and recording will be unavailable',
|
|
213
|
+
this.deviceData.description,
|
|
214
|
+
);
|
|
233
215
|
}
|
|
234
216
|
|
|
235
217
|
// Setup linkage to EveHome app if configured todo so
|
|
@@ -243,17 +225,13 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
243
225
|
});
|
|
244
226
|
}
|
|
245
227
|
|
|
246
|
-
//
|
|
247
|
-
let postSetupDetails = [];
|
|
228
|
+
// Extra setup details for output
|
|
248
229
|
this.deviceData.hksv === true &&
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
);
|
|
252
|
-
this.deviceData.localAccess === true && postSetupDetails.push('Local access');
|
|
253
|
-
return postSetupDetails;
|
|
230
|
+
this.postSetupDetail('HomeKit Secure Video support' + (this.streamer?.isBuffering() === true ? ' and recording buffer started' : ''));
|
|
231
|
+
this.deviceData.localAccess === true && this.postSetupDetail('Local access');
|
|
254
232
|
}
|
|
255
233
|
|
|
256
|
-
|
|
234
|
+
removeDevice() {
|
|
257
235
|
// Clean up our camera object since this device is being removed
|
|
258
236
|
clearTimeout(this.motionTimer);
|
|
259
237
|
clearTimeout(this.personTimer);
|
|
@@ -298,11 +276,10 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
298
276
|
// https://github.com/hjdhjd/homebridge-unifi-protect/blob/eee6a4e379272b659baa6c19986d51f5bf2cbbbc/src/protect-ffmpeg-record.ts
|
|
299
277
|
async *handleRecordingStreamRequest(sessionID) {
|
|
300
278
|
if (this.deviceData?.ffmpeg?.binary === undefined) {
|
|
301
|
-
this?.log?.warn
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
);
|
|
279
|
+
this?.log?.warn?.(
|
|
280
|
+
'Received request to start recording for "%s" however we do not have an ffmpeg binary present',
|
|
281
|
+
this.deviceData.description,
|
|
282
|
+
);
|
|
306
283
|
return;
|
|
307
284
|
}
|
|
308
285
|
|
|
@@ -312,16 +289,15 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
312
289
|
) {
|
|
313
290
|
// Should only be recording if motion detected.
|
|
314
291
|
// Sometimes when starting up, HAP-nodeJS or HomeKit triggers this even when motion isn't occuring
|
|
315
|
-
this?.log?.debug
|
|
292
|
+
this?.log?.debug?.('Received request to commence recording for "%s" however we have not detected any motion');
|
|
316
293
|
return;
|
|
317
294
|
}
|
|
318
295
|
|
|
319
296
|
if (this.streamer === undefined) {
|
|
320
|
-
this?.log?.error
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
);
|
|
297
|
+
this?.log?.error?.(
|
|
298
|
+
'Received request to start recording for "%s" however we do not any associated streaming protocol support',
|
|
299
|
+
this.deviceData.description,
|
|
300
|
+
);
|
|
325
301
|
return;
|
|
326
302
|
}
|
|
327
303
|
|
|
@@ -410,12 +386,11 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
410
386
|
// Start our ffmpeg recording process and stream from our streamer
|
|
411
387
|
// video is pipe #1
|
|
412
388
|
// audio is pipe #3 if including audio
|
|
413
|
-
this?.log?.debug
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
);
|
|
389
|
+
this?.log?.debug?.(
|
|
390
|
+
'ffmpeg process for recording stream from "%s" will be called using the following commandline',
|
|
391
|
+
this.deviceData.description,
|
|
392
|
+
commandLine.join(' ').toString(),
|
|
393
|
+
);
|
|
419
394
|
let ffmpegRecording = child_process.spawn(this.deviceData.ffmpeg.binary, commandLine.join(' ').split(' '), {
|
|
420
395
|
env: process.env,
|
|
421
396
|
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
|
@@ -455,8 +430,7 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
455
430
|
|
|
456
431
|
ffmpegRecording.on('exit', (code, signal) => {
|
|
457
432
|
if (signal !== 'SIGKILL' || signal === null) {
|
|
458
|
-
this?.log?.error
|
|
459
|
-
this.log.error('ffmpeg recording process for "%s" stopped unexpectedly. Exit code was "%s"', this.deviceData.description, code);
|
|
433
|
+
this?.log?.error?.('ffmpeg recording process for "%s" stopped unexpectedly. Exit code was "%s"', this.deviceData.description, code);
|
|
460
434
|
}
|
|
461
435
|
|
|
462
436
|
if (this.#hkSessions?.[sessionID] !== undefined) {
|
|
@@ -473,7 +447,7 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
473
447
|
ffmpegRecording.stderr.on('data', (data) => {
|
|
474
448
|
if (data.toString().includes('frame=') === false && this.deviceData?.ffmpeg?.debug === true) {
|
|
475
449
|
// Monitor ffmpeg output
|
|
476
|
-
this?.log?.debug
|
|
450
|
+
this?.log?.debug?.(data.toString());
|
|
477
451
|
}
|
|
478
452
|
});
|
|
479
453
|
|
|
@@ -486,8 +460,7 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
486
460
|
this.#hkSessions[sessionID].eventEmitter = eventEmitter;
|
|
487
461
|
this.#hkSessions[sessionID].ffmpeg = ffmpegRecording; // Store ffmpeg process ID
|
|
488
462
|
|
|
489
|
-
this?.log?.info
|
|
490
|
-
this.log.info('Started recording from "%s" %s', this.deviceData.description, includeAudio === false ? 'without audio' : '');
|
|
463
|
+
this?.log?.info?.('Started recording from "%s" %s', this.deviceData.description, includeAudio === false ? 'without audio' : '');
|
|
491
464
|
|
|
492
465
|
// Loop generating MOOF/MDAT box pairs for HomeKit Secure Video.
|
|
493
466
|
// HAP-NodeJS cancels this async generator function when recording completes also
|
|
@@ -539,14 +512,13 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
539
512
|
|
|
540
513
|
// Log recording finished messages depending on reason
|
|
541
514
|
if (closeReason === this.hap.HDSProtocolSpecificErrorReason.NORMAL) {
|
|
542
|
-
this?.log?.info
|
|
515
|
+
this?.log?.info?.('Completed recording from "%s"', this.deviceData.description);
|
|
543
516
|
} else {
|
|
544
|
-
this?.log?.warn
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
);
|
|
517
|
+
this?.log?.warn?.(
|
|
518
|
+
'Recording from "%s" completed with error. Reason was "%s"',
|
|
519
|
+
this.deviceData.description,
|
|
520
|
+
this.hap.HDSProtocolSpecificErrorReason[closeReason],
|
|
521
|
+
);
|
|
550
522
|
}
|
|
551
523
|
}
|
|
552
524
|
|
|
@@ -555,13 +527,13 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
555
527
|
// Start a buffering stream for this camera/doorbell. Ensures motion captures all video on motion trigger
|
|
556
528
|
// Required due to data delays by on prem Nest to cloud to HomeKit accessory to iCloud etc
|
|
557
529
|
// Make sure have appropriate bandwidth!!!
|
|
558
|
-
this?.log?.info
|
|
530
|
+
this?.log?.info?.('Recording was turned on for "%s"', this.deviceData.description);
|
|
559
531
|
this.streamer.startBuffering();
|
|
560
532
|
}
|
|
561
533
|
|
|
562
534
|
if (enableRecording === false && this.streamer?.isBuffering() === true) {
|
|
563
535
|
this.streamer.stopBuffering();
|
|
564
|
-
this?.log?.warn
|
|
536
|
+
this?.log?.warn?.('Recording was turned off for "%s"', this.deviceData.description);
|
|
565
537
|
}
|
|
566
538
|
}
|
|
567
539
|
|
|
@@ -586,7 +558,7 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
586
558
|
clearTimeout(this.snapshotTimer);
|
|
587
559
|
this.snapshotTimer = setTimeout(() => {
|
|
588
560
|
this.lastSnapshotImage = undefined;
|
|
589
|
-
},
|
|
561
|
+
}, SNAPSHOT_CACHE_TIMEOUT);
|
|
590
562
|
}
|
|
591
563
|
}
|
|
592
564
|
|
|
@@ -688,20 +660,18 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
688
660
|
// called when HomeKit asks to start/stop/reconfigure a camera/doorbell live stream
|
|
689
661
|
if (request.type === this.hap.StreamRequestTypes.START && this.streamer === undefined) {
|
|
690
662
|
// We have no streamer object configured, so cannot do live streams!!
|
|
691
|
-
this?.log?.error
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
);
|
|
663
|
+
this?.log?.error?.(
|
|
664
|
+
'Received request to start live video for "%s" however we do not any associated streaming protocol support',
|
|
665
|
+
this.deviceData.description,
|
|
666
|
+
);
|
|
696
667
|
}
|
|
697
668
|
|
|
698
669
|
if (request.type === this.hap.StreamRequestTypes.START && this.deviceData?.ffmpeg?.binary === undefined) {
|
|
699
670
|
// No ffmpeg binary present, so cannot do live streams!!
|
|
700
|
-
this?.log?.warn
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
);
|
|
671
|
+
this?.log?.warn?.(
|
|
672
|
+
'Received request to start live video for "%s" however we do not have an ffmpeg binary present',
|
|
673
|
+
this.deviceData.description,
|
|
674
|
+
);
|
|
705
675
|
}
|
|
706
676
|
|
|
707
677
|
if (
|
|
@@ -802,12 +772,11 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
802
772
|
// Start our ffmpeg streaming process and stream from our streamer
|
|
803
773
|
// video is pipe #1
|
|
804
774
|
// audio is pipe #3 if including audio
|
|
805
|
-
this?.log?.debug
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
);
|
|
775
|
+
this?.log?.debug?.(
|
|
776
|
+
'ffmpeg process for live streaming from "%s" will be called using the following commandline',
|
|
777
|
+
this.deviceData.description,
|
|
778
|
+
commandLine.join(' ').toString(),
|
|
779
|
+
);
|
|
811
780
|
let ffmpegStreaming = child_process.spawn(this.deviceData.ffmpeg.binary, commandLine.join(' ').split(' '), {
|
|
812
781
|
env: process.env,
|
|
813
782
|
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
|
@@ -815,12 +784,11 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
815
784
|
|
|
816
785
|
ffmpegStreaming.on('exit', (code, signal) => {
|
|
817
786
|
if (signal !== 'SIGKILL' || signal === null) {
|
|
818
|
-
this?.log?.error
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
);
|
|
787
|
+
this?.log?.error?.(
|
|
788
|
+
'ffmpeg video/audio live streaming process for "%s" stopped unexpectedly. Exit code was "%s"',
|
|
789
|
+
this.deviceData.description,
|
|
790
|
+
code,
|
|
791
|
+
);
|
|
824
792
|
|
|
825
793
|
// Clean up or streaming request, but calling it again with a 'STOP' reques
|
|
826
794
|
this.handleStreamRequest({ type: this.hap.StreamRequestTypes.STOP, sessionID: request.sessionID }, null);
|
|
@@ -831,7 +799,7 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
831
799
|
ffmpegStreaming.stderr.on('data', (data) => {
|
|
832
800
|
if (data.toString().includes('frame=') === false && this.deviceData?.ffmpeg?.debug === true) {
|
|
833
801
|
// Monitor ffmpeg output
|
|
834
|
-
this?.log?.debug
|
|
802
|
+
this?.log?.debug?.(data.toString());
|
|
835
803
|
}
|
|
836
804
|
});
|
|
837
805
|
|
|
@@ -894,24 +862,22 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
894
862
|
|
|
895
863
|
commandLine.push('-f data pipe:1');
|
|
896
864
|
|
|
897
|
-
this?.log?.debug
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
);
|
|
865
|
+
this?.log?.debug?.(
|
|
866
|
+
'ffmpeg process for talkback on "%s" will be called using the following commandline',
|
|
867
|
+
this.deviceData.description,
|
|
868
|
+
commandLine.join(' ').toString(),
|
|
869
|
+
);
|
|
903
870
|
ffmpegAudioTalkback = child_process.spawn(this.deviceData.ffmpeg.binary, commandLine.join(' ').split(' '), {
|
|
904
871
|
env: process.env,
|
|
905
872
|
});
|
|
906
873
|
|
|
907
874
|
ffmpegAudioTalkback.on('exit', (code, signal) => {
|
|
908
875
|
if (signal !== 'SIGKILL' || signal === null) {
|
|
909
|
-
this?.log?.error
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
);
|
|
876
|
+
this?.log?.error?.(
|
|
877
|
+
'ffmpeg audio talkback streaming process for "%s" stopped unexpectedly. Exit code was "%s"',
|
|
878
|
+
this.deviceData.description,
|
|
879
|
+
code,
|
|
880
|
+
);
|
|
915
881
|
|
|
916
882
|
// Clean up or streaming request, but calling it again with a 'STOP' request
|
|
917
883
|
this.handleStreamRequest({ type: this.hap.StreamRequestTypes.STOP, sessionID: request.sessionID }, null);
|
|
@@ -927,7 +893,7 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
927
893
|
ffmpegAudioTalkback.stderr.on('data', (data) => {
|
|
928
894
|
if (data.toString().includes('frame=') === false && this.deviceData?.ffmpeg?.debug === true) {
|
|
929
895
|
// Monitor ffmpeg output
|
|
930
|
-
this?.log?.debug
|
|
896
|
+
this?.log?.debug?.(data.toString());
|
|
931
897
|
}
|
|
932
898
|
});
|
|
933
899
|
|
|
@@ -971,12 +937,11 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
971
937
|
ffmpegAudioTalkback.stdin.end();
|
|
972
938
|
}
|
|
973
939
|
|
|
974
|
-
this?.log?.info
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
);
|
|
940
|
+
this?.log?.info?.(
|
|
941
|
+
'Live stream started on "%s" %s',
|
|
942
|
+
this.deviceData.description,
|
|
943
|
+
ffmpegAudioTalkback?.stdout ? 'with two-way audio' : '',
|
|
944
|
+
);
|
|
980
945
|
|
|
981
946
|
// Start the appropriate streamer
|
|
982
947
|
this.streamer !== undefined &&
|
|
@@ -1010,11 +975,11 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
1010
975
|
|
|
1011
976
|
delete this.#hkSessions[request.sessionID];
|
|
1012
977
|
|
|
1013
|
-
this?.log?.info
|
|
978
|
+
this?.log?.info?.('Live stream stopped from "%s"', this.deviceData.description);
|
|
1014
979
|
}
|
|
1015
980
|
|
|
1016
981
|
if (request.type === this.hap.StreamRequestTypes.RECONFIGURE && typeof this.#hkSessions[request.sessionID] === 'object') {
|
|
1017
|
-
this?.log?.debug
|
|
982
|
+
this?.log?.debug?.('Unsupported reconfiguration request for live stream on "%s"', this.deviceData.description);
|
|
1018
983
|
}
|
|
1019
984
|
|
|
1020
985
|
if (typeof callback === 'function') {
|
|
@@ -1022,32 +987,32 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
1022
987
|
}
|
|
1023
988
|
}
|
|
1024
989
|
|
|
1025
|
-
|
|
990
|
+
updateDevice(deviceData) {
|
|
1026
991
|
if (typeof deviceData !== 'object' || this.controller === undefined) {
|
|
1027
992
|
return;
|
|
1028
993
|
}
|
|
1029
994
|
|
|
1030
995
|
if (this.deviceData.migrating === false && deviceData.migrating === true) {
|
|
1031
996
|
// Migration happening between Nest <-> Google Home apps. We'll stop any active streams, close the current streaming object
|
|
1032
|
-
this?.log?.warn
|
|
997
|
+
this?.log?.warn?.('Migration between Nest <-> Google Home apps has started for "%s"', deviceData.description);
|
|
1033
998
|
this.streamer !== undefined && this.streamer.stopEverything();
|
|
1034
999
|
this.streamer = undefined;
|
|
1035
1000
|
}
|
|
1036
1001
|
|
|
1037
1002
|
if (this.deviceData.migrating === true && deviceData.migrating === false) {
|
|
1038
1003
|
// Migration has completed between Nest <-> Google Home apps
|
|
1039
|
-
this?.log?.success
|
|
1004
|
+
this?.log?.success?.('Migration between Nest <-> Google Home apps has completed for "%s"', deviceData.description);
|
|
1040
1005
|
}
|
|
1041
1006
|
|
|
1042
1007
|
// Handle case of changes in streaming protocols OR just finished migration between Nest <-> Google Home apps
|
|
1043
1008
|
if (this.streamer === undefined && deviceData.migrating === false) {
|
|
1044
1009
|
if (JSON.stringify(deviceData.streaming_protocols) !== JSON.stringify(this.deviceData.streaming_protocols)) {
|
|
1045
|
-
this?.log?.warn
|
|
1010
|
+
this?.log?.warn?.('Available streaming protocols have changed for "%s"', deviceData.description);
|
|
1046
1011
|
this.streamer !== undefined && this.streamer.stopEverything();
|
|
1047
1012
|
this.streamer = undefined;
|
|
1048
1013
|
}
|
|
1049
|
-
if (deviceData.streaming_protocols.includes(
|
|
1050
|
-
this?.log?.debug
|
|
1014
|
+
if (deviceData.streaming_protocols.includes(STREAMING_PROTOCOL.WEBRTC) === true && WebRTC !== undefined) {
|
|
1015
|
+
this?.log?.debug?.('Using WebRTC streamer for "%s"', deviceData.description);
|
|
1051
1016
|
this.streamer = new WebRTC(deviceData, {
|
|
1052
1017
|
log: this.log,
|
|
1053
1018
|
buffer:
|
|
@@ -1058,8 +1023,8 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
1058
1023
|
});
|
|
1059
1024
|
}
|
|
1060
1025
|
|
|
1061
|
-
if (deviceData.streaming_protocols.includes(
|
|
1062
|
-
this?.log?.debug
|
|
1026
|
+
if (deviceData.streaming_protocols.includes(STREAMING_PROTOCOL.NEXUSTALK) === true && NexusTalk !== undefined) {
|
|
1027
|
+
this?.log?.debug?.('Using NexusTalk streamer for "%s"', deviceData.description);
|
|
1063
1028
|
this.streamer = new NexusTalk(deviceData, {
|
|
1064
1029
|
log: this.log,
|
|
1065
1030
|
buffer:
|
|
@@ -1072,22 +1037,26 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
1072
1037
|
}
|
|
1073
1038
|
|
|
1074
1039
|
// Check to see if any activity zones were added for both non-HKSV and HKSV enabled devices
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
) {
|
|
1080
|
-
if (this.
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
tempService.
|
|
1040
|
+
if (
|
|
1041
|
+
Array.isArray(deviceData.activity_zones) === true &&
|
|
1042
|
+
JSON.stringify(deviceData.activity_zones) !== JSON.stringify(this.deviceData.activity_zones)
|
|
1043
|
+
) {
|
|
1044
|
+
deviceData.activity_zones.forEach((zone) => {
|
|
1045
|
+
if (this.deviceData.hksv === false || (this.deviceData.hksv === true && zone.id === 1)) {
|
|
1046
|
+
if (this.motionServices?.[zone.id]?.service === undefined) {
|
|
1047
|
+
// Zone doesn't have an associated motion sensor, so add one
|
|
1048
|
+
let zoneName = zone.id === 1 ? '' : zone.name;
|
|
1049
|
+
let tempService = this.addHKService(this.hap.Service.MotionSensor, zoneName, zone.id);
|
|
1050
|
+
|
|
1051
|
+
this.addHKCharacteristic(tempService, this.hap.Characteristic.Active);
|
|
1052
|
+
tempService.updateCharacteristic(this.hap.Characteristic.Name, zoneName);
|
|
1053
|
+
tempService.updateCharacteristic(this.hap.Characteristic.MotionDetected, false); // No motion initially
|
|
1054
|
+
|
|
1055
|
+
this.motionServices[zone.id] = { service: tempService, timer: undefined };
|
|
1085
1056
|
}
|
|
1086
|
-
tempService.updateCharacteristic(this.hap.Characteristic.MotionDetected, false); // No motion initially
|
|
1087
|
-
this.motionServices[zone.id] = { service: tempService, timer: undefined };
|
|
1088
1057
|
}
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1091
1060
|
|
|
1092
1061
|
// Check to see if any activity zones were removed for both non-HKSV and HKSV enabled devices
|
|
1093
1062
|
// We'll also update the online status of the camera in the motion service here
|
|
@@ -1098,12 +1067,15 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
1098
1067
|
deviceData.online === true ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE,
|
|
1099
1068
|
);
|
|
1100
1069
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1070
|
+
// Handle deleted zones (excluding zone ID 1 for HKSV)
|
|
1071
|
+
if (
|
|
1072
|
+
zoneID !== '1' &&
|
|
1073
|
+
Array.isArray(deviceData.activity_zones) === true &&
|
|
1074
|
+
deviceData.activity_zones.findIndex(({ id }) => id === Number(zoneID)) === -1
|
|
1075
|
+
) {
|
|
1076
|
+
// Motion service we created doesn't appear in zone list anymore, so assume deleted
|
|
1077
|
+
this.accessory.removeService(service.service);
|
|
1078
|
+
delete this.motionServices[zoneID];
|
|
1107
1079
|
}
|
|
1108
1080
|
});
|
|
1109
1081
|
|
|
@@ -1116,7 +1088,7 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
1116
1088
|
: this.hap.Characteristic.ManuallyDisabled.ENABLED,
|
|
1117
1089
|
);
|
|
1118
1090
|
|
|
1119
|
-
if (deviceData
|
|
1091
|
+
if (deviceData?.has_statusled === true) {
|
|
1120
1092
|
// Set camera recording indicator. This cannot be turned off on Nest Cameras/Doorbells
|
|
1121
1093
|
// 0 = auto
|
|
1122
1094
|
// 1 = low
|
|
@@ -1127,12 +1099,12 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
1127
1099
|
);
|
|
1128
1100
|
}
|
|
1129
1101
|
|
|
1130
|
-
if (deviceData
|
|
1102
|
+
if (deviceData?.has_irled === true) {
|
|
1131
1103
|
// Set nightvision status in HomeKit
|
|
1132
1104
|
this.operatingModeService.updateCharacteristic(this.hap.Characteristic.NightVision, deviceData.irled_enabled);
|
|
1133
1105
|
}
|
|
1134
1106
|
|
|
1135
|
-
if (deviceData
|
|
1107
|
+
if (deviceData?.has_video_flip === true) {
|
|
1136
1108
|
// Update image flip status
|
|
1137
1109
|
this.operatingModeService.updateCharacteristic(this.hap.Characteristic.ImageRotation, deviceData.video_flipped === true ? 180 : 0);
|
|
1138
1110
|
}
|
|
@@ -1183,7 +1155,7 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
1183
1155
|
// For a HKSV enabled camera, we will use this to trigger the starting of the HKSV recording if the camera is active
|
|
1184
1156
|
if (event.types.includes('motion') === true) {
|
|
1185
1157
|
if (this.motionTimer === undefined && (this.deviceData.hksv === false || this.streamer === undefined)) {
|
|
1186
|
-
this?.log?.info
|
|
1158
|
+
this?.log?.info?.('Motion detected at "%s"', deviceData.description);
|
|
1187
1159
|
}
|
|
1188
1160
|
|
|
1189
1161
|
event.zone_ids.forEach((zoneID) => {
|
|
@@ -1231,9 +1203,9 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
1231
1203
|
if (event.types.includes('person') === true || event.types.includes('face') === true) {
|
|
1232
1204
|
if (this.personTimer === undefined) {
|
|
1233
1205
|
// We don't have a person cooldown timer running, so we can process the 'person'/'face' event
|
|
1234
|
-
if (this
|
|
1206
|
+
if (this.deviceData.hksv === false || this.streamer === undefined) {
|
|
1235
1207
|
// We'll only log a person detected event if HKSV is disabled
|
|
1236
|
-
this
|
|
1208
|
+
this?.log?.info?.('Person detected at "%s"', deviceData.description);
|
|
1237
1209
|
}
|
|
1238
1210
|
|
|
1239
1211
|
// Cooldown for person being detected
|
|
@@ -1258,26 +1230,29 @@ export default class NestCamera extends HomeKitDevice {
|
|
|
1258
1230
|
// This will help with any 'restored' service Homebridge has done
|
|
1259
1231
|
// And allow for zone changes on the camera/doorbell
|
|
1260
1232
|
this.motionServices = {};
|
|
1261
|
-
this.accessory.services
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1233
|
+
this.accessory.services
|
|
1234
|
+
.filter((service) => service.UUID === this.hap.Service.MotionSensor.UUID)
|
|
1235
|
+
.forEach((service) => this.accessory.removeService(service));
|
|
1236
|
+
|
|
1237
|
+
let zones = Array.isArray(this.deviceData.activity_zones) === true ? this.deviceData.activity_zones : [];
|
|
1266
1238
|
|
|
1267
|
-
if (this.deviceData.has_motion_detection === true &&
|
|
1239
|
+
if (this.deviceData.has_motion_detection === true && zones.length > 0) {
|
|
1268
1240
|
// We have the capability of motion sensing on device, so setup motion sensor(s)
|
|
1269
1241
|
// If we have HKSV video enabled, we'll only create a single motion sensor
|
|
1270
1242
|
// A zone with the ID of 1 is treated as the main motion sensor
|
|
1271
|
-
|
|
1272
|
-
if (this.deviceData.hksv ===
|
|
1273
|
-
|
|
1274
|
-
if (tempService.testCharacteristic(this.hap.Characteristic.Active) === false) {
|
|
1275
|
-
tempService.addCharacteristic(this.hap.Characteristic.Active);
|
|
1276
|
-
}
|
|
1277
|
-
tempService.updateCharacteristic(this.hap.Characteristic.MotionDetected, false); // No motion initially
|
|
1278
|
-
this.motionServices[zone.id] = { service: tempService, timer: undefined };
|
|
1243
|
+
for (let zone of zones) {
|
|
1244
|
+
if (this.deviceData.hksv === true && zone.id !== 1) {
|
|
1245
|
+
continue;
|
|
1279
1246
|
}
|
|
1280
|
-
|
|
1247
|
+
|
|
1248
|
+
let zoneName = zone.id === 1 ? '' : zone.name;
|
|
1249
|
+
let service = this.addHKService(this.hap.Service.MotionSensor, zoneName, zone.id);
|
|
1250
|
+
this.addHKCharacteristic(service, this.hap.Characteristic.Active);
|
|
1251
|
+
service.updateCharacteristic(this.hap.Characteristic.Name, zoneName);
|
|
1252
|
+
service.updateCharacteristic(this.hap.Characteristic.MotionDetected, false); // No motion initially
|
|
1253
|
+
|
|
1254
|
+
this.motionServices[zone.id] = { service, timer: undefined };
|
|
1255
|
+
}
|
|
1281
1256
|
}
|
|
1282
1257
|
}
|
|
1283
1258
|
|