matterbridge-roborock-vacuum-plugin 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/.github/workflows/publish.yml +34 -0
  2. package/.tarignore +5 -0
  3. package/LICENSE +202 -0
  4. package/README.md +34 -0
  5. package/README_DEV.md +69 -0
  6. package/bmc-button.svg +22 -0
  7. package/dist/appliances.js +6 -0
  8. package/dist/behaviorFactory.js +18 -0
  9. package/dist/behaviors/BehaviorDeviceGeneric.js +31 -0
  10. package/dist/behaviors/roborock.vacuum/QREVO_EDGE_5V1/a187.js +94 -0
  11. package/dist/behaviors/roborock.vacuum/QREVO_EDGE_5V1/initalData.js +81 -0
  12. package/dist/behaviors/roborock.vacuum/QREVO_EDGE_5V1/runtimes.js +39 -0
  13. package/dist/behaviors/roborock.vacuum/default/default.js +58 -0
  14. package/dist/behaviors/roborock.vacuum/default/initalData.js +66 -0
  15. package/dist/clientManager.js +17 -0
  16. package/dist/extensions/AxiosStaticExtensions.js +22 -0
  17. package/dist/extensions/index.js +1 -0
  18. package/dist/helper.js +16 -0
  19. package/dist/index.js +4 -0
  20. package/dist/initialData/getBatteryStatus.js +24 -0
  21. package/dist/initialData/getOperationalStates.js +22 -0
  22. package/dist/initialData/getSupportedAreas.js +69 -0
  23. package/dist/initialData/getSupportedCleanModes.js +11 -0
  24. package/dist/initialData/getSupportedRunModes.js +18 -0
  25. package/dist/initialData/index.js +5 -0
  26. package/dist/model/CloudMessageModel.js +1 -0
  27. package/dist/model/DockingStationStatus.js +24 -0
  28. package/dist/model/RoomMap.js +18 -0
  29. package/dist/notifyMessageTypes.js +9 -0
  30. package/dist/platform.js +146 -0
  31. package/dist/platformRunner.js +249 -0
  32. package/dist/roborockCommunication/RESTAPI/roborockAuthenticateApi.js +73 -0
  33. package/dist/roborockCommunication/RESTAPI/roborockIoTApi.js +61 -0
  34. package/dist/roborockCommunication/Zenum/dockType.js +4 -0
  35. package/dist/roborockCommunication/Zenum/operationStatusCode.js +44 -0
  36. package/dist/roborockCommunication/Zenum/vacuumAndDockErrorCode.js +68 -0
  37. package/dist/roborockCommunication/Zmodel/apiResponse.js +1 -0
  38. package/dist/roborockCommunication/Zmodel/authenticateResponse.js +1 -0
  39. package/dist/roborockCommunication/Zmodel/baseURL.js +1 -0
  40. package/dist/roborockCommunication/Zmodel/device.js +1 -0
  41. package/dist/roborockCommunication/Zmodel/deviceModel.js +28 -0
  42. package/dist/roborockCommunication/Zmodel/deviceSchema.js +1 -0
  43. package/dist/roborockCommunication/Zmodel/deviceStatus.js +22 -0
  44. package/dist/roborockCommunication/Zmodel/dockInfo.js +6 -0
  45. package/dist/roborockCommunication/Zmodel/home.js +1 -0
  46. package/dist/roborockCommunication/Zmodel/homeInfo.js +1 -0
  47. package/dist/roborockCommunication/Zmodel/messageResult.js +7 -0
  48. package/dist/roborockCommunication/Zmodel/networkInfo.js +1 -0
  49. package/dist/roborockCommunication/Zmodel/product.js +1 -0
  50. package/dist/roborockCommunication/Zmodel/room.js +1 -0
  51. package/dist/roborockCommunication/Zmodel/roomInfo.js +22 -0
  52. package/dist/roborockCommunication/Zmodel/userData.js +1 -0
  53. package/dist/roborockCommunication/Zmodel/vacuumError.js +27 -0
  54. package/dist/roborockCommunication/broadcast/abstractClient.js +41 -0
  55. package/dist/roborockCommunication/broadcast/client/LocalNetworkClient.js +148 -0
  56. package/dist/roborockCommunication/broadcast/client/MQTTClient.js +101 -0
  57. package/dist/roborockCommunication/broadcast/client.js +1 -0
  58. package/dist/roborockCommunication/broadcast/clientRouter.js +79 -0
  59. package/dist/roborockCommunication/broadcast/listener/abstractConnectionListener.js +1 -0
  60. package/dist/roborockCommunication/broadcast/listener/abstractMessageHandler.js +1 -0
  61. package/dist/roborockCommunication/broadcast/listener/abstractMessageListener.js +1 -0
  62. package/dist/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.js +21 -0
  63. package/dist/roborockCommunication/broadcast/listener/implementation/chainedMessageListener.js +11 -0
  64. package/dist/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.js +27 -0
  65. package/dist/roborockCommunication/broadcast/listener/implementation/syncMessageListener.js +35 -0
  66. package/dist/roborockCommunication/broadcast/listener/index.js +1 -0
  67. package/dist/roborockCommunication/broadcast/messageProcessor.js +79 -0
  68. package/dist/roborockCommunication/broadcast/model/dps.js +1 -0
  69. package/dist/roborockCommunication/broadcast/model/messageContext.js +26 -0
  70. package/dist/roborockCommunication/broadcast/model/protocol.js +19 -0
  71. package/dist/roborockCommunication/broadcast/model/requestMessage.js +33 -0
  72. package/dist/roborockCommunication/broadcast/model/responseMessage.js +14 -0
  73. package/dist/roborockCommunication/helper/chunkBuffer.js +17 -0
  74. package/dist/roborockCommunication/helper/cryptoHelper.js +27 -0
  75. package/dist/roborockCommunication/helper/messageDeserializer.js +74 -0
  76. package/dist/roborockCommunication/helper/messageSerializer.js +70 -0
  77. package/dist/roborockCommunication/helper/nameDecoder.js +66 -0
  78. package/dist/roborockCommunication/helper/sequence.js +16 -0
  79. package/dist/roborockCommunication/index.js +9 -0
  80. package/dist/roborockService.js +300 -0
  81. package/dist/rvc.js +39 -0
  82. package/dist/settings.js +1 -0
  83. package/dist/share/function.js +96 -0
  84. package/dist/share/runtimeHelper.js +29 -0
  85. package/eslint.config.js +76 -0
  86. package/matterbridge-roborock-vacuum-plugin.config.json +11 -0
  87. package/matterbridge-roborock-vacuum-plugin.schema.json +57 -0
  88. package/package.json +43 -0
  89. package/prettier.config.js +49 -0
  90. package/src/appliances.ts +15 -0
  91. package/src/behaviorFactory.ts +24 -0
  92. package/src/behaviors/BehaviorDeviceGeneric.ts +39 -0
  93. package/src/behaviors/roborock.vacuum/QREVO_EDGE_5V1/a187.ts +117 -0
  94. package/src/behaviors/roborock.vacuum/QREVO_EDGE_5V1/initalData.ts +85 -0
  95. package/src/behaviors/roborock.vacuum/QREVO_EDGE_5V1/runtimes.ts +29 -0
  96. package/src/behaviors/roborock.vacuum/default/default.ts +78 -0
  97. package/src/behaviors/roborock.vacuum/default/initalData.ts +69 -0
  98. package/src/clientManager.ts +23 -0
  99. package/src/extensions/AxiosStaticExtensions.ts +35 -0
  100. package/src/extensions/index.ts +1 -0
  101. package/src/helper.ts +22 -0
  102. package/src/index.ts +16 -0
  103. package/src/initialData/getBatteryStatus.ts +26 -0
  104. package/src/initialData/getOperationalStates.ts +24 -0
  105. package/src/initialData/getSupportedAreas.ts +95 -0
  106. package/src/initialData/getSupportedCleanModes.ts +13 -0
  107. package/src/initialData/getSupportedRunModes.ts +21 -0
  108. package/src/initialData/index.ts +5 -0
  109. package/src/model/CloudMessageModel.ts +13 -0
  110. package/src/model/DockingStationStatus.ts +37 -0
  111. package/src/model/RoomMap.ts +44 -0
  112. package/src/notifyMessageTypes.ts +8 -0
  113. package/src/platform.ts +192 -0
  114. package/src/platformRunner.ts +292 -0
  115. package/src/roborockCommunication/RESTAPI/roborockAuthenticateApi.ts +98 -0
  116. package/src/roborockCommunication/RESTAPI/roborockIoTApi.ts +70 -0
  117. package/src/roborockCommunication/Zenum/dockType.ts +3 -0
  118. package/src/roborockCommunication/Zenum/operationStatusCode.ts +43 -0
  119. package/src/roborockCommunication/Zenum/vacuumAndDockErrorCode.ts +68 -0
  120. package/src/roborockCommunication/Zmodel/apiResponse.ts +3 -0
  121. package/src/roborockCommunication/Zmodel/authenticateResponse.ts +5 -0
  122. package/src/roborockCommunication/Zmodel/baseURL.ts +5 -0
  123. package/src/roborockCommunication/Zmodel/device.ts +39 -0
  124. package/src/roborockCommunication/Zmodel/deviceModel.ts +27 -0
  125. package/src/roborockCommunication/Zmodel/deviceSchema.ts +8 -0
  126. package/src/roborockCommunication/Zmodel/deviceStatus.ts +30 -0
  127. package/src/roborockCommunication/Zmodel/dockInfo.ts +9 -0
  128. package/src/roborockCommunication/Zmodel/home.ts +13 -0
  129. package/src/roborockCommunication/Zmodel/homeInfo.ts +5 -0
  130. package/src/roborockCommunication/Zmodel/messageResult.ts +72 -0
  131. package/src/roborockCommunication/Zmodel/networkInfo.ts +7 -0
  132. package/src/roborockCommunication/Zmodel/product.ts +9 -0
  133. package/src/roborockCommunication/Zmodel/room.ts +4 -0
  134. package/src/roborockCommunication/Zmodel/roomInfo.ts +25 -0
  135. package/src/roborockCommunication/Zmodel/userData.ts +23 -0
  136. package/src/roborockCommunication/Zmodel/vacuumError.ts +35 -0
  137. package/src/roborockCommunication/broadcast/abstractClient.ts +61 -0
  138. package/src/roborockCommunication/broadcast/client/LocalNetworkClient.ts +177 -0
  139. package/src/roborockCommunication/broadcast/client/MQTTClient.ts +129 -0
  140. package/src/roborockCommunication/broadcast/client.ts +19 -0
  141. package/src/roborockCommunication/broadcast/clientRouter.ts +100 -0
  142. package/src/roborockCommunication/broadcast/listener/abstractConnectionListener.ts +5 -0
  143. package/src/roborockCommunication/broadcast/listener/abstractMessageHandler.ts +11 -0
  144. package/src/roborockCommunication/broadcast/listener/abstractMessageListener.ts +5 -0
  145. package/src/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.ts +26 -0
  146. package/src/roborockCommunication/broadcast/listener/implementation/chainedMessageListener.ts +16 -0
  147. package/src/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.ts +37 -0
  148. package/src/roborockCommunication/broadcast/listener/implementation/syncMessageListener.ts +48 -0
  149. package/src/roborockCommunication/broadcast/listener/index.ts +3 -0
  150. package/src/roborockCommunication/broadcast/messageProcessor.ts +110 -0
  151. package/src/roborockCommunication/broadcast/model/dps.ts +17 -0
  152. package/src/roborockCommunication/broadcast/model/messageContext.ts +34 -0
  153. package/src/roborockCommunication/broadcast/model/protocol.ts +19 -0
  154. package/src/roborockCommunication/broadcast/model/requestMessage.ts +44 -0
  155. package/src/roborockCommunication/broadcast/model/responseMessage.ts +19 -0
  156. package/src/roborockCommunication/helper/chunkBuffer.ts +18 -0
  157. package/src/roborockCommunication/helper/cryptoHelper.ts +34 -0
  158. package/src/roborockCommunication/helper/messageDeserializer.ts +99 -0
  159. package/src/roborockCommunication/helper/messageSerializer.ts +82 -0
  160. package/src/roborockCommunication/helper/nameDecoder.ts +78 -0
  161. package/src/roborockCommunication/helper/sequence.ts +18 -0
  162. package/src/roborockCommunication/index.ts +15 -0
  163. package/src/roborockService.ts +379 -0
  164. package/src/rvc.ts +66 -0
  165. package/src/settings.ts +1 -0
  166. package/src/share/function.ts +106 -0
  167. package/src/share/runtimeHelper.ts +35 -0
  168. package/tsconfig.json +37 -0
  169. package/tsconfig.production.json +19 -0
  170. package/tslint.json +9 -0
@@ -0,0 +1,79 @@
1
+ import { RoomInfo } from '../Zmodel/roomInfo.js';
2
+ import { SimpleMessageListener } from './listener/index.js';
3
+ import { RequestMessage } from './model/requestMessage.js';
4
+ import { DeviceStatus } from '../Zmodel/deviceStatus.js';
5
+ export class MessageProcessor {
6
+ client;
7
+ messageListener;
8
+ logger;
9
+ constructor(client) {
10
+ this.client = client;
11
+ this.messageListener = new SimpleMessageListener();
12
+ this.client.registerMessageListener(this.messageListener);
13
+ }
14
+ injectLogger(logger) {
15
+ this.logger = logger;
16
+ }
17
+ registerListener(listener) {
18
+ this.messageListener.registerListener(listener);
19
+ }
20
+ async getNetworkInfo(duid) {
21
+ const request = new RequestMessage({ method: 'get_network_info' });
22
+ return await this.client.get(duid, request);
23
+ }
24
+ async getDeviceStatus(duid) {
25
+ const request = new RequestMessage({ method: 'get_status' });
26
+ const response = await this.client.get(duid, request);
27
+ this.logger?.debug('Device status: ', JSON.stringify(response));
28
+ return new DeviceStatus(response);
29
+ }
30
+ async getRooms(duid, rooms) {
31
+ const request = new RequestMessage({ method: 'get_room_mapping' });
32
+ return this.client.get(duid, request).then((response) => new RoomInfo(rooms, response));
33
+ }
34
+ async gotoDock(duid) {
35
+ const request = new RequestMessage({ method: 'app_charge' });
36
+ return this.client.send(duid, request);
37
+ }
38
+ async startClean(duid) {
39
+ const request = new RequestMessage({ method: 'app_start' });
40
+ return this.client.send(duid, request);
41
+ }
42
+ async startRoomClean(duid, roomIds, repeat) {
43
+ const request = new RequestMessage({
44
+ method: 'app_segment_clean',
45
+ params: [{ segments: roomIds, repeat: repeat }],
46
+ });
47
+ return this.client.send(duid, request);
48
+ }
49
+ async pauseClean(duid) {
50
+ const request = new RequestMessage({ method: 'app_pause' });
51
+ return this.client.send(duid, request);
52
+ }
53
+ async resumeClean(duid) {
54
+ const request = new RequestMessage({ method: 'app_resume' });
55
+ return this.client.send(duid, request);
56
+ }
57
+ async stopClean(duid) {
58
+ const request = new RequestMessage({ method: 'app_stop' });
59
+ return this.client.send(duid, request);
60
+ }
61
+ async sendCustomMessage(duid, def) {
62
+ const request = new RequestMessage(def);
63
+ return this.client.send(duid, request);
64
+ }
65
+ async getCustomMessage(duid, def) {
66
+ const response = this.client.get(duid, def);
67
+ this.logger?.warn('XXXXXXX: ', JSON.stringify(response));
68
+ return response;
69
+ }
70
+ async findMyRobot(duid) {
71
+ const request = new RequestMessage({ method: 'find_me' });
72
+ return this.client.send(duid, request);
73
+ }
74
+ async changeCleanMode(duid, suctionPower, waterFlow) {
75
+ this.logger?.notice(`Change clean mode for ${duid} to suctionPower: ${suctionPower}, waterFlow: ${waterFlow}`);
76
+ await this.client.send(duid, new RequestMessage({ method: 'set_custom_mode', params: [suctionPower] }));
77
+ await this.client.send(duid, new RequestMessage({ method: 'set_water_box_custom_mode', params: [waterFlow] }));
78
+ }
79
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { CryptoUtils } from '../../helper/cryptoHelper.js';
3
+ export class MessageContext {
4
+ endpoint;
5
+ nonce;
6
+ devices = new Map();
7
+ constructor(userdata) {
8
+ this.endpoint = CryptoUtils.md5bin(userdata.rriot.k).subarray(8, 14).toString('base64');
9
+ this.nonce = randomBytes(16);
10
+ }
11
+ registerDevice(duid, localKey, pv) {
12
+ this.devices.set(duid, { localKey: localKey, protocolVersion: pv });
13
+ }
14
+ getNonceAsHex() {
15
+ return this.nonce.toString('hex').toUpperCase();
16
+ }
17
+ getLocalKey(duid) {
18
+ return this.devices.get(duid)?.localKey;
19
+ }
20
+ getProtocolVersion(duid) {
21
+ return this.devices.get(duid)?.protocolVersion;
22
+ }
23
+ getEndpoint() {
24
+ return this.endpoint;
25
+ }
26
+ }
@@ -0,0 +1,19 @@
1
+ export var Protocol;
2
+ (function (Protocol) {
3
+ Protocol[Protocol["hello_request"] = 0] = "hello_request";
4
+ Protocol[Protocol["hello_response"] = 1] = "hello_response";
5
+ Protocol[Protocol["ping_request"] = 2] = "ping_request";
6
+ Protocol[Protocol["ping_response"] = 3] = "ping_response";
7
+ Protocol[Protocol["general_request"] = 4] = "general_request";
8
+ Protocol[Protocol["general_response"] = 5] = "general_response";
9
+ Protocol[Protocol["rpc_request"] = 101] = "rpc_request";
10
+ Protocol[Protocol["rpc_response"] = 102] = "rpc_response";
11
+ Protocol[Protocol["error"] = 120] = "error";
12
+ Protocol[Protocol["status_update"] = 121] = "status_update";
13
+ Protocol[Protocol["battery"] = 122] = "battery";
14
+ Protocol[Protocol["suction_power"] = 123] = "suction_power";
15
+ Protocol[Protocol["water_box_mode"] = 124] = "water_box_mode";
16
+ Protocol[Protocol["additional_props"] = 128] = "additional_props";
17
+ Protocol[Protocol["back_type"] = 139] = "back_type";
18
+ Protocol[Protocol["map_response"] = 301] = "map_response";
19
+ })(Protocol || (Protocol = {}));
@@ -0,0 +1,33 @@
1
+ import { Protocol } from './protocol.js';
2
+ import { randomInt } from 'crypto';
3
+ export class RequestMessage {
4
+ messageId;
5
+ protocol;
6
+ method;
7
+ params;
8
+ secure;
9
+ constructor(args) {
10
+ this.messageId = args.messageId ?? randomInt(10000, 32767);
11
+ this.protocol = args.protocol ?? Protocol.rpc_request;
12
+ this.method = args.method;
13
+ this.params = args.params;
14
+ this.secure = args.secure ?? false;
15
+ }
16
+ toMqttRequest() {
17
+ return this;
18
+ }
19
+ toLocalRequest() {
20
+ if (this.protocol == Protocol.rpc_request) {
21
+ return new RequestMessage({
22
+ messageId: this.messageId,
23
+ protocol: Protocol.general_request,
24
+ method: this.method,
25
+ params: this.params,
26
+ secure: this.secure,
27
+ });
28
+ }
29
+ else {
30
+ return this;
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,14 @@
1
+ export class ResponseMessage {
2
+ duid;
3
+ dps;
4
+ constructor(duid, dps) {
5
+ this.duid = duid;
6
+ this.dps = dps;
7
+ }
8
+ contain(index) {
9
+ return this.dps[index.toString()] !== undefined;
10
+ }
11
+ get(index) {
12
+ return this.dps[index.toString()];
13
+ }
14
+ }
@@ -0,0 +1,17 @@
1
+ export class ChunkBuffer {
2
+ buffer = Buffer.alloc(0);
3
+ get() {
4
+ return this.buffer;
5
+ }
6
+ reset() {
7
+ this.buffer = Buffer.alloc(0);
8
+ }
9
+ append(message) {
10
+ if (this.buffer.length == 0) {
11
+ this.buffer = message;
12
+ }
13
+ else {
14
+ this.buffer = Buffer.concat([this.buffer, message]);
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,27 @@
1
+ import crypto from 'crypto';
2
+ export class CryptoUtils {
3
+ static md5bin(str) {
4
+ return crypto.createHash('md5').update(str).digest();
5
+ }
6
+ static md5hex(str) {
7
+ return crypto.createHash('md5').update(str).digest('hex');
8
+ }
9
+ static decryptECB(encrypted, aesKey) {
10
+ const decipher = crypto.createDecipheriv('aes-128-ecb', aesKey, null);
11
+ decipher.setAutoPadding(false);
12
+ let decrypted = decipher.update(encrypted, 'binary', 'utf8');
13
+ decrypted += decipher.final('utf8');
14
+ return this.removePadding(decrypted);
15
+ }
16
+ static removePadding(str) {
17
+ const paddingLength = str.charCodeAt(str.length - 1);
18
+ return str.slice(0, -paddingLength);
19
+ }
20
+ }
21
+ export class MessageUtils {
22
+ static SALT = 'TXdfu$jyZ#TZHsg4';
23
+ static encodeTimestamp(timestamp) {
24
+ const hex = timestamp.toString(16).padStart(8, '0').split('');
25
+ return [5, 6, 3, 7, 1, 2, 0, 4].map((idx) => hex[idx]).join('');
26
+ }
27
+ }
@@ -0,0 +1,74 @@
1
+ import crypto from 'crypto';
2
+ import CRC32 from 'crc-32';
3
+ import { Parser } from 'binary-parser';
4
+ import assert from 'node:assert';
5
+ import { ResponseMessage } from '../broadcast/model/responseMessage.js';
6
+ import { CryptoUtils, MessageUtils } from './cryptoHelper.js';
7
+ import { Protocol } from '../broadcast/model/protocol.js';
8
+ export class MessageDeserializer {
9
+ context;
10
+ mqttMessageParser;
11
+ constructor(context) {
12
+ this.context = context;
13
+ this.mqttMessageParser = new Parser()
14
+ .endianess('big')
15
+ .string('version', {
16
+ length: 3,
17
+ })
18
+ .uint32('seq')
19
+ .uint32('random')
20
+ .uint32('timestamp')
21
+ .uint16('protocol')
22
+ .uint16('payloadLen')
23
+ .buffer('payload', {
24
+ length: 'payloadLen',
25
+ })
26
+ .uint32('crc32');
27
+ }
28
+ deserialize(duid, message) {
29
+ const version = message.toString('latin1', 0, 3);
30
+ if (version !== '1.0' && version !== 'A01') {
31
+ throw new Error('unknown protocol version ' + version);
32
+ }
33
+ const crc32 = CRC32.buf(message.subarray(0, message.length - 4)) >>> 0;
34
+ const expectedCrc32 = message.readUint32BE(message.length - 4);
35
+ if (crc32 != expectedCrc32) {
36
+ return new ResponseMessage(duid, { dps: { id: 0, result: null } });
37
+ }
38
+ const localKey = this.context.getLocalKey(duid);
39
+ assert(localKey, 'unable to retrieve local key for ' + duid);
40
+ const data = this.mqttMessageParser.parse(message);
41
+ if (version == '1.0') {
42
+ const aesKey = CryptoUtils.md5bin(MessageUtils.encodeTimestamp(data.timestamp) + localKey + MessageUtils.SALT);
43
+ const decipher = crypto.createDecipheriv('aes-128-ecb', aesKey, null);
44
+ data.payload = Buffer.concat([decipher.update(data.payload), decipher.final()]);
45
+ }
46
+ else if (version == 'A01') {
47
+ const iv = CryptoUtils.md5hex(data.random.toString(16).padStart(8, '0') + '726f626f726f636b2d67a6d6da').substring(8, 24);
48
+ const decipher = crypto.createDecipheriv('aes-128-cbc', localKey, iv);
49
+ data.payload = Buffer.concat([decipher.update(data.payload), decipher.final()]);
50
+ }
51
+ if (data.protocol == Protocol.map_response) {
52
+ return new ResponseMessage(duid, { dps: { id: 0, result: null } });
53
+ }
54
+ if (data.protocol == Protocol.rpc_response || data.protocol == Protocol.general_request) {
55
+ return this.deserializeProtocolRpcResponse(duid, data);
56
+ }
57
+ else {
58
+ return new ResponseMessage(duid, { dps: { id: 0, result: null } });
59
+ }
60
+ }
61
+ deserializeProtocolRpcResponse(duid, data) {
62
+ const payload = JSON.parse(data.payload.toString());
63
+ const dps = payload.dps;
64
+ this.parseJsonInDps(dps, Protocol.general_request);
65
+ this.parseJsonInDps(dps, Protocol.rpc_response);
66
+ return new ResponseMessage(duid, dps);
67
+ }
68
+ parseJsonInDps(dps, index) {
69
+ const indexString = index.toString();
70
+ if (dps[indexString] !== undefined) {
71
+ dps[indexString] = JSON.parse(dps[indexString]);
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,70 @@
1
+ import { CryptoUtils, MessageUtils } from './cryptoHelper.js';
2
+ import crypto from 'crypto';
3
+ import CRC32 from 'crc-32';
4
+ export class MessageSerializer {
5
+ context;
6
+ seq = 1;
7
+ random = 4711;
8
+ constructor(context) {
9
+ this.context = context;
10
+ }
11
+ serialize(duid, request) {
12
+ const messageId = request.messageId;
13
+ const buffer = this.buildBuffer(duid, messageId, request);
14
+ return { messageId: messageId, buffer: buffer };
15
+ }
16
+ buildPayload(messageId, request) {
17
+ const data = {
18
+ id: messageId,
19
+ method: request.method ?? '',
20
+ params: request.params,
21
+ security: undefined,
22
+ result: undefined,
23
+ };
24
+ if (request.secure) {
25
+ data.security = {
26
+ endpoint: this.context.getEndpoint(),
27
+ nonce: this.context.getNonceAsHex(),
28
+ };
29
+ }
30
+ const timestamp = Math.floor(Date.now() / 1000);
31
+ return {
32
+ dps: {
33
+ [request.protocol]: JSON.stringify(data),
34
+ },
35
+ t: timestamp,
36
+ };
37
+ }
38
+ buildBuffer(duid, messageId, request) {
39
+ const version = this.context.getProtocolVersion(duid);
40
+ const localKey = this.context.getLocalKey(duid);
41
+ const payloadData = this.buildPayload(messageId, request);
42
+ const payload = JSON.stringify(payloadData);
43
+ let encrypted;
44
+ if (version == '1.0') {
45
+ const aesKey = CryptoUtils.md5bin(MessageUtils.encodeTimestamp(payloadData.t) + localKey + MessageUtils.SALT);
46
+ const cipher = crypto.createCipheriv('aes-128-ecb', aesKey, null);
47
+ encrypted = Buffer.concat([cipher.update(payload), cipher.final()]);
48
+ }
49
+ else if (version == 'A01') {
50
+ const encoder = new TextEncoder();
51
+ const iv = CryptoUtils.md5hex(this.random.toString(16).padStart(8, '0') + '726f626f726f636b2d67a6d6da').substring(8, 24);
52
+ const cipher = crypto.createCipheriv('aes-128-cbc', encoder.encode(localKey), iv);
53
+ encrypted = Buffer.concat([cipher.update(payload), cipher.final()]);
54
+ }
55
+ else {
56
+ throw new Error('unable to build the message: unsupported protocol version: ' + version);
57
+ }
58
+ const msg = Buffer.alloc(23 + encrypted.length);
59
+ msg.write(version);
60
+ msg.writeUint32BE(this.seq++ & 0xffffffff, 3);
61
+ msg.writeUint32BE(this.random++ & 0xffffffff, 7);
62
+ msg.writeUint32BE(payloadData.t, 11);
63
+ msg.writeUint16BE(request.protocol, 15);
64
+ msg.writeUint16BE(encrypted.length, 17);
65
+ encrypted.copy(msg, 19);
66
+ const crc32 = CRC32.buf(msg.subarray(0, msg.length - 4)) >>> 0;
67
+ msg.writeUint32BE(crc32, msg.length - 4);
68
+ return msg;
69
+ }
70
+ }
@@ -0,0 +1,66 @@
1
+ const token = '%[a-f0-9]{2}';
2
+ const singleMatcher = new RegExp('(' + token + ')|([^%]+?)', 'gi');
3
+ const multiMatcher = new RegExp('(' + token + ')+', 'gi');
4
+ function decodeComponents(components, split = undefined) {
5
+ try {
6
+ return [decodeURIComponent(components.join(''))];
7
+ }
8
+ catch {
9
+ }
10
+ if (components.length === 1) {
11
+ return components;
12
+ }
13
+ split = split || 1;
14
+ const left = components.slice(0, split);
15
+ const right = components.slice(split);
16
+ return Array.prototype.concat.call([], decodeComponents(left), decodeComponents(right));
17
+ }
18
+ function decode(input) {
19
+ try {
20
+ return decodeURIComponent(input);
21
+ }
22
+ catch {
23
+ let tokens = input.match(singleMatcher) || [];
24
+ for (let i = 1; i < tokens.length; i++) {
25
+ input = decodeComponents(tokens, i).join('');
26
+ tokens = input.match(singleMatcher) || [];
27
+ }
28
+ return input;
29
+ }
30
+ }
31
+ function customDecodeURIComponent(input) {
32
+ const replaceMap = {
33
+ '%FE%FF': '\uFFFD\uFFFD',
34
+ '%FF%FE': '\uFFFD\uFFFD',
35
+ };
36
+ let match = multiMatcher.exec(input);
37
+ while (match) {
38
+ try {
39
+ replaceMap[match[0]] = decodeURIComponent(match[0]);
40
+ }
41
+ catch {
42
+ const result = decode(match[0]);
43
+ if (result !== match[0]) {
44
+ replaceMap[match[0]] = result;
45
+ }
46
+ }
47
+ match = multiMatcher.exec(input);
48
+ }
49
+ replaceMap['%C2'] = '\uFFFD';
50
+ const entries = Object.keys(replaceMap);
51
+ for (const key of entries) {
52
+ input = input.replace(new RegExp(key, 'g'), replaceMap[key]);
53
+ }
54
+ return input;
55
+ }
56
+ export default function decodeComponent(encodedURI) {
57
+ if (encodedURI === undefined) {
58
+ return undefined;
59
+ }
60
+ try {
61
+ return decodeURIComponent(encodedURI);
62
+ }
63
+ catch {
64
+ return customDecodeURIComponent(encodedURI);
65
+ }
66
+ }
@@ -0,0 +1,16 @@
1
+ export class Sequence {
2
+ min;
3
+ max;
4
+ current;
5
+ constructor(min, max) {
6
+ this.min = min;
7
+ this.max = max;
8
+ this.current = min;
9
+ }
10
+ next() {
11
+ if (this.current > this.max) {
12
+ this.current = this.min;
13
+ }
14
+ return this.current++;
15
+ }
16
+ }
@@ -0,0 +1,9 @@
1
+ export { RoborockAuthenticateApi } from './RESTAPI/roborockAuthenticateApi.js';
2
+ export { RoborockIoTApi } from './RESTAPI/roborockIoTApi.js';
3
+ export { MessageProcessor } from './broadcast/messageProcessor.js';
4
+ export { VacuumErrorCode } from './Zenum/vacuumAndDockErrorCode.js';
5
+ export { RequestMessage } from './broadcast/model/requestMessage.js';
6
+ export { Protocol } from './broadcast/model/protocol.js';
7
+ export { ClientRouter } from './broadcast/clientRouter.js';
8
+ export { DeviceStatus } from './Zmodel/deviceStatus.js';
9
+ export { ResponseMessage } from './broadcast/model/responseMessage.js';