diff options
author | Anton Tananaev <anton.tananaev@gmail.com> | 2022-09-18 10:51:55 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-18 10:51:55 -0700 |
commit | aa7509c4d234552dc9a8fc7041190e9da0fda590 (patch) | |
tree | 0f88d08b2e51d5285dde5ed7acdace5293be8373 | |
parent | 8532bffcec8e239c6699845e09e63da927f51d9a (diff) | |
parent | b118bd4cc8a64e768370ef19061e0f968136cf18 (diff) | |
download | trackermap-server-aa7509c4d234552dc9a8fc7041190e9da0fda590.tar.gz trackermap-server-aa7509c4d234552dc9a8fc7041190e9da0fda590.tar.bz2 trackermap-server-aa7509c4d234552dc9a8fc7041190e9da0fda590.zip |
Merge pull request #4943 from anton2920/g1rus-dev
Support for G1RUS protocol
-rw-r--r-- | setup/default.xml | 1 | ||||
-rw-r--r-- | src/main/java/org/traccar/protocol/G1rusProtocol.java | 24 | ||||
-rw-r--r-- | src/main/java/org/traccar/protocol/G1rusProtocolDecoder.java | 375 |
3 files changed, 400 insertions, 0 deletions
diff --git a/setup/default.xml b/setup/default.xml index 9d8923f52..098bdf95d 100644 --- a/setup/default.xml +++ b/setup/default.xml @@ -286,5 +286,6 @@ <entry key='bstpl.port'>5241</entry> <entry key='thuraya.port'>5242</entry> <entry key='ndtpv6.port'>5243</entry> + <entry key='g1rus.port'>5244</entry> </properties> diff --git a/src/main/java/org/traccar/protocol/G1rusProtocol.java b/src/main/java/org/traccar/protocol/G1rusProtocol.java new file mode 100644 index 000000000..0d77a8add --- /dev/null +++ b/src/main/java/org/traccar/protocol/G1rusProtocol.java @@ -0,0 +1,24 @@ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class G1rusProtocol extends BaseProtocol { + + @Inject + public G1rusProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new G1rusProtocolDecoder(G1rusProtocol.this)); + } + }); + } +} diff --git a/src/main/java/org/traccar/protocol/G1rusProtocolDecoder.java b/src/main/java/org/traccar/protocol/G1rusProtocolDecoder.java new file mode 100644 index 000000000..b1b7230e1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/G1rusProtocolDecoder.java @@ -0,0 +1,375 @@ +package org.traccar.protocol; + +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import com.google.common.primitives.Shorts; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Protocol; +import org.traccar.model.Position; +import org.traccar.session.ConnectionManager; +import org.traccar.session.DeviceSession; + +import java.net.SocketAddress; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class G1rusProtocolDecoder extends BaseProtocolDecoder { + public G1rusProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class); + + /* Constants */ + private static final int G1RUS_HEAD_TAIL = 0xF8; + + private static final int G1RUS_TYPE_HEARTBEAT = 0; + + private static final int G1RUS_TYPE_BCD_MASK = 0b00111111; + private static final int G1RUS_TYPE_REGULAR = 1; + private static final int G1RUS_TYPE_SMS_FORWARD = 2; + private static final int G1RUS_TYPE_SERIAL_PASS_THROUGH = 3; + private static final int G1RUS_TYPE_MIXED = 4; + + private static final int G1RUS_TYPE_EVENT_MASK = 0b01000000; + private static final int G1RUS_TYPE_NON_EVENT = 0; + private static final int G1RUS_TYPE_EVENT = 1; + + private static final int G1RUS_TYPE_IMEI_MASK = 0b10000000; + private static final int G1RUS_TYPE_IMEI_LONG = 0; + private static final int G1RUS_TYPE_IMEI_SHORT = 1; + + private static final int G1RUS_DATA_SYS_MASK = 0b00000001; + private static final int G1RUS_DATA_GPS_MASK = 0b00000010; + private static final int G1RUS_DATA_GSM_MASK = 0b00000100; + private static final int G1RUS_DATA_COT_MASK = 0b00001000; + private static final int G1RUS_DATA_ADC_MASK = 0b00010000; + private static final int G1RUS_DATA_DTT_MASK = 0b00100000; + /* Reserved */ + private static final int G1RUS_DATA_ETD_MASK = 0b10000000; + + private static final int G1RUS_GPS_SIGN_MASK = 0b00000001; + private static final int G1RUS_GPS_POS_MASK = 0b00000010; + private static final int G1RUS_GPS_SPD_MASK = 0b00000100; + private static final int G1RUS_GPS_AZTH_MASK = 0b00001000; + private static final int G1RUS_GPS_ALT_MASK = 0b00010000; + private static final int G1RUS_GPS_HDOP_MASK = 0b00100000; + private static final int G1RUS_GPS_VDOP_MASK = 0b01000000; + private static final int G1RUS_GPS_STAT_MASK = 0b10000000; + + private static final int G1RUS_ADC_DATA_MASK = 0b0000111111111111; + + private static final int G1RUS_ESCAPE_CHAR = 0x1B; + + + private short readUnsignedByteUnescaped(ByteBuf buf) { + short first = buf.readUnsignedByte(); + if (first != G1RUS_ESCAPE_CHAR) { + return first; + } else { /* first == 0x1B */ + byte second = (byte) buf.readUnsignedByte(); + if (second == 0x00) { + return first; + } else { /* second == 0xE3 */ + return (short) 0xF8; + } + } + } + + + private void skipBytesUnescaped(ByteBuf buf, int howMany) { + for (int i = 0; i < howMany; ++i) { + readUnsignedByteUnescaped(buf); + } + } + + + private void readBytesUnescaped(ByteBuf buf, byte[] to) { + for (int i = 0; i < to.length; ++i) { + to[i] = (byte) readUnsignedByteUnescaped(buf); + } + } + + private void readBytesUnescaped(ByteBuf buf, byte[] to, int dstIndex, int length) { + for (int i = dstIndex; i < length; ++i) { + to[i] = (byte) readUnsignedByteUnescaped(buf); + } + } + + + private int readUnsignedShortUnescaped(ByteBuf buf) { + byte[] shortBuf = new byte[2]; + readBytesUnescaped(buf, shortBuf); + return Shorts.fromByteArray(shortBuf); + } + + + private int readIntUnescaped(ByteBuf buf) { + byte[] intBuf = new byte[4]; + readBytesUnescaped(buf, intBuf); + return Ints.fromByteArray(intBuf); + } + + + private void decodeSYSSub(ByteBuf buf) { + LOGGER.debug("<SYS>"); + + skipBytesUnescaped(buf, 1); /* Total length */ + + /* NOTE: assuming order: + * Device name -> Firmware version -> Hardware version. + * TODO: actually check it. + */ + + /* Device name */ + short devNameLen = readUnsignedByteUnescaped(buf); + byte[] devName = new byte[devNameLen & 0xF]; + readBytesUnescaped(buf, devName); + String devNameString = new String(devName); + LOGGER.debug("Device name: " + devNameString); + + /* Firmware version */ + short firmwareLen = readUnsignedByteUnescaped(buf); + byte[] firmware = new byte[firmwareLen & 0xF]; + readBytesUnescaped(buf, firmware); + String firmwareString = new String(firmware); + LOGGER.debug("Firmware version: " + firmwareString); + + /* Hardware version */ + short hardwareLen = readUnsignedByteUnescaped(buf); + byte[] hardware = new byte[hardwareLen & 0xF]; + readBytesUnescaped(buf, hardware); + String hardwareString = new String(hardware); + LOGGER.debug("Hardware version: " + hardwareString); + + LOGGER.debug("</SYS>"); + } + + + private void decodeGPSSub(ByteBuf buf, Position position) { + LOGGER.debug("<GPS>"); + + skipBytesUnescaped(buf, 1); /* Total length */ + + int subMask = readUnsignedShortUnescaped(buf); + if ((subMask & G1RUS_GPS_SIGN_MASK) == G1RUS_GPS_SIGN_MASK) { + short signValid = readUnsignedByteUnescaped(buf); + LOGGER.debug("Fix sign: " + ((signValid & 0b1100000) >> 5)); + LOGGER.debug("Satellite number: " + (signValid & 0b0011111)); + position.setValid(((signValid & 0b1100000) >> 5) == 2); + position.set(Position.KEY_SATELLITES, signValid & 0b0011111); + } + if ((subMask & G1RUS_GPS_POS_MASK) == G1RUS_GPS_POS_MASK) { + byte[] posBuf = new byte[4]; + readBytesUnescaped(buf, posBuf); + position.setLatitude((float) Ints.fromByteArray(posBuf) / 1000000); + LOGGER.debug("Latitude: " + position.getLatitude()); + + readBytesUnescaped(buf, posBuf); + position.setLongitude((float) Ints.fromByteArray(posBuf) / 1000000); + LOGGER.debug("Longitude: " + position.getLongitude()); + } + if ((subMask & G1RUS_GPS_SPD_MASK) == G1RUS_GPS_SPD_MASK) { + position.setSpeed(readUnsignedShortUnescaped(buf)); + LOGGER.debug("Speed: " + position.getSpeed()); + } + if ((subMask & G1RUS_GPS_AZTH_MASK) == G1RUS_GPS_AZTH_MASK) { + position.setCourse(readUnsignedShortUnescaped(buf)); + LOGGER.debug("Course: " + position.getCourse()); + } + if ((subMask & G1RUS_GPS_ALT_MASK) == G1RUS_GPS_ALT_MASK) { + position.setAltitude(readUnsignedShortUnescaped(buf)); + LOGGER.debug("Altitude: " + position.getAltitude()); + } + if ((subMask & G1RUS_GPS_HDOP_MASK) == G1RUS_GPS_HDOP_MASK) { + position.set(Position.KEY_HDOP, readUnsignedShortUnescaped(buf)); + LOGGER.debug("HDOP: " + position.getAttributes().get(Position.KEY_HDOP)); + } + if ((subMask & G1RUS_GPS_VDOP_MASK) == G1RUS_GPS_VDOP_MASK) { + position.set(Position.KEY_VDOP, readUnsignedShortUnescaped(buf)); + LOGGER.debug("VDOP: " + position.getAttributes().get(Position.KEY_VDOP)); + } + + LOGGER.debug("</GPS>"); + } + + + private int getADValue(int rawValue) { + final int AD_MIN = -10; + final int AD_MAX = 100; + + return rawValue * (AD_MAX - AD_MIN) / 4096 + AD_MIN; + } + + + private void decodeADCSub(ByteBuf buf, Position position) { + LOGGER.debug("<ADC>"); + + skipBytesUnescaped(buf, 1); + + /* NOTE: assuming order: + * External battery voltage -> Backup battery voltage -> Device temperature voltage. + * TODO: actually check this. + */ + + int externalVoltage = readUnsignedShortUnescaped(buf) & G1RUS_ADC_DATA_MASK; + LOGGER.debug("External voltage: " + getADValue(externalVoltage) + "V [" + externalVoltage + "]"); + + int backupVoltage = readUnsignedShortUnescaped(buf) & G1RUS_ADC_DATA_MASK; + LOGGER.debug("Backup voltage: " + getADValue(backupVoltage) + "V [" + backupVoltage + "]"); + position.set(Position.KEY_BATTERY, getADValue(backupVoltage)); + + int temperature = readUnsignedShortUnescaped(buf) & G1RUS_ADC_DATA_MASK; + LOGGER.debug("Device temperature: " + getADValue(temperature) + "°C [" + temperature + "]"); + position.set(Position.KEY_DEVICE_TEMP, getADValue(temperature)); + + LOGGER.debug("</ADC>"); + } + + + private Position decodeRegular(Channel channel, SocketAddress remoteAddress, ByteBuf buf, long imei, short packetType) { + int timestamp_ = readIntUnescaped(buf); + long timestamp = (946684800 + timestamp_) * 1000L; /* Convert received time to proper UNIX timestamp */ + LOGGER.debug("Date and time: " + new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date(timestamp))); + + if ((packetType & G1RUS_TYPE_EVENT_MASK) != G1RUS_TYPE_NON_EVENT) { + skipBytesUnescaped(buf, 1); /* Event ID */ + } + + DeviceSession deviceSession = null; + Position position = null; + + int dataUploadingMask = readUnsignedShortUnescaped(buf); + if ((dataUploadingMask & G1RUS_DATA_SYS_MASK) == G1RUS_DATA_SYS_MASK) { + decodeSYSSub(buf); + } + if ((dataUploadingMask & G1RUS_DATA_GPS_MASK) == G1RUS_DATA_GPS_MASK) { + deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(imei)); + if (deviceSession == null) { + return null; + } + position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.setTime(new Date(timestamp)); + + decodeGPSSub(buf, position); + } + if ((dataUploadingMask & G1RUS_DATA_GSM_MASK) == G1RUS_DATA_GSM_MASK) { + skipBytesUnescaped(buf, readUnsignedByteUnescaped(buf)); + } + if ((dataUploadingMask & G1RUS_DATA_COT_MASK) == G1RUS_DATA_COT_MASK) { + skipBytesUnescaped(buf, readUnsignedByteUnescaped(buf)); + } + if ((dataUploadingMask & G1RUS_DATA_ADC_MASK) == G1RUS_DATA_ADC_MASK) { + if (deviceSession == null) { + skipBytesUnescaped(buf, readUnsignedByteUnescaped(buf)); + } else { + decodeADCSub(buf, position); + } + } + if ((dataUploadingMask & G1RUS_DATA_DTT_MASK) == G1RUS_DATA_DTT_MASK) { + skipBytesUnescaped(buf, readUnsignedByteUnescaped(buf)); + } + if ((dataUploadingMask & G1RUS_DATA_ETD_MASK) == G1RUS_DATA_ETD_MASK) { + skipBytesUnescaped(buf, readUnsignedByteUnescaped(buf)); + } + + return position; + } + + + private Object decodeSMSForward(ByteBuf buf) { + return null; + } + + + private Object decodeSerialPassThrough(ByteBuf buf) { + return null; + } + + + private void printPacketType(short packetType) { + LOGGER.debug("Packet type: " + (packetType == G1RUS_TYPE_HEARTBEAT ? "HEARTBEAT" : + "[" + ((packetType & G1RUS_TYPE_IMEI_MASK) == G1RUS_TYPE_IMEI_LONG ? "IMEI_LONG" : "IMEI_SHORT") + "]" + + "[" + ((packetType & G1RUS_TYPE_EVENT_MASK) == G1RUS_TYPE_NON_EVENT ? "NON-EVENT" : "EVENT") + "]" + + "[" + ((packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_REGULAR ? "REGULAR" : (packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_SMS_FORWARD ? "SMS FORWARD" : (packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_SERIAL_PASS_THROUGH ? "PASS THROUGH" : (packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_MIXED ? "MIXED PACKED" : "RESERVED/INVALID") + "]")); + } + + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + ByteBuf buf = (ByteBuf) msg; + + if (buf.readUnsignedByte() != G1RUS_HEAD_TAIL) { + return null; + } + + LOGGER.debug("Protocol version: " + readUnsignedByteUnescaped(buf)); + + short packetType = readUnsignedByteUnescaped(buf); + printPacketType(packetType); + + byte[] imei = new byte[8]; + readBytesUnescaped(buf, imei, 0, 7); + long imeiLong = Longs.fromByteArray(imei); + LOGGER.debug("IMEI: " + imeiLong); + + List<Position> positions = null; + + if (packetType == G1RUS_TYPE_HEARTBEAT) { + return null; + } else if ((packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_REGULAR) { + positions = new LinkedList<>(); + Position position = decodeRegular(channel, remoteAddress, buf, imeiLong, packetType); + if (position != null) { + positions.add(position); + } + } else if ((packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_SMS_FORWARD) { + return decodeSMSForward(buf); + } else if ((packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_SERIAL_PASS_THROUGH) { + return decodeSerialPassThrough(buf); + } else if ((packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_MIXED) { + positions = new LinkedList<>(); + + while (buf.readableBytes() > 5) { + int subPacketLength = readUnsignedShortUnescaped(buf); + short subPacketType = readUnsignedByteUnescaped(buf); + printPacketType(subPacketType); + + if ((subPacketType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_REGULAR) { + Position position = decodeRegular(channel, remoteAddress, buf, imeiLong, subPacketType); + if (position != null) { + positions.add(position); + } + } else { + skipBytesUnescaped(buf, subPacketLength - 1); + } + /* else if ((subPacketType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_SMS_FORWARD) { + skipBytesUnescaped(buf, subPacketLength - 1); + *//*decodeSMSForward(buf);*//* + } else if ((subPacketType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_SERIAL_PASS_THROUGH) { + skipBytesUnescaped(buf, subPacketLength - 1); + *//*decodeSerialPassThrough(buf);*//* + }*/ + } + } else { + LOGGER.error("Unknown packet type!"); + } + + skipBytesUnescaped(buf, 2); /* CRC */ /* TODO: actually check it */ + short tail = buf.readUnsignedByte(); + if (tail == G1RUS_HEAD_TAIL) { + LOGGER.debug("Tail: OK"); + } else { + LOGGER.error("Tail: FAIL!"); + } + + return positions; + } +} |