From bf783c7c3c8aae914ca779ba13df855ebe3502ea Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Thu, 11 Apr 2024 10:07:04 -0700 Subject: [PATCH] mqtt: switch auto discovery --- plugins/mqtt/package-lock.json | 4 +- plugins/mqtt/package.json | 2 +- plugins/mqtt/src/autodiscovery.ts | 81 +++++++++++++++++++++++++------ plugins/mqtt/src/main.ts | 15 ++++-- 4 files changed, 80 insertions(+), 22 deletions(-) diff --git a/plugins/mqtt/package-lock.json b/plugins/mqtt/package-lock.json index 76febc6ad..a233c0d91 100644 --- a/plugins/mqtt/package-lock.json +++ b/plugins/mqtt/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/mqtt", - "version": "0.0.77", + "version": "0.0.78", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/mqtt", - "version": "0.0.77", + "version": "0.0.78", "dependencies": { "aedes": "^0.46.1", "axios": "^0.23.0", diff --git a/plugins/mqtt/package.json b/plugins/mqtt/package.json index 9f6458c33..9740e7d12 100644 --- a/plugins/mqtt/package.json +++ b/plugins/mqtt/package.json @@ -41,5 +41,5 @@ "@types/node": "^18.4.2", "@types/nunjucks": "^3.2.0" }, - "version": "0.0.77" + "version": "0.0.78" } diff --git a/plugins/mqtt/src/autodiscovery.ts b/plugins/mqtt/src/autodiscovery.ts index ff130638a..39572948f 100644 --- a/plugins/mqtt/src/autodiscovery.ts +++ b/plugins/mqtt/src/autodiscovery.ts @@ -6,6 +6,7 @@ import nunjucks from 'nunjucks'; import sdk from "@scrypted/sdk"; import type { MqttProvider } from './main'; import { getHsvFromXyColor, getXyYFromHsvColor } from './color-util'; +import { MqttEvent } from './api/mqtt-client'; const { deviceManager } = sdk; @@ -25,7 +26,7 @@ typeMap.set('light', { const interfaces = [ScryptedInterface.OnOff, ScryptedInterface.Brightness]; if (config.color_mode) { config.supported_color_modes.forEach(color_mode => { - if (color_mode === 'xy') + if (color_mode === 'xy') interfaces.push(ScryptedInterface.ColorSettingHsv); else if (color_mode === 'hs') interfaces.push(ScryptedInterface.ColorSettingHsv); @@ -246,7 +247,7 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements Onlin return; } - this.debounceCallbacks = new Map void>>(); + this.debounceCallbacks = new Map void>>(); const { client } = provider; client.on('message', this.listener.bind(this)); @@ -297,7 +298,7 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements Onlin this.console.log('binding...'); const { client } = this.provider; - this.debounceCallbacks = new Map void>>(); + this.debounceCallbacks = new Map void>>(); if (this.providedInterfaces.includes(ScryptedInterface.Online)) { const config = this.loadComponentConfig(ScryptedInterface.Online); @@ -468,7 +469,7 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements Onlin config.command_off_template, command, "ON"); } else { - this.publishValue(config.command_topic, + this.publishValue(config.command_topic, undefined, command, command); } } @@ -489,7 +490,7 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements Onlin config.command_on_template, command, "ON"); } else { - this.publishValue(config.command_topic, + this.publishValue(config.command_topic, undefined, command, command); } } @@ -506,8 +507,8 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements Onlin config.brightness_value_template, scaledBrightness, scaledBrightness); } else { - this.publishValue(config.command_topic, - `{ "state": "${ scaledBrightness === 0 ? 'OFF' : 'ON'}", "brightness": ${scaledBrightness} }`, + this.publishValue(config.command_topic, + `{ "state": "${scaledBrightness === 0 ? 'OFF' : 'ON'}", "brightness": ${scaledBrightness} }`, scaledBrightness, 255); } } @@ -525,7 +526,7 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements Onlin if (kelvin >= 0 || kelvin <= 100) { const min = await this.getTemperatureMinK(); const max = await this.getTemperatureMaxK(); - const diff = (max - min) * (kelvin/100); + const diff = (max - min) * (kelvin / 100); kelvin = Math.round(min + diff); } @@ -542,7 +543,7 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements Onlin config.color_temp_command_template, color, color); } else { - this.publishValue(config.command_topic, + this.publishValue(config.command_topic, undefined, color, color); } } @@ -567,7 +568,7 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements Onlin config.hs_command_template, color, color); } else { - this.publishValue(config.command_topic, + this.publishValue(config.command_topic, undefined, color, color); } } else if (this.colorMode === "xy") { @@ -589,12 +590,12 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements Onlin config.xy_command_template, color, color); } else { - this.publishValue(config.command_topic, + this.publishValue(config.command_topic, undefined, color, color); } - } + } } - + async lock(): Promise { const config = this.loadComponentConfig(ScryptedInterface.Lock); return this.publishValue(config.command_topic, @@ -610,6 +611,9 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements Onlin interface AutoDiscoveryConfig { component: string; create: (mqttId: string, device: MixinDeviceBase, topic: string) => any; + subscriptions?: { + [topic: string]: (device: MixinDeviceBase, event: MqttEvent) => void; + } } const autoDiscoveryMap = new Map(); @@ -676,7 +680,25 @@ autoDiscoveryMap.set(ScryptedInterface.HumiditySensor, { } }); -export function publishAutoDiscovery(mqttId: string, client: Client, device: MixinDeviceBase, topic: string, autoDiscoveryPrefix = 'homeassistant') { +autoDiscoveryMap.set(ScryptedInterface.OnOff, { + component: 'switch', + create(mqttId, device, topic) { + return { + payload_on: 'true', + payload_off: 'false', + state_topic: `${topic}/${ScryptedInterfaceProperty.on}`, + command_topic: `${topic}/${ScryptedInterfaceProperty.on}/set`, + ...getAutoDiscoveryDevice(device, mqttId), + } + }, + subscriptions: { + 'on/set': (device, event) => { + device.on = event.json; + } + }, +}); + +export function publishAutoDiscovery(mqttId: string, client: Client, device: MixinDeviceBase, topic: string, subscribe: boolean, autoDiscoveryPrefix = 'homeassistant') { for (const iface of device.interfaces) { const found = autoDiscoveryMap.get(iface); if (!found) @@ -691,5 +713,36 @@ export function publishAutoDiscovery(mqttId: string, client: Client, device: Mix client.publish(configTopic, JSON.stringify(config), { retain: true, }); + + if (subscribe) { + const subscriptions = found.subscriptions || {}; + for (const subscriptionTopic of Object.keys(subscriptions || {})) { + const fullTopic = topic + '/' + subscriptionTopic; + const cb = subscriptions[subscriptionTopic]; + client.subscribe(fullTopic) + client.on('message', (messageTopic, message) => { + if (fullTopic !== messageTopic && fullTopic !== '/' + messageTopic) + return; + device.console.log('mqtt message', subscriptionTopic, message.toString()); + cb(device, { + get text() { + return message.toString(); + }, + get json() { + try { + return JSON.parse(message.toString()); + } + catch (e) { + } + }, + get buffer() { + return message; + } + }) + }); + } + } + + return found; } } diff --git a/plugins/mqtt/src/main.ts b/plugins/mqtt/src/main.ts index 73974da53..9a313b142 100644 --- a/plugins/mqtt/src/main.ts +++ b/plugins/mqtt/src/main.ts @@ -294,13 +294,15 @@ class MqttPublisherMixin extends SettingsMixinDeviceBase { allProperties.push(...properties); } + let found: ReturnType; + client.on('connect', packet => { this.console.log('MQTT client connected, publishing current state.'); for (const method of allMethods) { client.subscribe(this.pathname + '/' + method); } - publishAutoDiscovery(this.provider.storageSettings.values.mqttId, client, this, this.pathname, 'homeassistant'); + found = publishAutoDiscovery(this.provider.storageSettings.values.mqttId, client, this, this.pathname, true, 'homeassistant'); client.subscribe('homeassistant/status'); this.publishState(client); }); @@ -311,14 +313,17 @@ class MqttPublisherMixin extends SettingsMixinDeviceBase { client.on('message', async (messageTopic, message) => { if (messageTopic === 'homeassistant/status') { - publishAutoDiscovery(this.provider.storageSettings.values.mqttId, client, this, this.pathname, 'homeassistant'); + publishAutoDiscovery(this.provider.storageSettings.values.mqttId, client, this, this.pathname, false, 'homeassistant'); this.publishState(client); return; } const method = messageTopic.substring(this.pathname.length + 1); if (!allMethods.includes(method)) { - if (!allProperties.includes(method)) - this.console.warn('unknown topic', method); + if (!allProperties.includes(method)) { + if (!found?.subscriptions?.[method]) { + this.console.warn('unknown topic', method); + } + } return; } try { @@ -592,7 +597,7 @@ export class MqttProvider extends ScryptedDeviceBase implements DeviceProvider, return isPublishable(type, interfaces) ? [ScryptedInterface.Settings] : undefined; } - async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState:WritableDeviceState): Promise { + async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: WritableDeviceState): Promise { return new MqttPublisherMixin(this, { mixinDevice, mixinDeviceState,