plugins/sdk: add support for rebooting devices

This commit is contained in:
Koushik Dutta
2023-05-01 23:04:05 -07:00
parent eb864456df
commit 1ea2828e78
20 changed files with 140 additions and 61 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.121",
"version": "0.0.122",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/amcrest",
"version": "0.0.121",
"version": "0.0.122",
"license": "Apache",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.121",
"version": "0.0.122",
"description": "Amcrest Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -33,6 +33,16 @@ export class AmcrestCameraClient {
});
}
async reboot() {
const response = await this.digestAuth.request({
httpsAgent: amcrestHttpsAgent,
method: "GET",
responseType: 'text',
url: `http://${this.ip}/cgi-bin/magicBox.cgi?action=reboot`,
});
return response.data as string;
}
async checkTwoWayAudio() {
const response = await this.digestAuth.request({
httpsAgent: amcrestHttpsAgent,

View File

@@ -1,6 +1,6 @@
import { ffmpegLogInitialOutput } from '@scrypted/common/src/media-helpers';
import { readLength } from "@scrypted/common/src/read-stream";
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, MediaStreamOptions, PictureOptions, RequestRecordingStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, VideoCameraConfiguration, VideoRecorder } from "@scrypted/sdk";
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, MediaStreamOptions, PictureOptions, Reboot, RequestRecordingStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, VideoCameraConfiguration, VideoRecorder } from "@scrypted/sdk";
import child_process, { ChildProcess } from 'child_process';
import { PassThrough, Readable, Stream } from "stream";
import { OnvifIntercom } from "../../onvif/src/onvif-intercom";
@@ -23,7 +23,7 @@ function findValue(blob: string, prefix: string, key: string) {
return parts[1];
}
class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration, Camera, Intercom, VideoRecorder {
class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration, Camera, Intercom, VideoRecorder, Reboot {
eventStream: Stream;
cp: ChildProcess;
client: AmcrestCameraClient;
@@ -37,9 +37,15 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
this.storage.removeItem('amcrestDoorbell');
}
this.updateDevice();
this.updateDeviceInfo();
}
async reboot() {
const client = this.getClient();
await client.reboot();
}
getRecordingStreamCurrentTime(recordingStream: MediaObject): Promise<number> {
throw new Error("Method not implemented.");
}
@@ -440,6 +446,29 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
return this.videoStreamOptions;
}
updateDevice() {
const doorbellType = this.storage.getItem('doorbellType');
const isDoorbell = doorbellType === AMCREST_DOORBELL_TYPE || doorbellType === DAHUA_DOORBELL_TYPE;
// true is the legacy value before onvif was added.
const twoWayAudio = this.storage.getItem('twoWayAudio') === 'true'
|| this.storage.getItem('twoWayAudio') === 'ONVIF'
|| this.storage.getItem('twoWayAudio') === 'Amcrest';
const interfaces = this.provider.getInterfaces();
let type: ScryptedDeviceType = undefined;
if (isDoorbell) {
type = ScryptedDeviceType.Doorbell;
interfaces.push(ScryptedInterface.BinarySensor)
}
if (isDoorbell || twoWayAudio) {
interfaces.push(ScryptedInterface.Intercom);
}
const continuousRecording = this.storage.getItem('continuousRecording') === 'true';
if (continuousRecording)
interfaces.push(ScryptedInterface.VideoRecorder);
this.provider.updateDevice(this.nativeId, this.name, interfaces, type);
}
async putSetting(key: string, value: string) {
if (key === 'continuousRecording') {
if (value === 'true') {
@@ -461,27 +490,8 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
this.videoStreamOptions = undefined;
super.putSetting(key, value);
const doorbellType = this.storage.getItem('doorbellType');
const isDoorbell = doorbellType === AMCREST_DOORBELL_TYPE || doorbellType === DAHUA_DOORBELL_TYPE;
// true is the legacy value before onvif was added.
const twoWayAudio = this.storage.getItem('twoWayAudio') === 'true'
|| this.storage.getItem('twoWayAudio') === 'ONVIF'
|| this.storage.getItem('twoWayAudio') === 'Amcrest';
const interfaces = this.provider.getInterfaces();
let type: ScryptedDeviceType = undefined;
if (isDoorbell) {
type = ScryptedDeviceType.Doorbell;
interfaces.push(ScryptedInterface.BinarySensor)
}
if (isDoorbell || twoWayAudio) {
interfaces.push(ScryptedInterface.Intercom);
}
const continuousRecording = this.storage.getItem('continuousRecording') === 'true';
if (continuousRecording)
interfaces.push(ScryptedInterface.VideoRecorder);
this.provider.updateDevice(this.nativeId, this.name, interfaces, type);
this.updateDevice();
this.updateDeviceInfo();
}
@@ -576,6 +586,7 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
class AmcrestProvider extends RtspProvider {
getAdditionalInterfaces() {
return [
ScryptedInterface.Reboot,
ScryptedInterface.VideoCameraConfiguration,
ScryptedInterface.Camera,
ScryptedInterface.AudioSensor,

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/core",
"version": "0.1.114",
"version": "0.1.115",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/core",
"version": "0.1.114",
"version": "0.1.115",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/core",
"version": "0.1.114",
"version": "0.1.115",
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/hikvision",
"version": "0.0.126",
"version": "0.0.127",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/hikvision",
"version": "0.0.126",
"version": "0.0.127",
"license": "Apache",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/hikvision",
"version": "0.0.126",
"version": "0.0.127",
"description": "Hikvision Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -39,6 +39,17 @@ export class HikvisionCameraAPI {
});
}
async reboot() {
const response = await this.digestAuth.request({
httpsAgent: hikvisionHttpsAgent,
method: "PUT",
responseType: 'text',
url: `http://${this.ip}/ISAPI/System/reboot`,
});
return response.data;
}
async getDeviceInfo() {
return getDeviceInfo(this.digestAuth, this.ip);
}

View File

@@ -1,15 +1,12 @@
import { ffmpegLogInitialOutput } from '@scrypted/common/src/media-helpers';
import { readLength } from '@scrypted/common/src/read-stream';
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, MediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting } from "@scrypted/sdk";
import child_process, { ChildProcess } from 'child_process';
import { PassThrough, Readable } from "stream";
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, MediaStreamOptions, Reboot, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting } from "@scrypted/sdk";
import { PassThrough } from "stream";
import xml2js from 'xml2js';
import { RtpPacket } from '../../../external/werift/packages/rtp/src/rtp/rtp';
import { OnvifIntercom } from "../../onvif/src/onvif-intercom";
import { RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
import { startRtpForwarderProcess } from '../../webrtc/src/rtp-forwarders';
import { HikvisionCameraAPI, HikvisionCameraEvent } from "./hikvision-camera-api";
import { hikvisionHttpsAgent } from './probe';
import { startRtpForwarderProcess } from '../../webrtc/src/rtp-forwarders';
import { RtpPacket } from '../../../external/werift/packages/rtp/src/rtp/rtp';
const { mediaManager } = sdk;
@@ -19,18 +16,24 @@ function channelToCameraNumber(channel: string) {
return channel.substring(0, channel.length - 2);
}
class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom {
class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboot {
detectedChannels: Promise<Map<string, MediaStreamOptions>>;
client: HikvisionCameraAPI;
onvifIntercom = new OnvifIntercom(this);
activeIntercom: Awaited<ReturnType<typeof startRtpForwarderProcess>>;
constructor(nativeId: string, provider: RtspProvider) {
super(nativeId, provider);
this.updateDevice();
this.updateDeviceInfo();
}
async reboot() {
const client = this.getClient();
await client.reboot();
}
async updateDeviceInfo() {
const ip = this.storage.getItem('ip');
if (!ip)
@@ -40,7 +43,7 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom {
...this.info,
managementUrl,
ip,
manufacturer: 'Hikvision',
manufacturer: 'Hikvision',
};
const client = this.getClient();
const deviceInfo = await client.getDeviceInfo().catch(() => { });
@@ -272,11 +275,7 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom {
return false;
}
async putSetting(key: string, value: string) {
this.client = undefined;
this.detectedChannels = undefined;
super.putSetting(key, value);
updateDevice() {
const doorbellType = this.storage.getItem('doorbellType');
const isDoorbell = doorbellType === 'true';
@@ -295,7 +294,14 @@ class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom {
}
this.provider.updateDevice(this.nativeId, this.name, interfaces, type);
}
async putSetting(key: string, value: string) {
this.client = undefined;
this.detectedChannels = undefined;
super.putSetting(key, value);
this.updateDevice();
this.updateDeviceInfo();
}
@@ -495,6 +501,7 @@ class HikvisionProvider extends RtspProvider {
getAdditionalInterfaces() {
return [
ScryptedInterface.Reboot,
ScryptedInterface.Camera,
ScryptedInterface.MotionSensor,
];

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/onvif",
"version": "0.0.120",
"version": "0.0.121",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/onvif",
"version": "0.0.120",
"version": "0.0.121",
"license": "Apache",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/onvif",
"version": "0.0.120",
"version": "0.0.121",
"description": "ONVIF Camera Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -1,4 +1,4 @@
import sdk, { AdoptDevice, Device, DeviceCreatorSettings, DeviceDiscovery, DeviceInformation, DiscoveredDevice, Intercom, MediaObject, MediaStreamOptions, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, PanTiltZoom, PanTiltZoomCommand, PictureOptions, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting, Settings, SettingValue, VideoCamera, VideoCameraConfiguration } from "@scrypted/sdk";
import sdk, { AdoptDevice, Device, DeviceCreatorSettings, DeviceDiscovery, DeviceInformation, DiscoveredDevice, Intercom, MediaObject, MediaStreamOptions, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, PanTiltZoom, PanTiltZoomCommand, PictureOptions, Reboot, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting, Settings, SettingValue, VideoCamera, VideoCameraConfiguration } from "@scrypted/sdk";
import { AddressInfo } from "net";
import onvif from 'onvif';
import { Stream } from "stream";
@@ -30,7 +30,7 @@ function convertAudioCodec(codec: string) {
return codec?.toLowerCase();
}
class OnvifCamera extends RtspSmartCamera implements ObjectDetector, Intercom, VideoCameraConfiguration {
class OnvifCamera extends RtspSmartCamera implements ObjectDetector, Intercom, VideoCameraConfiguration, Reboot {
eventStream: Stream;
client: OnvifCameraAPI;
rtspMediaStreamOptions: Promise<UrlMediaStreamOptions[]>;
@@ -43,6 +43,11 @@ class OnvifCamera extends RtspSmartCamera implements ObjectDetector, Intercom, V
this.updateDevice();
}
async reboot(): Promise<void> {
const client = await this.getClient();
await client.reboot();
}
async setVideoStreamOptions(options: MediaStreamOptions): Promise<void> {
const client = await this.getClient();
const profiles: any[] = await client.getProfiles();
@@ -531,6 +536,7 @@ class OnvifProvider extends RtspProvider implements DeviceDiscovery {
getAdditionalInterfaces() {
return [
ScryptedInterface.Reboot,
ScryptedInterface.Camera,
ScryptedInterface.AudioSensor,
ScryptedInterface.MotionSensor,

View File

@@ -69,6 +69,19 @@ export class OnvifCameraAPI {
});
}
async reboot() {
return new Promise((resolve, reject) => {
this.cam.systemReboot((err: Error, data: any, xml: string) => {
if (err) {
this.console.log('reboot error', err);
return reject(err);
}
resolve(data as string);
});
})
}
listenEvents() {
const ret = new EventEmitter();

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/reolink",
"version": "0.0.22",
"version": "0.0.23",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/reolink",
"version": "0.0.22",
"version": "0.0.23",
"license": "Apache",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/reolink",
"version": "0.0.22",
"version": "0.0.23",
"description": "Reolink Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -1,18 +1,24 @@
import { sleep } from '@scrypted/common/src/sleep';
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, MediaObject, PictureOptions, ScryptedInterface, Setting } from "@scrypted/sdk";
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, MediaObject, PictureOptions, Reboot, ScryptedDeviceType, ScryptedInterface, Setting } from "@scrypted/sdk";
import { EventEmitter } from "stream";
import { Destroyable, RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
import { ReolinkCameraClient } from './reolink-api';
const { mediaManager } = sdk;
class ReolinkCamera extends RtspSmartCamera implements Camera {
class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot {
client: ReolinkCameraClient;
constructor(nativeId: string, provider: RtspProvider) {
super(nativeId, provider);
this.updateManagementUrl();
this.provider.updateDevice(this.nativeId, this.name, this.provider.getInterfaces(), this.providedType);
}
async reboot() {
const client = this.getClient();
await client.reboot();
}
updateManagementUrl() {
@@ -211,6 +217,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera {
class ReolinkProider extends RtspProvider {
getAdditionalInterfaces() {
return [
ScryptedInterface.Reboot,
ScryptedInterface.VideoCameraConfiguration,
ScryptedInterface.Camera,
ScryptedInterface.AudioSensor,

View File

@@ -13,6 +13,22 @@ export class ReolinkCameraClient {
});
}
async reboot() {
const url = new URL(`http://${this.host}/api.cgi`);
const params = url.searchParams;
params.set('cmd', 'Reboot');
params.set('user', this.username);
params.set('password', this.password);
const response = await this.digestAuth.request({
url: url.toString(),
httpsAgent: reolinkHttpsAgent,
});
return {
value: response.data?.[0]?.value?.rspCode,
data: response.data,
};
}
// [
// {
// "cmd" : "GetMdState",

View File

@@ -216,8 +216,6 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
const detection: ObjectsDetected = {
detectionId,
// eventId indicates that the detection is within a single frame.
eventId: detectionId,
timestamp: Date.now(),
detections,
};

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/server",
"version": "0.8.0",
"version": "0.9.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/server",
"version": "0.8.0",
"version": "0.9.0",
"license": "ISC",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.10",