@jgrimard/homebridge-vivint 2.0.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 (62) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +107 -0
  3. package/config.schema.json +90 -0
  4. package/dist/devices/camera.js +368 -0
  5. package/dist/devices/camera.js.map +1 -0
  6. package/dist/devices/carbonMonoxideSensor.js +44 -0
  7. package/dist/devices/carbonMonoxideSensor.js.map +1 -0
  8. package/dist/devices/contactSensor.js +65 -0
  9. package/dist/devices/contactSensor.js.map +1 -0
  10. package/dist/devices/device.js +147 -0
  11. package/dist/devices/device.js.map +1 -0
  12. package/dist/devices/dimmerSwitch.js +12 -0
  13. package/dist/devices/dimmerSwitch.js.map +1 -0
  14. package/dist/devices/garageDoor.js +58 -0
  15. package/dist/devices/garageDoor.js.map +1 -0
  16. package/dist/devices/index.js +55 -0
  17. package/dist/devices/index.js.map +1 -0
  18. package/dist/devices/lightGroup.js +15 -0
  19. package/dist/devices/lightGroup.js.map +1 -0
  20. package/dist/devices/lightSwitch.js +42 -0
  21. package/dist/devices/lightSwitch.js.map +1 -0
  22. package/dist/devices/lock.js +49 -0
  23. package/dist/devices/lock.js.map +1 -0
  24. package/dist/devices/motionSensor.js +73 -0
  25. package/dist/devices/motionSensor.js.map +1 -0
  26. package/dist/devices/multiLevelSwitchBase.js +55 -0
  27. package/dist/devices/multiLevelSwitchBase.js.map +1 -0
  28. package/dist/devices/panel.js +79 -0
  29. package/dist/devices/panel.js.map +1 -0
  30. package/dist/devices/smokeSensor.js +47 -0
  31. package/dist/devices/smokeSensor.js.map +1 -0
  32. package/dist/devices/thermostat.js +216 -0
  33. package/dist/devices/thermostat.js.map +1 -0
  34. package/dist/homebridge-ui/public/index.html +138 -0
  35. package/dist/homebridge-ui/server.js +120 -0
  36. package/dist/homebridge-ui/server.js.map +1 -0
  37. package/dist/index.js +9 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/platform.js +393 -0
  40. package/dist/platform.js.map +1 -0
  41. package/dist/settings.js +12 -0
  42. package/dist/settings.js.map +1 -0
  43. package/dist/types.js +2 -0
  44. package/dist/types.js.map +1 -0
  45. package/dist/utils/debounce.js +49 -0
  46. package/dist/utils/debounce.js.map +1 -0
  47. package/dist/utils/ports.js +39 -0
  48. package/dist/utils/ports.js.map +1 -0
  49. package/dist/utils/redact.js +19 -0
  50. package/dist/utils/redact.js.map +1 -0
  51. package/dist/utils/sanitizeName.js +22 -0
  52. package/dist/utils/sanitizeName.js.map +1 -0
  53. package/dist/vivint/api.js +246 -0
  54. package/dist/vivint/api.js.map +1 -0
  55. package/dist/vivint/datapatch.js +52 -0
  56. package/dist/vivint/datapatch.js.map +1 -0
  57. package/dist/vivint/dictionary.js +66 -0
  58. package/dist/vivint/dictionary.js.map +1 -0
  59. package/dist/vivint/eventStream.js +77 -0
  60. package/dist/vivint/eventStream.js.map +1 -0
  61. package/dist/vivint/vivint_dictionary.json +8720 -0
  62. package/package.json +62 -0
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2019 Tim Harper
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/homebridge/branding/latest/logos/homebridge-color-round-stylized.png" width="120">
3
+ </p>
4
+
5
+ # @jgrimard/homebridge-vivint
6
+
7
+ Homebridge plugin for [Vivint Smart Home](https://www.vivint.com). This is a fork of
8
+ [@balansse/homebridge-vivint](https://github.com/balansse/homebridge-vivint), rewritten in TypeScript
9
+ on the official [Homebridge plugin template](https://github.com/homebridge/homebridge-plugin-template)
10
+ and fully compatible with **Homebridge 1.8+ and 2.x**. The goal is to contribute these changes back
11
+ upstream; this fork is published so the rewrite can be used in the meantime. Accessory UUIDs and device
12
+ identities are kept compatible with the original plugin, so existing rooms, names, and automations are
13
+ preserved when switching between the two.
14
+
15
+ ## Features
16
+
17
+ Exposes the following Vivint devices to HomeKit:
18
+
19
+ - **Security panel** (arm home / arm away / disarm, alarm state)
20
+ - **Door locks** (including jammed detection)
21
+ - **Garage door openers**
22
+ - **Cameras and doorbell cameras** (live video via the panel's local RTSP streams, doorbell press
23
+ notifications, person-detected motion events, plus a contact sensor mirroring the doorbell button
24
+ for use in automations)
25
+ - **Contact, motion, smoke, CO, glass-break, tilt and flood sensors** (with battery and tamper status)
26
+ - **Thermostats** (mode, target temperature, thresholds, humidity, fan)
27
+ - **Z-Wave switches, dimmers and Philips Hue light groups** bridged through the panel
28
+
29
+ Device updates arrive in near-realtime over Vivint's push event stream, backed by periodic polling.
30
+
31
+ ## Installation
32
+
33
+ Install through the Homebridge UI (search for `@jgrimard/homebridge-vivint`), or manually:
34
+
35
+ ```
36
+ npm install -g @jgrimard/homebridge-vivint
37
+ ```
38
+
39
+ ## Configuration
40
+
41
+ ### Sign in with the plugin settings UI (recommended)
42
+
43
+ Vivint accounts use multi-factor authentication (MFA). Open the plugin's settings in the Homebridge UI
44
+ and use the built-in sign-in form:
45
+
46
+ 1. Enter your Vivint email and password. They are sent directly to Vivint and are never stored or logged.
47
+ 2. Enter the verification code Vivint sends you (or the code from your authenticator app).
48
+ 3. The resulting long-lived *refresh token* is saved to the plugin config automatically.
49
+ 4. Restart Homebridge.
50
+
51
+ If you cannot use the UI, the same flow is available on the command line from the plugin folder:
52
+ `npm run mfa`.
53
+
54
+ ### Options
55
+
56
+ | Option | Default | Description |
57
+ | --- | --- | --- |
58
+ | `refreshToken` | — | Vivint session token, generated by the sign-in UI. |
59
+ | `apiLoginRefreshSecs` | `1200` | How often the session is renewed. Device state is polled at 1/20th of this interval (60 s by default). Minimum 300. |
60
+ | `ignoreDeviceTypes` | `[]` | Device types, equipment codes or device IDs to skip (e.g. `thermostat_device` if Nest is managed by another plugin). |
61
+ | `logDeviceList` | `false` | Log all managed devices at startup. |
62
+ | `disableCameras` | `false` | Do not expose camera video feeds. |
63
+ | `useExternalVideoStreams` | `false` | Stream video via Vivint's servers instead of directly from the panel on the LAN. |
64
+ | `showCameraConfig` | `false` | Log camera/ffmpeg configuration at startup (credentials are redacted). |
65
+ | `lowBatteryLevel` | — | Battery % at or below which devices report low battery; if unset, the device's own flag is used. |
66
+ | `motionDetectedOccupancySensorMins` | `0` | If > 0, motion sensors also expose an occupancy sensor that stays triggered this many minutes after motion. |
67
+
68
+ Example `config.json` platform block:
69
+
70
+ ```json
71
+ {
72
+ "platform": "Vivint",
73
+ "name": "Vivint",
74
+ "refreshToken": "s=...",
75
+ "apiLoginRefreshSecs": 1200
76
+ }
77
+ ```
78
+
79
+ ## Reliability & API etiquette
80
+
81
+ - The plugin never retries failed requests in tight loops. Connection failures use exponential backoff
82
+ with jitter, and HTTP 403/429/503 responses (Cloudflare throttling) trigger much longer backoff to
83
+ avoid extended blocks.
84
+ - Local network or Vivint outages are handled gracefully: the plugin keeps running, logs a single
85
+ warning, and recovers automatically when connectivity returns.
86
+ - An invalid/expired token is reported with clear instructions instead of endless retries.
87
+ - Session cookies, passwords, and RTSP credentials are never written to the log.
88
+
89
+ ## Development
90
+
91
+ ```
92
+ npm install
93
+ npm run build # compile plugin + custom UI, copy assets to dist/
94
+ npm run lint
95
+ npm test
96
+ npm run watch # rebuild + restart a local homebridge instance on changes
97
+ ```
98
+
99
+ ## Credits
100
+
101
+ Based on the original work by [Alexandr Balan (balansse)](https://github.com/balansse/homebridge-vivint)
102
+ and [Tim Harper](https://github.com/timcharper), and all contributors to the original plugin.
103
+
104
+ ## Disclaimer
105
+
106
+ This plugin is not affiliated with or endorsed by Vivint. Use at your own risk; do not rely on HomeKit
107
+ as the sole interface for a life-safety system.
@@ -0,0 +1,90 @@
1
+ {
2
+ "pluginAlias": "Vivint",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "customUi": true,
6
+ "customUiPath": "./dist/homebridge-ui",
7
+ "headerDisplay": "Use the settings UI above to sign in to your Vivint account (including multi-factor authentication) and obtain a refresh token automatically.",
8
+ "schema": {
9
+ "type": "object",
10
+ "properties": {
11
+ "name": {
12
+ "title": "Name",
13
+ "type": "string",
14
+ "default": "Vivint",
15
+ "minLength": 1,
16
+ "required": true
17
+ },
18
+ "refreshToken": {
19
+ "title": "Vivint Refresh Token",
20
+ "type": "string",
21
+ "description": "Session token obtained by signing in through the plugin settings UI (or 'npm run mfa').",
22
+ "required": true
23
+ },
24
+ "apiLoginRefreshSecs": {
25
+ "title": "API Session Refresh (seconds)",
26
+ "type": "integer",
27
+ "description": "How often the plugin renews its Vivint session. Device state is polled at 1/20th of this interval. Lower values increase API traffic and the risk of being rate limited.",
28
+ "default": 1200,
29
+ "minimum": 300,
30
+ "maximum": 86400
31
+ },
32
+ "ignoreDeviceTypes": {
33
+ "title": "Ignore Devices",
34
+ "description": "Selected device types / equipment codes / IDs are ignored on startup. Useful for external integrations like Nest or MyQ that are managed directly by another Homebridge plugin.",
35
+ "type": "array",
36
+ "uniqueItems": true,
37
+ "items": {
38
+ "title": "Device Type / Equipment Code / ID",
39
+ "type": "string",
40
+ "typeahead": {
41
+ "source": [
42
+ "thermostat_device",
43
+ "door_lock_device",
44
+ "garage_door_device",
45
+ "camera_device",
46
+ "wireless_sensor",
47
+ "phillips_hue_bridge_device",
48
+ "multilevel_switch_device"
49
+ ]
50
+ }
51
+ }
52
+ },
53
+ "logDeviceList": {
54
+ "title": "Show device list",
55
+ "description": "Logs data for all configured Vivint devices at startup.",
56
+ "type": "boolean"
57
+ },
58
+ "disableCameras": {
59
+ "title": "Disable cameras",
60
+ "description": "If checked, camera video feeds will not appear in Homebridge.",
61
+ "type": "boolean"
62
+ },
63
+ "useExternalVideoStreams": {
64
+ "title": "Use external video streams",
65
+ "description": "Stream camera feeds from Vivint servers instead of streaming directly from the panel on the local network.",
66
+ "type": "boolean"
67
+ },
68
+ "showCameraConfig": {
69
+ "title": "Show camera config",
70
+ "description": "Logs configuration for all detected cameras (RTSP credentials are redacted).",
71
+ "type": "boolean"
72
+ },
73
+ "lowBatteryLevel": {
74
+ "title": "Low battery level (%)",
75
+ "type": "integer",
76
+ "description": "Battery percentage at or below which a device reports a low battery. If unset, the device's own low battery flag is used.",
77
+ "minimum": 1,
78
+ "maximum": 100
79
+ },
80
+ "motionDetectedOccupancySensorMins": {
81
+ "title": "Occupancy sensor timeout (minutes)",
82
+ "type": "integer",
83
+ "description": "If greater than 0, each motion sensor also exposes an occupancy sensor that stays triggered for this many minutes after motion.",
84
+ "minimum": 0
85
+ }
86
+ }
87
+ },
88
+ "form": null,
89
+ "display": null
90
+ }
@@ -0,0 +1,368 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { reservePorts } from '../utils/ports.js';
3
+ import { redactUrlCredentials } from '../utils/redact.js';
4
+ import { sanitizeDeviceName } from '../utils/sanitizeName.js';
5
+ import { VivintDict } from '../vivint/dictionary.js';
6
+ import { VivintDevice } from './device.js';
7
+ // To prevent double notifications, doorbell button events within this window are ignored.
8
+ const DOORBELL_IGNORE_MS = 5000;
9
+ const MOTION_RESET_MS = 5000;
10
+ export class Camera extends VivintDevice {
11
+ static className = 'Camera';
12
+ motionService;
13
+ buttonService;
14
+ controller;
15
+ rtspUrl;
16
+ lastDoorbellRing = 0;
17
+ pendingSessions = new Map();
18
+ ongoingSessions = new Map();
19
+ constructor(platform, accessory, data) {
20
+ super(platform, accessory, data);
21
+ const hap = this.platform.api.hap;
22
+ const sanitizedName = sanitizeDeviceName(this.name, this.id);
23
+ const isDoorbell = this.name.toLowerCase().includes('doorbell');
24
+ this.configureRtspUrls();
25
+ if (this.config.showCameraConfig) {
26
+ this.logCameraConfig(isDoorbell);
27
+ }
28
+ if (this.config.disableCameras !== true && this.rtspUrl) {
29
+ const streamingOptions = {
30
+ supportedCryptoSuites: [0 /* hap.SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80 */],
31
+ video: {
32
+ resolutions: [
33
+ [320, 180, 30],
34
+ [320, 240, 15], // Apple Watch requires this configuration
35
+ [320, 240, 30],
36
+ [480, 270, 30],
37
+ [480, 360, 30],
38
+ [640, 360, 30],
39
+ [640, 480, 30],
40
+ [1280, 720, 30],
41
+ [1280, 960, 30],
42
+ [1920, 1080, 30],
43
+ [1600, 1200, 30],
44
+ ],
45
+ codec: {
46
+ profiles: [0 /* hap.H264Profile.BASELINE */, 1 /* hap.H264Profile.MAIN */, 2 /* hap.H264Profile.HIGH */],
47
+ levels: [0 /* hap.H264Level.LEVEL3_1 */, 1 /* hap.H264Level.LEVEL3_2 */, 2 /* hap.H264Level.LEVEL4_0 */],
48
+ },
49
+ },
50
+ // Only advertise audio when the camera supports it.
51
+ ...(this.data.CameraInternalAudioURL ? {
52
+ audio: {
53
+ codecs: [{
54
+ type: "AAC-eld" /* hap.AudioStreamingCodecType.AAC_ELD */,
55
+ samplerate: 16 /* hap.AudioStreamingSamplerate.KHZ_16 */,
56
+ }],
57
+ },
58
+ } : {}),
59
+ };
60
+ const controllerOptions = {
61
+ cameraStreamCount: 2,
62
+ delegate: this,
63
+ streamingOptions,
64
+ };
65
+ this.controller = isDoorbell
66
+ ? new hap.DoorbellController(controllerOptions)
67
+ : new hap.CameraController(controllerOptions);
68
+ accessory.configureController(this.controller);
69
+ }
70
+ this.motionService = this.ensureService(this.Service.MotionSensor, `${sanitizedName} PIV Detector`);
71
+ this.motionService.getCharacteristic(this.Characteristic.MotionDetected)
72
+ .onGet(() => this.getMotionDetectedState());
73
+ if (isDoorbell) {
74
+ // A contact sensor mirrors the doorbell button so automations can react to it.
75
+ this.buttonService = this.ensureService(this.Service.ContactSensor, `${sanitizedName} Button`);
76
+ this.buttonService.getCharacteristic(this.Characteristic.ContactSensorState)
77
+ .onGet(() => this.getDoorbellButtonPress());
78
+ }
79
+ this.notify();
80
+ }
81
+ /**
82
+ * Builds the authenticated RTSP URLs from the panel's local credentials.
83
+ * Guarded because cameras occasionally appear in the snapshot before their
84
+ * stream URLs are provisioned.
85
+ */
86
+ configureRtspUrls() {
87
+ const external = this.config.useExternalVideoStreams === true;
88
+ const urls = external ? this.data.CameraExternalURL : this.data.CameraInternalURL;
89
+ const panelLogin = this.vivintApi.panelLogin;
90
+ if (!Array.isArray(urls) || urls.length === 0 || !panelLogin) {
91
+ this.log.warn(`Camera '${this.name}' has no ${external ? 'external' : 'internal'} stream URL yet; video is unavailable for now.`);
92
+ return;
93
+ }
94
+ const credentials = `rtsp://${encodeURIComponent(panelLogin.Name)}:${encodeURIComponent(panelLogin.Password)}@`;
95
+ this.rtspUrl = urls[0].replace('rtsp://', credentials);
96
+ }
97
+ logCameraConfig(isDoorbell) {
98
+ const informationService = this.accessory.getService(this.Service.AccessoryInformation);
99
+ const cameraConfig = {
100
+ name: this.data.Name,
101
+ manufacturer: informationService?.getCharacteristic(this.Characteristic.Manufacturer).value,
102
+ model: informationService?.getCharacteristic(this.Characteristic.Model).value,
103
+ motion: true,
104
+ motionTimeout: 1,
105
+ ...(isDoorbell ? { doorbell: true } : {}),
106
+ videoConfig: {
107
+ source: `-rtsp_transport tcp -re -i ${redactUrlCredentials(this.rtspUrl ?? '(no stream URL)')}`,
108
+ vcodec: 'copy',
109
+ audio: true,
110
+ },
111
+ };
112
+ this.log.info(`Camera [${this.data.Name}] configuration:`, JSON.stringify(cameraConfig, undefined, 4));
113
+ }
114
+ getMotionDetectedState() {
115
+ const motionDetected = Boolean(this.data.PersonInView) || Boolean(this.data.VisitorDetected);
116
+ // These are one-shot events; do not retain them.
117
+ this.data.PersonInView = this.data.VisitorDetected = null;
118
+ return motionDetected;
119
+ }
120
+ getDoorbellButtonPress() {
121
+ const pressed = Boolean(this.data.DingDong);
122
+ this.data.DingDong = null;
123
+ return pressed;
124
+ }
125
+ async handleSnapshotRequest(request, callback) {
126
+ this.log.debug(`Handling camera snapshot for '${this.data.Name}' at ${request.width}x${request.height}`);
127
+ if (!this.rtspUrl) {
128
+ callback(new Error('Camera stream URL is not available'));
129
+ return;
130
+ }
131
+ const snapshotArgs = [
132
+ '-rtsp_transport', 'tcp',
133
+ '-i', this.rtspUrl,
134
+ '-frames:v', '1',
135
+ '-vcodec', 'mjpeg',
136
+ '-f', 'image2',
137
+ '-',
138
+ ];
139
+ try {
140
+ const ffmpeg = spawn(this.platform.ffmpegPath, snapshotArgs, { env: process.env });
141
+ const snapshotBuffers = [];
142
+ let replied = false;
143
+ const reply = (error, buffer) => {
144
+ if (!replied) {
145
+ replied = true;
146
+ callback(error, buffer);
147
+ }
148
+ };
149
+ ffmpeg.stdout.on('data', (chunk) => snapshotBuffers.push(chunk));
150
+ ffmpeg.stderr.on('data', (chunk) => this.log.debug('SNAPSHOT: ' + redactUrlCredentials(String(chunk))));
151
+ ffmpeg.on('error', error => {
152
+ this.log.error(`Failed to start ffmpeg for a snapshot of '${this.name}':`, error.message);
153
+ reply(error);
154
+ });
155
+ ffmpeg.on('exit', (code, signal) => {
156
+ if (signal) {
157
+ this.log.error('Snapshot process was killed with signal:', signal);
158
+ reply(new Error('killed with signal ' + signal));
159
+ }
160
+ else if (code === 0) {
161
+ reply(undefined, Buffer.concat(snapshotBuffers));
162
+ }
163
+ else {
164
+ this.log.error('Snapshot process exited with code', code);
165
+ reply(new Error('Snapshot process exited with code ' + code));
166
+ }
167
+ });
168
+ }
169
+ catch (error) {
170
+ this.log.error('Error while requesting a camera snapshot:', error);
171
+ callback(error);
172
+ }
173
+ }
174
+ async prepareStream(request, callback) {
175
+ try {
176
+ const hap = this.platform.api.hap;
177
+ const sessionId = request.sessionID;
178
+ const video = request.video;
179
+ const returnVideoPort = (await reservePorts())[0];
180
+ const videoSSRC = hap.CameraController.generateSynchronisationSource();
181
+ const audio = request.audio;
182
+ const returnAudioPort = (await reservePorts())[0];
183
+ const audioServerPort = (await reservePorts())[0];
184
+ const audioSSRC = hap.CameraController.generateSynchronisationSource();
185
+ const sessionInfo = {
186
+ address: request.targetAddress,
187
+ videoPort: video.port,
188
+ returnVideoPort,
189
+ videoSRTP: Buffer.concat([video.srtp_key, video.srtp_salt]),
190
+ videoSSRC,
191
+ audioPort: audio.port,
192
+ returnAudioPort,
193
+ audioSRTP: Buffer.concat([audio.srtp_key, audio.srtp_salt]),
194
+ audioSSRC,
195
+ };
196
+ this.pendingSessions.set(sessionId, sessionInfo);
197
+ callback(undefined, {
198
+ video: {
199
+ port: returnVideoPort,
200
+ ssrc: videoSSRC,
201
+ srtp_key: video.srtp_key,
202
+ srtp_salt: video.srtp_salt,
203
+ },
204
+ audio: {
205
+ port: audioServerPort,
206
+ ssrc: audioSSRC,
207
+ srtp_key: audio.srtp_key,
208
+ srtp_salt: audio.srtp_salt,
209
+ },
210
+ });
211
+ }
212
+ catch (error) {
213
+ this.log.error(`Failed to prepare a video stream for '${this.name}':`, error);
214
+ callback(error);
215
+ }
216
+ }
217
+ handleStreamRequest(request, callback) {
218
+ const hap = this.platform.api.hap;
219
+ const sessionId = request.sessionID;
220
+ switch (request.type) {
221
+ case "start" /* hap.StreamRequestTypes.START */: {
222
+ this.log.info(`[${this.data.Name}] Starting stream for session ${sessionId}`);
223
+ const sessionInfo = this.pendingSessions.get(sessionId);
224
+ this.pendingSessions.delete(sessionId);
225
+ if (!sessionInfo || !this.rtspUrl) {
226
+ callback(new Error('No pending session or stream URL available'));
227
+ return;
228
+ }
229
+ const sourceArgs = [
230
+ '-rtsp_transport', 'tcp',
231
+ '-i', this.rtspUrl,
232
+ ];
233
+ const videoArgs = [
234
+ '-an', '-sn', '-dn',
235
+ '-codec:v', 'libx264',
236
+ '-pix_fmt', 'yuv420p',
237
+ '-preset', 'ultrafast',
238
+ '-tune', 'zerolatency',
239
+ '-r', `${request.video.fps}`,
240
+ '-b:v', '1000k',
241
+ '-bufsize', '1000k',
242
+ '-maxrate', '1000k',
243
+ '-payload_type', `${request.video.pt}`,
244
+ '-ssrc', `${sessionInfo.videoSSRC}`,
245
+ '-f', 'rtp',
246
+ '-srtp_out_suite', 'AES_CM_128_HMAC_SHA1_80',
247
+ '-srtp_out_params', sessionInfo.videoSRTP.toString('base64'),
248
+ `srtp://${sessionInfo.address}:${sessionInfo.videoPort}`
249
+ + `?rtcpport=${sessionInfo.videoPort}&localrtcpport=${sessionInfo.returnVideoPort}&pkt_size=1316`,
250
+ ];
251
+ const audioArgs = request.audio ? [
252
+ '-vn', '-sn', '-dn',
253
+ '-codec:a', 'libfdk_aac',
254
+ '-profile:a', 'aac_eld',
255
+ '-flags', '+global_header',
256
+ '-fflags', '+genpts',
257
+ '-ar', '16k',
258
+ '-b:a', '24k',
259
+ '-ac', '1',
260
+ '-use_wallclock_as_timestamps', '1',
261
+ '-bufsize', '24k',
262
+ '-payload_type', `${request.audio.pt}`,
263
+ '-ssrc', `${sessionInfo.audioSSRC}`,
264
+ '-f', 'rtp',
265
+ '-srtp_out_suite', 'AES_CM_128_HMAC_SHA1_80',
266
+ '-srtp_out_params', sessionInfo.audioSRTP.toString('base64'),
267
+ `srtp://${sessionInfo.address}:${sessionInfo.audioPort}`
268
+ + `?rtcpport=${sessionInfo.audioPort}&localrtcpport=${sessionInfo.returnAudioPort}&pkt_size=188`,
269
+ ] : [];
270
+ const args = [...sourceArgs, ...videoArgs, ...audioArgs];
271
+ const cmd = spawn(this.platform.ffmpegPath, args, { env: process.env });
272
+ this.log.debug(`Started ffmpeg stream for camera '${this.data.Name}':`, redactUrlCredentials([this.platform.ffmpegPath, ...args].join(' ')));
273
+ let started = false;
274
+ cmd.stderr.on('data', (chunk) => {
275
+ if (!started) {
276
+ started = true;
277
+ this.log.debug('FFmpeg received first frame');
278
+ callback();
279
+ }
280
+ this.log.debug(redactUrlCredentials(chunk.toString()));
281
+ });
282
+ cmd.on('error', error => {
283
+ this.log.error('An error occurred while starting the video stream:', error.message);
284
+ if (!started) {
285
+ started = true;
286
+ callback(error);
287
+ }
288
+ });
289
+ cmd.on('close', code => {
290
+ switch (code) {
291
+ case null:
292
+ case 0:
293
+ case 255:
294
+ this.log.debug('Camera stopped streaming');
295
+ break;
296
+ default:
297
+ this.log.debug(`FFmpeg exited with code ${code}`);
298
+ if (!started) {
299
+ started = true;
300
+ callback(new Error(`FFmpeg exited with code ${code}`));
301
+ }
302
+ else {
303
+ this.controller?.forceStopStreamingSession(sessionId);
304
+ }
305
+ break;
306
+ }
307
+ this.ongoingSessions.delete(sessionId);
308
+ });
309
+ this.ongoingSessions.set(sessionId, cmd);
310
+ break;
311
+ }
312
+ case "stop" /* hap.StreamRequestTypes.STOP */: {
313
+ const cmd = this.ongoingSessions.get(sessionId);
314
+ try {
315
+ cmd?.kill('SIGKILL');
316
+ }
317
+ catch (error) {
318
+ this.log.error('Error occurred terminating the video process:', error);
319
+ }
320
+ this.ongoingSessions.delete(sessionId);
321
+ callback();
322
+ break;
323
+ }
324
+ case "reconfigure" /* hap.StreamRequestTypes.RECONFIGURE */:
325
+ this.log.debug('(Not implemented) Received request to reconfigure the stream');
326
+ callback();
327
+ break;
328
+ }
329
+ }
330
+ ringDoorbell() {
331
+ if (this.controller && 'ringDoorbell' in this.controller) {
332
+ this.controller.ringDoorbell();
333
+ }
334
+ if (this.buttonService) {
335
+ this.buttonService.updateCharacteristic(this.Characteristic.ContactSensorState, this.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);
336
+ setTimeout(() => {
337
+ this.buttonService?.updateCharacteristic(this.Characteristic.ContactSensorState, this.Characteristic.ContactSensorState.CONTACT_DETECTED);
338
+ }, 500);
339
+ }
340
+ }
341
+ notify() {
342
+ if (this.getMotionDetectedState()) {
343
+ this.motionService.updateCharacteristic(this.Characteristic.MotionDetected, true);
344
+ setTimeout(() => {
345
+ this.motionService.updateCharacteristic(this.Characteristic.MotionDetected, false);
346
+ }, MOTION_RESET_MS);
347
+ }
348
+ if (this.getDoorbellButtonPress()) {
349
+ const now = Date.now();
350
+ if (now - this.lastDoorbellRing >= DOORBELL_IGNORE_MS) {
351
+ this.lastDoorbellRing = now;
352
+ this.ringDoorbell();
353
+ }
354
+ else {
355
+ this.log.debug('Ignoring duplicate doorbell press notification');
356
+ }
357
+ }
358
+ }
359
+ static appliesTo(data) {
360
+ return data.Type === VivintDict.PanelDeviceType.Camera;
361
+ }
362
+ static inferCategory(data, categories) {
363
+ return (data.Name ?? '').toLowerCase().includes('doorbell')
364
+ ? categories.VIDEO_DOORBELL
365
+ : categories.IP_CAMERA;
366
+ }
367
+ }
368
+ //# sourceMappingURL=camera.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera.js","sourceRoot":"","sources":["../../src/devices/camera.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { sanitizeDeviceName } from '../utils/sanitizeName.js';
2
+ import { VivintDict } from '../vivint/dictionary.js';
3
+ import { VivintDevice } from './device.js';
4
+ const EQUIPMENT_CODES = [
5
+ 'VS_CO3_DETECTOR',
6
+ 'EXISTING_CO',
7
+ 'CO1_CO_CANADA',
8
+ 'CO1_CO',
9
+ 'CO3_2GIG_CO',
10
+ 'CARBON_MONOXIDE_DETECTOR_345_MHZ',
11
+ ].map(name => VivintDict.EquipmentCode[name]);
12
+ export class CarbonMonoxideSensor extends VivintDevice {
13
+ static className = 'CarbonMonoxideSensor';
14
+ static hasBattery = true;
15
+ service;
16
+ constructor(platform, accessory, data) {
17
+ super(platform, accessory, data);
18
+ this.service = this.ensureService(this.Service.CarbonMonoxideSensor, sanitizeDeviceName(this.name, this.id));
19
+ this.batteryService?.updateCharacteristic(this.Characteristic.ChargingState, this.Characteristic.ChargingState.NOT_CHARGEABLE);
20
+ this.service.getCharacteristic(this.Characteristic.CarbonMonoxideDetected)
21
+ .onGet(() => this.getSensorState());
22
+ this.service.getCharacteristic(this.Characteristic.StatusTampered)
23
+ .onGet(() => this.getTamperedState());
24
+ this.notify();
25
+ }
26
+ getSensorState() {
27
+ return this.data.Status
28
+ ? this.Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL
29
+ : this.Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL;
30
+ }
31
+ notify() {
32
+ super.notify();
33
+ this.service.updateCharacteristic(this.Characteristic.CarbonMonoxideDetected, this.getSensorState());
34
+ this.service.updateCharacteristic(this.Characteristic.StatusTampered, this.getTamperedState());
35
+ }
36
+ static appliesTo(data) {
37
+ return data.Type === VivintDict.PanelDeviceType.WirelessSensor
38
+ && EQUIPMENT_CODES.includes(data.EquipmentCode);
39
+ }
40
+ static inferCategory(data, categories) {
41
+ return categories.SENSOR;
42
+ }
43
+ }
44
+ //# sourceMappingURL=carbonMonoxideSensor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"carbonMonoxideSensor.js","sourceRoot":"","sources":["../../src/devices/carbonMonoxideSensor.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAiB,YAAY,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,eAAe,GAAG;IACtB,iBAAiB;IACjB,aAAa;IACb,eAAe;IACf,QAAQ;IACR,aAAa;IACb,kCAAkC;CACnC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;AAE9C,MAAM,OAAO,oBAAqB,SAAQ,YAAY;IACpD,MAAM,CAAmB,SAAS,GAAG,sBAAsB,CAAC;IAC5D,MAAM,CAAmB,UAAU,GAAG,IAAI,CAAC;IAE1B,OAAO,CAAU;IAElC,YAAY,QAAwB,EAAE,SAAoD,EAAE,IAA4B;QACtH,KAAK,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QAEjC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7G,IAAI,CAAC,cAAc,EAAE,oBAAoB,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,EAAE,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;QAE/H,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,cAAc,CAAC,sBAAsB,CAAC;aACvE,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;QACtC,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC;aAC/D,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;QAExC,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,cAAc;QACZ,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM;YACrB,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,sBAAsB,CAAC,kBAAkB;YAC/D,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,sBAAsB,CAAC,gBAAgB,CAAC;IAClE,CAAC;IAEQ,MAAM;QACb,KAAK,CAAC,MAAM,EAAE,CAAC;QACf,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,cAAc,CAAC,sBAAsB,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;QACrG,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,cAAc,CAAC,cAAc,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;IACjG,CAAC;IAED,MAAM,CAAU,SAAS,CAAC,IAAgB;QACxC,OAAO,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,eAAe,CAAC,cAAc;eACzD,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAuB,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,CAAU,aAAa,CAAC,IAAgB,EAAE,UAAyB;QACvE,OAAO,UAAU,CAAC,MAAM,CAAC;IAC3B,CAAC"}