mqtt: switch auto discovery

This commit is contained in:
Koushik Dutta
2024-04-11 10:07:04 -07:00
parent cbd11908af
commit bf783c7c3c
4 changed files with 80 additions and 22 deletions

View File

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

View File

@@ -41,5 +41,5 @@
"@types/node": "^18.4.2",
"@types/nunjucks": "^3.2.0"
},
"version": "0.0.77"
"version": "0.0.78"
}

View File

@@ -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<string, Set<(payload: Buffer) => void>>();
this.debounceCallbacks = new Map<string, Set<(payload: Buffer) => 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<string, Set<(payload: Buffer) => void>>();
this.debounceCallbacks = new Map<string, Set<(payload: Buffer) => 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<void> {
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<any>, topic: string) => any;
subscriptions?: {
[topic: string]: (device: MixinDeviceBase<any>, event: MqttEvent) => void;
}
}
const autoDiscoveryMap = new Map<string, AutoDiscoveryConfig>();
@@ -676,7 +680,25 @@ autoDiscoveryMap.set(ScryptedInterface.HumiditySensor, {
}
});
export function publishAutoDiscovery(mqttId: string, client: Client, device: MixinDeviceBase<any>, 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<any>, 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;
}
}

View File

@@ -294,13 +294,15 @@ class MqttPublisherMixin extends SettingsMixinDeviceBase<any> {
allProperties.push(...properties);
}
let found: ReturnType<typeof publishAutoDiscovery>;
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<any> {
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<any> {
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: WritableDeviceState): Promise<any> {
return new MqttPublisherMixin(this, {
mixinDevice,
mixinDeviceState,