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 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ export var DeviceModel;
2
+ (function (DeviceModel) {
3
+ DeviceModel["Q5"] = "roborock.vacuum.a34";
4
+ DeviceModel["Q5_PRO"] = "roborock.vacuum.a72";
5
+ DeviceModel["S5"] = "roborock.vacuum.s5";
6
+ DeviceModel["S5_MAX"] = "roborock.vacuum.s5e";
7
+ DeviceModel["S6"] = "roborock.vacuum.s6";
8
+ DeviceModel["S6_MAXV"] = "roborock.vacuum.a10";
9
+ DeviceModel["S6_PURE"] = "roborock.vacuum.a08";
10
+ DeviceModel["Q7"] = "roborock.vacuum.a40";
11
+ DeviceModel["Q7_MAX"] = "roborock.vacuum.a38";
12
+ DeviceModel["Q7_PLUS"] = "roborock.vacuum.a40";
13
+ DeviceModel["S7"] = "roborock.vacuum.a15";
14
+ DeviceModel["S7_MAXV"] = "roborock.vacuum.a27";
15
+ DeviceModel["S7_MAXV_ULTRA"] = "roborock.vacuum.a65";
16
+ DeviceModel["S7_PRO_ULTRA"] = "roborock.vacuum.a62";
17
+ DeviceModel["Q8_MAX"] = "roborock.vacuum.a73";
18
+ DeviceModel["S8"] = "roborock.vacuum.a51";
19
+ DeviceModel["S8_PRO_ULTRA"] = "roborock.vacuum.a70";
20
+ DeviceModel["S8_MAXV_ULTRA"] = "roborock.vacuum.a97";
21
+ DeviceModel["QREVO_MASTER"] = "roborock.vacuum.a117";
22
+ DeviceModel["QREVO_CURV"] = "roborock.vacuum.a135";
23
+ DeviceModel["QREVO_S"] = "roborock.vacuum.a104";
24
+ DeviceModel["QREVO_PRO"] = "roborock.vacuum.a101";
25
+ DeviceModel["QREVO_MAXV"] = "roborock.vacuum.a87";
26
+ DeviceModel["QREVO_EDGE_5V1"] = "roborock.vacuum.a187";
27
+ DeviceModel["QREVO_EDGE_5AE"] = "roborock.vacuum.xxxx";
28
+ })(DeviceModel || (DeviceModel = {}));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import { DockInfo } from './dockInfo.js';
2
+ import { VacuumError } from './vacuumError.js';
3
+ export class DeviceStatus {
4
+ errorStatus;
5
+ message;
6
+ constructor(message) {
7
+ this.message = message;
8
+ this.errorStatus = new VacuumError(message.error_code, message.dock_error_status);
9
+ }
10
+ getBattery() {
11
+ return this.message.battery;
12
+ }
13
+ getVacuumErrorCode() {
14
+ return this.message.error_code;
15
+ }
16
+ getDockInfo() {
17
+ return new DockInfo(this.message.dock_type);
18
+ }
19
+ getDockErrorCode() {
20
+ return this.message.dock_error_status;
21
+ }
22
+ }
@@ -0,0 +1,6 @@
1
+ export class DockInfo {
2
+ dockType;
3
+ constructor(dockType) {
4
+ this.dockType = dockType;
5
+ }
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export var CarpetCleanMode;
2
+ (function (CarpetCleanMode) {
3
+ CarpetCleanMode[CarpetCleanMode["Avoid"] = 0] = "Avoid";
4
+ CarpetCleanMode[CarpetCleanMode["Ignore"] = 2] = "Ignore";
5
+ CarpetCleanMode[CarpetCleanMode["Cross"] = 3] = "Cross";
6
+ CarpetCleanMode[CarpetCleanMode["DynamicLift"] = 200] = "DynamicLift";
7
+ })(CarpetCleanMode || (CarpetCleanMode = {}));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import decodeComponent from '../helper/nameDecoder.js';
2
+ export class RoomInfo {
3
+ rooms = [];
4
+ constructor(roomInfo, roomData) {
5
+ this.rooms = roomData
6
+ .map((entry) => {
7
+ return { id: entry[0], globalId: entry[1] };
8
+ })
9
+ .map((entry) => {
10
+ return {
11
+ id: entry.id,
12
+ room: roomInfo.find((el) => el.id == entry.globalId),
13
+ };
14
+ })
15
+ .map((entry) => {
16
+ return {
17
+ id: entry.id,
18
+ name: decodeComponent(entry.room?.name)?.toLowerCase(),
19
+ };
20
+ });
21
+ }
22
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { DockErrorCode, VacuumErrorCode } from '../Zenum/vacuumAndDockErrorCode.js';
2
+ export class VacuumError {
3
+ vacuumErrorCode;
4
+ dockErrorCode;
5
+ constructor(errorCode, dockErrorCode) {
6
+ this.vacuumErrorCode = errorCode;
7
+ this.dockErrorCode = dockErrorCode;
8
+ }
9
+ hasError() {
10
+ return this.vacuumErrorCode != 0 || this.dockErrorCode != 0;
11
+ }
12
+ isStuck() {
13
+ return this.vacuumErrorCode === VacuumErrorCode.RobotTrapped;
14
+ }
15
+ isBatteryLow() {
16
+ return this.vacuumErrorCode === VacuumErrorCode.LowBattery;
17
+ }
18
+ isBinFull() {
19
+ return this.vacuumErrorCode === VacuumErrorCode.CleanAutoEmptyDock || this.dockErrorCode == DockErrorCode.DuctBlockage;
20
+ }
21
+ isCleanWaterEmpty() {
22
+ return this.vacuumErrorCode === VacuumErrorCode.ClearWaterTankEmpty || this.dockErrorCode == DockErrorCode.WaterEmpty;
23
+ }
24
+ isWasteWaterFull() {
25
+ return this.dockErrorCode == DockErrorCode.WasteWaterTankFull;
26
+ }
27
+ }
@@ -0,0 +1,41 @@
1
+ import { MessageDeserializer } from '../helper/messageDeserializer.js';
2
+ import { MessageSerializer } from '../helper/messageSerializer.js';
3
+ import { ChainedConnectionListener } from './listener/implementation/chainedConnectionListener.js';
4
+ import { ChainedMessageListener } from './listener/implementation/chainedMessageListener.js';
5
+ import { SyncMessageListener } from './listener/implementation/syncMessageListener.js';
6
+ export class AbstractClient {
7
+ connectionListeners = new ChainedConnectionListener();
8
+ messageListeners = new ChainedMessageListener();
9
+ connected = false;
10
+ context;
11
+ serializer;
12
+ deserializer;
13
+ syncMessageListener;
14
+ logger;
15
+ constructor(logger, context) {
16
+ this.context = context;
17
+ this.serializer = new MessageSerializer(this.context);
18
+ this.deserializer = new MessageDeserializer(this.context);
19
+ this.syncMessageListener = new SyncMessageListener(logger);
20
+ this.messageListeners.register(this.syncMessageListener);
21
+ this.logger = logger;
22
+ }
23
+ async get(duid, request) {
24
+ return new Promise((resolve, reject) => {
25
+ this.syncMessageListener.waitFor(request.messageId, resolve, reject);
26
+ this.send(duid, request);
27
+ });
28
+ }
29
+ registerDevice(duid, localKey, pv) {
30
+ this.context.registerDevice(duid, localKey, pv);
31
+ }
32
+ registerConnectionListener(listener) {
33
+ this.connectionListeners.register(listener);
34
+ }
35
+ registerMessageListener(listener) {
36
+ this.messageListeners.register(listener);
37
+ }
38
+ isConnected() {
39
+ return this.connected;
40
+ }
41
+ }
@@ -0,0 +1,148 @@
1
+ import { Socket } from 'node:net';
2
+ import { clearInterval } from 'node:timers';
3
+ import { Protocol } from '../model/protocol.js';
4
+ import { RequestMessage } from '../model/requestMessage.js';
5
+ import { AbstractClient } from '../abstractClient.js';
6
+ import { Sequence } from '../../helper/sequence.js';
7
+ import { ChunkBuffer } from '../../helper/chunkBuffer.js';
8
+ export class LocalNetworkClient extends AbstractClient {
9
+ socket = undefined;
10
+ buffer = new ChunkBuffer();
11
+ messageIdSeq;
12
+ pingInterval;
13
+ duid;
14
+ ip;
15
+ constructor(logger, context, duid, ip) {
16
+ super(logger, context);
17
+ this.duid = duid;
18
+ this.ip = ip;
19
+ this.messageIdSeq = new Sequence(100000, 999999);
20
+ }
21
+ connect() {
22
+ this.socket = new Socket();
23
+ this.socket.on('close', this.onDisconnect.bind(this));
24
+ this.socket.on('end', this.onDisconnect.bind(this));
25
+ this.socket.on('error', this.onError.bind(this));
26
+ this.socket.on('data', this.onMessage.bind(this));
27
+ this.socket.connect(58867, this.ip, this.onConnect.bind(this));
28
+ }
29
+ async disconnect() {
30
+ if (!this.socket) {
31
+ return;
32
+ }
33
+ if (this.pingInterval) {
34
+ clearInterval(this.pingInterval);
35
+ }
36
+ this.socket.destroy();
37
+ this.socket = undefined;
38
+ }
39
+ async send(duid, request) {
40
+ if (!this.socket || !this.connected) {
41
+ this.logger.error(`failed to send request to ${duid}, socket is not online, ${JSON.stringify(request)}`);
42
+ return;
43
+ }
44
+ const localRequest = request.toLocalRequest();
45
+ const message = this.serializer.serialize(duid, localRequest);
46
+ this.logger.debug(`sending message ${message.messageId}, protocol:${localRequest.protocol}, method:${localRequest.method}, secure:${request.secure} to ${duid}`);
47
+ this.socket.write(this.wrapWithLengthData(message.buffer));
48
+ }
49
+ async onConnect() {
50
+ this.connected = true;
51
+ const address = this.socket?.address();
52
+ this.logger.debug(`${this.duid} connected to ${this.ip}, address: ${JSON.stringify(address)}`);
53
+ await this.sendHelloMessage();
54
+ this.pingInterval = setInterval(this.sendPingRequest.bind(this), 5000);
55
+ await this.connectionListeners.onConnected();
56
+ }
57
+ async onDisconnect() {
58
+ this.logger.info('Socket has disconnected.');
59
+ this.connected = false;
60
+ if (this.socket) {
61
+ this.socket.destroy();
62
+ this.socket = undefined;
63
+ }
64
+ if (this.pingInterval) {
65
+ clearInterval(this.pingInterval);
66
+ }
67
+ await this.connectionListeners.onDisconnected();
68
+ }
69
+ async onError(result) {
70
+ this.logger.error('Socket connection error: ' + result);
71
+ this.connected = false;
72
+ if (this.socket) {
73
+ this.socket.destroy();
74
+ this.socket = undefined;
75
+ }
76
+ await this.connectionListeners.onError(result.toString());
77
+ }
78
+ async onMessage(message) {
79
+ if (!this.socket) {
80
+ this.logger.error('unable to receive data if there is no socket available');
81
+ return;
82
+ }
83
+ if (!message || message.length == 0) {
84
+ this.logger.debug('LocalNetworkClient received empty message from socket.');
85
+ return;
86
+ }
87
+ try {
88
+ this.buffer.append(message);
89
+ const recvBuffer = this.buffer.get();
90
+ if (!this.isMessageComplete(recvBuffer)) {
91
+ return;
92
+ }
93
+ this.buffer.reset();
94
+ let offset = 0;
95
+ while (offset + 4 <= recvBuffer.length) {
96
+ const segmentLength = recvBuffer.readUInt32BE(offset);
97
+ if (segmentLength == 17) {
98
+ offset += 4 + segmentLength;
99
+ continue;
100
+ }
101
+ try {
102
+ const currentBuffer = recvBuffer.subarray(offset + 4, offset + segmentLength + 4);
103
+ const response = this.deserializer.deserialize(this.duid, currentBuffer);
104
+ await this.messageListeners.onMessage(response);
105
+ }
106
+ catch (error) {
107
+ this.logger.error('unable to process message: ' + error);
108
+ }
109
+ offset += 4 + segmentLength;
110
+ }
111
+ }
112
+ catch (error) {
113
+ this.logger.error('failed to read from a socket: ' + error);
114
+ }
115
+ }
116
+ isMessageComplete(buffer) {
117
+ let totalLength = 0;
118
+ let offset = 0;
119
+ while (offset + 4 <= buffer.length) {
120
+ const segmentLength = buffer.readUInt32BE(offset);
121
+ totalLength += 4 + segmentLength;
122
+ offset += 4 + segmentLength;
123
+ if (offset > buffer.length) {
124
+ return false;
125
+ }
126
+ }
127
+ return totalLength <= buffer.length;
128
+ }
129
+ wrapWithLengthData(buffer) {
130
+ const lengthBuffer = Buffer.alloc(4);
131
+ lengthBuffer.writeUInt32BE(buffer.length, 0);
132
+ return Buffer.concat([lengthBuffer, buffer]);
133
+ }
134
+ async sendHelloMessage() {
135
+ const request = new RequestMessage({
136
+ protocol: Protocol.hello_request,
137
+ messageId: this.messageIdSeq.next(),
138
+ });
139
+ await this.send(this.duid, request);
140
+ }
141
+ async sendPingRequest() {
142
+ const request = new RequestMessage({
143
+ protocol: Protocol.ping_request,
144
+ messageId: this.messageIdSeq.next(),
145
+ });
146
+ await this.send(this.duid, request);
147
+ }
148
+ }
@@ -0,0 +1,101 @@
1
+ import mqtt from 'mqtt';
2
+ import { CryptoUtils } from '../../helper/cryptoHelper.js';
3
+ import { AbstractClient } from '../abstractClient.js';
4
+ export class MQTTClient extends AbstractClient {
5
+ rriot;
6
+ mqttUsername;
7
+ mqttPassword;
8
+ client = undefined;
9
+ constructor(logger, context, userdata) {
10
+ super(logger, context);
11
+ this.rriot = userdata.rriot;
12
+ this.mqttUsername = CryptoUtils.md5hex(userdata.rriot.u + ':' + userdata.rriot.k).substring(2, 10);
13
+ this.mqttPassword = CryptoUtils.md5hex(userdata.rriot.s + ':' + userdata.rriot.k).substring(16);
14
+ }
15
+ connect() {
16
+ if (this.client) {
17
+ return;
18
+ }
19
+ this.client = mqtt.connect(this.rriot.r.m, {
20
+ clientId: this.mqttUsername,
21
+ username: this.mqttUsername,
22
+ password: this.mqttPassword,
23
+ keepalive: 30,
24
+ log: (...args) => {
25
+ },
26
+ });
27
+ this.client.on('connect', this.onConnect.bind(this));
28
+ this.client.on('error', this.onError.bind(this));
29
+ this.client.on('reconnect', this.onReconnect.bind(this));
30
+ this.client.on('close', this.onDisconnect.bind(this));
31
+ this.client.on('disconnect', this.onDisconnect.bind(this));
32
+ this.client.on('offline', this.onDisconnect.bind(this));
33
+ this.client.on('message', this.onMessage.bind(this));
34
+ }
35
+ async disconnect() {
36
+ if (!this.client || !this.connected) {
37
+ return;
38
+ }
39
+ try {
40
+ this.client.end();
41
+ }
42
+ catch (error) {
43
+ this.logger.error('MQTT client failed to disconnect with error: ' + error);
44
+ }
45
+ }
46
+ async send(duid, request) {
47
+ if (!this.client || !this.connected) {
48
+ return;
49
+ }
50
+ const mqttRequest = request.toMqttRequest();
51
+ const message = this.serializer.serialize(duid, mqttRequest);
52
+ this.client.publish('rr/m/i/' + this.rriot.u + '/' + this.mqttUsername + '/' + duid, message.buffer, { qos: 1 });
53
+ }
54
+ async onConnect(result) {
55
+ if (!result) {
56
+ return;
57
+ }
58
+ this.connected = true;
59
+ await this.connectionListeners.onConnected();
60
+ this.subscribeToQueue();
61
+ }
62
+ subscribeToQueue() {
63
+ if (!this.client) {
64
+ return;
65
+ }
66
+ this.client.subscribe('rr/m/o/' + this.rriot.u + '/' + this.mqttUsername + '/#', this.onSubscribe.bind(this));
67
+ }
68
+ async onSubscribe(err, granted) {
69
+ if (!err) {
70
+ return;
71
+ }
72
+ this.logger.error('failed to subscribe to the queue: ' + err);
73
+ this.connected = false;
74
+ await this.connectionListeners.onDisconnected();
75
+ }
76
+ async onDisconnect() {
77
+ await this.connectionListeners.onDisconnected();
78
+ }
79
+ async onError(result) {
80
+ this.logger.error('MQTT connection error: ' + result);
81
+ this.connected = false;
82
+ await this.connectionListeners.onError(result.toString());
83
+ }
84
+ onReconnect() {
85
+ this.subscribeToQueue();
86
+ }
87
+ async onMessage(topic, message) {
88
+ if (!message) {
89
+ this.logger.notice('MQTTClient received empty message from topic: ' + topic);
90
+ return;
91
+ }
92
+ try {
93
+ const duid = topic.split('/').slice(-1)[0];
94
+ const response = this.deserializer.deserialize(duid, message);
95
+ await this.messageListeners.onMessage(response);
96
+ }
97
+ catch (error) {
98
+ this.logger.error('unable to process message from queue ' + topic + ': ' + error);
99
+ }
100
+ }
101
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,79 @@
1
+ import { ChainedConnectionListener } from './listener/implementation/chainedConnectionListener.js';
2
+ import { ChainedMessageListener } from './listener/implementation/chainedMessageListener.js';
3
+ import { MessageContext } from './model/messageContext.js';
4
+ import { LocalNetworkClient } from './client/LocalNetworkClient.js';
5
+ import { MQTTClient } from './client/MQTTClient.js';
6
+ export class ClientRouter {
7
+ connectionListeners = new ChainedConnectionListener();
8
+ messageListeners = new ChainedMessageListener();
9
+ context;
10
+ mqttClient;
11
+ localClients = new Map();
12
+ logger;
13
+ constructor(logger, userdata) {
14
+ this.context = new MessageContext(userdata);
15
+ this.logger = logger;
16
+ this.mqttClient = new MQTTClient(logger, this.context, userdata);
17
+ this.mqttClient.registerConnectionListener(this.connectionListeners);
18
+ this.mqttClient.registerMessageListener(this.messageListeners);
19
+ }
20
+ registerDevice(duid, localKey, pv) {
21
+ this.context.registerDevice(duid, localKey, pv);
22
+ }
23
+ registerClient(duid, ip) {
24
+ const localClient = new LocalNetworkClient(this.logger, this.context, duid, ip);
25
+ localClient.registerConnectionListener(this.connectionListeners);
26
+ localClient.registerMessageListener(this.messageListeners);
27
+ this.localClients.set(duid, localClient);
28
+ return localClient;
29
+ }
30
+ unregisterClient(duid) {
31
+ this.localClients.delete(duid);
32
+ }
33
+ registerConnectionListener(listener) {
34
+ this.connectionListeners.register(listener);
35
+ }
36
+ registerMessageListener(listener) {
37
+ this.messageListeners.register(listener);
38
+ }
39
+ isConnected() {
40
+ return this.mqttClient.isConnected();
41
+ }
42
+ connect() {
43
+ this.mqttClient.connect();
44
+ this.localClients.forEach((client, duid) => {
45
+ client.connect();
46
+ });
47
+ }
48
+ async disconnect() {
49
+ await this.mqttClient.disconnect();
50
+ this.localClients.forEach((client, duid) => {
51
+ client.disconnect();
52
+ });
53
+ }
54
+ async send(duid, request) {
55
+ if (request.secure) {
56
+ await this.mqttClient.send(duid, request);
57
+ }
58
+ else {
59
+ await this.getClient(duid).send(duid, request);
60
+ }
61
+ }
62
+ async get(duid, request) {
63
+ if (request.secure) {
64
+ return await this.mqttClient.get(duid, request);
65
+ }
66
+ else {
67
+ return await this.getClient(duid).get(duid, request);
68
+ }
69
+ }
70
+ getClient(duid) {
71
+ const localClient = this.localClients.get(duid);
72
+ if (localClient === undefined || !localClient.isConnected()) {
73
+ return this.mqttClient;
74
+ }
75
+ else {
76
+ return localClient;
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,21 @@
1
+ export class ChainedConnectionListener {
2
+ listeners = [];
3
+ register(listener) {
4
+ this.listeners.push(listener);
5
+ }
6
+ async onConnected() {
7
+ for (const listener of this.listeners) {
8
+ await listener.onConnected();
9
+ }
10
+ }
11
+ async onDisconnected() {
12
+ for (const listener of this.listeners) {
13
+ await listener.onDisconnected();
14
+ }
15
+ }
16
+ async onError(message) {
17
+ for (const listener of this.listeners) {
18
+ await listener.onError(message);
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,11 @@
1
+ export class ChainedMessageListener {
2
+ listeners = [];
3
+ register(listener) {
4
+ this.listeners.push(listener);
5
+ }
6
+ async onMessage(message) {
7
+ for (const listener of this.listeners) {
8
+ await listener.onMessage(message);
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,27 @@
1
+ import { Protocol } from '../../model/protocol.js';
2
+ export class SimpleMessageListener {
3
+ handler;
4
+ registerListener(handler) {
5
+ this.handler = handler;
6
+ }
7
+ async onMessage(message) {
8
+ if (!this.handler || message.contain(Protocol.rpc_response) || message.contain(Protocol.map_response)) {
9
+ return;
10
+ }
11
+ if (message.contain(Protocol.status_update) && this.handler.onStatusChanged) {
12
+ await this.handler.onStatusChanged();
13
+ }
14
+ if (message.contain(Protocol.error) && this.handler.onError) {
15
+ const value = message.get(Protocol.error);
16
+ await this.handler.onError(Number(value));
17
+ }
18
+ if (message.contain(Protocol.battery) && this.handler.onBatteryUpdate) {
19
+ const value = message.get(Protocol.battery);
20
+ await this.handler.onBatteryUpdate(Number(value));
21
+ }
22
+ if (message.contain(Protocol.additional_props) && this.handler.onAdditionalProps) {
23
+ const value = message.get(Protocol.additional_props);
24
+ await this.handler.onAdditionalProps(Number(value));
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,35 @@
1
+ import { Protocol } from '../../model/protocol.js';
2
+ export class SyncMessageListener {
3
+ pending = new Map();
4
+ logger;
5
+ constructor(logger) {
6
+ this.logger = logger;
7
+ }
8
+ waitFor(messageId, resolve, reject) {
9
+ this.pending.set(messageId, resolve);
10
+ setTimeout(() => {
11
+ this.pending.delete(messageId);
12
+ reject();
13
+ }, 10000);
14
+ }
15
+ async onMessage(message) {
16
+ if (message.contain(Protocol.rpc_response)) {
17
+ const dps = message.get(Protocol.rpc_response);
18
+ const messageId = dps.id;
19
+ const responseHandler = this.pending.get(messageId);
20
+ if (dps.result.length == 1 && dps.result[0] == 'ok') {
21
+ return;
22
+ }
23
+ if (responseHandler) {
24
+ responseHandler(dps.result);
25
+ }
26
+ this.pending.delete(messageId);
27
+ return;
28
+ }
29
+ if (message.contain(Protocol.map_response)) {
30
+ const dps = message.get(Protocol.map_response);
31
+ this.pending.delete(dps.id);
32
+ return;
33
+ }
34
+ }
35
+ }
@@ -0,0 +1 @@
1
+ export { SimpleMessageListener } from './implementation/simpleMessageListener.js';