Compare commits

...

14 Commits

Author SHA1 Message Date
Koushik Dutta
801bd46730 server: default auth should be last available option 2024-01-31 12:10:25 -08:00
Koushik Dutta
e5a764d82f reolink: use onvif events on doorbell 2024-01-31 12:10:08 -08:00
Koushik Dutta
7c9cd9f112 postrelease 2024-01-30 10:03:47 -08:00
Koushik Dutta
59e1391fae fix restore on lxc 2024-01-30 10:03:31 -08:00
Koushik Dutta
e0a6e66e8a core: fix lag with terminal input 2024-01-29 22:17:08 -08:00
Koushik Dutta
fa7071b335 Merge branch 'main' of github.com:koush/scrypted 2024-01-29 21:02:05 -08:00
Koushik Dutta
c28e60d875 tensorflow-lite: switch default model to efficientdet_lite0_320_ptq 2024-01-29 21:02:00 -08:00
Koushik Dutta
62cbb88207 install: ensure local/docker services cant run concurrently 2024-01-29 18:21:14 -08:00
Brett Jia
4b6a858f2b ui: add client-side flow control (#1290) 2024-01-29 13:59:51 -08:00
Matthew Lieder
97a254b5d2 synology-ss: make login more resilient (#1289)
Fixes #1266
2024-01-28 15:27:02 -08:00
Koushik Dutta
0ada6286e7 openvino: update dep 2024-01-25 19:57:16 -08:00
Koushik Dutta
9f12e6dd6e ha: publish 2024-01-25 08:51:41 -08:00
Koushik Dutta
604798e845 docker: fixup /dev/dri enabling 2024-01-25 08:26:40 -08:00
Koushik Dutta
d912266de1 postrelease 2024-01-24 23:47:11 -08:00
27 changed files with 427 additions and 153 deletions

View File

@@ -1,6 +1,6 @@
# Home Assistant Addon Configuration
name: Scrypted
version: "18-jammy-full.s6-v0.88.0"
version: "18-jammy-full.s6-v0.91.6"
slug: scrypted
description: Scrypted is a high performance home video integration and automation platform
url: "https://github.com/koush/scrypted"

View File

@@ -26,6 +26,10 @@ then
fi
fi
echo "Stopping local service if it is running..."
systemctl stop scrypted.service 2> /dev/null
systemctl disable scrypted.service 2> /dev/null
USER_HOME=$(eval echo ~$SERVICE_USER)
SCRYPTED_HOME=$USER_HOME/.scrypted
mkdir -p $SCRYPTED_HOME
@@ -48,7 +52,7 @@ echo "Created $DOCKER_COMPOSE_YML"
curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/docker-compose.yml | sed s/SET_THIS_TO_SOME_RANDOM_TEXT/"$(echo $RANDOM | md5sum | head -c 32)"/g > $DOCKER_COMPOSE_YML
if [ -d /dev/dri ]
then
sed -i 's/'#' - \/dev\/dri/- \/dev\/dri/g' $DOCKER_COMPOSE_YML
sed -i 's/'#' "\/dev\/dri/"\/dev\/dri/g' $DOCKER_COMPOSE_YML
fi
echo "Setting permissions on $SCRYPTED_HOME"

View File

@@ -89,6 +89,13 @@ USER_HOME=$(eval echo ~$SERVICE_USER)
echo "Setting permissions on $USER_HOME/.scrypted"
chown -R $SERVICE_USER $USER_HOME/.scrypted
echo "Stopping docker service if it exists..."
cd $USER_HOME/.scrypted
echo "docker compose down"
sudo -u $SERVICE_USER docker compose down 2> /dev/null
echo "docker compose rm -rf"
sudo -u $SERVICE_USER docker rm -f /scrypted /scrypted-watchtower 2> /dev/null
echo "Installing Scrypted..."
RUN sudo -u $SERVICE_USER npx -y scrypted@latest install-server

View File

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

View File

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

View File

@@ -121,7 +121,7 @@
},
"../../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.4",
"version": "0.3.5",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
@@ -158,7 +158,7 @@
},
"../../../sdk/types": {
"name": "@scrypted/types",
"version": "0.3.4",
"version": "0.3.5",
"license": "ISC",
"devDependencies": {
"@types/rimraf": "^3.0.2",

View File

@@ -33,17 +33,56 @@ export default {
const termSvcRaw = this.$scrypted.systemManager.getDeviceByName("@scrypted/core");
const termSvc = await termSvcRaw.getDevice("terminalservice");
const termSvcDirect = await this.$scrypted.connectRPCObject(termSvc);
const queue = createAsyncQueue();
const dataQueue = createAsyncQueue();
const ctrlQueue = createAsyncQueue();
queue.enqueue(JSON.stringify({ interactive: true }));
queue.enqueue(JSON.stringify({ dim: { cols: term.cols, rows: term.rows } }));
ctrlQueue.enqueue({ interactive: true });
ctrlQueue.enqueue({ dim: { cols: term.cols, rows: term.rows } });
term.onData(data => queue.enqueue(Buffer.from(data, 'utf8')));
term.onBinary(data => queue.enqueue(Buffer.from(data, 'binary')));
term.onResize(dim => queue.enqueue(JSON.stringify({ dim })));
let bufferedLength = 0;
const MAX_BUFFERED_LENGTH = 64000;
async function dataQueueEnqueue(data) {
bufferedLength += data.length;
const promise = dataQueue.enqueue(data).then(() => bufferedLength -= data.length);
if (bufferedLength >= MAX_BUFFERED_LENGTH) {
term.setOption("disableStdin", true);
await promise;
if (bufferedLength < MAX_BUFFERED_LENGTH)
term.setOption("disableStdin", false);
}
}
const localGenerator = queue.queue;
const remoteGenerator = await termSvcDirect.connectStream(localGenerator);
term.onData(data => dataQueueEnqueue(Buffer.from(data, 'utf8')));
term.onBinary(data => dataQueueEnqueue(Buffer.from(data, 'binary')));
term.onResize(dim => {
ctrlQueue.enqueue({ dim });
ctrlQueue.enqueue(Buffer.alloc(0));
});
async function* localGenerator() {
while (true) {
const ctrlBuffers = ctrlQueue.clear();
if (ctrlBuffers.length) {
for (const ctrl of ctrlBuffers) {
yield JSON.stringify(ctrl);
}
continue;
}
const dataBuffers = dataQueue.clear();
if (dataBuffers.length === 0) {
const buf = await dataQueue.dequeue();
if (buf.length)
yield buf;
continue;
}
const concat = Buffer.concat(dataBuffers);
if (concat.length)
yield concat;
}
}
const remoteGenerator = await termSvcDirect.connectStream(localGenerator());
for await (const message of remoteGenerator) {
if (!message) {

View File

@@ -103,6 +103,8 @@ export class OnvifCameraAPI {
const dataValue = event.message.message.data.simpleItem.$.Value;
const eventTopic = stripNamespaces(event.topic._);
ret.emit('onvifEvent', eventTopic, dataValue);
if (eventTopic.includes('MotionAlarm')) {
// ret.emit('event', OnvifEvent.MotionBuggy);
if (dataValue)

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/openvino",
"version": "0.1.48",
"version": "0.1.51",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/openvino",
"version": "0.1.48",
"version": "0.1.51",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -41,5 +41,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.48"
"version": "0.1.51"
}

View File

@@ -1,4 +1,4 @@
openvino==2023.2.0
openvino==2023.3.0
# pillow-simd is available on x64 linux
# pillow-simd confirmed not building with arm64 linux or apple silicon

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/reolink",
"version": "0.0.58",
"version": "0.0.59",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/reolink",
"version": "0.0.58",
"version": "0.0.59",
"license": "Apache",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

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

View File

@@ -3,7 +3,7 @@ import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, Intercom, MediaO
import { StorageSettings } from '@scrypted/sdk/storage-settings';
import { EventEmitter } from "stream";
import { Destroyable, RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
import { OnvifCameraAPI, connectCameraAPI } from './onvif-api';
import { OnvifCameraAPI, OnvifEvent, connectCameraAPI } from './onvif-api';
import { listenEvents } from './onvif-events';
import { OnvifIntercom } from './onvif-intercom';
import { AIState, DevInfo, Enc, ReolinkCameraClient } from './reolink-api';
@@ -49,6 +49,10 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom,
await this.updateDevice();
this.updatePtzCaps();
},
},
doorbellUseOnvifDetections: {
hide: true,
defaultValue: true,
}
});
@@ -80,6 +84,12 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom,
}
async getObjectTypes(): Promise<ObjectDetectionTypes> {
if (this.storageSettings.values.doorbell && this.storageSettings.values.doorbellUseOnvifDetections) {
return {
classes: ['person'],
};
}
try {
const ai: AIState = this.storageSettings.values.hasObjectDetector[0]?.value;
const classes: string[] = [];
@@ -228,9 +238,35 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom,
if (this.storageSettings.values.doorbell) {
const ret = await listenEvents(this, await this.createOnvifClient(), this.storageSettings.values.motionTimeout * 1000);
if (!this.storageSettings.values.doorbellUseOnvifDetections) {
startAI(ret, ret.triggerMotion);
}
else {
ret.on('onvifEvent', (eventTopic: string, dataValue: any) => {
if (eventTopic.includes('PeopleDetect')) {
if (dataValue) {
ret.emit('event', OnvifEvent.MotionStart);
const od: ObjectsDetected = {
timestamp: Date.now(),
detections: [
{
className: 'person',
score: 1,
}
],
};
sdk.deviceManager.onDeviceEvent(this.nativeId, ScryptedInterface.ObjectDetector, od);
}
else {
ret.emit('event', OnvifEvent.MotionStop);
}
}
});
}
ret.on('close', () => killed = true);
ret.on('error', () => killed = true);
startAI(ret, ret.triggerMotion);
return ret;
}

View File

@@ -1,30 +1,30 @@
{
"name": "@scrypted/synology-ss",
"version": "0.0.16",
"version": "0.0.17",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/synology-ss",
"version": "0.0.16",
"version": "0.0.17",
"license": "Apache",
"dependencies": {
"axios": "^0.24.0"
"axios": "^1.0.0"
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk",
"@types/node": "^16.6.1"
"@types/node": "^18.0.0"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.2.86",
"version": "0.3.5",
"dev": true,
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^0.21.4",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
@@ -59,23 +59,52 @@
"link": true
},
"node_modules/@types/node": {
"version": "16.11.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz",
"integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==",
"dev": true
"version": "18.19.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.10.tgz",
"integrity": "sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"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.6.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
"dependencies": {
"follow-redirects": "^1.14.4"
"follow-redirects": "^1.15.4",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"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/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/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"funding": [
{
"type": "individual",
@@ -90,6 +119,49 @@
"optional": true
}
}
},
"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/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/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/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
}
},
"dependencies": {
@@ -100,7 +172,7 @@
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"adm-zip": "^0.4.13",
"axios": "^0.21.4",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
@@ -118,23 +190,80 @@
}
},
"@types/node": {
"version": "16.11.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz",
"integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==",
"dev": true
},
"axios": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
"version": "18.19.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.10.tgz",
"integrity": "sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==",
"dev": true,
"requires": {
"follow-redirects": "^1.14.4"
"undici-types": "~5.26.4"
}
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"axios": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
"requires": {
"follow-redirects": "^1.15.4",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "~1.0.0"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw=="
},
"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==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"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==",
"requires": {
"mime-db": "1.52.0"
}
},
"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=="
},
"undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/synology-ss",
"version": "0.0.16",
"version": "0.0.17",
"description": "A Synology Surveillance Station plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",
@@ -36,10 +36,10 @@
]
},
"dependencies": {
"axios": "^0.24.0"
"axios": "^1.0.0"
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk",
"@types/node": "^16.6.1"
"@types/node": "^18.0.0"
}
}

View File

@@ -164,11 +164,12 @@ export class SynologyApiClient {
const response = await this.client.get<SynologyApiResponse<T>>(url ?? await this.getApiPath(params.api), { params });
if (!response.data?.success) {
if (response.data?.error?.code) {
const errorCode = response.data?.error?.code;
if (errorCode) {
const errorCodeLookup = { ...errorCodeDescriptions, ...extraErrorCodes }
throw new Error(`${errorCodeLookup[response.data.error.code]} (error code ${response.data.error.code})`)
throw new SynologyApiError(`${errorCodeLookup[errorCode]} (error code ${errorCode})`, errorCode)
} else {
throw new Error(`Synology API call failed with status code ${response.status}`);
throw new SynologyApiError(`Synology API call failed with status code ${response.status}`);
}
}
@@ -186,7 +187,17 @@ export interface SynologyApiInfo {
maxVersion: number;
}
export interface SynologyApiError {
export class SynologyApiError extends Error {
code?: string;
constructor(message: string, code?: string) {
super(message);
this.name = 'SynologyApiError';
this.code = code;
}
}
export interface SynologyApiErrorObject {
code: string;
}
@@ -198,7 +209,7 @@ interface SynologyApiRequestParams {
interface SynologyApiResponse<T> {
data?: T;
error?: SynologyApiError;
error?: SynologyApiErrorObject;
success: boolean;
}

View File

@@ -1,6 +1,6 @@
import sdk, { Camera, Device, DeviceProvider, HttpRequest, HttpRequestHandler, HttpResponse, MediaObject, MediaStreamOptions, MediaStreamUrl, MotionSensor, PictureOptions, ResponseMediaStreamOptions, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, VideoCamera } from "@scrypted/sdk";
import { createInstanceableProviderPlugin, enableInstanceableProviderMode, isInstanceableProviderModeEnabled } from '../../../common/src/provider-plugin';
import { SynologyApiClient, SynologyCamera, SynologyCameraStream } from "./api/synology-api-client";
import { SynologyApiClient, SynologyApiError, SynologyCamera, SynologyCameraStream } from "./api/synology-api-client";
const { deviceManager } = sdk;
@@ -162,10 +162,11 @@ class SynologyCameraDevice extends ScryptedDeviceBase implements Camera, HttpReq
}
class SynologySurveillanceStation extends ScryptedDeviceBase implements Settings, DeviceProvider {
private cameras: SynologyCamera[];
private cameras: SynologyCamera[] = [];
private cameraDevices: Map<string, SynologyCameraDevice> = new Map();
api: SynologyApiClient;
private startup: Promise<void>;
private discovering: boolean;
constructor(nativeId?: string) {
super(nativeId);
@@ -177,66 +178,23 @@ class SynologySurveillanceStation extends ScryptedDeviceBase implements Settings
}
public async discoverDevices(duration: number): Promise<void> {
const url = this.getSetting('url');
const username = this.getSetting('username');
const password = this.getSetting('password');
const otpCode = this.getSetting('otpCode');
const mfaDeviceId = this.getSetting('mfaDeviceId');
if (this.discovering) return;
this.discovering = true;
this.log.clearAlerts();
if (!url) {
this.log.a('Must provide URL.');
return
}
if (!username) {
this.log.a('Must provide username.');
return
}
if (!password) {
this.log.a('Must provide password.');
return
}
if (!this.api || url !== this.api.url) {
this.api = new SynologyApiClient(url);
}
this.console.info(`Fetching list of cameras from Synology server...`);
try {
const newMfaDeviceId = await this.api.login(username, password, otpCode ? parseInt(otpCode) : undefined, !!otpCode, 'Scrypted', mfaDeviceId);
// If a OTP was present, store the device ID to allow us to skip the OTP requirement next login.
if (otpCode) {
this.storage.setItem('mfaDeviceId', newMfaDeviceId);
if (!await this.tryLogin()) {
return;
}
}
catch (e) {
this.log.a(`login error: ${e}`);
this.console.error('login error', e);
// Clear device ID upon login failure, since it's likely useless now
this.storage.removeItem('mfaDeviceId');
return;
}
finally {
// Clear the OTP setting if provided since it's a temporary code
if (otpCode) {
this.storage.removeItem('otpCode');
this.onDeviceEvent(ScryptedInterface.Settings, undefined);
}
}
try {
this.cameras = await this.api.listCameras();
if (!this.cameras) {
this.console.error('Cameras failed to load. Retrying in 10 seconds.');
setTimeout(() => {
this.discoverDevices(0);
}, 100000);
}, 10000);
return;
}
@@ -285,6 +243,8 @@ class SynologySurveillanceStation extends ScryptedDeviceBase implements Settings
catch (e) {
this.log.a(`device discovery error: ${e}`);
this.console.error('device discovery error', e);
} finally {
this.discovering = false;
}
}
@@ -353,7 +313,81 @@ class SynologySurveillanceStation extends ScryptedDeviceBase implements Settings
return;
}
this.storage.setItem(key, value.toString());
this.discoverDevices(0);
// Delaying discover in case user updated multiple settings, so that it doesn't run until all have been set
setTimeout(() => this.discoverDevices(0), 200);
}
private async tryLogin(): Promise<boolean> {
this.console.info('Logging into Synology...');
const url = this.getSetting('url');
const username = this.getSetting('username');
const password = this.getSetting('password');
const otpCode = this.getSetting('otpCode');
const mfaDeviceId = this.getSetting('mfaDeviceId');
this.log.clearAlerts();
if (!url) {
this.log.a('Must provide URL.');
return
}
if (!username) {
this.log.a('Must provide username.');
return
}
if (!password) {
this.log.a('Must provide password.');
return
}
if (!this.api || url !== this.api.url) {
this.api = new SynologyApiClient(url);
}
let successful = false;
for (let attempt=1; attempt<=3; attempt++) {
try {
const newMfaDeviceId = await this.api.login(username, password, otpCode ? parseInt(otpCode) : undefined, !!otpCode, 'Scrypted', mfaDeviceId);
// If a OTP was present, store the device ID to allow us to skip the OTP requirement next login.
if (otpCode) {
this.storage.setItem('mfaDeviceId', newMfaDeviceId);
}
successful = true;
}
catch (e) {
this.log.a(`login error on attempt ${attempt}: ${e}`);
this.console.error(`login error on attempt ${attempt}`, e);
if (e instanceof SynologyApiError) {
break;
} else {
// Retry on failures that aren't Synology-specific, such as timeouts
await new Promise((resolve) => setTimeout(resolve, attempt * 1000));
continue;
}
}
finally {
// Clear the OTP setting if provided since it's a temporary code
if (otpCode) {
this.storage.removeItem('otpCode');
this.onDeviceEvent(ScryptedInterface.Settings, undefined);
}
}
}
if (successful) {
this.console.info(`Successfully logged into Synology`);
} else {
this.console.info(`Failed to log into Synology`);
}
return successful;
}
}

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"module": "commonjs",
"module": "Node16",
"target": "ES2021",
"resolveJsonModule": true,
"moduleResolution": "Node16",

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/tensorflow-lite",
"version": "0.1.45",
"version": "0.1.46",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/tensorflow-lite",
"version": "0.1.45",
"version": "0.1.46",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -53,5 +53,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.45"
"version": "0.1.46"
}

View File

@@ -70,7 +70,7 @@ class TensorFlowLitePlugin(
nonlocal model
if defaultModel:
model = "yolov8n_full_integer_quant_320"
model = "efficientdet_lite0_320_ptq"
self.yolo = "yolo" in model
self.yolov8 = "yolov8" in model

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/server",
"version": "0.91.6",
"version": "0.93.0",
"description": "",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",

View File

@@ -7,9 +7,15 @@ export function getScryptedVolume() {
return volumeDir;
}
export function getPluginVolume(pluginId: string) {
export function getPluginsVolume() {
const volume = getScryptedVolume();
const pluginVolume = path.join(volume, 'plugins', pluginId);
const pluginsVolume = path.join(volume, 'plugins');
return pluginsVolume;
}
export function getPluginVolume(pluginId: string) {
const volume = getPluginsVolume();
const pluginVolume = path.join(volume, pluginId);
return pluginVolume;
}

View File

@@ -220,14 +220,6 @@ async function start(mainFilename: string, options?: {
}
app.use(async (req, res, next) => {
const defaultAuthentication = getDefaultAuthentication(req);
if (defaultAuthentication) {
res.locals.username = defaultAuthentication._id;
res.locals.aclId = defaultAuthentication.aclId;
next();
return;
}
// the remote address may be ipv6 prefixed so use a fuzzy match.
// eg ::ffff:192.168.2.124
if (process.env.SCRYPTED_ADMIN_USERNAME
@@ -307,6 +299,15 @@ async function start(mainFilename: string, options?: {
else if (req.query['scryptedToken']) {
checkToken(req.query.scryptedToken.toString());
}
if (!res.locals.username) {
const defaultAuthentication = getDefaultAuthentication(req);
if (defaultAuthentication) {
res.locals.username = defaultAuthentication._id;
res.locals.aclId = defaultAuthentication.aclId;
}
}
next();
});
@@ -637,22 +638,6 @@ async function start(mainFilename: string, options?: {
return;
}
// env based anon user login
const defaultAuthentication = getDefaultAuthentication(req);
if (defaultAuthentication) {
const userToken = new UserToken(defaultAuthentication._id, defaultAuthentication.aclId, Date.now());
res.send({
...createTokens(userToken),
expiration: ONE_DAY_MILLISECONDS,
username: defaultAuthentication,
// TODO: do not return the token from a short term auth mechanism?
token: defaultAuthentication?.token,
...alternateAddresses,
hostname,
});
return;
}
// basic auth
if (req.protocol === 'https' && req.headers.authorization) {
const username = await new Promise(resolve => {
@@ -697,6 +682,22 @@ async function start(mainFilename: string, options?: {
})
}
catch (e) {
// env based anon user login
const defaultAuthentication = getDefaultAuthentication(req);
if (defaultAuthentication) {
const userToken = new UserToken(defaultAuthentication._id, defaultAuthentication.aclId, Date.now());
res.send({
...createTokens(userToken),
expiration: ONE_DAY_MILLISECONDS,
username: defaultAuthentication,
// TODO: do not return the token from a short term auth mechanism?
token: defaultAuthentication?.token,
...alternateAddresses,
hostname,
});
return;
}
res.send({
error: e?.message || 'Unknown Error.',
hasLogin,

View File

@@ -2,9 +2,10 @@ import fs from 'fs';
import path from 'path';
import Level from '../level';
import { sleep } from '../sleep';
import { getScryptedVolume } from '../plugin/plugin-volume';
import { getPluginsVolume, getScryptedVolume } from '../plugin/plugin-volume';
import AdmZip from 'adm-zip';
import { ScryptedRuntime } from '../runtime';
import { getPluginNodePath } from '../plugin/plugin-npm-dependencies';
export class Backup {
constructor(public runtime: ScryptedRuntime) {}
@@ -48,13 +49,17 @@ export class Backup {
await sleep(5000);
await this.runtime.datastore.close();
await fs.promises.rm(volumeDir, {
// nuke the existing database path
await fs.promises.rm(dbPath, {
recursive: true,
force: true,
});
await fs.promises.mkdir(volumeDir, {
recursive: true
// nuke all the plugins and associated files downloaded by thhem.
// first run after restore will reinstall everything.
await fs.promises.rm(getPluginsVolume(), {
recursive: true,
force: true,
});
zip.extractAllTo(dbPath, true);