Compare commits

..

53 Commits

Author SHA1 Message Date
Koushik Dutta
ec2e4d64fd rebroadcast: set online/offline state without prebuffer requirement 2024-10-29 12:21:40 -07:00
Koushik Dutta
44644448f5 postbeta 2024-10-29 12:07:01 -07:00
Koushik Dutta
0a86d5c4ea server: disable auto transferable Buffers 2024-10-29 12:06:48 -07:00
Koushik Dutta
20282e05ea Merge branch 'main' of github.com:koush/scrypted 2024-10-29 12:05:53 -07:00
Koushik Dutta
9d6b405fa9 postbeta 2024-10-29 12:05:45 -07:00
Koushik Dutta
b82ce5ff45 server: disable auto transferable Buffers 2024-10-29 12:05:33 -07:00
apocaliss92
f461198e1e reolink: battery params supported (#1621)
* Reolink battery params supported

* Style restored

* Battery check interval incresed

* Fix battery checks

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-28 18:36:17 -07:00
Jacob McSwain
7505e6907a feat(sdk): add support for notification channel in NotifierOptions (#1625)
* feat(sdk): add support for notification channel in NotifierOptions

This change spins off from conversation at https://github.com/scryptedapp/homeassistant/pull/1 and allows consumers of the `Notifier` interface to specify a channel for notifications to be sent to. Android platforms can use this to send notifications to a specific channel, allowing the user to have fine-grained control over the audio and priority of the notifications they receive.

* chore(sdk): place Android notification channel under the android object

* fix(automation-actions): remove channel from UI
2024-10-27 01:38:56 -05:00
apocaliss92
c1046d5706 HomeKit: Add flag to not autoenable devices on creation (#1626)
* Add flag to not autoenable devices on creation

* Invert naming logic

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-27 01:38:24 -05:00
Koushik Dutta
a61c06b607 remove dev site 2024-10-26 16:19:52 -05:00
Koushik Dutta
d3df5742e6 update index 2024-10-26 15:35:44 -05:00
Koushik Dutta
68ac42ca46 sdk: new dev site 2024-10-26 11:51:20 -05:00
apocaliss92
bb7c6ef8b9 reolink: Fix check for reolink nvrs (#1624)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-23 09:35:01 -05:00
apocaliss92
446e8ed61e reolink: add nvr flag (#1620)
* Reolink - Fix name/model fetching if homehub

* Checks removed

* style change removed

* Change logic for earlier returns

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-21 14:02:20 -07:00
Koushik Dutta
80372b35f2 npu: update drivers 2024-10-21 11:29:08 -07:00
Koushik Dutta
57eff2f296 Merge branch 'main' of github.com:koush/scrypted 2024-10-21 10:39:44 -07:00
Koushik Dutta
d996088041 sdk: fix deprecation on getCloudEndpoint 2024-10-21 10:39:40 -07:00
apocaliss92
04be70019b Reolink - Fix get ability (#1616)
* Reolink - Fix get ability

* getAbility current behavior kept

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-20 15:20:26 -07:00
Koushik Dutta
51732d0dcd proxmox: preserve hostname 2024-10-19 21:48:19 -07:00
Koushik Dutta
e40bc3ddee proxmox: preserve hostname 2024-10-19 21:38:07 -07:00
Koushik Dutta
3f4409e1c3 Merge branch 'main' of github.com:koush/scrypted 2024-10-19 21:26:23 -07:00
Koushik Dutta
63b7616ab3 proxmox: preserve mac address 2024-10-19 21:26:17 -07:00
apocaliss92
29059691ce Alexa: add option to not auto enable devices (#1615)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-19 16:10:42 -07:00
apocaliss92
531a9d28dc reolink: Reolink only-token client added (#1614)
* Reolink only-token client added

* Reolink auth fixes

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-19 15:06:35 -07:00
Koushik Dutta
3314b4d9ca reolink: publish 2024-10-18 20:30:40 -07:00
Koushik Dutta
37df9810c8 diagnostics: deprecate electron-core 2024-10-18 10:39:10 -07:00
Koushik Dutta
47c1cbba3c lxc-docker: shuffle volumes for faster resets 2024-10-18 09:53:52 -07:00
Koushik Dutta
ded7e549bb lxc-docker: link to docs for mount point specifics 2024-10-18 09:25:17 -07:00
Koushik Dutta
abb2b85cec lxc-docker: fix mp restore 2024-10-18 09:16:50 -07:00
Koushik Dutta
7d157d2882 lxc-docker: fix message line length 2024-10-18 09:14:01 -07:00
Koushik Dutta
c6c0a225dd lxc-docker: update messaging 2024-10-18 09:12:22 -07:00
Koushik Dutta
276fc386ec Merge branch 'main' of github.com:koush/scrypted 2024-10-18 09:08:15 -07:00
Koushik Dutta
0b21afd193 lxc-docker: handle moving volumes in migration 2024-10-18 09:08:10 -07:00
Koushik Dutta
1032e58e3b Update config.yaml 2024-10-17 21:06:52 -07:00
Koushik Dutta
4987b01167 Update package.json 2024-10-17 19:26:55 -07:00
Koushik Dutta
28bb8c5b3c proxmox: clarify docs 2024-10-17 14:15:44 -07:00
Koushik Dutta
2160170c3a proxmox: bump to lxc-docker 2024-10-17 14:06:21 -07:00
Koushik Dutta
c0eac9053b proxmox: restore warning 2024-10-17 13:58:46 -07:00
Koushik Dutta
d57501dd42 proxmox: fix restore 2024-10-17 13:53:34 -07:00
Koushik Dutta
264cb0404f proxmox: dont show existing container warning on restore 2024-10-17 13:41:40 -07:00
Koushik Dutta
dc9f4b39a8 proxmox: storage setup docs 2024-10-17 13:38:50 -07:00
Koushik Dutta
653eeceaf2 proxmox: stop container prior to restore. 2024-10-17 13:35:57 -07:00
Koushik Dutta
3d8711947a proxmox: detect force for better error handling 2024-10-17 13:33:02 -07:00
Koushik Dutta
38038d5f30 proxmox: script language cleanup 2024-10-17 13:28:59 -07:00
Koushik Dutta
e21d9c3a0c proxmox: additional installation options if container exists 2024-10-17 13:24:53 -07:00
Koushik Dutta
7b8c014b3b mac: remove libvips 2024-10-17 12:31:06 -07:00
Koushik Dutta
55a30864fd Revert "mac: update python to use whatever is latest"
This reverts commit 4f419ff75c.
2024-10-17 12:30:50 -07:00
Koushik Dutta
4f419ff75c mac: update python to use whatever is latest 2024-10-17 12:26:25 -07:00
Koushik Dutta
638a4f28ad postbeta 2024-10-17 12:09:53 -07:00
Koushik Dutta
8970154b8f lxc-docker: restore prune if pull is requested 2024-10-17 12:05:37 -07:00
Koushik Dutta
c96debaaed lxc-docker: wait 5 minutes prior to pruning 2024-10-17 12:04:46 -07:00
Koushik Dutta
fe7b479235 lxc-docker: use docker compose --pull as appropriate 2024-10-17 11:53:09 -07:00
Koushik Dutta
aa1486e641 postrelease 2024-10-17 11:15:41 -07:00
37 changed files with 1736 additions and 1176 deletions

3
.gitmodules vendored
View File

@@ -11,9 +11,6 @@
[submodule "external/werift"]
path = external/werift
url = ../../koush/werift-webrtc
[submodule "sdk/developer.scrypted.app"]
path = sdk/developer.scrypted.app
url = ../../koush/developer.scrypted.app
[submodule "plugins/sample-cameraprovider"]
path = plugins/sample-cameraprovider
url = ../../koush/scrypted-sample-cameraprovider

View File

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

View File

@@ -30,8 +30,8 @@ apt-get -y install intel-media-va-driver-non-free &&
apt-get -y dist-upgrade;
# manual installation
# https://github.com/intel/compute-runtime/releases/tag/24.13.29138.7
# https://github.com/intel/compute-runtime/releases/tag/24.35.30872.22
# these debs are seemingly ubuntu 22.04 only.
rm -rf /tmp/gpu && mkdir -p /tmp/gpu && cd /tmp/gpu

View File

@@ -7,7 +7,7 @@ fi
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
if [ -z "$UBUNTU_22_04" ]
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
then
# proxmox is compatible with ubuntu 22.04, check for /etc/pve directory
if [ -d "/etc/pve" ]
@@ -23,6 +23,13 @@ then
exit 0
fi
if [ -n "$UBUNTU_22_04" ]
then
distro="22.04_amd64"
else
distro="24.04_amd64"
fi
dpkg --purge --force-remove-reinstreq intel-driver-compiler-npu intel-fw-npu intel-level-zero-npu
# no errors beyond this point
@@ -30,27 +37,23 @@ set -e
rm -rf /tmp/npu && mkdir -p /tmp/npu && cd /tmp/npu
# different npu downloads for ubuntu versions
if [ -n "$UBUNTU_22_04" ]
then
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.8.0/intel-driver-compiler-npu_1.8.0.20240916-10885588273_ubuntu22.04_amd64.deb
# firmware can only be installed on host. will cause problems inside container.
if [ -n "$INTEL_FW_NPU" ]
then
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.8.0/intel-fw-npu_1.8.0.20240916-10885588273_ubuntu22.04_amd64.deb
fi
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.8.0/intel-level-zero-npu_1.8.0.20240916-10885588273_ubuntu22.04_amd64.deb
else
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.8.0/intel-driver-compiler-npu_1.8.0.20240916-10885588273_ubuntu24.04_amd64.deb
if [ -n "$INTEL_FW_NPU" ]
then
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.8.0/intel-fw-npu_1.8.0.20240916-10885588273_ubuntu24.04_amd64.deb
fi
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.8.0/intel-level-zero-npu_1.8.0.20240916-10885588273_ubuntu24.04_amd64.deb
fi
# level zero must also be installed
LEVEL_ZERO_VERSION=1.18.3
# https://github.com/oneapi-src/level-zero
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v"$LEVEL_ZERO_VERSION"/level-zero_"$LEVEL_ZERO_VERSION"+u$distro.deb
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v"$LEVEL_ZERO_VERSION"/level-zero-devel_"$LEVEL_ZERO_VERSION"+u$distro.deb
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v1.17.6/level-zero_1.17.6+u22.04_amd64.deb
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v1.17.6/level-zero-devel_1.17.6+u22.04_amd64.deb
# npu driver
# https://github.com/intel/linux-npu-driver
NPU_VERSION=1.8.0
NPU_VERSION_DATE=20240916-10885588273
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-driver-compiler-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
# firmware can only be installed on host. will cause problems inside container.
if [ -n "$INTEL_FW_NPU" ]
then
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-fw-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
fi
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-level-zero-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
apt -y update
apt -y install libtbb12

View File

@@ -40,8 +40,6 @@ echo "Installing Scrypted dependencies..."
RUN_IGNORE xcode-select --install
RUN brew update
RUN_IGNORE brew install node@20
# snapshot plugin and others
RUN brew install libvips
# dlib
RUN brew install cmake

View File

@@ -10,14 +10,15 @@ export DEBIAN_FRONTEND=noninteractive
if [ -e "volume/.pull" ]
then
rm -rf volume/.pull
docker compose pull && docker container prune -f && docker image prune -a -f
PULL="--pull"
(sleep 300 && docker container prune -f && docker image prune -a -f) &
else
# always background pull in case there's a broken image.
(docker compose pull && docker container prune -f && docker image prune -a -f) &
(sleep 300 && docker compose pull && docker container prune -f && docker image prune -a -f) &
fi
# do not daemonize, when it exits, systemd will restart it.
# force a recreate as .env may have changed.
# furthermore force recreate gets the container back into a known state
# which is preferable in case the user has made manual changes and then restarts.
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum | head -c 32) docker compose up --force-recreate --abort-on-container-exit
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum | head -c 32) docker compose up --force-recreate --abort-on-container-exit $PULL

View File

@@ -18,17 +18,30 @@ function readyn() {
}
cd /tmp
SCRYPTED_VERSION=v0.116.0
SCRYPTED_VERSION=v0.120.0
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
if [ -z "$VMID" ]
then
VMID=10443
fi
SCRYPTED_BACKUP_VMID=10445
if [ -n "$SCRYPTED_RESTORE" ]
then
pct config $VMID 2>&1 > /dev/null
if [ "$?" != "0" ]
then
echo "VMID $VMID not found."
exit 1
fi
# append existing mac address.
HWADDR=",hwaddr=$(pct config $VMID | grep -oE 'hwaddr=[A-Z0-9:]+' | cut -d '=' -f 2)"
RESTORE_HOSTNAME=$(pct config $VMID | grep -oE 'hostname: [^[:space:]]+' | cut -d ':' -f 2- | tr -d ' ')
pct destroy $SCRYPTED_BACKUP_VMID 2>&1 > /dev/null
RESTORE_VMID=$VMID
VMID=10444
VMID=$SCRYPTED_BACKUP_VMID
pct destroy $VMID 2>&1 > /dev/null
fi
@@ -39,18 +52,49 @@ then
mv scrypted.tar.zst $SCRYPTED_TAR_ZST
fi
echo "Checking for existing container."
pct config $VMID
if [ "$?" == "0" ]
if [[ "$@" =~ "--force" ]]
then
echo ""
echo "Existing container $VMID found. Run this script with --force to overwrite the existing container."
echo "This will wipe all existing data. Clone the existing container to retain the data, then reassign the owner of the scrypted volume after installation is complete."
echo ""
echo "bash $0 --force"
echo ""
IGNORE_EXISTING=true
fi
if [ -n "$SCRYPTED_RESTORE" ]
then
IGNORE_EXISTING=true
fi
if [ -z "$IGNORE_EXISTING" ]
then
echo "Checking for existing container."
pct config $VMID
if [ "$?" == "0" ]
then
echo ""
echo "==============================================================="
echo "Existing container $VMID found."
echo "Please choose from the following options to resolve this error."
echo "==============================================================="
echo ""
echo "1. To reinstall and reset Scrypted, run this script with --force to overwrite the existing container."
echo "THIS WILL WIPE THE EXISTING CONFIGURATION:"
echo ""
echo "VMID=$VMID bash $0 --force"
echo ""
echo "2. To reinstall Scrypted and and retain existing configuration, run this script with the environment variable SCRYPTED_RESTORE=true."
echo "This preserves existing data. Creating a backup within Scrypted is highly recommended in case the reset fails."
echo "THIS WILL WIPE ADDITIONAL VOLUMES SUCH AS NVR STORAGE. NVR volumes will need to be readded after the restore:"
echo ""
echo "SCRYPTED_RESTORE=true VMID=$VMID bash $0"
echo ""
echo "3. To install and run multiple Scrypted containers, run this script with the environment variable specifying"
echo "the new VMID=<number>. For example, to create a new LXC with VMID 12345:"
echo ""
echo "VMID=12345 bash $0"
exit 1
fi
fi
pct stop $VMID 2>&1 > /dev/null
pct restore $VMID $SCRYPTED_TAR_ZST $@
if [ "$?" != "0" ]
@@ -73,7 +117,7 @@ then
exit 1
fi
pct set $VMID -net0 name=eth0,bridge=vmbr0,ip=dhcp,ip6=auto
pct set $VMID -net0 name=eth0,bridge=vmbr0,ip=dhcp,ip6=auto$HWADDR
if [ "$?" != "0" ]
then
echo ""
@@ -82,6 +126,18 @@ then
echo "Ignoring... Please verify your container's network settings."
fi
if [ -n "$RESTORE_HOSTNAME" ]
then
pct set $VMID --hostname $RESTORE_HOSTNAME
if [ "$?" != "0" ]
then
echo ""
echo "pct hostname restore failed"
echo ""
echo "Ignoring... Please verify your container's dns settings."
fi
fi
CONF=/etc/pve/lxc/$VMID.conf
if [ -f "$CONF" ]
then
@@ -92,23 +148,89 @@ fi
if [ -n "$SCRYPTED_RESTORE" ]
then
readyn "Running this script will reset Scrypted to a factory state while preserving existing data. IT IS RECOMMENDED TO CREATE A BACKUP FIRST. Are you sure you want to continue?"
echo ""
echo ""
echo "Running this script will reset the Scrypted container to a factory state while preserving existing data."
echo "IT IS RECOMMENDED TO CREATE A BACKUP INSIDE SCRYPTED FIRST."
readyn "Are you sure you want to continue?"
if [ "$yn" != "y" ]
then
exit 1
fi
echo "Stopping scrypted..."
pct stop $RESTORE_VMID 2>&1 > /dev/null
echo "Preparing rootfs reset..."
# this copies the
pct set 10444 --delete mp0 && pct set 10444 --delete unused0 && pct move-volume $RESTORE_VMID mp0 --target-vmid 10444 --target-volume mp0
# remove the empty data volume from the downloaded image.
pct set $SCRYPTED_BACKUP_VMID --delete mp0 && pct set $SCRYPTED_BACKUP_VMID --delete unused0
if [ "$?" != "0" ]
then
echo "Failed to remove data volume from image."
exit 1
fi
# create a backup that contains only the root disk.
rm *.tar
vzdump 10444 --dumpdir /tmp
VMID=$RESTORE_VMID
echo "Moving data volume to backup..."
pct restore $VMID *.tar $@
vzdump $SCRYPTED_BACKUP_VMID --dumpdir /tmp
pct destroy 10444
# this moves the data volume from the current scrypted instance to the backup target to preserve it during
# the restore.
pct move-volume $RESTORE_VMID mp0 --target-vmid $SCRYPTED_BACKUP_VMID --target-volume mp0
if [ "$?" != "0" ]
then
echo "Failed to move data volume to backup."
exit 1
fi
# arguments: from to mp hide-warning
function move_volume() {
HAS_VOLUME=$(pct config $1 | grep $3:)
if [ -n "$HAS_VOLUME" ]
then
echo "Moving $3..."
# this may error and there may be recording loss. bailing at ths point is already too late.
pct move-volume $1 $3 --target-vmid $2 --target-volume $3
# volume must be inside /mnt to get into docker container
INSIDE_MNT=$(echo $HAS_VOLUME | grep /mnt)
if [ -z "$INSIDE_MNT" -a -z "$4" ]
then
echo "##################################################################"
echo "The following mount point is not visible to the"
echo "Scrypted docker container within the LXC:"
echo ""
echo "$HAS_VOLUME"
echo ""
echo "This recordings directory will be unavailable."
echo "The mount point must be updated to a path within /mnt."
echo "https://docs.scrypted.app/scrypted-nvr/recording-storage.html#proxmox-ve-mount-point"
echo "##################################################################"
fi
fi
}
# try moving 5 volumes, any more than that seems unlikely
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp1 hide-warning
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp2 hide-warning
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp3 hide-warning
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp4 hide-warning
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp5 hide-warning
VMID=$RESTORE_VMID
echo "Restoring with reset image..."
pct restore --force 1 $VMID *.tar $@
echo "Restoring volumes..."
move_volume $SCRYPTED_BACKUP_VMID $VMID mp0 hide-warning
move_volume $SCRYPTED_BACKUP_VMID $VMID mp1
move_volume $SCRYPTED_BACKUP_VMID $VMID mp2
move_volume $SCRYPTED_BACKUP_VMID $VMID mp3
move_volume $SCRYPTED_BACKUP_VMID $VMID mp4
move_volume $SCRYPTED_BACKUP_VMID $VMID mp5
pct destroy $SCRYPTED_BACKUP_VMID
fi
readyn "Add udev rule for hardware acceleration? This may conflict with existing rules."
@@ -143,4 +265,5 @@ then
fi
echo "Scrypted setup is complete and the container resources can be started."
echo "Scrypted NVR users should provide at least 4 cores and 16GB RAM prior to starting."
echo ""
echo "Scrypted NVR servers should run the disk setup script in the documentation to add storage prior to starting the container."

View File

@@ -1,12 +1,11 @@
{
"name": "@scrypted/alexa",
"version": "0.3.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/alexa",
"version": "0.3.3",
"version": "0.3.4",
"dependencies": {
"axios": "^1.3.4",
"uuid": "^9.0.0"
@@ -203,4 +202,4 @@
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/alexa",
"version": "0.3.3",
"version": "0.3.4",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",

View File

@@ -53,6 +53,12 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
title: "Pairing Key",
description: "The pairing key used to validate requests from Alexa. Clear this key or delete the plugin to allow pairing with a different Alexa login.",
},
disableAutoAdd: {
title: "Disable auto add",
description: "Disable automatic enablement of devices.",
type: 'boolean',
defaultValue: false,
},
});
accessToken: Promise<string>;
@@ -116,6 +122,10 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
if (!supportedTypes.has(device.type))
return DeviceMixinStatus.NotSupported;
if (this.storageSettings.values.disableAutoAdd) {
return DeviceMixinStatus.Skip;
}
mixins.push(this.id);
const plugins = await systemManager.getComponent('plugins');
@@ -671,7 +681,8 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
enum DeviceMixinStatus {
NotSupported = 0,
Setup = 1,
AlreadySetup = 2
AlreadySetup = 2,
Skip = 3,
}
class HttpResponseLoggingImpl implements AlexaHttpResponse {

View File

@@ -13,7 +13,7 @@
"devDependencies": {
"@types/node": "^22.5.4"
},
"version": "0.0.17"
"version": "0.0.18"
},
"../../common": {
"name": "@scrypted/common",
@@ -844,5 +844,5 @@
"dev": true
}
},
"version": "0.0.17"
"version": "0.0.18"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/diagnostics",
"version": "0.0.17",
"version": "0.0.18",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",

View File

@@ -128,6 +128,9 @@ class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings {
await device.sendNotification('Scrypted Diagnostics', {
body: 'Body',
subtitle: 'Subtitle',
android: {
channel: 'diagnostics',
}
}, mo);
this.warnStep(console, 'Check the device for the notification.');
@@ -512,6 +515,7 @@ class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings {
await this.validate(this.console, 'Deprecated Plugins', async () => {
const defunctPlugins = [
'@scrypted/electron-core',
'@scrypted/opencv',
'@scrypted/python-codecs',
'@scrypted/pam-diff',

View File

@@ -111,6 +111,12 @@ export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider,
hide: true,
description: 'The last home hub to request a recording. Internally used to determine if a streaming request is coming from remote wifi.',
},
autoAdd: {
title: "Auto enable",
description: "Automatically enable this mixin on new devices.",
type: 'boolean',
defaultValue: true,
},
});
mergedDevices = new Set<string>();
@@ -218,7 +224,8 @@ export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider,
try {
const mixins = (device.mixins || []).slice();
if (!mixins.includes(this.id)) {
const autoAdd = this.storageSettings.values.autoAdd ?? true;
if (!mixins.includes(this.id) && autoAdd) {
// don't sync this by default, as it's solely for automations
if (device.type === ScryptedDeviceType.Notifier)
continue;

View File

@@ -1,3 +1,3 @@
{
"scrypted.debugHost": "127.0.0.1",
"scrypted.debugHost": "scrypted-nvr",
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/prebuffer-mixin",
"version": "0.10.36",
"version": "0.10.37",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/prebuffer-mixin",
"version": "0.10.36",
"version": "0.10.37",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/prebuffer-mixin",
"version": "0.10.36",
"version": "0.10.37",
"description": "Video Stream Rebroadcast, Prebuffer, and Management Plugin for Scrypted.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -196,13 +196,33 @@ class PrebufferSession {
return;
this.console.log(this.streamName, 'prebuffer session started');
this.parserSessionPromise = this.startPrebufferSession();
this.parserSessionPromise.then(pso => pso.killed.finally(() => {
this.console.error(this.streamName, 'prebuffer session ended');
this.parserSessionPromise = undefined;
}))
let active = false;
this.parserSessionPromise.then(pso => {
pso.once('rtsp', () => {
active = true;
if (!this.mixin.online)
this.mixin.online = true;
});
pso.killed.finally(() => {
this.console.error(this.streamName, 'prebuffer session ended');
this.parserSessionPromise = undefined;
});
})
.catch(e => {
this.console.error(this.streamName, 'prebuffer session ended with error', e);
this.parserSessionPromise = undefined;
if (!active) {
// find sessions that arent this one, and check their prebuffers to see if any data has been received.
// if there's no data, then consider this camera offline.
const others = [...this.mixin.sessions.values()].filter(s => s !== this);
if (others.length) {
const hasData = others.some(s => s.rtspPrebuffer.length);
if (!hasData && this.mixin.online)
this.mixin.online = false;
}
}
});
}
@@ -1415,11 +1435,6 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
}
}
if (!enabledIds.length)
this.online = true;
let active = 0;
// figure out the default stream and streams that may have been removed due to
// a config change.
const toRemove = new Set(this.sessions.keys());
@@ -1462,23 +1477,13 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
}
session.ensurePrebufferSession();
let wasActive = false;
try {
this.console.log(name, 'prebuffer session starting');
const ps = await session.parserSessionPromise;
active++;
wasActive = true;
this.online = !!active;
await ps.killed;
}
catch (e) {
}
finally {
if (wasActive)
active--;
wasActive = false;
this.online = !!active;
}
this.console.log(this.name, 'restarting prebuffer session in 5 seconds');
await new Promise(resolve => setTimeout(resolve, 5000));
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/reolink",
"version": "0.0.96",
"version": "0.0.98",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/reolink",
"version": "0.0.96",
"version": "0.0.98",
"license": "Apache",
"dependencies": {
"@scrypted/common": "file:../../common",
@@ -35,7 +35,7 @@
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.65",
"version": "0.3.67",
"dev": true,
"license": "ISC",
"dependencies": {

View File

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

View File

@@ -52,11 +52,13 @@ class ReolinkCameraSiren extends ScryptedDeviceBase implements OnOff {
class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, Reboot, Intercom, ObjectDetector, PanTiltZoom {
client: ReolinkCameraClient;
clientWithToken: ReolinkCameraClient;
onvifClient: OnvifCameraAPI;
onvifIntercom = new OnvifIntercom(this);
videoStreamOptions: Promise<UrlMediaStreamOptions[]>;
motionTimeout: NodeJS.Timeout;
siren: ReolinkCameraSiren;
batteryTimeout: NodeJS.Timeout;
storageSettings = new StorageSettings(this, {
doorbell: {
@@ -180,6 +182,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
}
const api = this.getClient();
const deviceInfo = await api.getDeviceInfo();
this.console.log('deviceInfo', JSON.stringify(deviceInfo));
this.storageSettings.values.deviceInfo = deviceInfo;
await this.updateAbilities();
await this.updateDevice();
@@ -217,7 +220,13 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
async updateAbilities() {
const api = this.getClient();
const abilities = await api.getAbility();
const apiWithToken = this.getClientWithToken();
let abilities;
try {
abilities = await api.getAbility();
} catch (e) {
abilities = await apiWithToken.getAbility();
}
this.storageSettings.values.abilities = abilities;
this.console.log('getAbility', JSON.stringify(abilities));
}
@@ -286,6 +295,11 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
&& this.storageSettings.values.abilities?.value?.Ability?.supportAudioAlarm?.ver !== 0;
}
hasBattery() {
const batteryConfigVer = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[this.getRtspChannel()]?.battery?.ver ?? 0;
return batteryConfigVer > 0;
}
async updateDevice() {
const interfaces = this.provider.getInterfaces();
let type = ScryptedDeviceType.Camera;
@@ -311,8 +325,31 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
}
if (this.hasSiren())
interfaces.push(ScryptedInterface.DeviceProvider);
if (this.hasBattery()) {
interfaces.push(ScryptedInterface.Battery, ScryptedInterface.Online);
this.startBatteryCheckInterval();
}
await this.provider.updateDevice(this.nativeId, name, interfaces, type);
await this.provider.updateDevice(this.nativeId, this.name ?? name, interfaces, type);
}
startBatteryCheckInterval() {
if (this.batteryTimeout) {
clearInterval(this.batteryTimeout);
}
this.batteryTimeout = setInterval(async () => {
const api = this.getClientWithToken();
try {
const { batteryPercent, sleep } = await api.getBatteryInfo();
this.batteryLevel = batteryPercent;
this.online = !sleep;
}
catch (e) {
this.console.log('Error in getting battery info', e);
}
}, 1000 * 60 * 30);
}
async reboot() {
@@ -341,6 +378,12 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
return this.client;
}
getClientWithToken() {
if (!this.clientWithToken)
this.clientWithToken = new ReolinkCameraClient(this.getHttpAddress(), this.getUsername(), this.getPassword(), this.getRtspChannel(), this.console, true);
return this.clientWithToken;
}
async getOnvifClient() {
if (!this.onvifClient)
this.onvifClient = await this.createOnvifClient();
@@ -612,7 +655,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
// 1: support main/extern/sub stream
// 2: support main/sub stream
const live = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[0].live?.ver;
const live = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[this.getRtspChannel()].live?.ver;
const [rtmpMain, rtmpExt, rtmpSub, rtspMain, rtspSub] = streams;
streams.splice(0, streams.length);
@@ -621,7 +664,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
// 1: main stream enc type is H265
// anecdotally, encoders of type h265 do not have a working RTMP main stream.
const mainEncType = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[0].mainEncType?.ver;
const mainEncType = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[this.getRtspChannel()].mainEncType?.ver;
if (live === 2) {
if (mainEncType === 1) {
@@ -790,6 +833,7 @@ class ReolinkProvider extends RtspProvider {
const rtspChannel = parseInt(settings.rtspChannel?.toString()) || 0;
if (!skipValidate) {
const api = new ReolinkCameraClient(httpAddress, username, password, rtspChannel, this.console);
const apiWithToken = new ReolinkCameraClient(httpAddress, username, password, rtspChannel, this.console, true);
try {
await api.jpegSnapshot();
}
@@ -803,7 +847,11 @@ class ReolinkProvider extends RtspProvider {
doorbell = deviceInfo.type === 'BELL';
name = deviceInfo.name ?? 'Reolink Camera';
ai = await api.getAiState();
abilities = await api.getAbility();
try {
abilities = await api.getAbility();
} catch (e) {
abilities = await apiWithToken.getAbility();
}
}
catch (e) {
this.console.error('Reolink camera does not support AI events', e);

View File

@@ -46,25 +46,27 @@ async function getDeviceInfo(host: string, username: string, password: string):
return response.body?.[0]?.value?.DevInfo;
}
export async function getLoginParameters(host: string, username: string, password: string) {
try {
await getDeviceInfo(host, username, password);
return {
parameters: {
user: username,
password,
},
leaseTimeSeconds: Infinity,
export async function getLoginParameters(host: string, username: string, password: string, forceToken?: boolean) {
if (!forceToken) {
try {
await getDeviceInfo(host, username, password);
return {
parameters: {
user: username,
password,
},
leaseTimeSeconds: Infinity,
}
}
catch (e) {
}
}
catch (e) {
}
try {
const url = new URL(`http://${host}/api.cgi`);
const params = url.searchParams;
params.set('cmd', 'Login');
const response = await httpFetch({
url,
method: 'POST',
@@ -83,7 +85,7 @@ export async function getLoginParameters(host: string, username: string, passwor
},
],
});
const token = response.body?.[0]?.value?.Token?.name || response.body?.value?.Token?.name;
if (!token)
throw new Error('unable to login');

View File

@@ -49,7 +49,7 @@ export class ReolinkCameraClient {
parameters: Record<string, string>;
tokenLease: number;
constructor(public host: string, public username: string, public password: string, public channelId: number, public console: Console) {
constructor(public host: string, public username: string, public password: string, public channelId: number, public console: Console, public readonly forceToken?: boolean) {
this.credential = {
username,
password,
@@ -80,7 +80,7 @@ export class ReolinkCameraClient {
this.console.log(`token expired at ${this.tokenLease}, renewing...`);
const { parameters, leaseTimeSeconds } = await getLoginParameters(this.host, this.username, this.password);
const { parameters, leaseTimeSeconds } = await getLoginParameters(this.host, this.username, this.password, this.forceToken);
this.parameters = parameters
this.tokenLease = Date.now() + 1000 * leaseTimeSeconds;
}
@@ -153,15 +153,37 @@ export class ReolinkCameraClient {
const params = url.searchParams;
params.set('cmd', 'GetAbility');
params.set('channel', this.channelId.toString());
const response = await this.requestWithLogin({
let response = await this.requestWithLogin({
url,
responseType: 'json',
});
const error = response.body?.[0]?.error;
let error = response.body?.[0]?.error;
if (error) {
this.console.error('error during call to getAbility', error);
throw new Error('error during call to getAbility');
this.console.error('error during call to getAbility GET, Trying with POST', error);
url.search = '';
const body = [
{
cmd: "GetAbility",
action: 0,
param: { User: { userName: this.username } }
}
];
response = await this.requestWithLogin({
url,
responseType: 'json',
method: 'POST',
}, this.createReadable(body));
error = response.body?.[0]?.error;
if (error) {
this.console.error('error during call to getAbility GET, Trying with POST', error);
throw new Error('error during call to getAbility');
}
}
return {
value: response.body?.[0]?.value || response.body?.value,
data: response.body,
@@ -210,7 +232,46 @@ export class ReolinkCameraClient {
this.console.error('error during call to getDeviceInfo', error);
throw new Error('error during call to getDeviceInfo');
}
return response.body?.[0]?.value?.DevInfo;
const deviceInfo: DevInfo = await response.body?.[0]?.value?.DevInfo;
// Will need to check if it's valid for NVR and NVR_WIFI
if (!['HOMEHUB', 'NVR', 'NVR_WIFI'].includes(deviceInfo.exactType)) {
return deviceInfo;
}
// If the device is listed as homehub, fetch the channel specific information
url.search = '';
const body = [
{ cmd: "GetChnTypeInfo", action: 0, param: { channel: this.channelId } },
{ cmd: "GetChannelstatus", action: 0, param: {} },
]
const additionalInfoResponse = await this.requestWithLogin({
url,
method: 'POST',
responseType: 'json'
}, this.createReadable(body));
const chnTypeInfo = additionalInfoResponse?.body?.find(elem => elem.cmd === 'GetChnTypeInfo');
const chnStatus = additionalInfoResponse?.body?.find(elem => elem.cmd === 'GetChannelstatus');
if (chnTypeInfo?.value) {
deviceInfo.firmVer = chnTypeInfo.value.firmVer;
deviceInfo.model = chnTypeInfo.value.typeInfo;
deviceInfo.pakSuffix = chnTypeInfo.value.pakSuffix;
}
if (chnStatus?.value) {
const specificChannelStatus = chnStatus.value?.status?.find(elem => elem.channel === this.channelId);
if (specificChannelStatus) {
deviceInfo.name = specificChannelStatus.name;
}
}
return deviceInfo;
}
async getPtzPresets(): Promise<PtzPreset[]> {
@@ -369,4 +430,39 @@ export class ReolinkCameraClient {
data: response.body,
};
}
async getBatteryInfo() {
const url = new URL(`http://${this.host}/api.cgi`);
const body = [
{
cmd: "GetBatteryInfo",
action: 0,
param: { channel: this.channelId }
},
{
cmd: "GetChannelstatus",
}
];
const response = await this.requestWithLogin({
url,
responseType: 'json',
method: 'POST',
}, this.createReadable(body));
const error = response.body?.find(elem => elem.error)?.error;
if (error) {
this.console.error('error during call to getBatteryInfo', error);
}
const batteryInfoEntry = response.body.find(entry => entry.cmd === 'GetBatteryInfo')?.value?.Battery;
const channelStatusEntry = response.body.find(entry => entry.cmd === 'GetChannelstatus')?.value?.status
?.find(chStatus => chStatus.channel === this.channelId)
return {
batteryPercent: batteryInfoEntry?.batteryPercent,
sleep: channelStatusEntry?.sleep === 1,
}
}
}

View File

@@ -1,4 +1,4 @@
{
"scrypted.debugHost": "127.0.0.1",
"scrypted.debugHost": "scrypted-nvr",
}

View File

@@ -1,213 +1 @@
# Table of Contents
* [Getting Started](#getting-started)
* [Typescript Sample Setup](#typescript-sample-setup)
* [Creating a Switch](#creating-a-switch)
* [Core Concepts](#core-concepts)
* [Interfaces](#interfaces)
* [Events](#events)
* [Creating Multiple Devices](#creating-multiple-devices)
* [Full Reference](/modules)
* [Sample Plugins](https://github.com/koush/scrypted/tree/main/plugins)
* [Camera Provider Sample](https://github.com/koush/scrypted-sample-cameraprovider)
<br/>
<br/>
# Getting Started
The quickest way to get started is to check out the the [Typescript sample](https://github.com/koush/scrypted-vscode-typescript) and open it in Visual Studio Code. The setup instructions can be found in the readme for the [project](https://github.com/koush/scrypted-vscode-typescript).
<br/>
## Typescript Sample Setup
These instructions can be followed on your preferred development machine, and do not need to be run on the Scrypted Server itself. The Scrypted SDK can deploy and **debug** plugins running on a remote server. For example, the VS Code development environment can be running on a Mac, while the server is running on a Raspberry Pi.
1. npm install
2. Open this plugin director yin VS Code.
3. Edit `.vscode/settings.json` to point to the IP address of your Scrypted server. The default is `127.0.0.1`, your local machine.
4. Press Launch (green arrow button in the Run and Debug sidebar) to start debugging.
* The VS Code `Terminal` area may show an authentication failure and prompt you to log in to the Scrypted Management Console with `npx scrypted login`. You will only need to do this once. You can then relaunch afterwards.
<p align="center">
<img width="538" alt="image" src="https://user-images.githubusercontent.com/73924/151676616-c730eb56-26dd-466d-b7f5-25783300b3bc.png">
</p>
<br/>
## Creating a Switch
The aforementioned sample will create a single switch device.
```typescript
import axios from 'axios';
import { OnOff, ScryptedDeviceBase } from '@scrypted/sdk';
console.log('Hello World. This will create a virtual OnOff device.');
// OnOff is a simple binary switch. See "interfaces" in package.json
// to add support for more capabilities, like Brightness or Lock.
class TypescriptLight extends ScryptedDeviceBase implements OnOff {
constructor() {
super();
this.on = this.on || false;
}
async turnOff() {
this.console.log('turnOff was called!');
this.on = false;
}
async turnOn() {
// set a breakpoint here.
this.console.log('turnOn was called!');
this.console.log("Let's pretend to perform a web request on an API that would turn on a light.");
const ip = await axios.get('http://jsonip.com');
this.console.log(`my ip: ${ip.data.ip}`);
this.on = true;
}
}
export default TypescriptLight;
```
<br/>
<br/>
# Core Concepts
Devices the core entry points and objects within Scrypted. A device can be a physical device, a virtual device, a provider of other devices (like a hub), a webhook, etc. Devices have two primary properties: Interfaces and Events.
<br/>
## Interfaces
Interfaces are how devices expose their capabilities to Scrypted. An OnOff interface represents a binary switch. The Brightness interface represents a light that can be dimmed. The ColorSettingRgb interface indicates the light can change color. A device may expose multiple different interfaces to describe its functionality.
For example, given the following devices, the interfaces they would implement:
Outlet: OnOff,
Dimmer Switch: OnOff, Brightness,
Color Bulb: OnOff, Brightness, ColorSettingRgb
Interfaces aren't only used represent characteristics of physical devices. As mentioned, they provide ways to hook into Scrypted. The HttpRequestHandler lets you add a web hook to handle incoming web requests. EventListener lets you create handlers that respond to events. DeviceProvider acts as a controller platform (like Hue or Lifx) for exposing multiple other devices to Scrypted.
Interfaces also provide a way to query the device state. Such as checking whether an outlet is on or off, the current brightness level, or the current color.
```typescript
// Interfaces describe how the current state of a device, and can be used to modify that state.
if (light.on) {
light.turnOff();
}
else {
light.turnOn();
}
```
<br/>
## Events
Scrypted maintains the state of all connected devices. Whenever the state of an interface is updated on a device, an Event will be triggered for that particular interface.
For example, when a light turns on, the Light device would send an OnOff event. If a Slack message is received, the Slack device would send a MessagingEndpoint event. Setting a schedule for sunrise on weekdays would send an Alarm event on that schedule.
Automations subscribe to these events in your smart home setup and react accordingly.
```
// Events are triggered by the device on update, and can be observed.
light.listen('OnOff', (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: object) => {
if (eventData) {
log.i('The light was turned on.');
}
else {
log.i('The light was turned off.');
}
});
```
<br/>
<br/>
# Creating Multiple Devices
Most plugins will want to create multiple devices. This is done by implementing the DeviceProvider interface.
To do this, thep project `package.json` needs to update the `scrypted` section that describes the plugin:
```json
"scrypted": {
"name": "TypeScript Light Provider",
"type": "DeviceProvider",
"interfaces": [
"DeviceProvider"
]
},
```
Then, the code is updated to support multiple lights:
```typescript
import axios from 'axios';
import sdk, { DeviceProvider, OnOff, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface } from '@scrypted/sdk';
class TypescriptLight extends ScryptedDeviceBase implements OnOff {
constructor(nativeId?: string) {
super(nativeId);
this.on = this.on || false;
}
async turnOff() {
this.console.log('turnOff was called!');
this.on = false;
}
async turnOn() {
// set a breakpoint here.
this.console.log('turnOn was called!');
this.console.log("Let's pretend to perform a web request on an API that would turn on a light.");
const ip = await axios.get('http://jsonip.com');
this.console.log(`my ip: ${ip.data.ip}`);
this.on = true;
}
}
class MyDeviceProvider extends ScryptedDeviceBase implements DeviceProvider {
constructor(nativeId?: string) {
super(nativeId);
this.prepareDevices();
}
async prepareDevices() {
// "Discover" the lights provided by this provider to Scrypted.
await sdk.deviceManager.onDevicesChanged({
devices: [
{
// the native id is the unique identifier for this light within
// your plugin.
nativeId: 'light1',
name: 'Light 1',
type: ScryptedDeviceType.Light,
interfaces: [
ScryptedInterface.OnOff,
]
},
{
nativeId: 'light2',
name: 'Light 1',
type: ScryptedDeviceType.Light,
interfaces: [
ScryptedInterface.OnOff,
]
}
]
});
}
// After the lights are discovered, Scrypted will request the plugin create the
// instance that can be used to control and query the light.
getDevice(nativeId: string) {
return new TypescriptLight(nativeId);
}
}
// Export the provider from the plugin, rather than the individual light.
export default MyDeviceProvider;
```
Running the sample will then create 3 devices: the plugin/hub and the 2 lights it controls.
[Scrypted SDK](https://developer.scrypted.app)

1915
sdk/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/sdk",
"version": "0.3.67",
"version": "0.3.68",
"description": "",
"main": "dist/src/index.js",
"exports": {
@@ -12,8 +12,6 @@
"scripts": {
"prepublishOnly": "npm run build && cd types && npm version patch && npm publish",
"prebuild": "cd types && npm run build",
"predocs": "npm run build",
"docs": "typedoc && cp developer.scrypted.app/CNAME developer.scrypted.app/docs",
"build": "rimraf dist && tsc",
"webpack": "webpack-cli --config webpack.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
@@ -30,10 +28,10 @@
"author": "",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.24.7",
"adm-zip": "^0.5.14",
"axios": "^1.7.3",
"babel-loader": "^9.1.3",
"@babel/preset-typescript": "^7.26.0",
"adm-zip": "^0.5.16",
"axios": "^1.7.7",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
@@ -41,15 +39,15 @@
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"typescript": "^5.5.4",
"webpack": "^5.93.0",
"webpack": "^5.95.0",
"webpack-bundle-analyzer": "^4.10.2"
},
"devDependencies": {
"@types/node": "^22.1.0",
"@types/node": "^22.8.1",
"@types/stringify-object": "^4.0.5",
"stringify-object": "^3.3.0",
"ts-node": "^10.9.2",
"typedoc": "^0.26.5"
"typedoc": "^0.26.10"
},
"types": "dist/src/index.d.ts"
}

View File

@@ -1,23 +0,0 @@
{
"entryPoints": [
"./src"
],
"sort": ["source-order"],
"name": "Scrypted Documentation",
"tsconfig": "./tsconfig.json",
"out": "./developer.scrypted.app/docs",
"categorizeByGroup": false,
"defaultCategory": "Device Interfaces Reference",
"excludePrivate": true,
"disableSources": true,
"categoryOrder": [
"Core Reference",
"Device Provider Reference",
"Media Reference",
"Webhook and Push Reference",
"Mixin Reference",
"WebRTC Reference"
],
"customCss": "./developer.scrypted.app/docs.css",
"readme": "./README.md"
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/types",
"version": "0.3.62",
"version": "0.3.63",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/types",
"version": "0.3.62",
"version": "0.3.63",
"license": "ISC",
"devDependencies": {
"@types/node": "^22.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/types",
"version": "0.3.62",
"version": "0.3.63",
"description": "",
"main": "dist/index.js",
"author": "",

View File

@@ -71,8 +71,8 @@ class PanTiltZoomMovement(str, Enum):
class ScryptedDeviceType(str, Enum):
API = "API"
AirPurifier = "AirPurifier"
API = "API"
Automation = "Automation"
Builtin = "Builtin"
Camera = "Camera"
@@ -117,9 +117,9 @@ class ScryptedInterface(str, Enum):
BinarySensor = "BinarySensor"
Brightness = "Brightness"
BufferConverter = "BufferConverter"
CO2Sensor = "CO2Sensor"
Camera = "Camera"
Charger = "Charger"
CO2Sensor = "CO2Sensor"
ColorSettingHsv = "ColorSettingHsv"
ColorSettingRgb = "ColorSettingRgb"
ColorSettingTemperature = "ColorSettingTemperature"
@@ -147,8 +147,8 @@ class ScryptedInterface(str, Enum):
Microphone = "Microphone"
MixinProvider = "MixinProvider"
MotionSensor = "MotionSensor"
NOXSensor = "NOXSensor"
Notifier = "Notifier"
NOXSensor = "NOXSensor"
OauthClient = "OauthClient"
ObjectDetection = "ObjectDetection"
ObjectDetectionGenerator = "ObjectDetectionGenerator"
@@ -156,22 +156,22 @@ class ScryptedInterface(str, Enum):
ObjectDetector = "ObjectDetector"
ObjectTracker = "ObjectTracker"
OccupancySensor = "OccupancySensor"
OnOff = "OnOff"
Online = "Online"
PM10Sensor = "PM10Sensor"
PM25Sensor = "PM25Sensor"
OnOff = "OnOff"
PanTiltZoom = "PanTiltZoom"
PasswordStore = "PasswordStore"
Pause = "Pause"
PM10Sensor = "PM10Sensor"
PM25Sensor = "PM25Sensor"
PositionSensor = "PositionSensor"
PowerSensor = "PowerSensor"
Program = "Program"
PushHandler = "PushHandler"
RTCSignalingChannel = "RTCSignalingChannel"
RTCSignalingClient = "RTCSignalingClient"
Readme = "Readme"
Reboot = "Reboot"
Refresh = "Refresh"
RTCSignalingChannel = "RTCSignalingChannel"
RTCSignalingClient = "RTCSignalingClient"
Scene = "Scene"
Scriptable = "Scriptable"
ScryptedDevice = "ScryptedDevice"
@@ -185,13 +185,12 @@ class ScryptedInterface(str, Enum):
Settings = "Settings"
StartStop = "StartStop"
StreamService = "StreamService"
TTY = "TTY"
TTYSettings = "TTYSettings"
TamperSensor = "TamperSensor"
TemperatureSetting = "TemperatureSetting"
Thermometer = "Thermometer"
TTY = "TTY"
TTYSettings = "TTYSettings"
UltravioletSensor = "UltravioletSensor"
VOCSensor = "VOCSensor"
VideoCamera = "VideoCamera"
VideoCameraConfiguration = "VideoCameraConfiguration"
VideoCameraMask = "VideoCameraMask"
@@ -199,6 +198,7 @@ class ScryptedInterface(str, Enum):
VideoFrameGenerator = "VideoFrameGenerator"
VideoRecorder = "VideoRecorder"
VideoRecorderManagement = "VideoRecorderManagement"
VOCSensor = "VOCSensor"
class ScryptedMimeTypes(str, Enum):
@@ -211,11 +211,11 @@ class ScryptedMimeTypes(str, Enum):
MediaStreamFeedback = "x-scrypted/x-media-stream-feedback"
MediaStreamUrl = "text/x-media-url"
PushEndpoint = "text/x-push-endpoint"
RequestMediaObject = "x-scrypted/x-scrypted-request-media-object"
RequestMediaStream = "x-scrypted/x-scrypted-request-stream"
RTCConnectionManagement = "x-scrypted/x-scrypted-rtc-connection-management"
RTCSignalingChannel = "x-scrypted/x-scrypted-rtc-signaling-channel"
RTCSignalingSession = "x-scrypted/x-scrypted-rtc-signaling-session"
RequestMediaObject = "x-scrypted/x-scrypted-request-media-object"
RequestMediaStream = "x-scrypted/x-scrypted-request-stream"
SchemePrefix = "x-scrypted/x-scrypted-scheme-"
ServerId = "text/x-server-id"
Url = "text/x-uri"
@@ -543,20 +543,6 @@ class EventListenerOptions(TypedDict):
mixinId: str # The EventListener will listen to events and property changes from a device or mixin that is suppressed by a mixin.
watch: bool # This EventListener will passively watch for events, and not initiate polling.
class FFmpegInput(TypedDict):
container: str
destinationVideoBitrate: float
env: Any # Environment variables to set when launching FFmpeg.
ffmpegPath: str # Path to a custom FFmpeg binary.
h264EncoderArguments: list[str]
h264FilterArguments: list[str]
inputArguments: list[str]
mediaStreamOptions: ResponseMediaStreamOptions
url: str # The media url for this FFmpegInput.
urls: list[str] # Alternate media urls for this FFmpegInput.
videoDecoderArguments: list[str]
class FanState(TypedDict):
counterClockwise: bool
@@ -574,6 +560,20 @@ class FanStatus(TypedDict):
speed: float # Rotations per minute, if available, otherwise 0 or 1.
swing: bool
class FFmpegInput(TypedDict):
container: str
destinationVideoBitrate: float
env: Any # Environment variables to set when launching FFmpeg.
ffmpegPath: str # Path to a custom FFmpeg binary.
h264EncoderArguments: list[str]
h264FilterArguments: list[str]
inputArguments: list[str]
mediaStreamOptions: ResponseMediaStreamOptions
url: str # The media url for this FFmpegInput.
urls: list[str] # Alternate media urls for this FFmpegInput.
videoDecoderArguments: list[str]
class HttpRequest(TypedDict):
aclId: str
@@ -643,9 +643,13 @@ class MediaStreamOptions(TypedDict):
tool: MediaStreamTool # The tool was used to write the container or will be used to read teh container. Ie, scrypted, the ffmpeg tools, gstreamer.
video: VideoStreamOptions
class AndroidNotificationOptions(TypedDict):
channel: str
class NotifierOptions(TypedDict):
actions: list[NotificationAction]
android: AndroidNotificationOptions
badge: str
body: str
bodyWithSubtitle: str
@@ -982,10 +986,6 @@ class BufferConverter:
pass
class CO2Sensor:
co2ppm: float
class Camera:
"""Camera devices can take still photos."""
@@ -1001,6 +1001,10 @@ class Charger:
chargeState: ChargeState
class CO2Sensor:
co2ppm: float
class ColorSettingHsv:
"""ColorSettingHsv sets the color of a colored light using the HSV representation."""
@@ -1218,10 +1222,6 @@ class MotionSensor:
motionDetected: bool
class NOXSensor:
noxDensity: float
class Notifier:
"""Notifier can be any endpoint that can receive messages, such as speakers, phone numbers, messaging clients, etc. The messages may optionally contain media."""
@@ -1229,6 +1229,10 @@ class Notifier:
pass
class NOXSensor:
noxDensity: float
class OauthClient:
"""The OauthClient can be implemented to perform the browser based Oauth process from within a plugin."""
@@ -1283,6 +1287,11 @@ class OccupancySensor:
occupied: bool
class Online:
"""Online denotes whether the device is online or unresponsive. It may be unresponsive due to being unplugged, network error, etc."""
online: bool
class OnOff:
"""OnOff is a basic binary switch."""
@@ -1294,19 +1303,6 @@ class OnOff:
pass
class Online:
"""Online denotes whether the device is online or unresponsive. It may be unresponsive due to being unplugged, network error, etc."""
online: bool
class PM10Sensor:
pm10Density: float
class PM25Sensor:
pm25Density: float
class PanTiltZoom:
ptzCapabilities: PanTiltZoomCapabilities
@@ -1337,6 +1333,14 @@ class Pause:
pass
class PM10Sensor:
pm10Density: float
class PM25Sensor:
pm25Density: float
class PositionSensor:
position: Position
@@ -1510,19 +1514,6 @@ class StreamService:
pass
class TTY:
"""TTY connection offered by a remote device that can be connected to by an interactive terminal interface. Implementors should also implement StreamService to handle the actual data transfer."""
pass
class TTYSettings:
"""TTYSettings allows TTY backends to query plugins for modifications to the (non-)interactive terminal environment."""
async def getTTYSettings(self) -> Any:
pass
class TamperSensor:
tampered: TamperState
@@ -1543,14 +1534,23 @@ class Thermometer:
pass
class TTY:
"""TTY connection offered by a remote device that can be connected to by an interactive terminal interface. Implementors should also implement StreamService to handle the actual data transfer."""
pass
class TTYSettings:
"""TTYSettings allows TTY backends to query plugins for modifications to the (non-)interactive terminal environment."""
async def getTTYSettings(self) -> Any:
pass
class UltravioletSensor:
ultraviolet: float
class VOCSensor:
vocDensity: float
class VideoCamera:
"""VideoCamera devices can capture video streams."""
@@ -1581,10 +1581,10 @@ class VideoClips:
async def getVideoClip(self, videoId: str) -> MediaObject:
pass
async def getVideoClipThumbnail(self, thumbnailId: str, options: VideoClipThumbnailOptions = None) -> MediaObject:
async def getVideoClips(self, options: VideoClipOptions = None) -> list[VideoClip]:
pass
async def getVideoClips(self, options: VideoClipOptions = None) -> list[VideoClip]:
async def getVideoClipThumbnail(self, thumbnailId: str, options: VideoClipThumbnailOptions = None) -> MediaObject:
pass
async def removeVideoClips(self, videoClipIds: list[str]) -> None:
@@ -1622,6 +1622,10 @@ class VideoRecorderManagement:
pass
class VOCSensor:
vocDensity: float
class Logger:
"""Logger is exposed via log.* to allow writing to the Scrypted log."""
@@ -1911,8 +1915,8 @@ class ScryptedInterfaceMethods(str, Enum):
ptzCommand = "ptzCommand"
getRecordedEvents = "getRecordedEvents"
getVideoClip = "getVideoClip"
getVideoClipThumbnail = "getVideoClipThumbnail"
getVideoClips = "getVideoClips"
getVideoClipThumbnail = "getVideoClipThumbnail"
removeVideoClips = "removeVideoClips"
setVideoStreamOptions = "setVideoStreamOptions"
startIntercom = "startIntercom"
@@ -2721,8 +2725,8 @@ ScryptedInterfaceDescriptors = {
"name": "VideoClips",
"methods": [
"getVideoClip",
"getVideoClipThumbnail",
"getVideoClips",
"getVideoClipThumbnail",
"removeVideoClips"
],
"properties": []

View File

@@ -1,8 +1,8 @@
import stringifyObject from 'stringify-object';
import { ScryptedInterface, ScryptedInterfaceDescriptor } from "./types.input";
import path from 'path';
import fs from "fs";
import { DeclarationReflection, ProjectReflection, ReflectionKind, SomeType } from 'typedoc';
import path from 'path';
import stringifyObject from 'stringify-object';
import { DeclarationReflection, ProjectReflection, ReflectionKind } from 'typedoc';
import { ScryptedInterface, ScryptedInterfaceDescriptor } from "./types.input";
const schema = JSON.parse(fs.readFileSync(path.join(__dirname, '../gen/schema.json')).toString()) as ProjectReflection;
const packageJson = require('../package.json');

View File

@@ -225,6 +225,9 @@ export interface NotifierOptions {
badge?: string;
bodyWithSubtitle?: string;
body?: string;
android?: {
channel?: string;
}
data?: any;
dir?: NotificationDirection;
lang?: string;
@@ -1991,7 +1994,6 @@ export interface EndpointManager {
/**
* Get an URL that can be externally accessed by anyone with the link. Plugin implementation is responsible for authentication.
* @deprecated
*/
getCloudEndpoint(nativeId?: ScryptedNativeId, options?: {
/**

View File

@@ -1,18 +1,18 @@
{
"name": "@scrypted/server",
"version": "0.119.3",
"version": "0.121.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/server",
"version": "0.119.3",
"version": "0.121.5",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"@scrypted/ffmpeg-static": "^6.1.0-build1",
"@scrypted/node-pty": "^1.0.18",
"@scrypted/types": "^0.3.62",
"@scrypted/types": "^0.3.63",
"adm-zip": "^0.5.16",
"body-parser": "^1.20.3",
"cookie-parser": "^1.4.7",
@@ -40,13 +40,13 @@
"scrypted-serve": "bin/scrypted-serve"
},
"devDependencies": {
"@types/adm-zip": "^0.5.5",
"@types/adm-zip": "^0.5.6",
"@types/cookie-parser": "^1.4.7",
"@types/express": "^5.0.0",
"@types/follow-redirects": "^1.14.4",
"@types/http-auth": "^4.1.4",
"@types/lodash": "^4.17.10",
"@types/node": "^22.7.6",
"@types/lodash": "^4.17.12",
"@types/node": "^22.8.4",
"@types/node-dijkstra": "^2.5.6",
"@types/node-forge": "^1.3.11",
"@types/semver": "^7.5.8",
@@ -557,14 +557,14 @@
}
},
"node_modules/@scrypted/types": {
"version": "0.3.62",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.62.tgz",
"integrity": "sha512-tJHpCS8B0K7h33plkbajOAHUfOpqrEJOwYAojdOTc/DGf6+xKOUMKXl+wnEHmtjtUmlSNjeBQJxJ8ua813gS6Q=="
"version": "0.3.63",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.63.tgz",
"integrity": "sha512-9oYF/tsSXl5v9uOjuoRZvzn6drpMSw+5sspOlBM6OfT91HewCHR2IgQV/h7lSwQK1xzXkdbuyJI1Rsp0Z/cl2A=="
},
"node_modules/@types/adm-zip": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.5.tgz",
"integrity": "sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw==",
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.6.tgz",
"integrity": "sha512-lRlcSLg5Yoo7C2H2AUiAoYlvifWoCx/se7iUNiCBTfEVVYFVn+Tr9ZGed4K73tYgLe9O4PjdJvbxlkdAOx/qiw==",
"dev": true,
"dependencies": {
"@types/node": "*"
@@ -654,9 +654,9 @@
}
},
"node_modules/@types/lodash": {
"version": "4.17.10",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==",
"version": "4.17.12",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.12.tgz",
"integrity": "sha512-sviUmCE8AYdaF/KIHLDJBQgeYzPBI0vf/17NaYehBJfYD1j6/L95Slh07NlyK2iNyBNaEkb3En2jRt+a8y3xZQ==",
"dev": true
},
"node_modules/@types/mime": {
@@ -666,11 +666,11 @@
"dev": true
},
"node_modules/@types/node": {
"version": "22.7.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.6.tgz",
"integrity": "sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==",
"version": "22.8.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz",
"integrity": "sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw==",
"dependencies": {
"undici-types": "~6.19.2"
"undici-types": "~6.19.8"
}
},
"node_modules/@types/node-dijkstra": {

View File

@@ -1,11 +1,11 @@
{
"name": "@scrypted/server",
"version": "0.119.4",
"version": "0.121.5",
"description": "",
"dependencies": {
"@scrypted/ffmpeg-static": "^6.1.0-build1",
"@scrypted/node-pty": "^1.0.18",
"@scrypted/types": "^0.3.62",
"@scrypted/types": "^0.3.63",
"adm-zip": "^0.5.16",
"body-parser": "^1.20.3",
"cookie-parser": "^1.4.7",
@@ -30,13 +30,13 @@
"ws": "^8.18.0"
},
"devDependencies": {
"@types/adm-zip": "^0.5.5",
"@types/adm-zip": "^0.5.6",
"@types/cookie-parser": "^1.4.7",
"@types/express": "^5.0.0",
"@types/follow-redirects": "^1.14.4",
"@types/http-auth": "^4.1.4",
"@types/lodash": "^4.17.10",
"@types/node": "^22.7.6",
"@types/lodash": "^4.17.12",
"@types/node": "^22.8.4",
"@types/node-dijkstra": "^2.5.6",
"@types/node-forge": "^1.3.11",
"@types/semver": "^7.5.8",

View File

@@ -13,11 +13,10 @@ class BufferTransfer implements RpcSerializer {
if (!serializationContext)
return this.bufferSerializer.serialize(value);
// node may used pooled buffers for Buffer.allocUnsafe, Buffer.from, and other calls.
// these buffers will be smaller than Buffer.poolSize.
// In these instances, do not transfer the buffer, as it may not be transferible.
// create a copy so it can be safely transfered.
if (value.buffer.byteLength <= Buffer.poolSize) {
// allow transfer of the buffer only if it sets the __rpc_transferable property.
// this is the only safe way to do this, since call sites may return the same buffer
// multiple times (like an image/jpeg MediaObject).
if ((value as any).__rpc_transferable !== true) {
const ab = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
value = Buffer.from(ab);
}