matterbridge-roborock-vacuum-plugin
Version:
Matterbridge Roborock Vacuum Plugin
140 lines (139 loc) • 5.53 kB
JavaScript
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);
}
}
}