eufy-security-client 2.4.2 → 2.4.4

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 (87) hide show
  1. package/README.md +12 -0
  2. package/build/error.d.ts +57 -57
  3. package/build/error.js +155 -155
  4. package/build/eufysecurity.d.ts +162 -161
  5. package/build/eufysecurity.js +2104 -2091
  6. package/build/eufysecurity.js.map +1 -1
  7. package/build/http/api.d.ts +90 -90
  8. package/build/http/api.js +1407 -1407
  9. package/build/http/api.js.map +1 -1
  10. package/build/http/cache.d.ts +8 -8
  11. package/build/http/cache.js +33 -33
  12. package/build/http/const.d.ts +3 -3
  13. package/build/http/const.js +8545 -8545
  14. package/build/http/device.d.ts +360 -360
  15. package/build/http/device.js +2793 -2793
  16. package/build/http/device.js.map +1 -1
  17. package/build/http/error.d.ts +28 -28
  18. package/build/http/error.js +76 -76
  19. package/build/http/index.d.ts +10 -10
  20. package/build/http/index.js +29 -29
  21. package/build/http/interfaces.d.ts +202 -202
  22. package/build/http/interfaces.js +2 -2
  23. package/build/http/models.d.ts +561 -561
  24. package/build/http/models.js +2 -2
  25. package/build/http/parameter.d.ts +5 -5
  26. package/build/http/parameter.js +75 -75
  27. package/build/http/station.d.ts +292 -292
  28. package/build/http/station.js +6780 -6780
  29. package/build/http/station.js.map +1 -1
  30. package/build/http/types.d.ts +945 -945
  31. package/build/http/types.js +6070 -6070
  32. package/build/http/utils.d.ts +37 -37
  33. package/build/http/utils.js +370 -370
  34. package/build/index.d.ts +7 -7
  35. package/build/index.js +25 -25
  36. package/build/interfaces.d.ts +113 -113
  37. package/build/interfaces.js +2 -2
  38. package/build/mqtt/interface.d.ts +6 -6
  39. package/build/mqtt/interface.js +2 -2
  40. package/build/mqtt/model.d.ts +24 -24
  41. package/build/mqtt/model.js +2 -2
  42. package/build/mqtt/service.d.ts +30 -30
  43. package/build/mqtt/service.js +168 -168
  44. package/build/mqtt/service.js.map +1 -1
  45. package/build/p2p/ble.d.ts +47 -47
  46. package/build/p2p/ble.js +188 -188
  47. package/build/p2p/ble.js.map +1 -1
  48. package/build/p2p/error.d.ts +24 -24
  49. package/build/p2p/error.js +67 -67
  50. package/build/p2p/index.d.ts +8 -8
  51. package/build/p2p/index.js +27 -27
  52. package/build/p2p/interfaces.d.ts +162 -162
  53. package/build/p2p/interfaces.js +2 -2
  54. package/build/p2p/models.d.ts +146 -146
  55. package/build/p2p/models.js +2 -2
  56. package/build/p2p/session.d.ts +168 -168
  57. package/build/p2p/session.js +2087 -2087
  58. package/build/p2p/session.js.map +1 -1
  59. package/build/p2p/talkback.d.ts +10 -10
  60. package/build/p2p/talkback.js +22 -22
  61. package/build/p2p/types.d.ts +923 -923
  62. package/build/p2p/types.js +957 -957
  63. package/build/p2p/utils.d.ts +56 -56
  64. package/build/p2p/utils.js +653 -653
  65. package/build/push/client.d.ts +51 -51
  66. package/build/push/client.js +311 -311
  67. package/build/push/client.js.map +1 -1
  68. package/build/push/index.d.ts +5 -5
  69. package/build/push/index.js +24 -24
  70. package/build/push/interfaces.d.ts +19 -19
  71. package/build/push/interfaces.js +2 -2
  72. package/build/push/models.d.ts +292 -292
  73. package/build/push/models.js +30 -30
  74. package/build/push/parser.d.ts +28 -28
  75. package/build/push/parser.js +215 -215
  76. package/build/push/parser.js.map +1 -1
  77. package/build/push/service.d.ts +45 -45
  78. package/build/push/service.js +643 -643
  79. package/build/push/service.js.map +1 -1
  80. package/build/push/types.d.ts +176 -176
  81. package/build/push/types.js +192 -192
  82. package/build/push/utils.d.ts +7 -7
  83. package/build/push/utils.js +102 -102
  84. package/build/utils.d.ts +16 -13
  85. package/build/utils.js +207 -191
  86. package/build/utils.js.map +1 -1
  87. package/package.json +10 -10
@@ -1,2088 +1,2088 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.P2PClientProtocol = void 0;
4
- const dgram_1 = require("dgram");
5
- const tiny_typed_emitter_1 = require("tiny-typed-emitter");
6
- const stream_1 = require("stream");
7
- const sweet_collections_1 = require("sweet-collections");
8
- const utils_1 = require("./utils");
9
- const types_1 = require("./types");
10
- const types_2 = require("../http/types");
11
- const device_1 = require("../http/device");
12
- const utils_2 = require("../http/utils");
13
- const talkback_1 = require("./talkback");
14
- const error_1 = require("../error");
15
- const types_3 = require("../push/types");
16
- const ble_1 = require("./ble");
17
- const http_1 = require("../http");
18
- const utils_3 = require("../utils");
19
- class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
20
- constructor(rawStation, api, ipAddress, publicKey = "") {
21
- super();
22
- this.MAX_RETRIES = 10;
23
- this.MAX_COMMAND_RESULT_WAIT = 30 * 1000;
24
- this.MAX_AKNOWLEDGE_TIMEOUT = 15 * 1000;
25
- this.MAX_LOOKUP_TIMEOUT = 15 * 1000;
26
- this.LOOKUP_RETRY_TIMEOUT = 3 * 1000;
27
- this.MAX_EXPECTED_SEQNO_WAIT = 20 * 1000;
28
- this.HEARTBEAT_INTERVAL = 5 * 1000;
29
- this.MAX_COMMAND_QUEUE_TIMEOUT = 120 * 1000;
30
- this.AUDIO_CODEC_ANALYZE_TIMEOUT = 650;
31
- this.KEEPALIVE_INTERVAL = 5 * 1000;
32
- this.ESD_DISCONNECT_TIMEOUT = 30 * 1000;
33
- this.MAX_STREAM_DATA_WAIT = 5 * 1000;
34
- this.RESEND_NOT_ACKNOWLEDGED_COMMAND = 100;
35
- this.UDP_RECVBUFFERSIZE_BYTES = 1048576;
36
- this.MAX_PAYLOAD_BYTES = 1028;
37
- this.MAX_PACKET_BYTES = 1024;
38
- this.MAX_VIDEO_PACKET_BYTES = 655360;
39
- this.P2P_DATA_HEADER_BYTES = 16;
40
- this.MAX_SEQUENCE_NUMBER = 65535;
41
- /*
42
- * SEQUENCE_PROCESSING_BOUNDARY is used to determine if an incoming sequence number
43
- * that is lower than the expected one was already processed.
44
- * If it is within the boundary, it is determined as 'already processed',
45
- * If it is even lower, it is assumed that the sequence count has reached
46
- * MAX_SEQUENCE_NUMBER and restarted at 0.
47
- * */
48
- this.SEQUENCE_PROCESSING_BOUNDARY = 20000; // worth of approx. 90 seconds of continous streaming
49
- this.binded = false;
50
- this.connected = false;
51
- this.connecting = false;
52
- this.terminating = false;
53
- this.seqNumber = 0;
54
- this.offsetDataSeqNumber = 0;
55
- this.videoSeqNumber = 0;
56
- this.lockSeqNumber = -1;
57
- this.expectedSeqNo = {};
58
- this.currentMessageBuilder = {};
59
- this.currentMessageState = {};
60
- this.downloadTotalBytes = 0;
61
- this.downloadReceivedBytes = 0;
62
- this.messageStates = new sweet_collections_1.SortedMap((a, b) => a - b);
63
- this.messageVideoStates = new sweet_collections_1.SortedMap((a, b) => a - b);
64
- this.sendQueue = new Array();
65
- this.connectTime = null;
66
- this.lastPong = null;
67
- this.lastPongData = undefined;
68
- this.connectionType = types_1.P2PConnectionType.QUICKEST;
69
- this.energySavingDevice = false;
70
- this.energySavingDeviceP2PSeqMapping = new Map();
71
- this.energySavingDeviceP2PDataSeqNumber = 0;
72
- this.connectAddress = undefined;
73
- this.localIPAddress = undefined;
74
- this.preferredIPAddress = undefined;
75
- this.dskKey = "";
76
- this.dskExpiration = null;
77
- this.deviceSNs = {};
78
- this.lockAESKeys = new Map();
79
- this.channel = 255;
80
- this.api = api;
81
- this.lockPublicKey = publicKey;
82
- this.preferredIPAddress = ipAddress;
83
- this.log = api.getLog();
84
- this.cloudAddresses = (0, utils_1.decodeP2PCloudIPs)(rawStation.app_conn);
85
- this.log.debug("Loaded P2P cloud ip addresses", this.cloudAddresses);
86
- this.updateRawStation(rawStation);
87
- this.socket = (0, dgram_1.createSocket)("udp4");
88
- this.socket.on("message", (msg, rinfo) => this.handleMsg(msg, rinfo));
89
- this.socket.on("error", (error) => this.onError(error));
90
- this.socket.on("close", () => this.onClose());
91
- this._initialize();
92
- }
93
- _incrementSequence(sequence) {
94
- if (sequence < this.MAX_SEQUENCE_NUMBER)
95
- return sequence + 1;
96
- return 0;
97
- }
98
- _isBetween(n, lowBoundary, highBoundary) {
99
- if (n < lowBoundary)
100
- return false;
101
- if (n >= highBoundary)
102
- return false;
103
- return true;
104
- }
105
- _wasSequenceNumberAlreadyProcessed(expectedSequence, receivedSequence) {
106
- if ((expectedSequence - this.SEQUENCE_PROCESSING_BOUNDARY) > 0) { // complete boundary without squence number reset
107
- return this._isBetween(receivedSequence, expectedSequence - this.SEQUENCE_PROCESSING_BOUNDARY, expectedSequence);
108
- }
109
- else { // there was a sequence number reset recently
110
- const isInRangeAfterReset = this._isBetween(receivedSequence, 0, expectedSequence);
111
- const isInRangeBeforeReset = this._isBetween(receivedSequence, this.MAX_SEQUENCE_NUMBER + (expectedSequence - this.SEQUENCE_PROCESSING_BOUNDARY), this.MAX_SEQUENCE_NUMBER);
112
- return (isInRangeBeforeReset || isInRangeAfterReset);
113
- }
114
- }
115
- _initialize() {
116
- let rsaKey;
117
- this.connected = false;
118
- this.connecting = false;
119
- this.lastPong = null;
120
- this.lastPongData = undefined;
121
- this.connectTime = null;
122
- this.seqNumber = 0;
123
- this.offsetDataSeqNumber = 0;
124
- this.videoSeqNumber = 0;
125
- this.energySavingDeviceP2PDataSeqNumber = 0;
126
- this.lockSeqNumber = -1;
127
- this.connectAddress = undefined;
128
- this.lastChannel = undefined;
129
- this.lastCustomData = undefined;
130
- this.lockAESKeys.clear();
131
- this._clearMessageStateTimeouts();
132
- this._clearMessageVideoStateTimeouts();
133
- this.messageStates.clear();
134
- this.messageVideoStates.clear();
135
- this.energySavingDeviceP2PSeqMapping.clear();
136
- for (let datatype = 0; datatype < 4; datatype++) {
137
- this.expectedSeqNo[datatype] = 0;
138
- if (datatype === types_1.P2PDataType.VIDEO)
139
- rsaKey = (0, utils_1.getNewRSAPrivateKey)();
140
- else
141
- rsaKey = null;
142
- this.initializeMessageBuilder(datatype);
143
- this.initializeMessageState(datatype, rsaKey);
144
- this.initializeStream(datatype);
145
- }
146
- }
147
- initializeMessageBuilder(datatype) {
148
- this.currentMessageBuilder[datatype] = {
149
- header: {
150
- commandId: 0,
151
- bytesToRead: 0,
152
- channel: 0,
153
- signCode: 0,
154
- type: 0
155
- },
156
- bytesRead: 0,
157
- messages: {}
158
- };
159
- }
160
- initializeMessageState(datatype, rsaKey = null) {
161
- this.currentMessageState[datatype] = {
162
- leftoverData: Buffer.from([]),
163
- queuedData: new sweet_collections_1.SortedMap((a, b) => a - b),
164
- rsaKey: rsaKey,
165
- videoStream: null,
166
- audioStream: null,
167
- invalidStream: false,
168
- p2pStreaming: false,
169
- p2pStreamNotStarted: true,
170
- p2pStreamChannel: -1,
171
- p2pStreamFirstAudioDataReceived: false,
172
- p2pStreamFirstVideoDataReceived: false,
173
- p2pStreamMetadata: {
174
- videoCodec: types_1.VideoCodec.H264,
175
- videoFPS: 15,
176
- videoHeight: 1080,
177
- videoWidth: 1920,
178
- audioCodec: types_1.AudioCodec.NONE
179
- },
180
- rtspStream: {},
181
- rtspStreaming: {},
182
- receivedFirstIFrame: false,
183
- preFrameVideoData: Buffer.from([]),
184
- p2pTalkback: false,
185
- p2pTalkbackChannel: -1
186
- };
187
- }
188
- _clearTimeout(timeout) {
189
- if (!!timeout) {
190
- clearTimeout(timeout);
191
- }
192
- }
193
- _clearMessageStateTimeouts() {
194
- for (const message of this.messageStates.values()) {
195
- this._clearTimeout(message.timeout);
196
- }
197
- }
198
- _clearMessageVideoStateTimeouts() {
199
- for (const message of this.messageVideoStates.values()) {
200
- this._clearTimeout(message.timeout);
201
- }
202
- }
203
- _clearHeartbeatTimeout() {
204
- this._clearTimeout(this.heartbeatTimeout);
205
- this.heartbeatTimeout = undefined;
206
- }
207
- _clearKeepaliveTimeout() {
208
- this._clearTimeout(this.keepaliveTimeout);
209
- this.keepaliveTimeout = undefined;
210
- }
211
- _clearConnectTimeout() {
212
- this._clearTimeout(this.connectTimeout);
213
- this.connectTimeout = undefined;
214
- }
215
- _clearLookupTimeout() {
216
- this._clearTimeout(this.lookupTimeout);
217
- this.lookupTimeout = undefined;
218
- }
219
- _clearLookupRetryTimeout() {
220
- this._clearTimeout(this.lookupRetryTimeout);
221
- this.lookupRetryTimeout = undefined;
222
- }
223
- _clearESDDisconnectTimeout() {
224
- this._clearTimeout(this.esdDisconnectTimeout);
225
- this.esdDisconnectTimeout = undefined;
226
- }
227
- _clearSecondaryCommandTimeout() {
228
- this._clearTimeout(this.secondaryCommandTimeout);
229
- this.secondaryCommandTimeout = undefined;
230
- }
231
- async sendMessage(errorSubject, address, msgID, payload) {
232
- await (0, utils_1.sendMessage)(this.socket, address, msgID, payload).catch((error) => {
233
- this.log.error(`${errorSubject} - msgID: ${msgID.toString("hex")} payload: ${payload === null || payload === void 0 ? void 0 : payload.toString("hex")} - Error:`, error);
234
- });
235
- }
236
- _disconnected() {
237
- this._clearHeartbeatTimeout();
238
- this._clearKeepaliveTimeout();
239
- this._clearLookupRetryTimeout();
240
- this._clearLookupTimeout();
241
- this._clearConnectTimeout();
242
- this._clearESDDisconnectTimeout();
243
- this._clearSecondaryCommandTimeout();
244
- this._clearMessageStateTimeouts();
245
- this._clearMessageVideoStateTimeouts();
246
- if (this.currentMessageState[types_1.P2PDataType.VIDEO].p2pStreaming) {
247
- this.endStream(types_1.P2PDataType.VIDEO);
248
- }
249
- if (this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreaming) {
250
- this.endStream(types_1.P2PDataType.BINARY);
251
- }
252
- for (const channel in this.currentMessageState[types_1.P2PDataType.DATA].rtspStreaming) {
253
- this.endRTSPStream(Number.parseInt(channel));
254
- }
255
- this.sendQueue = this.sendQueue.filter((queue) => queue.commandType !== types_1.CommandType.CMD_PING && queue.commandType !== types_1.CommandType.CMD_GET_DEVICE_PING);
256
- if (this.connected) {
257
- this.emit("close");
258
- }
259
- else if (!this.terminating) {
260
- this.emit("timeout");
261
- }
262
- this._initialize();
263
- }
264
- closeEnergySavingDevice() {
265
- if (this.sendQueue.filter((queue) => queue.commandType !== types_1.CommandType.CMD_PING && queue.commandType !== types_1.CommandType.CMD_GET_DEVICE_PING).length === 0 &&
266
- this.energySavingDevice &&
267
- !this.isCurrentlyStreaming() &&
268
- Array.from(this.messageStates.values()).filter((msgState) => msgState.acknowledged === false).length === 0) {
269
- if (this.esdDisconnectTimeout === undefined) {
270
- this.log.debug(`Station ${this.rawStation.station_sn} - Energy saving device - No more p2p commands to execute or running streams, initiate disconnect timeout in ${this.ESD_DISCONNECT_TIMEOUT} milliseconds...`);
271
- this.esdDisconnectTimeout = setTimeout(() => {
272
- this.esdDisconnectTimeout = undefined;
273
- this.sendMessage(`Station ${this.rawStation.station_sn}`, this.connectAddress, types_1.RequestMessageType.END);
274
- this.log.info(`Initiated closing of connection to station ${this.rawStation.station_sn} for saving battery.`);
275
- this.terminating = true;
276
- this._disconnected();
277
- }, this.ESD_DISCONNECT_TIMEOUT);
278
- }
279
- }
280
- }
281
- async renewDSKKey() {
282
- if (this.dskKey === "" || (this.dskExpiration && (new Date()).getTime() >= this.dskExpiration.getTime())) {
283
- this.log.debug(`Station ${this.rawStation.station_sn} DSK keys not present or expired, get/renew it`, { dskKey: this.dskKey, dskExpiration: this.dskExpiration });
284
- await this.getDSKKeys();
285
- }
286
- }
287
- localLookup(host) {
288
- this.log.debug(`Trying to local lookup address for station ${this.rawStation.station_sn} with host ${host}`);
289
- this.localLookupByAddress({ host: host, port: 32108 });
290
- }
291
- cloudLookup() {
292
- this.cloudAddresses.map((address) => this.cloudLookupByAddress(address));
293
- this.cloudAddresses.map((address) => this.cloudLookupByAddress2(address));
294
- }
295
- cloudLookup2(origAddress, data) {
296
- this.cloudAddresses.map((address) => this.cloudLookupByAddress3(address, origAddress, data));
297
- }
298
- async localLookupByAddress(address) {
299
- // Send lookup message
300
- const msgId = types_1.RequestMessageType.LOCAL_LOOKUP;
301
- const payload = Buffer.from([0, 0]);
302
- await this.sendMessage(`Local lookup address for station ${this.rawStation.station_sn}`, address, msgId, payload);
303
- }
304
- async cloudLookupByAddress(address) {
305
- // Send lookup message
306
- const msgId = types_1.RequestMessageType.LOOKUP_WITH_KEY;
307
- const payload = (0, utils_1.buildLookupWithKeyPayload)(this.socket, this.rawStation.p2p_did, this.dskKey);
308
- await this.sendMessage(`Cloud lookup addresses for station ${this.rawStation.station_sn}`, address, msgId, payload);
309
- }
310
- async cloudLookupByAddress2(address) {
311
- // Send lookup message2
312
- const msgId = types_1.RequestMessageType.LOOKUP_WITH_KEY2;
313
- const payload = (0, utils_1.buildLookupWithKeyPayload2)(this.rawStation.p2p_did, this.dskKey);
314
- await this.sendMessage(`Cloud lookup addresses (2) for station ${this.rawStation.station_sn}`, address, msgId, payload);
315
- }
316
- async cloudLookupByAddress3(address, origAddress, data) {
317
- // Send lookup message3
318
- const msgId = types_1.RequestMessageType.LOOKUP_WITH_KEY3;
319
- const payload = (0, utils_1.buildLookupWithKeyPayload3)(this.rawStation.p2p_did, origAddress, data);
320
- await this.sendMessage(`Cloud lookup addresses (3) for station ${this.rawStation.station_sn}`, address, msgId, payload);
321
- }
322
- isConnected() {
323
- return this.connected;
324
- }
325
- _startConnectTimeout() {
326
- if (this.connectTimeout === undefined)
327
- this.connectTimeout = setTimeout(() => {
328
- this.log.warn(`Station ${this.rawStation.station_sn} - Tried all hosts, no connection could be established`);
329
- this._disconnected();
330
- }, this.MAX_AKNOWLEDGE_TIMEOUT);
331
- }
332
- _connect(address, p2p_did) {
333
- this.log.debug(`Station ${this.rawStation.station_sn} - CHECK_CAM - Connecting to host ${address.host} on port ${address.port}...`);
334
- for (let i = 0; i < 4; i++)
335
- this.sendCamCheck(address, p2p_did);
336
- this._startConnectTimeout();
337
- }
338
- lookup(host) {
339
- if (host === undefined) {
340
- if (this.preferredIPAddress !== undefined) {
341
- host = this.preferredIPAddress;
342
- }
343
- else if (this.localIPAddress !== undefined) {
344
- host = this.localIPAddress;
345
- }
346
- else {
347
- const localIP = (0, utils_1.getLocalIpAddress)();
348
- host = localIP.substring(0, localIP.lastIndexOf(".") + 1).concat("255");
349
- }
350
- }
351
- this.localLookup(host);
352
- this.cloudLookup();
353
- this._clearLookupTimeout();
354
- this._clearLookupRetryTimeout();
355
- this.lookupTimeout = setTimeout(() => {
356
- this.lookupTimeout = undefined;
357
- this.log.error(`Station ${this.rawStation.station_sn} - All address lookup tentatives failed.`);
358
- if (this.localIPAddress !== undefined)
359
- this.localIPAddress = undefined;
360
- this._disconnected();
361
- }, this.MAX_LOOKUP_TIMEOUT);
362
- }
363
- async connect(host) {
364
- if (!this.connected && !this.connecting && this.rawStation.p2p_did !== undefined) {
365
- this.connecting = true;
366
- this.terminating = false;
367
- await this.renewDSKKey();
368
- if (!this.binded)
369
- this.socket.bind(0, () => {
370
- this.binded = true;
371
- try {
372
- this.socket.setRecvBufferSize(this.UDP_RECVBUFFERSIZE_BYTES);
373
- this.socket.setBroadcast(true);
374
- }
375
- catch (error) {
376
- this.log.error(`Station ${this.rawStation.station_sn} - Error:`, { error: error, currentRecBufferSize: this.socket.getRecvBufferSize(), recBufferRequestedSize: this.UDP_RECVBUFFERSIZE_BYTES });
377
- }
378
- this.lookup(host);
379
- });
380
- else {
381
- this.lookup(host);
382
- }
383
- }
384
- }
385
- async sendCamCheck(address, p2p_did) {
386
- const payload = (0, utils_1.buildCheckCamPayload)(p2p_did);
387
- await this.sendMessage(`Send cam check to station ${this.rawStation.station_sn}`, address, types_1.RequestMessageType.CHECK_CAM, payload);
388
- }
389
- async sendCamCheck2(address, data) {
390
- const payload = (0, utils_1.buildCheckCamPayload2)(this.rawStation.p2p_did, data);
391
- await this.sendMessage(`Send cam check (2) to station ${this.rawStation.station_sn}`, address, types_1.RequestMessageType.CHECK_CAM2, payload);
392
- }
393
- async sendPing(address) {
394
- if ((this.lastPong && ((new Date().getTime() - this.lastPong) / this.getHeartbeatInterval() >= this.MAX_RETRIES)) ||
395
- (this.connectTime && !this.lastPong && ((new Date().getTime() - this.connectTime) / this.getHeartbeatInterval() >= this.MAX_RETRIES))) {
396
- if (!this.energySavingDevice)
397
- this.log.warn(`Station ${this.rawStation.station_sn} - Heartbeat check failed. Connection seems lost. Try to reconnect...`);
398
- this._disconnected();
399
- }
400
- await this.sendMessage(`Send ping to station ${this.rawStation.station_sn}`, address, types_1.RequestMessageType.PING, this.lastPongData);
401
- }
402
- sendCommandWithIntString(p2pcommand, customData) {
403
- if (p2pcommand.channel === undefined)
404
- p2pcommand.channel = 0;
405
- if (p2pcommand.value === undefined || typeof p2pcommand.value !== "number")
406
- throw new TypeError("value must be a number");
407
- const payload = (0, utils_1.buildIntStringCommandPayload)(p2pcommand.value, p2pcommand.valueSub === undefined ? 0 : p2pcommand.valueSub, p2pcommand.strValue === undefined ? "" : p2pcommand.strValue, p2pcommand.strValueSub === undefined ? "" : p2pcommand.strValueSub, p2pcommand.channel);
408
- if (p2pcommand.commandType === types_1.CommandType.CMD_NAS_TEST) {
409
- this.currentMessageState[types_1.P2PDataType.DATA].rtspStream[p2pcommand.channel] = p2pcommand.value === 1 ? true : false;
410
- }
411
- this.sendCommand(p2pcommand.commandType, payload, p2pcommand.channel, undefined, customData);
412
- }
413
- sendCommandWithInt(p2pcommand, customData) {
414
- if (p2pcommand.channel === undefined)
415
- p2pcommand.channel = this.channel;
416
- if (p2pcommand.value === undefined || typeof p2pcommand.value !== "number")
417
- throw new TypeError("value must be a number");
418
- const payload = (0, utils_1.buildIntCommandPayload)(p2pcommand.value, p2pcommand.strValue === undefined ? "" : p2pcommand.strValue, p2pcommand.channel);
419
- this.sendCommand(p2pcommand.commandType, payload, p2pcommand.channel, undefined, customData);
420
- }
421
- sendCommandWithStringPayload(p2pcommand, customData) {
422
- if (p2pcommand.channel === undefined)
423
- p2pcommand.channel = 0;
424
- if (p2pcommand.value === undefined || typeof p2pcommand.value !== "string")
425
- throw new TypeError("value must be a string");
426
- const payload = (0, utils_1.buildCommandWithStringTypePayload)(p2pcommand.value, p2pcommand.channel);
427
- let nested_commandType = undefined;
428
- if (p2pcommand.commandType == types_1.CommandType.CMD_SET_PAYLOAD) {
429
- try {
430
- const json = JSON.parse(p2pcommand.value);
431
- nested_commandType = json.cmd;
432
- }
433
- catch (error) {
434
- this.log.error(`CMD_SET_PAYLOAD - Station ${this.rawStation.station_sn} - Error:`, error);
435
- }
436
- }
437
- else if (p2pcommand.commandType == types_1.CommandType.CMD_DOORBELL_SET_PAYLOAD) {
438
- try {
439
- const json = JSON.parse(p2pcommand.value);
440
- nested_commandType = json.commandType;
441
- }
442
- catch (error) {
443
- this.log.error(`CMD_DOORBELL_SET_PAYLOAD - Station ${this.rawStation.station_sn} - Error:`, error);
444
- }
445
- }
446
- this.sendCommand(p2pcommand.commandType, payload, p2pcommand.channel, nested_commandType, customData);
447
- }
448
- sendCommandWithString(p2pcommand, customData) {
449
- if (p2pcommand.channel === undefined)
450
- p2pcommand.channel = this.channel;
451
- if (p2pcommand.strValue === undefined)
452
- throw new TypeError("strValue must be defined");
453
- if (p2pcommand.strValueSub === undefined)
454
- throw new TypeError("strValueSub must be defined");
455
- const payload = (0, utils_1.buildStringTypeCommandPayload)(p2pcommand.strValue, p2pcommand.strValueSub, p2pcommand.channel);
456
- this.sendCommand(p2pcommand.commandType, payload, p2pcommand.channel, p2pcommand.commandType, customData);
457
- }
458
- sendCommandPing(channel = this.channel) {
459
- const payload = (0, utils_1.buildVoidCommandPayload)(channel);
460
- this.sendCommand(types_1.CommandType.CMD_PING, payload, channel);
461
- }
462
- sendCommandDevicePing(channel = this.channel) {
463
- const payload = (0, utils_1.buildVoidCommandPayload)(channel);
464
- this.sendCommand(types_1.CommandType.CMD_GET_DEVICE_PING, payload, channel);
465
- }
466
- sendCommandWithoutData(commandType, channel = this.channel) {
467
- const payload = (0, utils_1.buildVoidCommandPayload)(channel);
468
- this.sendCommand(commandType, payload, channel);
469
- }
470
- sendQueuedMessage() {
471
- if (this.sendQueue.length > 0) {
472
- if (this.connected) {
473
- let queuedMessage;
474
- while ((queuedMessage = this.sendQueue.shift()) !== undefined) {
475
- let exists = false;
476
- let waitingAcknowledge = false;
477
- this.messageStates.forEach(stateMessage => {
478
- if (stateMessage.commandType === queuedMessage.commandType && stateMessage.nestedCommandType === queuedMessage.nestedCommandType && !stateMessage.acknowledged) {
479
- exists = true;
480
- }
481
- if (!stateMessage.acknowledged) {
482
- waitingAcknowledge = true;
483
- }
484
- });
485
- if (!exists && !waitingAcknowledge) {
486
- this._sendCommand(queuedMessage);
487
- break;
488
- }
489
- else {
490
- this.sendQueue.unshift(queuedMessage);
491
- break;
492
- }
493
- }
494
- }
495
- else if (!this.connected) {
496
- this.connect();
497
- }
498
- }
499
- this.closeEnergySavingDevice();
500
- }
501
- sendCommand(commandType, payload, channel, nestedCommandType, customData) {
502
- const message = {
503
- commandType: commandType,
504
- nestedCommandType: nestedCommandType,
505
- channel: channel,
506
- payload: payload,
507
- timestamp: +new Date,
508
- customData: customData
509
- };
510
- this.sendQueue.push(message);
511
- if (message.commandType !== types_1.CommandType.CMD_PING && message.commandType !== types_1.CommandType.CMD_GET_DEVICE_PING)
512
- this._clearESDDisconnectTimeout();
513
- this.sendQueuedMessage();
514
- }
515
- resendNotAcknowledgedCommand(sequence) {
516
- const messageState = this.messageStates.get(sequence);
517
- if (messageState) {
518
- messageState.retryTimeout = setTimeout(() => {
519
- if (this.connectAddress) {
520
- (0, utils_1.sendMessage)(this.socket, this.connectAddress, types_1.RequestMessageType.DATA, messageState.data).catch((error) => {
521
- this.log.error(`Station ${this.rawStation.station_sn} - Error:`, error);
522
- });
523
- this.resendNotAcknowledgedCommand(sequence);
524
- }
525
- }, this.RESEND_NOT_ACKNOWLEDGED_COMMAND);
526
- }
527
- }
528
- async _sendCommand(message) {
529
- var _a;
530
- if ((0, utils_1.isP2PQueueMessage)(message)) {
531
- const ageing = +new Date - message.timestamp;
532
- if (ageing <= this.MAX_COMMAND_QUEUE_TIMEOUT) {
533
- const commandHeader = (0, utils_1.buildCommandHeader)(this.seqNumber, message.commandType);
534
- const data = Buffer.concat([commandHeader, message.payload]);
535
- const messageState = {
536
- sequence: this.seqNumber,
537
- commandType: message.commandType,
538
- nestedCommandType: message.nestedCommandType,
539
- channel: message.channel,
540
- data: data,
541
- retries: 0,
542
- acknowledged: false,
543
- returnCode: types_1.ErrorCode.ERROR_COMMAND_TIMEOUT,
544
- customData: message.customData
545
- };
546
- message = messageState;
547
- this.seqNumber = this._incrementSequence(this.seqNumber);
548
- }
549
- else if (message.commandType === types_1.CommandType.CMD_PING || message.commandType === types_1.CommandType.CMD_GET_DEVICE_PING) {
550
- return;
551
- }
552
- else {
553
- this.log.warn(`Station ${this.rawStation.station_sn} - Command aged out from queue`, { commandType: message.commandType, nestedCommandType: message.nestedCommandType, channel: message.channel, ageing: ageing, maxAgeing: this.MAX_COMMAND_QUEUE_TIMEOUT });
554
- this.emit("command", {
555
- command_type: message.nestedCommandType !== undefined ? message.nestedCommandType : message.commandType,
556
- channel: message.channel,
557
- return_code: types_1.ErrorCode.ERROR_CONNECT_TIMEOUT,
558
- customData: message.customData
559
- });
560
- return;
561
- }
562
- }
563
- else {
564
- if (message.retries < this.MAX_RETRIES && message.returnCode !== types_1.ErrorCode.ERROR_CONNECT_TIMEOUT) {
565
- if (message.returnCode === types_1.ErrorCode.ERROR_FAILED_TO_REQUEST) {
566
- this.messageStates.delete(message.sequence);
567
- message.sequence = this.seqNumber;
568
- message.data.writeUInt16BE(message.sequence, 2);
569
- this.seqNumber = this._incrementSequence(this.seqNumber);
570
- this.messageStates.set(message.sequence, message);
571
- }
572
- message.retries++;
573
- }
574
- else {
575
- this.log.error(`Station ${this.rawStation.station_sn} - Max retries ${(_a = this.messageStates.get(message.sequence)) === null || _a === void 0 ? void 0 : _a.retries} - stop with error ${types_1.ErrorCode[message.returnCode]}`, { sequence: message.sequence, commandType: message.commandType, channel: message.channel, retries: message.retries, returnCode: message.returnCode });
576
- this.emit("command", {
577
- command_type: message.nestedCommandType !== undefined ? message.nestedCommandType : message.commandType,
578
- channel: message.channel,
579
- return_code: message.returnCode,
580
- customData: message.customData
581
- });
582
- this.messageStates.delete(message.sequence);
583
- this.sendQueuedMessage();
584
- return;
585
- }
586
- }
587
- const messageState = message;
588
- messageState.returnCode = types_1.ErrorCode.ERROR_COMMAND_TIMEOUT;
589
- messageState.timeout = setTimeout(() => {
590
- this._clearTimeout(messageState.retryTimeout);
591
- this._sendCommand(messageState);
592
- this._clearESDDisconnectTimeout();
593
- this.closeEnergySavingDevice();
594
- }, this.MAX_AKNOWLEDGE_TIMEOUT);
595
- this.messageStates.set(messageState.sequence, messageState);
596
- messageState.retryTimeout = setTimeout(() => {
597
- this.resendNotAcknowledgedCommand(messageState.sequence);
598
- }, this.RESEND_NOT_ACKNOWLEDGED_COMMAND);
599
- if (messageState.commandType !== types_1.CommandType.CMD_PING && this.energySavingDevice) {
600
- this.energySavingDeviceP2PSeqMapping.set(this.energySavingDeviceP2PDataSeqNumber, message.sequence);
601
- this.log.debug(`Station ${this.rawStation.station_sn} - Energy saving Device - Added sequence number mapping`, { commandType: message.commandType, seqNumber: message.sequence, energySavingDeviceP2PDataSeqNumber: this.energySavingDeviceP2PDataSeqNumber, energySavingDeviceP2PSeqMappingCount: this.energySavingDeviceP2PSeqMapping.size });
602
- this.energySavingDeviceP2PDataSeqNumber = this._incrementSequence(this.energySavingDeviceP2PDataSeqNumber);
603
- }
604
- this.log.debug("Sending p2p command...", { station: this.rawStation.station_sn, sequence: messageState.sequence, commandType: messageState.commandType, channel: messageState.channel, retries: messageState.retries, messageStatesSize: this.messageStates.size });
605
- await this.sendMessage(`Send command to station ${this.rawStation.station_sn}`, this.connectAddress, types_1.RequestMessageType.DATA, messageState.data);
606
- if (messageState.retries === 0) {
607
- if (messageState.commandType === types_1.CommandType.CMD_START_REALTIME_MEDIA ||
608
- (messageState.nestedCommandType !== undefined && messageState.nestedCommandType === types_1.CommandType.CMD_START_REALTIME_MEDIA && messageState.commandType === types_1.CommandType.CMD_SET_PAYLOAD) ||
609
- messageState.commandType === types_1.CommandType.CMD_RECORD_VIEW ||
610
- (messageState.nestedCommandType !== undefined && messageState.nestedCommandType === 1000 && messageState.commandType === types_1.CommandType.CMD_DOORBELL_SET_PAYLOAD)) {
611
- if (this.currentMessageState[types_1.P2PDataType.VIDEO].p2pStreaming && messageState.channel !== this.currentMessageState[types_1.P2PDataType.VIDEO].p2pStreamChannel) {
612
- this.endStream(types_1.P2PDataType.VIDEO);
613
- }
614
- this.currentMessageState[types_1.P2PDataType.VIDEO].p2pStreaming = true;
615
- this.currentMessageState[types_1.P2PDataType.VIDEO].p2pStreamChannel = messageState.channel;
616
- }
617
- else if (messageState.commandType === types_1.CommandType.CMD_DOWNLOAD_VIDEO) {
618
- if (this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreaming && messageState.channel !== this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreamChannel) {
619
- this.endStream(types_1.P2PDataType.BINARY);
620
- }
621
- this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreaming = true;
622
- this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreamChannel = message.channel;
623
- }
624
- else if (messageState.commandType === types_1.CommandType.CMD_STOP_REALTIME_MEDIA) { //TODO: CommandType.CMD_RECORD_PLAY_CTRL only if stop
625
- this.endStream(types_1.P2PDataType.VIDEO);
626
- }
627
- else if (messageState.commandType === types_1.CommandType.CMD_DOWNLOAD_CANCEL) {
628
- this.endStream(types_1.P2PDataType.BINARY);
629
- }
630
- else if (messageState.commandType === types_1.CommandType.CMD_NAS_TEST) {
631
- if (this.currentMessageState[types_1.P2PDataType.DATA].rtspStream[messageState.channel]) {
632
- this.currentMessageState[types_1.P2PDataType.DATA].rtspStreaming[messageState.channel] = true;
633
- this.emit("rtsp livestream started", messageState.channel);
634
- }
635
- else {
636
- this.endRTSPStream(messageState.channel);
637
- }
638
- }
639
- }
640
- }
641
- handleMsg(msg, rinfo) {
642
- if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.LOCAL_LOOKUP_RESP)) {
643
- if (!this.connected) {
644
- this._clearLookupTimeout();
645
- this._clearLookupRetryTimeout();
646
- const p2pDid = `${msg.slice(4, 12).toString("utf8").replace(/[\0]+$/g, "")}-${msg.slice(12, 16).readUInt32BE()}-${msg.slice(16, 24).toString("utf8").replace(/[\0]+$/g, "")}`;
647
- this.log.debug(`Station ${this.rawStation.station_sn} - LOCAL_LOOKUP_RESP - Got response`, { ip: rinfo.address, port: rinfo.port, p2pDid: p2pDid });
648
- if (p2pDid === this.rawStation.p2p_did) {
649
- this.log.debug(`Station ${this.rawStation.station_sn} - LOCAL_LOOKUP_RESP - Wanted device was found, connect to it`, { ip: rinfo.address, port: rinfo.port, p2pDid: p2pDid });
650
- this._connect({ host: rinfo.address, port: rinfo.port }, p2pDid);
651
- }
652
- else {
653
- this.log.debug(`Station ${this.rawStation.station_sn} - LOCAL_LOOKUP_RESP - Unwanted device was found, don't connect to it`, { ip: rinfo.address, port: rinfo.port, p2pDid: p2pDid });
654
- }
655
- }
656
- }
657
- else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.LOOKUP_ADDR)) {
658
- if (!this.connected) {
659
- const port = msg.slice(6, 8).readUInt16LE();
660
- const ip = `${msg[11]}.${msg[10]}.${msg[9]}.${msg[8]}`;
661
- this.log.debug(`Station ${this.rawStation.station_sn} - LOOKUP_ADDR - Got response`, { remoteAddress: rinfo.address, remotePort: rinfo.port, response: { ip: ip, port: port } });
662
- if (ip === "0.0.0.0") {
663
- this.log.debug(`Station ${this.rawStation.station_sn} - LOOKUP_ADDR - Got invalid ip address 0.0.0.0, ignoring response...`);
664
- return;
665
- }
666
- if ((0, utils_1.isPrivateIp)(ip))
667
- this.localIPAddress = ip;
668
- if (this.connectionType === types_1.P2PConnectionType.ONLY_LOCAL) {
669
- if ((0, utils_1.isPrivateIp)(ip)) {
670
- this._clearLookupTimeout();
671
- this._clearLookupRetryTimeout();
672
- this.log.debug(`Station ${this.rawStation.station_sn} - ONLY_LOCAL - Try to connect to ${ip}:${port}...`);
673
- this._connect({ host: ip, port: port }, this.rawStation.p2p_did);
674
- }
675
- }
676
- else if (this.connectionType === types_1.P2PConnectionType.QUICKEST) {
677
- this._clearLookupTimeout();
678
- this._clearLookupRetryTimeout();
679
- this.log.debug(`Station ${this.rawStation.station_sn} - QUICKEST - Try to connect to ${ip}:${port}...`);
680
- this._connect({ host: ip, port: port }, this.rawStation.p2p_did);
681
- }
682
- }
683
- }
684
- else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.CAM_ID) || (0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.CAM_ID2)) {
685
- // Answer from the device to a CAM_CHECK message
686
- if (!this.connected) {
687
- this.log.debug(`Station ${this.rawStation.station_sn} - CAM_ID - Connected to station ${this.rawStation.station_sn} on host ${rinfo.address} port ${rinfo.port}`);
688
- this._clearLookupRetryTimeout();
689
- this._clearLookupTimeout();
690
- this._clearConnectTimeout();
691
- this.connected = true;
692
- this.connectTime = new Date().getTime();
693
- this.lastPong = null;
694
- this.lastPongData = undefined;
695
- this.connectAddress = { host: rinfo.address, port: rinfo.port };
696
- if ((0, utils_1.isPrivateIp)(rinfo.address))
697
- this.localIPAddress = rinfo.address;
698
- this.heartbeatTimeout = setTimeout(() => {
699
- this.scheduleHeartbeat();
700
- }, this.getHeartbeatInterval());
701
- if (this.energySavingDevice) {
702
- this.keepaliveTimeout = setTimeout(() => {
703
- this.scheduleP2PKeepalive();
704
- }, this.KEEPALIVE_INTERVAL);
705
- }
706
- if (device_1.Device.isLockWifi(this.rawStation.device_type) || device_1.Device.isLockWifiNoFinger(this.rawStation.device_type)) {
707
- const tmpSendQueue = [...this.sendQueue];
708
- this.sendQueue = [];
709
- this.sendCommandWithoutData(types_1.CommandType.CMD_GATEWAYINFO, 255);
710
- this.sendCommandWithStringPayload({
711
- commandType: types_1.CommandType.CMD_SET_PAYLOAD,
712
- value: JSON.stringify({
713
- "account_id": this.rawStation.member.admin_user_id,
714
- "cmd": types_1.CommandType.P2P_QUERY_STATUS_IN_LOCK,
715
- "mChannel": 0,
716
- "mValue3": 0,
717
- "payload": {
718
- "timezone": this.rawStation.time_zone === undefined || this.rawStation.time_zone === "" ? (0, utils_2.getAdvancedLockTimezone)(this.rawStation.station_sn) : this.rawStation.time_zone,
719
- }
720
- }),
721
- channel: 0
722
- });
723
- tmpSendQueue.forEach(element => {
724
- this.sendQueue.push(element);
725
- });
726
- }
727
- else if (device_1.Device.isSmartSafe(this.rawStation.device_type)) {
728
- const payload = (0, utils_1.buildVoidCommandPayload)(255);
729
- const data = Buffer.concat([(0, utils_1.buildCommandHeader)(this.seqNumber, types_1.CommandType.CMD_GATEWAYINFO), payload]);
730
- const message = {
731
- sequence: this.seqNumber,
732
- commandType: types_1.CommandType.CMD_GATEWAYINFO,
733
- channel: 255,
734
- data: data,
735
- retries: 0,
736
- acknowledged: false,
737
- returnCode: types_1.ErrorCode.ERROR_COMMAND_TIMEOUT,
738
- };
739
- this.messageStates.set(message.sequence, message);
740
- message.retryTimeout = setTimeout(() => {
741
- this.resendNotAcknowledgedCommand(message.sequence);
742
- }, this.RESEND_NOT_ACKNOWLEDGED_COMMAND);
743
- this.seqNumber = this._incrementSequence(this.seqNumber);
744
- this.sendMessage(`Send smartsafe gateway command to station ${this.rawStation.station_sn}`, this.connectAddress, types_1.RequestMessageType.DATA, data);
745
- const tmpSendQueue = [...this.sendQueue];
746
- this.sendQueue = [];
747
- this.sendCommandPing();
748
- tmpSendQueue.forEach(element => {
749
- this.sendQueue.push(element);
750
- });
751
- }
752
- else if (device_1.Device.isLockWifiR10(this.rawStation.device_type) || device_1.Device.isLockWifiR20(this.rawStation.device_type)) {
753
- const tmpSendQueue = [...this.sendQueue];
754
- this.sendQueue = [];
755
- /*const payload = buildVoidCommandPayload(255);
756
- const data = Buffer.concat([buildCommandHeader(0, CommandType.CMD_GATEWAYINFO), payload]);
757
- sendMessage(this.socket, this.connectAddress!, RequestMessageType.DATA, data).catch((error) => {
758
- this.log.error(`Station ${this.rawStation.station_sn} - Error:`, error);
759
- });
760
- this.sendCommand(CommandType.CMD_PING, payload, 255);*/
761
- const payload = (0, utils_1.buildVoidCommandPayload)(255);
762
- const data = Buffer.concat([(0, utils_1.buildCommandHeader)(0, types_1.CommandType.CMD_GATEWAYINFO), payload.slice(0, payload.length - 2), (0, utils_1.buildCommandHeader)(0, types_1.CommandType.CMD_PING).slice(2), payload]);
763
- const message = {
764
- sequence: this.seqNumber,
765
- commandType: types_1.CommandType.CMD_PING,
766
- channel: 255,
767
- data: data,
768
- retries: 0,
769
- acknowledged: false,
770
- returnCode: types_1.ErrorCode.ERROR_COMMAND_TIMEOUT,
771
- };
772
- this.messageStates.set(message.sequence, message);
773
- message.retryTimeout = setTimeout(() => {
774
- this.resendNotAcknowledgedCommand(message.sequence);
775
- }, this.RESEND_NOT_ACKNOWLEDGED_COMMAND);
776
- this.seqNumber = this._incrementSequence(this.seqNumber);
777
- this.sendMessage(`Send lock wifi gateway command to station ${this.rawStation.station_sn}`, this.connectAddress, types_1.RequestMessageType.DATA, data);
778
- try {
779
- const command = (0, utils_1.getLockV12P2PCommand)(this.rawStation.station_sn, this.rawStation.member.admin_user_id, types_1.ESLCommand.QUERY_STATUS_IN_LOCK, 0, this.lockPublicKey, this.incLockSequenceNumber(), device_1.Lock.encodeCmdStatus(this.rawStation.member.admin_user_id));
780
- this.sendCommandWithStringPayload(command.payload);
781
- }
782
- catch (error) {
783
- this.log.error(`Send query status lock command to station ${this.rawStation.station_sn} - Error`, error);
784
- }
785
- tmpSendQueue.forEach(element => {
786
- this.sendQueue.push(element);
787
- });
788
- }
789
- this.sendQueuedMessage();
790
- this.emit("connect", this.connectAddress);
791
- }
792
- }
793
- else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.PONG)) {
794
- // Response to a ping from our side
795
- this.lastPong = new Date().getTime();
796
- if (msg.length > 4)
797
- this.lastPongData = msg.slice(4);
798
- else
799
- this.lastPongData = undefined;
800
- return;
801
- }
802
- else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.PING)) {
803
- // Response with PONG to keep alive
804
- this.sendMessage(`Send pong to station ${this.rawStation.station_sn}`, { host: rinfo.address, port: rinfo.port }, types_1.RequestMessageType.PONG);
805
- return;
806
- }
807
- else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.END)) {
808
- // Connection is closed by device
809
- this.log.debug(`Station ${this.rawStation.station_sn} - END - received from host ${rinfo.address}:${rinfo.port}`);
810
- this.onClose();
811
- return;
812
- }
813
- else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.ACK)) {
814
- // Device ACK a message from our side
815
- // Number of Acks sended in the message
816
- const dataTypeBuffer = msg.slice(4, 6);
817
- const dataType = this.getDataType(dataTypeBuffer);
818
- const numAcksBuffer = msg.slice(6, 8);
819
- const numAcks = numAcksBuffer.readUIntBE(0, numAcksBuffer.length);
820
- for (let i = 1; i <= numAcks; i++) {
821
- const idx = 6 + i * 2;
822
- const seqBuffer = msg.slice(idx, idx + 2);
823
- const ackedSeqNo = seqBuffer.readUIntBE(0, seqBuffer.length);
824
- // -> Message with seqNo was received at the station
825
- this.log.debug(`Station ${this.rawStation.station_sn} - ACK ${types_1.P2PDataType[dataType]} - received from host ${rinfo.address}:${rinfo.port} for sequence ${ackedSeqNo}`);
826
- if (dataType === types_1.P2PDataType.DATA) {
827
- const msg_state = this.messageStates.get(ackedSeqNo);
828
- if (msg_state && !msg_state.acknowledged) {
829
- this._clearTimeout(msg_state.retryTimeout);
830
- this._clearTimeout(msg_state.timeout);
831
- if (msg_state.commandType === types_1.CommandType.CMD_PING || msg_state.commandType === types_1.CommandType.CMD_GET_DEVICE_PING) {
832
- this.messageStates.delete(ackedSeqNo);
833
- this.sendQueuedMessage();
834
- }
835
- else {
836
- msg_state.acknowledged = true;
837
- if (device_1.Device.isSmartSafe(this.rawStation.device_type) && msg_state.commandType === types_1.CommandType.CMD_GATEWAYINFO) {
838
- //In this case no result data is received.
839
- this.messageStates.delete(ackedSeqNo);
840
- }
841
- else {
842
- msg_state.timeout = setTimeout(() => {
843
- //TODO: Retry command in these case?
844
- this.log.warn(`Station ${this.rawStation.station_sn} - Result data for command not received`, { message: { sequence: msg_state.sequence, commandType: msg_state.commandType, nestedCommandType: msg_state.nestedCommandType, channel: msg_state.channel, acknowledged: msg_state.acknowledged, retries: msg_state.retries, returnCode: msg_state.returnCode, data: msg_state.data } });
845
- this.messageStates.delete(ackedSeqNo);
846
- this.emit("command", {
847
- command_type: msg_state.nestedCommandType !== undefined ? msg_state.nestedCommandType : msg_state.commandType,
848
- channel: msg_state.channel,
849
- return_code: types_1.ErrorCode.ERROR_COMMAND_TIMEOUT,
850
- customData: msg_state.customData
851
- });
852
- this.sendQueuedMessage();
853
- }, this.MAX_COMMAND_RESULT_WAIT);
854
- this.messageStates.set(ackedSeqNo, msg_state);
855
- }
856
- this.sendQueuedMessage();
857
- }
858
- }
859
- }
860
- else if (dataType === types_1.P2PDataType.VIDEO) {
861
- const msg_state = this.messageVideoStates.get(ackedSeqNo);
862
- if (msg_state) {
863
- this._clearTimeout(msg_state.timeout);
864
- this.messageVideoStates.delete(ackedSeqNo);
865
- }
866
- }
867
- }
868
- }
869
- else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.DATA)) {
870
- if (this.connected) {
871
- const seqNo = msg.slice(6, 8).readUInt16BE();
872
- const dataTypeBuffer = msg.slice(4, 6);
873
- const dataType = this.getDataType(dataTypeBuffer);
874
- const message = {
875
- bytesToRead: msg.slice(2, 4).readUInt16BE(),
876
- type: dataType,
877
- seqNo: seqNo,
878
- data: msg.slice(8)
879
- };
880
- this.sendAck({ host: rinfo.address, port: rinfo.port }, dataTypeBuffer, seqNo);
881
- this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[message.type]} - received from host ${rinfo.address}:${rinfo.port} - Processing sequence ${message.seqNo}...`);
882
- if (message.seqNo === this.expectedSeqNo[dataType]) {
883
- // expected seq packet arrived
884
- const timeout = this.currentMessageState[dataType].waitForSeqNoTimeout;
885
- if (!!timeout) {
886
- clearTimeout(timeout);
887
- this.currentMessageState[dataType].waitForSeqNoTimeout = undefined;
888
- }
889
- this.expectedSeqNo[dataType] = this._incrementSequence(this.expectedSeqNo[dataType]);
890
- this.parseDataMessage(message);
891
- this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[message.type]} - Received expected sequence (expectedSeqNo: ${this.expectedSeqNo[dataType]} seqNo: ${message.seqNo} queuedData.size: ${this.currentMessageState[dataType].queuedData.size})`);
892
- let queuedMessage = this.currentMessageState[dataType].queuedData.get(this.expectedSeqNo[dataType]);
893
- while (queuedMessage) {
894
- this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[queuedMessage.type]} - Work off queued data (expectedSeqNo: ${this.expectedSeqNo[dataType]} seqNo: ${queuedMessage.seqNo} queuedData.size: ${this.currentMessageState[dataType].queuedData.size})`);
895
- this.expectedSeqNo[dataType] = this._incrementSequence(this.expectedSeqNo[dataType]);
896
- this.parseDataMessage(queuedMessage);
897
- this.currentMessageState[dataType].queuedData.delete(queuedMessage.seqNo);
898
- queuedMessage = this.currentMessageState[dataType].queuedData.get(this.expectedSeqNo[dataType]);
899
- }
900
- }
901
- else if (this._wasSequenceNumberAlreadyProcessed(this.expectedSeqNo[dataType], message.seqNo)) {
902
- // We have already seen this message, skip!
903
- // This can happen because the device is sending the message till it gets a ACK
904
- // which can take some time.
905
- this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[message.type]} - Received already processed sequence (expectedSeqNo: ${this.expectedSeqNo[dataType]} seqNo: ${message.seqNo} queuedData.size: ${this.currentMessageState[dataType].queuedData.size})`);
906
- return;
907
- }
908
- else {
909
- if (!this.currentMessageState[dataType].waitForSeqNoTimeout)
910
- this.currentMessageState[dataType].waitForSeqNoTimeout = setTimeout(() => {
911
- this.endStream(dataType, true);
912
- this.currentMessageState[dataType].waitForSeqNoTimeout = undefined;
913
- }, this.MAX_EXPECTED_SEQNO_WAIT);
914
- if (!this.currentMessageState[dataType].queuedData.get(message.seqNo)) {
915
- this.currentMessageState[dataType].queuedData.set(message.seqNo, message);
916
- this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[message.type]} - Received not expected sequence, added to the queue for future processing (expectedSeqNo: ${this.expectedSeqNo[dataType]} seqNo: ${message.seqNo} queuedData.size: ${this.currentMessageState[dataType].queuedData.size})`);
917
- }
918
- else {
919
- this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[message.type]} - Received not expected sequence, discarded since already present in queue for future processing (expectedSeqNo: ${this.expectedSeqNo[dataType]} seqNo: ${message.seqNo} queuedData.size: ${this.currentMessageState[dataType].queuedData.size})`);
920
- }
921
- }
922
- }
923
- }
924
- else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.LOOKUP_ADDR2)) {
925
- if (!this.connected) {
926
- const port = msg.slice(6, 8).readUInt16LE();
927
- const ip = `${msg[11]}.${msg[10]}.${msg[9]}.${msg[8]}`;
928
- const data = msg.slice(20, 24);
929
- this._clearLookupTimeout();
930
- this._clearLookupRetryTimeout();
931
- this.log.debug(`Station ${this.rawStation.station_sn} - LOOKUP_ADDR2 - Got response`, { remoteAddress: rinfo.address, remotePort: rinfo.port, response: { ip: ip, port: port, data: data.toString("hex") } });
932
- this.log.debug(`Station ${this.rawStation.station_sn} - CHECK_CAM2 - Connecting to host ${ip} on port ${port}...`);
933
- for (let i = 0; i < 4; i++)
934
- this.sendCamCheck2({ host: ip, port: port }, data);
935
- this._startConnectTimeout();
936
- this.sendMessage(`Send UNKNOWN_70 to station ${this.rawStation.station_sn}`, { host: ip, port: port }, types_1.RequestMessageType.UNKNOWN_70);
937
- }
938
- }
939
- else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.UNKNOWN_71)) {
940
- if (!this.connected) {
941
- this.log.debug(`Station ${this.rawStation.station_sn} - UNKNOWN_71 - Got response`, { remoteAddress: rinfo.address, remotePort: rinfo.port, response: { message: msg.toString("hex"), length: msg.length } });
942
- this.sendMessage(`Send UNKNOWN_71 to station ${this.rawStation.station_sn}`, { host: rinfo.address, port: rinfo.port }, types_1.RequestMessageType.UNKNOWN_71);
943
- }
944
- }
945
- else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.UNKNOWN_73)) {
946
- if (!this.connected) {
947
- const port = msg.slice(8, 10).readUInt16BE();
948
- const data = msg.slice(4, 8);
949
- this.log.debug(`Station ${this.rawStation.station_sn} - UNKNOWN_73 - Got response`, { remoteAddress: rinfo.address, remotePort: rinfo.port, response: { port: port, data: data.toString("hex") } });
950
- this.cloudLookup2({ host: rinfo.address, port: port }, data);
951
- }
952
- }
953
- else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.UNKNOWN_81) || (0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.UNKNOWN_83)) {
954
- // Do nothing / ignore
955
- }
956
- else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.LOOKUP_RESP)) {
957
- if (!this.connected) {
958
- const responseCode = msg.slice(4, 6).readUInt16LE();
959
- this.log.debug(`Station ${this.rawStation.station_sn} - LOOKUP_RESP - Got response`, { remoteAddress: rinfo.address, remotePort: rinfo.port, response: { responseCode: responseCode } });
960
- if (responseCode !== 0 && this.lookupTimeout !== undefined && this.lookupRetryTimeout === undefined) {
961
- this.lookupRetryTimeout = setTimeout(() => {
962
- this.lookupRetryTimeout = undefined;
963
- this.cloudAddresses.map((address) => this.cloudLookupByAddress(address));
964
- }, this.LOOKUP_RETRY_TIMEOUT);
965
- }
966
- }
967
- }
968
- else {
969
- this.log.debug(`Station ${this.rawStation.station_sn} - received unknown message`, { remoteAddress: rinfo.address, remotePort: rinfo.port, response: { message: msg.toString("hex"), length: msg.length } });
970
- }
971
- }
972
- parseDataMessage(message) {
973
- if ((message.type === types_1.P2PDataType.BINARY || message.type === types_1.P2PDataType.VIDEO) && !this.currentMessageState[message.type].p2pStreaming) {
974
- this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[message.type]} - Stream not started ignore this data`, { seqNo: message.seqNo, header: this.currentMessageBuilder[message.type].header, bytesRead: this.currentMessageBuilder[message.type].bytesRead, bytesToRead: this.currentMessageBuilder[message.type].header.bytesToRead, messageSize: message.data.length });
975
- }
976
- else {
977
- if (this.currentMessageState[message.type].leftoverData.length > 0) {
978
- message.data = Buffer.concat([this.currentMessageState[message.type].leftoverData, message.data]);
979
- this.currentMessageState[message.type].leftoverData = Buffer.from([]);
980
- }
981
- let data = message.data;
982
- do {
983
- // is this the first message?
984
- const firstPartMessage = data.slice(0, 4).toString() === utils_1.MAGIC_WORD;
985
- if (firstPartMessage) {
986
- const header = {
987
- commandId: 0,
988
- bytesToRead: 0,
989
- channel: 0,
990
- signCode: 0,
991
- type: 0
992
- };
993
- header.commandId = data.slice(4, 6).readUIntLE(0, 2);
994
- header.bytesToRead = data.slice(6, 10).readUIntLE(0, 4);
995
- header.channel = data.slice(12, 13).readUInt8();
996
- header.signCode = data.slice(13, 14).readUInt8();
997
- header.type = data.slice(14, 15).readUInt8();
998
- this.currentMessageBuilder[message.type].header = header;
999
- data = data.slice(this.P2P_DATA_HEADER_BYTES);
1000
- if (data.length >= header.bytesToRead) {
1001
- const payload = data.slice(0, header.bytesToRead);
1002
- this.currentMessageBuilder[message.type].messages[message.seqNo] = payload;
1003
- this.currentMessageBuilder[message.type].bytesRead = payload.byteLength;
1004
- data = data.slice(header.bytesToRead);
1005
- if (data.length <= this.P2P_DATA_HEADER_BYTES) {
1006
- this.currentMessageState[message.type].leftoverData = data;
1007
- data = Buffer.from([]);
1008
- }
1009
- }
1010
- else {
1011
- if (data.length <= this.P2P_DATA_HEADER_BYTES) {
1012
- this.currentMessageState[message.type].leftoverData = data;
1013
- }
1014
- else {
1015
- this.currentMessageBuilder[message.type].messages[message.seqNo] = data;
1016
- this.currentMessageBuilder[message.type].bytesRead = data.byteLength;
1017
- }
1018
- data = Buffer.from([]);
1019
- }
1020
- }
1021
- else {
1022
- // finish message and print
1023
- if (this.currentMessageBuilder[message.type].header.bytesToRead - this.currentMessageBuilder[message.type].bytesRead <= data.length) {
1024
- const payload = data.slice(0, this.currentMessageBuilder[message.type].header.bytesToRead - this.currentMessageBuilder[message.type].bytesRead);
1025
- this.currentMessageBuilder[message.type].messages[message.seqNo] = payload;
1026
- this.currentMessageBuilder[message.type].bytesRead += payload.byteLength;
1027
- data = data.slice(payload.byteLength);
1028
- if (data.length <= this.P2P_DATA_HEADER_BYTES) {
1029
- this.currentMessageState[message.type].leftoverData = data;
1030
- data = Buffer.from([]);
1031
- }
1032
- }
1033
- else {
1034
- if (data.length <= this.P2P_DATA_HEADER_BYTES) {
1035
- this.currentMessageState[message.type].leftoverData = data;
1036
- }
1037
- else {
1038
- this.currentMessageBuilder[message.type].messages[message.seqNo] = data;
1039
- this.currentMessageBuilder[message.type].bytesRead += data.byteLength;
1040
- }
1041
- data = Buffer.from([]);
1042
- }
1043
- }
1044
- this.log.debug(`Station ${this.rawStation.station_sn} - Received data`, { seqNo: message.seqNo, header: this.currentMessageBuilder[message.type].header, bytesRead: this.currentMessageBuilder[message.type].bytesRead, bytesToRead: this.currentMessageBuilder[message.type].header.bytesToRead, firstPartMessage: firstPartMessage, messageSize: message.data.length });
1045
- if (this.currentMessageBuilder[message.type].bytesRead === this.currentMessageBuilder[message.type].header.bytesToRead) {
1046
- const completeMessage = (0, utils_1.sortP2PMessageParts)(this.currentMessageBuilder[message.type].messages);
1047
- const data_message = {
1048
- ...this.currentMessageBuilder[message.type].header,
1049
- seqNo: (message.seqNo + this.offsetDataSeqNumber),
1050
- dataType: message.type,
1051
- data: completeMessage
1052
- };
1053
- this.handleData(data_message);
1054
- this.initializeMessageBuilder(message.type);
1055
- if (data.length > 0 && message.type === types_1.P2PDataType.DATA) {
1056
- this.log.debug(`Station ${this.rawStation.station_sn} - Parsed data`, { seqNo: message.seqNo, data_message: data_message, datalen: data.length, data: data.toString("hex"), offsetDataSeqNumber: this.offsetDataSeqNumber, seqNumber: this.seqNumber, energySavingDeviceP2PDataSeqNumber: this.energySavingDeviceP2PDataSeqNumber });
1057
- this.offsetDataSeqNumber++;
1058
- }
1059
- }
1060
- } while (data.length > 0);
1061
- }
1062
- }
1063
- handleData(message) {
1064
- if (message.dataType === types_1.P2PDataType.CONTROL) {
1065
- this.handleDataControl(message);
1066
- }
1067
- else if (message.dataType === types_1.P2PDataType.DATA) {
1068
- const commandStr = types_1.CommandType[message.commandId];
1069
- const result_msg = message.type === 1 ? true : false;
1070
- if (result_msg) {
1071
- const return_code = message.data.slice(0, 4).readUInt32LE() | 0;
1072
- const return_msg = message.data.slice(4, 4 + 128).toString();
1073
- const error_codeStr = types_1.ErrorCode[return_code];
1074
- this.log.debug(`Station ${this.rawStation.station_sn} - Received data`, { commandIdName: commandStr, commandId: message.commandId, resultCodeName: error_codeStr, resultCode: return_code, message: return_msg, data: message.data.toString("hex"), seqNumber: this.seqNumber, energySavingDeviceP2PDataSeqNumber: this.energySavingDeviceP2PDataSeqNumber, offsetDataSeqNumber: this.offsetDataSeqNumber });
1075
- let msg_state = this.messageStates.get(message.seqNo);
1076
- if (this.energySavingDevice) {
1077
- const goodSeqNumber = this.energySavingDeviceP2PSeqMapping.get(message.seqNo);
1078
- if (goodSeqNumber) {
1079
- this.energySavingDeviceP2PSeqMapping.delete(message.seqNo);
1080
- msg_state = this.messageStates.get(goodSeqNumber);
1081
- this.log.debug(`Station ${this.rawStation.station_sn} - Energy saving Device - Result data received - Detecting correct sequence number`, { commandIdName: commandStr, commandId: message.commandId, seqNumber: message.seqNo, newSeqNumber: goodSeqNumber, energySavingDeviceP2PSeqMappingCount: this.energySavingDeviceP2PSeqMapping.size });
1082
- message.seqNo = goodSeqNumber;
1083
- }
1084
- }
1085
- if (msg_state) {
1086
- if (msg_state.commandType === message.commandId) {
1087
- this._clearTimeout(msg_state.timeout);
1088
- const command_type = msg_state.nestedCommandType !== undefined ? msg_state.nestedCommandType : msg_state.commandType;
1089
- this.log.debug(`Station ${this.rawStation.station_sn} - Result data for command received`, { message: { sequence: msg_state.sequence, commandType: msg_state.commandType, nestedCommandType: msg_state.nestedCommandType, channel: msg_state.channel, acknowledged: msg_state.acknowledged, retries: msg_state.retries, returnCode: msg_state.returnCode, data: msg_state.data, customData: msg_state.customData }, resultCodeName: error_codeStr, resultCode: return_code });
1090
- if (return_code === types_1.ErrorCode.ERROR_FAILED_TO_REQUEST) {
1091
- msg_state.returnCode = return_code;
1092
- this._sendCommand(msg_state);
1093
- }
1094
- else {
1095
- this.emit("command", {
1096
- command_type: command_type,
1097
- channel: msg_state.channel,
1098
- return_code: return_code,
1099
- customData: msg_state.customData
1100
- });
1101
- this.messageStates.delete(message.seqNo);
1102
- if (command_type === types_1.CommandType.CMD_SMARTSAFE_SETTINGS || command_type === types_1.CommandType.CMD_SET_PAYLOAD_LOCKV12) {
1103
- this.lastCustomData = msg_state.customData;
1104
- this.lastChannel = msg_state.channel;
1105
- this.secondaryCommandTimeout = setTimeout(() => {
1106
- this.log.warn(`Station ${this.rawStation.station_sn} - Result data for secondary command not received`, { message: { sequence: msg_state.sequence, commandType: msg_state.commandType, nestedCommandType: msg_state.nestedCommandType, channel: msg_state.channel, acknowledged: msg_state.acknowledged, retries: msg_state.retries, returnCode: msg_state.returnCode, data: msg_state.data, customData: msg_state.customData } });
1107
- this.secondaryCommandTimeout = undefined;
1108
- this.emit("secondary command", {
1109
- command_type: msg_state.nestedCommandType !== undefined ? msg_state.nestedCommandType : msg_state.commandType,
1110
- channel: msg_state.channel,
1111
- return_code: types_1.ErrorCode.ERROR_COMMAND_TIMEOUT,
1112
- customData: msg_state.customData
1113
- });
1114
- this._clearESDDisconnectTimeout();
1115
- this.sendQueuedMessage();
1116
- }, this.MAX_COMMAND_RESULT_WAIT);
1117
- }
1118
- else {
1119
- this._clearESDDisconnectTimeout();
1120
- this.sendQueuedMessage();
1121
- }
1122
- if (msg_state.commandType === types_1.CommandType.CMD_START_REALTIME_MEDIA ||
1123
- (msg_state.nestedCommandType !== undefined && msg_state.nestedCommandType === types_1.CommandType.CMD_START_REALTIME_MEDIA && msg_state.commandType === types_1.CommandType.CMD_SET_PAYLOAD) ||
1124
- msg_state.commandType === types_1.CommandType.CMD_RECORD_VIEW ||
1125
- (msg_state.nestedCommandType !== undefined && msg_state.nestedCommandType === 1000 && msg_state.commandType === types_1.CommandType.CMD_DOORBELL_SET_PAYLOAD)) {
1126
- this.waitForStreamData(types_1.P2PDataType.VIDEO);
1127
- }
1128
- else if (msg_state.commandType === types_1.CommandType.CMD_DOWNLOAD_VIDEO) {
1129
- this.waitForStreamData(types_1.P2PDataType.BINARY);
1130
- }
1131
- else if (msg_state.commandType === types_1.CommandType.CMD_START_TALKBACK || (msg_state.commandType === types_1.CommandType.CMD_DOORBELL_SET_PAYLOAD && msg_state.nestedCommandType === types_1.IndoorSoloSmartdropCommandType.CMD_START_SPEAK)) {
1132
- if (return_code === types_1.ErrorCode.ERROR_PPCS_SUCCESSFUL) {
1133
- this.startTalkback(msg_state.channel);
1134
- }
1135
- else if (return_code === types_1.ErrorCode.ERROR_NOT_FIND_DEV) {
1136
- this.emit("talkback error", msg_state.channel, new error_1.TalkbackError(`Station ${this.rawStation.station_sn} channel ${msg_state.channel} someone is responding now.`));
1137
- }
1138
- else if (return_code === types_1.ErrorCode.ERROR_DEV_BUSY) {
1139
- this.emit("talkback error", msg_state.channel, new error_1.TalkbackError(`Station ${this.rawStation.station_sn} channel ${msg_state.channel} wait a second, device is busy.`));
1140
- }
1141
- else {
1142
- this.emit("talkback error", msg_state.channel, new error_1.TalkbackError(`Station ${this.rawStation.station_sn} channel ${msg_state.channel} connect failed please try again later.`));
1143
- }
1144
- }
1145
- else if (msg_state.commandType === types_1.CommandType.CMD_STOP_TALKBACK || (msg_state.commandType === types_1.CommandType.CMD_DOORBELL_SET_PAYLOAD && msg_state.nestedCommandType === types_1.IndoorSoloSmartdropCommandType.CMD_END_SPEAK)) {
1146
- this.stopTalkback(msg_state.channel);
1147
- }
1148
- else if (msg_state.commandType === types_1.CommandType.CMD_SDINFO_EX) {
1149
- this.emit("sd info ex", message.data.slice(0, 4).readUInt32LE(), message.data.slice(4, 8).readUInt32LE(), message.data.slice(8, 12).readUInt32LE());
1150
- }
1151
- }
1152
- }
1153
- else {
1154
- this.messageStates.delete(message.seqNo);
1155
- this.log.debug(`Station ${this.rawStation.station_sn} - dataType: ${types_1.P2PDataType[message.dataType]} commandtype and sequencenumber different!`, { msg_sequence: msg_state.sequence, msg_channel: msg_state.channel, msg_commandType: msg_state.commandType, message: message, seqNumber: this.seqNumber, energySavingDeviceP2PDataSeqNumber: this.energySavingDeviceP2PDataSeqNumber, offsetDataSeqNumber: this.offsetDataSeqNumber });
1156
- this.log.warn(`P2P protocol instability detected for station ${this.rawStation.station_sn}. Please reinitialise the connection to solve the problem!`);
1157
- }
1158
- }
1159
- else if (message.commandId !== types_1.CommandType.CMD_PING && message.commandId !== types_1.CommandType.CMD_GET_DEVICE_PING) {
1160
- this.log.debug(`Station ${this.rawStation.station_sn} - dataType: ${types_1.P2PDataType[message.dataType]} commandId: ${message.commandId} sequence: ${message.seqNo} not present!`, { seqNumber: this.seqNumber, energySavingDeviceP2PDataSeqNumber: this.energySavingDeviceP2PDataSeqNumber, offsetDataSeqNumber: this.offsetDataSeqNumber });
1161
- }
1162
- }
1163
- else {
1164
- this.log.debug(`Station ${this.rawStation.station_sn} - Unsupported response`, { dataType: types_1.P2PDataType[message.dataType], commandIdName: commandStr, commandId: message.commandId, message: message.data.toString("hex") });
1165
- }
1166
- }
1167
- else if (message.dataType === types_1.P2PDataType.VIDEO || message.dataType === types_1.P2PDataType.BINARY) {
1168
- this.handleDataBinaryAndVideo(message);
1169
- }
1170
- else {
1171
- this.log.debug(`Station ${this.rawStation.station_sn} - Not implemented data type`, { seqNo: message.seqNo, dataType: message.dataType, commandId: message.commandId, message: message.data.toString("hex") });
1172
- }
1173
- }
1174
- isIFrame(data, isKeyFrame) {
1175
- if (this.rawStation.station_sn.startsWith("T8410") || this.rawStation.station_sn.startsWith("T8400") || this.rawStation.station_sn.startsWith("T8401") || this.rawStation.station_sn.startsWith("T8411") ||
1176
- this.rawStation.station_sn.startsWith("T8202") || this.rawStation.station_sn.startsWith("T8422") || this.rawStation.station_sn.startsWith("T8424") || this.rawStation.station_sn.startsWith("T8423") ||
1177
- this.rawStation.station_sn.startsWith("T8130") || this.rawStation.station_sn.startsWith("T8131") || this.rawStation.station_sn.startsWith("T8420") || this.rawStation.station_sn.startsWith("T8440") ||
1178
- this.rawStation.station_sn.startsWith("T8441") || this.rawStation.station_sn.startsWith("T8442") || (0, utils_1.checkT8420)(this.rawStation.station_sn)) {
1179
- //TODO: Need to add battery doorbells as seen in source => T8210,T8220,T8221,T8222
1180
- return isKeyFrame;
1181
- }
1182
- const iframe = (0, utils_1.isIFrame)(data);
1183
- if (iframe === false) {
1184
- // Fallback
1185
- return isKeyFrame;
1186
- }
1187
- return iframe;
1188
- }
1189
- waitForStreamData(dataType) {
1190
- if (this.currentMessageState[dataType].p2pStreamingTimeout) {
1191
- clearTimeout(this.currentMessageState[dataType].p2pStreamingTimeout);
1192
- }
1193
- this.currentMessageState[dataType].p2pStreamingTimeout = setTimeout(() => {
1194
- var _a;
1195
- this.log.info(`Stopping the station stream for the device ${(_a = this.deviceSNs[this.currentMessageState[dataType].p2pStreamChannel]) === null || _a === void 0 ? void 0 : _a.sn}, because we haven't received any data for ${this.MAX_STREAM_DATA_WAIT} seconds`);
1196
- this.endStream(dataType, true);
1197
- }, this.MAX_STREAM_DATA_WAIT);
1198
- }
1199
- handleDataBinaryAndVideo(message) {
1200
- var _a, _b, _c;
1201
- if (!this.currentMessageState[message.dataType].invalidStream) {
1202
- switch (message.commandId) {
1203
- case types_1.CommandType.CMD_VIDEO_FRAME:
1204
- this.waitForStreamData(message.dataType);
1205
- const videoMetaData = {
1206
- streamType: 0,
1207
- videoSeqNo: 0,
1208
- videoFPS: 15,
1209
- videoWidth: 1920,
1210
- videoHeight: 1080,
1211
- videoTimestamp: 0,
1212
- videoDataLength: 0,
1213
- aesKey: ""
1214
- };
1215
- const data_length = message.data.readUInt32LE();
1216
- const isKeyFrame = message.data.slice(4, 5).readUInt8() === 1 ? true : false;
1217
- videoMetaData.videoDataLength = message.data.slice(0, 4).readUInt32LE();
1218
- videoMetaData.streamType = message.data.slice(5, 6).readUInt8();
1219
- videoMetaData.videoSeqNo = message.data.slice(6, 8).readUInt16LE();
1220
- videoMetaData.videoFPS = message.data.slice(8, 10).readUInt16LE();
1221
- videoMetaData.videoWidth = message.data.slice(10, 12).readUInt16LE();
1222
- videoMetaData.videoHeight = message.data.slice(12, 14).readUInt16LE();
1223
- videoMetaData.videoTimestamp = message.data.slice(14, 20).readUIntLE(0, 6);
1224
- let payloadStart = 22;
1225
- if (message.signCode > 0 && data_length >= 128) {
1226
- const key = message.data.slice(22, 150);
1227
- const rsaKey = this.currentMessageState[message.dataType].rsaKey;
1228
- if (rsaKey) {
1229
- try {
1230
- videoMetaData.aesKey = rsaKey.decrypt(key).toString("hex");
1231
- this.log.debug(`Station ${this.rawStation.station_sn} - Decrypted AES key: ${videoMetaData.aesKey}`);
1232
- }
1233
- catch (error) {
1234
- this.log.warn(`Station ${this.rawStation.station_sn} - AES key could not be decrypted! The entire stream is discarded. - Error:`, error);
1235
- this.currentMessageState[message.dataType].invalidStream = true;
1236
- this.emit("livestream error", message.channel, new error_1.LivestreamError(`Station ${this.rawStation.station_sn} AES key could not be decrypted! The entire stream is discarded.`));
1237
- return;
1238
- }
1239
- }
1240
- else {
1241
- this.log.warn(`Station ${this.rawStation.station_sn} - Private RSA key is missing! Stream could not be decrypted. The entire stream is discarded.`);
1242
- this.currentMessageState[message.dataType].invalidStream = true;
1243
- this.emit("livestream error", message.channel, new error_1.LivestreamError(`Station ${this.rawStation.station_sn} private RSA key is missing! Stream could not be decrypted. The entire stream is discarded.`));
1244
- return;
1245
- }
1246
- payloadStart = 151;
1247
- }
1248
- let video_data;
1249
- if (videoMetaData.aesKey !== "") {
1250
- const encrypted_data = message.data.slice(payloadStart, payloadStart + 128);
1251
- const unencrypted_data = message.data.slice(payloadStart + 128, payloadStart + videoMetaData.videoDataLength);
1252
- video_data = Buffer.concat([(0, utils_1.decryptAESData)(videoMetaData.aesKey, encrypted_data), unencrypted_data]);
1253
- }
1254
- else {
1255
- video_data = message.data.slice(payloadStart, payloadStart + videoMetaData.videoDataLength);
1256
- }
1257
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME`, { dataSize: message.data.length, metadata: videoMetaData, videoDataSize: video_data.length });
1258
- this.currentMessageState[message.dataType].p2pStreamMetadata.videoFPS = videoMetaData.videoFPS;
1259
- this.currentMessageState[message.dataType].p2pStreamMetadata.videoHeight = videoMetaData.videoHeight;
1260
- this.currentMessageState[message.dataType].p2pStreamMetadata.videoWidth = videoMetaData.videoWidth;
1261
- if (!this.currentMessageState[message.dataType].p2pStreamFirstVideoDataReceived) {
1262
- if (this.rawStation.station_sn.startsWith("T8410") || this.rawStation.station_sn.startsWith("T8400") || this.rawStation.station_sn.startsWith("T8401") || this.rawStation.station_sn.startsWith("T8411") ||
1263
- this.rawStation.station_sn.startsWith("T8202") || this.rawStation.station_sn.startsWith("T8422") || this.rawStation.station_sn.startsWith("T8424") || this.rawStation.station_sn.startsWith("T8423") ||
1264
- this.rawStation.station_sn.startsWith("T8130") || this.rawStation.station_sn.startsWith("T8131") || this.rawStation.station_sn.startsWith("T8420") || this.rawStation.station_sn.startsWith("T8440") ||
1265
- this.rawStation.station_sn.startsWith("T8441") || this.rawStation.station_sn.startsWith("T8442") || (0, utils_1.checkT8420)(this.rawStation.station_sn)) {
1266
- this.currentMessageState[message.dataType].p2pStreamMetadata.videoCodec = videoMetaData.streamType === 1 ? types_1.VideoCodec.H264 : videoMetaData.streamType === 2 ? types_1.VideoCodec.H265 : (0, utils_1.getVideoCodec)(video_data);
1267
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME - Video codec information received from packet`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, metadata: videoMetaData });
1268
- }
1269
- else if (this.isIFrame(video_data, isKeyFrame)) {
1270
- this.currentMessageState[message.dataType].p2pStreamMetadata.videoCodec = (0, utils_1.getVideoCodec)(video_data);
1271
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME - Video codec extracted from video data`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, metadata: videoMetaData });
1272
- }
1273
- else {
1274
- this.currentMessageState[message.dataType].p2pStreamMetadata.videoCodec = (0, utils_1.getVideoCodec)(video_data);
1275
- if (this.currentMessageState[message.dataType].p2pStreamMetadata.videoCodec === types_1.VideoCodec.UNKNOWN) {
1276
- this.currentMessageState[message.dataType].p2pStreamMetadata.videoCodec = videoMetaData.streamType === 1 ? types_1.VideoCodec.H264 : videoMetaData.streamType === 2 ? types_1.VideoCodec.H265 : types_1.VideoCodec.UNKNOWN;
1277
- if (this.currentMessageState[message.dataType].p2pStreamMetadata.videoCodec === types_1.VideoCodec.UNKNOWN) {
1278
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME - Unknown video codec`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, metadata: videoMetaData });
1279
- }
1280
- else {
1281
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME - Fallback, using video codec information received from packet`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, metadata: videoMetaData });
1282
- }
1283
- }
1284
- else {
1285
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME - Fallback, video codec extracted from video data`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, metadata: videoMetaData });
1286
- }
1287
- }
1288
- this.currentMessageState[message.dataType].p2pStreamFirstVideoDataReceived = true;
1289
- this.currentMessageState[message.dataType].waitForAudioData = setTimeout(() => {
1290
- this.currentMessageState[message.dataType].waitForAudioData = undefined;
1291
- this.currentMessageState[message.dataType].p2pStreamMetadata.audioCodec = types_1.AudioCodec.NONE;
1292
- this.currentMessageState[message.dataType].p2pStreamFirstAudioDataReceived = true;
1293
- if (this.currentMessageState[message.dataType].p2pStreamFirstAudioDataReceived && this.currentMessageState[message.dataType].p2pStreamFirstVideoDataReceived) {
1294
- this.emitStreamStartEvent(message.dataType);
1295
- }
1296
- }, this.AUDIO_CODEC_ANALYZE_TIMEOUT);
1297
- }
1298
- if (this.currentMessageState[message.dataType].p2pStreamNotStarted) {
1299
- if (this.currentMessageState[message.dataType].p2pStreamFirstAudioDataReceived && this.currentMessageState[message.dataType].p2pStreamFirstVideoDataReceived) {
1300
- this.emitStreamStartEvent(message.dataType);
1301
- }
1302
- }
1303
- if (message.dataType === types_1.P2PDataType.VIDEO) {
1304
- if ((0, utils_1.findStartCode)(video_data)) {
1305
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME: startcode found`, { isKeyFrame: isKeyFrame, preFrameVideoDataLength: this.currentMessageState[message.dataType].preFrameVideoData.length });
1306
- if (!this.currentMessageState[message.dataType].receivedFirstIFrame)
1307
- this.currentMessageState[message.dataType].receivedFirstIFrame = this.isIFrame(video_data, isKeyFrame);
1308
- if (this.currentMessageState[message.dataType].receivedFirstIFrame) {
1309
- if (this.currentMessageState[message.dataType].preFrameVideoData.length > this.MAX_VIDEO_PACKET_BYTES)
1310
- this.currentMessageState[message.dataType].preFrameVideoData = Buffer.from([]);
1311
- if (this.currentMessageState[message.dataType].preFrameVideoData.length > 0) {
1312
- (_a = this.currentMessageState[message.dataType].videoStream) === null || _a === void 0 ? void 0 : _a.push(this.currentMessageState[message.dataType].preFrameVideoData);
1313
- }
1314
- this.currentMessageState[message.dataType].preFrameVideoData = Buffer.from(video_data);
1315
- }
1316
- else {
1317
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME: Skipping because first frame is not an I frame.`);
1318
- }
1319
- }
1320
- else {
1321
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME: No startcode found`, { isKeyFrame: isKeyFrame, preFrameVideoDataLength: this.currentMessageState[message.dataType].preFrameVideoData.length });
1322
- if (this.currentMessageState[message.dataType].preFrameVideoData.length > 0) {
1323
- this.currentMessageState[message.dataType].preFrameVideoData = Buffer.concat([this.currentMessageState[message.dataType].preFrameVideoData, video_data]);
1324
- }
1325
- }
1326
- }
1327
- else if (message.dataType === types_1.P2PDataType.BINARY) {
1328
- (_b = this.currentMessageState[message.dataType].videoStream) === null || _b === void 0 ? void 0 : _b.push(video_data);
1329
- }
1330
- break;
1331
- case types_1.CommandType.CMD_AUDIO_FRAME:
1332
- this.waitForStreamData(message.dataType);
1333
- const audioMetaData = {
1334
- audioType: types_1.AudioCodec.NONE,
1335
- audioSeqNo: 0,
1336
- audioTimestamp: 0,
1337
- audioDataLength: 0
1338
- };
1339
- audioMetaData.audioDataLength = message.data.slice(0, 4).readUInt32LE();
1340
- audioMetaData.audioType = message.data.slice(5, 6).readUInt8();
1341
- audioMetaData.audioSeqNo = message.data.slice(6, 8).readUInt16LE();
1342
- audioMetaData.audioTimestamp = message.data.slice(8, 14).readUIntLE(0, 6);
1343
- const audio_data = Buffer.from(message.data.slice(16));
1344
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_AUDIO_FRAME`, { dataSize: message.data.length, metadata: audioMetaData, audioDataSize: audio_data.length });
1345
- if (!this.currentMessageState[message.dataType].p2pStreamFirstAudioDataReceived) {
1346
- if (this.currentMessageState[message.dataType].waitForAudioData !== undefined) {
1347
- clearTimeout(this.currentMessageState[message.dataType].waitForAudioData);
1348
- }
1349
- this.currentMessageState[message.dataType].p2pStreamFirstAudioDataReceived = true;
1350
- this.currentMessageState[message.dataType].p2pStreamMetadata.audioCodec = audioMetaData.audioType === 0 ? types_1.AudioCodec.AAC : audioMetaData.audioType === 1 ? types_1.AudioCodec.AAC_LC : audioMetaData.audioType === 7 ? types_1.AudioCodec.AAC_ELD : types_1.AudioCodec.UNKNOWN;
1351
- }
1352
- if (this.currentMessageState[message.dataType].p2pStreamNotStarted) {
1353
- if (this.currentMessageState[message.dataType].p2pStreamFirstAudioDataReceived && this.currentMessageState[message.dataType].p2pStreamFirstVideoDataReceived) {
1354
- this.emitStreamStartEvent(message.dataType);
1355
- }
1356
- }
1357
- (_c = this.currentMessageState[message.dataType].audioStream) === null || _c === void 0 ? void 0 : _c.push(audio_data);
1358
- break;
1359
- default:
1360
- this.log.debug(`Station ${this.rawStation.station_sn} - Not implemented message`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, data: message.data.toString("hex") });
1361
- break;
1362
- }
1363
- }
1364
- else {
1365
- this.log.debug(`Station ${this.rawStation.station_sn} - Invalid stream data, dropping complete stream`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, data: message.data.toString("hex") });
1366
- }
1367
- }
1368
- handleDataControl(message) {
1369
- try {
1370
- switch (message.commandId) {
1371
- case types_1.CommandType.CMD_GET_ALARM_MODE:
1372
- this.log.debug(`Station ${this.rawStation.station_sn} - Alarm mode changed to: ${types_2.AlarmMode[message.data.readUIntBE(0, 1)]}`);
1373
- this.emit("alarm mode", message.data.readUIntBE(0, 1));
1374
- break;
1375
- case types_1.CommandType.CMD_CAMERA_INFO:
1376
- try {
1377
- const data = message.data.toString("utf8");
1378
- this.log.debug(`Station ${this.rawStation.station_sn} - Camera info`, { cameraInfo: data });
1379
- this.emit("camera info", (0, utils_3.parseJSON)(data, this.log));
1380
- }
1381
- catch (error) {
1382
- this.log.error(`Station ${this.rawStation.station_sn} - Camera info - Error:`, error);
1383
- }
1384
- break;
1385
- case types_1.CommandType.CMD_CONVERT_MP4_OK:
1386
- const totalBytes = message.data.slice(1).readUInt32LE();
1387
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_CONVERT_MP4_OK`, { channel: message.channel, totalBytes: totalBytes });
1388
- this.downloadTotalBytes = totalBytes;
1389
- this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreaming = true;
1390
- this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreamChannel = message.channel;
1391
- break;
1392
- case types_1.CommandType.CMD_WIFI_CONFIG:
1393
- const rssi = message.data.readInt32LE();
1394
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_WIFI_CONFIG`, { channel: message.channel, rssi: rssi });
1395
- this.emit("wifi rssi", message.channel, rssi);
1396
- break;
1397
- case types_1.CommandType.CMD_DOWNLOAD_FINISH:
1398
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_DOWNLOAD_FINISH`, { channel: message.channel });
1399
- this.endStream(types_1.P2PDataType.BINARY);
1400
- break;
1401
- case types_1.CommandType.CMD_DOORBELL_NOTIFY_PAYLOAD:
1402
- try {
1403
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_DOORBELL_NOTIFY_PAYLOAD`, { payload: message.data.toString() });
1404
- //TODO: Finish implementation, emit an event...
1405
- //VDBStreamInfo (1005) and VoltageEvent (1015)
1406
- //this.emit("", parseJSON(message.data.toString(), this.log) as xy);
1407
- }
1408
- catch (error) {
1409
- this.log.error(`Station ${this.rawStation.station_sn} - CMD_DOORBELL_NOTIFY_PAYLOAD - Error:`, error);
1410
- }
1411
- break;
1412
- case types_1.CommandType.CMD_NAS_SWITCH:
1413
- try {
1414
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NAS_SWITCH`, { payload: message.data.toString() });
1415
- this.emit("rtsp url", message.channel, message.data.toString("utf8", 0, message.data.indexOf("\0", 0, "utf8")));
1416
- }
1417
- catch (error) {
1418
- this.log.error(`Station ${this.rawStation.station_sn} - CMD_NAS_SWITCH - Error:`, error);
1419
- }
1420
- break;
1421
- case types_1.CommandType.SUB1G_REP_UNPLUG_POWER_LINE:
1422
- try {
1423
- this.log.debug(`Station ${this.rawStation.station_sn} - SUB1G_REP_UNPLUG_POWER_LINE`, { payload: message.data.toString() });
1424
- const chargeType = message.data.slice(0, 4).readUInt32LE();
1425
- const batteryLevel = message.data.slice(4, 8).readUInt32LE();
1426
- this.emit("charging state", message.channel, chargeType, batteryLevel);
1427
- }
1428
- catch (error) {
1429
- this.log.error(`Station ${this.rawStation.station_sn} - SUB1G_REP_UNPLUG_POWER_LINE - Error:`, error);
1430
- }
1431
- break;
1432
- case types_1.CommandType.SUB1G_REP_RUNTIME_STATE:
1433
- try {
1434
- this.log.debug(`Station ${this.rawStation.station_sn} - SUB1G_REP_RUNTIME_STATE`, { payload: message.data.toString() });
1435
- const batteryLevel = message.data.slice(0, 4).readUInt32LE();
1436
- const temperature = message.data.slice(4, 8).readUInt32LE();
1437
- this.emit("runtime state", message.channel, batteryLevel, temperature);
1438
- }
1439
- catch (error) {
1440
- this.log.error(`Station ${this.rawStation.station_sn} - SUB1G_REP_RUNTIME_STATE - Error:`, error);
1441
- }
1442
- break;
1443
- case types_1.CommandType.CMD_SET_FLOODLIGHT_MANUAL_SWITCH:
1444
- try {
1445
- const enabled = message.data.readUIntBE(0, 1) === 1 ? true : false;
1446
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_SET_FLOODLIGHT_MANUAL_SWITCH`, { enabled: enabled, payload: message.data.toString() });
1447
- this.emit("floodlight manual switch", message.channel, enabled);
1448
- }
1449
- catch (error) {
1450
- this.log.error(`Station ${this.rawStation.station_sn} - CMD_SET_FLOODLIGHT_MANUAL_SWITCH - Error:`, error);
1451
- }
1452
- break;
1453
- case types_1.CommandType.CMD_GET_DEVICE_PING:
1454
- try {
1455
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_GET_DEVICE_PING`, { payload: message.data.toString() });
1456
- this.sendCommandDevicePing(message.channel);
1457
- }
1458
- catch (error) {
1459
- this.log.error(`Station ${this.rawStation.station_sn} - CMD_GET_DEVICE_PING - Error:`, error);
1460
- }
1461
- break;
1462
- case types_1.CommandType.CMD_NOTIFY_PAYLOAD:
1463
- try {
1464
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD`, { payload: message.data.toString() });
1465
- const json = (0, utils_3.parseJSON)(message.data.toString("utf-8"), this.log);
1466
- if (json !== undefined) {
1467
- if (this.rawStation.station_sn.startsWith("T8520")) {
1468
- //TODO: Implement notification payload or T8520
1469
- if (json.cmd === types_1.CommandType.P2P_ADD_PW || json.cmd === types_1.CommandType.P2P_QUERY_PW || json.cmd === types_1.CommandType.P2P_GET_LOCK_PARAM || json.cmd === types_1.CommandType.P2P_GET_USER_AND_PW_ID) {
1470
- // encrypted data
1471
- //TODO: Handle decryption of encrypted Data (AES) - For decryption use the cached aeskey used for sending the command!
1472
- const aesKey = this.getLockAESKey(json.cmd);
1473
- if (aesKey !== undefined) {
1474
- const decryptedPayload = (0, utils_1.decryptPayloadData)(Buffer.from(json.payload, "base64"), Buffer.from(aesKey, "hex"), Buffer.from((0, utils_1.getLockVectorBytes)(this.rawStation.station_sn), "hex")).toString();
1475
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD Lock - Received`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, decryptedPayload: decryptedPayload, aesKey: aesKey });
1476
- switch (json.cmd) {
1477
- case types_1.CommandType.P2P_ADD_PW:
1478
- // decryptedPayload: {"code":0,"passwordId":"002C"}
1479
- break;
1480
- }
1481
- }
1482
- }
1483
- else if (json.cmd === types_1.CommandType.P2P_QUERY_STATUS_IN_LOCK) {
1484
- // Example: {"code":0,"slBattery":"82","slState":"4","trigger":2}
1485
- const payload = json.payload;
1486
- this.emit("parameter", message.channel, types_1.CommandType.CMD_SMARTLOCK_QUERY_BATTERY_LEVEL, payload.slBattery);
1487
- this.emit("parameter", message.channel, types_1.CommandType.CMD_SMARTLOCK_QUERY_STATUS, payload.slState);
1488
- }
1489
- else {
1490
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD - Not implemented`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, message: message.data.toString() });
1491
- }
1492
- }
1493
- else if (json.cmd === types_1.CommandType.CMD_DOORLOCK_P2P_SEQ) {
1494
- const payload = json.payload;
1495
- switch (payload.lock_cmd) {
1496
- case 0:
1497
- if (payload.seq_num !== undefined) {
1498
- this.lockSeqNumber = payload.seq_num;
1499
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD - Lock sequence number`, { lockSeqNumber: this.lockSeqNumber });
1500
- }
1501
- break;
1502
- default:
1503
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD - Not implemented`, { message: message.data.toString() });
1504
- break;
1505
- }
1506
- }
1507
- else if (json.cmd === types_1.CommandType.CMD_DOORLOCK_DATA_PASS_THROUGH) {
1508
- const payload = json.payload;
1509
- if (this.deviceSNs[message.channel] !== undefined) {
1510
- if (payload.lock_payload !== undefined) {
1511
- const decoded = (0, utils_1.decodeBase64)((0, utils_1.decodeLockPayload)(Buffer.from(payload.lock_payload)));
1512
- const key = (0, utils_1.generateBasicLockAESKey)(this.deviceSNs[message.channel].adminUserId, this.rawStation.station_sn);
1513
- const iv = (0, utils_1.getLockVectorBytes)(this.rawStation.station_sn);
1514
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_DOORLOCK_DATA_PASS_THROUGH`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, key: key, iv: iv, decoded: decoded.toString("hex") });
1515
- payload.lock_payload = (0, utils_1.decryptLockAESData)(key, iv, decoded).toString("hex");
1516
- switch (payload.lock_cmd) {
1517
- case types_1.ESLBleCommand.NOTIFY:
1518
- const notifyBuffer = Buffer.from(payload.lock_payload, "hex");
1519
- this.emit("parameter", message.channel, types_1.CommandType.CMD_GET_BATTERY, notifyBuffer.slice(3, 4).readInt8().toString());
1520
- this.emit("parameter", message.channel, types_1.CommandType.CMD_DOORLOCK_GET_STATE, notifyBuffer.slice(6, 7).readInt8().toString());
1521
- break;
1522
- default:
1523
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_DOORLOCK_DATA_PASS_THROUGH - Not implemented`, { message: message.data.toString() });
1524
- break;
1525
- }
1526
- }
1527
- }
1528
- }
1529
- else if (json.cmd === types_1.CommandType.CMD_SET_PAYLOAD_LOCKV12) {
1530
- const payload = json.payload;
1531
- if (payload.lock_payload !== undefined) {
1532
- const fac = new ble_1.BleCommandFactory(payload.lock_payload);
1533
- if (fac.getCommandCode() !== types_1.ESLBleCommand.NOTIFY) {
1534
- const aesKey = this.getLockAESKey(fac.getCommandCode());
1535
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD Lock V12 - Received`, { fac: fac.toString(), aesKey: aesKey });
1536
- let data = fac.getData();
1537
- if (aesKey !== undefined) {
1538
- data = (0, utils_1.decryptPayloadData)(data, Buffer.from(aesKey, "hex"), Buffer.from((0, utils_1.getLockVectorBytes)(this.rawStation.station_sn), "hex"));
1539
- }
1540
- const returnCode = data.readInt8(0);
1541
- if (this.lastChannel !== undefined && this.lastCustomData !== undefined) {
1542
- const result = {
1543
- channel: this.lastChannel,
1544
- command_type: Number.parseInt(types_1.ESLCommand[types_1.ESLBleCommand[fac.getCommandCode()]]),
1545
- return_code: returnCode,
1546
- customData: this.lastCustomData
1547
- };
1548
- this.emit("secondary command", result);
1549
- }
1550
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD Lock V12 return code: ${returnCode}`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, decoded: data, bleCommandCode: types_1.ESLBleCommand[fac.getCommandCode()], returnCode: returnCode, channel: this.lastChannel, customData: this.lastCustomData });
1551
- this._clearSecondaryCommandTimeout();
1552
- this.sendQueuedMessage();
1553
- }
1554
- else {
1555
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD Lock V12 - Received notify`, { fac: fac.toString() });
1556
- }
1557
- }
1558
- else {
1559
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD Lock V12 - Unexpected response`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, message: message.data.toString() });
1560
- }
1561
- }
1562
- else if (device_1.Device.isSmartSafe(this.rawStation.device_type)) {
1563
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD SmartSafe`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd });
1564
- switch (json.cmd) {
1565
- case types_1.CommandType.CMD_SMARTSAFE_SETTINGS:
1566
- {
1567
- const payload = json.payload;
1568
- try {
1569
- const data = (0, utils_1.decodeSmartSafeData)(this.rawStation.station_sn, Buffer.from(payload.data, "hex"));
1570
- const returnCode = data.data.readInt8(0);
1571
- if (this.lastChannel !== undefined && this.lastCustomData !== undefined) {
1572
- const result = {
1573
- channel: this.lastChannel,
1574
- command_type: payload.prj_id,
1575
- return_code: returnCode,
1576
- customData: this.lastCustomData
1577
- };
1578
- this.emit("secondary command", result);
1579
- }
1580
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD SmartSafe return code: ${data.data.readInt8(0)}`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, decoded: data, commandCode: types_1.SmartSafeCommandCode[data.commandCode], returnCode: returnCode, channel: this.lastChannel, customData: this.lastCustomData });
1581
- }
1582
- catch (error) {
1583
- this.log.error(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD SmartSafe Error:`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, channel: this.lastChannel, customData: this.lastCustomData, payload: payload, error: error });
1584
- }
1585
- this._clearSecondaryCommandTimeout();
1586
- this.sendQueuedMessage();
1587
- break;
1588
- }
1589
- case types_1.CommandType.CMD_SMARTSAFE_STATUS_UPDATE:
1590
- {
1591
- const payload = json.payload;
1592
- switch (payload.event_type) {
1593
- case types_3.SmartSafeEvent.LOCK_STATUS:
1594
- {
1595
- const eventValues = payload.event_value;
1596
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD SmartSafe Status update - LOCK_STATUS`, { eventValues: eventValues });
1597
- /*
1598
- type values:
1599
- 1: Unlocked by PIN
1600
- 2: Unlocked by User
1601
- 3: Unlocked by key
1602
- 4: Unlocked by App
1603
- 5: Unlocked by Dual Unlock
1604
- */
1605
- if (eventValues.action === 0) {
1606
- this.emit("parameter", message.channel, types_1.CommandType.CMD_SMARTSAFE_LOCK_STATUS, "0");
1607
- }
1608
- else if (eventValues.action === 1) {
1609
- this.emit("parameter", message.channel, types_1.CommandType.CMD_SMARTSAFE_LOCK_STATUS, "1");
1610
- }
1611
- else if (eventValues.action === 2) {
1612
- this.emit("jammed", message.channel);
1613
- }
1614
- else if (eventValues.action === 3) {
1615
- this.emit("low battery", message.channel);
1616
- }
1617
- break;
1618
- }
1619
- case types_3.SmartSafeEvent.SHAKE_ALARM:
1620
- this.emit("shake alarm", message.channel, payload.event_value);
1621
- break;
1622
- case types_3.SmartSafeEvent.ALARM_911:
1623
- this.emit("911 alarm", message.channel, payload.event_value);
1624
- break;
1625
- //case SmartSafeEvent.BATTERY_STATUS:
1626
- // break;
1627
- case types_3.SmartSafeEvent.INPUT_ERR_MAX:
1628
- this.emit("wrong try-protect alarm", message.channel);
1629
- break;
1630
- default:
1631
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD SmartSafe Status update - Not implemented`, { message: message.data.toString() });
1632
- break;
1633
- }
1634
- break;
1635
- }
1636
- default:
1637
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD SmartSafe - Not implemented`, { message: message.data.toString() });
1638
- break;
1639
- }
1640
- }
1641
- else {
1642
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD - Not implemented`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, message: message.data.toString() });
1643
- }
1644
- }
1645
- }
1646
- catch (error) {
1647
- this.log.error(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD Error:`, { error: error, payload: message.data.toString() });
1648
- }
1649
- break;
1650
- case types_1.CommandType.CMD_GET_DELAY_ALARM:
1651
- try {
1652
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_GET_DELAY_ALARM :`, { payload: message.data.toString("hex") });
1653
- //When the alarm is armed, CMD_GET_DELAY_ALARM is called with event data 0, so ignore it
1654
- const alarmEventNumber = message.data.slice(0, 4).readUInt32LE();
1655
- const alarmDelay = message.data.slice(4, 8).readUInt32LE();
1656
- if (alarmEventNumber === 0) {
1657
- this.emit("alarm armed");
1658
- }
1659
- else {
1660
- this.emit("alarm delay", alarmEventNumber, alarmDelay);
1661
- }
1662
- }
1663
- catch (error) {
1664
- this.log.error(`Station ${this.rawStation.station_sn} - CMD_GET_DELAY_ALARM - Error:`, { error: error, payload: message.data.toString("hex") });
1665
- }
1666
- break;
1667
- case types_1.CommandType.CMD_SET_TONE_FILE:
1668
- try {
1669
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_SET_TONE_FILE :`, { payload: message.data.toString("hex") });
1670
- const alarmEventNumber = message.data.slice(0, 4).readUInt32LE();
1671
- this.emit("alarm event", alarmEventNumber);
1672
- }
1673
- catch (error) {
1674
- this.log.error(`Station ${this.rawStation.station_sn} - CMD_SET_TONE_FILE - Error:`, { error: error, payload: message.data.toString("hex") });
1675
- }
1676
- break;
1677
- case types_1.CommandType.CMD_SET_SNOOZE_MODE:
1678
- // Received for station managed devices when snooze time ends
1679
- try {
1680
- this.log.debug(`Station ${this.rawStation.station_sn} - CMD_SET_SNOOZE_MODE`, { payload: Buffer.from(message.data.toString(), "base64").toString() });
1681
- this.emit("parameter", message.channel, types_1.CommandType.CMD_SET_SNOOZE_MODE, message.data.toString());
1682
- }
1683
- catch (error) {
1684
- this.log.error(`Station ${this.rawStation.station_sn} - CMD_SET_SNOOZE_MODE - Error:`, error);
1685
- }
1686
- break;
1687
- case types_1.CommandType.CMD_PING:
1688
- // Ignore
1689
- break;
1690
- default:
1691
- this.log.debug(`Station ${this.rawStation.station_sn} - Not implemented - CONTROL message`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, data: message.data.toString("hex") });
1692
- break;
1693
- }
1694
- }
1695
- catch (error) {
1696
- this.log.error(`Station ${this.rawStation.station_sn} - ${types_1.CommandType[message.commandId]} - Error:`, error);
1697
- }
1698
- }
1699
- async sendAck(address, dataType, seqNo) {
1700
- const num_pending_acks = 1; // Max possible: 17 in one ack packet
1701
- const pendingAcksBuffer = Buffer.allocUnsafe(2);
1702
- pendingAcksBuffer.writeUInt16BE(num_pending_acks, 0);
1703
- const seqBuffer = Buffer.allocUnsafe(2);
1704
- seqBuffer.writeUInt16BE(seqNo, 0);
1705
- const payload = Buffer.concat([dataType, pendingAcksBuffer, seqBuffer]);
1706
- await this.sendMessage(`Send ack to station ${this.rawStation.station_sn}`, address, types_1.RequestMessageType.ACK, payload);
1707
- }
1708
- getDataType(input) {
1709
- if (input.compare(types_1.P2PDataTypeHeader.DATA) === 0) {
1710
- return types_1.P2PDataType.DATA;
1711
- }
1712
- else if (input.compare(types_1.P2PDataTypeHeader.VIDEO) === 0) {
1713
- return types_1.P2PDataType.VIDEO;
1714
- }
1715
- else if (input.compare(types_1.P2PDataTypeHeader.CONTROL) === 0) {
1716
- return types_1.P2PDataType.CONTROL;
1717
- }
1718
- else if (input.compare(types_1.P2PDataTypeHeader.BINARY) === 0) {
1719
- return types_1.P2PDataType.BINARY;
1720
- }
1721
- return types_1.P2PDataType.UNKNOWN;
1722
- }
1723
- async close() {
1724
- this.terminating = true;
1725
- this._clearLookupTimeout();
1726
- this._clearLookupRetryTimeout();
1727
- this._clearConnectTimeout();
1728
- this._clearHeartbeatTimeout();
1729
- this._clearMessageStateTimeouts();
1730
- this._clearMessageVideoStateTimeouts();
1731
- this._clearSecondaryCommandTimeout();
1732
- this.sendQueue = [];
1733
- if (this.socket) {
1734
- if (this.connected) {
1735
- await this.sendMessage(`Send end to station ${this.rawStation.station_sn}`, this.connectAddress, types_1.RequestMessageType.END);
1736
- this._disconnected();
1737
- }
1738
- else {
1739
- this._initialize();
1740
- }
1741
- }
1742
- }
1743
- getHeartbeatInterval() {
1744
- return this.HEARTBEAT_INTERVAL;
1745
- }
1746
- onClose() {
1747
- this.socket.removeAllListeners();
1748
- this.socket = (0, dgram_1.createSocket)("udp4");
1749
- this.socket.on("message", (msg, rinfo) => this.handleMsg(msg, rinfo));
1750
- this.socket.on("error", (error) => this.onError(error));
1751
- this.socket.on("close", () => this.onClose());
1752
- this.binded = false;
1753
- this._disconnected();
1754
- }
1755
- onError(error) {
1756
- this.log.debug(`Station ${this.rawStation.station_sn} - Error:`, error);
1757
- }
1758
- scheduleHeartbeat() {
1759
- if (this.isConnected()) {
1760
- this.sendPing(this.connectAddress);
1761
- this.heartbeatTimeout = setTimeout(() => {
1762
- this.scheduleHeartbeat();
1763
- }, this.getHeartbeatInterval());
1764
- }
1765
- else {
1766
- this.log.debug(`Station ${this.rawStation.station_sn} - Heartbeat disabled!`);
1767
- }
1768
- }
1769
- scheduleP2PKeepalive() {
1770
- if (this.isConnected()) {
1771
- this.sendCommandPing();
1772
- this.keepaliveTimeout = setTimeout(() => {
1773
- this.scheduleP2PKeepalive();
1774
- }, this.KEEPALIVE_INTERVAL);
1775
- this.closeEnergySavingDevice();
1776
- }
1777
- else {
1778
- this.log.debug(`Station ${this.rawStation.station_sn} - p2p keepalive disabled!`);
1779
- }
1780
- }
1781
- getDownloadRSAPrivateKey() {
1782
- if (this.currentMessageState[types_1.P2PDataType.BINARY].rsaKey === null) {
1783
- this.currentMessageState[types_1.P2PDataType.BINARY].rsaKey = (0, utils_1.getNewRSAPrivateKey)();
1784
- }
1785
- return this.currentMessageState[types_1.P2PDataType.BINARY].rsaKey;
1786
- }
1787
- setDownloadRSAPrivateKeyPem(pem) {
1788
- this.currentMessageState[types_1.P2PDataType.BINARY].rsaKey = (0, utils_1.getRSAPrivateKey)(pem);
1789
- }
1790
- getRSAPrivateKey() {
1791
- return this.currentMessageState[types_1.P2PDataType.VIDEO].rsaKey;
1792
- }
1793
- initializeStream(datatype) {
1794
- var _a, _b;
1795
- (_a = this.currentMessageState[datatype].videoStream) === null || _a === void 0 ? void 0 : _a.destroy();
1796
- (_b = this.currentMessageState[datatype].audioStream) === null || _b === void 0 ? void 0 : _b.destroy();
1797
- this.currentMessageState[datatype].videoStream = null;
1798
- this.currentMessageState[datatype].audioStream = null;
1799
- this.currentMessageState[datatype].videoStream = new stream_1.Readable({ autoDestroy: true,
1800
- // eslint-disable-next-line @typescript-eslint/no-empty-function
1801
- read() { } /*,
1802
-
1803
- destroy(this, error, _callback) {
1804
- if (error) {
1805
- this.emit("error", error);
1806
- }
1807
- this.emit("end");
1808
- this.emit("close");
1809
- }*/
1810
- });
1811
- this.currentMessageState[datatype].audioStream = new stream_1.Readable({ autoDestroy: true,
1812
- // eslint-disable-next-line @typescript-eslint/no-empty-function
1813
- read() { } /*,
1814
-
1815
- destroy(this, error, _callback) {
1816
- if (error) {
1817
- this.emit("error", error);
1818
- }
1819
- this.emit("end");
1820
- this.emit("close");
1821
- }*/
1822
- });
1823
- this.currentMessageState[datatype].p2pStreaming = false;
1824
- if (this.currentMessageState[datatype].waitForSeqNoTimeout !== undefined) {
1825
- clearTimeout(this.currentMessageState[datatype].waitForSeqNoTimeout);
1826
- this.currentMessageState[datatype].waitForSeqNoTimeout = undefined;
1827
- }
1828
- if (this.currentMessageState[datatype].waitForAudioData !== undefined) {
1829
- clearTimeout(this.currentMessageState[datatype].waitForAudioData);
1830
- this.currentMessageState[datatype].waitForAudioData = undefined;
1831
- }
1832
- }
1833
- endStream(datatype, force = false) {
1834
- var _a, _b;
1835
- if (this.currentMessageState[datatype].p2pStreaming) {
1836
- if (force) {
1837
- switch (datatype) {
1838
- case types_1.P2PDataType.VIDEO:
1839
- this.sendCommandWithInt({
1840
- commandType: types_1.CommandType.CMD_STOP_REALTIME_MEDIA,
1841
- value: this.currentMessageState[datatype].p2pStreamChannel,
1842
- channel: this.currentMessageState[datatype].p2pStreamChannel
1843
- }, {
1844
- command: {
1845
- name: http_1.CommandName.DeviceStopLivestream
1846
- }
1847
- });
1848
- break;
1849
- case types_1.P2PDataType.BINARY:
1850
- this.sendCommandWithInt({
1851
- commandType: types_1.CommandType.CMD_DOWNLOAD_CANCEL,
1852
- value: this.currentMessageState[datatype].p2pStreamChannel,
1853
- strValueSub: this.rawStation.member.admin_user_id,
1854
- channel: this.currentMessageState[datatype].p2pStreamChannel
1855
- }, {
1856
- command: {
1857
- name: http_1.CommandName.DeviceCancelDownload
1858
- }
1859
- });
1860
- break;
1861
- }
1862
- }
1863
- this.currentMessageState[datatype].p2pStreaming = false;
1864
- (_a = this.currentMessageState[datatype].videoStream) === null || _a === void 0 ? void 0 : _a.push(null);
1865
- (_b = this.currentMessageState[datatype].audioStream) === null || _b === void 0 ? void 0 : _b.push(null);
1866
- if (this.currentMessageState[datatype].p2pStreamingTimeout) {
1867
- clearTimeout(this.currentMessageState[datatype].p2pStreamingTimeout);
1868
- this.currentMessageState[datatype].p2pStreamingTimeout = undefined;
1869
- }
1870
- if (!this.currentMessageState[datatype].invalidStream && !this.currentMessageState[datatype].p2pStreamNotStarted)
1871
- this.emitStreamStopEvent(datatype);
1872
- if (this.currentMessageState[datatype].queuedData.size > 0) {
1873
- this.expectedSeqNo[datatype] = this._incrementSequence([...this.currentMessageState[datatype].queuedData.keys()][this.currentMessageState[datatype].queuedData.size - 1]);
1874
- }
1875
- this.initializeMessageBuilder(datatype);
1876
- this.initializeMessageState(datatype, this.currentMessageState[datatype].rsaKey);
1877
- this.initializeStream(datatype);
1878
- this.closeEnergySavingDevice();
1879
- }
1880
- }
1881
- endRTSPStream(channel) {
1882
- if (this.currentMessageState[types_1.P2PDataType.DATA].rtspStreaming[channel]) {
1883
- this.currentMessageState[types_1.P2PDataType.DATA].rtspStream[channel] = false;
1884
- this.currentMessageState[types_1.P2PDataType.DATA].rtspStreaming[channel] = false;
1885
- this.emit("rtsp livestream stopped", channel);
1886
- }
1887
- }
1888
- emitStreamStartEvent(datatype) {
1889
- this.currentMessageState[datatype].p2pStreamNotStarted = false;
1890
- if (datatype === types_1.P2PDataType.VIDEO) {
1891
- this.emit("livestream started", this.currentMessageState[datatype].p2pStreamChannel, this.currentMessageState[datatype].p2pStreamMetadata, this.currentMessageState[datatype].videoStream, this.currentMessageState[datatype].audioStream);
1892
- }
1893
- else if (datatype === types_1.P2PDataType.BINARY) {
1894
- this.emit("download started", this.currentMessageState[datatype].p2pStreamChannel, this.currentMessageState[datatype].p2pStreamMetadata, this.currentMessageState[datatype].videoStream, this.currentMessageState[datatype].audioStream);
1895
- }
1896
- }
1897
- emitStreamStopEvent(datatype) {
1898
- if (datatype === types_1.P2PDataType.VIDEO) {
1899
- this.emit("livestream stopped", this.currentMessageState[datatype].p2pStreamChannel);
1900
- }
1901
- else if (datatype === types_1.P2PDataType.BINARY) {
1902
- this.emit("download finished", this.currentMessageState[datatype].p2pStreamChannel);
1903
- }
1904
- }
1905
- isStreaming(channel, datatype) {
1906
- if (this.currentMessageState[datatype].p2pStreamChannel === channel)
1907
- return this.currentMessageState[datatype].p2pStreaming;
1908
- return false;
1909
- }
1910
- isLiveStreaming(channel) {
1911
- return this.isStreaming(channel, types_1.P2PDataType.VIDEO);
1912
- }
1913
- isCurrentlyStreaming() {
1914
- for (const element of Object.values(this.currentMessageState)) {
1915
- if (element.p2pStreaming || element.p2pTalkback)
1916
- return true;
1917
- }
1918
- return false;
1919
- }
1920
- isRTSPLiveStreaming(channel) {
1921
- return this.currentMessageState[types_1.P2PDataType.DATA].rtspStreaming[channel] ? this.currentMessageState[types_1.P2PDataType.DATA].rtspStreaming[channel] : false;
1922
- }
1923
- isDownloading(channel) {
1924
- return this.isStreaming(channel, types_1.P2PDataType.BINARY);
1925
- }
1926
- getLockSequenceNumber() {
1927
- if (this.lockSeqNumber === -1)
1928
- this.lockSeqNumber = (0, utils_1.generateLockSequence)(this.rawStation.devices[0].device_type);
1929
- return this.lockSeqNumber;
1930
- }
1931
- incLockSequenceNumber() {
1932
- if (this.lockSeqNumber === -1)
1933
- this.lockSeqNumber = (0, utils_1.generateLockSequence)(this.rawStation.devices[0].device_type);
1934
- else
1935
- this.lockSeqNumber++;
1936
- return this.lockSeqNumber;
1937
- }
1938
- setConnectionType(type) {
1939
- this.connectionType = type;
1940
- }
1941
- getConnectionType() {
1942
- return this.connectionType;
1943
- }
1944
- isEnergySavingDevice() {
1945
- return this.energySavingDevice;
1946
- }
1947
- async getDSKKeys() {
1948
- if (this.api.isConnected()) {
1949
- try {
1950
- const data = {
1951
- invalid_dsks: {},
1952
- station_sns: [this.rawStation.station_sn],
1953
- transaction: `${new Date().getTime()}`
1954
- };
1955
- data.invalid_dsks[this.rawStation.station_sn] = "";
1956
- const response = await this.api.request({
1957
- method: "post",
1958
- endpoint: "v1/app/equipment/get_dsk_keys",
1959
- data: data
1960
- });
1961
- this.log.debug(`Station ${this.rawStation.station_sn} - Response:`, response.data);
1962
- if (response.status == 200) {
1963
- const result = response.data;
1964
- if (result.code == 0) {
1965
- const dataresult = result.data;
1966
- dataresult.dsk_keys.forEach(key => {
1967
- if (key.station_sn == this.rawStation.station_sn) {
1968
- this.dskKey = key.dsk_key;
1969
- this.dskExpiration = new Date(key.expiration * 1000);
1970
- this.log.debug(`${this.constructor.name}.getDSKKeys(): dskKey: ${this.dskKey} dskExpiration: ${this.dskExpiration}`);
1971
- }
1972
- });
1973
- }
1974
- else {
1975
- this.log.error(`Station ${this.rawStation.station_sn} - Response code not ok`, { code: result.code, msg: result.msg });
1976
- }
1977
- }
1978
- else {
1979
- this.log.error(`Station ${this.rawStation.station_sn} - Status return code not 200`, { status: response.status, statusText: response.statusText });
1980
- }
1981
- }
1982
- catch (error) {
1983
- this.log.error(`Station ${this.rawStation.station_sn} - Generic Error:`, error);
1984
- }
1985
- }
1986
- }
1987
- updateRawStation(value) {
1988
- var _a;
1989
- this.rawStation = value;
1990
- this.channel = http_1.Station.getChannel(value.device_type);
1991
- if (((_a = this.rawStation.devices) === null || _a === void 0 ? void 0 : _a.length) > 0) {
1992
- if (!this.energySavingDevice) {
1993
- for (const device of this.rawStation.devices) {
1994
- if (device.device_sn === this.rawStation.station_sn && device_1.Device.hasBattery(device.device_type)) {
1995
- this.energySavingDevice = true;
1996
- break;
1997
- }
1998
- }
1999
- if (this.energySavingDevice)
2000
- this.log.debug(`Identified standalone battery device ${this.rawStation.station_sn} => activate p2p keepalive command`);
2001
- }
2002
- }
2003
- else {
2004
- this.energySavingDevice = false;
2005
- }
2006
- if (this.rawStation.devices)
2007
- for (const device of this.rawStation.devices) {
2008
- this.deviceSNs[device.device_channel] = {
2009
- sn: device.device_sn,
2010
- adminUserId: this.rawStation.member.admin_user_id
2011
- };
2012
- }
2013
- }
2014
- initializeTalkbackStream(channel = 0) {
2015
- this.talkbackStream = new talkback_1.TalkbackStream();
2016
- this.talkbackStream.on("data", (audioData) => { this.sendTalkbackAudioFrame(audioData, channel); });
2017
- this.talkbackStream.on("error", (error) => { this.onTalkbackStreamError(error); });
2018
- this.talkbackStream.on("close", () => { this.onTalkbackStreamClose(); });
2019
- }
2020
- sendTalkbackAudioFrame(audioData, channel) {
2021
- const messageHeader = (0, utils_1.buildCommandHeader)(this.videoSeqNumber, types_1.CommandType.CMD_AUDIO_FRAME, types_1.P2PDataTypeHeader.VIDEO);
2022
- const messageAudioHeader = (0, utils_1.buildTalkbackAudioFrameHeader)(audioData, channel);
2023
- const messageData = Buffer.concat([messageHeader, messageAudioHeader, audioData]);
2024
- const message = {
2025
- sequence: this.videoSeqNumber,
2026
- channel: channel,
2027
- data: messageData,
2028
- retries: 0
2029
- };
2030
- this.videoSeqNumber = this._incrementSequence(this.videoSeqNumber);
2031
- this._sendVideoData(message);
2032
- }
2033
- onTalkbackStreamClose() {
2034
- var _a;
2035
- (_a = this.talkbackStream) === null || _a === void 0 ? void 0 : _a.removeAllListeners();
2036
- }
2037
- onTalkbackStreamError(error) {
2038
- this.log.debug(`Station ${this.rawStation.station_sn} - Talkback Error:`, error);
2039
- }
2040
- async _sendVideoData(message) {
2041
- var _a, _b;
2042
- if (message.retries < this.MAX_RETRIES) {
2043
- message.retries++;
2044
- }
2045
- else {
2046
- this.log.error(`Station ${this.rawStation.station_sn} - Max send video data retries ${(_a = this.messageVideoStates.get(message.sequence)) === null || _a === void 0 ? void 0 : _a.retries} reached. Discard data.`, { sequence: message.sequence, channel: message.channel, retries: message.retries });
2047
- this.messageVideoStates.delete(message.sequence);
2048
- this.emit("talkback error", message.channel, new error_1.TalkbackError(`Station ${this.rawStation.station_sn} max send video data retries ${(_b = this.messageVideoStates.get(message.sequence)) === null || _b === void 0 ? void 0 : _b.retries} reached. Discard data packet.`));
2049
- return;
2050
- }
2051
- message = message;
2052
- message.timeout = setTimeout(() => {
2053
- this._sendVideoData(message);
2054
- }, this.MAX_AKNOWLEDGE_TIMEOUT);
2055
- this.messageVideoStates.set(message.sequence, message);
2056
- this.log.debug("Sending p2p video data...", { station: this.rawStation.station_sn, sequence: message.sequence, channel: message.channel, retries: message.retries, messageVideoStatesSize: this.messageVideoStates.size });
2057
- await this.sendMessage(`Send p2p video data to station ${this.rawStation.station_sn}`, this.connectAddress, types_1.RequestMessageType.DATA, message.data);
2058
- }
2059
- isTalkbackOngoing(channel) {
2060
- if (this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkbackChannel === channel)
2061
- return this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkback;
2062
- return false;
2063
- }
2064
- startTalkback(channel = 0) {
2065
- var _a;
2066
- this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkback = true;
2067
- this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkbackChannel = channel;
2068
- this.initializeTalkbackStream(channel);
2069
- (_a = this.talkbackStream) === null || _a === void 0 ? void 0 : _a.startTalkback();
2070
- this.emit("talkback started", channel, this.talkbackStream);
2071
- }
2072
- stopTalkback(channel = 0) {
2073
- var _a;
2074
- this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkback = false;
2075
- this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkbackChannel = -1;
2076
- (_a = this.talkbackStream) === null || _a === void 0 ? void 0 : _a.stopTalkback();
2077
- this.emit("talkback stopped", channel);
2078
- this.closeEnergySavingDevice();
2079
- }
2080
- setLockAESKey(commandCode, aesKey) {
2081
- this.lockAESKeys.set(commandCode, aesKey);
2082
- }
2083
- getLockAESKey(commandCode) {
2084
- return this.lockAESKeys.get(commandCode);
2085
- }
2086
- }
2087
- exports.P2PClientProtocol = P2PClientProtocol;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.P2PClientProtocol = void 0;
4
+ const dgram_1 = require("dgram");
5
+ const tiny_typed_emitter_1 = require("tiny-typed-emitter");
6
+ const stream_1 = require("stream");
7
+ const sweet_collections_1 = require("sweet-collections");
8
+ const utils_1 = require("./utils");
9
+ const types_1 = require("./types");
10
+ const types_2 = require("../http/types");
11
+ const device_1 = require("../http/device");
12
+ const utils_2 = require("../http/utils");
13
+ const talkback_1 = require("./talkback");
14
+ const error_1 = require("../error");
15
+ const types_3 = require("../push/types");
16
+ const ble_1 = require("./ble");
17
+ const http_1 = require("../http");
18
+ const utils_3 = require("../utils");
19
+ class P2PClientProtocol extends tiny_typed_emitter_1.TypedEmitter {
20
+ constructor(rawStation, api, ipAddress, publicKey = "") {
21
+ super();
22
+ this.MAX_RETRIES = 10;
23
+ this.MAX_COMMAND_RESULT_WAIT = 30 * 1000;
24
+ this.MAX_AKNOWLEDGE_TIMEOUT = 15 * 1000;
25
+ this.MAX_LOOKUP_TIMEOUT = 15 * 1000;
26
+ this.LOOKUP_RETRY_TIMEOUT = 3 * 1000;
27
+ this.MAX_EXPECTED_SEQNO_WAIT = 20 * 1000;
28
+ this.HEARTBEAT_INTERVAL = 5 * 1000;
29
+ this.MAX_COMMAND_QUEUE_TIMEOUT = 120 * 1000;
30
+ this.AUDIO_CODEC_ANALYZE_TIMEOUT = 650;
31
+ this.KEEPALIVE_INTERVAL = 5 * 1000;
32
+ this.ESD_DISCONNECT_TIMEOUT = 30 * 1000;
33
+ this.MAX_STREAM_DATA_WAIT = 5 * 1000;
34
+ this.RESEND_NOT_ACKNOWLEDGED_COMMAND = 100;
35
+ this.UDP_RECVBUFFERSIZE_BYTES = 1048576;
36
+ this.MAX_PAYLOAD_BYTES = 1028;
37
+ this.MAX_PACKET_BYTES = 1024;
38
+ this.MAX_VIDEO_PACKET_BYTES = 655360;
39
+ this.P2P_DATA_HEADER_BYTES = 16;
40
+ this.MAX_SEQUENCE_NUMBER = 65535;
41
+ /*
42
+ * SEQUENCE_PROCESSING_BOUNDARY is used to determine if an incoming sequence number
43
+ * that is lower than the expected one was already processed.
44
+ * If it is within the boundary, it is determined as 'already processed',
45
+ * If it is even lower, it is assumed that the sequence count has reached
46
+ * MAX_SEQUENCE_NUMBER and restarted at 0.
47
+ * */
48
+ this.SEQUENCE_PROCESSING_BOUNDARY = 20000; // worth of approx. 90 seconds of continous streaming
49
+ this.binded = false;
50
+ this.connected = false;
51
+ this.connecting = false;
52
+ this.terminating = false;
53
+ this.seqNumber = 0;
54
+ this.offsetDataSeqNumber = 0;
55
+ this.videoSeqNumber = 0;
56
+ this.lockSeqNumber = -1;
57
+ this.expectedSeqNo = {};
58
+ this.currentMessageBuilder = {};
59
+ this.currentMessageState = {};
60
+ this.downloadTotalBytes = 0;
61
+ this.downloadReceivedBytes = 0;
62
+ this.messageStates = new sweet_collections_1.SortedMap((a, b) => a - b);
63
+ this.messageVideoStates = new sweet_collections_1.SortedMap((a, b) => a - b);
64
+ this.sendQueue = new Array();
65
+ this.connectTime = null;
66
+ this.lastPong = null;
67
+ this.lastPongData = undefined;
68
+ this.connectionType = types_1.P2PConnectionType.QUICKEST;
69
+ this.energySavingDevice = false;
70
+ this.energySavingDeviceP2PSeqMapping = new Map();
71
+ this.energySavingDeviceP2PDataSeqNumber = 0;
72
+ this.connectAddress = undefined;
73
+ this.localIPAddress = undefined;
74
+ this.preferredIPAddress = undefined;
75
+ this.dskKey = "";
76
+ this.dskExpiration = null;
77
+ this.deviceSNs = {};
78
+ this.lockAESKeys = new Map();
79
+ this.channel = 255;
80
+ this.api = api;
81
+ this.lockPublicKey = publicKey;
82
+ this.preferredIPAddress = ipAddress;
83
+ this.log = api.getLog();
84
+ this.cloudAddresses = (0, utils_1.decodeP2PCloudIPs)(rawStation.app_conn);
85
+ this.log.debug("Loaded P2P cloud ip addresses", this.cloudAddresses);
86
+ this.updateRawStation(rawStation);
87
+ this.socket = (0, dgram_1.createSocket)("udp4");
88
+ this.socket.on("message", (msg, rinfo) => this.handleMsg(msg, rinfo));
89
+ this.socket.on("error", (error) => this.onError(error));
90
+ this.socket.on("close", () => this.onClose());
91
+ this._initialize();
92
+ }
93
+ _incrementSequence(sequence) {
94
+ if (sequence < this.MAX_SEQUENCE_NUMBER)
95
+ return sequence + 1;
96
+ return 0;
97
+ }
98
+ _isBetween(n, lowBoundary, highBoundary) {
99
+ if (n < lowBoundary)
100
+ return false;
101
+ if (n >= highBoundary)
102
+ return false;
103
+ return true;
104
+ }
105
+ _wasSequenceNumberAlreadyProcessed(expectedSequence, receivedSequence) {
106
+ if ((expectedSequence - this.SEQUENCE_PROCESSING_BOUNDARY) > 0) { // complete boundary without squence number reset
107
+ return this._isBetween(receivedSequence, expectedSequence - this.SEQUENCE_PROCESSING_BOUNDARY, expectedSequence);
108
+ }
109
+ else { // there was a sequence number reset recently
110
+ const isInRangeAfterReset = this._isBetween(receivedSequence, 0, expectedSequence);
111
+ const isInRangeBeforeReset = this._isBetween(receivedSequence, this.MAX_SEQUENCE_NUMBER + (expectedSequence - this.SEQUENCE_PROCESSING_BOUNDARY), this.MAX_SEQUENCE_NUMBER);
112
+ return (isInRangeBeforeReset || isInRangeAfterReset);
113
+ }
114
+ }
115
+ _initialize() {
116
+ let rsaKey;
117
+ this.connected = false;
118
+ this.connecting = false;
119
+ this.lastPong = null;
120
+ this.lastPongData = undefined;
121
+ this.connectTime = null;
122
+ this.seqNumber = 0;
123
+ this.offsetDataSeqNumber = 0;
124
+ this.videoSeqNumber = 0;
125
+ this.energySavingDeviceP2PDataSeqNumber = 0;
126
+ this.lockSeqNumber = -1;
127
+ this.connectAddress = undefined;
128
+ this.lastChannel = undefined;
129
+ this.lastCustomData = undefined;
130
+ this.lockAESKeys.clear();
131
+ this._clearMessageStateTimeouts();
132
+ this._clearMessageVideoStateTimeouts();
133
+ this.messageStates.clear();
134
+ this.messageVideoStates.clear();
135
+ this.energySavingDeviceP2PSeqMapping.clear();
136
+ for (let datatype = 0; datatype < 4; datatype++) {
137
+ this.expectedSeqNo[datatype] = 0;
138
+ if (datatype === types_1.P2PDataType.VIDEO)
139
+ rsaKey = (0, utils_1.getNewRSAPrivateKey)();
140
+ else
141
+ rsaKey = null;
142
+ this.initializeMessageBuilder(datatype);
143
+ this.initializeMessageState(datatype, rsaKey);
144
+ this.initializeStream(datatype);
145
+ }
146
+ }
147
+ initializeMessageBuilder(datatype) {
148
+ this.currentMessageBuilder[datatype] = {
149
+ header: {
150
+ commandId: 0,
151
+ bytesToRead: 0,
152
+ channel: 0,
153
+ signCode: 0,
154
+ type: 0
155
+ },
156
+ bytesRead: 0,
157
+ messages: {}
158
+ };
159
+ }
160
+ initializeMessageState(datatype, rsaKey = null) {
161
+ this.currentMessageState[datatype] = {
162
+ leftoverData: Buffer.from([]),
163
+ queuedData: new sweet_collections_1.SortedMap((a, b) => a - b),
164
+ rsaKey: rsaKey,
165
+ videoStream: null,
166
+ audioStream: null,
167
+ invalidStream: false,
168
+ p2pStreaming: false,
169
+ p2pStreamNotStarted: true,
170
+ p2pStreamChannel: -1,
171
+ p2pStreamFirstAudioDataReceived: false,
172
+ p2pStreamFirstVideoDataReceived: false,
173
+ p2pStreamMetadata: {
174
+ videoCodec: types_1.VideoCodec.H264,
175
+ videoFPS: 15,
176
+ videoHeight: 1080,
177
+ videoWidth: 1920,
178
+ audioCodec: types_1.AudioCodec.NONE
179
+ },
180
+ rtspStream: {},
181
+ rtspStreaming: {},
182
+ receivedFirstIFrame: false,
183
+ preFrameVideoData: Buffer.from([]),
184
+ p2pTalkback: false,
185
+ p2pTalkbackChannel: -1
186
+ };
187
+ }
188
+ _clearTimeout(timeout) {
189
+ if (!!timeout) {
190
+ clearTimeout(timeout);
191
+ }
192
+ }
193
+ _clearMessageStateTimeouts() {
194
+ for (const message of this.messageStates.values()) {
195
+ this._clearTimeout(message.timeout);
196
+ }
197
+ }
198
+ _clearMessageVideoStateTimeouts() {
199
+ for (const message of this.messageVideoStates.values()) {
200
+ this._clearTimeout(message.timeout);
201
+ }
202
+ }
203
+ _clearHeartbeatTimeout() {
204
+ this._clearTimeout(this.heartbeatTimeout);
205
+ this.heartbeatTimeout = undefined;
206
+ }
207
+ _clearKeepaliveTimeout() {
208
+ this._clearTimeout(this.keepaliveTimeout);
209
+ this.keepaliveTimeout = undefined;
210
+ }
211
+ _clearConnectTimeout() {
212
+ this._clearTimeout(this.connectTimeout);
213
+ this.connectTimeout = undefined;
214
+ }
215
+ _clearLookupTimeout() {
216
+ this._clearTimeout(this.lookupTimeout);
217
+ this.lookupTimeout = undefined;
218
+ }
219
+ _clearLookupRetryTimeout() {
220
+ this._clearTimeout(this.lookupRetryTimeout);
221
+ this.lookupRetryTimeout = undefined;
222
+ }
223
+ _clearESDDisconnectTimeout() {
224
+ this._clearTimeout(this.esdDisconnectTimeout);
225
+ this.esdDisconnectTimeout = undefined;
226
+ }
227
+ _clearSecondaryCommandTimeout() {
228
+ this._clearTimeout(this.secondaryCommandTimeout);
229
+ this.secondaryCommandTimeout = undefined;
230
+ }
231
+ async sendMessage(errorSubject, address, msgID, payload) {
232
+ await (0, utils_1.sendMessage)(this.socket, address, msgID, payload).catch((error) => {
233
+ this.log.error(`${errorSubject} - msgID: ${msgID.toString("hex")} payload: ${payload === null || payload === void 0 ? void 0 : payload.toString("hex")} - Error:`, error);
234
+ });
235
+ }
236
+ _disconnected() {
237
+ this._clearHeartbeatTimeout();
238
+ this._clearKeepaliveTimeout();
239
+ this._clearLookupRetryTimeout();
240
+ this._clearLookupTimeout();
241
+ this._clearConnectTimeout();
242
+ this._clearESDDisconnectTimeout();
243
+ this._clearSecondaryCommandTimeout();
244
+ this._clearMessageStateTimeouts();
245
+ this._clearMessageVideoStateTimeouts();
246
+ if (this.currentMessageState[types_1.P2PDataType.VIDEO].p2pStreaming) {
247
+ this.endStream(types_1.P2PDataType.VIDEO);
248
+ }
249
+ if (this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreaming) {
250
+ this.endStream(types_1.P2PDataType.BINARY);
251
+ }
252
+ for (const channel in this.currentMessageState[types_1.P2PDataType.DATA].rtspStreaming) {
253
+ this.endRTSPStream(Number.parseInt(channel));
254
+ }
255
+ this.sendQueue = this.sendQueue.filter((queue) => queue.commandType !== types_1.CommandType.CMD_PING && queue.commandType !== types_1.CommandType.CMD_GET_DEVICE_PING);
256
+ if (this.connected) {
257
+ this.emit("close");
258
+ }
259
+ else if (!this.terminating) {
260
+ this.emit("timeout");
261
+ }
262
+ this._initialize();
263
+ }
264
+ closeEnergySavingDevice() {
265
+ if (this.sendQueue.filter((queue) => queue.commandType !== types_1.CommandType.CMD_PING && queue.commandType !== types_1.CommandType.CMD_GET_DEVICE_PING).length === 0 &&
266
+ this.energySavingDevice &&
267
+ !this.isCurrentlyStreaming() &&
268
+ Array.from(this.messageStates.values()).filter((msgState) => msgState.acknowledged === false).length === 0) {
269
+ if (this.esdDisconnectTimeout === undefined) {
270
+ this.log.debug(`Station ${this.rawStation.station_sn} - Energy saving device - No more p2p commands to execute or running streams, initiate disconnect timeout in ${this.ESD_DISCONNECT_TIMEOUT} milliseconds...`);
271
+ this.esdDisconnectTimeout = setTimeout(() => {
272
+ this.esdDisconnectTimeout = undefined;
273
+ this.sendMessage(`Station ${this.rawStation.station_sn}`, this.connectAddress, types_1.RequestMessageType.END);
274
+ this.log.info(`Initiated closing of connection to station ${this.rawStation.station_sn} for saving battery.`);
275
+ this.terminating = true;
276
+ this._disconnected();
277
+ }, this.ESD_DISCONNECT_TIMEOUT);
278
+ }
279
+ }
280
+ }
281
+ async renewDSKKey() {
282
+ if (this.dskKey === "" || (this.dskExpiration && (new Date()).getTime() >= this.dskExpiration.getTime())) {
283
+ this.log.debug(`Station ${this.rawStation.station_sn} DSK keys not present or expired, get/renew it`, { dskKey: this.dskKey, dskExpiration: this.dskExpiration });
284
+ await this.getDSKKeys();
285
+ }
286
+ }
287
+ localLookup(host) {
288
+ this.log.debug(`Trying to local lookup address for station ${this.rawStation.station_sn} with host ${host}`);
289
+ this.localLookupByAddress({ host: host, port: 32108 });
290
+ }
291
+ cloudLookup() {
292
+ this.cloudAddresses.map((address) => this.cloudLookupByAddress(address));
293
+ this.cloudAddresses.map((address) => this.cloudLookupByAddress2(address));
294
+ }
295
+ cloudLookup2(origAddress, data) {
296
+ this.cloudAddresses.map((address) => this.cloudLookupByAddress3(address, origAddress, data));
297
+ }
298
+ async localLookupByAddress(address) {
299
+ // Send lookup message
300
+ const msgId = types_1.RequestMessageType.LOCAL_LOOKUP;
301
+ const payload = Buffer.from([0, 0]);
302
+ await this.sendMessage(`Local lookup address for station ${this.rawStation.station_sn}`, address, msgId, payload);
303
+ }
304
+ async cloudLookupByAddress(address) {
305
+ // Send lookup message
306
+ const msgId = types_1.RequestMessageType.LOOKUP_WITH_KEY;
307
+ const payload = (0, utils_1.buildLookupWithKeyPayload)(this.socket, this.rawStation.p2p_did, this.dskKey);
308
+ await this.sendMessage(`Cloud lookup addresses for station ${this.rawStation.station_sn}`, address, msgId, payload);
309
+ }
310
+ async cloudLookupByAddress2(address) {
311
+ // Send lookup message2
312
+ const msgId = types_1.RequestMessageType.LOOKUP_WITH_KEY2;
313
+ const payload = (0, utils_1.buildLookupWithKeyPayload2)(this.rawStation.p2p_did, this.dskKey);
314
+ await this.sendMessage(`Cloud lookup addresses (2) for station ${this.rawStation.station_sn}`, address, msgId, payload);
315
+ }
316
+ async cloudLookupByAddress3(address, origAddress, data) {
317
+ // Send lookup message3
318
+ const msgId = types_1.RequestMessageType.LOOKUP_WITH_KEY3;
319
+ const payload = (0, utils_1.buildLookupWithKeyPayload3)(this.rawStation.p2p_did, origAddress, data);
320
+ await this.sendMessage(`Cloud lookup addresses (3) for station ${this.rawStation.station_sn}`, address, msgId, payload);
321
+ }
322
+ isConnected() {
323
+ return this.connected;
324
+ }
325
+ _startConnectTimeout() {
326
+ if (this.connectTimeout === undefined)
327
+ this.connectTimeout = setTimeout(() => {
328
+ this.log.warn(`Station ${this.rawStation.station_sn} - Tried all hosts, no connection could be established`);
329
+ this._disconnected();
330
+ }, this.MAX_AKNOWLEDGE_TIMEOUT);
331
+ }
332
+ _connect(address, p2p_did) {
333
+ this.log.debug(`Station ${this.rawStation.station_sn} - CHECK_CAM - Connecting to host ${address.host} on port ${address.port}...`);
334
+ for (let i = 0; i < 4; i++)
335
+ this.sendCamCheck(address, p2p_did);
336
+ this._startConnectTimeout();
337
+ }
338
+ lookup(host) {
339
+ if (host === undefined) {
340
+ if (this.preferredIPAddress !== undefined) {
341
+ host = this.preferredIPAddress;
342
+ }
343
+ else if (this.localIPAddress !== undefined) {
344
+ host = this.localIPAddress;
345
+ }
346
+ else {
347
+ const localIP = (0, utils_1.getLocalIpAddress)();
348
+ host = localIP.substring(0, localIP.lastIndexOf(".") + 1).concat("255");
349
+ }
350
+ }
351
+ this.localLookup(host);
352
+ this.cloudLookup();
353
+ this._clearLookupTimeout();
354
+ this._clearLookupRetryTimeout();
355
+ this.lookupTimeout = setTimeout(() => {
356
+ this.lookupTimeout = undefined;
357
+ this.log.error(`Station ${this.rawStation.station_sn} - All address lookup tentatives failed.`);
358
+ if (this.localIPAddress !== undefined)
359
+ this.localIPAddress = undefined;
360
+ this._disconnected();
361
+ }, this.MAX_LOOKUP_TIMEOUT);
362
+ }
363
+ async connect(host) {
364
+ if (!this.connected && !this.connecting && this.rawStation.p2p_did !== undefined) {
365
+ this.connecting = true;
366
+ this.terminating = false;
367
+ await this.renewDSKKey();
368
+ if (!this.binded)
369
+ this.socket.bind(0, () => {
370
+ this.binded = true;
371
+ try {
372
+ this.socket.setRecvBufferSize(this.UDP_RECVBUFFERSIZE_BYTES);
373
+ this.socket.setBroadcast(true);
374
+ }
375
+ catch (error) {
376
+ this.log.error(`Station ${this.rawStation.station_sn} - Error:`, { error: error, currentRecBufferSize: this.socket.getRecvBufferSize(), recBufferRequestedSize: this.UDP_RECVBUFFERSIZE_BYTES });
377
+ }
378
+ this.lookup(host);
379
+ });
380
+ else {
381
+ this.lookup(host);
382
+ }
383
+ }
384
+ }
385
+ async sendCamCheck(address, p2p_did) {
386
+ const payload = (0, utils_1.buildCheckCamPayload)(p2p_did);
387
+ await this.sendMessage(`Send cam check to station ${this.rawStation.station_sn}`, address, types_1.RequestMessageType.CHECK_CAM, payload);
388
+ }
389
+ async sendCamCheck2(address, data) {
390
+ const payload = (0, utils_1.buildCheckCamPayload2)(this.rawStation.p2p_did, data);
391
+ await this.sendMessage(`Send cam check (2) to station ${this.rawStation.station_sn}`, address, types_1.RequestMessageType.CHECK_CAM2, payload);
392
+ }
393
+ async sendPing(address) {
394
+ if ((this.lastPong && ((new Date().getTime() - this.lastPong) / this.getHeartbeatInterval() >= this.MAX_RETRIES)) ||
395
+ (this.connectTime && !this.lastPong && ((new Date().getTime() - this.connectTime) / this.getHeartbeatInterval() >= this.MAX_RETRIES))) {
396
+ if (!this.energySavingDevice)
397
+ this.log.warn(`Station ${this.rawStation.station_sn} - Heartbeat check failed. Connection seems lost. Try to reconnect...`);
398
+ this._disconnected();
399
+ }
400
+ await this.sendMessage(`Send ping to station ${this.rawStation.station_sn}`, address, types_1.RequestMessageType.PING, this.lastPongData);
401
+ }
402
+ sendCommandWithIntString(p2pcommand, customData) {
403
+ if (p2pcommand.channel === undefined)
404
+ p2pcommand.channel = 0;
405
+ if (p2pcommand.value === undefined || typeof p2pcommand.value !== "number")
406
+ throw new TypeError("value must be a number");
407
+ const payload = (0, utils_1.buildIntStringCommandPayload)(p2pcommand.value, p2pcommand.valueSub === undefined ? 0 : p2pcommand.valueSub, p2pcommand.strValue === undefined ? "" : p2pcommand.strValue, p2pcommand.strValueSub === undefined ? "" : p2pcommand.strValueSub, p2pcommand.channel);
408
+ if (p2pcommand.commandType === types_1.CommandType.CMD_NAS_TEST) {
409
+ this.currentMessageState[types_1.P2PDataType.DATA].rtspStream[p2pcommand.channel] = p2pcommand.value === 1 ? true : false;
410
+ }
411
+ this.sendCommand(p2pcommand.commandType, payload, p2pcommand.channel, undefined, customData);
412
+ }
413
+ sendCommandWithInt(p2pcommand, customData) {
414
+ if (p2pcommand.channel === undefined)
415
+ p2pcommand.channel = this.channel;
416
+ if (p2pcommand.value === undefined || typeof p2pcommand.value !== "number")
417
+ throw new TypeError("value must be a number");
418
+ const payload = (0, utils_1.buildIntCommandPayload)(p2pcommand.value, p2pcommand.strValue === undefined ? "" : p2pcommand.strValue, p2pcommand.channel);
419
+ this.sendCommand(p2pcommand.commandType, payload, p2pcommand.channel, undefined, customData);
420
+ }
421
+ sendCommandWithStringPayload(p2pcommand, customData) {
422
+ if (p2pcommand.channel === undefined)
423
+ p2pcommand.channel = 0;
424
+ if (p2pcommand.value === undefined || typeof p2pcommand.value !== "string")
425
+ throw new TypeError("value must be a string");
426
+ const payload = (0, utils_1.buildCommandWithStringTypePayload)(p2pcommand.value, p2pcommand.channel);
427
+ let nested_commandType = undefined;
428
+ if (p2pcommand.commandType == types_1.CommandType.CMD_SET_PAYLOAD) {
429
+ try {
430
+ const json = JSON.parse(p2pcommand.value);
431
+ nested_commandType = json.cmd;
432
+ }
433
+ catch (error) {
434
+ this.log.error(`CMD_SET_PAYLOAD - Station ${this.rawStation.station_sn} - Error:`, error);
435
+ }
436
+ }
437
+ else if (p2pcommand.commandType == types_1.CommandType.CMD_DOORBELL_SET_PAYLOAD) {
438
+ try {
439
+ const json = JSON.parse(p2pcommand.value);
440
+ nested_commandType = json.commandType;
441
+ }
442
+ catch (error) {
443
+ this.log.error(`CMD_DOORBELL_SET_PAYLOAD - Station ${this.rawStation.station_sn} - Error:`, error);
444
+ }
445
+ }
446
+ this.sendCommand(p2pcommand.commandType, payload, p2pcommand.channel, nested_commandType, customData);
447
+ }
448
+ sendCommandWithString(p2pcommand, customData) {
449
+ if (p2pcommand.channel === undefined)
450
+ p2pcommand.channel = this.channel;
451
+ if (p2pcommand.strValue === undefined)
452
+ throw new TypeError("strValue must be defined");
453
+ if (p2pcommand.strValueSub === undefined)
454
+ throw new TypeError("strValueSub must be defined");
455
+ const payload = (0, utils_1.buildStringTypeCommandPayload)(p2pcommand.strValue, p2pcommand.strValueSub, p2pcommand.channel);
456
+ this.sendCommand(p2pcommand.commandType, payload, p2pcommand.channel, p2pcommand.commandType, customData);
457
+ }
458
+ sendCommandPing(channel = this.channel) {
459
+ const payload = (0, utils_1.buildVoidCommandPayload)(channel);
460
+ this.sendCommand(types_1.CommandType.CMD_PING, payload, channel);
461
+ }
462
+ sendCommandDevicePing(channel = this.channel) {
463
+ const payload = (0, utils_1.buildVoidCommandPayload)(channel);
464
+ this.sendCommand(types_1.CommandType.CMD_GET_DEVICE_PING, payload, channel);
465
+ }
466
+ sendCommandWithoutData(commandType, channel = this.channel) {
467
+ const payload = (0, utils_1.buildVoidCommandPayload)(channel);
468
+ this.sendCommand(commandType, payload, channel);
469
+ }
470
+ sendQueuedMessage() {
471
+ if (this.sendQueue.length > 0) {
472
+ if (this.connected) {
473
+ let queuedMessage;
474
+ while ((queuedMessage = this.sendQueue.shift()) !== undefined) {
475
+ let exists = false;
476
+ let waitingAcknowledge = false;
477
+ this.messageStates.forEach(stateMessage => {
478
+ if (stateMessage.commandType === queuedMessage.commandType && stateMessage.nestedCommandType === queuedMessage.nestedCommandType && !stateMessage.acknowledged) {
479
+ exists = true;
480
+ }
481
+ if (!stateMessage.acknowledged) {
482
+ waitingAcknowledge = true;
483
+ }
484
+ });
485
+ if (!exists && !waitingAcknowledge) {
486
+ this._sendCommand(queuedMessage);
487
+ break;
488
+ }
489
+ else {
490
+ this.sendQueue.unshift(queuedMessage);
491
+ break;
492
+ }
493
+ }
494
+ }
495
+ else if (!this.connected) {
496
+ this.connect();
497
+ }
498
+ }
499
+ this.closeEnergySavingDevice();
500
+ }
501
+ sendCommand(commandType, payload, channel, nestedCommandType, customData) {
502
+ const message = {
503
+ commandType: commandType,
504
+ nestedCommandType: nestedCommandType,
505
+ channel: channel,
506
+ payload: payload,
507
+ timestamp: +new Date,
508
+ customData: customData
509
+ };
510
+ this.sendQueue.push(message);
511
+ if (message.commandType !== types_1.CommandType.CMD_PING && message.commandType !== types_1.CommandType.CMD_GET_DEVICE_PING)
512
+ this._clearESDDisconnectTimeout();
513
+ this.sendQueuedMessage();
514
+ }
515
+ resendNotAcknowledgedCommand(sequence) {
516
+ const messageState = this.messageStates.get(sequence);
517
+ if (messageState) {
518
+ messageState.retryTimeout = setTimeout(() => {
519
+ if (this.connectAddress) {
520
+ (0, utils_1.sendMessage)(this.socket, this.connectAddress, types_1.RequestMessageType.DATA, messageState.data).catch((error) => {
521
+ this.log.error(`Station ${this.rawStation.station_sn} - Error:`, error);
522
+ });
523
+ this.resendNotAcknowledgedCommand(sequence);
524
+ }
525
+ }, this.RESEND_NOT_ACKNOWLEDGED_COMMAND);
526
+ }
527
+ }
528
+ async _sendCommand(message) {
529
+ var _a;
530
+ if ((0, utils_1.isP2PQueueMessage)(message)) {
531
+ const ageing = +new Date - message.timestamp;
532
+ if (ageing <= this.MAX_COMMAND_QUEUE_TIMEOUT) {
533
+ const commandHeader = (0, utils_1.buildCommandHeader)(this.seqNumber, message.commandType);
534
+ const data = Buffer.concat([commandHeader, message.payload]);
535
+ const messageState = {
536
+ sequence: this.seqNumber,
537
+ commandType: message.commandType,
538
+ nestedCommandType: message.nestedCommandType,
539
+ channel: message.channel,
540
+ data: data,
541
+ retries: 0,
542
+ acknowledged: false,
543
+ returnCode: types_1.ErrorCode.ERROR_COMMAND_TIMEOUT,
544
+ customData: message.customData
545
+ };
546
+ message = messageState;
547
+ this.seqNumber = this._incrementSequence(this.seqNumber);
548
+ }
549
+ else if (message.commandType === types_1.CommandType.CMD_PING || message.commandType === types_1.CommandType.CMD_GET_DEVICE_PING) {
550
+ return;
551
+ }
552
+ else {
553
+ this.log.warn(`Station ${this.rawStation.station_sn} - Command aged out from queue`, { commandType: message.commandType, nestedCommandType: message.nestedCommandType, channel: message.channel, ageing: ageing, maxAgeing: this.MAX_COMMAND_QUEUE_TIMEOUT });
554
+ this.emit("command", {
555
+ command_type: message.nestedCommandType !== undefined ? message.nestedCommandType : message.commandType,
556
+ channel: message.channel,
557
+ return_code: types_1.ErrorCode.ERROR_CONNECT_TIMEOUT,
558
+ customData: message.customData
559
+ });
560
+ return;
561
+ }
562
+ }
563
+ else {
564
+ if (message.retries < this.MAX_RETRIES && message.returnCode !== types_1.ErrorCode.ERROR_CONNECT_TIMEOUT) {
565
+ if (message.returnCode === types_1.ErrorCode.ERROR_FAILED_TO_REQUEST) {
566
+ this.messageStates.delete(message.sequence);
567
+ message.sequence = this.seqNumber;
568
+ message.data.writeUInt16BE(message.sequence, 2);
569
+ this.seqNumber = this._incrementSequence(this.seqNumber);
570
+ this.messageStates.set(message.sequence, message);
571
+ }
572
+ message.retries++;
573
+ }
574
+ else {
575
+ this.log.error(`Station ${this.rawStation.station_sn} - Max retries ${(_a = this.messageStates.get(message.sequence)) === null || _a === void 0 ? void 0 : _a.retries} - stop with error ${types_1.ErrorCode[message.returnCode]}`, { sequence: message.sequence, commandType: message.commandType, channel: message.channel, retries: message.retries, returnCode: message.returnCode });
576
+ this.emit("command", {
577
+ command_type: message.nestedCommandType !== undefined ? message.nestedCommandType : message.commandType,
578
+ channel: message.channel,
579
+ return_code: message.returnCode,
580
+ customData: message.customData
581
+ });
582
+ this.messageStates.delete(message.sequence);
583
+ this.sendQueuedMessage();
584
+ return;
585
+ }
586
+ }
587
+ const messageState = message;
588
+ messageState.returnCode = types_1.ErrorCode.ERROR_COMMAND_TIMEOUT;
589
+ messageState.timeout = setTimeout(() => {
590
+ this._clearTimeout(messageState.retryTimeout);
591
+ this._sendCommand(messageState);
592
+ this._clearESDDisconnectTimeout();
593
+ this.closeEnergySavingDevice();
594
+ }, this.MAX_AKNOWLEDGE_TIMEOUT);
595
+ this.messageStates.set(messageState.sequence, messageState);
596
+ messageState.retryTimeout = setTimeout(() => {
597
+ this.resendNotAcknowledgedCommand(messageState.sequence);
598
+ }, this.RESEND_NOT_ACKNOWLEDGED_COMMAND);
599
+ if (messageState.commandType !== types_1.CommandType.CMD_PING && this.energySavingDevice) {
600
+ this.energySavingDeviceP2PSeqMapping.set(this.energySavingDeviceP2PDataSeqNumber, message.sequence);
601
+ this.log.debug(`Station ${this.rawStation.station_sn} - Energy saving Device - Added sequence number mapping`, { commandType: message.commandType, seqNumber: message.sequence, energySavingDeviceP2PDataSeqNumber: this.energySavingDeviceP2PDataSeqNumber, energySavingDeviceP2PSeqMappingCount: this.energySavingDeviceP2PSeqMapping.size });
602
+ this.energySavingDeviceP2PDataSeqNumber = this._incrementSequence(this.energySavingDeviceP2PDataSeqNumber);
603
+ }
604
+ this.log.debug("Sending p2p command...", { station: this.rawStation.station_sn, sequence: messageState.sequence, commandType: messageState.commandType, channel: messageState.channel, retries: messageState.retries, messageStatesSize: this.messageStates.size });
605
+ await this.sendMessage(`Send command to station ${this.rawStation.station_sn}`, this.connectAddress, types_1.RequestMessageType.DATA, messageState.data);
606
+ if (messageState.retries === 0) {
607
+ if (messageState.commandType === types_1.CommandType.CMD_START_REALTIME_MEDIA ||
608
+ (messageState.nestedCommandType !== undefined && messageState.nestedCommandType === types_1.CommandType.CMD_START_REALTIME_MEDIA && messageState.commandType === types_1.CommandType.CMD_SET_PAYLOAD) ||
609
+ messageState.commandType === types_1.CommandType.CMD_RECORD_VIEW ||
610
+ (messageState.nestedCommandType !== undefined && messageState.nestedCommandType === 1000 && messageState.commandType === types_1.CommandType.CMD_DOORBELL_SET_PAYLOAD)) {
611
+ if (this.currentMessageState[types_1.P2PDataType.VIDEO].p2pStreaming && messageState.channel !== this.currentMessageState[types_1.P2PDataType.VIDEO].p2pStreamChannel) {
612
+ this.endStream(types_1.P2PDataType.VIDEO);
613
+ }
614
+ this.currentMessageState[types_1.P2PDataType.VIDEO].p2pStreaming = true;
615
+ this.currentMessageState[types_1.P2PDataType.VIDEO].p2pStreamChannel = messageState.channel;
616
+ }
617
+ else if (messageState.commandType === types_1.CommandType.CMD_DOWNLOAD_VIDEO) {
618
+ if (this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreaming && messageState.channel !== this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreamChannel) {
619
+ this.endStream(types_1.P2PDataType.BINARY);
620
+ }
621
+ this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreaming = true;
622
+ this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreamChannel = message.channel;
623
+ }
624
+ else if (messageState.commandType === types_1.CommandType.CMD_STOP_REALTIME_MEDIA) { //TODO: CommandType.CMD_RECORD_PLAY_CTRL only if stop
625
+ this.endStream(types_1.P2PDataType.VIDEO);
626
+ }
627
+ else if (messageState.commandType === types_1.CommandType.CMD_DOWNLOAD_CANCEL) {
628
+ this.endStream(types_1.P2PDataType.BINARY);
629
+ }
630
+ else if (messageState.commandType === types_1.CommandType.CMD_NAS_TEST) {
631
+ if (this.currentMessageState[types_1.P2PDataType.DATA].rtspStream[messageState.channel]) {
632
+ this.currentMessageState[types_1.P2PDataType.DATA].rtspStreaming[messageState.channel] = true;
633
+ this.emit("rtsp livestream started", messageState.channel);
634
+ }
635
+ else {
636
+ this.endRTSPStream(messageState.channel);
637
+ }
638
+ }
639
+ }
640
+ }
641
+ handleMsg(msg, rinfo) {
642
+ if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.LOCAL_LOOKUP_RESP)) {
643
+ if (!this.connected) {
644
+ this._clearLookupTimeout();
645
+ this._clearLookupRetryTimeout();
646
+ const p2pDid = `${msg.slice(4, 12).toString("utf8").replace(/[\0]+$/g, "")}-${msg.slice(12, 16).readUInt32BE().toString().padStart(6, "0")}-${msg.slice(16, 24).toString("utf8").replace(/[\0]+$/g, "")}`;
647
+ this.log.debug(`Station ${this.rawStation.station_sn} - LOCAL_LOOKUP_RESP - Got response`, { ip: rinfo.address, port: rinfo.port, p2pDid: p2pDid });
648
+ if (p2pDid === this.rawStation.p2p_did) {
649
+ this.log.debug(`Station ${this.rawStation.station_sn} - LOCAL_LOOKUP_RESP - Wanted device was found, connect to it`, { ip: rinfo.address, port: rinfo.port, p2pDid: p2pDid });
650
+ this._connect({ host: rinfo.address, port: rinfo.port }, p2pDid);
651
+ }
652
+ else {
653
+ this.log.debug(`Station ${this.rawStation.station_sn} - LOCAL_LOOKUP_RESP - Unwanted device was found, don't connect to it`, { ip: rinfo.address, port: rinfo.port, p2pDid: p2pDid });
654
+ }
655
+ }
656
+ }
657
+ else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.LOOKUP_ADDR)) {
658
+ if (!this.connected) {
659
+ const port = msg.slice(6, 8).readUInt16LE();
660
+ const ip = `${msg[11]}.${msg[10]}.${msg[9]}.${msg[8]}`;
661
+ this.log.debug(`Station ${this.rawStation.station_sn} - LOOKUP_ADDR - Got response`, { remoteAddress: rinfo.address, remotePort: rinfo.port, response: { ip: ip, port: port } });
662
+ if (ip === "0.0.0.0") {
663
+ this.log.debug(`Station ${this.rawStation.station_sn} - LOOKUP_ADDR - Got invalid ip address 0.0.0.0, ignoring response...`);
664
+ return;
665
+ }
666
+ if ((0, utils_1.isPrivateIp)(ip))
667
+ this.localIPAddress = ip;
668
+ if (this.connectionType === types_1.P2PConnectionType.ONLY_LOCAL) {
669
+ if ((0, utils_1.isPrivateIp)(ip)) {
670
+ this._clearLookupTimeout();
671
+ this._clearLookupRetryTimeout();
672
+ this.log.debug(`Station ${this.rawStation.station_sn} - ONLY_LOCAL - Try to connect to ${ip}:${port}...`);
673
+ this._connect({ host: ip, port: port }, this.rawStation.p2p_did);
674
+ }
675
+ }
676
+ else if (this.connectionType === types_1.P2PConnectionType.QUICKEST) {
677
+ this._clearLookupTimeout();
678
+ this._clearLookupRetryTimeout();
679
+ this.log.debug(`Station ${this.rawStation.station_sn} - QUICKEST - Try to connect to ${ip}:${port}...`);
680
+ this._connect({ host: ip, port: port }, this.rawStation.p2p_did);
681
+ }
682
+ }
683
+ }
684
+ else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.CAM_ID) || (0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.CAM_ID2)) {
685
+ // Answer from the device to a CAM_CHECK message
686
+ if (!this.connected) {
687
+ this.log.debug(`Station ${this.rawStation.station_sn} - CAM_ID - Connected to station ${this.rawStation.station_sn} on host ${rinfo.address} port ${rinfo.port}`);
688
+ this._clearLookupRetryTimeout();
689
+ this._clearLookupTimeout();
690
+ this._clearConnectTimeout();
691
+ this.connected = true;
692
+ this.connectTime = new Date().getTime();
693
+ this.lastPong = null;
694
+ this.lastPongData = undefined;
695
+ this.connectAddress = { host: rinfo.address, port: rinfo.port };
696
+ if ((0, utils_1.isPrivateIp)(rinfo.address))
697
+ this.localIPAddress = rinfo.address;
698
+ this.heartbeatTimeout = setTimeout(() => {
699
+ this.scheduleHeartbeat();
700
+ }, this.getHeartbeatInterval());
701
+ if (this.energySavingDevice) {
702
+ this.keepaliveTimeout = setTimeout(() => {
703
+ this.scheduleP2PKeepalive();
704
+ }, this.KEEPALIVE_INTERVAL);
705
+ }
706
+ if (device_1.Device.isLockWifi(this.rawStation.device_type) || device_1.Device.isLockWifiNoFinger(this.rawStation.device_type)) {
707
+ const tmpSendQueue = [...this.sendQueue];
708
+ this.sendQueue = [];
709
+ this.sendCommandWithoutData(types_1.CommandType.CMD_GATEWAYINFO, 255);
710
+ this.sendCommandWithStringPayload({
711
+ commandType: types_1.CommandType.CMD_SET_PAYLOAD,
712
+ value: JSON.stringify({
713
+ "account_id": this.rawStation.member.admin_user_id,
714
+ "cmd": types_1.CommandType.P2P_QUERY_STATUS_IN_LOCK,
715
+ "mChannel": 0,
716
+ "mValue3": 0,
717
+ "payload": {
718
+ "timezone": this.rawStation.time_zone === undefined || this.rawStation.time_zone === "" ? (0, utils_2.getAdvancedLockTimezone)(this.rawStation.station_sn) : this.rawStation.time_zone,
719
+ }
720
+ }),
721
+ channel: 0
722
+ });
723
+ tmpSendQueue.forEach(element => {
724
+ this.sendQueue.push(element);
725
+ });
726
+ }
727
+ else if (device_1.Device.isSmartSafe(this.rawStation.device_type)) {
728
+ const payload = (0, utils_1.buildVoidCommandPayload)(255);
729
+ const data = Buffer.concat([(0, utils_1.buildCommandHeader)(this.seqNumber, types_1.CommandType.CMD_GATEWAYINFO), payload]);
730
+ const message = {
731
+ sequence: this.seqNumber,
732
+ commandType: types_1.CommandType.CMD_GATEWAYINFO,
733
+ channel: 255,
734
+ data: data,
735
+ retries: 0,
736
+ acknowledged: false,
737
+ returnCode: types_1.ErrorCode.ERROR_COMMAND_TIMEOUT,
738
+ };
739
+ this.messageStates.set(message.sequence, message);
740
+ message.retryTimeout = setTimeout(() => {
741
+ this.resendNotAcknowledgedCommand(message.sequence);
742
+ }, this.RESEND_NOT_ACKNOWLEDGED_COMMAND);
743
+ this.seqNumber = this._incrementSequence(this.seqNumber);
744
+ this.sendMessage(`Send smartsafe gateway command to station ${this.rawStation.station_sn}`, this.connectAddress, types_1.RequestMessageType.DATA, data);
745
+ const tmpSendQueue = [...this.sendQueue];
746
+ this.sendQueue = [];
747
+ this.sendCommandPing();
748
+ tmpSendQueue.forEach(element => {
749
+ this.sendQueue.push(element);
750
+ });
751
+ }
752
+ else if (device_1.Device.isLockWifiR10(this.rawStation.device_type) || device_1.Device.isLockWifiR20(this.rawStation.device_type)) {
753
+ const tmpSendQueue = [...this.sendQueue];
754
+ this.sendQueue = [];
755
+ /*const payload = buildVoidCommandPayload(255);
756
+ const data = Buffer.concat([buildCommandHeader(0, CommandType.CMD_GATEWAYINFO), payload]);
757
+ sendMessage(this.socket, this.connectAddress!, RequestMessageType.DATA, data).catch((error) => {
758
+ this.log.error(`Station ${this.rawStation.station_sn} - Error:`, error);
759
+ });
760
+ this.sendCommand(CommandType.CMD_PING, payload, 255);*/
761
+ const payload = (0, utils_1.buildVoidCommandPayload)(255);
762
+ const data = Buffer.concat([(0, utils_1.buildCommandHeader)(0, types_1.CommandType.CMD_GATEWAYINFO), payload.slice(0, payload.length - 2), (0, utils_1.buildCommandHeader)(0, types_1.CommandType.CMD_PING).slice(2), payload]);
763
+ const message = {
764
+ sequence: this.seqNumber,
765
+ commandType: types_1.CommandType.CMD_PING,
766
+ channel: 255,
767
+ data: data,
768
+ retries: 0,
769
+ acknowledged: false,
770
+ returnCode: types_1.ErrorCode.ERROR_COMMAND_TIMEOUT,
771
+ };
772
+ this.messageStates.set(message.sequence, message);
773
+ message.retryTimeout = setTimeout(() => {
774
+ this.resendNotAcknowledgedCommand(message.sequence);
775
+ }, this.RESEND_NOT_ACKNOWLEDGED_COMMAND);
776
+ this.seqNumber = this._incrementSequence(this.seqNumber);
777
+ this.sendMessage(`Send lock wifi gateway command to station ${this.rawStation.station_sn}`, this.connectAddress, types_1.RequestMessageType.DATA, data);
778
+ try {
779
+ const command = (0, utils_1.getLockV12P2PCommand)(this.rawStation.station_sn, this.rawStation.member.admin_user_id, types_1.ESLCommand.QUERY_STATUS_IN_LOCK, 0, this.lockPublicKey, this.incLockSequenceNumber(), device_1.Lock.encodeCmdStatus(this.rawStation.member.admin_user_id));
780
+ this.sendCommandWithStringPayload(command.payload);
781
+ }
782
+ catch (error) {
783
+ this.log.error(`Send query status lock command to station ${this.rawStation.station_sn} - Error`, error);
784
+ }
785
+ tmpSendQueue.forEach(element => {
786
+ this.sendQueue.push(element);
787
+ });
788
+ }
789
+ this.sendQueuedMessage();
790
+ this.emit("connect", this.connectAddress);
791
+ }
792
+ }
793
+ else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.PONG)) {
794
+ // Response to a ping from our side
795
+ this.lastPong = new Date().getTime();
796
+ if (msg.length > 4)
797
+ this.lastPongData = msg.slice(4);
798
+ else
799
+ this.lastPongData = undefined;
800
+ return;
801
+ }
802
+ else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.PING)) {
803
+ // Response with PONG to keep alive
804
+ this.sendMessage(`Send pong to station ${this.rawStation.station_sn}`, { host: rinfo.address, port: rinfo.port }, types_1.RequestMessageType.PONG);
805
+ return;
806
+ }
807
+ else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.END)) {
808
+ // Connection is closed by device
809
+ this.log.debug(`Station ${this.rawStation.station_sn} - END - received from host ${rinfo.address}:${rinfo.port}`);
810
+ this.onClose();
811
+ return;
812
+ }
813
+ else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.ACK)) {
814
+ // Device ACK a message from our side
815
+ // Number of Acks sended in the message
816
+ const dataTypeBuffer = msg.slice(4, 6);
817
+ const dataType = this.getDataType(dataTypeBuffer);
818
+ const numAcksBuffer = msg.slice(6, 8);
819
+ const numAcks = numAcksBuffer.readUIntBE(0, numAcksBuffer.length);
820
+ for (let i = 1; i <= numAcks; i++) {
821
+ const idx = 6 + i * 2;
822
+ const seqBuffer = msg.slice(idx, idx + 2);
823
+ const ackedSeqNo = seqBuffer.readUIntBE(0, seqBuffer.length);
824
+ // -> Message with seqNo was received at the station
825
+ this.log.debug(`Station ${this.rawStation.station_sn} - ACK ${types_1.P2PDataType[dataType]} - received from host ${rinfo.address}:${rinfo.port} for sequence ${ackedSeqNo}`);
826
+ if (dataType === types_1.P2PDataType.DATA) {
827
+ const msg_state = this.messageStates.get(ackedSeqNo);
828
+ if (msg_state && !msg_state.acknowledged) {
829
+ this._clearTimeout(msg_state.retryTimeout);
830
+ this._clearTimeout(msg_state.timeout);
831
+ if (msg_state.commandType === types_1.CommandType.CMD_PING || msg_state.commandType === types_1.CommandType.CMD_GET_DEVICE_PING) {
832
+ this.messageStates.delete(ackedSeqNo);
833
+ this.sendQueuedMessage();
834
+ }
835
+ else {
836
+ msg_state.acknowledged = true;
837
+ if (device_1.Device.isSmartSafe(this.rawStation.device_type) && msg_state.commandType === types_1.CommandType.CMD_GATEWAYINFO) {
838
+ //In this case no result data is received.
839
+ this.messageStates.delete(ackedSeqNo);
840
+ }
841
+ else {
842
+ msg_state.timeout = setTimeout(() => {
843
+ //TODO: Retry command in these case?
844
+ this.log.warn(`Station ${this.rawStation.station_sn} - Result data for command not received`, { message: { sequence: msg_state.sequence, commandType: msg_state.commandType, nestedCommandType: msg_state.nestedCommandType, channel: msg_state.channel, acknowledged: msg_state.acknowledged, retries: msg_state.retries, returnCode: msg_state.returnCode, data: msg_state.data } });
845
+ this.messageStates.delete(ackedSeqNo);
846
+ this.emit("command", {
847
+ command_type: msg_state.nestedCommandType !== undefined ? msg_state.nestedCommandType : msg_state.commandType,
848
+ channel: msg_state.channel,
849
+ return_code: types_1.ErrorCode.ERROR_COMMAND_TIMEOUT,
850
+ customData: msg_state.customData
851
+ });
852
+ this.sendQueuedMessage();
853
+ }, this.MAX_COMMAND_RESULT_WAIT);
854
+ this.messageStates.set(ackedSeqNo, msg_state);
855
+ }
856
+ this.sendQueuedMessage();
857
+ }
858
+ }
859
+ }
860
+ else if (dataType === types_1.P2PDataType.VIDEO) {
861
+ const msg_state = this.messageVideoStates.get(ackedSeqNo);
862
+ if (msg_state) {
863
+ this._clearTimeout(msg_state.timeout);
864
+ this.messageVideoStates.delete(ackedSeqNo);
865
+ }
866
+ }
867
+ }
868
+ }
869
+ else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.DATA)) {
870
+ if (this.connected) {
871
+ const seqNo = msg.slice(6, 8).readUInt16BE();
872
+ const dataTypeBuffer = msg.slice(4, 6);
873
+ const dataType = this.getDataType(dataTypeBuffer);
874
+ const message = {
875
+ bytesToRead: msg.slice(2, 4).readUInt16BE(),
876
+ type: dataType,
877
+ seqNo: seqNo,
878
+ data: msg.slice(8)
879
+ };
880
+ this.sendAck({ host: rinfo.address, port: rinfo.port }, dataTypeBuffer, seqNo);
881
+ this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[message.type]} - received from host ${rinfo.address}:${rinfo.port} - Processing sequence ${message.seqNo}...`);
882
+ if (message.seqNo === this.expectedSeqNo[dataType]) {
883
+ // expected seq packet arrived
884
+ const timeout = this.currentMessageState[dataType].waitForSeqNoTimeout;
885
+ if (!!timeout) {
886
+ clearTimeout(timeout);
887
+ this.currentMessageState[dataType].waitForSeqNoTimeout = undefined;
888
+ }
889
+ this.expectedSeqNo[dataType] = this._incrementSequence(this.expectedSeqNo[dataType]);
890
+ this.parseDataMessage(message);
891
+ this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[message.type]} - Received expected sequence (expectedSeqNo: ${this.expectedSeqNo[dataType]} seqNo: ${message.seqNo} queuedData.size: ${this.currentMessageState[dataType].queuedData.size})`);
892
+ let queuedMessage = this.currentMessageState[dataType].queuedData.get(this.expectedSeqNo[dataType]);
893
+ while (queuedMessage) {
894
+ this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[queuedMessage.type]} - Work off queued data (expectedSeqNo: ${this.expectedSeqNo[dataType]} seqNo: ${queuedMessage.seqNo} queuedData.size: ${this.currentMessageState[dataType].queuedData.size})`);
895
+ this.expectedSeqNo[dataType] = this._incrementSequence(this.expectedSeqNo[dataType]);
896
+ this.parseDataMessage(queuedMessage);
897
+ this.currentMessageState[dataType].queuedData.delete(queuedMessage.seqNo);
898
+ queuedMessage = this.currentMessageState[dataType].queuedData.get(this.expectedSeqNo[dataType]);
899
+ }
900
+ }
901
+ else if (this._wasSequenceNumberAlreadyProcessed(this.expectedSeqNo[dataType], message.seqNo)) {
902
+ // We have already seen this message, skip!
903
+ // This can happen because the device is sending the message till it gets a ACK
904
+ // which can take some time.
905
+ this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[message.type]} - Received already processed sequence (expectedSeqNo: ${this.expectedSeqNo[dataType]} seqNo: ${message.seqNo} queuedData.size: ${this.currentMessageState[dataType].queuedData.size})`);
906
+ return;
907
+ }
908
+ else {
909
+ if (!this.currentMessageState[dataType].waitForSeqNoTimeout)
910
+ this.currentMessageState[dataType].waitForSeqNoTimeout = setTimeout(() => {
911
+ this.endStream(dataType, true);
912
+ this.currentMessageState[dataType].waitForSeqNoTimeout = undefined;
913
+ }, this.MAX_EXPECTED_SEQNO_WAIT);
914
+ if (!this.currentMessageState[dataType].queuedData.get(message.seqNo)) {
915
+ this.currentMessageState[dataType].queuedData.set(message.seqNo, message);
916
+ this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[message.type]} - Received not expected sequence, added to the queue for future processing (expectedSeqNo: ${this.expectedSeqNo[dataType]} seqNo: ${message.seqNo} queuedData.size: ${this.currentMessageState[dataType].queuedData.size})`);
917
+ }
918
+ else {
919
+ this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[message.type]} - Received not expected sequence, discarded since already present in queue for future processing (expectedSeqNo: ${this.expectedSeqNo[dataType]} seqNo: ${message.seqNo} queuedData.size: ${this.currentMessageState[dataType].queuedData.size})`);
920
+ }
921
+ }
922
+ }
923
+ }
924
+ else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.LOOKUP_ADDR2)) {
925
+ if (!this.connected) {
926
+ const port = msg.slice(6, 8).readUInt16LE();
927
+ const ip = `${msg[11]}.${msg[10]}.${msg[9]}.${msg[8]}`;
928
+ const data = msg.slice(20, 24);
929
+ this._clearLookupTimeout();
930
+ this._clearLookupRetryTimeout();
931
+ this.log.debug(`Station ${this.rawStation.station_sn} - LOOKUP_ADDR2 - Got response`, { remoteAddress: rinfo.address, remotePort: rinfo.port, response: { ip: ip, port: port, data: data.toString("hex") } });
932
+ this.log.debug(`Station ${this.rawStation.station_sn} - CHECK_CAM2 - Connecting to host ${ip} on port ${port}...`);
933
+ for (let i = 0; i < 4; i++)
934
+ this.sendCamCheck2({ host: ip, port: port }, data);
935
+ this._startConnectTimeout();
936
+ this.sendMessage(`Send UNKNOWN_70 to station ${this.rawStation.station_sn}`, { host: ip, port: port }, types_1.RequestMessageType.UNKNOWN_70);
937
+ }
938
+ }
939
+ else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.UNKNOWN_71)) {
940
+ if (!this.connected) {
941
+ this.log.debug(`Station ${this.rawStation.station_sn} - UNKNOWN_71 - Got response`, { remoteAddress: rinfo.address, remotePort: rinfo.port, response: { message: msg.toString("hex"), length: msg.length } });
942
+ this.sendMessage(`Send UNKNOWN_71 to station ${this.rawStation.station_sn}`, { host: rinfo.address, port: rinfo.port }, types_1.RequestMessageType.UNKNOWN_71);
943
+ }
944
+ }
945
+ else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.UNKNOWN_73)) {
946
+ if (!this.connected) {
947
+ const port = msg.slice(8, 10).readUInt16BE();
948
+ const data = msg.slice(4, 8);
949
+ this.log.debug(`Station ${this.rawStation.station_sn} - UNKNOWN_73 - Got response`, { remoteAddress: rinfo.address, remotePort: rinfo.port, response: { port: port, data: data.toString("hex") } });
950
+ this.cloudLookup2({ host: rinfo.address, port: port }, data);
951
+ }
952
+ }
953
+ else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.UNKNOWN_81) || (0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.UNKNOWN_83)) {
954
+ // Do nothing / ignore
955
+ }
956
+ else if ((0, utils_1.hasHeader)(msg, types_1.ResponseMessageType.LOOKUP_RESP)) {
957
+ if (!this.connected) {
958
+ const responseCode = msg.slice(4, 6).readUInt16LE();
959
+ this.log.debug(`Station ${this.rawStation.station_sn} - LOOKUP_RESP - Got response`, { remoteAddress: rinfo.address, remotePort: rinfo.port, response: { responseCode: responseCode } });
960
+ if (responseCode !== 0 && this.lookupTimeout !== undefined && this.lookupRetryTimeout === undefined) {
961
+ this.lookupRetryTimeout = setTimeout(() => {
962
+ this.lookupRetryTimeout = undefined;
963
+ this.cloudAddresses.map((address) => this.cloudLookupByAddress(address));
964
+ }, this.LOOKUP_RETRY_TIMEOUT);
965
+ }
966
+ }
967
+ }
968
+ else {
969
+ this.log.debug(`Station ${this.rawStation.station_sn} - received unknown message`, { remoteAddress: rinfo.address, remotePort: rinfo.port, response: { message: msg.toString("hex"), length: msg.length } });
970
+ }
971
+ }
972
+ parseDataMessage(message) {
973
+ if ((message.type === types_1.P2PDataType.BINARY || message.type === types_1.P2PDataType.VIDEO) && !this.currentMessageState[message.type].p2pStreaming) {
974
+ this.log.debug(`Station ${this.rawStation.station_sn} - DATA ${types_1.P2PDataType[message.type]} - Stream not started ignore this data`, { seqNo: message.seqNo, header: this.currentMessageBuilder[message.type].header, bytesRead: this.currentMessageBuilder[message.type].bytesRead, bytesToRead: this.currentMessageBuilder[message.type].header.bytesToRead, messageSize: message.data.length });
975
+ }
976
+ else {
977
+ if (this.currentMessageState[message.type].leftoverData.length > 0) {
978
+ message.data = Buffer.concat([this.currentMessageState[message.type].leftoverData, message.data]);
979
+ this.currentMessageState[message.type].leftoverData = Buffer.from([]);
980
+ }
981
+ let data = message.data;
982
+ do {
983
+ // is this the first message?
984
+ const firstPartMessage = data.slice(0, 4).toString() === utils_1.MAGIC_WORD;
985
+ if (firstPartMessage) {
986
+ const header = {
987
+ commandId: 0,
988
+ bytesToRead: 0,
989
+ channel: 0,
990
+ signCode: 0,
991
+ type: 0
992
+ };
993
+ header.commandId = data.slice(4, 6).readUIntLE(0, 2);
994
+ header.bytesToRead = data.slice(6, 10).readUIntLE(0, 4);
995
+ header.channel = data.slice(12, 13).readUInt8();
996
+ header.signCode = data.slice(13, 14).readUInt8();
997
+ header.type = data.slice(14, 15).readUInt8();
998
+ this.currentMessageBuilder[message.type].header = header;
999
+ data = data.slice(this.P2P_DATA_HEADER_BYTES);
1000
+ if (data.length >= header.bytesToRead) {
1001
+ const payload = data.slice(0, header.bytesToRead);
1002
+ this.currentMessageBuilder[message.type].messages[message.seqNo] = payload;
1003
+ this.currentMessageBuilder[message.type].bytesRead = payload.byteLength;
1004
+ data = data.slice(header.bytesToRead);
1005
+ if (data.length <= this.P2P_DATA_HEADER_BYTES) {
1006
+ this.currentMessageState[message.type].leftoverData = data;
1007
+ data = Buffer.from([]);
1008
+ }
1009
+ }
1010
+ else {
1011
+ if (data.length <= this.P2P_DATA_HEADER_BYTES) {
1012
+ this.currentMessageState[message.type].leftoverData = data;
1013
+ }
1014
+ else {
1015
+ this.currentMessageBuilder[message.type].messages[message.seqNo] = data;
1016
+ this.currentMessageBuilder[message.type].bytesRead = data.byteLength;
1017
+ }
1018
+ data = Buffer.from([]);
1019
+ }
1020
+ }
1021
+ else {
1022
+ // finish message and print
1023
+ if (this.currentMessageBuilder[message.type].header.bytesToRead - this.currentMessageBuilder[message.type].bytesRead <= data.length) {
1024
+ const payload = data.slice(0, this.currentMessageBuilder[message.type].header.bytesToRead - this.currentMessageBuilder[message.type].bytesRead);
1025
+ this.currentMessageBuilder[message.type].messages[message.seqNo] = payload;
1026
+ this.currentMessageBuilder[message.type].bytesRead += payload.byteLength;
1027
+ data = data.slice(payload.byteLength);
1028
+ if (data.length <= this.P2P_DATA_HEADER_BYTES) {
1029
+ this.currentMessageState[message.type].leftoverData = data;
1030
+ data = Buffer.from([]);
1031
+ }
1032
+ }
1033
+ else {
1034
+ if (data.length <= this.P2P_DATA_HEADER_BYTES) {
1035
+ this.currentMessageState[message.type].leftoverData = data;
1036
+ }
1037
+ else {
1038
+ this.currentMessageBuilder[message.type].messages[message.seqNo] = data;
1039
+ this.currentMessageBuilder[message.type].bytesRead += data.byteLength;
1040
+ }
1041
+ data = Buffer.from([]);
1042
+ }
1043
+ }
1044
+ this.log.debug(`Station ${this.rawStation.station_sn} - Received data`, { seqNo: message.seqNo, header: this.currentMessageBuilder[message.type].header, bytesRead: this.currentMessageBuilder[message.type].bytesRead, bytesToRead: this.currentMessageBuilder[message.type].header.bytesToRead, firstPartMessage: firstPartMessage, messageSize: message.data.length });
1045
+ if (this.currentMessageBuilder[message.type].bytesRead === this.currentMessageBuilder[message.type].header.bytesToRead) {
1046
+ const completeMessage = (0, utils_1.sortP2PMessageParts)(this.currentMessageBuilder[message.type].messages);
1047
+ const data_message = {
1048
+ ...this.currentMessageBuilder[message.type].header,
1049
+ seqNo: (message.seqNo + this.offsetDataSeqNumber),
1050
+ dataType: message.type,
1051
+ data: completeMessage
1052
+ };
1053
+ this.handleData(data_message);
1054
+ this.initializeMessageBuilder(message.type);
1055
+ if (data.length > 0 && message.type === types_1.P2PDataType.DATA) {
1056
+ this.log.debug(`Station ${this.rawStation.station_sn} - Parsed data`, { seqNo: message.seqNo, data_message: data_message, datalen: data.length, data: data.toString("hex"), offsetDataSeqNumber: this.offsetDataSeqNumber, seqNumber: this.seqNumber, energySavingDeviceP2PDataSeqNumber: this.energySavingDeviceP2PDataSeqNumber });
1057
+ this.offsetDataSeqNumber++;
1058
+ }
1059
+ }
1060
+ } while (data.length > 0);
1061
+ }
1062
+ }
1063
+ handleData(message) {
1064
+ if (message.dataType === types_1.P2PDataType.CONTROL) {
1065
+ this.handleDataControl(message);
1066
+ }
1067
+ else if (message.dataType === types_1.P2PDataType.DATA) {
1068
+ const commandStr = types_1.CommandType[message.commandId];
1069
+ const result_msg = message.type === 1 ? true : false;
1070
+ if (result_msg) {
1071
+ const return_code = message.data.slice(0, 4).readUInt32LE() | 0;
1072
+ const return_msg = message.data.slice(4, 4 + 128).toString();
1073
+ const error_codeStr = types_1.ErrorCode[return_code];
1074
+ this.log.debug(`Station ${this.rawStation.station_sn} - Received data`, { commandIdName: commandStr, commandId: message.commandId, resultCodeName: error_codeStr, resultCode: return_code, message: return_msg, data: message.data.toString("hex"), seqNumber: this.seqNumber, energySavingDeviceP2PDataSeqNumber: this.energySavingDeviceP2PDataSeqNumber, offsetDataSeqNumber: this.offsetDataSeqNumber });
1075
+ let msg_state = this.messageStates.get(message.seqNo);
1076
+ if (this.energySavingDevice) {
1077
+ const goodSeqNumber = this.energySavingDeviceP2PSeqMapping.get(message.seqNo);
1078
+ if (goodSeqNumber) {
1079
+ this.energySavingDeviceP2PSeqMapping.delete(message.seqNo);
1080
+ msg_state = this.messageStates.get(goodSeqNumber);
1081
+ this.log.debug(`Station ${this.rawStation.station_sn} - Energy saving Device - Result data received - Detecting correct sequence number`, { commandIdName: commandStr, commandId: message.commandId, seqNumber: message.seqNo, newSeqNumber: goodSeqNumber, energySavingDeviceP2PSeqMappingCount: this.energySavingDeviceP2PSeqMapping.size });
1082
+ message.seqNo = goodSeqNumber;
1083
+ }
1084
+ }
1085
+ if (msg_state) {
1086
+ if (msg_state.commandType === message.commandId) {
1087
+ this._clearTimeout(msg_state.timeout);
1088
+ const command_type = msg_state.nestedCommandType !== undefined ? msg_state.nestedCommandType : msg_state.commandType;
1089
+ this.log.debug(`Station ${this.rawStation.station_sn} - Result data for command received`, { message: { sequence: msg_state.sequence, commandType: msg_state.commandType, nestedCommandType: msg_state.nestedCommandType, channel: msg_state.channel, acknowledged: msg_state.acknowledged, retries: msg_state.retries, returnCode: msg_state.returnCode, data: msg_state.data, customData: msg_state.customData }, resultCodeName: error_codeStr, resultCode: return_code });
1090
+ if (return_code === types_1.ErrorCode.ERROR_FAILED_TO_REQUEST) {
1091
+ msg_state.returnCode = return_code;
1092
+ this._sendCommand(msg_state);
1093
+ }
1094
+ else {
1095
+ this.emit("command", {
1096
+ command_type: command_type,
1097
+ channel: msg_state.channel,
1098
+ return_code: return_code,
1099
+ customData: msg_state.customData
1100
+ });
1101
+ this.messageStates.delete(message.seqNo);
1102
+ if (command_type === types_1.CommandType.CMD_SMARTSAFE_SETTINGS || command_type === types_1.CommandType.CMD_SET_PAYLOAD_LOCKV12) {
1103
+ this.lastCustomData = msg_state.customData;
1104
+ this.lastChannel = msg_state.channel;
1105
+ this.secondaryCommandTimeout = setTimeout(() => {
1106
+ this.log.warn(`Station ${this.rawStation.station_sn} - Result data for secondary command not received`, { message: { sequence: msg_state.sequence, commandType: msg_state.commandType, nestedCommandType: msg_state.nestedCommandType, channel: msg_state.channel, acknowledged: msg_state.acknowledged, retries: msg_state.retries, returnCode: msg_state.returnCode, data: msg_state.data, customData: msg_state.customData } });
1107
+ this.secondaryCommandTimeout = undefined;
1108
+ this.emit("secondary command", {
1109
+ command_type: msg_state.nestedCommandType !== undefined ? msg_state.nestedCommandType : msg_state.commandType,
1110
+ channel: msg_state.channel,
1111
+ return_code: types_1.ErrorCode.ERROR_COMMAND_TIMEOUT,
1112
+ customData: msg_state.customData
1113
+ });
1114
+ this._clearESDDisconnectTimeout();
1115
+ this.sendQueuedMessage();
1116
+ }, this.MAX_COMMAND_RESULT_WAIT);
1117
+ }
1118
+ else {
1119
+ this._clearESDDisconnectTimeout();
1120
+ this.sendQueuedMessage();
1121
+ }
1122
+ if (msg_state.commandType === types_1.CommandType.CMD_START_REALTIME_MEDIA ||
1123
+ (msg_state.nestedCommandType !== undefined && msg_state.nestedCommandType === types_1.CommandType.CMD_START_REALTIME_MEDIA && msg_state.commandType === types_1.CommandType.CMD_SET_PAYLOAD) ||
1124
+ msg_state.commandType === types_1.CommandType.CMD_RECORD_VIEW ||
1125
+ (msg_state.nestedCommandType !== undefined && msg_state.nestedCommandType === 1000 && msg_state.commandType === types_1.CommandType.CMD_DOORBELL_SET_PAYLOAD)) {
1126
+ this.waitForStreamData(types_1.P2PDataType.VIDEO);
1127
+ }
1128
+ else if (msg_state.commandType === types_1.CommandType.CMD_DOWNLOAD_VIDEO) {
1129
+ this.waitForStreamData(types_1.P2PDataType.BINARY);
1130
+ }
1131
+ else if (msg_state.commandType === types_1.CommandType.CMD_START_TALKBACK || (msg_state.commandType === types_1.CommandType.CMD_DOORBELL_SET_PAYLOAD && msg_state.nestedCommandType === types_1.IndoorSoloSmartdropCommandType.CMD_START_SPEAK)) {
1132
+ if (return_code === types_1.ErrorCode.ERROR_PPCS_SUCCESSFUL) {
1133
+ this.startTalkback(msg_state.channel);
1134
+ }
1135
+ else if (return_code === types_1.ErrorCode.ERROR_NOT_FIND_DEV) {
1136
+ this.emit("talkback error", msg_state.channel, new error_1.TalkbackError(`Station ${this.rawStation.station_sn} channel ${msg_state.channel} someone is responding now.`));
1137
+ }
1138
+ else if (return_code === types_1.ErrorCode.ERROR_DEV_BUSY) {
1139
+ this.emit("talkback error", msg_state.channel, new error_1.TalkbackError(`Station ${this.rawStation.station_sn} channel ${msg_state.channel} wait a second, device is busy.`));
1140
+ }
1141
+ else {
1142
+ this.emit("talkback error", msg_state.channel, new error_1.TalkbackError(`Station ${this.rawStation.station_sn} channel ${msg_state.channel} connect failed please try again later.`));
1143
+ }
1144
+ }
1145
+ else if (msg_state.commandType === types_1.CommandType.CMD_STOP_TALKBACK || (msg_state.commandType === types_1.CommandType.CMD_DOORBELL_SET_PAYLOAD && msg_state.nestedCommandType === types_1.IndoorSoloSmartdropCommandType.CMD_END_SPEAK)) {
1146
+ this.stopTalkback(msg_state.channel);
1147
+ }
1148
+ else if (msg_state.commandType === types_1.CommandType.CMD_SDINFO_EX) {
1149
+ this.emit("sd info ex", message.data.slice(0, 4).readUInt32LE(), message.data.slice(4, 8).readUInt32LE(), message.data.slice(8, 12).readUInt32LE());
1150
+ }
1151
+ }
1152
+ }
1153
+ else {
1154
+ this.messageStates.delete(message.seqNo);
1155
+ this.log.debug(`Station ${this.rawStation.station_sn} - dataType: ${types_1.P2PDataType[message.dataType]} commandtype and sequencenumber different!`, { msg_sequence: msg_state.sequence, msg_channel: msg_state.channel, msg_commandType: msg_state.commandType, message: message, seqNumber: this.seqNumber, energySavingDeviceP2PDataSeqNumber: this.energySavingDeviceP2PDataSeqNumber, offsetDataSeqNumber: this.offsetDataSeqNumber });
1156
+ this.log.warn(`P2P protocol instability detected for station ${this.rawStation.station_sn}. Please reinitialise the connection to solve the problem!`);
1157
+ }
1158
+ }
1159
+ else if (message.commandId !== types_1.CommandType.CMD_PING && message.commandId !== types_1.CommandType.CMD_GET_DEVICE_PING) {
1160
+ this.log.debug(`Station ${this.rawStation.station_sn} - dataType: ${types_1.P2PDataType[message.dataType]} commandId: ${message.commandId} sequence: ${message.seqNo} not present!`, { seqNumber: this.seqNumber, energySavingDeviceP2PDataSeqNumber: this.energySavingDeviceP2PDataSeqNumber, offsetDataSeqNumber: this.offsetDataSeqNumber });
1161
+ }
1162
+ }
1163
+ else {
1164
+ this.log.debug(`Station ${this.rawStation.station_sn} - Unsupported response`, { dataType: types_1.P2PDataType[message.dataType], commandIdName: commandStr, commandId: message.commandId, message: message.data.toString("hex") });
1165
+ }
1166
+ }
1167
+ else if (message.dataType === types_1.P2PDataType.VIDEO || message.dataType === types_1.P2PDataType.BINARY) {
1168
+ this.handleDataBinaryAndVideo(message);
1169
+ }
1170
+ else {
1171
+ this.log.debug(`Station ${this.rawStation.station_sn} - Not implemented data type`, { seqNo: message.seqNo, dataType: message.dataType, commandId: message.commandId, message: message.data.toString("hex") });
1172
+ }
1173
+ }
1174
+ isIFrame(data, isKeyFrame) {
1175
+ if (this.rawStation.station_sn.startsWith("T8410") || this.rawStation.station_sn.startsWith("T8400") || this.rawStation.station_sn.startsWith("T8401") || this.rawStation.station_sn.startsWith("T8411") ||
1176
+ this.rawStation.station_sn.startsWith("T8202") || this.rawStation.station_sn.startsWith("T8422") || this.rawStation.station_sn.startsWith("T8424") || this.rawStation.station_sn.startsWith("T8423") ||
1177
+ this.rawStation.station_sn.startsWith("T8130") || this.rawStation.station_sn.startsWith("T8131") || this.rawStation.station_sn.startsWith("T8420") || this.rawStation.station_sn.startsWith("T8440") ||
1178
+ this.rawStation.station_sn.startsWith("T8441") || this.rawStation.station_sn.startsWith("T8442") || (0, utils_1.checkT8420)(this.rawStation.station_sn)) {
1179
+ //TODO: Need to add battery doorbells as seen in source => T8210,T8220,T8221,T8222
1180
+ return isKeyFrame;
1181
+ }
1182
+ const iframe = (0, utils_1.isIFrame)(data);
1183
+ if (iframe === false) {
1184
+ // Fallback
1185
+ return isKeyFrame;
1186
+ }
1187
+ return iframe;
1188
+ }
1189
+ waitForStreamData(dataType) {
1190
+ if (this.currentMessageState[dataType].p2pStreamingTimeout) {
1191
+ clearTimeout(this.currentMessageState[dataType].p2pStreamingTimeout);
1192
+ }
1193
+ this.currentMessageState[dataType].p2pStreamingTimeout = setTimeout(() => {
1194
+ var _a;
1195
+ this.log.info(`Stopping the station stream for the device ${(_a = this.deviceSNs[this.currentMessageState[dataType].p2pStreamChannel]) === null || _a === void 0 ? void 0 : _a.sn}, because we haven't received any data for ${this.MAX_STREAM_DATA_WAIT} seconds`);
1196
+ this.endStream(dataType, true);
1197
+ }, this.MAX_STREAM_DATA_WAIT);
1198
+ }
1199
+ handleDataBinaryAndVideo(message) {
1200
+ var _a, _b, _c;
1201
+ if (!this.currentMessageState[message.dataType].invalidStream) {
1202
+ switch (message.commandId) {
1203
+ case types_1.CommandType.CMD_VIDEO_FRAME:
1204
+ this.waitForStreamData(message.dataType);
1205
+ const videoMetaData = {
1206
+ streamType: 0,
1207
+ videoSeqNo: 0,
1208
+ videoFPS: 15,
1209
+ videoWidth: 1920,
1210
+ videoHeight: 1080,
1211
+ videoTimestamp: 0,
1212
+ videoDataLength: 0,
1213
+ aesKey: ""
1214
+ };
1215
+ const data_length = message.data.readUInt32LE();
1216
+ const isKeyFrame = message.data.slice(4, 5).readUInt8() === 1 ? true : false;
1217
+ videoMetaData.videoDataLength = message.data.slice(0, 4).readUInt32LE();
1218
+ videoMetaData.streamType = message.data.slice(5, 6).readUInt8();
1219
+ videoMetaData.videoSeqNo = message.data.slice(6, 8).readUInt16LE();
1220
+ videoMetaData.videoFPS = message.data.slice(8, 10).readUInt16LE();
1221
+ videoMetaData.videoWidth = message.data.slice(10, 12).readUInt16LE();
1222
+ videoMetaData.videoHeight = message.data.slice(12, 14).readUInt16LE();
1223
+ videoMetaData.videoTimestamp = message.data.slice(14, 20).readUIntLE(0, 6);
1224
+ let payloadStart = 22;
1225
+ if (message.signCode > 0 && data_length >= 128) {
1226
+ const key = message.data.slice(22, 150);
1227
+ const rsaKey = this.currentMessageState[message.dataType].rsaKey;
1228
+ if (rsaKey) {
1229
+ try {
1230
+ videoMetaData.aesKey = rsaKey.decrypt(key).toString("hex");
1231
+ this.log.debug(`Station ${this.rawStation.station_sn} - Decrypted AES key: ${videoMetaData.aesKey}`);
1232
+ }
1233
+ catch (error) {
1234
+ this.log.warn(`Station ${this.rawStation.station_sn} - AES key could not be decrypted! The entire stream is discarded. - Error:`, error);
1235
+ this.currentMessageState[message.dataType].invalidStream = true;
1236
+ this.emit("livestream error", message.channel, new error_1.LivestreamError(`Station ${this.rawStation.station_sn} AES key could not be decrypted! The entire stream is discarded.`));
1237
+ return;
1238
+ }
1239
+ }
1240
+ else {
1241
+ this.log.warn(`Station ${this.rawStation.station_sn} - Private RSA key is missing! Stream could not be decrypted. The entire stream is discarded.`);
1242
+ this.currentMessageState[message.dataType].invalidStream = true;
1243
+ this.emit("livestream error", message.channel, new error_1.LivestreamError(`Station ${this.rawStation.station_sn} private RSA key is missing! Stream could not be decrypted. The entire stream is discarded.`));
1244
+ return;
1245
+ }
1246
+ payloadStart = 151;
1247
+ }
1248
+ let video_data;
1249
+ if (videoMetaData.aesKey !== "") {
1250
+ const encrypted_data = message.data.slice(payloadStart, payloadStart + 128);
1251
+ const unencrypted_data = message.data.slice(payloadStart + 128, payloadStart + videoMetaData.videoDataLength);
1252
+ video_data = Buffer.concat([(0, utils_1.decryptAESData)(videoMetaData.aesKey, encrypted_data), unencrypted_data]);
1253
+ }
1254
+ else {
1255
+ video_data = message.data.slice(payloadStart, payloadStart + videoMetaData.videoDataLength);
1256
+ }
1257
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME`, { dataSize: message.data.length, metadata: videoMetaData, videoDataSize: video_data.length });
1258
+ this.currentMessageState[message.dataType].p2pStreamMetadata.videoFPS = videoMetaData.videoFPS;
1259
+ this.currentMessageState[message.dataType].p2pStreamMetadata.videoHeight = videoMetaData.videoHeight;
1260
+ this.currentMessageState[message.dataType].p2pStreamMetadata.videoWidth = videoMetaData.videoWidth;
1261
+ if (!this.currentMessageState[message.dataType].p2pStreamFirstVideoDataReceived) {
1262
+ if (this.rawStation.station_sn.startsWith("T8410") || this.rawStation.station_sn.startsWith("T8400") || this.rawStation.station_sn.startsWith("T8401") || this.rawStation.station_sn.startsWith("T8411") ||
1263
+ this.rawStation.station_sn.startsWith("T8202") || this.rawStation.station_sn.startsWith("T8422") || this.rawStation.station_sn.startsWith("T8424") || this.rawStation.station_sn.startsWith("T8423") ||
1264
+ this.rawStation.station_sn.startsWith("T8130") || this.rawStation.station_sn.startsWith("T8131") || this.rawStation.station_sn.startsWith("T8420") || this.rawStation.station_sn.startsWith("T8440") ||
1265
+ this.rawStation.station_sn.startsWith("T8441") || this.rawStation.station_sn.startsWith("T8442") || (0, utils_1.checkT8420)(this.rawStation.station_sn)) {
1266
+ this.currentMessageState[message.dataType].p2pStreamMetadata.videoCodec = videoMetaData.streamType === 1 ? types_1.VideoCodec.H264 : videoMetaData.streamType === 2 ? types_1.VideoCodec.H265 : (0, utils_1.getVideoCodec)(video_data);
1267
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME - Video codec information received from packet`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, metadata: videoMetaData });
1268
+ }
1269
+ else if (this.isIFrame(video_data, isKeyFrame)) {
1270
+ this.currentMessageState[message.dataType].p2pStreamMetadata.videoCodec = (0, utils_1.getVideoCodec)(video_data);
1271
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME - Video codec extracted from video data`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, metadata: videoMetaData });
1272
+ }
1273
+ else {
1274
+ this.currentMessageState[message.dataType].p2pStreamMetadata.videoCodec = (0, utils_1.getVideoCodec)(video_data);
1275
+ if (this.currentMessageState[message.dataType].p2pStreamMetadata.videoCodec === types_1.VideoCodec.UNKNOWN) {
1276
+ this.currentMessageState[message.dataType].p2pStreamMetadata.videoCodec = videoMetaData.streamType === 1 ? types_1.VideoCodec.H264 : videoMetaData.streamType === 2 ? types_1.VideoCodec.H265 : types_1.VideoCodec.UNKNOWN;
1277
+ if (this.currentMessageState[message.dataType].p2pStreamMetadata.videoCodec === types_1.VideoCodec.UNKNOWN) {
1278
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME - Unknown video codec`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, metadata: videoMetaData });
1279
+ }
1280
+ else {
1281
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME - Fallback, using video codec information received from packet`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, metadata: videoMetaData });
1282
+ }
1283
+ }
1284
+ else {
1285
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME - Fallback, video codec extracted from video data`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, metadata: videoMetaData });
1286
+ }
1287
+ }
1288
+ this.currentMessageState[message.dataType].p2pStreamFirstVideoDataReceived = true;
1289
+ this.currentMessageState[message.dataType].waitForAudioData = setTimeout(() => {
1290
+ this.currentMessageState[message.dataType].waitForAudioData = undefined;
1291
+ this.currentMessageState[message.dataType].p2pStreamMetadata.audioCodec = types_1.AudioCodec.NONE;
1292
+ this.currentMessageState[message.dataType].p2pStreamFirstAudioDataReceived = true;
1293
+ if (this.currentMessageState[message.dataType].p2pStreamFirstAudioDataReceived && this.currentMessageState[message.dataType].p2pStreamFirstVideoDataReceived) {
1294
+ this.emitStreamStartEvent(message.dataType);
1295
+ }
1296
+ }, this.AUDIO_CODEC_ANALYZE_TIMEOUT);
1297
+ }
1298
+ if (this.currentMessageState[message.dataType].p2pStreamNotStarted) {
1299
+ if (this.currentMessageState[message.dataType].p2pStreamFirstAudioDataReceived && this.currentMessageState[message.dataType].p2pStreamFirstVideoDataReceived) {
1300
+ this.emitStreamStartEvent(message.dataType);
1301
+ }
1302
+ }
1303
+ if (message.dataType === types_1.P2PDataType.VIDEO) {
1304
+ if ((0, utils_1.findStartCode)(video_data)) {
1305
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME: startcode found`, { isKeyFrame: isKeyFrame, preFrameVideoDataLength: this.currentMessageState[message.dataType].preFrameVideoData.length });
1306
+ if (!this.currentMessageState[message.dataType].receivedFirstIFrame)
1307
+ this.currentMessageState[message.dataType].receivedFirstIFrame = this.isIFrame(video_data, isKeyFrame);
1308
+ if (this.currentMessageState[message.dataType].receivedFirstIFrame) {
1309
+ if (this.currentMessageState[message.dataType].preFrameVideoData.length > this.MAX_VIDEO_PACKET_BYTES)
1310
+ this.currentMessageState[message.dataType].preFrameVideoData = Buffer.from([]);
1311
+ if (this.currentMessageState[message.dataType].preFrameVideoData.length > 0) {
1312
+ (_a = this.currentMessageState[message.dataType].videoStream) === null || _a === void 0 ? void 0 : _a.push(this.currentMessageState[message.dataType].preFrameVideoData);
1313
+ }
1314
+ this.currentMessageState[message.dataType].preFrameVideoData = Buffer.from(video_data);
1315
+ }
1316
+ else {
1317
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME: Skipping because first frame is not an I frame.`);
1318
+ }
1319
+ }
1320
+ else {
1321
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_VIDEO_FRAME: No startcode found`, { isKeyFrame: isKeyFrame, preFrameVideoDataLength: this.currentMessageState[message.dataType].preFrameVideoData.length });
1322
+ if (this.currentMessageState[message.dataType].preFrameVideoData.length > 0) {
1323
+ this.currentMessageState[message.dataType].preFrameVideoData = Buffer.concat([this.currentMessageState[message.dataType].preFrameVideoData, video_data]);
1324
+ }
1325
+ }
1326
+ }
1327
+ else if (message.dataType === types_1.P2PDataType.BINARY) {
1328
+ (_b = this.currentMessageState[message.dataType].videoStream) === null || _b === void 0 ? void 0 : _b.push(video_data);
1329
+ }
1330
+ break;
1331
+ case types_1.CommandType.CMD_AUDIO_FRAME:
1332
+ this.waitForStreamData(message.dataType);
1333
+ const audioMetaData = {
1334
+ audioType: types_1.AudioCodec.NONE,
1335
+ audioSeqNo: 0,
1336
+ audioTimestamp: 0,
1337
+ audioDataLength: 0
1338
+ };
1339
+ audioMetaData.audioDataLength = message.data.slice(0, 4).readUInt32LE();
1340
+ audioMetaData.audioType = message.data.slice(5, 6).readUInt8();
1341
+ audioMetaData.audioSeqNo = message.data.slice(6, 8).readUInt16LE();
1342
+ audioMetaData.audioTimestamp = message.data.slice(8, 14).readUIntLE(0, 6);
1343
+ const audio_data = Buffer.from(message.data.slice(16));
1344
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_AUDIO_FRAME`, { dataSize: message.data.length, metadata: audioMetaData, audioDataSize: audio_data.length });
1345
+ if (!this.currentMessageState[message.dataType].p2pStreamFirstAudioDataReceived) {
1346
+ if (this.currentMessageState[message.dataType].waitForAudioData !== undefined) {
1347
+ clearTimeout(this.currentMessageState[message.dataType].waitForAudioData);
1348
+ }
1349
+ this.currentMessageState[message.dataType].p2pStreamFirstAudioDataReceived = true;
1350
+ this.currentMessageState[message.dataType].p2pStreamMetadata.audioCodec = audioMetaData.audioType === 0 ? types_1.AudioCodec.AAC : audioMetaData.audioType === 1 ? types_1.AudioCodec.AAC_LC : audioMetaData.audioType === 7 ? types_1.AudioCodec.AAC_ELD : types_1.AudioCodec.UNKNOWN;
1351
+ }
1352
+ if (this.currentMessageState[message.dataType].p2pStreamNotStarted) {
1353
+ if (this.currentMessageState[message.dataType].p2pStreamFirstAudioDataReceived && this.currentMessageState[message.dataType].p2pStreamFirstVideoDataReceived) {
1354
+ this.emitStreamStartEvent(message.dataType);
1355
+ }
1356
+ }
1357
+ (_c = this.currentMessageState[message.dataType].audioStream) === null || _c === void 0 ? void 0 : _c.push(audio_data);
1358
+ break;
1359
+ default:
1360
+ this.log.debug(`Station ${this.rawStation.station_sn} - Not implemented message`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, data: message.data.toString("hex") });
1361
+ break;
1362
+ }
1363
+ }
1364
+ else {
1365
+ this.log.debug(`Station ${this.rawStation.station_sn} - Invalid stream data, dropping complete stream`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, data: message.data.toString("hex") });
1366
+ }
1367
+ }
1368
+ handleDataControl(message) {
1369
+ try {
1370
+ switch (message.commandId) {
1371
+ case types_1.CommandType.CMD_GET_ALARM_MODE:
1372
+ this.log.debug(`Station ${this.rawStation.station_sn} - Alarm mode changed to: ${types_2.AlarmMode[message.data.readUIntBE(0, 1)]}`);
1373
+ this.emit("alarm mode", message.data.readUIntBE(0, 1));
1374
+ break;
1375
+ case types_1.CommandType.CMD_CAMERA_INFO:
1376
+ try {
1377
+ const data = message.data.toString("utf8");
1378
+ this.log.debug(`Station ${this.rawStation.station_sn} - Camera info`, { cameraInfo: data });
1379
+ this.emit("camera info", (0, utils_3.parseJSON)(data, this.log));
1380
+ }
1381
+ catch (error) {
1382
+ this.log.error(`Station ${this.rawStation.station_sn} - Camera info - Error:`, error);
1383
+ }
1384
+ break;
1385
+ case types_1.CommandType.CMD_CONVERT_MP4_OK:
1386
+ const totalBytes = message.data.slice(1).readUInt32LE();
1387
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_CONVERT_MP4_OK`, { channel: message.channel, totalBytes: totalBytes });
1388
+ this.downloadTotalBytes = totalBytes;
1389
+ this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreaming = true;
1390
+ this.currentMessageState[types_1.P2PDataType.BINARY].p2pStreamChannel = message.channel;
1391
+ break;
1392
+ case types_1.CommandType.CMD_WIFI_CONFIG:
1393
+ const rssi = message.data.readInt32LE();
1394
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_WIFI_CONFIG`, { channel: message.channel, rssi: rssi });
1395
+ this.emit("wifi rssi", message.channel, rssi);
1396
+ break;
1397
+ case types_1.CommandType.CMD_DOWNLOAD_FINISH:
1398
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_DOWNLOAD_FINISH`, { channel: message.channel });
1399
+ this.endStream(types_1.P2PDataType.BINARY);
1400
+ break;
1401
+ case types_1.CommandType.CMD_DOORBELL_NOTIFY_PAYLOAD:
1402
+ try {
1403
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_DOORBELL_NOTIFY_PAYLOAD`, { payload: message.data.toString() });
1404
+ //TODO: Finish implementation, emit an event...
1405
+ //VDBStreamInfo (1005) and VoltageEvent (1015)
1406
+ //this.emit("", parseJSON(message.data.toString(), this.log) as xy);
1407
+ }
1408
+ catch (error) {
1409
+ this.log.error(`Station ${this.rawStation.station_sn} - CMD_DOORBELL_NOTIFY_PAYLOAD - Error:`, error);
1410
+ }
1411
+ break;
1412
+ case types_1.CommandType.CMD_NAS_SWITCH:
1413
+ try {
1414
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NAS_SWITCH`, { payload: message.data.toString() });
1415
+ this.emit("rtsp url", message.channel, message.data.toString("utf8", 0, message.data.indexOf("\0", 0, "utf8")));
1416
+ }
1417
+ catch (error) {
1418
+ this.log.error(`Station ${this.rawStation.station_sn} - CMD_NAS_SWITCH - Error:`, error);
1419
+ }
1420
+ break;
1421
+ case types_1.CommandType.SUB1G_REP_UNPLUG_POWER_LINE:
1422
+ try {
1423
+ this.log.debug(`Station ${this.rawStation.station_sn} - SUB1G_REP_UNPLUG_POWER_LINE`, { payload: message.data.toString() });
1424
+ const chargeType = message.data.slice(0, 4).readUInt32LE();
1425
+ const batteryLevel = message.data.slice(4, 8).readUInt32LE();
1426
+ this.emit("charging state", message.channel, chargeType, batteryLevel);
1427
+ }
1428
+ catch (error) {
1429
+ this.log.error(`Station ${this.rawStation.station_sn} - SUB1G_REP_UNPLUG_POWER_LINE - Error:`, error);
1430
+ }
1431
+ break;
1432
+ case types_1.CommandType.SUB1G_REP_RUNTIME_STATE:
1433
+ try {
1434
+ this.log.debug(`Station ${this.rawStation.station_sn} - SUB1G_REP_RUNTIME_STATE`, { payload: message.data.toString() });
1435
+ const batteryLevel = message.data.slice(0, 4).readUInt32LE();
1436
+ const temperature = message.data.slice(4, 8).readUInt32LE();
1437
+ this.emit("runtime state", message.channel, batteryLevel, temperature);
1438
+ }
1439
+ catch (error) {
1440
+ this.log.error(`Station ${this.rawStation.station_sn} - SUB1G_REP_RUNTIME_STATE - Error:`, error);
1441
+ }
1442
+ break;
1443
+ case types_1.CommandType.CMD_SET_FLOODLIGHT_MANUAL_SWITCH:
1444
+ try {
1445
+ const enabled = message.data.readUIntBE(0, 1) === 1 ? true : false;
1446
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_SET_FLOODLIGHT_MANUAL_SWITCH`, { enabled: enabled, payload: message.data.toString() });
1447
+ this.emit("floodlight manual switch", message.channel, enabled);
1448
+ }
1449
+ catch (error) {
1450
+ this.log.error(`Station ${this.rawStation.station_sn} - CMD_SET_FLOODLIGHT_MANUAL_SWITCH - Error:`, error);
1451
+ }
1452
+ break;
1453
+ case types_1.CommandType.CMD_GET_DEVICE_PING:
1454
+ try {
1455
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_GET_DEVICE_PING`, { payload: message.data.toString() });
1456
+ this.sendCommandDevicePing(message.channel);
1457
+ }
1458
+ catch (error) {
1459
+ this.log.error(`Station ${this.rawStation.station_sn} - CMD_GET_DEVICE_PING - Error:`, error);
1460
+ }
1461
+ break;
1462
+ case types_1.CommandType.CMD_NOTIFY_PAYLOAD:
1463
+ try {
1464
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD`, { payload: message.data.toString() });
1465
+ const json = (0, utils_3.parseJSON)(message.data.toString("utf-8"), this.log);
1466
+ if (json !== undefined) {
1467
+ if (this.rawStation.station_sn.startsWith("T8520")) {
1468
+ //TODO: Implement notification payload or T8520
1469
+ if (json.cmd === types_1.CommandType.P2P_ADD_PW || json.cmd === types_1.CommandType.P2P_QUERY_PW || json.cmd === types_1.CommandType.P2P_GET_LOCK_PARAM || json.cmd === types_1.CommandType.P2P_GET_USER_AND_PW_ID) {
1470
+ // encrypted data
1471
+ //TODO: Handle decryption of encrypted Data (AES) - For decryption use the cached aeskey used for sending the command!
1472
+ const aesKey = this.getLockAESKey(json.cmd);
1473
+ if (aesKey !== undefined) {
1474
+ const decryptedPayload = (0, utils_1.decryptPayloadData)(Buffer.from(json.payload, "base64"), Buffer.from(aesKey, "hex"), Buffer.from((0, utils_1.getLockVectorBytes)(this.rawStation.station_sn), "hex")).toString();
1475
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD Lock - Received`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, decryptedPayload: decryptedPayload, aesKey: aesKey });
1476
+ switch (json.cmd) {
1477
+ case types_1.CommandType.P2P_ADD_PW:
1478
+ // decryptedPayload: {"code":0,"passwordId":"002C"}
1479
+ break;
1480
+ }
1481
+ }
1482
+ }
1483
+ else if (json.cmd === types_1.CommandType.P2P_QUERY_STATUS_IN_LOCK) {
1484
+ // Example: {"code":0,"slBattery":"82","slState":"4","trigger":2}
1485
+ const payload = json.payload;
1486
+ this.emit("parameter", message.channel, types_1.CommandType.CMD_SMARTLOCK_QUERY_BATTERY_LEVEL, payload.slBattery);
1487
+ this.emit("parameter", message.channel, types_1.CommandType.CMD_SMARTLOCK_QUERY_STATUS, payload.slState);
1488
+ }
1489
+ else {
1490
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD - Not implemented`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, message: message.data.toString() });
1491
+ }
1492
+ }
1493
+ else if (json.cmd === types_1.CommandType.CMD_DOORLOCK_P2P_SEQ) {
1494
+ const payload = json.payload;
1495
+ switch (payload.lock_cmd) {
1496
+ case 0:
1497
+ if (payload.seq_num !== undefined) {
1498
+ this.lockSeqNumber = payload.seq_num;
1499
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD - Lock sequence number`, { lockSeqNumber: this.lockSeqNumber });
1500
+ }
1501
+ break;
1502
+ default:
1503
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD - Not implemented`, { message: message.data.toString() });
1504
+ break;
1505
+ }
1506
+ }
1507
+ else if (json.cmd === types_1.CommandType.CMD_DOORLOCK_DATA_PASS_THROUGH) {
1508
+ const payload = json.payload;
1509
+ if (this.deviceSNs[message.channel] !== undefined) {
1510
+ if (payload.lock_payload !== undefined) {
1511
+ const decoded = (0, utils_1.decodeBase64)((0, utils_1.decodeLockPayload)(Buffer.from(payload.lock_payload)));
1512
+ const key = (0, utils_1.generateBasicLockAESKey)(this.deviceSNs[message.channel].adminUserId, this.rawStation.station_sn);
1513
+ const iv = (0, utils_1.getLockVectorBytes)(this.rawStation.station_sn);
1514
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_DOORLOCK_DATA_PASS_THROUGH`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, key: key, iv: iv, decoded: decoded.toString("hex") });
1515
+ payload.lock_payload = (0, utils_1.decryptLockAESData)(key, iv, decoded).toString("hex");
1516
+ switch (payload.lock_cmd) {
1517
+ case types_1.ESLBleCommand.NOTIFY:
1518
+ const notifyBuffer = Buffer.from(payload.lock_payload, "hex");
1519
+ this.emit("parameter", message.channel, types_1.CommandType.CMD_GET_BATTERY, notifyBuffer.slice(3, 4).readInt8().toString());
1520
+ this.emit("parameter", message.channel, types_1.CommandType.CMD_DOORLOCK_GET_STATE, notifyBuffer.slice(6, 7).readInt8().toString());
1521
+ break;
1522
+ default:
1523
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_DOORLOCK_DATA_PASS_THROUGH - Not implemented`, { message: message.data.toString() });
1524
+ break;
1525
+ }
1526
+ }
1527
+ }
1528
+ }
1529
+ else if (json.cmd === types_1.CommandType.CMD_SET_PAYLOAD_LOCKV12) {
1530
+ const payload = json.payload;
1531
+ if (payload.lock_payload !== undefined) {
1532
+ const fac = new ble_1.BleCommandFactory(payload.lock_payload);
1533
+ if (fac.getCommandCode() !== types_1.ESLBleCommand.NOTIFY) {
1534
+ const aesKey = this.getLockAESKey(fac.getCommandCode());
1535
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD Lock V12 - Received`, { fac: fac.toString(), aesKey: aesKey });
1536
+ let data = fac.getData();
1537
+ if (aesKey !== undefined) {
1538
+ data = (0, utils_1.decryptPayloadData)(data, Buffer.from(aesKey, "hex"), Buffer.from((0, utils_1.getLockVectorBytes)(this.rawStation.station_sn), "hex"));
1539
+ }
1540
+ const returnCode = data.readInt8(0);
1541
+ if (this.lastChannel !== undefined && this.lastCustomData !== undefined) {
1542
+ const result = {
1543
+ channel: this.lastChannel,
1544
+ command_type: Number.parseInt(types_1.ESLCommand[types_1.ESLBleCommand[fac.getCommandCode()]]),
1545
+ return_code: returnCode,
1546
+ customData: this.lastCustomData
1547
+ };
1548
+ this.emit("secondary command", result);
1549
+ }
1550
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD Lock V12 return code: ${returnCode}`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, decoded: data, bleCommandCode: types_1.ESLBleCommand[fac.getCommandCode()], returnCode: returnCode, channel: this.lastChannel, customData: this.lastCustomData });
1551
+ this._clearSecondaryCommandTimeout();
1552
+ this.sendQueuedMessage();
1553
+ }
1554
+ else {
1555
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD Lock V12 - Received notify`, { fac: fac.toString() });
1556
+ }
1557
+ }
1558
+ else {
1559
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD Lock V12 - Unexpected response`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, message: message.data.toString() });
1560
+ }
1561
+ }
1562
+ else if (device_1.Device.isSmartSafe(this.rawStation.device_type)) {
1563
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD SmartSafe`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd });
1564
+ switch (json.cmd) {
1565
+ case types_1.CommandType.CMD_SMARTSAFE_SETTINGS:
1566
+ {
1567
+ const payload = json.payload;
1568
+ try {
1569
+ const data = (0, utils_1.decodeSmartSafeData)(this.rawStation.station_sn, Buffer.from(payload.data, "hex"));
1570
+ const returnCode = data.data.readInt8(0);
1571
+ if (this.lastChannel !== undefined && this.lastCustomData !== undefined) {
1572
+ const result = {
1573
+ channel: this.lastChannel,
1574
+ command_type: payload.prj_id,
1575
+ return_code: returnCode,
1576
+ customData: this.lastCustomData
1577
+ };
1578
+ this.emit("secondary command", result);
1579
+ }
1580
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD SmartSafe return code: ${data.data.readInt8(0)}`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, decoded: data, commandCode: types_1.SmartSafeCommandCode[data.commandCode], returnCode: returnCode, channel: this.lastChannel, customData: this.lastCustomData });
1581
+ }
1582
+ catch (error) {
1583
+ this.log.error(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD SmartSafe Error:`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, channel: this.lastChannel, customData: this.lastCustomData, payload: payload, error: error });
1584
+ }
1585
+ this._clearSecondaryCommandTimeout();
1586
+ this.sendQueuedMessage();
1587
+ break;
1588
+ }
1589
+ case types_1.CommandType.CMD_SMARTSAFE_STATUS_UPDATE:
1590
+ {
1591
+ const payload = json.payload;
1592
+ switch (payload.event_type) {
1593
+ case types_3.SmartSafeEvent.LOCK_STATUS:
1594
+ {
1595
+ const eventValues = payload.event_value;
1596
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD SmartSafe Status update - LOCK_STATUS`, { eventValues: eventValues });
1597
+ /*
1598
+ type values:
1599
+ 1: Unlocked by PIN
1600
+ 2: Unlocked by User
1601
+ 3: Unlocked by key
1602
+ 4: Unlocked by App
1603
+ 5: Unlocked by Dual Unlock
1604
+ */
1605
+ if (eventValues.action === 0) {
1606
+ this.emit("parameter", message.channel, types_1.CommandType.CMD_SMARTSAFE_LOCK_STATUS, "0");
1607
+ }
1608
+ else if (eventValues.action === 1) {
1609
+ this.emit("parameter", message.channel, types_1.CommandType.CMD_SMARTSAFE_LOCK_STATUS, "1");
1610
+ }
1611
+ else if (eventValues.action === 2) {
1612
+ this.emit("jammed", message.channel);
1613
+ }
1614
+ else if (eventValues.action === 3) {
1615
+ this.emit("low battery", message.channel);
1616
+ }
1617
+ break;
1618
+ }
1619
+ case types_3.SmartSafeEvent.SHAKE_ALARM:
1620
+ this.emit("shake alarm", message.channel, payload.event_value);
1621
+ break;
1622
+ case types_3.SmartSafeEvent.ALARM_911:
1623
+ this.emit("911 alarm", message.channel, payload.event_value);
1624
+ break;
1625
+ //case SmartSafeEvent.BATTERY_STATUS:
1626
+ // break;
1627
+ case types_3.SmartSafeEvent.INPUT_ERR_MAX:
1628
+ this.emit("wrong try-protect alarm", message.channel);
1629
+ break;
1630
+ default:
1631
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD SmartSafe Status update - Not implemented`, { message: message.data.toString() });
1632
+ break;
1633
+ }
1634
+ break;
1635
+ }
1636
+ default:
1637
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD SmartSafe - Not implemented`, { message: message.data.toString() });
1638
+ break;
1639
+ }
1640
+ }
1641
+ else {
1642
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD - Not implemented`, { commandIdName: types_1.CommandType[json.cmd], commandId: json.cmd, message: message.data.toString() });
1643
+ }
1644
+ }
1645
+ }
1646
+ catch (error) {
1647
+ this.log.error(`Station ${this.rawStation.station_sn} - CMD_NOTIFY_PAYLOAD Error:`, { error: error, payload: message.data.toString() });
1648
+ }
1649
+ break;
1650
+ case types_1.CommandType.CMD_GET_DELAY_ALARM:
1651
+ try {
1652
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_GET_DELAY_ALARM :`, { payload: message.data.toString("hex") });
1653
+ //When the alarm is armed, CMD_GET_DELAY_ALARM is called with event data 0, so ignore it
1654
+ const alarmEventNumber = message.data.slice(0, 4).readUInt32LE();
1655
+ const alarmDelay = message.data.slice(4, 8).readUInt32LE();
1656
+ if (alarmEventNumber === 0) {
1657
+ this.emit("alarm armed");
1658
+ }
1659
+ else {
1660
+ this.emit("alarm delay", alarmEventNumber, alarmDelay);
1661
+ }
1662
+ }
1663
+ catch (error) {
1664
+ this.log.error(`Station ${this.rawStation.station_sn} - CMD_GET_DELAY_ALARM - Error:`, { error: error, payload: message.data.toString("hex") });
1665
+ }
1666
+ break;
1667
+ case types_1.CommandType.CMD_SET_TONE_FILE:
1668
+ try {
1669
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_SET_TONE_FILE :`, { payload: message.data.toString("hex") });
1670
+ const alarmEventNumber = message.data.slice(0, 4).readUInt32LE();
1671
+ this.emit("alarm event", alarmEventNumber);
1672
+ }
1673
+ catch (error) {
1674
+ this.log.error(`Station ${this.rawStation.station_sn} - CMD_SET_TONE_FILE - Error:`, { error: error, payload: message.data.toString("hex") });
1675
+ }
1676
+ break;
1677
+ case types_1.CommandType.CMD_SET_SNOOZE_MODE:
1678
+ // Received for station managed devices when snooze time ends
1679
+ try {
1680
+ this.log.debug(`Station ${this.rawStation.station_sn} - CMD_SET_SNOOZE_MODE`, { payload: Buffer.from(message.data.toString(), "base64").toString() });
1681
+ this.emit("parameter", message.channel, types_1.CommandType.CMD_SET_SNOOZE_MODE, message.data.toString());
1682
+ }
1683
+ catch (error) {
1684
+ this.log.error(`Station ${this.rawStation.station_sn} - CMD_SET_SNOOZE_MODE - Error:`, error);
1685
+ }
1686
+ break;
1687
+ case types_1.CommandType.CMD_PING:
1688
+ // Ignore
1689
+ break;
1690
+ default:
1691
+ this.log.debug(`Station ${this.rawStation.station_sn} - Not implemented - CONTROL message`, { commandIdName: types_1.CommandType[message.commandId], commandId: message.commandId, channel: message.channel, data: message.data.toString("hex") });
1692
+ break;
1693
+ }
1694
+ }
1695
+ catch (error) {
1696
+ this.log.error(`Station ${this.rawStation.station_sn} - ${types_1.CommandType[message.commandId]} - Error:`, error);
1697
+ }
1698
+ }
1699
+ async sendAck(address, dataType, seqNo) {
1700
+ const num_pending_acks = 1; // Max possible: 17 in one ack packet
1701
+ const pendingAcksBuffer = Buffer.allocUnsafe(2);
1702
+ pendingAcksBuffer.writeUInt16BE(num_pending_acks, 0);
1703
+ const seqBuffer = Buffer.allocUnsafe(2);
1704
+ seqBuffer.writeUInt16BE(seqNo, 0);
1705
+ const payload = Buffer.concat([dataType, pendingAcksBuffer, seqBuffer]);
1706
+ await this.sendMessage(`Send ack to station ${this.rawStation.station_sn}`, address, types_1.RequestMessageType.ACK, payload);
1707
+ }
1708
+ getDataType(input) {
1709
+ if (input.compare(types_1.P2PDataTypeHeader.DATA) === 0) {
1710
+ return types_1.P2PDataType.DATA;
1711
+ }
1712
+ else if (input.compare(types_1.P2PDataTypeHeader.VIDEO) === 0) {
1713
+ return types_1.P2PDataType.VIDEO;
1714
+ }
1715
+ else if (input.compare(types_1.P2PDataTypeHeader.CONTROL) === 0) {
1716
+ return types_1.P2PDataType.CONTROL;
1717
+ }
1718
+ else if (input.compare(types_1.P2PDataTypeHeader.BINARY) === 0) {
1719
+ return types_1.P2PDataType.BINARY;
1720
+ }
1721
+ return types_1.P2PDataType.UNKNOWN;
1722
+ }
1723
+ async close() {
1724
+ this.terminating = true;
1725
+ this._clearLookupTimeout();
1726
+ this._clearLookupRetryTimeout();
1727
+ this._clearConnectTimeout();
1728
+ this._clearHeartbeatTimeout();
1729
+ this._clearMessageStateTimeouts();
1730
+ this._clearMessageVideoStateTimeouts();
1731
+ this._clearSecondaryCommandTimeout();
1732
+ this.sendQueue = [];
1733
+ if (this.socket) {
1734
+ if (this.connected) {
1735
+ await this.sendMessage(`Send end to station ${this.rawStation.station_sn}`, this.connectAddress, types_1.RequestMessageType.END);
1736
+ this._disconnected();
1737
+ }
1738
+ else {
1739
+ this._initialize();
1740
+ }
1741
+ }
1742
+ }
1743
+ getHeartbeatInterval() {
1744
+ return this.HEARTBEAT_INTERVAL;
1745
+ }
1746
+ onClose() {
1747
+ this.socket.removeAllListeners();
1748
+ this.socket = (0, dgram_1.createSocket)("udp4");
1749
+ this.socket.on("message", (msg, rinfo) => this.handleMsg(msg, rinfo));
1750
+ this.socket.on("error", (error) => this.onError(error));
1751
+ this.socket.on("close", () => this.onClose());
1752
+ this.binded = false;
1753
+ this._disconnected();
1754
+ }
1755
+ onError(error) {
1756
+ this.log.debug(`Station ${this.rawStation.station_sn} - Error:`, error);
1757
+ }
1758
+ scheduleHeartbeat() {
1759
+ if (this.isConnected()) {
1760
+ this.sendPing(this.connectAddress);
1761
+ this.heartbeatTimeout = setTimeout(() => {
1762
+ this.scheduleHeartbeat();
1763
+ }, this.getHeartbeatInterval());
1764
+ }
1765
+ else {
1766
+ this.log.debug(`Station ${this.rawStation.station_sn} - Heartbeat disabled!`);
1767
+ }
1768
+ }
1769
+ scheduleP2PKeepalive() {
1770
+ if (this.isConnected()) {
1771
+ this.sendCommandPing();
1772
+ this.keepaliveTimeout = setTimeout(() => {
1773
+ this.scheduleP2PKeepalive();
1774
+ }, this.KEEPALIVE_INTERVAL);
1775
+ this.closeEnergySavingDevice();
1776
+ }
1777
+ else {
1778
+ this.log.debug(`Station ${this.rawStation.station_sn} - p2p keepalive disabled!`);
1779
+ }
1780
+ }
1781
+ getDownloadRSAPrivateKey() {
1782
+ if (this.currentMessageState[types_1.P2PDataType.BINARY].rsaKey === null) {
1783
+ this.currentMessageState[types_1.P2PDataType.BINARY].rsaKey = (0, utils_1.getNewRSAPrivateKey)();
1784
+ }
1785
+ return this.currentMessageState[types_1.P2PDataType.BINARY].rsaKey;
1786
+ }
1787
+ setDownloadRSAPrivateKeyPem(pem) {
1788
+ this.currentMessageState[types_1.P2PDataType.BINARY].rsaKey = (0, utils_1.getRSAPrivateKey)(pem);
1789
+ }
1790
+ getRSAPrivateKey() {
1791
+ return this.currentMessageState[types_1.P2PDataType.VIDEO].rsaKey;
1792
+ }
1793
+ initializeStream(datatype) {
1794
+ var _a, _b;
1795
+ (_a = this.currentMessageState[datatype].videoStream) === null || _a === void 0 ? void 0 : _a.destroy();
1796
+ (_b = this.currentMessageState[datatype].audioStream) === null || _b === void 0 ? void 0 : _b.destroy();
1797
+ this.currentMessageState[datatype].videoStream = null;
1798
+ this.currentMessageState[datatype].audioStream = null;
1799
+ this.currentMessageState[datatype].videoStream = new stream_1.Readable({ autoDestroy: true,
1800
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
1801
+ read() { } /*,
1802
+
1803
+ destroy(this, error, _callback) {
1804
+ if (error) {
1805
+ this.emit("error", error);
1806
+ }
1807
+ this.emit("end");
1808
+ this.emit("close");
1809
+ }*/
1810
+ });
1811
+ this.currentMessageState[datatype].audioStream = new stream_1.Readable({ autoDestroy: true,
1812
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
1813
+ read() { } /*,
1814
+
1815
+ destroy(this, error, _callback) {
1816
+ if (error) {
1817
+ this.emit("error", error);
1818
+ }
1819
+ this.emit("end");
1820
+ this.emit("close");
1821
+ }*/
1822
+ });
1823
+ this.currentMessageState[datatype].p2pStreaming = false;
1824
+ if (this.currentMessageState[datatype].waitForSeqNoTimeout !== undefined) {
1825
+ clearTimeout(this.currentMessageState[datatype].waitForSeqNoTimeout);
1826
+ this.currentMessageState[datatype].waitForSeqNoTimeout = undefined;
1827
+ }
1828
+ if (this.currentMessageState[datatype].waitForAudioData !== undefined) {
1829
+ clearTimeout(this.currentMessageState[datatype].waitForAudioData);
1830
+ this.currentMessageState[datatype].waitForAudioData = undefined;
1831
+ }
1832
+ }
1833
+ endStream(datatype, force = false) {
1834
+ var _a, _b;
1835
+ if (this.currentMessageState[datatype].p2pStreaming) {
1836
+ if (force) {
1837
+ switch (datatype) {
1838
+ case types_1.P2PDataType.VIDEO:
1839
+ this.sendCommandWithInt({
1840
+ commandType: types_1.CommandType.CMD_STOP_REALTIME_MEDIA,
1841
+ value: this.currentMessageState[datatype].p2pStreamChannel,
1842
+ channel: this.currentMessageState[datatype].p2pStreamChannel
1843
+ }, {
1844
+ command: {
1845
+ name: http_1.CommandName.DeviceStopLivestream
1846
+ }
1847
+ });
1848
+ break;
1849
+ case types_1.P2PDataType.BINARY:
1850
+ this.sendCommandWithInt({
1851
+ commandType: types_1.CommandType.CMD_DOWNLOAD_CANCEL,
1852
+ value: this.currentMessageState[datatype].p2pStreamChannel,
1853
+ strValueSub: this.rawStation.member.admin_user_id,
1854
+ channel: this.currentMessageState[datatype].p2pStreamChannel
1855
+ }, {
1856
+ command: {
1857
+ name: http_1.CommandName.DeviceCancelDownload
1858
+ }
1859
+ });
1860
+ break;
1861
+ }
1862
+ }
1863
+ this.currentMessageState[datatype].p2pStreaming = false;
1864
+ (_a = this.currentMessageState[datatype].videoStream) === null || _a === void 0 ? void 0 : _a.push(null);
1865
+ (_b = this.currentMessageState[datatype].audioStream) === null || _b === void 0 ? void 0 : _b.push(null);
1866
+ if (this.currentMessageState[datatype].p2pStreamingTimeout) {
1867
+ clearTimeout(this.currentMessageState[datatype].p2pStreamingTimeout);
1868
+ this.currentMessageState[datatype].p2pStreamingTimeout = undefined;
1869
+ }
1870
+ if (!this.currentMessageState[datatype].invalidStream && !this.currentMessageState[datatype].p2pStreamNotStarted)
1871
+ this.emitStreamStopEvent(datatype);
1872
+ if (this.currentMessageState[datatype].queuedData.size > 0) {
1873
+ this.expectedSeqNo[datatype] = this._incrementSequence([...this.currentMessageState[datatype].queuedData.keys()][this.currentMessageState[datatype].queuedData.size - 1]);
1874
+ }
1875
+ this.initializeMessageBuilder(datatype);
1876
+ this.initializeMessageState(datatype, this.currentMessageState[datatype].rsaKey);
1877
+ this.initializeStream(datatype);
1878
+ this.closeEnergySavingDevice();
1879
+ }
1880
+ }
1881
+ endRTSPStream(channel) {
1882
+ if (this.currentMessageState[types_1.P2PDataType.DATA].rtspStreaming[channel]) {
1883
+ this.currentMessageState[types_1.P2PDataType.DATA].rtspStream[channel] = false;
1884
+ this.currentMessageState[types_1.P2PDataType.DATA].rtspStreaming[channel] = false;
1885
+ this.emit("rtsp livestream stopped", channel);
1886
+ }
1887
+ }
1888
+ emitStreamStartEvent(datatype) {
1889
+ this.currentMessageState[datatype].p2pStreamNotStarted = false;
1890
+ if (datatype === types_1.P2PDataType.VIDEO) {
1891
+ this.emit("livestream started", this.currentMessageState[datatype].p2pStreamChannel, this.currentMessageState[datatype].p2pStreamMetadata, this.currentMessageState[datatype].videoStream, this.currentMessageState[datatype].audioStream);
1892
+ }
1893
+ else if (datatype === types_1.P2PDataType.BINARY) {
1894
+ this.emit("download started", this.currentMessageState[datatype].p2pStreamChannel, this.currentMessageState[datatype].p2pStreamMetadata, this.currentMessageState[datatype].videoStream, this.currentMessageState[datatype].audioStream);
1895
+ }
1896
+ }
1897
+ emitStreamStopEvent(datatype) {
1898
+ if (datatype === types_1.P2PDataType.VIDEO) {
1899
+ this.emit("livestream stopped", this.currentMessageState[datatype].p2pStreamChannel);
1900
+ }
1901
+ else if (datatype === types_1.P2PDataType.BINARY) {
1902
+ this.emit("download finished", this.currentMessageState[datatype].p2pStreamChannel);
1903
+ }
1904
+ }
1905
+ isStreaming(channel, datatype) {
1906
+ if (this.currentMessageState[datatype].p2pStreamChannel === channel)
1907
+ return this.currentMessageState[datatype].p2pStreaming;
1908
+ return false;
1909
+ }
1910
+ isLiveStreaming(channel) {
1911
+ return this.isStreaming(channel, types_1.P2PDataType.VIDEO);
1912
+ }
1913
+ isCurrentlyStreaming() {
1914
+ for (const element of Object.values(this.currentMessageState)) {
1915
+ if (element.p2pStreaming || element.p2pTalkback)
1916
+ return true;
1917
+ }
1918
+ return false;
1919
+ }
1920
+ isRTSPLiveStreaming(channel) {
1921
+ return this.currentMessageState[types_1.P2PDataType.DATA].rtspStreaming[channel] ? this.currentMessageState[types_1.P2PDataType.DATA].rtspStreaming[channel] : false;
1922
+ }
1923
+ isDownloading(channel) {
1924
+ return this.isStreaming(channel, types_1.P2PDataType.BINARY);
1925
+ }
1926
+ getLockSequenceNumber() {
1927
+ if (this.lockSeqNumber === -1)
1928
+ this.lockSeqNumber = (0, utils_1.generateLockSequence)(this.rawStation.devices[0].device_type);
1929
+ return this.lockSeqNumber;
1930
+ }
1931
+ incLockSequenceNumber() {
1932
+ if (this.lockSeqNumber === -1)
1933
+ this.lockSeqNumber = (0, utils_1.generateLockSequence)(this.rawStation.devices[0].device_type);
1934
+ else
1935
+ this.lockSeqNumber++;
1936
+ return this.lockSeqNumber;
1937
+ }
1938
+ setConnectionType(type) {
1939
+ this.connectionType = type;
1940
+ }
1941
+ getConnectionType() {
1942
+ return this.connectionType;
1943
+ }
1944
+ isEnergySavingDevice() {
1945
+ return this.energySavingDevice;
1946
+ }
1947
+ async getDSKKeys() {
1948
+ if (this.api.isConnected()) {
1949
+ try {
1950
+ const data = {
1951
+ invalid_dsks: {},
1952
+ station_sns: [this.rawStation.station_sn],
1953
+ transaction: `${new Date().getTime()}`
1954
+ };
1955
+ data.invalid_dsks[this.rawStation.station_sn] = "";
1956
+ const response = await this.api.request({
1957
+ method: "post",
1958
+ endpoint: "v1/app/equipment/get_dsk_keys",
1959
+ data: data
1960
+ });
1961
+ this.log.debug(`Station ${this.rawStation.station_sn} - Response:`, response.data);
1962
+ if (response.status == 200) {
1963
+ const result = response.data;
1964
+ if (result.code == 0) {
1965
+ const dataresult = result.data;
1966
+ dataresult.dsk_keys.forEach(key => {
1967
+ if (key.station_sn == this.rawStation.station_sn) {
1968
+ this.dskKey = key.dsk_key;
1969
+ this.dskExpiration = new Date(key.expiration * 1000);
1970
+ this.log.debug(`${this.constructor.name}.getDSKKeys(): dskKey: ${this.dskKey} dskExpiration: ${this.dskExpiration}`);
1971
+ }
1972
+ });
1973
+ }
1974
+ else {
1975
+ this.log.error(`Station ${this.rawStation.station_sn} - Response code not ok`, { code: result.code, msg: result.msg });
1976
+ }
1977
+ }
1978
+ else {
1979
+ this.log.error(`Station ${this.rawStation.station_sn} - Status return code not 200`, { status: response.status, statusText: response.statusText });
1980
+ }
1981
+ }
1982
+ catch (error) {
1983
+ this.log.error(`Station ${this.rawStation.station_sn} - Generic Error:`, error);
1984
+ }
1985
+ }
1986
+ }
1987
+ updateRawStation(value) {
1988
+ var _a;
1989
+ this.rawStation = value;
1990
+ this.channel = http_1.Station.getChannel(value.device_type);
1991
+ if (((_a = this.rawStation.devices) === null || _a === void 0 ? void 0 : _a.length) > 0) {
1992
+ if (!this.energySavingDevice) {
1993
+ for (const device of this.rawStation.devices) {
1994
+ if (device.device_sn === this.rawStation.station_sn && device_1.Device.hasBattery(device.device_type)) {
1995
+ this.energySavingDevice = true;
1996
+ break;
1997
+ }
1998
+ }
1999
+ if (this.energySavingDevice)
2000
+ this.log.debug(`Identified standalone battery device ${this.rawStation.station_sn} => activate p2p keepalive command`);
2001
+ }
2002
+ }
2003
+ else {
2004
+ this.energySavingDevice = false;
2005
+ }
2006
+ if (this.rawStation.devices)
2007
+ for (const device of this.rawStation.devices) {
2008
+ this.deviceSNs[device.device_channel] = {
2009
+ sn: device.device_sn,
2010
+ adminUserId: this.rawStation.member.admin_user_id
2011
+ };
2012
+ }
2013
+ }
2014
+ initializeTalkbackStream(channel = 0) {
2015
+ this.talkbackStream = new talkback_1.TalkbackStream();
2016
+ this.talkbackStream.on("data", (audioData) => { this.sendTalkbackAudioFrame(audioData, channel); });
2017
+ this.talkbackStream.on("error", (error) => { this.onTalkbackStreamError(error); });
2018
+ this.talkbackStream.on("close", () => { this.onTalkbackStreamClose(); });
2019
+ }
2020
+ sendTalkbackAudioFrame(audioData, channel) {
2021
+ const messageHeader = (0, utils_1.buildCommandHeader)(this.videoSeqNumber, types_1.CommandType.CMD_AUDIO_FRAME, types_1.P2PDataTypeHeader.VIDEO);
2022
+ const messageAudioHeader = (0, utils_1.buildTalkbackAudioFrameHeader)(audioData, channel);
2023
+ const messageData = Buffer.concat([messageHeader, messageAudioHeader, audioData]);
2024
+ const message = {
2025
+ sequence: this.videoSeqNumber,
2026
+ channel: channel,
2027
+ data: messageData,
2028
+ retries: 0
2029
+ };
2030
+ this.videoSeqNumber = this._incrementSequence(this.videoSeqNumber);
2031
+ this._sendVideoData(message);
2032
+ }
2033
+ onTalkbackStreamClose() {
2034
+ var _a;
2035
+ (_a = this.talkbackStream) === null || _a === void 0 ? void 0 : _a.removeAllListeners();
2036
+ }
2037
+ onTalkbackStreamError(error) {
2038
+ this.log.debug(`Station ${this.rawStation.station_sn} - Talkback Error:`, error);
2039
+ }
2040
+ async _sendVideoData(message) {
2041
+ var _a, _b;
2042
+ if (message.retries < this.MAX_RETRIES) {
2043
+ message.retries++;
2044
+ }
2045
+ else {
2046
+ this.log.error(`Station ${this.rawStation.station_sn} - Max send video data retries ${(_a = this.messageVideoStates.get(message.sequence)) === null || _a === void 0 ? void 0 : _a.retries} reached. Discard data.`, { sequence: message.sequence, channel: message.channel, retries: message.retries });
2047
+ this.messageVideoStates.delete(message.sequence);
2048
+ this.emit("talkback error", message.channel, new error_1.TalkbackError(`Station ${this.rawStation.station_sn} max send video data retries ${(_b = this.messageVideoStates.get(message.sequence)) === null || _b === void 0 ? void 0 : _b.retries} reached. Discard data packet.`));
2049
+ return;
2050
+ }
2051
+ message = message;
2052
+ message.timeout = setTimeout(() => {
2053
+ this._sendVideoData(message);
2054
+ }, this.MAX_AKNOWLEDGE_TIMEOUT);
2055
+ this.messageVideoStates.set(message.sequence, message);
2056
+ this.log.debug("Sending p2p video data...", { station: this.rawStation.station_sn, sequence: message.sequence, channel: message.channel, retries: message.retries, messageVideoStatesSize: this.messageVideoStates.size });
2057
+ await this.sendMessage(`Send p2p video data to station ${this.rawStation.station_sn}`, this.connectAddress, types_1.RequestMessageType.DATA, message.data);
2058
+ }
2059
+ isTalkbackOngoing(channel) {
2060
+ if (this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkbackChannel === channel)
2061
+ return this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkback;
2062
+ return false;
2063
+ }
2064
+ startTalkback(channel = 0) {
2065
+ var _a;
2066
+ this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkback = true;
2067
+ this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkbackChannel = channel;
2068
+ this.initializeTalkbackStream(channel);
2069
+ (_a = this.talkbackStream) === null || _a === void 0 ? void 0 : _a.startTalkback();
2070
+ this.emit("talkback started", channel, this.talkbackStream);
2071
+ }
2072
+ stopTalkback(channel = 0) {
2073
+ var _a;
2074
+ this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkback = false;
2075
+ this.currentMessageState[types_1.P2PDataType.VIDEO].p2pTalkbackChannel = -1;
2076
+ (_a = this.talkbackStream) === null || _a === void 0 ? void 0 : _a.stopTalkback();
2077
+ this.emit("talkback stopped", channel);
2078
+ this.closeEnergySavingDevice();
2079
+ }
2080
+ setLockAESKey(commandCode, aesKey) {
2081
+ this.lockAESKeys.set(commandCode, aesKey);
2082
+ }
2083
+ getLockAESKey(commandCode) {
2084
+ return this.lockAESKeys.get(commandCode);
2085
+ }
2086
+ }
2087
+ exports.P2PClientProtocol = P2PClientProtocol;
2088
2088
  //# sourceMappingURL=session.js.map