From a2caad710954cfe3cb7514f5ff0fa72228fa4d6a Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Tue, 10 Sep 2024 17:43:13 -0700 Subject: [PATCH] diagnostics: idr check --- plugins/diagnostics/package-lock.json | 4 +- plugins/diagnostics/package.json | 2 +- plugins/diagnostics/src/main.ts | 93 +++++++++++++++++---------- 3 files changed, 63 insertions(+), 36 deletions(-) diff --git a/plugins/diagnostics/package-lock.json b/plugins/diagnostics/package-lock.json index 6e09d82dd..50fc80e0a 100644 --- a/plugins/diagnostics/package-lock.json +++ b/plugins/diagnostics/package-lock.json @@ -13,7 +13,7 @@ "devDependencies": { "@types/node": "^22.5.4" }, - "version": "0.0.4" + "version": "0.0.6" }, "../../common": { "name": "@scrypted/common", @@ -844,5 +844,5 @@ "dev": true } }, - "version": "0.0.4" + "version": "0.0.6" } diff --git a/plugins/diagnostics/package.json b/plugins/diagnostics/package.json index 334b8b2f4..db7b1655a 100644 --- a/plugins/diagnostics/package.json +++ b/plugins/diagnostics/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/diagnostics", - "version": "0.0.4", + "version": "0.0.6", "scripts": { "scrypted-setup-project": "scrypted-setup-project", "prescrypted-setup-project": "scrypted-package-json", diff --git a/plugins/diagnostics/src/main.ts b/plugins/diagnostics/src/main.ts index 9e00c2735..3bc5ddd46 100644 --- a/plugins/diagnostics/src/main.ts +++ b/plugins/diagnostics/src/main.ts @@ -2,7 +2,7 @@ import sharp from 'sharp'; import net from 'net'; import fs from 'fs'; import os from 'os'; -import sdk, { Camera, MediaObject, MotionSensor, Notifier, OnOff, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, Settings, VideoCamera } from '@scrypted/sdk'; +import sdk, { Camera, MediaObject, MediaStreamDestination, MotionSensor, Notifier, OnOff, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, Settings, VideoCamera } from '@scrypted/sdk'; import { StorageSettings } from '@scrypted/sdk/storage-settings'; import { httpFetch, httpFetchParseIncomingMessage } from '../../../server/src/fetch/http-fetch'; @@ -94,6 +94,14 @@ class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings { this.console.log(`Device Validation: ${device?.name}`); this.console.log(''.padEnd(80, '=')); + await this.validate('Device Selected', async () => { + if (!device) + throw new Error('Select a device in the Settings UI.'); + }); + + if (!device) + return; + if (device.type === ScryptedDeviceType.Camera || device.type === ScryptedDeviceType.Doorbell) { await this.validateCamera(device); } @@ -107,7 +115,7 @@ class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings { } async validateNotifier(device: ScryptedDevice & Notifier) { - this.validate('Test Notification', async () => { + await this.validate('Test Notification', async () => { const logo = await httpFetch({ url: 'https://home.scrypted.app/_punch/web_hi_res_512.png', responseType: 'buffer', @@ -124,14 +132,6 @@ class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings { } async validateCamera(device: ScryptedDevice & Camera & VideoCamera & MotionSensor) { - await this.validate('Device Selected', async () => { - if (!device) - throw new Error('Select a device in the Settings UI.'); - }); - - if (!device) - return; - await this.validate('Device Capabilities', async () => { if (!device.interfaces.includes(ScryptedInterface.MotionSensor)) throw new Error('Motion Sensor not found.'); @@ -141,6 +141,9 @@ class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings { }); await this.validate('Recent Motion', async () => { + if (!device.interfaces.includes(ScryptedInterface.MotionSensor)) + throw new Error('Motion Sensor not found. Enabling a software motion sensor extension is recommended.'); + const lastMotion = this.loggedMotion.get(device.id); if (!lastMotion) throw new Error('No recent motion detected. Go wave your hand in front of the camera.'); @@ -158,18 +161,21 @@ class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings { }); } - const validateMedia = async (stepName: string, mo: MediaObject, snapshot = false) => { + const validateMedia = async (stepName: string, mo: Promise, snapshot = false, and?: () => Promise) => { await this.validate(stepName, async () => { - const jpeg = await sdk.mediaManager.convertMediaObjectToBuffer(mo, 'image/jpeg'); + if (snapshot && !device.interfaces.includes(ScryptedInterface.Camera)) + throw new Error('Snapshot not supported. Enable the Snapshot extension.'); + const jpeg = await sdk.mediaManager.convertMediaObjectToBuffer(await mo, 'image/jpeg'); const metadata = await sharp(jpeg).metadata(); if (!metadata.width || !metadata.height || metadata.width < 100 || metadata.height < 100) throw new Error('Malformed image.'); - if (snapshot && device.pluginId === '@scrypted/unifi-protect' && metadata.width < 1280) + if (!and && snapshot && device.pluginId === '@scrypted/unifi-protect' && metadata.width < 1280) this.warnStep('Unifi Protect provides low quality snapshots. Consider using Snapshot from Prebuffer for full resolution screenshots.'); + await and?.(); }); }; - await validateMedia('Snapshot', await device.takePicture({ + await validateMedia('Snapshot', device.takePicture({ reason: 'event', }), true); @@ -191,25 +197,34 @@ class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings { this.warnStep(`Unused streams detected.`); }); - await validateMedia('Local Stream', await device.getVideoStream({ - destination: 'local', - })); + const getVideoStream = async (destination: MediaStreamDestination) => { + if (!device.interfaces.includes(ScryptedInterface.VideoCamera)) + throw new Error('Streaming not supported.'); + return await device.getVideoStream({ + destination, + prebuffer: 0, + }); + }; - await validateMedia('Local Recorder Stream', await device.getVideoStream({ - destination: 'local-recorder', - })); + const validateMediaStream = async (stepName: string, destination: MediaStreamDestination) => { + await validateMedia(stepName, getVideoStream(destination)); + const start = Date.now(); + await validateMedia(stepName + ' (IDR)', getVideoStream(destination), false, async () => { + const end = Date.now(); + if (end - start > 4500) + throw new Error(`High IDR Interval. This may cause issues with HomeKit Secure Video. Adjust codec configuration if possible.`); + }); + }; - await validateMedia('Remote Recorder Stream', await device.getVideoStream({ - destination: 'remote-recorder', - })); + await validateMediaStream('Local', 'local'); - await validateMedia('Remote Stream', await device.getVideoStream({ - destination: 'remote', - })); + await validateMediaStream('Local Recorder', 'local-recorder'); - await validateMedia('Low Resolution Stream', await device.getVideoStream({ - destination: 'low-resolution', - })); + await validateMediaStream('Remote Recorder', 'remote-recorder'); + + await validateMediaStream('Remote', 'remote'); + + await validateMediaStream('Low Resolution', 'low-resolution'); await this.validate('Audio Codecs', async () => { const vsos = await device.getVideoStreamOptions(); @@ -245,6 +260,7 @@ class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings { const nvrPlugin = sdk.systemManager.getDeviceById('@scrypted/nvr'); const cloudPlugin = sdk.systemManager.getDeviceById('@scrypted/cloud'); + const openvinoPlugin = sdk.systemManager.getDeviceById('@scrypted/openvino'); await this.validate('Scrypted Installation', async () => { const e = process.env.SCRYPTED_INSTALL_ENVIRONMENT; @@ -258,15 +274,17 @@ class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings { throw new Error('Unrecognized Linux installation. Installation via Docker image or the official Proxmox LXC script is recommended.'); }); - await this.validate('IPv4 Connectivity (jsonip.com)', httpFetch({ + await this.validate('IPv4 Check (jsonip.com)', httpFetch({ url: 'https://jsonip.com', family: 4, - }).then(() => { })); + responseType: 'json', + }).then(r => r.body.ip)); - await this.validate('IPv6 Connectivity (jsonip.com)', httpFetch({ + await this.validate('IPv6 Check (jsonip.com)', httpFetch({ url: 'https://jsonip.com', family: 6, - }).then(() => { })); + responseType: 'json', + }).then(r => r.body.ip)); await this.validate('Scrypted Server Address', async () => { const addresses = await sdk.endpointManager.getLocalAddresses(); @@ -324,6 +342,15 @@ class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings { }); } + if (openvinoPlugin) { + await this.validate('OpenVINO Plugin', async () => { + const settings = await openvinoPlugin.getSettings(); + const availbleDevices = settings.find(s => s.key === 'available_devices'); + if (!availbleDevices?.value?.toString().includes('GPU')) + throw new Error('GPU device unvailable or not passed through to container.'); + }); + } + this.console.log(''.padEnd(80, '=')); this.console.log('System Validation Complete'); this.console.log(''.padEnd(80, '='));