matterbridge-roborock-vacuum-plugin
Version:
Matterbridge Roborock Vacuum Plugin
130 lines • 6.26 kB
JavaScript
import { Parser } from 'binary-parser/dist/binary_parser.js';
import CRC32 from 'crc-32';
import { debugStringify } from 'matterbridge/logger';
import { ProtocolVersion } from '../../enums/index.js';
import { HeaderMessage, Protocol, ResponseBody, ResponseMessage, } from '../../models/index.js';
import { MessageSerializerFactory } from '../serializers/messageSerializerFactory.js';
export class MessageDeserializer {
context;
headerMessageParser;
contentMessageParser;
logger;
supportedVersions = [
ProtocolVersion.V1,
ProtocolVersion.A01,
ProtocolVersion.B01,
ProtocolVersion.L01,
];
protocolsWithoutPayload = [
Protocol.hello_request,
Protocol.hello_response,
Protocol.ping_request,
Protocol.ping_response,
Protocol.general_response,
];
ignoredProtocols = [Protocol.map_response];
messageSerializerFactory = new MessageSerializerFactory();
constructor(context, logger) {
this.context = context;
this.logger = logger;
this.headerMessageParser = new Parser()
.endianness('big')
.string('version', {
length: 3,
})
.uint32('seq')
.uint32('nonce')
.uint32('timestamp')
.uint16('protocol');
this.contentMessageParser = new Parser()
.endianness('big')
.uint16('payloadLen')
.buffer('payload', {
length: 'payloadLen',
})
.uint32('crc32');
}
deserialize(duid, message, from) {
const rawHeader = this.headerMessageParser.parse(message);
const header = new HeaderMessage(rawHeader.version, rawHeader.seq, rawHeader.nonce, rawHeader.timestamp, rawHeader.protocol);
this.logger.debug(`[${from}][MessageDeserializer] deserialized header: ${debugStringify(header)}`);
if (!this.supportedVersions.includes(header.version)) {
throw new Error(`[${from}][MessageDeserializer] unknown protocol: ${header.version ?? ''}`);
}
if (this.protocolsWithoutPayload.includes(header.protocol)) {
// || this.ignoredProtocols.includes(header.protocol)) {
const responseMessage = new ResponseMessage(duid, header, undefined);
this.logger.debug(`[${from}][MessageDeserializer] deserialized message without payload: ${debugStringify(responseMessage)}`);
return responseMessage;
}
// parse message content
const data = this.contentMessageParser.parse(message.subarray(this.headerMessageParser.sizeOf()));
// check crc32
const crc32 = CRC32.buf(message.subarray(0, message.length - 4)) >>> 0;
const expectedCrc32 = message.readUInt32BE(message.length - 4);
if (crc32 != expectedCrc32) {
throw new Error(`Wrong CRC32 ${crc32}, expected ${expectedCrc32}`);
}
const localKey = this.context.getLocalKey(duid);
if (!localKey) {
this.logger.notice(`Unable to retrieve local key for ${duid}, it should be from other vacuums`);
return new ResponseMessage(duid, header, undefined);
}
const connectNonce = this.context.nonce;
const ackNonce = this.context.getDeviceNonce(duid);
const messageSerializer = this.messageSerializerFactory.getMessageSerializer(header.version);
data.payload = messageSerializer.decode(data.payload, localKey, header.timestamp, header.seq, header.nonce, connectNonce, ackNonce);
if (header.isForProtocol(Protocol.rpc_response) || header.isForProtocol(Protocol.general_request)) {
const response = this.deserializeRpcResponse(duid, data, header);
this.logger.debug(`[${from}][MessageDeserializer] deserialized body: ${debugStringify(response.body ?? {})}`);
return response;
}
else if (header.protocol === Protocol.map_response) {
const body = {
[Protocol.map_response]: data.payload,
};
const responseMessage = new ResponseMessage(duid, header, new ResponseBody(body));
this.logger.debug(`[${from}][MessageDeserializer] deserialized message for map_response: ${debugStringify(responseMessage)}`);
return responseMessage;
}
else if (header.protocol === Protocol.device_status_ota) {
return this.deserializeDeviceStatusOta(duid, data, header, from);
}
else {
this.logger.error(`unknown protocol: ${header.protocol}`);
const response = this.deserializeUnknownProtocolPayload(duid, data, header);
this.logger.debug(`[${from}][MessageDeserializer] deserialized body: ${debugStringify(response.body ?? {})}`);
return new ResponseMessage(duid, header, undefined);
}
}
deserializeRpcResponse(duid, data, header) {
const payload = JSON.parse(data.payload.toString());
const dps = payload.dps;
this.parseJsonInDps(dps, Protocol.general_request);
this.parseJsonInDps(dps, Protocol.rpc_response);
return new ResponseMessage(duid, header, new ResponseBody(dps));
}
parseJsonInDps(dps, index) {
const indexString = index.toString();
if (dps[indexString] !== undefined) {
dps[indexString] = JSON.parse(dps[indexString]);
}
}
deserializeDeviceStatusOta(duid, data, header, from) {
try {
const parsedData = JSON.parse(data.payload.toString('utf8'));
return new ResponseMessage(duid, header, new ResponseBody({ [Protocol.device_status_ota]: parsedData }));
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
this.logger.warn(`[${from}][MessageDeserializer][${duid}] device_status_ota parse error: ${message}`);
}
return new ResponseMessage(duid, header, undefined);
}
deserializeUnknownProtocolPayload(duid, data, header) {
const payload = JSON.parse(data.payload.toString());
const dps = payload.dps;
return new ResponseMessage(duid, header, new ResponseBody(dps));
}
}
//# sourceMappingURL=messageDeserializer.js.map