diff options
-rw-r--r-- | setup/default.xml | 1 | ||||
-rw-r--r-- | src/org/traccar/helper/Checksum.java | 8 | ||||
-rw-r--r-- | src/org/traccar/protocol/Arnavi4FrameDecoder.java | 51 | ||||
-rw-r--r-- | src/org/traccar/protocol/Arnavi4Protocol.java | 33 | ||||
-rw-r--r-- | src/org/traccar/protocol/Arnavi4ProtocolDecoder.java | 168 | ||||
-rw-r--r-- | test/org/traccar/helper/ChecksumTest.java | 8 | ||||
-rw-r--r-- | test/org/traccar/protocol/Arnavi4FrameDecoderTest.java | 37 | ||||
-rw-r--r-- | test/org/traccar/protocol/Arnavi4ProtocolDecoderTest.java | 37 |
8 files changed, 343 insertions, 0 deletions
diff --git a/setup/default.xml b/setup/default.xml index 2fc6c3cf0..223b9a585 100644 --- a/setup/default.xml +++ b/setup/default.xml @@ -579,5 +579,6 @@ <entry key='vt200.port'>5143</entry> <entry key='owntracks.port'>5144</entry> <entry key='vtfms.port'>5145</entry> + <entry key='arnavi4.port'>5146</entry> </properties> diff --git a/src/org/traccar/helper/Checksum.java b/src/org/traccar/helper/Checksum.java index 43ba6a689..7a0151d36 100644 --- a/src/org/traccar/helper/Checksum.java +++ b/src/org/traccar/helper/Checksum.java @@ -243,4 +243,12 @@ public final class Checksum { return (10 - (checksum % 10)) % 10; } + public static int modulo256(byte... bytes) { + int sum = 0; + for (byte b : bytes) { + sum = (sum + b) & 0xFF; + } + return sum; + } + } diff --git a/src/org/traccar/protocol/Arnavi4FrameDecoder.java b/src/org/traccar/protocol/Arnavi4FrameDecoder.java new file mode 100644 index 000000000..eaf829cc3 --- /dev/null +++ b/src/org/traccar/protocol/Arnavi4FrameDecoder.java @@ -0,0 +1,51 @@ +package org.traccar.protocol; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.frame.FrameDecoder; + +/** + * Created by Ivan Muratov @binakot on 12.07.2017. + */ +public class Arnavi4FrameDecoder extends FrameDecoder { + + static final byte HEADER_START_SIGN = (byte)0xFF; + static final byte HEADER_VERSION_1 = 0x22; + static final byte HEADER_VERSION_2 = 0x23; + static final int HEADER_LENGTH = 10; + + static final byte PACKAGE_START_SIGN = 0x5B; + static final byte PACKAGE_END_SIGN = 0x5D; + static final int PACKAGE_MIN_PARCEL_NUMBER = 0x01; + static final int PACKAGE_MAX_PARCEL_NUMBER = 0xFB; + + @Override + protected Object decode( + ChannelHandlerContext ctx, + Channel channel, + ChannelBuffer buf) throws Exception { + + if (buf.readableBytes() == 0) { + return null; + } + + byte[] bytes = new byte[buf.readableBytes()]; + buf.getBytes(0, bytes); + + if (bytes[0] == HEADER_START_SIGN + && bytes.length == HEADER_LENGTH + && (bytes[1] == HEADER_VERSION_1 || bytes[1] == HEADER_VERSION_2)) { + return buf.readBytes(HEADER_LENGTH); + } + + int parcelNumber = bytes[1] & 0xFF; + if (bytes[0] == PACKAGE_START_SIGN && bytes[bytes.length - 1] == PACKAGE_END_SIGN + && parcelNumber >= PACKAGE_MIN_PARCEL_NUMBER && parcelNumber <= PACKAGE_MAX_PARCEL_NUMBER) { + return buf.readBytes(bytes.length); + } + + return null; + } + +} diff --git a/src/org/traccar/protocol/Arnavi4Protocol.java b/src/org/traccar/protocol/Arnavi4Protocol.java new file mode 100644 index 000000000..227397980 --- /dev/null +++ b/src/org/traccar/protocol/Arnavi4Protocol.java @@ -0,0 +1,33 @@ +package org.traccar.protocol; + +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.ChannelPipeline; +import org.traccar.BaseProtocol; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; +import java.util.List; + +/** + * Created by Ivan Muratov @binakot on 11.07.2017. + */ +public class Arnavi4Protocol extends BaseProtocol { + + public Arnavi4Protocol() { + super("arnavi4"); + } + + @Override + public void initTrackerServers(List<TrackerServer> serverList) { + TrackerServer server = new TrackerServer(new ServerBootstrap(), getName()) { + @Override + protected void addSpecificHandlers(ChannelPipeline pipeline) { + pipeline.addLast("frameDecoder", new Arnavi4FrameDecoder()); + pipeline.addLast("objectDecoder", new Arnavi4ProtocolDecoder(Arnavi4Protocol.this)); + } + }; + server.setEndianness(ByteOrder.LITTLE_ENDIAN); + serverList.add(server); + } + +} diff --git a/src/org/traccar/protocol/Arnavi4ProtocolDecoder.java b/src/org/traccar/protocol/Arnavi4ProtocolDecoder.java new file mode 100644 index 000000000..46caebb71 --- /dev/null +++ b/src/org/traccar/protocol/Arnavi4ProtocolDecoder.java @@ -0,0 +1,168 @@ +package org.traccar.protocol; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.helper.Checksum; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +import static org.traccar.protocol.Arnavi4FrameDecoder.*; + +/** + * Created by Ivan Muratov @binakot on 11.07.2017. + */ +public class Arnavi4ProtocolDecoder extends BaseProtocolDecoder { + + private static final byte RECORD_PING = 0x00; + private static final byte RECORD_DATA = 0x01; + private static final byte RECORD_TEXT = 0x03; + private static final byte RECORD_FILE = 0x04; + private static final byte RECORD_BINARY = 0x06; + + private static final byte TAG_LATITUDE = 3; + private static final byte TAG_LONGITUDE = 4; + private static final byte TAG_COORD_PARAMS = 5; + + public Arnavi4ProtocolDecoder(Arnavi4Protocol protocol) { + super(protocol); + } + + private Position decodePosition(DeviceSession deviceSession, ChannelBuffer buf, long timestamp) { + + final Position position = new Position(); + position.setProtocol(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date(timestamp)); + + while (buf.readableBytes() > 0) { + short tagId = buf.readUnsignedByte(); + int tagValue = buf.readInt(); + switch (tagId) { + case TAG_LATITUDE: + position.setLatitude(Float.intBitsToFloat(tagValue)); + position.setValid(true); + break; + + case TAG_LONGITUDE: + position.setLongitude(Float.intBitsToFloat(tagValue)); + position.setValid(true); + break; + + case TAG_COORD_PARAMS: + position.setSpeed((tagValue >> 24) * 1.852); + position.set(Position.KEY_SATELLITES, (tagValue >> 16 & 0x0F) + (tagValue >> 20 & 0x0F)); + position.setAltitude((tagValue >> 8 & 0xFF) * 10.0); + position.setCourse((tagValue & 0xFF) * 2.0); + break; + + default: + break; // Skip other tags + } + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ChannelBuffer buf = (ChannelBuffer) msg; + + byte startSign = buf.readByte(); + + if (startSign == HEADER_START_SIGN) { + + byte version = buf.readByte(); + + String imei = String.valueOf(buf.readLong()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + + if (deviceSession != null && channel != null) { + + final ChannelBuffer response; + + if (version == HEADER_VERSION_1) { + response = ChannelBuffers.dynamicBuffer(ByteOrder.LITTLE_ENDIAN, 4); + response.writeBytes(new byte[]{0x7B, 0x00, 0x00, 0x7D}); + + } else if (version == HEADER_VERSION_2) { + response = ChannelBuffers.dynamicBuffer(ByteOrder.LITTLE_ENDIAN, 9); + response.writeBytes(new byte[]{0x7B, 0x04, 0x00}); + byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) (System.currentTimeMillis() / 1000)).array(); + response.writeByte(Checksum.modulo256(timestampBytes)); + response.writeBytes(timestampBytes); + response.writeByte(0x7D); + + } else { + throw new IllegalArgumentException("unsupported header version"); + } + + channel.write(response); + } + + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + if (startSign == PACKAGE_START_SIGN) { + + List<Position> positions = new LinkedList<>(); + + int parcelNumber = buf.readUnsignedByte(); + + byte recordStartSign = buf.readByte(); + while (recordStartSign != PACKAGE_END_SIGN) { + switch (recordStartSign) { + case RECORD_PING: + case RECORD_DATA: + case RECORD_TEXT: + case RECORD_FILE: + case RECORD_BINARY: { + int length = buf.readUnsignedShort(); + long timestamp = buf.readUnsignedInt() * 1000; + ChannelBuffer recordBuf = buf.readBytes(length); + + if (recordStartSign == RECORD_DATA) { + positions.add(decodePosition(deviceSession, recordBuf, timestamp)); + } + + buf.readUnsignedByte(); // crc + + break; + } + + default: + throw new IllegalArgumentException("unsupported record type"); + } + + recordStartSign = buf.readByte(); + } + + if (channel != null) { + final ChannelBuffer response = ChannelBuffers.dynamicBuffer(ByteOrder.LITTLE_ENDIAN, 4); + response.writeBytes(new byte[]{0x7B, 0x00, (byte) parcelNumber, 0x7D}); + channel.write(response); + } + + return positions; + } + + return null; + } + +} diff --git a/test/org/traccar/helper/ChecksumTest.java b/test/org/traccar/helper/ChecksumTest.java index c37eda88d..737b65c62 100644 --- a/test/org/traccar/helper/ChecksumTest.java +++ b/test/org/traccar/helper/ChecksumTest.java @@ -4,6 +4,7 @@ import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.junit.Assert; import org.junit.Test; +import org.traccar.protocol.Arnavi4ProtocolDecoder; import java.nio.charset.StandardCharsets; @@ -28,4 +29,11 @@ public class ChecksumTest { Assert.assertEquals(0, Checksum.luhn(63070019470771L)); } + @Test + public void testModulo256() { + Assert.assertEquals(0x00, Checksum.modulo256((byte)0x00)); + Assert.assertEquals(0x00, Checksum.modulo256((byte)0x00, (byte)0x00, (byte)0x00)); + Assert.assertEquals(0x06, Checksum.modulo256((byte)0x01, (byte)0x02, (byte)0x03)); + } + } diff --git a/test/org/traccar/protocol/Arnavi4FrameDecoderTest.java b/test/org/traccar/protocol/Arnavi4FrameDecoderTest.java new file mode 100644 index 000000000..08abd3835 --- /dev/null +++ b/test/org/traccar/protocol/Arnavi4FrameDecoderTest.java @@ -0,0 +1,37 @@ +package org.traccar.protocol; + +import org.junit.Assert; +import org.junit.Test; +import org.traccar.ProtocolTest; + +import java.nio.ByteOrder; + +/** + * Created by Ivan Muratov @binakot on 13.07.2017. + */ +public class Arnavi4FrameDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + Arnavi4FrameDecoder decoder = new Arnavi4FrameDecoder(); + + Assert.assertEquals( // Valid HEADER v1 packet with IMEI + binary(ByteOrder.LITTLE_ENDIAN, "ff22f30c45f5c90f0300"), + decoder.decode(null, null, binary(ByteOrder.LITTLE_ENDIAN, "ff22f30c45f5c90f0300"))); + + Assert.assertEquals( // Valid HEADER v2 packet with IMEI + binary(ByteOrder.LITTLE_ENDIAN, "ff23f30c45f5c90f0300"), + decoder.decode(null, null, binary(ByteOrder.LITTLE_ENDIAN, "ff23f30c45f5c90f0300"))); + + Assert.assertEquals( // Valid PACKAGE packet with one DATA packet + binary(ByteOrder.LITTLE_ENDIAN, "5b01012800a3175f5903513934420447221c42055402781E0900f0c5215b4e0084005c00007c005d0000a300fa37010000295d"), + decoder.decode(null, null, binary(ByteOrder.LITTLE_ENDIAN, "5b01012800a3175f5903513934420447221c42055402781E0900f0c5215b4e0084005c00007c005d0000a300fa37010000295d"))); + + Assert.assertEquals( // Valid PACKAGE packet with two DATA packet + binary(ByteOrder.LITTLE_ENDIAN, "5b01012800a3175f5903513934420447221c42055402781E0900f0c5215b4e0084005c00007c005d0000a300fa3701000029012800a3175f5903513934420447221c42055402781E0900f0c5215b4e0084005c00007c005d0000a300fa37010000295d"), + decoder.decode(null, null, binary(ByteOrder.LITTLE_ENDIAN, "5b01012800a3175f5903513934420447221c42055402781E0900f0c5215b4e0084005c00007c005d0000a300fa3701000029012800a3175f5903513934420447221c42055402781E0900f0c5215b4e0084005c00007c005d0000a300fa37010000295d"))); + + } + +}
\ No newline at end of file diff --git a/test/org/traccar/protocol/Arnavi4ProtocolDecoderTest.java b/test/org/traccar/protocol/Arnavi4ProtocolDecoderTest.java new file mode 100644 index 000000000..2395572a1 --- /dev/null +++ b/test/org/traccar/protocol/Arnavi4ProtocolDecoderTest.java @@ -0,0 +1,37 @@ +package org.traccar.protocol; + +import org.junit.Test; +import org.traccar.ProtocolTest; + +import java.nio.ByteOrder; + +/** + * Created by Ivan Muratov @binakot on 11.07.2017. + */ +public class Arnavi4ProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + Arnavi4ProtocolDecoder decoder; + + decoder = new Arnavi4ProtocolDecoder(new Arnavi4Protocol()); + + verifyNull(decoder, binary(ByteOrder.LITTLE_ENDIAN, // Valid HEADER v1 packet with IMEI + "ff22f30c45f5c90f0300")); + + verifyPositions(decoder, binary(ByteOrder.LITTLE_ENDIAN, // Valid PACKAGE packet with one DATA packet + "5b01012800a3175f5903513934420447221c42055402781E0900f0c5215b4e0084005c00007c005d0000a300fa37010000295d"), + position("2017-07-07 05:09:55.000", true, 45.05597, 39.03347)); + + decoder = new Arnavi4ProtocolDecoder(new Arnavi4Protocol()); + + verifyNull(decoder, binary(ByteOrder.LITTLE_ENDIAN, // Valid HEADER v2 packet with IMEI + "ff23f30c45f5c90f0300")); + + verifyPositions(decoder, binary(ByteOrder.LITTLE_ENDIAN, // Valid PACKAGE packet with two DATA packet + "5b01012800a3175f5903513934420447221c42055402781E0900f0c5215b4e0084005c00007c005d0000a300fa3701000029012800a3175f5903513934420447221c42055402781E0900f0c5215b4e0084005c00007c005d0000a300fa37010000295d"), + position("2017-07-07 05:09:55.000", true, 45.05597, 39.03347)); + } + +} |