aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny S Maksimov <admin@vragam.net>2019-01-17 19:05:23 +0300
committerEvgeny S Maksimov <admin@vragam.net>2019-01-17 19:05:23 +0300
commitfc707a69c3c95223ecb8e263d7de32a72ec84af3 (patch)
treefc0a9b3f75ea0f6077df332a9cbaf6cd75412e97
parentca2cca9355c3b36b2e7ed9ad808db797fa94cb78 (diff)
downloadtrackermap-server-fc707a69c3c95223ecb8e263d7de32a72ec84af3.tar.gz
trackermap-server-fc707a69c3c95223ecb8e263d7de32a72ec84af3.tar.bz2
trackermap-server-fc707a69c3c95223ecb8e263d7de32a72ec84af3.zip
Implemented FLEX 1.0 extension support for Navis (NTCB) protocol
-rw-r--r--checkstyle.xml2
-rw-r--r--src/org/traccar/protocol/NavisFrameDecoder.java80
-rw-r--r--src/org/traccar/protocol/NavisProtocol.java4
-rw-r--r--src/org/traccar/protocol/NavisProtocolDecoder.java463
-rw-r--r--test/org/traccar/protocol/NavisProtocolDecoderTest.java26
5 files changed, 522 insertions, 53 deletions
diff --git a/checkstyle.xml b/checkstyle.xml
index d85100471..a456428a6 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -68,7 +68,7 @@
<property name="max" value="120"/>
</module>
<module name="MethodLength">
- <property name="max" value="200"/>
+ <property name="max" value="250"/>
</module>
<module name="ParameterNumber"/>
diff --git a/src/org/traccar/protocol/NavisFrameDecoder.java b/src/org/traccar/protocol/NavisFrameDecoder.java
new file mode 100644
index 000000000..2480629ac
--- /dev/null
+++ b/src/org/traccar/protocol/NavisFrameDecoder.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2019 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.TooLongFrameException;
+import org.traccar.BaseFrameDecoder;
+
+public class NavisFrameDecoder extends BaseFrameDecoder {
+
+ private static final int NTCB_HEADER_LENGHT = 16;
+ private static final int NTCB_LENGHT_OFFSET = 12;
+ private static final int MAX_FRAME_LENGHT = 65551;
+
+ private static byte src8Checksum(ByteBuf buf, int length) {
+ byte sum = (byte) 0xFF;
+ for (int i = 0; i < length; i++) {
+ sum ^= buf.getUnsignedByte(i);
+ for (int j = 0; j < 8; j++) {
+ sum = (sum & 0x80) != 0 ? (byte) ((sum << 1) ^ 0x31) : (byte) (sum << 1);
+ }
+ }
+ return sum;
+ }
+
+ @Override
+ protected Object decode(
+ ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception {
+
+ // Check minimum length
+ if (buf.readableBytes() < 1) {
+ return null;
+ }
+
+ if (buf.getByte(buf.readerIndex()) == 0x7F) {
+ // FLEX keep alive frame
+ return buf.readRetainedSlice(1);
+ } else if (buf.getByte(buf.readerIndex()) == 0x7E /* "~" */) {
+ // FLEX frame
+ if (buf.readableBytes() > MAX_FRAME_LENGHT) {
+ throw new TooLongFrameException();
+ }
+
+ if (src8Checksum(buf, buf.readableBytes() - 1) == buf.getByte(buf.readableBytes() - 1)) {
+ return buf.readRetainedSlice(buf.readableBytes());
+ }
+ } else {
+ // NTCB frame
+ if (buf.readableBytes() < NTCB_HEADER_LENGHT) {
+ return null;
+ }
+
+ int length = buf.getUnsignedShortLE(buf.readerIndex() + NTCB_LENGHT_OFFSET);
+ if (buf.readableBytes() >= NTCB_HEADER_LENGHT + length) {
+ if (buf.readableBytes() > MAX_FRAME_LENGHT) {
+ throw new TooLongFrameException();
+ }
+ return buf.readRetainedSlice(NTCB_HEADER_LENGHT + length);
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/org/traccar/protocol/NavisProtocol.java b/src/org/traccar/protocol/NavisProtocol.java
index 83a9dc99d..4250ba4dc 100644
--- a/src/org/traccar/protocol/NavisProtocol.java
+++ b/src/org/traccar/protocol/NavisProtocol.java
@@ -15,19 +15,17 @@
*/
package org.traccar.protocol;
-import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import org.traccar.BaseProtocol;
import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
-import java.nio.ByteOrder;
public class NavisProtocol extends BaseProtocol {
public NavisProtocol() {
addServer(new TrackerServer(false, getName()) {
@Override
protected void addProtocolHandlers(PipelineBuilder pipeline) {
- pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 4096, 12, 2, 2, 0, true));
+ pipeline.addLast(new NavisFrameDecoder());
pipeline.addLast(new NavisProtocolDecoder(NavisProtocol.this));
}
});
diff --git a/src/org/traccar/protocol/NavisProtocolDecoder.java b/src/org/traccar/protocol/NavisProtocolDecoder.java
index 83088e9f0..3c5abc2a4 100644
--- a/src/org/traccar/protocol/NavisProtocolDecoder.java
+++ b/src/org/traccar/protocol/NavisProtocolDecoder.java
@@ -18,6 +18,8 @@ package org.traccar.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.traccar.BaseProtocolDecoder;
import org.traccar.DeviceSession;
import org.traccar.NetworkMessage;
@@ -31,14 +33,20 @@ import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
+import java.util.Date;
public class NavisProtocolDecoder extends BaseProtocolDecoder {
private String prefix;
private long deviceUniqueId, serverId;
+ private int flexDataSize; // bits
+ private final byte[] flexBitfield;
+ private byte flexProtocolVersion, flexStructVersion;
+ private static final Logger LOGGER = LoggerFactory.getLogger(NavisProtocolDecoder.class);
public NavisProtocolDecoder(Protocol protocol) {
super(protocol);
+ this.flexBitfield = new byte[16];
}
public static final int F10 = 0x01;
@@ -77,7 +85,7 @@ public class NavisProtocolDecoder extends BaseProtocolDecoder {
}
}
- private ParseResult parsePosition(DeviceSession deviceSession, ByteBuf buf) {
+ private ParseResult parseNTCBPosition(DeviceSession deviceSession, ByteBuf buf) {
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
@@ -95,7 +103,7 @@ public class NavisProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_EVENT, buf.readUnsignedShortLE());
- buf.skipBytes(6); // event time
+ buf.skipBytes(6); // Event time
short armedStatus = buf.readUnsignedByte();
if (isFormat(format, F10, F20, F30, F40, F50, F51, F52)) {
@@ -114,19 +122,48 @@ public class NavisProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_RSSI, buf.readUnsignedByte());
if (isFormat(format, F10, F20, F30)) {
- position.set(Position.KEY_OUTPUT, buf.readUnsignedShortLE());
+ int output = buf.readUnsignedShortLE();
+ position.set(Position.KEY_OUTPUT, output);
+ for (int i = 0; i < 16; i++) {
+ position.set(Position.PREFIX_OUT + (i + 1), BitUtil.check(output, i));
+ }
} else if (isFormat(format, F50, F51, F52)) {
- int extField = buf.readUnsignedByte();
+ short extField = buf.readUnsignedByte();
position.set(Position.KEY_OUTPUT, extField & 0x3);
position.set(Position.KEY_SATELLITES, extField >> 2);
+ position.set(Position.PREFIX_OUT + 1, BitUtil.check(extField, 0));
+ position.set(Position.PREFIX_OUT + 2, BitUtil.check(extField, 1));
} else if (isFormat(format, F40, F60)) {
- position.set(Position.KEY_OUTPUT, buf.readUnsignedByte() & 0xF);
+ short output = buf.readUnsignedByte();
+ position.set(Position.KEY_OUTPUT, output & 0xF);
+ for (int i = 0; i < 4; i++) {
+ position.set(Position.PREFIX_OUT + (i + 1), BitUtil.check(output, i));
+ }
}
if (isFormat(format, F10, F20, F30, F40)) {
- position.set(Position.KEY_INPUT, buf.readUnsignedShortLE());
+ int input = buf.readUnsignedShortLE();
+ position.set(Position.KEY_INPUT, input);
+ if (!isFormat(format, F40)) {
+ for (int i = 0; i < 16; i++) {
+ position.set(Position.PREFIX_IN + (i + 1), BitUtil.check(input, i));
+ }
+ } else {
+ position.set(Position.PREFIX_IN + 1, BitUtil.check(input, 0));
+ position.set(Position.PREFIX_IN + 2, BitUtil.check(input, 1));
+ position.set(Position.PREFIX_IN + 3, BitUtil.check(input, 2));
+ position.set(Position.PREFIX_IN + 4, BitUtil.check(input, 3));
+ position.set(Position.PREFIX_IN + 5, (input & 0x70) >> 4);
+ position.set(Position.PREFIX_IN + 6, (input & 0x380) >> 7);
+ position.set(Position.PREFIX_IN + 7, (input & 0xC00) >> 10);
+ position.set(Position.PREFIX_IN + 8, (input & 0x3000) >> 12);
+ }
} else if (isFormat(format, F50, F51, F52, F60)) {
- position.set(Position.KEY_INPUT, buf.readUnsignedByte());
+ short input = buf.readUnsignedByte();
+ position.set(Position.KEY_INPUT, input);
+ for (int i = 0; i < 8; i++) {
+ position.set(Position.PREFIX_IN + (i + 1), BitUtil.check(input, i));
+ }
}
position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.001);
@@ -199,6 +236,7 @@ public class NavisProtocolDecoder extends BaseProtocolDecoder {
position.setLatitude(buf.readFloatLE() / Math.PI * 180);
position.setLongitude(buf.readFloatLE() / Math.PI * 180);
}
+
position.setSpeed(UnitsConverter.knotsFromKph(buf.readFloatLE()));
position.setCourse(buf.readUnsignedShortLE());
@@ -234,13 +272,13 @@ public class NavisProtocolDecoder extends BaseProtocolDecoder {
return new ParseResult(index, position);
}
- private Object processSingle(DeviceSession deviceSession, Channel channel, ByteBuf buf) {
- ParseResult result = parsePosition(deviceSession, buf);
+ private Object processNTCBSingle(DeviceSession deviceSession, Channel channel, ByteBuf buf) {
+ ParseResult result = parseNTCBPosition(deviceSession, buf);
- ByteBuf response = Unpooled.buffer(8);
+ ByteBuf response = Unpooled.buffer(7);
response.writeCharSequence("*<T", StandardCharsets.US_ASCII);
response.writeIntLE((int) result.getId());
- sendReply(channel, response);
+ sendNTCBReply(channel, response);
if (result.getPosition().getFixTime() == null) {
return null;
@@ -249,21 +287,21 @@ public class NavisProtocolDecoder extends BaseProtocolDecoder {
return result.getPosition();
}
- private Object processArray(DeviceSession deviceSession, Channel channel, ByteBuf buf) {
+ private Object processNTCBArray(DeviceSession deviceSession, Channel channel, ByteBuf buf) {
List<Position> positions = new LinkedList<>();
int count = buf.readUnsignedByte();
for (int i = 0; i < count; i++) {
- Position position = parsePosition(deviceSession, buf).getPosition();
+ Position position = parseNTCBPosition(deviceSession, buf).getPosition();
if (position.getFixTime() != null) {
positions.add(position);
}
}
- ByteBuf response = Unpooled.buffer(8);
+ ByteBuf response = Unpooled.buffer(7);
response.writeCharSequence("*<A", StandardCharsets.US_ASCII);
response.writeByte(count);
- sendReply(channel, response);
+ sendNTCBReply(channel, response);
if (positions.isEmpty()) {
return null;
@@ -272,70 +310,405 @@ public class NavisProtocolDecoder extends BaseProtocolDecoder {
return positions;
}
+ private ParseResult parseFLEXPosition(DeviceSession deviceSession, ByteBuf buf) {
+ LOGGER.info("DEBUG: id={} parseFLEXPosition_START", deviceSession.getDeviceId());
+ Position position = new Position(getProtocolName());
+
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ long index = 0;
+
+ for (int i = 0; i < flexDataSize; i++) {
+ if ((flexBitfield[(int) (i / 8)] & (0x80 >> i % 8)) == 0) {
+ // Skip FLEX data field
+ continue;
+ }
+ LOGGER.info("DEBUG: id={} parseFLEXPosition_FIELD={}", deviceSession.getDeviceId(), i + 1);
+ int status = 0;
+ short input = 0;
+ short output = 0;
+
+ switch (i) {
+ case 0:
+ index = buf.readUnsignedIntLE();
+ position.set(Position.KEY_INDEX, index);
+ break;
+ case 1:
+ position.set(Position.KEY_EVENT, buf.readUnsignedShortLE());
+ break;
+ case 2:
+ buf.skipBytes(4); // Event time
+ break;
+ case 3:
+ short armedStatus = buf.readUnsignedByte();
+ position.set(Position.KEY_ARMED, armedStatus & 0x1);
+ if (BitUtil.check(armedStatus, 1)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_GENERAL);
+ }
+ break;
+ case 4:
+ status = buf.readUnsignedByte();
+ position.set(Position.KEY_STATUS, status);
+ break;
+ case 5:
+ int status2 = buf.readUnsignedByte();
+ position.set(Position.KEY_STATUS, (short) ((status & 0xFF) | (status2 << 1)));
+ break;
+ case 6:
+ position.set(Position.KEY_RSSI, buf.readUnsignedByte());
+ break;
+ case 7:
+ int navSensorState = buf.readUnsignedByte();
+ position.setValid(BitUtil.check(navSensorState, 1));
+ position.set(Position.KEY_SATELLITES, navSensorState >> 2);
+ break;
+ case 8:
+ position.setTime(new DateBuilder(new Date(buf.readUnsignedIntLE() * 1000)).getDate());
+ break;
+ case 9:
+ position.setLatitude(buf.readIntLE() / 600000.0);
+ break;
+ case 10:
+ position.setLongitude(buf.readIntLE() / 600000.0);
+ break;
+ case 11:
+ position.setAltitude(buf.readIntLE() * 0.1);
+ break;
+ case 12:
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readFloatLE()));
+ break;
+ case 13:
+ position.setCourse(buf.readUnsignedShortLE());
+ break;
+ case 14:
+ position.set(Position.KEY_ODOMETER, buf.readFloatLE() * 1000);
+ break;
+ case 15:
+ position.set(Position.KEY_DISTANCE, buf.readFloatLE() * 1000);
+ break;
+ case 16:
+ case 17:
+ buf.skipBytes(2);
+ break;
+ case 18:
+ position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.001);
+ break;
+ case 19:
+ position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001);
+ break;
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ case 24:
+ case 25:
+ case 26:
+ case 27:
+ position.set(Position.PREFIX_ADC + (i - 19), buf.readUnsignedShortLE());
+ break;
+ case 28:
+ input = buf.readUnsignedByte();
+ position.set(Position.KEY_INPUT, input);
+ for (int k = 0; k < 8; k++) {
+ position.set(Position.PREFIX_IN + (k + 1), BitUtil.check(input, k));
+ }
+ break;
+ case 29:
+ short input2 = buf.readUnsignedByte();
+ position.set(Position.KEY_INPUT, (short) ((input & 0xFF) | (input2 << 1)));
+ for (int k = 0; k < 8; k++) {
+ position.set(Position.PREFIX_IN + (k + 9), BitUtil.check(input2, k));
+ }
+ break;
+ case 30:
+ output = buf.readUnsignedByte();
+ position.set(Position.KEY_OUTPUT, output);
+ for (int k = 0; k < 8; k++) {
+ position.set(Position.PREFIX_OUT + (k + 1), BitUtil.check(output, k));
+ }
+ break;
+ case 31:
+ short output2 = buf.readUnsignedByte();
+ position.set(Position.KEY_OUTPUT, (short) ((output & 0xFF) | (output2 << 1)));
+ for (int k = 0; k < 8; k++) {
+ position.set(Position.PREFIX_OUT + (k + 9), BitUtil.check(output2, k));
+ }
+ break;
+ case 32:
+ case 33: // Impulse counters
+ buf.skipBytes(4);
+ break;
+ case 34:
+ case 35: // Fuel
+ buf.skipBytes(2);
+ break;
+ case 36:
+ position.set(Position.KEY_HOURS, buf.readUnsignedIntLE() * 1000);
+ break;
+ case 37:
+ case 38:
+ case 39:
+ case 40:
+ case 41:
+ case 42:
+ case 43: // Fuel RS-485
+ buf.skipBytes(2);
+ break;
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ case 48:
+ case 49:
+ case 50:
+ case 51:
+ position.set(Position.PREFIX_TEMP + (i - 43), buf.readByte());
+ break;
+ case 52: // CAN Fuel
+ buf.skipBytes(2);
+ break;
+ case 53: // CAN Fuel consumption
+ buf.skipBytes(4);
+ break;
+ case 54: // CAN RPM
+ buf.skipBytes(2);
+ break;
+ case 55: // CAN Coolant temperature
+ buf.skipBytes(1);
+ break;
+ case 56: // CAN Kilometrage
+ buf.skipBytes(4);
+ break;
+ case 57:
+ case 58:
+ case 59:
+ case 60:
+ case 61: // CAN Axle weight
+ buf.skipBytes(2);
+ break;
+ case 62: // CAN Accelerator
+ buf.skipBytes(1);
+ break;
+ case 63: // CAN Brake
+ buf.skipBytes(1);
+ break;
+ case 64: // CAN Engine load
+ buf.skipBytes(1);
+ break;
+ case 65: // CAN Filter fluid level
+ buf.skipBytes(2);
+ break;
+ case 66: // CAN Engine time
+ buf.skipBytes(4);
+ break;
+ case 67: // CAN Distance to maintenance
+ buf.skipBytes(2);
+ break;
+ case 68: // CAN Speed
+ position.set("can-speed", buf.readByte());
+ break;
+ default:
+ break;
+ }
+ }
+
+ return new ParseResult(index, position);
+ }
+
+ private Object processFLEXSingle(DeviceSession deviceSession, Channel channel, ByteBuf buf) {
+ buf.skipBytes(4); // Event index
+
+ ParseResult result = parseFLEXPosition(deviceSession, buf);
+
+ ByteBuf response = Unpooled.buffer(6);
+ response.writeCharSequence("~T", StandardCharsets.US_ASCII);
+ response.writeIntLE((int) result.getId());
+ sendFLEXReply(channel, response);
+
+ if (result.getPosition().getFixTime() == null) {
+ return null;
+ }
+
+ return result.getPosition();
+ }
+
+ private Object processFLEXArray(DeviceSession deviceSession, Channel channel, ByteBuf buf) {
+ List<Position> positions = new LinkedList<>();
+ int count = buf.readUnsignedByte();
+ LOGGER.info("DEBUG: id={} processFLEXArray_COUNT={}", deviceSession.getDeviceId(), count);
+ for (int i = 0; i < count; i++) {
+ Position position = parseFLEXPosition(deviceSession, buf).getPosition();
+ if (position.getFixTime() != null) {
+ positions.add(position);
+ }
+ }
+
+ ByteBuf response = Unpooled.buffer(6);
+ response.writeCharSequence("~A", StandardCharsets.US_ASCII);
+ response.writeByte(count);
+ sendFLEXReply(channel, response);
+
+ if (positions.isEmpty()) {
+ return null;
+ }
+
+ return positions;
+ }
+
+ private Object processFLEXNegotiation(Channel channel, ByteBuf buf) {
+ if (buf.readByte() != (byte) 0xB0) {
+ return null;
+ }
+
+ flexProtocolVersion = buf.readByte();
+ flexStructVersion = buf.readByte();
+ if (flexProtocolVersion != (byte) 0x0A && flexProtocolVersion != (byte) 0x14) {
+ return null;
+ }
+ if (flexStructVersion != (byte) 0x0A && flexStructVersion != (byte) 0x14) {
+ return null;
+ }
+
+ flexDataSize = buf.readByte();
+ if (flexDataSize > 122) {
+ return null;
+ }
+ buf.readBytes(flexBitfield, 0, (int) Math.ceil(flexDataSize / 8));
+
+ ByteBuf response = Unpooled.buffer(9);
+ response.writeCharSequence("*<FLEX", StandardCharsets.US_ASCII);
+ response.writeByte(0xB0);
+ response.writeByte(flexProtocolVersion);
+ response.writeByte(flexStructVersion);
+ sendNTCBReply(channel, response);
+
+ return null;
+ }
+
private Object processHandshake(Channel channel, SocketAddress remoteAddress, ByteBuf buf) {
- buf.readByte(); // colon symbol
+ buf.readByte(); // Colon symbol
if (getDeviceSession(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII)) != null) {
- sendReply(channel, Unpooled.copiedBuffer("*<S", StandardCharsets.US_ASCII));
+ sendNTCBReply(channel, Unpooled.copiedBuffer("*<S", StandardCharsets.US_ASCII));
}
return null;
}
- private static short checksum(ByteBuf buf) {
- short sum = 0;
+ private static byte xorChecksum(ByteBuf buf) {
+ byte sum = 0;
+ for (int i = 0; i < buf.readableBytes(); i++) {
+ sum ^= buf.getUnsignedByte(i);
+ }
+ return sum;
+ }
+
+ private static byte src8Checksum(ByteBuf buf) {
+ byte sum = (byte) 0xFF;
for (int i = 0; i < buf.readableBytes(); i++) {
sum ^= buf.getUnsignedByte(i);
+ for (int j = 0; j < 8; j++) {
+ sum = (sum & 0x80) != 0 ? (byte) ((sum << 1) ^ 0x31) : (byte) (sum << 1);
+ }
}
return sum;
}
- private void sendReply(Channel channel, ByteBuf data) {
+ private void sendNTCBReply(Channel channel, ByteBuf data) {
if (channel != null) {
ByteBuf header = Unpooled.buffer(16);
header.writeCharSequence(prefix, StandardCharsets.US_ASCII);
header.writeIntLE((int) deviceUniqueId);
header.writeIntLE((int) serverId);
header.writeShortLE(data.readableBytes());
- header.writeByte(checksum(data));
- header.writeByte(checksum(header));
+ header.writeByte(xorChecksum(data));
+ header.writeByte(xorChecksum(header));
channel.writeAndFlush(new NetworkMessage(Unpooled.wrappedBuffer(header, data), channel.remoteAddress()));
}
}
+ private void sendFLEXReply(Channel channel, ByteBuf data) {
+ if (channel != null) {
+ ByteBuf cs = Unpooled.buffer(1);
+ cs.writeByte(src8Checksum(data));
+
+ channel.writeAndFlush(new NetworkMessage(Unpooled.wrappedBuffer(data, cs), channel.remoteAddress()));
+ }
+ }
+
@Override
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
- prefix = buf.toString(buf.readerIndex(), 4, StandardCharsets.US_ASCII);
- buf.skipBytes(prefix.length()); // prefix @NTC by default
- serverId = buf.readUnsignedIntLE();
- deviceUniqueId = buf.readUnsignedIntLE();
- int length = buf.readUnsignedShortLE();
- buf.skipBytes(2); // header and data XOR checksum
-
- if (length == 0) {
- return null; // keep alive message
- }
-
- String type = buf.toString(buf.readerIndex(), 3, StandardCharsets.US_ASCII);
- buf.skipBytes(type.length());
-
- if (type.equals("*>S")) {
- return processHandshake(channel, remoteAddress, buf);
+ if (buf.getByte(buf.readerIndex()) == 0x7F) {
+ // FLEX keep alive message
+ return null;
+ } else if (buf.getByte(buf.readerIndex()) == 0x7E) { // "~"
+ // FLEX message
+ try {
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
+ if (deviceSession != null) {
+ switch (buf.readShortLE()) {
+ // FLEX 1.0
+ case 0x417E: // "~A"
+ return processFLEXArray(deviceSession, channel, buf);
+ case 0x547E: // "~T"
+ case 0x437E: // "~C"
+ return processFLEXSingle(deviceSession, channel, buf);
+ // FLEX 2.0
+ case 0x457E: // "~E"
+ LOGGER.info("DEBUG: id={} FLEX \"~E\"", deviceSession.getDeviceId());
+ break;
+ case 0x587E: // "~X"
+ LOGGER.info("DEBUG: id={} FLEX \"~X\"", deviceSession.getDeviceId());
+ break;
+ default:
+ break;
+ }
+ }
+ } catch (IndexOutOfBoundsException error) {
+ LOGGER.warn("Navis FLEX message parsing error", error);
+ }
} else {
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
- if (deviceSession != null) {
- if (type.equals("*>T")) {
- return processSingle(deviceSession, channel, buf);
- } else if (type.equals("*>A")) {
- return processArray(deviceSession, channel, buf);
+ // NTCB message
+ prefix = buf.toString(buf.readerIndex(), 4, StandardCharsets.US_ASCII);
+ buf.skipBytes(prefix.length()); // Prefix @NTC by default
+ serverId = buf.readUnsignedIntLE();
+ deviceUniqueId = buf.readUnsignedIntLE();
+ int length = buf.readUnsignedShortLE();
+ buf.skipBytes(2); // Header and data XOR checksum
+
+ if (length == 0) {
+ return null; // Keep alive message
+ }
+
+ int type = buf.getIntLE(buf.readerIndex());
+ buf.skipBytes(3);
+ if ((type & 0xFFFFFF) == 0x533E2AL) { // "*>S"
+ return processHandshake(channel, remoteAddress, buf);
+ } else {
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
+ if (deviceSession != null) {
+ try {
+ switch (type & 0xFFFFFF) {
+ case 0x413E2A: // "*>A"
+ return processNTCBArray(deviceSession, channel, buf);
+ case 0x543E2A: // "*>T"
+ return processNTCBSingle(deviceSession, channel, buf);
+ case 0x463E2A: // "*>F" (*>FLEX)
+ buf.skipBytes(3);
+ return processFLEXNegotiation(channel, buf);
+ default:
+ break;
+ }
+ } catch (IndexOutOfBoundsException error) {
+ LOGGER.warn("Navis NTCB message parsing error", error);
+ }
}
}
}
return null;
}
-
}
diff --git a/test/org/traccar/protocol/NavisProtocolDecoderTest.java b/test/org/traccar/protocol/NavisProtocolDecoderTest.java
index a60460691..88973dce7 100644
--- a/test/org/traccar/protocol/NavisProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/NavisProtocolDecoderTest.java
@@ -11,28 +11,46 @@ public class NavisProtocolDecoderTest extends ProtocolTest {
NavisProtocolDecoder decoder = new NavisProtocolDecoder(null);
+ // Handshake
verifyNull(decoder, binary(
"404E5443010000007B000000130044342A3E533A383631373835303035323035303739"));
+ verifyNull(decoder, binary(
+ "404E5443010000007B000000130047372A3E533A383631373835303035313236303639"));
+ // NTCB F5.2 - Single
verifyPosition(decoder, binary(
"404e5443010000000000000059009adb2a3e54250000000000ff1500040b0a1008291838001200760ee600000000000000000000000f1500040b0a10ac20703fb1aec23f00000000320149668f430000000000000000000000000000000000000000000000f3808080"),
position("2016-11-11 21:00:04.000", true, 53.74336, 87.14437));
+ // NTCB 5.2 - Array (1 message)
verifyPositions(decoder, binary(
"404e544300000000040000005a00c6812a3e410125e3a60700011705071503011030210c0000fa200910e6000000000000000000000001082106150010ae97643f88a39f3f0000000090001fcc6c450000000000000000000000000000000000000000000000f6808080"));
-
verifyPositions(decoder, binary(
"404e544301000000000000005a002e6c2a3e410125d7540100001512233a0b0a0f08026300000a000b000b00020000000000000000000c12233b0b0a0f03fd6d3f0fde603f00000000ba0051e0c845000000000000000000000000000000000000000000000080808080"));
-
verifyPositions(decoder, binary(
"404E5443010000007B0000005A0050692A3E410125DB0E00000015110707110A0C0880630000AA39A2381600020000000000000000000C110708110A0CB389793F1AEF263F00000000120034F516440000000000000000000000FAFF000000FAFF000000FAFF80808080"));
- verifyNull(decoder, binary(
- "404E5443010000007B000000130047372A3E533A383631373835303035313236303639"));
+ // NTCB 6.0 - Single
+ verifyPosition(decoder, binary(
+ "404e544301000000cdfbf5027200852e2a3e5406aa170000c11116162410001310a9110e80996b281003000a0008000000000000000000d207d207ffffff00fbff00fbff00fbff00fbff00fbff00fbff00fbff2d808080ffffffffffff2b161624100013509b0302b0f89201830500000000000037002fb8cf43eed5843a35003500"),
+ position("2019-01-16 22:22:36.000", true, 56.31800, 44.01523));
+ // NTCB 6.0 - Array (1 message)
verifyPositions(decoder, binary(
"404e54430100000045635902730081972a3e4101060b7e0e000b171328050d00133029110e00bc6141100200000000000000000000000000d207d307ffffff00fbff00fbff00fbff00fbff00fbff00fbff00fbff02808080ffffffffffff4f1328050d001371cd0302c5109101a60300000000000000003d1b37470000000096009600"));
+ // FLEX 1.0 - Negotiation
+ verifyNull(decoder, binary(
+ "404e544301000000aaecf6021300c8712a3e464c4558b00a0a45ffff300a08080f8388"));
+
+ // FLEX 1.0 - Single
+ verifyPosition(decoder, binary(
+ "7e54040000000400000030129957405c000b00632f9857405ccace03021e129101a103000000000000c4005ba3fe3b00000000120046100000000000001aff7f000080bfffff80000080bfffffffff9f"),
+ position("2019-01-17 10:23:20.000", true, 56.33996, 43.80762));
+
+ // FLEX 1.0 - Array (1 message)
+ verifyPositions(decoder, binary(
+ "7e4101080000000917c057405c002b001833c057405cbbce030225129101a00300007c6102408900400c1b3cfce3b23a12004710e000000000001bff7f000080bfffff80000080bfffffffffb2"));
}
}