homebridge-ring 9.22.0 → 9.25.0-beta.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 +43 -0
- package/README.md +8 -0
- package/config.schema.json +11 -0
- package/lib/api/api.d.ts +1 -0
- package/lib/api/api.js +4 -2
- package/lib/api/rest-client.d.ts +3 -0
- package/lib/api/rest-client.js +18 -6
- package/lib/api/ring-camera.d.ts +12 -8
- package/lib/api/ring-camera.js +78 -97
- package/lib/api/ring-chime.d.ts +1 -1
- package/lib/api/ring-chime.js +1 -1
- package/lib/api/ring-types.d.ts +3 -5
- package/lib/api/ring-types.js +4 -1
- package/lib/api/sip-session.d.ts +4 -0
- package/lib/api/sip-session.js +26 -22
- package/lib/api/sip.d.ts +1 -0
- package/lib/api/sip.js +1 -0
- package/lib/api/util.js +5 -2
- package/lib/homebridge/camera-source.js +4 -3
- package/lib/homebridge/camera.js +1 -1
- package/lib/homebridge/config.d.ts +1 -0
- package/lib/homebridge/ring-platform.js +10 -4
- package/package.json +21 -21
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [9.25.0-beta.0](https://github.com/dgreif/ring/compare/v9.24.0...v9.25.0-beta.0) (2022-01-29)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **api:** decouple ffmpeg prep from execution ([#859](https://github.com/dgreif/ring/issues/859)) ([e3cc9d1](https://github.com/dgreif/ring/commit/e3cc9d15584377bf2be94a9d457efc3dfcb4bd8b))
|
|
11
|
+
* handle new camera types with no battery ([11ae286](https://github.com/dgreif/ring/commit/11ae28697d8cb0e2da71e880c82e1883f04047b9))
|
|
12
|
+
* slightly faster snapshots via new endpoint ([b840471](https://github.com/dgreif/ring/commit/b840471bc6b7b66e5873370251bd57c1a35b267a))
|
|
13
|
+
|
|
14
|
+
## [9.24.0](https://github.com/dgreif/ring/compare/v9.23.0...v9.24.0) (2022-01-22)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* support glassbreak sensor ([98271f9](https://github.com/dgreif/ring/commit/98271f91f13aaae051fccaab7165cd17f2a2377c))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* allow process to exit when RingApi.disconnect() is called ([#851](https://github.com/dgreif/ring/issues/851)) ([8ef25ac](https://github.com/dgreif/ring/commit/8ef25acca66c533873b7fd43c7c899e34b4a7617))
|
|
25
|
+
* avoid error when ring terminates video stream ([7717d5e](https://github.com/dgreif/ring/commit/7717d5eaa40afed3d34f0e8e6eea1402e21afef7)), closes [#853](https://github.com/dgreif/ring/issues/853)
|
|
26
|
+
* update dependencies ([c80d052](https://github.com/dgreif/ring/commit/c80d052f9eaf4c0a50a495166177daa40299ffa7))
|
|
27
|
+
|
|
28
|
+
## [9.23.0](https://github.com/dgreif/ring/compare/v9.22.1...v9.23.0) (2021-10-17)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Features
|
|
32
|
+
|
|
33
|
+
* **homebridge:** config option to hide camera lights ([58be17c](https://github.com/dgreif/ring/commit/58be17cfee524bfb158566e59d60438e9b6f0f5d)), closes [#802](https://github.com/dgreif/ring/issues/802)
|
|
34
|
+
* **homebridge:** option to treat "knock" as a ding ([7ece8b2](https://github.com/dgreif/ring/commit/7ece8b25314ef403a6c2bab6a28476a3b7229507)), closes [#514](https://github.com/dgreif/ring/issues/514)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Bug Fixes
|
|
38
|
+
|
|
39
|
+
* update dependencies ([b685e27](https://github.com/dgreif/ring/commit/b685e27e952a94635a277848821ebfa640d50ee2))
|
|
40
|
+
|
|
41
|
+
### [9.22.1](https://github.com/dgreif/ring/compare/v9.22.0...v9.22.1) (2021-10-13)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### Bug Fixes
|
|
45
|
+
|
|
46
|
+
* **homebridge:** prevent login attempt for null refresh tokens ([#791](https://github.com/dgreif/ring/issues/791)) ([bbc9db5](https://github.com/dgreif/ring/commit/bbc9db5578f9b3ed2af49256c8581fef5af8e134))
|
|
47
|
+
|
|
5
48
|
## [9.22.0](https://github.com/dgreif/ring/compare/v9.21.2...v9.22.0) (2021-10-02)
|
|
6
49
|
|
|
7
50
|
|
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ Only include an optional parameter if you actually need it. Default behavior wi
|
|
|
57
57
|
"hideDeviceIds": ["477e4800-fcde-4493-969b-d1a06f683102", "5aaed7a7-06df-4f18-b3af-291c89854d60"],
|
|
58
58
|
"hideLightGroups": true,
|
|
59
59
|
"hideDoorbellSwitch": true,
|
|
60
|
+
"hideCameraLight": true,
|
|
60
61
|
"hideCameraMotionSensor": true,
|
|
61
62
|
"sendCameraMotionNotificationsToTv": true,
|
|
62
63
|
"sendDoorbellMotionNotificationsToTv": true,
|
|
@@ -65,6 +66,7 @@ Only include an optional parameter if you actually need it. Default behavior wi
|
|
|
65
66
|
"hideAlarmSirenSwitch": true,
|
|
66
67
|
"hideUnsupportedServices": true,
|
|
67
68
|
"avoidSnapshotBatteryDrain": true,
|
|
69
|
+
"treatKnockAsDing": true,
|
|
68
70
|
"cameraStatusPollingSeconds": 20,
|
|
69
71
|
"cameraDingsPollingSeconds": 2,
|
|
70
72
|
"locationModePollingSeconds": 20,
|
|
@@ -79,6 +81,7 @@ Option | Default | Explanation
|
|
|
79
81
|
`hideDeviceIds` | `[]` | Allows you to hide specific devices by an array of ids. The id for each device is logged when homebridge starts.
|
|
80
82
|
`hideLightGroups` | `false` | Ring smart lighting allows you to create lighting groups within the Ring app. These groups are convenient for detecting motion in an area of your yard and turning on/off all lights in the group. However, you may wish to group the lights differently in HomeKit and ignore the groups you have configured in Ring. If this option is `true`, your Ring groups (and their associated motion sensor) will be ignored and will not show up in HomeKit.
|
|
81
83
|
`hideDoorbellSwitch` | `false` | If you have a Ring video doorbell, you will see a Programmable Switch associated with it. This switch can be used to perform actions on when the doorbell is pressed using "Single Press" actions. If you do not care to perform actions when the doorbell is pressed, you can hide the Programmable Switch by setting this option to `true`. You will still be able to receive _notifications_ from the doorbell even if the Programmable Switch is hidden (notifications can be configured in the settings for the doorbell camera in the Home app)
|
|
84
|
+
`hideCameraLight` | `false` | If `true`, hides the light for Ring cameras in HomeKit.
|
|
82
85
|
`hideCameraMotionSensor` | `false` | If `true`, hides the motion sensor for Ring cameras in HomeKit.
|
|
83
86
|
`sendCameraMotionNotificationsToTv` | `false` | If `true`, creates a 'Doorbell' accessory triggered by non-doorbell camera motion alerts for tvOS 14 notification support.
|
|
84
87
|
`sendDoorbellMotionNotificationsToTv` | `false` | If `true`, motion events on doorbell cameras will trigger a doorbell press event for tvOS 14 notification support.
|
|
@@ -89,6 +92,7 @@ Option | Default | Explanation
|
|
|
89
92
|
`showPanicButtons` | `false` | Creates a new `Panic Buttons` device in HomeKit with `Burglar Alarm` and `Fire Alarm` switches. **Use these at your own risk. I do not guarantee functionality in case of emergency, nor do I take responsibility for any false alarms**. These function just like the SOS sliders in the Ring app.
|
|
90
93
|
`nightModeBypassFor` | no Night Mode | Allows you to use Night mode to "Bypass and Arm" a Ring Alarm. Can be set to `all` for Away or `some` for Home. If set and Night mode is activated from HomeKit, any open contact sensors will automatically be bypassed.
|
|
91
94
|
`avoidSnapshotBatteryDrain` | false | Causes snapshots for battery cameras to be fetched at a minimum 10 minute interval to avoid draining the battery.
|
|
95
|
+
`treatKnockAsDing` | false | When someone knocks on your door, it will be recognized as a doorbell press in HomeKit. Limited to select camera models (Peephole Cam).
|
|
92
96
|
`cameraStatusPollingSeconds` | `20` | How frequently to poll for updates to your cameras and chimes (in seconds). Information like light/siren/volume/snooze status do not update in real time and need to be requested periodically.
|
|
93
97
|
`cameraDingsPollingSeconds` | `2` | How frequently to poll for new events from your cameras. These include motion and doorbell presses.
|
|
94
98
|
`locationModePollingSeconds` | `20` | How frequently to poll for location mode updates (in seconds). This is only useful if you are using location modes to control camera settings and want to keep an up-to-date reference of the current mode for each location. Polling is automatically disabled for locations equipped with a Ring Alarm. Will hide Location Mode switch if set to `0`
|
|
@@ -161,6 +165,10 @@ If you are having issues with your cameras in the Home app, please see the [Came
|
|
|
161
165
|
* Detect water leak
|
|
162
166
|
* Tamper status
|
|
163
167
|
* Battery status
|
|
168
|
+
* Glassbreak Sensor (**untested**)
|
|
169
|
+
* Detect glass breaking
|
|
170
|
+
* Tamper status
|
|
171
|
+
* Battery status
|
|
164
172
|
* Ring Smart Lights (Motion Detector, Flood/Path/Step/Spot Lights, Bulbs, Transformer, Outdoor Smart Plug, Mailbox Sensor)
|
|
165
173
|
* On/Off
|
|
166
174
|
* Brightness Level
|
package/config.schema.json
CHANGED
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
"type": "boolean",
|
|
32
32
|
"description": "Hides the Programmable Switch for Ring doorbells. This switch is only useful for triggering an automation when the doorbell is pressed, so most users can safely hide it"
|
|
33
33
|
},
|
|
34
|
+
"hideCameraLight": {
|
|
35
|
+
"title": "Hide Camera Light",
|
|
36
|
+
"type": "boolean"
|
|
37
|
+
},
|
|
34
38
|
"hideCameraMotionSensor": {
|
|
35
39
|
"title": "Hide Camera Motion Sensors",
|
|
36
40
|
"type": "boolean"
|
|
@@ -82,6 +86,11 @@
|
|
|
82
86
|
"type": "boolean",
|
|
83
87
|
"description": "Causes snapshots for battery cameras to be fetched at a minimum 10 minute interval to avoid draining the battery. This is useful if have the Home app open constantly, causing frequent snapshot requests."
|
|
84
88
|
},
|
|
89
|
+
"treatKnockAsDing": {
|
|
90
|
+
"title": "Treat Knock as Ding",
|
|
91
|
+
"type": "boolean",
|
|
92
|
+
"description": "When someone knocks on your door, it will be recognized as a doorbell press in HomeKit. Limited to select camera models (Peephole Cam)"
|
|
93
|
+
},
|
|
85
94
|
"sendCameraMotionNotificationsToTv": {
|
|
86
95
|
"title": "Create a 'Doorbell' accessory triggered by non-doorbell camera motion alerts for tvOS 14 notification support",
|
|
87
96
|
"type": "boolean"
|
|
@@ -161,6 +170,7 @@
|
|
|
161
170
|
"alarmOnEntryDelay",
|
|
162
171
|
"hideLightGroups",
|
|
163
172
|
"hideDoorbellSwitch",
|
|
173
|
+
"hideCameraLight",
|
|
164
174
|
"hideCameraMotionSensor",
|
|
165
175
|
"hideCameraSirenSwitch",
|
|
166
176
|
"hideInHomeDoorbellSwitch",
|
|
@@ -169,6 +179,7 @@
|
|
|
169
179
|
"showPanicButtons",
|
|
170
180
|
"nightModeBypassFor",
|
|
171
181
|
"avoidSnapshotBatteryDrain",
|
|
182
|
+
"treatKnockAsDing",
|
|
172
183
|
"sendCameraMotionNotificationsToTv",
|
|
173
184
|
"sendDoorbellMotionNotificationsToTv",
|
|
174
185
|
"beamDurationSeconds",
|
package/lib/api/api.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface RingApiOptions extends SessionOptions {
|
|
|
9
9
|
cameraDingsPollingSeconds?: number;
|
|
10
10
|
locationModePollingSeconds?: number;
|
|
11
11
|
avoidSnapshotBatteryDrain?: boolean;
|
|
12
|
+
treatKnockAsDing?: boolean;
|
|
12
13
|
debug?: boolean;
|
|
13
14
|
ffmpegPath?: string;
|
|
14
15
|
externalPorts?: {
|
package/lib/api/api.js
CHANGED
|
@@ -24,7 +24,8 @@ class RingApi extends subscribed_1.Subscribed {
|
|
|
24
24
|
super();
|
|
25
25
|
this.options = options;
|
|
26
26
|
this.restClient = new rest_client_1.RingRestClient(this.options);
|
|
27
|
-
this.onRefreshTokenUpdated =
|
|
27
|
+
this.onRefreshTokenUpdated =
|
|
28
|
+
this.restClient.onRefreshTokenUpdated.asObservable();
|
|
28
29
|
if (options.debug) {
|
|
29
30
|
(0, util_1.enableDebug)();
|
|
30
31
|
}
|
|
@@ -128,7 +129,7 @@ class RingApi extends subscribed_1.Subscribed {
|
|
|
128
129
|
return __awaiter(this, void 0, void 0, function* () {
|
|
129
130
|
const rawLocations = yield this.fetchRawLocations(), { authorizedDoorbots, chimes, doorbots, allCameras, baseStations, beamBridges, } = yield this.fetchRingDevices(), locationIdsWithHubs = [...baseStations, ...beamBridges].map((x) => x.location_id), cameras = allCameras.map((data) => new ring_camera_1.RingCamera(data, doorbots.includes(data) ||
|
|
130
131
|
authorizedDoorbots.includes(data) ||
|
|
131
|
-
data.kind.startsWith('doorbell'), this.restClient, this.options.avoidSnapshotBatteryDrain || false)), ringChimes = chimes.map((data) => new ring_chime_1.RingChime(data, this.restClient)), locations = rawLocations
|
|
132
|
+
data.kind.startsWith('doorbell'), this.restClient, this.options.avoidSnapshotBatteryDrain || false, this.options.treatKnockAsDing || false)), ringChimes = chimes.map((data) => new ring_chime_1.RingChime(data, this.restClient)), locations = rawLocations
|
|
132
133
|
.filter((location) => {
|
|
133
134
|
return (!Array.isArray(this.options.locationIds) ||
|
|
134
135
|
this.options.locationIds.includes(location.location_id));
|
|
@@ -169,6 +170,7 @@ class RingApi extends subscribed_1.Subscribed {
|
|
|
169
170
|
.catch((e) => {
|
|
170
171
|
(0, util_1.logError)(e);
|
|
171
172
|
});
|
|
173
|
+
this.restClient.clearTimeouts();
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
exports.RingApi = RingApi;
|
package/lib/api/rest-client.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare function deviceApi(path: string): string;
|
|
|
6
6
|
export declare function appApi(path: string): string;
|
|
7
7
|
export interface ExtendedResponse {
|
|
8
8
|
responseTimestamp: number;
|
|
9
|
+
timeMillis: number;
|
|
9
10
|
}
|
|
10
11
|
export interface EmailAuth {
|
|
11
12
|
email: string;
|
|
@@ -24,6 +25,7 @@ export declare class RingRestClient {
|
|
|
24
25
|
refreshToken: string | undefined;
|
|
25
26
|
private hardwareIdPromise;
|
|
26
27
|
private _authPromise;
|
|
28
|
+
private timeouts;
|
|
27
29
|
private clearPreviousAuth;
|
|
28
30
|
private get authPromise();
|
|
29
31
|
private sessionPromise?;
|
|
@@ -44,4 +46,5 @@ export declare class RingRestClient {
|
|
|
44
46
|
url: string;
|
|
45
47
|
}): Promise<T & ExtendedResponse>;
|
|
46
48
|
getCurrentAuth(): Promise<AuthTokenResponse>;
|
|
49
|
+
clearTimeouts(): void;
|
|
47
50
|
}
|
package/lib/api/rest-client.js
CHANGED
|
@@ -44,8 +44,13 @@ function requestWithRetry(requestOptions) {
|
|
|
44
44
|
return __awaiter(this, void 0, void 0, function* () {
|
|
45
45
|
try {
|
|
46
46
|
const options = Object.assign(Object.assign({}, defaultRequestOptions), requestOptions), { headers, body } = (yield (0, got_1.default)(options)), data = body;
|
|
47
|
-
if (data !== null && typeof data === 'object'
|
|
48
|
-
|
|
47
|
+
if (data !== null && typeof data === 'object') {
|
|
48
|
+
if (headers.date) {
|
|
49
|
+
data.responseTimestamp = new Date(headers.date).getTime();
|
|
50
|
+
}
|
|
51
|
+
if (headers['x-time-millis']) {
|
|
52
|
+
data.timeMillis = Number(headers['x-time-millis']);
|
|
53
|
+
}
|
|
49
54
|
}
|
|
50
55
|
return data;
|
|
51
56
|
}
|
|
@@ -66,12 +71,15 @@ function requestWithRetry(requestOptions) {
|
|
|
66
71
|
class RingRestClient {
|
|
67
72
|
constructor(authOptions) {
|
|
68
73
|
this.authOptions = authOptions;
|
|
69
|
-
|
|
70
|
-
this.refreshToken = ('refreshToken' in this.authOptions ? this.authOptions.refreshToken : undefined);
|
|
71
|
-
this.hardwareIdPromise = (0, util_1.getHardwareId)(this.authOptions.systemId);
|
|
74
|
+
this.timeouts = [];
|
|
72
75
|
this.sessionPromise = undefined;
|
|
73
76
|
this.using2fa = false;
|
|
74
77
|
this.onRefreshTokenUpdated = new rxjs_1.ReplaySubject(1);
|
|
78
|
+
this.refreshToken =
|
|
79
|
+
'refreshToken' in this.authOptions
|
|
80
|
+
? this.authOptions.refreshToken
|
|
81
|
+
: undefined;
|
|
82
|
+
this.hardwareIdPromise = (0, util_1.getHardwareId)(this.authOptions.systemId);
|
|
75
83
|
}
|
|
76
84
|
clearPreviousAuth() {
|
|
77
85
|
this._authPromise = undefined;
|
|
@@ -83,11 +91,12 @@ class RingRestClient {
|
|
|
83
91
|
authPromise
|
|
84
92
|
.then(({ expires_in }) => {
|
|
85
93
|
// clear the existing auth promise 1 minute before it expires
|
|
86
|
-
setTimeout(() => {
|
|
94
|
+
const timeout = setTimeout(() => {
|
|
87
95
|
if (this._authPromise === authPromise) {
|
|
88
96
|
this.clearPreviousAuth();
|
|
89
97
|
}
|
|
90
98
|
}, ((expires_in || 3600) - 60) * 1000);
|
|
99
|
+
this.timeouts.push(timeout);
|
|
91
100
|
})
|
|
92
101
|
.catch(() => {
|
|
93
102
|
// ignore these errors here, they should be handled by the function making a rest request
|
|
@@ -288,5 +297,8 @@ class RingRestClient {
|
|
|
288
297
|
getCurrentAuth() {
|
|
289
298
|
return this.authPromise;
|
|
290
299
|
}
|
|
300
|
+
clearTimeouts() {
|
|
301
|
+
this.timeouts.forEach(clearTimeout);
|
|
302
|
+
}
|
|
291
303
|
}
|
|
292
304
|
exports.RingRestClient = RingRestClient;
|
package/lib/api/ring-camera.d.ts
CHANGED
|
@@ -16,13 +16,13 @@ export declare class RingCamera extends Subscribed {
|
|
|
16
16
|
isDoorbot: boolean;
|
|
17
17
|
private restClient;
|
|
18
18
|
private avoidSnapshotBatteryDrain;
|
|
19
|
+
private treatKnockAsDing;
|
|
19
20
|
id: number;
|
|
20
21
|
deviceType: import("./ring-types").RingCameraKind;
|
|
21
22
|
model: string;
|
|
22
23
|
onData: BehaviorSubject<CameraData>;
|
|
23
24
|
hasLight: boolean;
|
|
24
25
|
hasSiren: boolean;
|
|
25
|
-
hasBattery: boolean;
|
|
26
26
|
onRequestUpdate: Subject<unknown>;
|
|
27
27
|
onRequestActiveDings: Subject<unknown>;
|
|
28
28
|
onNewDing: Subject<ActiveDing>;
|
|
@@ -32,13 +32,14 @@ export declare class RingCamera extends Subscribed {
|
|
|
32
32
|
onMotionStarted: import("rxjs").Observable<null>;
|
|
33
33
|
onBatteryLevel: import("rxjs").Observable<number | null>;
|
|
34
34
|
onInHomeDoorbellStatus: import("rxjs").Observable<boolean>;
|
|
35
|
-
constructor(initialData: CameraData, isDoorbot: boolean, restClient: RingRestClient, avoidSnapshotBatteryDrain: boolean);
|
|
35
|
+
constructor(initialData: CameraData, isDoorbot: boolean, restClient: RingRestClient, avoidSnapshotBatteryDrain: boolean, treatKnockAsDing: boolean);
|
|
36
36
|
updateData(update: CameraData): void;
|
|
37
37
|
requestUpdate(): void;
|
|
38
38
|
get data(): CameraData;
|
|
39
39
|
get name(): string;
|
|
40
40
|
get activeDings(): ActiveDing[];
|
|
41
41
|
get batteryLevel(): number | null;
|
|
42
|
+
get hasBattery(): boolean;
|
|
42
43
|
get hasLowBattery(): boolean;
|
|
43
44
|
get isCharging(): boolean;
|
|
44
45
|
get operatingOnBattery(): boolean;
|
|
@@ -74,18 +75,21 @@ export declare class RingCamera extends Subscribed {
|
|
|
74
75
|
}): Promise<string>;
|
|
75
76
|
private isTimestampInLifeTime;
|
|
76
77
|
get snapshotsAreBlocked(): boolean;
|
|
77
|
-
private getSnapshotTimestamp;
|
|
78
|
-
private refreshSnapshotInProgress?;
|
|
79
78
|
get snapshotLifeTime(): number;
|
|
79
|
+
private lastSnapshotTimestamp;
|
|
80
80
|
private lastSnapshotTimestampLocal;
|
|
81
81
|
private lastSnapshotPromise?;
|
|
82
82
|
get currentTimestampAge(): number;
|
|
83
|
-
get currentTimestampExpiresIn(): number;
|
|
84
83
|
get hasSnapshotWithinLifetime(): boolean;
|
|
85
84
|
private checkIfSnapshotsAreBlocked;
|
|
86
|
-
private
|
|
87
|
-
private
|
|
88
|
-
getSnapshot(): Promise<Buffer
|
|
85
|
+
private shouldUseExistingSnapshotPromise;
|
|
86
|
+
private fetchingSnapshot;
|
|
87
|
+
getSnapshot(): Promise<Buffer>;
|
|
88
|
+
getNextSnapshot({ afterMs, maxWaitMs, force, }: {
|
|
89
|
+
afterMs?: number;
|
|
90
|
+
maxWaitMs?: number;
|
|
91
|
+
force?: boolean;
|
|
92
|
+
}): Promise<Buffer & import("./rest-client").ExtendedResponse>;
|
|
89
93
|
getSipOptions(): Promise<SipOptions>;
|
|
90
94
|
getUpdatedSipOptions(expiredDingId: string): Promise<SipOptions>;
|
|
91
95
|
createSipSession(options?: {
|
package/lib/api/ring-camera.js
CHANGED
|
@@ -18,8 +18,7 @@ const camera_utils_1 = require("@homebridge/camera-utils");
|
|
|
18
18
|
const util_1 = require("./util");
|
|
19
19
|
const sip_session_1 = require("./sip-session");
|
|
20
20
|
const subscribed_1 = require("./subscribed");
|
|
21
|
-
const
|
|
22
|
-
maxSnapshotRefreshAttempts = (maxSnapshotRefreshSeconds * 1000) / snapshotRefreshDelay, fullDayMs = 24 * 60 * 60 * 1000;
|
|
21
|
+
const maxSnapshotRefreshSeconds = 15, fullDayMs = 24 * 60 * 60 * 1000;
|
|
23
22
|
function parseBatteryLife(batteryLife) {
|
|
24
23
|
if (batteryLife === null || batteryLife === undefined) {
|
|
25
24
|
return null;
|
|
@@ -66,37 +65,36 @@ function getSearchQueryString(options) {
|
|
|
66
65
|
}
|
|
67
66
|
exports.getSearchQueryString = getSearchQueryString;
|
|
68
67
|
class RingCamera extends subscribed_1.Subscribed {
|
|
69
|
-
constructor(initialData, isDoorbot, restClient, avoidSnapshotBatteryDrain) {
|
|
68
|
+
constructor(initialData, isDoorbot, restClient, avoidSnapshotBatteryDrain, treatKnockAsDing) {
|
|
70
69
|
super();
|
|
71
70
|
this.initialData = initialData;
|
|
72
71
|
this.isDoorbot = isDoorbot;
|
|
73
72
|
this.restClient = restClient;
|
|
74
73
|
this.avoidSnapshotBatteryDrain = avoidSnapshotBatteryDrain;
|
|
75
|
-
this.
|
|
76
|
-
this.deviceType = this.initialData.kind;
|
|
77
|
-
this.model = ring_types_1.RingCameraModel[this.initialData.kind] || 'Unknown Model';
|
|
78
|
-
this.onData = new rxjs_1.BehaviorSubject(this.initialData);
|
|
79
|
-
this.hasLight = this.initialData.led_status !== undefined;
|
|
80
|
-
this.hasSiren = this.initialData.siren_status !== undefined;
|
|
81
|
-
this.hasBattery = (0, ring_types_1.isBatteryCameraKind)(this.deviceType) ||
|
|
82
|
-
(typeof this.initialData.battery_life === 'string' &&
|
|
83
|
-
this.batteryLevel !== null &&
|
|
84
|
-
this.batteryLevel < 100 &&
|
|
85
|
-
this.batteryLevel >= 0);
|
|
74
|
+
this.treatKnockAsDing = treatKnockAsDing;
|
|
86
75
|
this.onRequestUpdate = new rxjs_1.Subject();
|
|
87
76
|
this.onRequestActiveDings = new rxjs_1.Subject();
|
|
88
77
|
this.onNewDing = new rxjs_1.Subject();
|
|
89
78
|
this.onActiveDings = new rxjs_1.BehaviorSubject([]);
|
|
90
|
-
this.onDoorbellPressed = this.onNewDing.pipe((0, operators_1.filter)((ding) => ding.kind === 'ding'
|
|
79
|
+
this.onDoorbellPressed = this.onNewDing.pipe((0, operators_1.filter)((ding) => ding.kind === 'ding' ||
|
|
80
|
+
(this.treatKnockAsDing && ding.kind === 'door_activity')), (0, operators_1.share)());
|
|
91
81
|
this.onMotionDetected = this.onActiveDings.pipe((0, operators_1.map)((dings) => dings.some((ding) => ding.motion || ding.kind === 'motion')), (0, operators_1.distinctUntilChanged)(), (0, operators_1.publishReplay)(1), (0, operators_1.refCount)());
|
|
92
82
|
this.onMotionStarted = this.onMotionDetected.pipe((0, operators_1.filter)((currentlyDetected) => currentlyDetected), (0, operators_1.mapTo)(null), // no value needed, event is what matters
|
|
93
83
|
(0, operators_1.share)());
|
|
84
|
+
this.expiredDingIds = [];
|
|
85
|
+
this.lastSnapshotTimestamp = 0;
|
|
86
|
+
this.lastSnapshotTimestampLocal = 0;
|
|
87
|
+
this.fetchingSnapshot = false;
|
|
88
|
+
this.id = this.initialData.id;
|
|
89
|
+
this.deviceType = this.initialData.kind;
|
|
90
|
+
this.model = ring_types_1.RingCameraModel[this.initialData.kind] || 'Unknown Model';
|
|
91
|
+
this.onData = new rxjs_1.BehaviorSubject(this.initialData);
|
|
92
|
+
this.hasLight = this.initialData.led_status !== undefined;
|
|
93
|
+
this.hasSiren = this.initialData.siren_status !== undefined;
|
|
94
94
|
this.onBatteryLevel = this.onData.pipe((0, operators_1.map)(getBatteryLevel), (0, operators_1.distinctUntilChanged)());
|
|
95
95
|
this.onInHomeDoorbellStatus = this.onData.pipe((0, operators_1.map)(({ settings: { chime_settings } }) => {
|
|
96
96
|
return Boolean(chime_settings === null || chime_settings === void 0 ? void 0 : chime_settings.enable);
|
|
97
97
|
}), (0, operators_1.distinctUntilChanged)());
|
|
98
|
-
this.expiredDingIds = [];
|
|
99
|
-
this.lastSnapshotTimestampLocal = 0;
|
|
100
98
|
if (!initialData.subscribed) {
|
|
101
99
|
this.subscribeToDingEvents().catch((e) => {
|
|
102
100
|
(0, util_1.logError)('Failed to subscribe ' + initialData.description + ' to ding events');
|
|
@@ -128,6 +126,15 @@ class RingCamera extends subscribed_1.Subscribed {
|
|
|
128
126
|
get batteryLevel() {
|
|
129
127
|
return getBatteryLevel(this.data);
|
|
130
128
|
}
|
|
129
|
+
get hasBattery() {
|
|
130
|
+
if (this.batteryLevel === null) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return ((0, ring_types_1.isBatteryCameraKind)(this.deviceType) ||
|
|
134
|
+
(typeof this.initialData.battery_life === 'string' &&
|
|
135
|
+
this.batteryLevel < 100 &&
|
|
136
|
+
this.batteryLevel >= 0));
|
|
137
|
+
}
|
|
131
138
|
get hasLowBattery() {
|
|
132
139
|
return this.data.alerts.battery === 'low';
|
|
133
140
|
}
|
|
@@ -309,34 +316,14 @@ class RingCamera extends subscribed_1.Subscribed {
|
|
|
309
316
|
get snapshotsAreBlocked() {
|
|
310
317
|
return this.data.settings.motion_detection_enabled === false;
|
|
311
318
|
}
|
|
312
|
-
getSnapshotTimestamp() {
|
|
313
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
314
|
-
const { timestamps, responseTimestamp } = yield this.restClient.request({
|
|
315
|
-
url: (0, rest_client_1.clientApi)('snapshots/timestamps'),
|
|
316
|
-
method: 'POST',
|
|
317
|
-
json: {
|
|
318
|
-
doorbot_ids: [this.id],
|
|
319
|
-
},
|
|
320
|
-
}), deviceTimestamp = timestamps[0], timestamp = deviceTimestamp ? deviceTimestamp.timestamp : 0, timestampAge = Math.abs(responseTimestamp - timestamp);
|
|
321
|
-
this.lastSnapshotTimestampLocal = timestamp ? Date.now() - timestampAge : 0;
|
|
322
|
-
return {
|
|
323
|
-
timestamp,
|
|
324
|
-
inLifeTime: this.isTimestampInLifeTime(timestampAge),
|
|
325
|
-
};
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
319
|
get snapshotLifeTime() {
|
|
329
320
|
return this.avoidSnapshotBatteryDrain && this.operatingOnBattery
|
|
330
321
|
? 600 * 1000 // battery cams only refresh timestamp every 10 minutes
|
|
331
|
-
: 10 * 1000; // snapshot updates will be forced. Limit to
|
|
322
|
+
: 10 * 1000; // snapshot updates will be forced. Limit to 10s lifetime
|
|
332
323
|
}
|
|
333
324
|
get currentTimestampAge() {
|
|
334
325
|
return Date.now() - this.lastSnapshotTimestampLocal;
|
|
335
326
|
}
|
|
336
|
-
get currentTimestampExpiresIn() {
|
|
337
|
-
// Gets 0 if stale snapshot is used because snapshot timestamp refused to update (recording in progress on battery cam)
|
|
338
|
-
return Math.max(this.lastSnapshotTimestampLocal - Date.now() + this.snapshotLifeTime, 0);
|
|
339
|
-
}
|
|
340
327
|
get hasSnapshotWithinLifetime() {
|
|
341
328
|
return this.isTimestampInLifeTime(this.currentTimestampAge);
|
|
342
329
|
}
|
|
@@ -344,79 +331,73 @@ class RingCamera extends subscribed_1.Subscribed {
|
|
|
344
331
|
if (this.snapshotsAreBlocked) {
|
|
345
332
|
throw new Error(`Motion detection is disabled for ${this.name}, which prevents snapshots from this camera. This can be caused by Modes settings or by turning off the Record Motion setting.`);
|
|
346
333
|
}
|
|
334
|
+
if (this.isOffline) {
|
|
335
|
+
throw new Error(`Cannot fetch snapshot for ${this.name} because it is offline`);
|
|
336
|
+
}
|
|
347
337
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
(0, util_1.logDebug)(`Snapshot for ${this.name} is still within its life time (${this.currentTimestampAge / 1000}s old)`);
|
|
362
|
-
return true;
|
|
363
|
-
}
|
|
364
|
-
this.checkIfSnapshotsAreBlocked();
|
|
365
|
-
if (!this.avoidSnapshotBatteryDrain || !this.operatingOnBattery) {
|
|
366
|
-
// tell the camera to update snapshot immediately.
|
|
367
|
-
// avoidSnapshotBatteryDrain is best if you have a battery cam that you request snapshots for frequently. This can lead to battery drain if snapshot updates are forced.
|
|
368
|
-
yield this.requestSnapshotUpdate();
|
|
369
|
-
}
|
|
370
|
-
for (let i = 0; i < maxSnapshotRefreshAttempts; i++) {
|
|
371
|
-
this.checkIfSnapshotsAreBlocked();
|
|
372
|
-
const { timestamp, inLifeTime } = yield this.getSnapshotTimestamp();
|
|
373
|
-
if (!timestamp && this.isOffline) {
|
|
374
|
-
throw new Error(`No snapshot available and device ${this.name} is offline`);
|
|
375
|
-
}
|
|
376
|
-
if (inLifeTime) {
|
|
377
|
-
return false;
|
|
378
|
-
}
|
|
379
|
-
yield (0, util_1.delay)(snapshotRefreshDelay);
|
|
380
|
-
}
|
|
381
|
-
const extraMessageForBatteryCam = this.operatingOnBattery
|
|
382
|
-
? '. This is normal behavior since this camera is unable to capture snapshots while streaming'
|
|
383
|
-
: '';
|
|
384
|
-
throw new Error(`Snapshot for ${this.name} (${this.deviceType} - ${this.model}) failed to refresh after ${maxSnapshotRefreshAttempts} attempts${extraMessageForBatteryCam}`);
|
|
385
|
-
});
|
|
338
|
+
shouldUseExistingSnapshotPromise() {
|
|
339
|
+
if (this.fetchingSnapshot) {
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
if (this.hasSnapshotWithinLifetime) {
|
|
343
|
+
(0, util_1.logDebug)(`Snapshot for ${this.name} is still within its life time (${this.currentTimestampAge / 1000}s old)`);
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
if (!this.avoidSnapshotBatteryDrain || !this.operatingOnBattery) {
|
|
347
|
+
// tell the camera to update snapshot immediately.
|
|
348
|
+
// avoidSnapshotBatteryDrain is best if you have a battery cam that you request snapshots for frequently. This can lead to battery drain if snapshot updates are forced.
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
386
351
|
}
|
|
387
352
|
getSnapshot() {
|
|
388
353
|
return __awaiter(this, void 0, void 0, function* () {
|
|
389
|
-
this.
|
|
390
|
-
this.
|
|
391
|
-
this.refreshSnapshot().catch((e) => {
|
|
392
|
-
(0, util_1.logError)(e.message);
|
|
393
|
-
throw e;
|
|
394
|
-
});
|
|
395
|
-
try {
|
|
396
|
-
const useLastSnapshot = yield this.refreshSnapshotInProgress;
|
|
397
|
-
this.refreshSnapshotInProgress = undefined;
|
|
398
|
-
if (useLastSnapshot && this.lastSnapshotPromise) {
|
|
399
|
-
return this.lastSnapshotPromise;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
catch (e) {
|
|
403
|
-
this.refreshSnapshotInProgress = undefined;
|
|
404
|
-
throw e;
|
|
354
|
+
if (this.lastSnapshotPromise && this.shouldUseExistingSnapshotPromise()) {
|
|
355
|
+
return this.lastSnapshotPromise;
|
|
405
356
|
}
|
|
406
|
-
this.
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
357
|
+
this.checkIfSnapshotsAreBlocked();
|
|
358
|
+
this.lastSnapshotPromise = Promise.race([
|
|
359
|
+
this.getNextSnapshot({
|
|
360
|
+
afterMs: this.lastSnapshotTimestamp,
|
|
361
|
+
force: true,
|
|
362
|
+
}),
|
|
363
|
+
(0, util_1.delay)(maxSnapshotRefreshSeconds * 1000).then(() => {
|
|
364
|
+
const extraMessageForBatteryCam = this.operatingOnBattery
|
|
365
|
+
? '. This is normal behavior since this camera is unable to capture snapshots while streaming'
|
|
366
|
+
: '';
|
|
367
|
+
throw new Error(`Snapshot for ${this.name} (${this.deviceType} - ${this.model}) failed to refresh after ${maxSnapshotRefreshSeconds} seconds${extraMessageForBatteryCam}`);
|
|
368
|
+
}),
|
|
369
|
+
]);
|
|
410
370
|
try {
|
|
411
371
|
yield this.lastSnapshotPromise;
|
|
412
372
|
}
|
|
413
|
-
catch (
|
|
373
|
+
catch (e) {
|
|
414
374
|
// snapshot request failed, don't use it again
|
|
415
375
|
this.lastSnapshotPromise = undefined;
|
|
376
|
+
throw e;
|
|
416
377
|
}
|
|
378
|
+
this.fetchingSnapshot = false;
|
|
417
379
|
return this.lastSnapshotPromise;
|
|
418
380
|
});
|
|
419
381
|
}
|
|
382
|
+
getNextSnapshot({ afterMs, maxWaitMs, force, }) {
|
|
383
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
384
|
+
const response = yield this.restClient.request({
|
|
385
|
+
url: `https://app-snaps.ring.com/snapshots/next/${this.id}?extras=force`,
|
|
386
|
+
responseType: 'buffer',
|
|
387
|
+
searchParams: {
|
|
388
|
+
'after-ms': afterMs,
|
|
389
|
+
'max-wait-ms': maxWaitMs,
|
|
390
|
+
extras: force ? 'force' : undefined,
|
|
391
|
+
},
|
|
392
|
+
headers: {
|
|
393
|
+
accept: 'image/jpeg',
|
|
394
|
+
},
|
|
395
|
+
}), { responseTimestamp, timeMillis } = response, timestampAge = Math.abs(responseTimestamp - timeMillis);
|
|
396
|
+
this.lastSnapshotTimestamp = timeMillis;
|
|
397
|
+
this.lastSnapshotTimestampLocal = Date.now() - timestampAge;
|
|
398
|
+
return response;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
420
401
|
getSipOptions() {
|
|
421
402
|
return __awaiter(this, void 0, void 0, function* () {
|
|
422
403
|
const activeDings = this.onActiveDings.getValue(), existingDing = activeDings
|
package/lib/api/ring-chime.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare class RingChime {
|
|
|
5
5
|
private initialData;
|
|
6
6
|
private restClient;
|
|
7
7
|
id: number;
|
|
8
|
-
deviceType: "
|
|
8
|
+
deviceType: import("./ring-types").ChimeKind;
|
|
9
9
|
model: string;
|
|
10
10
|
onData: BehaviorSubject<ChimeData>;
|
|
11
11
|
onRequestUpdate: Subject<unknown>;
|
package/lib/api/ring-chime.js
CHANGED
|
@@ -23,11 +23,11 @@ class RingChime {
|
|
|
23
23
|
constructor(initialData, restClient) {
|
|
24
24
|
this.initialData = initialData;
|
|
25
25
|
this.restClient = restClient;
|
|
26
|
+
this.onRequestUpdate = new rxjs_1.Subject();
|
|
26
27
|
this.id = this.initialData.id;
|
|
27
28
|
this.deviceType = this.initialData.kind;
|
|
28
29
|
this.model = ring_types_1.ChimeModel[this.deviceType] || 'Chime';
|
|
29
30
|
this.onData = new rxjs_1.BehaviorSubject(this.initialData);
|
|
30
|
-
this.onRequestUpdate = new rxjs_1.Subject();
|
|
31
31
|
}
|
|
32
32
|
updateData(update) {
|
|
33
33
|
this.onData.next(update);
|
package/lib/api/ring-types.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare enum RingDeviceType {
|
|
|
10
10
|
TemperatureSensor = "sensor.temperature",
|
|
11
11
|
WaterSensor = "sensor.water",
|
|
12
12
|
TiltSensor = "sensor.tilt",
|
|
13
|
+
GlassbreakSensor = "sensor.glassbreak",
|
|
13
14
|
RangeExtender = "range-extender.zwave",
|
|
14
15
|
ZigbeeAdapter = "adapter.zigbee",
|
|
15
16
|
AccessCodeVault = "access-code.vault",
|
|
@@ -67,6 +68,7 @@ export declare enum RingCameraKind {
|
|
|
67
68
|
doorbell_portal = "doorbell_portal",
|
|
68
69
|
doorbell_scallop = "doorbell_scallop",
|
|
69
70
|
doorbell_scallop_lite = "doorbell_scallop_lite",
|
|
71
|
+
doorbell_graham_cracker = "doorbell_graham_cracker",
|
|
70
72
|
lpd_v1 = "lpd_v1",
|
|
71
73
|
lpd_v2 = "lpd_v2",
|
|
72
74
|
jbox_v1 = "jbox_v1",
|
|
@@ -214,7 +216,7 @@ export interface BeamBridge {
|
|
|
214
216
|
role: string;
|
|
215
217
|
updated_at: string;
|
|
216
218
|
}
|
|
217
|
-
declare type ChimeKind = 'chime' | 'chime_pro' | 'chime_v2' | 'chime_pro_v2';
|
|
219
|
+
export declare type ChimeKind = 'chime' | 'chime_pro' | 'chime_v2' | 'chime_pro_v2';
|
|
218
220
|
export declare const ChimeModel: {
|
|
219
221
|
readonly [P in ChimeKind]: string;
|
|
220
222
|
};
|
|
@@ -686,10 +688,6 @@ export interface ActiveDing {
|
|
|
686
688
|
sip_token: string;
|
|
687
689
|
sip_ding_id: string;
|
|
688
690
|
}
|
|
689
|
-
export interface SnapshotTimestamp {
|
|
690
|
-
timestamp: number;
|
|
691
|
-
doorbot_id: number;
|
|
692
|
-
}
|
|
693
691
|
export interface AuthTokenResponse {
|
|
694
692
|
access_token: string;
|
|
695
693
|
expires_in: number;
|
package/lib/api/ring-types.js
CHANGED
|
@@ -14,6 +14,7 @@ var RingDeviceType;
|
|
|
14
14
|
RingDeviceType["TemperatureSensor"] = "sensor.temperature";
|
|
15
15
|
RingDeviceType["WaterSensor"] = "sensor.water";
|
|
16
16
|
RingDeviceType["TiltSensor"] = "sensor.tilt";
|
|
17
|
+
RingDeviceType["GlassbreakSensor"] = "sensor.glassbreak";
|
|
17
18
|
RingDeviceType["RangeExtender"] = "range-extender.zwave";
|
|
18
19
|
RingDeviceType["ZigbeeAdapter"] = "adapter.zigbee";
|
|
19
20
|
RingDeviceType["AccessCodeVault"] = "access-code.vault";
|
|
@@ -75,6 +76,7 @@ var RingCameraKind;
|
|
|
75
76
|
RingCameraKind["doorbell_portal"] = "doorbell_portal";
|
|
76
77
|
RingCameraKind["doorbell_scallop"] = "doorbell_scallop";
|
|
77
78
|
RingCameraKind["doorbell_scallop_lite"] = "doorbell_scallop_lite";
|
|
79
|
+
RingCameraKind["doorbell_graham_cracker"] = "doorbell_graham_cracker";
|
|
78
80
|
RingCameraKind["lpd_v1"] = "lpd_v1";
|
|
79
81
|
RingCameraKind["lpd_v2"] = "lpd_v2";
|
|
80
82
|
RingCameraKind["jbox_v1"] = "jbox_v1";
|
|
@@ -93,7 +95,7 @@ var RingCameraKind;
|
|
|
93
95
|
RingCameraKind["stickup_cam_mini"] = "stickup_cam_mini";
|
|
94
96
|
})(RingCameraKind = exports.RingCameraKind || (exports.RingCameraKind = {}));
|
|
95
97
|
// RegExp taken from ring.com app
|
|
96
|
-
const isWiredCameraRegExp = new RegExp(/(^(lpd|jbox|stickup_cam_elite|stickup_cam_mini|hp_cam|spotlightw|floodlight))/);
|
|
98
|
+
const isWiredCameraRegExp = new RegExp(/(^(lpd|jbox|stickup_cam_elite|stickup_cam_mini|hp_cam|spotlightw|floodlight|doorbell_graham_cracker))/);
|
|
97
99
|
function isBatteryCameraKind(kind) {
|
|
98
100
|
return !kind.match(isWiredCameraRegExp);
|
|
99
101
|
}
|
|
@@ -107,6 +109,7 @@ exports.RingCameraModel = {
|
|
|
107
109
|
doorbell_portal: 'Door View Cam',
|
|
108
110
|
doorbell_scallop: 'Doorbell 3 Plus',
|
|
109
111
|
doorbell_scallop_lite: 'Doorbell 3',
|
|
112
|
+
doorbell_graham_cracker: 'Doorbell Wired',
|
|
110
113
|
lpd_v1: 'Doorbell Pro',
|
|
111
114
|
lpd_v2: 'Doorbell Pro',
|
|
112
115
|
jbox_v1: 'Doorbell Elite',
|
package/lib/api/sip-session.d.ts
CHANGED
|
@@ -27,6 +27,10 @@ export declare class SipSession extends Subscribed {
|
|
|
27
27
|
constructor(sipOptions: SipOptions, rtpOptions: RtpOptions, audioSplitter: RtpSplitter, audioRtcpSplitter: RtpSplitter, videoSplitter: RtpSplitter, videoRtcpSplitter: RtpSplitter, tlsPort: number, camera: RingCamera);
|
|
28
28
|
createSipCall(sipOptions: SipOptions): SipCall;
|
|
29
29
|
start(ffmpegOptions?: FfmpegOptions): Promise<RtpDescription>;
|
|
30
|
+
prepareTranscoder(transcodeVideoStream: boolean, ffmpegInputOptions: SpawnInput[] | undefined, remoteRtpOptions: RtpOptions, audioPort: number, videoPort: number, sdpInput: string): {
|
|
31
|
+
ffmpegInputArguments: SpawnInput[];
|
|
32
|
+
inputSdpLines: string[];
|
|
33
|
+
};
|
|
30
34
|
private startTranscoder;
|
|
31
35
|
reservePort(bufferPorts?: number): Promise<number>;
|
|
32
36
|
requestKeyFrame(): Promise<void>;
|
package/lib/api/sip-session.js
CHANGED
|
@@ -32,8 +32,8 @@ class SipSession extends subscribed_1.Subscribed {
|
|
|
32
32
|
this.hasStarted = false;
|
|
33
33
|
this.hasCallEnded = false;
|
|
34
34
|
this.onCallEndedSubject = new rxjs_1.ReplaySubject(1);
|
|
35
|
-
this.sipCall = this.createSipCall(this.sipOptions);
|
|
36
35
|
this.onCallEnded = this.onCallEndedSubject.asObservable();
|
|
36
|
+
this.sipCall = this.createSipCall(this.sipOptions);
|
|
37
37
|
}
|
|
38
38
|
createSipCall(sipOptions) {
|
|
39
39
|
if (this.sipCall) {
|
|
@@ -114,31 +114,17 @@ class SipSession extends subscribed_1.Subscribed {
|
|
|
114
114
|
}
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
|
-
|
|
118
|
-
const
|
|
117
|
+
prepareTranscoder(transcodeVideoStream, ffmpegInputOptions, remoteRtpOptions, audioPort, videoPort, sdpInput) {
|
|
118
|
+
const ffmpegInputArguments = [
|
|
119
119
|
'-hide_banner',
|
|
120
120
|
'-protocol_whitelist',
|
|
121
121
|
'pipe,udp,rtp,file,crypto',
|
|
122
122
|
'-f',
|
|
123
123
|
'sdp',
|
|
124
|
-
...(
|
|
124
|
+
...(ffmpegInputOptions || []),
|
|
125
125
|
'-i',
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
...(transcodeVideoStream
|
|
129
|
-
? ffmpegOptions.video || ['-vcodec', 'copy']
|
|
130
|
-
: []),
|
|
131
|
-
...(ffmpegOptions.output || []),
|
|
132
|
-
], ff = new camera_utils_1.FfmpegProcess({
|
|
133
|
-
ffmpegArgs,
|
|
134
|
-
ffmpegPath: (0, ffmpeg_1.getFfmpegPath)(),
|
|
135
|
-
exitCallback: () => this.callEnded(true),
|
|
136
|
-
logLabel: `From Ring (${this.camera.name})`,
|
|
137
|
-
logger: {
|
|
138
|
-
error: util_1.logError,
|
|
139
|
-
info: util_1.logDebug,
|
|
140
|
-
},
|
|
141
|
-
}), inputSdpLines = [
|
|
126
|
+
sdpInput,
|
|
127
|
+
], inputSdpLines = [
|
|
142
128
|
'v=0',
|
|
143
129
|
'o=105202070 3747 461 IN IP4 127.0.0.1',
|
|
144
130
|
's=Talk',
|
|
@@ -167,8 +153,6 @@ class SipSession extends subscribed_1.Subscribed {
|
|
|
167
153
|
};
|
|
168
154
|
});
|
|
169
155
|
}
|
|
170
|
-
this.onCallEnded.subscribe(() => ff.stop());
|
|
171
|
-
ff.writeStdin(inputSdpLines.filter((x) => Boolean(x)).join('\n'));
|
|
172
156
|
this.audioSplitter.addMessageHandler(({ isRtpMessage, message }) => {
|
|
173
157
|
if ((0, rtp_utils_1.isStunMessage)(message)) {
|
|
174
158
|
return null;
|
|
@@ -177,6 +161,26 @@ class SipSession extends subscribed_1.Subscribed {
|
|
|
177
161
|
port: isRtpMessage ? audioPort : audioPort + 1,
|
|
178
162
|
};
|
|
179
163
|
});
|
|
164
|
+
return {
|
|
165
|
+
ffmpegInputArguments,
|
|
166
|
+
inputSdpLines,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
startTranscoder(ffmpegOptions, remoteRtpOptions, audioPort, videoPort) {
|
|
170
|
+
const transcodeVideoStream = ffmpegOptions.video !== false, { ffmpegInputArguments, inputSdpLines } = this.prepareTranscoder(transcodeVideoStream, ffmpegOptions.input, remoteRtpOptions, audioPort, videoPort, 'pipe:'), ff = new camera_utils_1.FfmpegProcess({
|
|
171
|
+
ffmpegArgs: ffmpegInputArguments.concat(...(ffmpegOptions.audio || ['-acodec', 'aac']), ...(transcodeVideoStream
|
|
172
|
+
? ffmpegOptions.video || ['-vcodec', 'copy']
|
|
173
|
+
: []), ...(ffmpegOptions.output || [])),
|
|
174
|
+
ffmpegPath: (0, ffmpeg_1.getFfmpegPath)(),
|
|
175
|
+
exitCallback: () => this.callEnded(true),
|
|
176
|
+
logLabel: `From Ring (${this.camera.name})`,
|
|
177
|
+
logger: {
|
|
178
|
+
error: util_1.logError,
|
|
179
|
+
info: util_1.logDebug,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
this.onCallEnded.subscribe(() => ff.stop());
|
|
183
|
+
ff.writeStdin(inputSdpLines.filter((x) => Boolean(x)).join('\n'));
|
|
180
184
|
}
|
|
181
185
|
reservePort(bufferPorts = 0) {
|
|
182
186
|
return __awaiter(this, void 0, void 0, function* () {
|
package/lib/api/sip.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export function create(options: any, callback: any): {
|
|
|
19
19
|
isFlowUri: (uri: any) => boolean;
|
|
20
20
|
hostname: () => any;
|
|
21
21
|
destroy: () => void;
|
|
22
|
+
makeResponse: typeof makeResponse;
|
|
22
23
|
};
|
|
23
24
|
export function start(options: any, callback: any): void;
|
|
24
25
|
export function parseAOR(data: any): any;
|
package/lib/api/sip.js
CHANGED
package/lib/api/util.js
CHANGED
|
@@ -8,14 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
11
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
15
|
exports.randomString = exports.randomInteger = exports.mapAsync = exports.stringify = exports.requestInput = exports.getHardwareId = exports.generateUuid = exports.enableDebug = exports.useLogger = exports.logError = exports.logInfo = exports.logDebug = exports.delay = void 0;
|
|
13
|
-
const
|
|
16
|
+
const debug_1 = __importDefault(require("debug"));
|
|
14
17
|
const colors_1 = require("colors");
|
|
15
18
|
const readline_1 = require("readline");
|
|
16
19
|
const uuid_1 = require("uuid");
|
|
17
20
|
const systeminformation_1 = require("systeminformation");
|
|
18
|
-
const debugLogger =
|
|
21
|
+
const debugLogger = (0, debug_1.default)('ring'), uuidNamespace = 'e53ffdc0-e91d-4ce1-bec2-df939d94739c';
|
|
19
22
|
let logger = {
|
|
20
23
|
logInfo(message) {
|
|
21
24
|
debugLogger(message);
|
|
@@ -99,6 +99,7 @@ class CameraSource {
|
|
|
99
99
|
(0, util_1.logDebug)(`Snapshot cached for ${this.ringCamera.name} (${getDurationSeconds(start)}s)`);
|
|
100
100
|
}
|
|
101
101
|
catch (e) {
|
|
102
|
+
this.cachedSnapshot = undefined;
|
|
102
103
|
(0, util_1.logDebug)(`Failed to cache snapshot for ${this.ringCamera.name} (${getDurationSeconds(start)}s), The camera currently reports that it is ${this.ringCamera.isOffline ? 'offline' : 'online'}`);
|
|
103
104
|
}
|
|
104
105
|
});
|
|
@@ -268,13 +269,13 @@ class CameraSource {
|
|
|
268
269
|
});
|
|
269
270
|
let returnAudioPort = null;
|
|
270
271
|
if (libfdkAacInstalled) {
|
|
271
|
-
let
|
|
272
|
+
let cameraSpeakerActive = false;
|
|
272
273
|
const ringAudioLocation = {
|
|
273
274
|
address: ringRtpDescription.address,
|
|
274
275
|
port: ringRtpDescription.audio.port,
|
|
275
276
|
}, returnAudioTranscodedSplitter = new camera_utils_1.RtpSplitter((description) => {
|
|
276
|
-
if (!
|
|
277
|
-
|
|
277
|
+
if (!cameraSpeakerActive) {
|
|
278
|
+
cameraSpeakerActive = true;
|
|
278
279
|
sipSession.activateCameraSpeaker().catch(util_1.logError);
|
|
279
280
|
}
|
|
280
281
|
sipSession.audioSplitter
|
package/lib/homebridge/camera.js
CHANGED
|
@@ -89,7 +89,7 @@ class Camera extends base_data_accessory_1.BaseDataAccessory {
|
|
|
89
89
|
})),
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
-
if (device.hasLight) {
|
|
92
|
+
if (device.hasLight && !config.hideCameraLight) {
|
|
93
93
|
const lightTargetTimer = new target_value_timer_1.TargetValueTimer();
|
|
94
94
|
this.registerCharacteristic({
|
|
95
95
|
characteristicType: Characteristic.On,
|
|
@@ -5,6 +5,7 @@ export interface RingPlatformConfig extends RingApiOptions {
|
|
|
5
5
|
beamDurationSeconds?: number;
|
|
6
6
|
hideLightGroups?: boolean;
|
|
7
7
|
hideDoorbellSwitch?: boolean;
|
|
8
|
+
hideCameraLight?: boolean;
|
|
8
9
|
hideCameraMotionSensor?: boolean;
|
|
9
10
|
sendCameraMotionNotificationsToTv?: boolean;
|
|
10
11
|
sendDoorbellMotionNotificationsToTv?: boolean;
|
|
@@ -61,6 +61,7 @@ function getAccessoryClass(device) {
|
|
|
61
61
|
case api_1.RingDeviceType.ContactSensor:
|
|
62
62
|
case api_1.RingDeviceType.RetrofitZone:
|
|
63
63
|
case api_1.RingDeviceType.TiltSensor:
|
|
64
|
+
case api_1.RingDeviceType.GlassbreakSensor:
|
|
64
65
|
return contact_sensor_1.ContactSensor;
|
|
65
66
|
case api_1.RingDeviceType.MotionSensor:
|
|
66
67
|
return motion_sensor_1.MotionSensor;
|
|
@@ -143,10 +144,15 @@ class RingPlatform {
|
|
|
143
144
|
config.locationModePollingSeconds = (_c = config.locationModePollingSeconds) !== null && _c !== void 0 ? _c : 20;
|
|
144
145
|
this.api.on('didFinishLaunching', () => {
|
|
145
146
|
this.log.debug('didFinishLaunching');
|
|
146
|
-
|
|
147
|
-
this.
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
if (config.refreshToken) {
|
|
148
|
+
this.connectToApi().catch((e) => {
|
|
149
|
+
this.log.error('Error connecting to API');
|
|
150
|
+
this.log.error(e);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
this.log.warn('Plugin is not configured. Visit https://github.com/dgreif/ring/tree/master/homebridge#homebridge-configuration for more information.');
|
|
155
|
+
}
|
|
150
156
|
});
|
|
151
157
|
this.homebridgeAccessories = {};
|
|
152
158
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-ring",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.25.0-beta.0",
|
|
4
4
|
"description": "Homebridge plugin for Ring doorbells, cameras, security alarm system and smart lighting",
|
|
5
5
|
"main": "lib/homebridge/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -43,38 +43,38 @@
|
|
|
43
43
|
"@homebridge/plugin-ui-utils": "0.0.19",
|
|
44
44
|
"@types/socket.io-client": "1.4.36",
|
|
45
45
|
"colors": "1.4.0",
|
|
46
|
-
"debug": "4.3.
|
|
47
|
-
"got": "11.8.
|
|
48
|
-
"rxjs": "7.
|
|
46
|
+
"debug": "4.3.3",
|
|
47
|
+
"got": "11.8.3",
|
|
48
|
+
"rxjs": "7.5.2",
|
|
49
49
|
"sdp": "3.0.3",
|
|
50
50
|
"socket.io-client": "2.4.0",
|
|
51
51
|
"stun": "2.1.0",
|
|
52
|
-
"systeminformation": "5.
|
|
52
|
+
"systeminformation": "5.10.7",
|
|
53
53
|
"uuid": "8.3.2"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/debug": "4.1.7",
|
|
57
|
-
"@types/jest": "27.0
|
|
58
|
-
"@types/node": "
|
|
59
|
-
"@types/uuid": "8.3.
|
|
60
|
-
"@typescript-eslint/eslint-plugin": "
|
|
61
|
-
"@typescript-eslint/parser": "
|
|
62
|
-
"concurrently": "^
|
|
57
|
+
"@types/jest": "27.4.0",
|
|
58
|
+
"@types/node": "17.0.10",
|
|
59
|
+
"@types/uuid": "8.3.4",
|
|
60
|
+
"@typescript-eslint/eslint-plugin": "5.10.0",
|
|
61
|
+
"@typescript-eslint/parser": "5.10.0",
|
|
62
|
+
"concurrently": "^7.0.0",
|
|
63
63
|
"conventional-github-releaser": "3.1.5",
|
|
64
|
-
"dotenv": "
|
|
65
|
-
"eslint": "7.
|
|
64
|
+
"dotenv": "14.2.0",
|
|
65
|
+
"eslint": "8.7.0",
|
|
66
66
|
"eslint-config-prettier": "8.3.0",
|
|
67
|
-
"eslint-plugin-jest": "
|
|
67
|
+
"eslint-plugin-jest": "25.7.0",
|
|
68
68
|
"eslint-plugin-prettier": "4.0.0",
|
|
69
|
-
"express": "4.17.
|
|
70
|
-
"homebridge": "1.
|
|
71
|
-
"jest": "27.
|
|
72
|
-
"prettier": "2.
|
|
69
|
+
"express": "4.17.2",
|
|
70
|
+
"homebridge": "1.4.0",
|
|
71
|
+
"jest": "27.4.7",
|
|
72
|
+
"prettier": "2.5.1",
|
|
73
73
|
"reflect-metadata": "0.1.13",
|
|
74
74
|
"rimraf": "3.0.2",
|
|
75
|
-
"standard-version": "9.3.
|
|
76
|
-
"ts-jest": "27.
|
|
77
|
-
"typescript": "4.
|
|
75
|
+
"standard-version": "9.3.2",
|
|
76
|
+
"ts-jest": "27.1.3",
|
|
77
|
+
"typescript": "4.5.5"
|
|
78
78
|
},
|
|
79
79
|
"engines": {
|
|
80
80
|
"node": ">=8.3.0",
|