From 31b6fd0503ca890724abb28fa315dca6d49f917a Mon Sep 17 00:00:00 2001 From: Matthew Lieder Date: Wed, 17 Nov 2021 21:56:16 -0600 Subject: [PATCH] synology-ss: add motion detected webhook --- plugins/synology-ss/package-lock.json | 31 +++++++++----- plugins/synology-ss/package.json | 2 +- plugins/synology-ss/src/main.ts | 61 +++++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/plugins/synology-ss/package-lock.json b/plugins/synology-ss/package-lock.json index f450e66ab..d21d6b429 100644 --- a/plugins/synology-ss/package-lock.json +++ b/plugins/synology-ss/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/synology-ss", - "version": "0.0.2", + "version": "0.0.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/synology-ss", - "version": "0.0.2", + "version": "0.0.3", "license": "Apache", "dependencies": { "axios": "^0.24.0" @@ -17,8 +17,7 @@ } }, "../../sdk": { - "name": "@scrypted/sdk", - "version": "0.0.93", + "version": "0.0.121", "dev": true, "license": "ISC", "dependencies": { @@ -38,7 +37,10 @@ "ncp": "^2.0.0", "raw-loader": "^4.0.2", "rimraf": "^3.0.2", + "stringify-object": "^3.3.0", + "tmp": "^0.2.1", "ts-loader": "^9.2.6", + "typedoc": "^0.22.8", "typescript-json-schema": "^0.50.1", "webpack": "^5.59.0" }, @@ -49,6 +51,10 @@ "scrypted-package-json": "bin/scrypted-package-json.js", "scrypted-readme": "bin/scrypted-readme.js", "scrypted-webpack": "bin/scrypted-webpack.js" + }, + "devDependencies": { + "@types/stringify-object": "^4.0.0", + "ts-node": "^10.4.0" } }, "node_modules/@scrypted/sdk": { @@ -56,9 +62,9 @@ "link": true }, "node_modules/@types/node": { - "version": "16.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", - "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==", + "version": "16.11.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz", + "integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==", "dev": true }, "node_modules/axios": { @@ -101,6 +107,7 @@ "@babel/plugin-transform-typescript": "^7.15.8", "@babel/preset-typescript": "^7.15.0", "@types/node": "^16.11.1", + "@types/stringify-object": "^4.0.0", "adm-zip": "^0.4.13", "axios": "^0.21.4", "babel-loader": "^8.2.3", @@ -109,15 +116,19 @@ "ncp": "^2.0.0", "raw-loader": "^4.0.2", "rimraf": "^3.0.2", + "stringify-object": "^3.3.0", + "tmp": "^0.2.1", "ts-loader": "^9.2.6", + "ts-node": "^10.4.0", + "typedoc": "^0.22.8", "typescript-json-schema": "^0.50.1", "webpack": "^5.59.0" } }, "@types/node": { - "version": "16.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", - "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==", + "version": "16.11.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz", + "integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==", "dev": true }, "axios": { diff --git a/plugins/synology-ss/package.json b/plugins/synology-ss/package.json index a4c2dca4b..6bb327c12 100644 --- a/plugins/synology-ss/package.json +++ b/plugins/synology-ss/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/synology-ss", - "version": "0.0.2", + "version": "0.0.3", "description": "A Synology Surveillance Station plugin for Scrypted", "author": "Scrypted", "license": "Apache", diff --git a/plugins/synology-ss/src/main.ts b/plugins/synology-ss/src/main.ts index 3c596b769..d1f97f1f1 100644 --- a/plugins/synology-ss/src/main.ts +++ b/plugins/synology-ss/src/main.ts @@ -1,11 +1,14 @@ -import sdk, { ScryptedDeviceBase, DeviceProvider, Settings, Setting, ScryptedDeviceType, VideoCamera, MediaObject, Device, ScryptedInterface, Camera, MediaStreamOptions, PictureOptions } from "@scrypted/sdk"; +import sdk, { ScryptedDeviceBase, DeviceProvider, HttpRequest, HttpRequestHandler, HttpResponse, Settings, Setting, ScryptedDeviceType, VideoCamera, MediaObject, Device, MotionSensor, ScryptedInterface, Camera, MediaStreamOptions, PictureOptions } from "@scrypted/sdk"; import { createInstanceableProviderPlugin, enableInstanceableProviderMode, isInstanceableProviderModeEnabled } from '../../../common/src/provider-plugin'; import { recommendRebroadcast } from "../../rtsp/src/recommend"; import {SynologyApiClient, SynologyCameraStream, SynologyCamera} from "./api/synology-api-client"; const { log, deviceManager, mediaManager } = sdk; -class SynologyCameraDevice extends ScryptedDeviceBase implements Camera, Settings, VideoCamera { +class SynologyCameraDevice extends ScryptedDeviceBase implements Camera, HttpRequestHandler, MotionSensor, Settings, VideoCamera { + private static readonly DefaultSensorTimeoutSecs: number = 30; + + private motionTimeout?: NodeJS.Timeout; private provider: SynologySurveillanceStation; private streams: SynologyCameraStream[]; @@ -13,6 +16,7 @@ class SynologyCameraDevice extends ScryptedDeviceBase implements Camera, Setting super(nativeId); this.provider = provider; + this.motionDetected = false; this.streams = SynologyCameraDevice.identifyStreams(camera); } @@ -38,6 +42,7 @@ class SynologyCameraDevice extends ScryptedDeviceBase implements Camera, Setting public async getSettings(): Promise { const vsos = await this.getVideoStreamOptions(); const defaultStream = this.getDefaultStream(vsos); + return [ { title: 'Default Stream', @@ -45,6 +50,20 @@ class SynologyCameraDevice extends ScryptedDeviceBase implements Camera, Setting value: defaultStream?.name, choices: vsos.map(vso => vso.name), description: 'The default stream to use when not specified', + }, + { + title: 'Motion Sensor Timeout', + key: 'sensorTimeout', + type: 'integer', + value: this.storage.getItem('sensorTimeout') || SynologyCameraDevice.DefaultSensorTimeoutSecs, + description: 'Time to wait in seconds before clearing the motion detected state.', + }, + { + title: 'Motion Sensor Webhook', + type: 'string', + readonly: true, + value: await this.getMotionDetectedWebhookUrl(), + description: 'To get motion alerts, create an alert rule in Surveillance Station that POSTs to this webhook URL upon motion detected.', } ]; } @@ -61,6 +80,17 @@ class SynologyCameraDevice extends ScryptedDeviceBase implements Camera, Setting this.onDeviceEvent(ScryptedInterface.Settings, undefined); } + getSensorTimeout() { + return (parseInt(this.storage.getItem('sensorTimeout')) || SynologyCameraDevice.DefaultSensorTimeoutSecs) * 1000; + } + + resetMotionTimeout() { + clearTimeout(this.motionTimeout); + this.motionTimeout = setTimeout(() => { + this.motionDetected = false; + }, this.getSensorTimeout()); + } + private async getSnapshot(options?: PictureOptions): Promise { const data = await this.provider.api.getCameraSnapshot(this.nativeId); @@ -83,6 +113,7 @@ class SynologyCameraDevice extends ScryptedDeviceBase implements Camera, Setting throw new Error(`Unable to locate RTSP stream for camera ${this.nativeId}`); return mediaManager.createFFmpegMediaObject({ + url: liveViewPaths[0].rtspPath, inputArguments: [ "-rtsp_transport", "tcp", @@ -127,6 +158,26 @@ class SynologyCameraDevice extends ScryptedDeviceBase implements Camera, Setting return; } + public async onRequest(request: HttpRequest, response: HttpResponse): Promise { + if (request.url.endsWith('/motionDetected')) { + this.motionDetected = true; + this.resetMotionTimeout(); + + response.send('Success', { + code: 200, + }); + } else { + response.send('Unsupported operation', { + code: 400, + }); + } + } + + private async getMotionDetectedWebhookUrl(): Promise { + const webhookUrl = await sdk.endpointManager.getInsecurePublicLocalEndpoint(this.nativeId); + return `${webhookUrl}motionDetected`; + } + /** * Identify and return available streams on the provided camera. */ @@ -207,11 +258,13 @@ class SynologySurveillanceStation extends ScryptedDeviceBase implements Settings manufacturer: camera.vendor, model: camera.model, firmware: camera.firmware, - serialNumber: '' + camera.id + serialNumber: `Camera-${camera.id}`, }, interfaces: [ - ScryptedInterface.Settings, ScryptedInterface.Camera, + ScryptedInterface.HttpRequestHandler, + ScryptedInterface.MotionSensor, + ScryptedInterface.Settings, ScryptedInterface.VideoCamera, ], type: ScryptedDeviceType.Camera