UNPKG

matterbridge-roborock-vacuum-plugin

Version:
131 lines (107 loc) 4.14 kB
import mqtt, { ErrorWithReasonCode, IConnackPacket, MqttClient as MqttLibClient } from 'mqtt'; import * as CryptoUtils from '../../helper/cryptoHelper.js'; import { RequestMessage } from '../model/requestMessage.js'; import { AbstractClient } from '../abstractClient.js'; import { MessageContext } from '../model/messageContext.js'; import { Rriot, UserData } from '../../Zmodel/userData.js'; import { AnsiLogger, debugStringify } from 'matterbridge/logger'; export class MQTTClient extends AbstractClient { private readonly rriot: Rriot; private readonly mqttUsername: string; private readonly mqttPassword: string; private client: MqttLibClient | undefined = undefined; public constructor(logger: AnsiLogger, context: MessageContext, userdata: 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); } public connect(): void { if (this.client) { return; } this.client = mqtt.connect(this.rriot.r.m, { clientId: this.mqttUsername, username: this.mqttUsername, password: this.mqttPassword, keepalive: 30, log: () => { // ...args: unknown[] - this.logger.debug('MQTTClient args:: ' + args[0]); }, }); this.client.on('connect', this.onConnect.bind(this)); this.client.on('error', this.onError.bind(this)); this.client.on('reconnect', this.onReconnect.bind(this)); this.client.on('close', this.onDisconnect.bind(this)); this.client.on('disconnect', this.onDisconnect.bind(this)); this.client.on('offline', this.onDisconnect.bind(this)); // message events this.client.on('message', this.onMessage.bind(this)); } public async disconnect(): Promise<void> { if (!this.client || !this.connected) { return; } try { this.client.end(); } catch (error) { this.logger.error('MQTT client failed to disconnect with error: ' + error); } } public async send(duid: string, request: RequestMessage): Promise<void> { if (!this.client || !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.client.publish('rr/m/i/' + this.rriot.u + '/' + this.mqttUsername + '/' + duid, message.buffer, { qos: 1 }); } private async onConnect(result: IConnackPacket) { if (!result) { return; } this.connected = true; await this.connectionListeners.onConnected(); this.subscribeToQueue(); } private subscribeToQueue() { if (!this.client) { return; } this.client.subscribe('rr/m/o/' + this.rriot.u + '/' + this.mqttUsername + '/#', this.onSubscribe.bind(this)); } private async onSubscribe(err: Error | null) { if (!err) { return; } this.logger.error('failed to subscribe to the queue: ' + err); this.connected = false; await this.connectionListeners.onDisconnected(); } private async onDisconnect() { await this.connectionListeners.onDisconnected(); } private async onError(result: Error | ErrorWithReasonCode) { this.logger.error('MQTT connection error: ' + result); this.connected = false; await this.connectionListeners.onError(result.toString()); } private onReconnect() { this.subscribeToQueue(); } private async onMessage(topic: string, message: Buffer<ArrayBufferLike>) { if (!message) { // Ignore empty messages 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); } } }