159 lines
4.2 KiB
Plaintext
159 lines
4.2 KiB
Plaintext
'use strict';
|
|
|
|
const TCPDevice = require('./tcp-device');
|
|
|
|
const READ_0 = 0;
|
|
const READ_ID = 1;
|
|
const READ_LEN = 2;
|
|
const READ_BODY = 3;
|
|
const READ_BLOCKED = 99;
|
|
|
|
const RESP_ID_HEADER = 0xE1;
|
|
const RESP_CMD_HEADER = 0xF1;
|
|
|
|
class RAPTCPDevice extends TCPDevice {
|
|
|
|
constructor(socket, ops) {
|
|
super(socket, ops);
|
|
|
|
this.id = null;
|
|
this.msgReceived = 0;
|
|
|
|
this._idPart = null;
|
|
this._lenPart = null;
|
|
this._dataLength = 0;
|
|
this._bodyPart = null;
|
|
this._cmdBuf = null;
|
|
|
|
this._readState = READ_0;
|
|
}
|
|
|
|
/**
|
|
* _onData is triggered by the "readable" event on the underlying TCP socket.
|
|
* It is called each time there is new data * received. It is responsible for reading data from the socket and
|
|
* performing the appropriate action given the current read state.
|
|
*/
|
|
_onData() {
|
|
try {
|
|
// Loop while there was still data to process on the socket's buffer.
|
|
// This will stop when we don't have enough data or encountering a back pressure issue;
|
|
let readMore = true;
|
|
do {
|
|
switch (this._readState) {
|
|
case READ_0:
|
|
// this._idPart = null;
|
|
// this._lenPart = null;
|
|
// this._dataLength = 0;
|
|
// this._bodyPart = null;
|
|
readMore = this._processRead_0();
|
|
break;
|
|
case READ_ID:
|
|
readMore = this._processReadId();
|
|
break;
|
|
case READ_LEN:
|
|
readMore = this._processReadLen();
|
|
break;
|
|
case READ_BODY:
|
|
readMore = this._processReadBody();
|
|
break;
|
|
case READ_BLOCKED:
|
|
readMore = false;
|
|
break;
|
|
default:
|
|
throw new Error('Unknown read state');
|
|
}
|
|
} while (readMore);
|
|
} catch (err) {
|
|
// Terminate on failures as we won't be able to recovery since data was corrupted and we won't
|
|
// be able to any more data without additional errors.
|
|
this.destroy(err);
|
|
}
|
|
}
|
|
|
|
_processRead_0() {
|
|
this._firstByte = this._socket.read(1);
|
|
if (!this._firstByte)
|
|
return false;
|
|
|
|
if (this._firstByte[0] === RESP_ID_HEADER) {
|
|
this._readState = READ_ID;
|
|
} else if (this._firstByte[0] === RESP_CMD_HEADER) {
|
|
this._readState = READ_LEN;
|
|
} else {
|
|
this._readState = READ_0;
|
|
this._guardInvalidTries();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
_getDeviceId(buf) {
|
|
buf[7] = 0; // byte 8th is not used in the case ID is IMEI
|
|
// Convert this buf to 64 integer number
|
|
return buf.readBigUInt64LE(0).toString();
|
|
}
|
|
|
|
_processReadId() {
|
|
// Try to read the Report Data Length from the 3rd byte of the GPS Response message
|
|
// If we cannot read the 8 bytes, the attempt to process the message will abort.
|
|
// E1 => [8 byte ID]
|
|
this._idPart = this._socket.read(8);
|
|
if (!this._idPart)
|
|
return false;
|
|
|
|
if (!this.id) {
|
|
// Parse the Device ID (8bytes) for the IMEI number
|
|
const id8byte = this._idPart.slice(0);
|
|
if (id8byte[7] === 0x01)
|
|
this.id = this._getDeviceId(id8byte);
|
|
}
|
|
|
|
this._readState = READ_0;
|
|
return true;
|
|
}
|
|
|
|
_processReadLen() {
|
|
// E1[8 byte ID]F1 => <Cmd><Len>
|
|
this._lenPart = this._socket.read(2);
|
|
if (!this._lenPart)
|
|
return false;
|
|
|
|
this._dataLength = this._lenPart[1];
|
|
|
|
this._readState = READ_BODY;
|
|
return true;
|
|
}
|
|
|
|
_processReadBody() {
|
|
// Read payload data by the length
|
|
this._bodyPart = this._socket.read(this._dataLength);
|
|
if (!this._bodyPart) {
|
|
// this._socket.unshift(this._lenPart); // ??? needed
|
|
return false;
|
|
}
|
|
|
|
this._invalidTry = 0;
|
|
this.msgReceived++;
|
|
this._cmdBuf = Buffer.alloc(this._lenPart.length + this._dataLength);
|
|
|
|
// Copy the <Cmd> from read <Cmd><Len>
|
|
this._lenPart.copy(this._cmdBuf);
|
|
this._bodyPart.copy(this._cmdBuf, this._lenPart.length);
|
|
// Push the message onto the read buffer for the consumer to read. We are mindful of slow reads by the consumer
|
|
// and will respect backpressure signals.
|
|
const pushOk = this.push(this._cmdBuf);
|
|
if (pushOk) {
|
|
this._readState = READ_0;
|
|
return true;
|
|
} else {
|
|
// debug("socket read is blocked");
|
|
console.log('socket read is blocked');
|
|
this._readState = READ_BLOCKED;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = RAPTCPDevice;
|