From 99d1e51f36edf678ca446067ecc5ee5bdacb267f Mon Sep 17 00:00:00 2001 From: Alex Leeds Date: Mon, 27 Feb 2023 13:52:50 -0500 Subject: [PATCH] ring: add support for various sensor types (#591) * ring: add support for various sensor types * ring: bump version --- plugins/ring/package-lock.json | 96 ++++++++++++++++++++++++++++------ plugins/ring/package.json | 12 ++--- plugins/ring/src/main.ts | 54 +++++++++++++++---- 3 files changed, 129 insertions(+), 33 deletions(-) diff --git a/plugins/ring/package-lock.json b/plugins/ring/package-lock.json index 7d499b2de..c6445915b 100644 --- a/plugins/ring/package-lock.json +++ b/plugins/ring/package-lock.json @@ -1,24 +1,23 @@ { "name": "@scrypted/ring", - "version": "0.0.94", + "version": "0.0.96", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@scrypted/ring", - "version": "0.0.94", - "hasInstallScript": true, + "version": "0.0.96", "dependencies": { "@koush/ring-client-api": "file:../../external/ring-client-api", "@scrypted/common": "file:../../common", "@scrypted/sdk": "file:../../sdk", - "@types/node": "^16.6.1", - "axios": "^0.24.0", - "rxjs": "^7.5.5" + "@types/node": "^16.9.0", + "axios": "^1.3.4", + "rxjs": "^7.8.0" }, "optionalDependencies": { - "got": "11.8.2", - "socket.io-client": "^2.4.0" + "got": "11.8.6", + "socket.io-client": "^2.5.0" } }, "../../common": { @@ -107,7 +106,7 @@ }, "../../sdk": { "name": "@scrypted/sdk", - "version": "0.2.69", + "version": "0.2.70", "license": "ISC", "dependencies": { "@babel/preset-typescript": "^7.18.6", @@ -231,12 +230,19 @@ "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "optional": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/axios": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", - "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", "dependencies": { - "follow-redirects": "^1.14.4" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/backo2": { @@ -299,6 +305,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", @@ -362,6 +379,14 @@ "node": ">=10" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -422,6 +447,19 @@ } } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -438,9 +476,9 @@ } }, "node_modules/got": { - "version": "11.8.2", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", - "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", "optional": true, "dependencies": { "@sindresorhus/is": "^4.0.0", @@ -448,7 +486,7 @@ "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.1", + "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", @@ -532,6 +570,25 @@ "node": ">=8" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -589,6 +646,11 @@ "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", "optional": true }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", diff --git a/plugins/ring/package.json b/plugins/ring/package.json index b01819890..6551f43b9 100644 --- a/plugins/ring/package.json +++ b/plugins/ring/package.json @@ -36,13 +36,13 @@ "@koush/ring-client-api": "file:../../external/ring-client-api", "@scrypted/common": "file:../../common", "@scrypted/sdk": "file:../../sdk", - "@types/node": "^16.6.1", - "axios": "^0.24.0", - "rxjs": "^7.5.5" + "@types/node": "^16.9.0", + "axios": "^1.3.4", + "rxjs": "^7.8.0" }, "optionalDependencies": { - "got": "11.8.2", - "socket.io-client": "^2.4.0" + "got": "11.8.6", + "socket.io-client": "^2.5.0" }, - "version": "0.0.95" + "version": "0.0.96" } diff --git a/plugins/ring/src/main.ts b/plugins/ring/src/main.ts index 67d69b139..a57f2faa2 100644 --- a/plugins/ring/src/main.ts +++ b/plugins/ring/src/main.ts @@ -4,7 +4,7 @@ import { connectRTCSignalingClients } from '@scrypted/common/src/rtc-signaling'; import { RtspServer } from '@scrypted/common/src/rtsp-server'; import { addTrackControls, parseSdp, replacePorts } from '@scrypted/common/src/sdp-utils'; import { StorageSettings } from '@scrypted/sdk/storage-settings'; -import sdk, { BinarySensor, Camera, Device, DeviceProvider, EntrySensor, FFmpegInput, MediaObject, MediaStreamUrl, MotionSensor, OnOff, PictureOptions, RequestMediaStreamOptions, RequestPictureOptions, ResponseMediaStreamOptions, RTCAVSignalingSetup, RTCSessionControl, RTCSignalingChannel, RTCSignalingSendIceCandidate, RTCSignalingSession, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, SecuritySystem, SecuritySystemMode, Setting, Settings, SettingValue, VideoCamera } from '@scrypted/sdk'; +import sdk, { Battery, BinarySensor, Camera, Device, DeviceProvider, EntrySensor, FFmpegInput, FloodSensor, MediaObject, MediaStreamUrl, MotionSensor, OnOff, PictureOptions, RequestMediaStreamOptions, RequestPictureOptions, ResponseMediaStreamOptions, RTCAVSignalingSetup, RTCSessionControl, RTCSignalingChannel, RTCSignalingSendIceCandidate, RTCSignalingSession, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, SecuritySystem, SecuritySystemMode, Setting, Settings, SettingValue, TamperSensor, VideoCamera } from '@scrypted/sdk'; import child_process, { ChildProcess } from 'child_process'; import dgram from 'dgram'; import { RtcpReceiverInfo, RtcpRrPacket } from '../../../external/werift/packages/rtp/src/rtcp/rr'; @@ -661,9 +661,13 @@ class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvider, Cam } } -class RingContactSensor extends ScryptedDeviceBase implements EntrySensor { +class RingSensor extends ScryptedDeviceBase implements TamperSensor, Battery, EntrySensor, MotionSensor, FloodSensor { updateState(data: RingDeviceData) { - this.entryOpen = data.faulted + this.tampered = data.tamperStatus === 'tamper'; + this.batteryLevel = data.batteryLevel; + this.entryOpen = data.faulted; + this.motionDetected = data.faulted; + this.flooded = data.flood?.faulted || data.faulted; } } @@ -761,7 +765,7 @@ export class RingLocationDevice extends ScryptedDeviceBase implements DeviceProv async getDevice(nativeId: string) { if (!this.devices.has(nativeId)) { if (nativeId.endsWith('-sensor')) { - const sensor = new RingContactSensor(nativeId); + const sensor = new RingSensor(nativeId); this.devices.set(nativeId, sensor); } else { const camera = new RingCameraDevice(this.plugin, this, nativeId); @@ -1025,31 +1029,61 @@ class RingPlugin extends ScryptedDeviceBase implements DeviceProvider, Settings } const sensors = (await location.getDevices()).filter(x => { - return x.data.status !== 'disabled' && (x.data.deviceType === RingDeviceType.ContactSensor || x.data.deviceType === RingDeviceType.RetrofitZone) + const supportedSensors = [ + RingDeviceType.ContactSensor, + RingDeviceType.RetrofitZone, + RingDeviceType.TiltSensor, + RingDeviceType.MotionSensor, + RingDeviceType.FloodFreezeSensor, + RingDeviceType.WaterSensor, + ] + return x.data.status !== 'disabled' && (supportedSensors.includes(x.data.deviceType)) }); for (const sensor of sensors) { const nativeId = sensor.id.toString() + '-sensor'; + const data: RingDeviceData = sensor.data; + + const interfaces = [ScryptedInterface.TamperSensor]; + switch (data.deviceType){ + case RingDeviceType.ContactSensor: + case RingDeviceType.RetrofitZone: + case RingDeviceType.TiltSensor: + interfaces.push(ScryptedInterface.EntrySensor); + break; + case RingDeviceType.MotionSensor: + interfaces.push(ScryptedInterface.MotionSensor); + break; + case RingDeviceType.FloodFreezeSensor: + case RingDeviceType.WaterSensor: + interfaces.push(ScryptedInterface.FloodSensor); + break; + default: break; + } + + if (data.batteryStatus !== 'none') + interfaces.push(ScryptedInterface.Battery); + const device: Device = { info: { - model: sensor.data.deviceType, + model: data.deviceType, manufacturer: 'Ring', - serialNumber: sensor.data.serialNumber ?? 'Unknown' + serialNumber: data.serialNumber ?? 'Unknown' }, providerNativeId: location.id, nativeId: nativeId, name: sensor.name, type: ScryptedDeviceType.Sensor, - interfaces: [ScryptedInterface.EntrySensor], + interfaces, }; devices.push(device); const getScryptedDevice = async () => { const locationDevice = await this.getDevice(location.id); const scryptedDevice = await locationDevice?.getDevice(nativeId); - return scryptedDevice as RingContactSensor; + return scryptedDevice as RingSensor; } - sensor.onData.subscribe(async data => { + sensor.onData.subscribe(async (data: RingDeviceData) => { const scryptedDevice = await getScryptedDevice(); scryptedDevice?.updateState(data) });