diff --git a/plugins/mqtt/package-lock.json b/plugins/mqtt/package-lock.json index ddc5bed15..458cbd359 100644 --- a/plugins/mqtt/package-lock.json +++ b/plugins/mqtt/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/mqtt", - "version": "0.0.14", + "version": "0.0.17", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/mqtt", - "version": "0.0.14", + "version": "0.0.17", "dependencies": { "@types/node": "^16.6.1", "aedes": "^0.46.1", diff --git a/plugins/mqtt/package.json b/plugins/mqtt/package.json index bb8f1279a..af0f20baf 100644 --- a/plugins/mqtt/package.json +++ b/plugins/mqtt/package.json @@ -35,5 +35,5 @@ "devDependencies": { "@scrypted/sdk": "file:../../sdk" }, - "version": "0.0.14" + "version": "0.0.17" } diff --git a/plugins/mqtt/src/api/frigate.ts b/plugins/mqtt/src/api/frigate.ts new file mode 100644 index 000000000..c36f0806b --- /dev/null +++ b/plugins/mqtt/src/api/frigate.ts @@ -0,0 +1,108 @@ +import type { ScryptedDeviceBase } from "@scrypted/sdk"; +import type { MqttClient, MqttSubscriptions } from "./mqtt-client"; + +declare const mqtt: MqttClient; +declare const device: ScryptedDeviceBase; + + +export type FrigateObjectType = + 'person' | + 'bicycle' | + 'car' | + 'motorcycle' | + 'airplane' | + 'bus' | + 'train' | + 'car' | + 'boat' | + 'traffic light' | + 'fire hydrant' | + 'stop sign' | + 'parking meter' | + 'bench' | + 'bird' | + 'cat' | + 'dog' | + 'horse' | + 'sheep' | + 'cow' | + 'elephant' | + 'bear' | + 'zebra' | + 'giraffe' | + 'backpack' | + 'umbrella' | + 'handbag' | + 'tie' | + 'suitcase' | + 'frisbee' | + 'skis' | + 'snowboard' | + 'sports ball' | + 'kite' | + 'baseball bat' | + 'baseball glove' | + 'skateboard' | + 'surfboard' | + 'tennis racket' | + 'bottle' | + 'wine glass' | + 'cup' | + 'fork' | + 'knife' | + 'spoon' | + 'bowl' | + 'banana' | + 'apple' | + 'sandwich' | + 'orange' | + 'broccoli' | + 'carrot' | + 'hot dog' | + 'pizza' | + 'donut' | + 'cake' | + 'chair' | + 'couch' | + 'potted plant' | + 'bed' | + 'dining table' | + 'toilet' | + 'tv' | + 'laptop' | + 'mouse' | + 'remote' | + 'keyboard' | + 'cell phone' | + 'microwave' | + 'oven' | + 'toaster' | + 'sink' | + 'refrigerator' | + 'book' | + 'clock' | + 'vase' | + 'scissors' | + 'teddy bear' | + 'hair drier' | + 'toothbrush'; + +export interface FrigateZoneWatcher { + zone: string, + objects: FrigateObjectType[]; +} + +export function frigateMotionSensor(zoneWatcher: FrigateZoneWatcher, ...zoneWatchers: FrigateZoneWatcher[]) { + const subs: MqttSubscriptions = {}; + const counts: { [type: string]: number } = {}; + for (const z of [zoneWatcher, ...zoneWatchers]) { + for (const o of z.objects) { + subs[`/${z.zone}/${o}`] = message => { + counts[z.zone] = message.json; + device.motionDetected = Object.values(counts).reduce((a, b) => a + b, 0) !== 0; + } + } + } + mqtt.subscribe(subs); + mqtt.handle({}, "MotionSensor"); +} diff --git a/plugins/mqtt/src/mqtt-client.ts b/plugins/mqtt/src/api/mqtt-client.ts similarity index 85% rename from plugins/mqtt/src/mqtt-client.ts rename to plugins/mqtt/src/api/mqtt-client.ts index e64a1ffae..6c6de1a64 100644 --- a/plugins/mqtt/src/mqtt-client.ts +++ b/plugins/mqtt/src/api/mqtt-client.ts @@ -16,6 +16,6 @@ export interface MqttHandler { export interface MqttClient { subscribe(subscriptions: MqttSubscriptions, options?: any): void; - handle(handler?: T, ...interfaces: ScryptedInterface[]): void; + handle(handler?: T, ...interfaces: string[]): void; publish(topic: string, value: any): Promise; } diff --git a/plugins/mqtt/src/loopback-light.ts b/plugins/mqtt/src/examples/loopback-light.ts similarity index 100% rename from plugins/mqtt/src/loopback-light.ts rename to plugins/mqtt/src/examples/loopback-light.ts diff --git a/plugins/mqtt/src/main.ts b/plugins/mqtt/src/main.ts index 5cee195d3..06df1c2a0 100644 --- a/plugins/mqtt/src/main.ts +++ b/plugins/mqtt/src/main.ts @@ -6,14 +6,14 @@ import { Settings, Setting, DeviceProvider, OnOff, ScryptedDeviceBase, ScryptedI import sdk from '@scrypted/sdk'; import { monacoEvalDefaults } from './monaco'; import { scryptedEval } from './scrypted-eval'; -import { MqttClient, MqttSubscriptions } from './mqtt-client'; +import { MqttClient, MqttSubscriptions } from './api/mqtt-client'; import { connect, Client, IClientSubscribeOptions, ClientSubscribeCallback } from 'mqtt'; import aedes from 'aedes'; import net from 'net'; import ws from 'websocket-stream'; import http from 'http'; -const loopbackLight = require("!!raw-loader!./loopback-light.ts"); +const loopbackLight = require("!!raw-loader!./examples/loopback-light.ts"); const methodInterfaces: { [method: string]: string } = {}; for (const desc of Object.values(ScryptedInterfaceDescriptors)) { @@ -77,8 +77,7 @@ class MqttDevice extends ScryptedDeviceBase implements Scriptable, Settings { this.client?.end(); this.client = undefined; const url = new URL(this.storage.getItem('url')); - const { pathname } = url; - this.pathname = pathname; + this.pathname = url.pathname.substring(1); const urlWithoutPath = new URL(this.storage.getItem('url')); urlWithoutPath.pathname = ''; @@ -100,7 +99,7 @@ class MqttDevice extends ScryptedDeviceBase implements Scriptable, Settings { const mqtt: MqttClient = { subscribe: (subscriptions: MqttSubscriptions, options?: any) => { for (const topic of Object.keys(subscriptions)) { - const fullTopic = pathname + topic; + const fullTopic = this.pathname + topic; const cb = subscriptions[topic]; if (options) { client.subscribe(fullTopic, options) @@ -109,7 +108,7 @@ class MqttDevice extends ScryptedDeviceBase implements Scriptable, Settings { client.subscribe(fullTopic) } client.on('message', (messageTopic, message) => { - if (fullTopic !== messageTopic) + if (fullTopic !== messageTopic && fullTopic !== '/' + messageTopic) return; this.console.log('mqtt message', topic, message.toString()); cb({ @@ -139,7 +138,7 @@ class MqttDevice extends ScryptedDeviceBase implements Scriptable, Settings { value = JSON.stringify(value); if (value.constructor.name !== Buffer.name) value = value.toString(); - client.publish(pathname + topic, value); + client.publish(this.pathname + topic, value); } } await scryptedEval(this, script, { diff --git a/plugins/mqtt/src/monaco.ts b/plugins/mqtt/src/monaco.ts index 690ebbc8e..374a20f14 100644 --- a/plugins/mqtt/src/monaco.ts +++ b/plugins/mqtt/src/monaco.ts @@ -1,8 +1,11 @@ -const types = require("!!raw-loader!@scrypted/sdk/types.d.ts"); -const sdk = require("!!raw-loader!@scrypted/sdk/index.d.ts"); -const client = require("!!raw-loader!./mqtt-client.ts"); +const libs = { + types: require("!!raw-loader!@scrypted/sdk/types.d.ts"), + sdk: require("!!raw-loader!@scrypted/sdk/index.d.ts"), + client: require("!!raw-loader!./api/mqtt-client.ts"), + frigate: require("!!raw-loader!./api/frigate.ts"), +}; -function monacoEvalDefaultsFunction(monaco, types, sdk, client) { +function monacoEvalDefaultsFunction(monaco, libs) { monaco.languages.typescript.typescriptDefaults.setCompilerOptions( Object.assign( {}, @@ -14,14 +17,13 @@ function monacoEvalDefaultsFunction(monaco, types, sdk, client) { ) ); + const catLibs = Object.values(libs).join('\n'); + const catlibsNoExport = Object.keys(libs).filter(lib => lib !== 'sdk').map(lib => libs[lib]).map(lib => lib.toString().replace(/export /g, '')).join('\n'); monaco.languages.typescript.typescriptDefaults.addExtraLib(` - ${types} - ${sdk} - ${client} + ${catLibs} declare global { - ${types.replace("export interface", "interface")} - ${client.replace("export interface", "interface")} + ${catlibsNoExport} const log: Logger; @@ -38,18 +40,16 @@ function monacoEvalDefaultsFunction(monaco, types, sdk, client) { ); monaco.languages.typescript.typescriptDefaults.addExtraLib( - sdk, + libs['sdk'], "node_modules/@types/scrypted__sdk/index.d.ts" ); } export const monacoEvalDefaults = `(function() { -const types = \`${types}\`; -const sdk = \`${sdk}\`; -const client = \`${client}\`; +const libs = ${JSON.stringify(libs)}; return (monaco) => { - (${monacoEvalDefaultsFunction})(monaco, types, sdk, client); + (${monacoEvalDefaultsFunction})(monaco, libs); } })(); `; diff --git a/plugins/mqtt/src/scrypted-eval.ts b/plugins/mqtt/src/scrypted-eval.ts index 075d4bf7f..9dddf9856 100644 --- a/plugins/mqtt/src/scrypted-eval.ts +++ b/plugins/mqtt/src/scrypted-eval.ts @@ -2,6 +2,13 @@ import ts, { ScriptTarget } from "typescript"; import sdk, { ScryptedDeviceBase } from "@scrypted/sdk"; import vm from "vm"; +const frigate = require("!!raw-loader!./api/frigate.ts"); +const types = require("!!raw-loader!!@scrypted/sdk/types.d.ts"); +const libs = { + frigate, + types, +}; + const { systemManager, deviceManager, mediaManager, endpointManager } = sdk; function tsCompile(source: string, options: ts.TranspileOptions = null): string { @@ -19,7 +26,8 @@ function tsCompile(source: string, options: ts.TranspileOptions = null): string export async function scryptedEval(device: ScryptedDeviceBase, script: string, params: { [name: string]: any }) { try { - const compiled = tsCompile(script); + const allScripts = Object.values(libs).join('\n').toString() + script; + const compiled = tsCompile(allScripts); const allParams = Object.assign({}, params, { systemManager, @@ -30,6 +38,7 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, p console: device.console, localStorage: device.storage, device, + exports: {}, }); try {