mirror of
https://github.com/koush/scrypted.git
synced 2026-06-20 08:30:30 +01:00
homekit/core/sdk: use global setting for server address and transcoding
This commit is contained in:
2
plugins/core/.vscode/settings.json
vendored
2
plugins/core/.vscode/settings.json
vendored
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"scrypted.debugHost": "koushik-ubuntu",
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
# Scrypted Web UI Plugin
|
||||
# Scrypted Core Plugin
|
||||
|
||||
The Core Plugin provides the web UI for Scrypted.
|
||||
The Core Plugin provides the UI, Automations, Device Groups, and other core functionality within Scrypted.
|
||||
|
||||
24
plugins/core/package-lock.json
generated
24
plugins/core/package-lock.json
generated
@@ -87,7 +87,7 @@
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.0.206",
|
||||
"version": "0.2.21",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
@@ -95,12 +95,14 @@
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.13.8",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"webpack": "^5.59.0"
|
||||
"typescript": "^4.9.3",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-debug": "bin/scrypted-debug.js",
|
||||
@@ -112,13 +114,11 @@
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.1",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.22.8",
|
||||
"typescript-json-schema": "^0.50.1",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
"typedoc": "^0.23.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
@@ -250,22 +250,22 @@
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@types/node": "^16.11.1",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.13.8",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.22.8",
|
||||
"typescript-json-schema": "^0.50.1",
|
||||
"webpack": "^5.59.0",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "^4.9.3",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
"@scrypted/launcher-ignore",
|
||||
"HttpRequestHandler",
|
||||
"EngineIOHandler",
|
||||
"DeviceProvider"
|
||||
"DeviceProvider",
|
||||
"Settings"
|
||||
],
|
||||
"pluginDependencies": [
|
||||
"@scrypted/webrtc"
|
||||
@@ -46,4 +47,4 @@
|
||||
"@types/mime": "^2.0.3",
|
||||
"@types/node": "^16.9.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
86
plugins/core/src/automations-core.ts
Normal file
86
plugins/core/src/automations-core.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import sdk, { Device, DeviceCreator, DeviceCreatorSettings, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting } from '@scrypted/sdk';
|
||||
import { randomBytes } from "crypto";
|
||||
import { Automation } from "./automation";
|
||||
import { updatePluginsData } from './update-plugins';
|
||||
|
||||
const { deviceManager } = sdk;
|
||||
export const AutomationCoreNativeId = 'automationcore';
|
||||
|
||||
export class AutomationCore extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator, Readme {
|
||||
automations = new Map<string, Automation>();
|
||||
|
||||
constructor() {
|
||||
super(AutomationCoreNativeId);
|
||||
|
||||
for (const nativeId of deviceManager.getNativeIds()) {
|
||||
if (nativeId?.startsWith('automation:')) {
|
||||
const automation = new Automation(nativeId);
|
||||
this.automations.set(nativeId, automation);
|
||||
this.reportAutomation(nativeId, automation.providedName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
(async () => {
|
||||
const updatePluginsNativeId = 'automation:update-plugins'
|
||||
let updatePlugins = this.automations.get(updatePluginsNativeId);
|
||||
if (!updatePlugins) {
|
||||
await this.reportAutomation(updatePluginsNativeId, 'Autoupdate Plugins');
|
||||
updatePlugins = new Automation(updatePluginsNativeId);
|
||||
updatePlugins.storage.setItem('data', JSON.stringify(updatePluginsData));
|
||||
this.automations.set(updatePluginsNativeId, updatePlugins);
|
||||
}
|
||||
})();
|
||||
|
||||
// update the automations devices on storage change.
|
||||
// todo: make this use setting api
|
||||
sdk.systemManager.listen((eventSource, eventDetails, eventData) => {
|
||||
if (eventDetails.eventInterface === 'Storage') {
|
||||
const ids = [...this.automations.values()].map(a => a.id);
|
||||
if (ids.includes(eventSource.id)) {
|
||||
const automation = [...this.automations.values()].find(a => a.id === eventSource.id);
|
||||
automation.bind();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getReadmeMarkdown(): Promise<string> {
|
||||
return "Create custom smart home actions that trigger on specific events.";
|
||||
}
|
||||
|
||||
async getCreateDeviceSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Name',
|
||||
description: 'The name or description of the new automation.',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async createDevice(settings: DeviceCreatorSettings): Promise<string> {
|
||||
const { name, template } = settings;
|
||||
const nativeId = 'automation:' + randomBytes(8).toString('hex');
|
||||
await this.reportAutomation(nativeId, name?.toString());
|
||||
const automation = new Automation(nativeId);
|
||||
this.automations.set(nativeId, automation);
|
||||
return nativeId;
|
||||
}
|
||||
|
||||
async reportAutomation(nativeId: string, name?: string) {
|
||||
const device: Device = {
|
||||
providerNativeId: AutomationCoreNativeId,
|
||||
name,
|
||||
nativeId,
|
||||
type: ScryptedDeviceType.Automation,
|
||||
interfaces: [ScryptedInterface.OnOff, ScryptedInterface.Settings]
|
||||
}
|
||||
await deviceManager.onDeviceDiscovered(device);
|
||||
}
|
||||
|
||||
|
||||
async getDevice(nativeId: string) {
|
||||
return this.automations.get(nativeId);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
import { DeviceState, MixinProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk";
|
||||
import { DeviceState, MixinProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk";
|
||||
import { typeToIcon } from "../ui/src/components/helpers";
|
||||
|
||||
export class LauncherMixin extends ScryptedDeviceBase implements MixinProvider {
|
||||
export class LauncherMixin extends ScryptedDeviceBase implements MixinProvider, Readme {
|
||||
async getReadmeMarkdown(): Promise<string> {
|
||||
return 'Add Scrypted Plugins or Devices to the Scrypted launch screen for quick access.';
|
||||
}
|
||||
|
||||
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
|
||||
if (interfaces.includes("@scrypted/launcher-ignore"))
|
||||
return;
|
||||
if (type === ScryptedDeviceType.Builtin || type === ScryptedDeviceType.API)
|
||||
return;
|
||||
return [
|
||||
ScryptedInterface.LauncherApplication,
|
||||
];
|
||||
|
||||
@@ -1,39 +1,29 @@
|
||||
import { ScryptedDeviceBase, HttpRequestHandler, HttpRequest, HttpResponse, EngineIOHandler, Device, DeviceProvider, ScryptedInterface, ScryptedDeviceType, RTCSignalingChannel, VideoCamera, VideoRecorder } from '@scrypted/sdk';
|
||||
import sdk from '@scrypted/sdk';
|
||||
import Router from 'router';
|
||||
import { UserStorage } from './userStorage';
|
||||
import { RpcPeer } from '../../../server/src/rpc';
|
||||
import { setupPluginRemote } from '../../../server/src/plugin/plugin-remote';
|
||||
import { PluginAPIProxy } from '../../../server/src/plugin/plugin-api';
|
||||
import sdk, { Device, DeviceProvider, EngineIOHandler, HttpRequest, HttpRequestHandler, HttpResponse, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, Settings, SettingValue } from '@scrypted/sdk';
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
import fs from 'fs';
|
||||
import { sendJSON } from './http-helpers';
|
||||
import { Automation } from './automation';
|
||||
import { AggregateDevice, createAggregateDevice } from './aggregate';
|
||||
import net from 'net';
|
||||
import { updatePluginsData } from './update-plugins';
|
||||
import Router from 'router';
|
||||
import { AggregateDevice, createAggregateDevice } from './aggregate';
|
||||
import { AutomationCore, AutomationCoreNativeId } from './automations-core';
|
||||
import { sendJSON } from './http-helpers';
|
||||
import { LauncherMixin } from './launcher-mixin';
|
||||
import { MediaCore } from './media-core';
|
||||
import { ScriptCore, ScriptCoreNativeId } from './script-core';
|
||||
import { LauncherMixin } from './launcher-mixin';
|
||||
import os from 'os';
|
||||
|
||||
const { pluginHostAPI, systemManager, deviceManager, mediaManager, endpointManager } = sdk;
|
||||
const { systemManager, deviceManager, endpointManager } = sdk;
|
||||
|
||||
const indexHtml = fs.readFileSync('dist/index.html').toString();
|
||||
|
||||
export function getAddresses() {
|
||||
const addresses = Object.entries(os.networkInterfaces()).filter(([iface]) => iface.startsWith('en') || iface.startsWith('eth') || iface.startsWith('wlan')).map(([_, addr]) => addr).flat().map(info => info.address).filter(address => address);
|
||||
return addresses;
|
||||
}
|
||||
|
||||
interface RoutedHttpRequest extends HttpRequest {
|
||||
params: { [key: string]: string };
|
||||
}
|
||||
|
||||
async function reportAutomation(nativeId: string, name?: string) {
|
||||
const device: Device = {
|
||||
name,
|
||||
nativeId,
|
||||
type: ScryptedDeviceType.Automation,
|
||||
interfaces: [ScryptedInterface.OnOff, ScryptedInterface.Settings]
|
||||
}
|
||||
await deviceManager.onDeviceDiscovered(device);
|
||||
}
|
||||
|
||||
|
||||
async function reportAggregate(nativeId: string, interfaces: string[]) {
|
||||
const device: Device = {
|
||||
name: undefined,
|
||||
@@ -44,14 +34,33 @@ async function reportAggregate(nativeId: string, interfaces: string[]) {
|
||||
await deviceManager.onDeviceDiscovered(device);
|
||||
}
|
||||
|
||||
class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, EngineIOHandler, DeviceProvider {
|
||||
class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, EngineIOHandler, DeviceProvider, Settings {
|
||||
router: any = Router();
|
||||
publicRouter: any = Router();
|
||||
mediaCore: MediaCore;
|
||||
launcher: LauncherMixin;
|
||||
scriptCore: ScriptCore;
|
||||
automations = new Map<string, Automation>();
|
||||
automationCore: AutomationCore;
|
||||
aggregate = new Map<string, AggregateDevice>();
|
||||
localAddresses: string[];
|
||||
storageSettings = new StorageSettings(this, {
|
||||
localAddresses: {
|
||||
title: 'Scrypted Server Address',
|
||||
description: 'The IP address used by the Scrypted server. Set this to the wired IP address to prevent usage of a wireless address.',
|
||||
combobox: true,
|
||||
async onGet() {
|
||||
return {
|
||||
choices: getAddresses(),
|
||||
};
|
||||
},
|
||||
mapGet: () => this.localAddresses?.[0],
|
||||
onPut: async (oldValue, newValue) => {
|
||||
this.localAddresses = newValue ? [newValue] : undefined;
|
||||
const service = await sdk.systemManager.getComponent('addresses');
|
||||
service.setLocalAddresses(this.localAddresses);
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -62,7 +71,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
name: 'Media Core',
|
||||
nativeId: 'mediacore',
|
||||
interfaces: [ScryptedInterface.DeviceProvider, ScryptedInterface.BufferConverter, ScryptedInterface.HttpRequestHandler],
|
||||
type: ScryptedDeviceType.API,
|
||||
type: ScryptedDeviceType.Builtin,
|
||||
},
|
||||
);
|
||||
this.mediaCore = new MediaCore('mediacore');
|
||||
@@ -73,10 +82,22 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
name: 'Scripting Core',
|
||||
nativeId: ScriptCoreNativeId,
|
||||
interfaces: [ScryptedInterface.DeviceProvider, ScryptedInterface.DeviceCreator, ScryptedInterface.Readme],
|
||||
type: ScryptedDeviceType.API,
|
||||
type: ScryptedDeviceType.Builtin,
|
||||
},
|
||||
);
|
||||
this.scriptCore = new ScriptCore(ScriptCoreNativeId);
|
||||
this.scriptCore = new ScriptCore();
|
||||
})();
|
||||
|
||||
(async () => {
|
||||
await deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
name: 'Automation Core',
|
||||
nativeId: AutomationCoreNativeId,
|
||||
interfaces: [ScryptedInterface.DeviceProvider, ScryptedInterface.DeviceCreator, ScryptedInterface.Readme],
|
||||
type: ScryptedDeviceType.Builtin,
|
||||
},
|
||||
);
|
||||
this.automationCore = new AutomationCore();
|
||||
})();
|
||||
|
||||
deviceManager.onDeviceDiscovered({
|
||||
@@ -85,44 +106,19 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
interfaces: [
|
||||
'@scrypted/launcher-ignore',
|
||||
ScryptedInterface.MixinProvider,
|
||||
ScryptedInterface.Readme,
|
||||
],
|
||||
type: ScryptedDeviceType.Builtin,
|
||||
});
|
||||
|
||||
for (const nativeId of deviceManager.getNativeIds()) {
|
||||
if (nativeId?.startsWith('automation:')) {
|
||||
const automation = new Automation(nativeId);
|
||||
this.automations.set(nativeId, automation);
|
||||
reportAutomation(nativeId, automation.providedName);
|
||||
}
|
||||
else if (nativeId?.startsWith('aggregate:')) {
|
||||
if (nativeId?.startsWith('aggregate:')) {
|
||||
const aggregate = createAggregateDevice(nativeId);
|
||||
this.aggregate.set(nativeId, aggregate);
|
||||
reportAggregate(nativeId, aggregate.computeInterfaces());
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const updatePluginsNativeId = 'automation:update-plugins'
|
||||
let updatePlugins = this.automations.get(updatePluginsNativeId);
|
||||
if (!updatePlugins) {
|
||||
await reportAutomation(updatePluginsNativeId, 'Autoupdate Plugins');
|
||||
updatePlugins = new Automation(updatePluginsNativeId);
|
||||
updatePlugins.storage.setItem('data', JSON.stringify(updatePluginsData));
|
||||
this.automations.set(updatePluginsNativeId, updatePlugins);
|
||||
}
|
||||
})();
|
||||
|
||||
this.router.post('/api/new/automation', async (req: RoutedHttpRequest, res: HttpResponse) => {
|
||||
const nativeId = `automation:${Math.random()}`;
|
||||
await reportAutomation(nativeId);
|
||||
const automation = new Automation(nativeId);
|
||||
this.automations.set(nativeId, automation);
|
||||
const { id } = automation;
|
||||
sendJSON(res, {
|
||||
id,
|
||||
});
|
||||
});
|
||||
|
||||
this.router.post('/api/new/aggregate', async (req: RoutedHttpRequest, res: HttpResponse) => {
|
||||
const nativeId = `aggregate:${Math.random()}`;
|
||||
@@ -138,12 +134,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
// update the automations and grouped devices on storage change.
|
||||
systemManager.listen((eventSource, eventDetails, eventData) => {
|
||||
if (eventDetails.eventInterface === 'Storage') {
|
||||
let ids = [...this.automations.values()].map(a => a.id);
|
||||
if (ids.includes(eventSource.id)) {
|
||||
const automation = [...this.automations.values()].find(a => a.id === eventSource.id);
|
||||
automation.bind();
|
||||
}
|
||||
ids = [...this.aggregate.values()].map(a => a.id);
|
||||
const ids = [...this.aggregate.values()].map(a => a.id);
|
||||
if (ids.includes(eventSource.id)) {
|
||||
const aggregate = [...this.aggregate.values()].find(a => a.id === eventSource.id);
|
||||
reportAggregate(aggregate.nativeId, aggregate.computeInterfaces());
|
||||
@@ -152,6 +143,19 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
});
|
||||
}
|
||||
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
try {
|
||||
const service = await sdk.systemManager.getComponent('addresses');
|
||||
this.localAddresses = await service.getLocalAddresses();
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
return this.storageSettings.getSettings();
|
||||
}
|
||||
async putSetting(key: string, value: SettingValue): Promise<void> {
|
||||
await this.storageSettings.putSetting(key, value);
|
||||
}
|
||||
|
||||
async getDevice(nativeId: string) {
|
||||
if (nativeId === 'launcher')
|
||||
return new LauncherMixin('launcher');
|
||||
@@ -159,8 +163,8 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
return this.mediaCore;
|
||||
if (nativeId === ScriptCoreNativeId)
|
||||
return this.scriptCore;
|
||||
if (nativeId?.startsWith('automation:'))
|
||||
return this.automations.get(nativeId);
|
||||
if (nativeId === AutomationCoreNativeId)
|
||||
return this.automationCore;
|
||||
if (nativeId?.startsWith('aggregate:'))
|
||||
return this.aggregate.get(nativeId);
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ export class MediaCore extends ScryptedDeviceBase implements DeviceProvider, Buf
|
||||
}
|
||||
}
|
||||
|
||||
getDevice(nativeId: string) {
|
||||
async getDevice(nativeId: string) {
|
||||
if (nativeId === 'http')
|
||||
return this.httpHost;
|
||||
if (nativeId === 'https')
|
||||
|
||||
@@ -11,8 +11,8 @@ export const ScriptCoreNativeId = 'scriptcore';
|
||||
export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator, Readme {
|
||||
scripts = new Map<string, Promise<Script>>();
|
||||
|
||||
constructor(nativeId: string) {
|
||||
super(nativeId);
|
||||
constructor() {
|
||||
super(ScriptCoreNativeId);
|
||||
|
||||
for (const nativeId of deviceManager.getNativeIds()) {
|
||||
if (nativeId?.startsWith('script:')) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "Node16",
|
||||
"target": "esnext",
|
||||
"esModuleInterop": true,
|
||||
},
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
<LogCard :rows="15" :logRoute="`/device/${id}/`"></LogCard>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 v-if="!device.interfaces.includes(ScryptedInterface.Settings) && (availableMixins.length || !device.interfaces.includes(ScryptedInterface.ScryptedPlugin))">
|
||||
<v-flex xs12 v-if="!device.interfaces.includes(ScryptedInterface.Settings) && (availableMixins.length || deviceIsEditable(device))">
|
||||
<Settings :device="device"></Settings>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
@@ -206,6 +206,7 @@ import {
|
||||
getAlertIcon,
|
||||
hasFixedPhysicalLocation,
|
||||
getInterfaceFriendlyName,
|
||||
deviceIsEditable,
|
||||
} from "./helpers";
|
||||
import { ScryptedInterface } from "@scrypted/types";
|
||||
import RTCSignalingClient from "../interfaces/RTCSignalingClient.vue";
|
||||
@@ -410,6 +411,7 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
deviceIsEditable,
|
||||
getInterfaceFriendlyName,
|
||||
hasFixedPhysicalLocation,
|
||||
getComponentWebPath,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<v-layout>
|
||||
<v-layout row wrap>
|
||||
<v-flex xs12 md6 lg4>
|
||||
<Settings :device="coreDevice" class="mb-2"></Settings>
|
||||
<v-card v-if="updateAvailable">
|
||||
<v-toolbar>
|
||||
<v-toolbar-title>Update Available </v-toolbar-title>
|
||||
@@ -101,10 +102,16 @@
|
||||
</template>
|
||||
<script>
|
||||
import { checkUpdate } from "../plugin/plugin";
|
||||
import Settings from "../../interfaces/Settings.vue"
|
||||
import {createSystemSettingsDevice} from './system-settings';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Settings,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
coreDevice: createSystemSettingsDevice(this.$scrypted.systemManager),
|
||||
currentVersion: null,
|
||||
updateAvailable: null,
|
||||
canUpdate: false,
|
||||
|
||||
64
plugins/core/ui/src/components/builtin/system-settings.ts
Normal file
64
plugins/core/ui/src/components/builtin/system-settings.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { ScryptedDevice, ScryptedDeviceType, ScryptedInterface, Settings, SystemManager } from "@scrypted/types";
|
||||
|
||||
export function createSystemSettingsDevice(systemManager: SystemManager): ScryptedDevice & Settings {
|
||||
const core = systemManager.getDeviceByName<Settings>('@scrypted/core');
|
||||
let transcode: ScryptedDevice & Settings;
|
||||
for (const id of Object.keys(systemManager.getSystemState())) {
|
||||
const device = systemManager.getDeviceById<Settings>(id);
|
||||
if (device.nativeId === 'transcode' && device.pluginId === '@scrypted/prebuffer-mixin') {
|
||||
transcode = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: 'Settings',
|
||||
type: ScryptedDeviceType.Builtin,
|
||||
interfaces: [
|
||||
ScryptedInterface.Settings,
|
||||
],
|
||||
async setName(name) {
|
||||
|
||||
},
|
||||
async setRoom() {
|
||||
|
||||
},
|
||||
async setType() {
|
||||
|
||||
},
|
||||
async probe() {
|
||||
return true;
|
||||
},
|
||||
listen(event, callback) {
|
||||
const cl = core.listen(event, callback);
|
||||
const tl = transcode?.listen(event, callback);
|
||||
return {
|
||||
removeListener() {
|
||||
cl.removeListener();
|
||||
tl?.removeListener();
|
||||
},
|
||||
}
|
||||
},
|
||||
async getSettings() {
|
||||
return [
|
||||
...(await core.getSettings()).map(s => ({
|
||||
...s,
|
||||
key: 'core:' + s.key,
|
||||
group: 'Network Settings',
|
||||
})),
|
||||
...(await transcode?.getSettings() || []).map(s => ({
|
||||
...s,
|
||||
key: 'transcode:' + s.key,
|
||||
group: 'Transcoding',
|
||||
})),
|
||||
];
|
||||
},
|
||||
async putSetting(key, value) {
|
||||
if (key.startsWith('core:')) {
|
||||
await core.putSetting(key.substring(5), value);
|
||||
}
|
||||
else if (key.startsWith('transcode:')) {
|
||||
await core.putSetting(key.substring(10), value);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,15 @@
|
||||
import { ScryptedDeviceType, ScryptedInterface } from "@scrypted/types";
|
||||
import { ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from "@scrypted/types";
|
||||
|
||||
export function deviceIsEditable(device: ScryptedDevice) {
|
||||
if (!device)
|
||||
return;
|
||||
if (device.interfaces.includes(ScryptedInterface.ScryptedPlugin))
|
||||
return;
|
||||
if (device.type === ScryptedDeviceType.Builtin || device.type === ScryptedDeviceType.API)
|
||||
return;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function typeToIcon(type) {
|
||||
switch (type) {
|
||||
@@ -30,6 +41,7 @@ export function typeToIcon(type) {
|
||||
case ScryptedDeviceType.Irrigation: return "fa-faucet";
|
||||
case ScryptedDeviceType.Person: return "fa-user";
|
||||
case ScryptedDeviceType.SecuritySystem: return "fa-shield-alt";
|
||||
case ScryptedDeviceType.Builtin: return "fa-server";
|
||||
|
||||
}
|
||||
return "fa-toggle-on";
|
||||
|
||||
@@ -41,15 +41,15 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import { ScryptedInterface } from "@scrypted/types";
|
||||
import Vue from 'vue';
|
||||
import AvailableMixins from "../components/AvailableMixins.vue";
|
||||
import CardTitle from "../components/CardTitle.vue";
|
||||
import { deviceIsEditable, hasFixedPhysicalLocation, inferTypesFromInterfaces } from "../components/helpers";
|
||||
import Mixin from "../components/Mixin.vue";
|
||||
import RPCInterface from "./RPCInterface.vue";
|
||||
import Setting from "./Setting.vue";
|
||||
import SettingMultiple from "./SettingMultiple.vue";
|
||||
import CardTitle from "../components/CardTitle.vue";
|
||||
import AvailableMixins from "../components/AvailableMixins.vue";
|
||||
import Mixin from "../components/Mixin.vue";
|
||||
import { ScryptedInterface } from "@scrypted/types";
|
||||
import { hasFixedPhysicalLocation, inferTypesFromInterfaces } from "../components/helpers";
|
||||
import Vue from 'vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -93,7 +93,7 @@ export default {
|
||||
|
||||
let addAt = 0;
|
||||
|
||||
if (this.device && !this.device.interfaces.includes(ScryptedInterface.ScryptedPlugin)) {
|
||||
if (deviceIsEditable(this.device)) {
|
||||
const inferredTypes = inferTypesFromInterfaces(
|
||||
this.device.type,
|
||||
this.device.providedType,
|
||||
|
||||
13
plugins/homekit/src/address-override.ts
Normal file
13
plugins/homekit/src/address-override.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import sdk from '@scrypted/sdk';
|
||||
|
||||
export async function getAddressOverride(legacy: string) {
|
||||
try {
|
||||
const service = await sdk.systemManager.getComponent('addresses');
|
||||
const addresses = await service.getLocalAddresses();
|
||||
const address = addresses?.[0];
|
||||
return address || legacy;
|
||||
}
|
||||
catch (e) {
|
||||
return legacy;
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,19 @@ export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider,
|
||||
}
|
||||
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
try {
|
||||
this.storageSettings.settings.addressOverride.hide = false;
|
||||
const service = await sdk.systemManager.getComponent('addresses');
|
||||
if (service) {
|
||||
if (this.storageSettings.values.addressOverride) {
|
||||
await service.setLocalAddresses([this.storageSettings.values.addressOverride]);
|
||||
this.storageSettings.values.addressOverride = undefined;
|
||||
}
|
||||
this.storageSettings.settings.addressOverride.hide = true;;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
return this.storageSettings.getSettings();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import sdk, { Camera, FFmpegInput, Intercom, MediaStreamFeedback, MediaStreamOpt
|
||||
import dgram, { SocketType } from 'dgram';
|
||||
import { once } from 'events';
|
||||
import os from 'os';
|
||||
import { getAddressOverride } from '../../address-override';
|
||||
import { AudioStreamingCodecType, CameraController, CameraStreamingDelegate, PrepareStreamCallback, PrepareStreamRequest, PrepareStreamResponse, StartStreamRequest, StreamingRequest, StreamRequestCallback, StreamRequestTypes } from '../../hap';
|
||||
import type { HomeKitPlugin } from "../../main";
|
||||
import { startRtpSink } from '../../rtp/rtp-ffmpeg-input';
|
||||
@@ -57,12 +58,14 @@ export function createCameraStreamingDelegate(device: ScryptedDevice & VideoCame
|
||||
});
|
||||
|
||||
const socketType = request.addressVersion === 'ipv6' ? 'udp6' : 'udp4';
|
||||
let addressOverride = homekitPlugin.storageSettings.values.addressOverride || undefined;
|
||||
let addressOverride = await getAddressOverride(homekitPlugin.storageSettings.values.addressOverride || undefined);
|
||||
|
||||
if (addressOverride) {
|
||||
const infos = Object.values(os.networkInterfaces()).flat().map(i => i?.address);
|
||||
if (!infos.find(address => address === addressOverride)) {
|
||||
console.error('The provided Scrypted Server Address was not found in the list of network addresses and may be invalid and will not be used (DHCP assignment change?): ' + addressOverride);
|
||||
const error = 'The provided Scrypted Server Address was not found in the list of network addresses and may be invalid and will not be used (DHCP assignment change?): ' + addressOverride;
|
||||
console.error(error);
|
||||
sdk.log.a(error);
|
||||
addressOverride = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,6 +372,7 @@ class HumiditySettingStatus(TypedDict):
|
||||
|
||||
class LauncherApplicationInfo(TypedDict):
|
||||
description: str
|
||||
href: str
|
||||
icon: str
|
||||
name: str
|
||||
pass
|
||||
|
||||
@@ -1606,6 +1606,7 @@ export interface LauncherApplicationInfo {
|
||||
*/
|
||||
icon?: string;
|
||||
description?: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
export interface LauncherApplication {
|
||||
|
||||
Reference in New Issue
Block a user