UNPKG

matterbridge-roborock-vacuum-plugin

Version:
140 lines (139 loc) 5.53 kB
import mqtt from 'mqtt'; import * as CryptoUtils from '../../helper/cryptoHelper.js'; import { AbstractClient } from '../abstractClient.js'; import { debugStringify } from 'matterbridge/logger'; export class MQTTClient extends AbstractClient { clientName = 'MQTTClient'; shouldReconnect = false; rriot; mqttUsername; mqttPassword; mqttClient = undefined; keepConnectionAliveInterval = undefined; constructor(logger, context, userdata) { super(logger, context); this.rriot = userdata.rriot; this.mqttUsername = CryptoUtils.md5hex(userdata.rriot.u + ':' + userdata.rriot.k).substring(2, 10); this.mqttPassword = CryptoUtils.md5hex(userdata.rriot.s + ':' + userdata.rriot.k).substring(16); this.initializeConnectionStateListener(); } connect() { if (this.mqttClient) { return; } this.mqttClient = mqtt.connect(this.rriot.r.m, { clientId: this.mqttUsername, username: this.mqttUsername, password: this.mqttPassword, keepalive: 30, log: () => { }, }); this.mqttClient.on('connect', this.onConnect.bind(this)); this.mqttClient.on('error', this.onError.bind(this)); this.mqttClient.on('reconnect', this.onReconnect.bind(this)); this.mqttClient.on('close', this.onClose.bind(this)); this.mqttClient.on('disconnect', this.onDisconnect.bind(this)); this.mqttClient.on('offline', this.onOffline.bind(this)); this.mqttClient.on('message', this.onMessage.bind(this)); this.keepConnectionAlive(); } async disconnect() { if (!this.mqttClient || !this.connected) { return; } try { this.isInDisconnectingStep = true; this.mqttClient.end(); } catch (error) { this.logger.error('MQTT client failed to disconnect with error: ' + error); } } async send(duid, request) { if (!this.mqttClient || !this.connected) { this.logger.error(`${duid}: mqtt is not available, ${debugStringify(request)}`); return; } const mqttRequest = request.toMqttRequest(); const message = this.serializer.serialize(duid, mqttRequest); this.logger.debug(`MQTTClient sending message to ${duid}: ${debugStringify(mqttRequest)}`); this.mqttClient.publish(`rr/m/i/${this.rriot.u}/${this.mqttUsername}/${duid}`, message.buffer, { qos: 1 }); this.logger.debug(`MQTTClient published message to topic: rr/m/i/${this.rriot.u}/${this.mqttUsername}/${duid}`); } keepConnectionAlive() { if (this.keepConnectionAliveInterval) { clearTimeout(this.keepConnectionAliveInterval); this.keepConnectionAliveInterval.unref(); } this.keepConnectionAliveInterval = setInterval(() => { if (this.mqttClient) { this.mqttClient.end(); this.mqttClient.reconnect(); } else { this.connect(); } }, 30 * 60 * 1000); } async onConnect(result) { if (!result) { return; } this.connected = true; await this.connectionListeners.onConnected('mqtt-' + this.mqttUsername); this.subscribeToQueue(); } subscribeToQueue() { if (!this.mqttClient || !this.connected) { return; } this.mqttClient.subscribe('rr/m/o/' + this.rriot.u + '/' + this.mqttUsername + '/#', this.onSubscribe.bind(this)); } async onSubscribe(err, granted) { if (!err) { this.logger.info('onSubscribe: ' + JSON.stringify(granted)); return; } this.logger.error('failed to subscribe: ' + err); this.connected = false; await this.connectionListeners.onDisconnected('mqtt-' + this.mqttUsername, 'Failed to subscribe to the queue: ' + err.toString()); } async onDisconnect() { this.connected = false; await this.connectionListeners.onDisconnected('mqtt-' + this.mqttUsername, 'Disconnected from MQTT broker'); } async onError(result) { this.logger.error('MQTT connection error: ' + result); this.connected = false; await this.connectionListeners.onError('mqtt-' + this.mqttUsername, result.toString()); } async onClose() { if (this.connected) { await this.connectionListeners.onDisconnected('mqtt-' + this.mqttUsername, 'MQTT connection closed'); } this.connected = false; } async onOffline() { this.connected = false; await this.connectionListeners.onDisconnected('mqtt-' + this.mqttUsername, 'MQTT connection offline'); } onReconnect() { this.subscribeToQueue(); this.connectionListeners.onReconnect('mqtt-' + this.mqttUsername, 'Reconnected to MQTT broker'); } async onMessage(topic, message) { if (!message) { this.logger.notice('MQTTClient received empty message from topic: ' + topic); return; } try { const duid = topic.split('/').slice(-1)[0]; const response = this.deserializer.deserialize(duid, message); await this.messageListeners.onMessage(response); } catch (error) { this.logger.error('MQTTClient: unable to process message with error: ' + topic + ': ' + error); } } }