From 74e381f0099241947e358bc97c6ebf4280a9e2d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Hagelb=C3=A4ck?= Date: Mon, 26 Apr 2021 13:13:10 +0200 Subject: [PATCH] Device triggers for scenes implemented --- plejd/DeviceRegistry.js | 2 ++ plejd/MqttClient.js | 76 +++++++++++++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/plejd/DeviceRegistry.js b/plejd/DeviceRegistry.js index 6f07155..a031752 100644 --- a/plejd/DeviceRegistry.js +++ b/plejd/DeviceRegistry.js @@ -72,6 +72,8 @@ class DeviceRegistry { ...this.sceneDevices, [scene.uniqueId]: scene, }; + this.sceneUniqueIdByBleOutputAddress[scene.bleOutputAddress] = scene.uniqueId; + logger.verbose( `Added/updated scene: ${JSON.stringify(scene)}. ${ Object.keys(this.sceneDevices).length diff --git a/plejd/MqttClient.js b/plejd/MqttClient.js index 75e0b4a..92d7f4c 100644 --- a/plejd/MqttClient.js +++ b/plejd/MqttClient.js @@ -19,20 +19,6 @@ const MQTT_TYPES = { SWITCH: 'switch', }; -const getMqttType = (/** @type {{ uniqueId: string; type: string; }} */ plug) => (plug.type === 'scene' ? MQTT_TYPES.SCENE : plug.type); - -const getBaseTopic = (/** @type {{ uniqueId: string; type: string; }} */ plug) => `${discoveryPrefix}/${getMqttType(plug)}/${nodeId}/${plug.uniqueId}`; -const getSceneEventTopic = () => 'plejd/event/scene'; -const getSubscribePath = () => `${discoveryPrefix}/+/${nodeId}/#`; - -const getTopicName = ( - /** @type {{ uniqueId: string; type: string; }} */ plug, - /** @type {'config' | 'state' | 'availability' | 'set'} */ topicType, -) => `${getBaseTopic(plug)}/${topicType}`; - -// Very loosely check if string is a GUID/UUID -const isGuid = (s) => /^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/.test(s); - const TOPICS = { CONFIG: 'config', STATE: 'state', @@ -40,6 +26,21 @@ const TOPICS = { COMMAND: 'set', }; +const getMqttType = (/** @type {{ uniqueId: string; type: string; }} */ plug) => (plug.type === 'scene' ? MQTT_TYPES.SCENE : plug.type); + +const getBaseTopic = (/** @type {{ uniqueId: string; type: string; }} */ plug) => `${discoveryPrefix}/${getMqttType(plug)}/${nodeId}/${plug.uniqueId}`; + +const getTopicName = ( + /** @type {{ uniqueId: string; type: string; }} */ plug, + /** @type {'config' | 'state' | 'availability' | 'set'} */ topicType, +) => `${getBaseTopic(plug)}/${topicType}`; + +const getSceneEventTopic = (sceneId) => `${getTopicName({ uniqueId: `${sceneId}_trigger`, type: 'device_automation' }, 'state')}`; +const getSubscribePath = () => `${discoveryPrefix}/+/${nodeId}/#`; + +// Very loosely check if string is a GUID/UUID +const isGuid = (s) => /^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/.test(s); + const decodeTopicRegexp = new RegExp( /(?[^[]+)\/(?.+)\/plejd\/(?.+)\/(?config|state|availability|set|scene)/, ); @@ -75,7 +76,7 @@ const getLightDiscoveryPayload = ( }, }); -const getScenehDiscoveryPayload = ( +const getSceneDiscoveryPayload = ( /** @type {import('./types/DeviceRegistry').OutputDevice} */ sceneDevice, ) => ({ name: sceneDevice.name, @@ -88,6 +89,26 @@ const getScenehDiscoveryPayload = ( retain: false, }); +const getSceneDeviceTriggerhDiscoveryPayload = ( + /** @type {import('./types/DeviceRegistry').OutputDevice} */ sceneDevice, +) => ({ + automation_type: 'trigger', + '~': getBaseTopic({ + uniqueId: sceneDevice.uniqueId, + type: 'device_automation', + }), + qos: 1, + topic: `~/${TOPICS.STATE}`, + type: 'scene', + subtype: 'trigger', + device: { + identifiers: `${sceneDevice.uniqueId}`, + manufacturer: 'Plejd', + model: sceneDevice.typeName, + name: sceneDevice.name, + }, +}); + // #endregion const getMqttStateString = (/** @type {boolean} */ state) => (state ? 'ON' : 'OFF'); @@ -272,15 +293,34 @@ class MqttClient extends EventEmitter { allSceneDevices.forEach((sceneDevice) => { logger.debug(`Sending discovery for ${sceneDevice.name}`); - const configPayload = getScenehDiscoveryPayload(sceneDevice); + const sceneConfigPayload = getSceneDiscoveryPayload(sceneDevice); logger.info( `Discovered ${sceneDevice.typeName} (${sceneDevice.type}) named ${sceneDevice.name} (${sceneDevice.bleOutputAddress} : ${sceneDevice.uniqueId}).`, ); - this.client.publish(getTopicName(sceneDevice, 'config'), JSON.stringify(configPayload), { + this.client.publish(getTopicName(sceneDevice, 'config'), JSON.stringify(sceneConfigPayload), { retain: true, qos: 1, }); + + const sceneTriggerConfigPayload = getSceneDeviceTriggerhDiscoveryPayload(sceneDevice); + + this.client.publish( + getTopicName( + { + ...sceneDevice, + uniqueId: `${sceneDevice.uniqueId}_trigger`, + type: 'device_automation', + }, + 'config', + ), + JSON.stringify(sceneTriggerConfigPayload), + { + retain: true, + qos: 1, + }, + ); + setTimeout(() => { this.client.publish(getTopicName(sceneDevice, 'availability'), AVAILABLILITY.ONLINE, { retain: true, @@ -338,7 +378,7 @@ class MqttClient extends EventEmitter { */ sceneTriggered(sceneId) { logger.verbose(`Scene triggered: ${sceneId}`); - this.client.publish(getSceneEventTopic(), JSON.stringify({ scene: sceneId }), { qos: 1 }); + this.client.publish(getSceneEventTopic(sceneId), '', { qos: 1 }); } }