agn-utils/.svn/pristine/58/58481746c0d1c983d655c4749115639b00ae8b94.svn-base

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;