UNPKG

matterbridge-dyson-robot

Version:

A Matterbridge plugin that connects Dyson robot vacuums and air treatment devices to the Matter smart home ecosystem via their local or cloud MQTT APIs.

96 lines 4 kB
// Matterbridge plugin for Dyson robot vacuum and air treatment devices // Copyright © 2025 Alexander Thoukydides import EventEmitter from 'events'; import { formatList, plural, tryListener } from './utils.js'; // Manage Dyson MQTT topic subscriptions export class DysonMqttSubscribe extends EventEmitter { log; mqtt; config; topics; root_topic; username; // Construct a new subscription manager constructor(log, mqtt, config, topics, root_topic, username) { super({ captureRejections: true }); this.log = log; this.mqtt = mqtt; this.config = config; this.topics = topics; this.root_topic = root_topic; this.username = username; // (Re)subscribe to topics when the MQTT (re)connects mqtt.on('connect', tryListener(this, async (_connack) => { await this.subscribe(); this.emit('subscribed'); })); } // Subscribe to topics when the client (re)connects async subscribe() { // Select the required subscription topics const replacePlaceholders = (topics) => topics.map(t => this.replaceTopicPlaceholders(t)); const topics = replacePlaceholders(this.topics.subscribe); if (this.config.wildcardTopic) { if (this.config.provisioningMethod !== 'Remote Account') { // Use full wildcard topic for local connections topics.push('#'); } else { // AWS IoT disconnects on wildcards; subscribe to command topic topics.push(this.replaceTopicPlaceholders(this.topics.command)); } } // Attempt the subscription this.log.debug(`MQTT subscribe: ${formatList(topics)}`); const grant = await this.mqtt.subscribeAsync(topics, { qos: 1 }); // Check whether const failures = topics.filter(topic => !grant.some(g => g.topic === topic)); if (!grant.length) { throw new Error(`MQTT subscribe unsuccessful: all ${plural(topics.length, 'topic')} rejected`); } else if (failures.length) { this.log.warn('MQTT subscribe partially successful: ' + `${failures.length} of ${plural(topics.length, 'topic')} rejected`); failures.forEach(topic => { this.log.warn(` '${topic}'`); }); } else { this.log.info(`MQTT subscribe successful: all ${plural(grant.length, 'topic')} granted`); } } // Warn if a received message's topic is unexpected checkTopic(topic) { // Check whether the topic is expected const isTopics = (topics) => topics.some(t => this.replaceTopicPlaceholders(t) === topic); if (isTopics([this.topics.command])) return 'command'; if (isTopics(this.topics.subscribe)) return 'subscribed'; if (isTopics(this.topics.other ?? [])) return 'expected'; // Attempt to diagnose common problems const [root_topic, username] = topic.split('/', 2); if (root_topic !== this.root_topic) { this.log.warn('MQTT topic root (product type) mismatch:' + ` expected '${this.root_topic}', received '${root_topic}'`); } else if (username !== this.username) { this.log.warn('MQTT topic username (product serial number) mismatch:' + ` expected '${this.username}', received '${username}'`); } else { this.log.warn(`Unexpected MQTT topic received: ${topic}`); } return 'unexpected'; } // The full topic for publishing commands get commandTopic() { return this.replaceTopicPlaceholders(this.topics.command); } // Replace any placeholders in topic replaceTopicPlaceholders(topic) { return topic .replace('@', this.root_topic) .replace('@', this.username); } } //# sourceMappingURL=dyson-mqtt-subscribe.js.map