Merge branch 'main' of github.com:koush/scrypted

This commit is contained in:
Koushik Dutta
2024-10-29 12:05:53 -07:00
13 changed files with 1414 additions and 1035 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

@@ -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.');

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

@@ -58,6 +58,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
videoStreamOptions: Promise<UrlMediaStreamOptions[]>;
motionTimeout: NodeJS.Timeout;
siren: ReolinkCameraSiren;
batteryTimeout: NodeJS.Timeout;
storageSettings = new StorageSettings(this, {
doorbell: {
@@ -181,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();
@@ -222,7 +224,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
let abilities;
try {
abilities = await api.getAbility();
} catch(e) {
} catch (e) {
abilities = await apiWithToken.getAbility();
}
this.storageSettings.values.abilities = abilities;
@@ -293,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;
@@ -318,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() {
@@ -625,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);
@@ -634,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) {
@@ -819,7 +849,7 @@ class ReolinkProvider extends RtspProvider {
ai = await api.getAiState();
try {
abilities = await api.getAbility();
} catch(e) {
} catch (e) {
abilities = await apiWithToken.getAbility();
}
}

View File

@@ -232,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[]> {
@@ -391,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,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)

1911
sdk/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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

@@ -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;