aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--setup/default.xml1
-rw-r--r--src/org/traccar/BaseProtocolDecoder.java4
-rw-r--r--src/org/traccar/MainEventHandler.java68
-rw-r--r--src/org/traccar/ServerManager.java2
-rw-r--r--src/org/traccar/geocoder/GoogleGeocoder.java5
-rw-r--r--src/org/traccar/geocoder/JsonGeocoder.java9
-rw-r--r--src/org/traccar/protocol/AtrackProtocolDecoder.java7
-rw-r--r--src/org/traccar/protocol/Gl200TextProtocolDecoder.java8
-rw-r--r--src/org/traccar/protocol/GoSafeProtocolDecoder.java2
-rw-r--r--src/org/traccar/protocol/ItsProtocolDecoder.java9
-rw-r--r--src/org/traccar/protocol/L100FrameDecoder.java31
-rw-r--r--src/org/traccar/protocol/L100ProtocolDecoder.java70
-rw-r--r--src/org/traccar/protocol/MeiligaoProtocolDecoder.java2
-rw-r--r--src/org/traccar/protocol/NyitechProtocol.java37
-rw-r--r--src/org/traccar/protocol/NyitechProtocolDecoder.java123
-rw-r--r--src/org/traccar/protocol/SpotProtocolDecoder.java11
-rw-r--r--src/org/traccar/protocol/WristbandProtocolDecoder.java80
-rw-r--r--src/org/traccar/sms/HttpSmsClient.java7
-rw-r--r--swagger.json60
-rw-r--r--test/org/traccar/protocol/AtrackProtocolDecoderTest.java3
-rw-r--r--test/org/traccar/protocol/Gl200TextProtocolDecoderTest.java3
-rw-r--r--test/org/traccar/protocol/GoSafeProtocolDecoderTest.java3
-rw-r--r--test/org/traccar/protocol/L100FrameDecoderTest.java4
-rw-r--r--test/org/traccar/protocol/L100ProtocolDecoderTest.java9
-rw-r--r--test/org/traccar/protocol/NyitechProtocolDecoderTest.java30
-rw-r--r--test/org/traccar/protocol/WristbandProtocolDecoderTest.java18
26 files changed, 549 insertions, 57 deletions
diff --git a/setup/default.xml b/setup/default.xml
index ee66774ec..f0aecddb4 100644
--- a/setup/default.xml
+++ b/setup/default.xml
@@ -258,5 +258,6 @@
<entry key='its.port'>5179</entry>
<entry key='xrb28.port'>5180</entry>
<entry key='c2stek.port'>5181</entry>
+ <entry key='nyitech.port'>5182</entry>
</properties>
diff --git a/src/org/traccar/BaseProtocolDecoder.java b/src/org/traccar/BaseProtocolDecoder.java
index 7d840960b..cbef4568d 100644
--- a/src/org/traccar/BaseProtocolDecoder.java
+++ b/src/org/traccar/BaseProtocolDecoder.java
@@ -55,13 +55,13 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder {
return protocol != null ? protocol.getName() : PROTOCOL_UNKNOWN;
}
- public String getServer(Channel channel) {
+ public String getServer(Channel channel, char delimiter) {
String server = config.getString(getProtocolName() + ".server");
if (server == null && channel != null) {
InetSocketAddress address = (InetSocketAddress) channel.localAddress();
server = address.getAddress().getHostAddress() + ":" + address.getPort();
}
- return server;
+ return server != null ? server.replace(':', delimiter) : null;
}
protected double convertSpeed(double value, String defaultUnits) {
diff --git a/src/org/traccar/MainEventHandler.java b/src/org/traccar/MainEventHandler.java
index 3032646c1..d4e1ff860 100644
--- a/src/org/traccar/MainEventHandler.java
+++ b/src/org/traccar/MainEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 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.
@@ -29,23 +29,28 @@ import org.traccar.model.Position;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.Set;
public class MainEventHandler extends ChannelInboundHandlerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(GeocoderHandler.class);
+ private static final String DEFAULT_LOGGER_EVENTS = "time,position,speed,course,accuracy,result";
private final Set<String> connectionlessProtocols = new HashSet<>();
+ private final Set<String> logEvents;
public MainEventHandler() {
String connectionlessProtocolList = Context.getConfig().getString("status.ignoreOffline");
if (connectionlessProtocolList != null) {
connectionlessProtocols.addAll(Arrays.asList(connectionlessProtocolList.split(",")));
}
+ logEvents = new LinkedHashSet<>(Arrays.asList(
+ Context.getConfig().getString("logger.events", DEFAULT_LOGGER_EVENTS).split(",")));
}
@Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof Position) {
Position position = (Position) msg;
@@ -61,19 +66,45 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter {
StringBuilder s = new StringBuilder();
s.append(formatChannel(ctx.channel())).append(" ");
s.append("id: ").append(uniqueId);
- s.append(", time: ").append(DateUtil.formatDate(position.getFixTime(), false));
- s.append(", lat: ").append(String.format("%.5f", position.getLatitude()));
- s.append(", lon: ").append(String.format("%.5f", position.getLongitude()));
- if (position.getSpeed() > 0) {
- s.append(", speed: ").append(String.format("%.1f", position.getSpeed()));
- }
- s.append(", course: ").append(String.format("%.1f", position.getCourse()));
- if (position.getAccuracy() > 0) {
- s.append(", accuracy: ").append(String.format("%.1f", position.getAccuracy()));
- }
- Object cmdResult = position.getAttributes().get(Position.KEY_RESULT);
- if (cmdResult != null) {
- s.append(", result: ").append(cmdResult);
+ for (String event : logEvents) {
+ switch (event) {
+ case "time":
+ s.append(", time: ").append(DateUtil.formatDate(position.getFixTime(), false));
+ break;
+ case "position":
+ s.append(", lat: ").append(String.format("%.5f", position.getLatitude()));
+ s.append(", lon: ").append(String.format("%.5f", position.getLongitude()));
+ break;
+ case "speed":
+ if (position.getSpeed() > 0) {
+ s.append(", speed: ").append(String.format("%.1f", position.getSpeed()));
+ }
+ break;
+ case "course":
+ s.append(", course: ").append(String.format("%.1f", position.getCourse()));
+ break;
+ case "accuracy":
+ if (position.getAccuracy() > 0) {
+ s.append(", accuracy: ").append(String.format("%.1f", position.getAccuracy()));
+ }
+ break;
+ case "outdated":
+ if (position.getOutdated()) {
+ s.append(", outdated");
+ }
+ break;
+ case "invalid":
+ if (!position.getValid()) {
+ s.append(", invalid");
+ }
+ break;
+ default:
+ Object value = position.getAttributes().get(event);
+ if (value != null) {
+ s.append(", ").append(event).append(": ").append(value);
+ }
+ break;
+ }
}
LOGGER.info(s.toString());
@@ -104,13 +135,16 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter {
}
@Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+ while (cause.getCause() != null && cause.getCause() != cause) {
+ cause = cause.getCause();
+ }
LOGGER.warn(formatChannel(ctx.channel()) + " error", cause);
closeChannel(ctx.channel());
}
@Override
- public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
LOGGER.info(formatChannel(ctx.channel()) + " timed out");
closeChannel(ctx.channel());
diff --git a/src/org/traccar/ServerManager.java b/src/org/traccar/ServerManager.java
index cdc679e8a..6a3273402 100644
--- a/src/org/traccar/ServerManager.java
+++ b/src/org/traccar/ServerManager.java
@@ -88,7 +88,7 @@ public class ServerManager {
try {
server.start();
} catch (BindException e) {
- LOGGER.warn("One of the protocols is disabled due to port conflict");
+ LOGGER.warn("Port {} is disabled due to conflict", server.getPort());
}
}
}
diff --git a/src/org/traccar/geocoder/GoogleGeocoder.java b/src/org/traccar/geocoder/GoogleGeocoder.java
index af9b58a90..9494cab45 100644
--- a/src/org/traccar/geocoder/GoogleGeocoder.java
+++ b/src/org/traccar/geocoder/GoogleGeocoder.java
@@ -90,4 +90,9 @@ public class GoogleGeocoder extends JsonGeocoder {
return null;
}
+ @Override
+ protected String parseError(JsonObject json) {
+ return json.getString("error_message");
+ }
+
}
diff --git a/src/org/traccar/geocoder/JsonGeocoder.java b/src/org/traccar/geocoder/JsonGeocoder.java
index 80123e01e..ed59a1d8d 100644
--- a/src/org/traccar/geocoder/JsonGeocoder.java
+++ b/src/org/traccar/geocoder/JsonGeocoder.java
@@ -64,10 +64,11 @@ public abstract class JsonGeocoder implements Geocoder {
}
return formattedAddress;
} else {
+ String msg = "Empty address. Error: " + parseError(json);
if (callback != null) {
- callback.onFailure(new GeocoderException("Empty address"));
+ callback.onFailure(new GeocoderException(msg));
} else {
- LOGGER.warn("Empty address");
+ LOGGER.warn(msg);
}
}
return null;
@@ -113,4 +114,8 @@ public abstract class JsonGeocoder implements Geocoder {
public abstract Address parseAddress(JsonObject json);
+ protected String parseError(JsonObject json) {
+ return null;
+ }
+
}
diff --git a/src/org/traccar/protocol/AtrackProtocolDecoder.java b/src/org/traccar/protocol/AtrackProtocolDecoder.java
index 1963763ed..71bb6791c 100644
--- a/src/org/traccar/protocol/AtrackProtocolDecoder.java
+++ b/src/org/traccar/protocol/AtrackProtocolDecoder.java
@@ -381,7 +381,7 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder {
.number("(d+),") // speed
.number("(d+),") // outputs
.number("(d+),") // adc
- .number("[^,]*,") // driver
+ .number("([^,]+)?,") // driver
.number("(d+),") // temp1
.number("(d+),") // temp2
.expression("[^,]*,") // text message
@@ -455,6 +455,11 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_OUTPUT, parser.nextInt());
position.set(Position.PREFIX_ADC + 1, parser.nextInt());
+
+ if (parser.hasNext()) {
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next());
+ }
+
position.set(Position.PREFIX_TEMP + 1, parser.nextInt());
position.set(Position.PREFIX_TEMP + 2, parser.nextInt());
diff --git a/src/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/org/traccar/protocol/Gl200TextProtocolDecoder.java
index 97cc8f987..31ff4a670 100644
--- a/src/org/traccar/protocol/Gl200TextProtocolDecoder.java
+++ b/src/org/traccar/protocol/Gl200TextProtocolDecoder.java
@@ -1079,9 +1079,11 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
return null;
}
- int hdop = parser.nextInt();
- position.setValid(hdop > 0);
- position.set(Position.KEY_HDOP, hdop);
+ if (parser.hasNext()) {
+ int hdop = parser.nextInt();
+ position.setValid(hdop > 0);
+ position.set(Position.KEY_HDOP, hdop);
+ }
position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0)));
position.setCourse(parser.nextDouble(0));
diff --git a/src/org/traccar/protocol/GoSafeProtocolDecoder.java b/src/org/traccar/protocol/GoSafeProtocolDecoder.java
index 06bd0873d..95ef18f20 100644
--- a/src/org/traccar/protocol/GoSafeProtocolDecoder.java
+++ b/src/org/traccar/protocol/GoSafeProtocolDecoder.java
@@ -112,7 +112,7 @@ public class GoSafeProtocolDecoder extends BaseProtocolDecoder {
break;
case "COT":
if (index < values.length) {
- position.set(Position.KEY_ODOMETER, Integer.parseInt(values[index++]));
+ position.set(Position.KEY_ODOMETER, Long.parseLong(values[index++]));
}
if (index < values.length) {
String[] hours = values[index].split("-");
diff --git a/src/org/traccar/protocol/ItsProtocolDecoder.java b/src/org/traccar/protocol/ItsProtocolDecoder.java
index d3cdb14c3..000e7759f 100644
--- a/src/org/traccar/protocol/ItsProtocolDecoder.java
+++ b/src/org/traccar/protocol/ItsProtocolDecoder.java
@@ -18,6 +18,7 @@ package org.traccar.protocol;
import io.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
import org.traccar.DeviceSession;
+import org.traccar.NetworkMessage;
import org.traccar.Protocol;
import org.traccar.helper.Parser;
import org.traccar.helper.PatternBuilder;
@@ -72,7 +73,13 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder {
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
- Parser parser = new Parser(PATTERN, (String) msg);
+ String sentence = (String) msg;
+
+ if (channel != null && sentence.startsWith("$,01,")) {
+ channel.writeAndFlush(new NetworkMessage("$,01,", remoteAddress));
+ }
+
+ Parser parser = new Parser(PATTERN, sentence);
if (!parser.matches()) {
return null;
}
diff --git a/src/org/traccar/protocol/L100FrameDecoder.java b/src/org/traccar/protocol/L100FrameDecoder.java
index b7caa14f4..9e08120dd 100644
--- a/src/org/traccar/protocol/L100FrameDecoder.java
+++ b/src/org/traccar/protocol/L100FrameDecoder.java
@@ -20,6 +20,8 @@ import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import org.traccar.BaseFrameDecoder;
+import java.nio.charset.StandardCharsets;
+
public class L100FrameDecoder extends BaseFrameDecoder {
@Override
@@ -30,6 +32,15 @@ public class L100FrameDecoder extends BaseFrameDecoder {
return null;
}
+ if (buf.getCharSequence(buf.readerIndex(), 4, StandardCharsets.US_ASCII).equals("ATL,")) {
+ return decodeNew(buf);
+ } else {
+ return decodeOld(buf);
+ }
+ }
+
+ private Object decodeOld(ByteBuf buf) {
+
int header = buf.getByte(buf.readerIndex());
boolean obd = header == 'L' || header == 'H';
@@ -38,9 +49,9 @@ public class L100FrameDecoder extends BaseFrameDecoder {
index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '*');
} else {
index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x02);
- if (index == -1) {
+ if (index < 0) {
index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x04);
- if (index == -1) {
+ if (index < 0) {
return null;
}
}
@@ -60,4 +71,20 @@ public class L100FrameDecoder extends BaseFrameDecoder {
return null;
}
+ private Object decodeNew(ByteBuf buf) {
+
+ int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '@');
+ if (index < 0) {
+ return null;
+ }
+
+ if (buf.writerIndex() >= index + 1) {
+ ByteBuf frame = buf.readRetainedSlice(index - buf.readerIndex());
+ buf.skipBytes(1); // delimiter
+ return frame;
+ }
+
+ return null;
+ }
+
}
diff --git a/src/org/traccar/protocol/L100ProtocolDecoder.java b/src/org/traccar/protocol/L100ProtocolDecoder.java
index 07c5622cb..4d6b8f34b 100644
--- a/src/org/traccar/protocol/L100ProtocolDecoder.java
+++ b/src/org/traccar/protocol/L100ProtocolDecoder.java
@@ -40,6 +40,7 @@ public class L100ProtocolDecoder extends BaseProtocolDecoder {
private static final Pattern PATTERN = new PatternBuilder()
.text("ATL")
+ .expression(",[^,]+,").optional()
.number("(d{15}),") // imei
.text("$GPRMC,")
.number("(dd)(dd)(dd)") // time (hhmmss.sss)
@@ -109,6 +110,27 @@ public class L100ProtocolDecoder extends BaseProtocolDecoder {
.expression("(.+)") // data
.compile();
+ private static final Pattern PATTERN_NEW = new PatternBuilder()
+ .groupBegin()
+ .text("ATL,")
+ .expression("[LH],") // archive
+ .number("(d{15}),") // imei
+ .groupEnd("?")
+ .expression("([NPT]),") // alarm
+ .number("(dd)(dd)(dd),") // date (ddmmyy)
+ .number("(dd)(dd)(dd),") // time (hhmmss)
+ .expression("([AV]),") // validity
+ .number("(d+.d+),([NS]),") // latitude
+ .number("(d+.d+),([EW]),") // longitude
+ .number("(d+.?d*),") // speed
+ .expression("(?:GPS|GSM|INV),")
+ .number("(d+),") // battery
+ .number("(d+),") // mcc
+ .number("(d+),") // mnc
+ .number("(d+),") // lac
+ .number("(d+)") // cid
+ .compile();
+
@Override
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
@@ -121,6 +143,8 @@ public class L100ProtocolDecoder extends BaseProtocolDecoder {
} else {
return decodeObdLocation(channel, remoteAddress, sentence);
}
+ } else if (!sentence.contains("$GPRMC")) {
+ return decodeNew(channel, remoteAddress, sentence);
} else {
return decodeNormal(channel, remoteAddress, sentence);
}
@@ -264,4 +288,50 @@ public class L100ProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private Object decodeNew(Channel channel, SocketAddress remoteAddress, String sentence) {
+
+ Parser parser = new Parser(PATTERN_NEW, sentence);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ String imei = parser.next();
+ DeviceSession deviceSession;
+ if (imei != null) {
+ deviceSession = getDeviceSession(channel, remoteAddress, imei);
+ } else {
+ deviceSession = getDeviceSession(channel, remoteAddress);
+ }
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ switch (parser.next()) {
+ case "P":
+ position.set(Position.KEY_ALARM, Position.ALARM_SOS);
+ break;
+ case "T":
+ position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING);
+ break;
+ default:
+ break;
+ }
+
+ position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS));
+ position.setValid(parser.next().equals("A"));
+ position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM));
+ position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM));
+ position.setSpeed(parser.nextDouble());
+
+ position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001);
+
+ position.setNetwork(new Network(CellTower.from(
+ parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextHexInt())));
+
+ return position;
+ }
+
}
diff --git a/src/org/traccar/protocol/MeiligaoProtocolDecoder.java b/src/org/traccar/protocol/MeiligaoProtocolDecoder.java
index 06ecba357..cbfc3660a 100644
--- a/src/org/traccar/protocol/MeiligaoProtocolDecoder.java
+++ b/src/org/traccar/protocol/MeiligaoProtocolDecoder.java
@@ -402,7 +402,7 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder {
sendResponse(channel, remoteAddress, id, MSG_HEARTBEAT, response);
return null;
} else if (command == MSG_SERVER) {
- ByteBuf response = Unpooled.copiedBuffer(getServer(channel), StandardCharsets.US_ASCII);
+ ByteBuf response = Unpooled.copiedBuffer(getServer(channel, ':'), StandardCharsets.US_ASCII);
sendResponse(channel, remoteAddress, id, MSG_SERVER, response);
return null;
} else if (command == MSG_UPLOAD_PHOTO) {
diff --git a/src/org/traccar/protocol/NyitechProtocol.java b/src/org/traccar/protocol/NyitechProtocol.java
new file mode 100644
index 000000000..58974be5c
--- /dev/null
+++ b/src/org/traccar/protocol/NyitechProtocol.java
@@ -0,0 +1,37 @@
+/*
+ * 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.handler.codec.LengthFieldBasedFrameDecoder;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+
+import java.nio.ByteOrder;
+
+public class NyitechProtocol extends BaseProtocol {
+
+ public NyitechProtocol() {
+ addServer(new TrackerServer(false, getName()) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline) {
+ pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 2, -4, 0, true));
+ pipeline.addLast(new NyitechProtocolDecoder(NyitechProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/org/traccar/protocol/NyitechProtocolDecoder.java b/src/org/traccar/protocol/NyitechProtocolDecoder.java
new file mode 100644
index 000000000..e145205f7
--- /dev/null
+++ b/src/org/traccar/protocol/NyitechProtocolDecoder.java
@@ -0,0 +1,123 @@
+/*
+ * 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 org.traccar.BaseProtocolDecoder;
+import org.traccar.DeviceSession;
+import org.traccar.Protocol;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.DateBuilder;
+import org.traccar.helper.UnitsConverter;
+import org.traccar.model.Position;
+
+import java.net.SocketAddress;
+import java.nio.charset.StandardCharsets;
+
+public class NyitechProtocolDecoder extends BaseProtocolDecoder {
+
+ public NyitechProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ public static final short MSG_LOGIN = 0x1001;
+ public static final short MSG_COMPREHENSIVE_LIVE = 0x2001;
+ public static final short MSG_COMPREHENSIVE_HISTORY = 0x2002;
+ public static final short MSG_ALARM = 0x2003;
+ public static final short MSG_FIXED = 0x2004;
+
+ private void decodeLocation(Position position, ByteBuf buf) {
+
+ DateBuilder dateBuilder = new DateBuilder()
+ .setDateReverse(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte())
+ .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte());
+ position.setTime(dateBuilder.getDate());
+
+ int flags = buf.readUnsignedByte();
+ position.setValid(BitUtil.to(flags, 2) > 0);
+
+ double lat = buf.readUnsignedIntLE() / 3600000.0;
+ double lon = buf.readUnsignedIntLE() / 3600000.0;
+
+ position.setLatitude(BitUtil.check(flags, 2) ? lat : -lat);
+ position.setLongitude(BitUtil.check(flags, 3) ? lon : -lon);
+
+ position.setSpeed(UnitsConverter.knotsFromCps(buf.readUnsignedShortLE()));
+ position.setCourse(buf.readUnsignedShortLE() * 0.1);
+ position.setAltitude(buf.readShortLE() * 0.1);
+ }
+
+ private String decodeAlarm(int type) {
+ switch (type) {
+ case 0x09:
+ return Position.ALARM_ACCELERATION;
+ case 0x0a:
+ return Position.ALARM_BRAKING;
+ case 0x0b:
+ return Position.ALARM_CORNERING;
+ case 0x0e:
+ return Position.ALARM_SOS;
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ ByteBuf buf = (ByteBuf) msg;
+
+ buf.skipBytes(2); // header
+ buf.readUnsignedShortLE(); // length
+
+ String id = buf.readCharSequence(12, StandardCharsets.US_ASCII).toString();
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id);
+ if (deviceSession == null) {
+ return null;
+ }
+
+ int type = buf.readUnsignedShortLE();
+
+ if (type != MSG_LOGIN && type != MSG_COMPREHENSIVE_LIVE
+ && type != MSG_COMPREHENSIVE_HISTORY && type != MSG_ALARM && type != MSG_FIXED) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ if (type == MSG_COMPREHENSIVE_LIVE || type == MSG_COMPREHENSIVE_HISTORY) {
+ buf.skipBytes(6); // time
+ buf.skipBytes(3); // data
+ } else if (type == MSG_ALARM) {
+ buf.readUnsignedShortLE(); // random number
+ buf.readUnsignedByte(); // tag
+ position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte()));
+ buf.readUnsignedShortLE(); // threshold
+ buf.readUnsignedShortLE(); // value
+ buf.skipBytes(6); // time
+ } else if (type == MSG_FIXED) {
+ buf.skipBytes(6); // time
+ }
+
+ decodeLocation(position, buf);
+
+ return position;
+ }
+
+}
diff --git a/src/org/traccar/protocol/SpotProtocolDecoder.java b/src/org/traccar/protocol/SpotProtocolDecoder.java
index 78b2b0487..da36c2048 100644
--- a/src/org/traccar/protocol/SpotProtocolDecoder.java
+++ b/src/org/traccar/protocol/SpotProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 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.
@@ -49,7 +49,14 @@ public class SpotProtocolDecoder extends BaseHttpProtocolDecoder {
public SpotProtocolDecoder(Protocol protocol) {
super(protocol);
try {
- documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
+ builderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ builderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ builderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ builderFactory.setXIncludeAware(false);
+ builderFactory.setExpandEntityReferences(false);
+ documentBuilder = builderFactory.newDocumentBuilder();
xPath = XPathFactory.newInstance().newXPath();
messageExpression = xPath.compile("//messageList/message");
} catch (ParserConfigurationException | XPathExpressionException e) {
diff --git a/src/org/traccar/protocol/WristbandProtocolDecoder.java b/src/org/traccar/protocol/WristbandProtocolDecoder.java
index 4e044c915..7f2b0af85 100644
--- a/src/org/traccar/protocol/WristbandProtocolDecoder.java
+++ b/src/org/traccar/protocol/WristbandProtocolDecoder.java
@@ -25,7 +25,10 @@ import org.traccar.Protocol;
import org.traccar.helper.Parser;
import org.traccar.helper.PatternBuilder;
import org.traccar.helper.UnitsConverter;
+import org.traccar.model.CellTower;
+import org.traccar.model.Network;
import org.traccar.model.Position;
+import org.traccar.model.WifiAccessPoint;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
@@ -46,12 +49,16 @@ public class WristbandProtocolDecoder extends BaseProtocolDecoder {
Channel channel, String imei, String version, int type, String data) {
if (channel != null) {
- String sentence = String.format("YX%s|%s|0|{F%d#%s}\r\n", imei, version, type, data);
+ String sentence = String.format("YX%s|%s|0|{F%02d#%s}\r\n", imei, version, type, data);
ByteBuf response = Unpooled.buffer();
- response.writeBytes(new byte[]{0x00, 0x01, 0x02});
- response.writeShort(sentence.length());
+ if (type != 91) {
+ response.writeBytes(new byte[]{0x00, 0x01, 0x02});
+ response.writeShort(sentence.length());
+ }
response.writeCharSequence(sentence, StandardCharsets.US_ASCII);
- response.writeBytes(new byte[]{(byte) 0xFF, (byte) 0xFE, (byte) 0xFC});
+ if (type != 91) {
+ response.writeBytes(new byte[]{(byte) 0xFF, (byte) 0xFE, (byte) 0xFC});
+ }
channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress()));
}
}
@@ -59,7 +66,7 @@ public class WristbandProtocolDecoder extends BaseProtocolDecoder {
private static final Pattern PATTERN = new PatternBuilder()
.expression("..") // header
.number("(d+)|") // imei
- .number("(vd+.d+)|") // version
+ .number("([vV]d+.d+)|") // version
.number("d+|") // model
.text("{")
.number("F(d+)") // function
@@ -81,12 +88,56 @@ public class WristbandProtocolDecoder extends BaseProtocolDecoder {
position.setValid(true);
position.setLongitude(Double.parseDouble(values[0]));
position.setLatitude(Double.parseDouble(values[1]));
- position.setTime(new SimpleDateFormat("yyyyMMddHHmmss").parse(values[2]));
+ position.setTime(new SimpleDateFormat("yyyyMMddHHmm").parse(values[2]));
position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[3])));
return position;
}
+ private Position decodeStatus(DeviceSession deviceSession, String sentence) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ getLastLocation(position, null);
+
+ position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(sentence.split(",")[0]));
+
+ return position;
+ }
+
+ private Position decodeNetwork(DeviceSession deviceSession, String sentence, boolean wifi) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ getLastLocation(position, null);
+
+ Network network = new Network();
+ String[] fragments = sentence.split("\\|");
+
+ if (wifi) {
+ for (String item : fragments[0].split("_")) {
+ String[] values = item.split(",");
+ network.addWifiAccessPoint(WifiAccessPoint.from(values[0], Integer.parseInt(values[1])));
+ }
+ }
+
+ for (String item : fragments[wifi ? 1 : 0].split(":")) {
+ String[] values = item.split(",");
+ int lac = Integer.parseInt(values[0]);
+ int mnc = Integer.parseInt(values[1]);
+ int mcc = Integer.parseInt(values[2]);
+ int cid = Integer.parseInt(values[3]);
+ int rssi = Integer.parseInt(values[4]);
+ network.addCellTower(CellTower.from(mcc, mnc, lac, cid, rssi));
+ }
+
+ position.setNetwork(network);
+
+ return position;
+ }
+
private List<Position> decodeMessage(
Channel channel, SocketAddress remoteAddress, String sentence) throws ParseException {
@@ -105,23 +156,32 @@ public class WristbandProtocolDecoder extends BaseProtocolDecoder {
int type = parser.nextInt();
List<Position> positions = new LinkedList<>();
+ String data = parser.next();
switch (type) {
case 90:
- sendResponse(channel, imei, version, type, getServer(channel));
+ sendResponse(channel, imei, version, type, getServer(channel, ','));
break;
case 91:
String time = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
- sendResponse(channel, imei, version, type, time + "|" + getServer(channel));
+ sendResponse(channel, imei, version, type, time + "|" + getServer(channel, ','));
break;
case 1:
- sendResponse(channel, imei, version, type, "0");
+ positions.add(decodeStatus(deviceSession, data));
+ sendResponse(channel, imei, version, type, data.split(",")[1]);
break;
case 2:
- for (String fragment : parser.next().split("\\|")) {
+ for (String fragment : data.split("\\|")) {
positions.add(decodePosition(deviceSession, fragment));
}
break;
+ case 3:
+ case 4:
+ positions.add(decodeNetwork(deviceSession, data, type == 3));
+ break;
+ case 64:
+ sendResponse(channel, imei, version, type, data);
+ break;
default:
break;
}
diff --git a/src/org/traccar/sms/HttpSmsClient.java b/src/org/traccar/sms/HttpSmsClient.java
index 994e2db4e..8e2b67bf7 100644
--- a/src/org/traccar/sms/HttpSmsClient.java
+++ b/src/org/traccar/sms/HttpSmsClient.java
@@ -38,6 +38,7 @@ public class HttpSmsClient implements SmsManager {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpSmsClient.class);
private String url;
+ private String authorizationHeader;
private String authorization;
private String template;
private boolean encode;
@@ -45,6 +46,8 @@ public class HttpSmsClient implements SmsManager {
public HttpSmsClient() {
url = Context.getConfig().getString("sms.http.url");
+ authorizationHeader = Context.getConfig().getString("sms.http.authorizationHeader",
+ SecurityRequestFilter.AUTHORIZATION_HEADER);
authorization = Context.getConfig().getString("sms.http.authorization");
if (authorization == null) {
String user = Context.getConfig().getString("sms.http.user");
@@ -70,7 +73,7 @@ public class HttpSmsClient implements SmsManager {
try {
return template
.replace("{phone}", prepareValue(destAddress))
- .replace("{message}", prepareValue(message));
+ .replace("{message}", prepareValue(message.trim()));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
@@ -78,7 +81,7 @@ public class HttpSmsClient implements SmsManager {
private Invocation.Builder getRequestBuilder() {
return Context.getClient().target(url).request()
- .header(SecurityRequestFilter.AUTHORIZATION_HEADER, authorization);
+ .header(authorizationHeader, authorization);
}
@Override
diff --git a/swagger.json b/swagger.json
index 54ff10d1b..040e9d40e 100644
--- a/swagger.json
+++ b/swagger.json
@@ -1494,7 +1494,10 @@
"network": {
"type": "string"
},
- "attributes": {}
+ "attributes": {
+ "type": "object",
+ "additionalProperties": true
+ }
}
},
"User": {
@@ -1561,7 +1564,10 @@
"token": {
"type": "string"
},
- "attributes": {}
+ "attributes": {
+ "type": "object",
+ "additionalProperties": true
+ }
}
},
"Server": {
@@ -1614,7 +1620,10 @@
"coordinateFormat": {
"type": "string"
},
- "attributes": {}
+ "attributes": {
+ "type": "object",
+ "additionalProperties": true
+ }
}
},
"Command": {
@@ -1631,7 +1640,10 @@
"type": {
"type": "string"
},
- "attributes": {}
+ "attributes": {
+ "type": "object",
+ "additionalProperties": true
+ }
}
},
"Device": {
@@ -1680,7 +1692,10 @@
"type": "integer"
}
},
- "attributes": {}
+ "attributes": {
+ "type": "object",
+ "additionalProperties": true
+ }
}
},
"Group": {
@@ -1694,7 +1709,10 @@
"groupId": {
"type": "integer"
},
- "attributes": {}
+ "attributes": {
+ "type": "object",
+ "additionalProperties": true
+ }
}
},
"Permission": {
@@ -1758,7 +1776,10 @@
"calendarId": {
"type": "integer"
},
- "attributes": {}
+ "attributes": {
+ "type": "object",
+ "additionalProperties": true
+ }
}
},
"Notification": {
@@ -1784,7 +1805,10 @@
"calendarId": {
"type": "integer"
},
- "attributes": {}
+ "attributes": {
+ "type": "object",
+ "additionalProperties": true
+ }
}
},
"NotificationType": {
@@ -1819,7 +1843,10 @@
"maintenanceId": {
"type": "integer"
},
- "attributes": {}
+ "attributes": {
+ "type": "object",
+ "additionalProperties": true
+ }
}
},
"ReportSummary": {
@@ -2003,7 +2030,10 @@
"type": "string",
"description": "base64 encoded in iCalendar format"
},
- "atributes": {}
+ "attributes": {
+ "type": "object",
+ "additionalProperties": true
+ }
}
},
"Attribute": {
@@ -2037,7 +2067,10 @@
"uniqueId": {
"type": "string"
},
- "atributes": {}
+ "attributes": {
+ "type": "object",
+ "additionalProperties": true
+ }
}
},
"Maintenance": {
@@ -2057,7 +2090,10 @@
"period": {
"type": "number"
},
- "atributes": {}
+ "attributes": {
+ "type": "object",
+ "additionalProperties": true
+ }
}
}
},
diff --git a/test/org/traccar/protocol/AtrackProtocolDecoderTest.java b/test/org/traccar/protocol/AtrackProtocolDecoderTest.java
index 82b4c92af..3a9382086 100644
--- a/test/org/traccar/protocol/AtrackProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/AtrackProtocolDecoderTest.java
@@ -35,6 +35,9 @@ public class AtrackProtocolDecoderTest extends ProtocolTest {
decoder.setCustom(true);
verifyPositions(decoder, buffer(
+ "@P,9493,402,143,356961075931165,1546830150,1546830151,1546830151,-88429209,44271154,54,10,0,10,1,0,0,0,1858AE010000,2000,2000,\u001A,%CI%FL%ML%VN%PD%FC%EL%ET%AT%MF%MV%BV%DT%GN%GV%ME%RL%RP%SA%SM%TR%IA%MP,0,0,2T1KR32E28C706185,0,1,0,7,251,89,118,41,0,00A5001A040800A5001A040B00A5001A040C00A5001A040900A4001C040D00A50019040900A60019040900A4001B040B00A5001A040900A7001A040E\u001A,008CFE7C03C4\u001A,356961075931165,0,0,12,0,18,5,0\n"));
+
+ verifyPositions(decoder, buffer(
"@P,FD34,720,12256,357520076794151,1535445349,1535445354,1535500603,106784149,-6283086,105,2,138,0,3,0,0,0,,2000,2000,,%CI%TR%MV%BV%AT%SA%ET%GQ%GS%PC%RP%OD%AV1%XS%VS,0,0,0,0,0,0,0,0,1011677,0,138,0,0,0\r\n",
"1535445376,1535445374,1535500603,106783763,-6282981,105,101,138,6,2,0,0,0,,2000,2000,,%CI%TR%MV%BV%AT%SA%ET%GQ%GS%PC%RP%OD%AV1%XS%VS,0,141,41,60,12,0,0,7,1011677,0,138,0,0,0\r\n",
"1535445380,1535445378,1535500603,106783763,-6282981,105,103,138,6,2,0,0,0,,2000,2000,,%CI%TR%MV%BV%AT%SA%ET%GQ%GS%PC%RP%OD%AV1%XS%VS,0,135,41,61,12,0,0,9,1011677,0,138,0,0,0\r\n",
diff --git a/test/org/traccar/protocol/Gl200TextProtocolDecoderTest.java b/test/org/traccar/protocol/Gl200TextProtocolDecoderTest.java
index d68479097..ae4201f94 100644
--- a/test/org/traccar/protocol/Gl200TextProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/Gl200TextProtocolDecoderTest.java
@@ -10,6 +10,9 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest {
Gl200TextProtocolDecoder decoder = new Gl200TextProtocolDecoder(null);
+ verifyAttributes(decoder, buffer(
+ "+BUFF:GTSTC,410301,864802030022424,,,0,,,,,,,0228,0002,4EE8,1BFF489,00,20181207134332,EC90$"));
+
verifyPositions(decoder, buffer(
"+RESP:GTFRI,1A0900,860599000306845,G3-313,0,0,4,1,2.1,0,426.7,8.611466,47.681639,20181214134603,0228,0001,077F,4812,25.2,1,5.7,34,437.3,8.611600,47.681846,20181214134619,0228,0001,077F,4812,25.2,1,4.4,62,438.2,8.611893,47.681983,20181214134633,0228,0001,077F,4812,25.2,1,4.8,78,436.6,8.612236,47.682040,20181214134648,0228,0001,077F,4812,25.2,83,20181214134702,0654$"));
diff --git a/test/org/traccar/protocol/GoSafeProtocolDecoderTest.java b/test/org/traccar/protocol/GoSafeProtocolDecoderTest.java
index 9cd8e4800..70c86bb23 100644
--- a/test/org/traccar/protocol/GoSafeProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/GoSafeProtocolDecoderTest.java
@@ -11,6 +11,9 @@ public class GoSafeProtocolDecoderTest extends ProtocolTest {
GoSafeProtocolDecoder decoder = new GoSafeProtocolDecoder(null);
verifyPositions(decoder, text(
+ "*GS06,860078024226974,101437211218,,SYS:G3SC;V3.36;V1.1.8,GPS:A;7;N3.052302;E101.787216;16;137;48;1.58,COT:4261733103,ADC:22.86;0.58;0.01,DTT:4004;E1;0;0;0;3$101439211218,,SYS:G3SC;V3.36;V1.1.8,GPS:A;8;N3.052265;E101.787200;12;152;46;1.31,COT:4261733103,ADC:22.98;0.58;0.01,DTT:4004;E1;0;0;0;3$101441211218,,SYS:G3SC;V3.36;V1.1.8,GPS:A;8;N3.052247;E101.787232;8;131;46;1.34,COT:4261733103,ADC:23.13;0.58;0.01,DTT:4004;E1;0;0;0;3$101510211218,,SYS:G3SC;V3.36;V1.1.8,GPS:A;8;N3.052150;E101.787152;0;131;40;0.97,COT:4261733160,ADC:22.88;0.58;0.01,DTT:4000;E1;0;0;0;1$101540211218,,SYS:G3SC;V3.36;V1.1.8,GPS:A;7;N3.052150;E101.787152;0;131;40;0.97,COT:4261733160,ADC:22.91;0.58;0.00,DTT:4000;E1;0;0;0;1#"));
+
+ verifyPositions(decoder, text(
"*GS06,359568052570135,090349191018,,SYS:G3C;V1.40;V1.0.4,GPS:A;10;S26.112722;E28.078766;0;23;1581;0.80,COT:,ADC:10.86;3.79,DTT:4000;E7;0;0;0;1$090419191018,,SYS:G3C;V1.40;V1.0.4,GPS:A;10;S26.112722;E28.078766;0;23;1581;0.80,COT:,ADC:10.85;3.79,DTT:4000;E7;0;0;0;1#"));
verifyPositions(decoder, text(
diff --git a/test/org/traccar/protocol/L100FrameDecoderTest.java b/test/org/traccar/protocol/L100FrameDecoderTest.java
index a94f2eb0d..5ffa3d8d1 100644
--- a/test/org/traccar/protocol/L100FrameDecoderTest.java
+++ b/test/org/traccar/protocol/L100FrameDecoderTest.java
@@ -11,6 +11,10 @@ public class L100FrameDecoderTest extends ProtocolTest {
L100FrameDecoder decoder = new L100FrameDecoder();
verifyFrame(
+ binary("41544c2c4c2c3836383334353033383137313936332c4e2c3230313231382c3039333031362c412c3032352e3036373134342c4e2c3035352e3134343833332c452c3030302e302c4750532c333933392c3432342c30332c30303430352c303038383334"),
+ decoder.decode(null, null, binary("41544c2c4c2c3836383334353033383137313936332c4e2c3230313231382c3039333031362c412c3032352e3036373134342c4e2c3035352e3134343833332c452c3030302e302c4750532c333933392c3432342c30332c30303430352c30303838333440")));
+
+ verifyFrame(
binary("4c2c41544c2c3836363739353033303437373935322c30312c303033352c"),
decoder.decode(null, null, binary("4c2c41544c2c3836363739353033303437373935322c30312c303033352c2a28")));
diff --git a/test/org/traccar/protocol/L100ProtocolDecoderTest.java b/test/org/traccar/protocol/L100ProtocolDecoderTest.java
index e86ea0e81..04f586f7a 100644
--- a/test/org/traccar/protocol/L100ProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/L100ProtocolDecoderTest.java
@@ -10,6 +10,15 @@ public class L100ProtocolDecoderTest extends ProtocolTest {
L100ProtocolDecoder decoder = new L100ProtocolDecoder(null);
+ verifyPosition(decoder, text(
+ "ATL,NP,868004029750174,$GPRMC,062943,A,2533.6719,N,09154.3203,E,0,179,311218,,,*39,#01111011000000,0,0,0,934.82,27.13,4.0,25,405,755,15af,974b,0,0,0,ATL"));
+
+ verifyPosition(decoder, text(
+ "ATL,L,868345038171963,N,201218,093016,A,025.067144,N,055.144833,E,000.0,GPS,3939,424,03,00405,008834"));
+
+ verifyPosition(decoder, text(
+ "N,111116,090031,A,028.123456,N,077.123456,E,000.0,GPS,4180,404,11,00159,064753"));
+
verifyAttributes(decoder, text(
"L,ATLOBD,866795030475584,03,7429,143344,130918,CAN,0101:00076100,0103:0200,0104:3C,0105:84,010A:XX,010B:19,010C:0F98,010D:22,010E:68,010F:5A,0110:XXXX,0111:28,011C:20,011F:XXXX,0121:0000,0122:XXXX,012F:XX,0162:XX,0132:XXXX,0133:61,0143:00A8,0145:0F,0146:XX,0147:30,0148:XX,0149:31,014A:18,014B:XX,014C:92,0151:XX,0131:00BB,0144:8000,015E:XXXX,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0902:XXXXXXXXXXXXXXX"));
diff --git a/test/org/traccar/protocol/NyitechProtocolDecoderTest.java b/test/org/traccar/protocol/NyitechProtocolDecoderTest.java
new file mode 100644
index 000000000..4cafd7612
--- /dev/null
+++ b/test/org/traccar/protocol/NyitechProtocolDecoderTest.java
@@ -0,0 +1,30 @@
+package org.traccar.protocol;
+
+import org.junit.Test;
+import org.traccar.ProtocolTest;
+
+public class NyitechProtocolDecoderTest extends ProtocolTest {
+
+ @Test
+ public void testDecode() throws Exception {
+
+ NyitechProtocolDecoder decoder = new NyitechProtocolDecoder(null);
+
+ verifyPosition(decoder, binary(
+ "4040690030313436383230303238373201201c0c12031a308080801c0c12031a3007d67e7e08aceb841002000000ae08000000000000000000000000001e002900f0ffdd002700f2ffe0002700f2ffe1002400f0ffdf002400f3ffe3008a00ffff01010000a9c70d0a"));
+
+ verifyPosition(decoder, binary(
+ "4040390030313436383230303238373203200100010c000000001c0c1203192a1b0c12171d3104fed87d089288801000000000000011ec0d0a"));
+
+ verifyPosition(decoder, binary(
+ "4040480030313436383230303238373201101c0c12031a2907fa7e7e08b8eb841002000000bc080101040904040300010100000a818283848586878862611c0c12031a293f9c0d0a"));
+
+ verifyPosition(decoder, binary(
+ "40404b003247512d313630313030313901101e0b100604190c02c83707f887ac0f000000002d130101030304020000010100000d426162636465666768696a6ba51e0b1006041965c30d0a"));
+
+ verifyPosition(decoder, binary(
+ "4040490030313436383230303238373202201c0c120319348080001b0c12171d3104fed87d0892888010000000000000000000000000000000000000008b00ffff010100008a480d0a"));
+
+ }
+
+}
diff --git a/test/org/traccar/protocol/WristbandProtocolDecoderTest.java b/test/org/traccar/protocol/WristbandProtocolDecoderTest.java
index cedc06f79..5635ce3d4 100644
--- a/test/org/traccar/protocol/WristbandProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/WristbandProtocolDecoderTest.java
@@ -10,9 +10,27 @@ public class WristbandProtocolDecoderTest extends ProtocolTest {
WristbandProtocolDecoder decoder = new WristbandProtocolDecoder(null);
+ verifyNotNull(decoder, binary(
+ "000102004459583836383730343034343735303035357c56312e307c317c7b4630342331382c30372c332c3539303139322c33303a31382c30372c332c3539303139322c33307d0d0afffefc"));
+
+ verifyNotNull(decoder, binary(
+ "000102009c59583836383730343034343735303035357c56312e307c317c7b4630332330383a30353a38313a64383a31383a38372c2d37365f30303a37663a32383a63373a62613a63312c2d37375f39633a33643a63663a65643a62643a36622c2d36335f64383a65623a39373a65653a37373a32342c2d38327c31382c30372c332c3539303735342c33303a31382c30372c332c3539303735342c33307d0d0afffefc"));
+
+ verifyNull(decoder, binary(
+ "000102002259583836383730343034343735303035357c56312e307c307c7b46363423317d0d0afffefc"));
+
+ verifyPositions(decoder, binary(
+ "00010200bc59583836383730343034343735303035357c56312e307c317c7b4630322337372e3437373831372c2d33382e3839363239322c3230313831323239313235352c302e35387c37372e3437373739362c2d33382e3839363234352c3230313831323239313235352c302e30307c37372e3437373738392c2d33382e3839363233322c3230313831323239313235352c302e30307c37372e3437373737362c2d33382e3839363232322c3230313831323239313235352c302e30307d0d0afffefc"));
+
+ verifyNull(decoder, binary(
+ "000102004759583836383730343034343735303035357c56312e307c317c7b463931233331305f30307c30307c30307c30307c57414e444149323031382f31322f31342031353a35367d0d0afffefc"));
+
verifyNull(decoder, binary(
"000102004159583336373535313631303030303934347c56312e307c317c7b4639312330317c30307c30307c33475f7065745f323031382f30352f31362031313a30307d0d0afffefc"));
+ verifyPositions(decoder, false, binary(
+ "000102003559583836383730343034343735303035357c56312e307c317c7b4630312339342c312c3130302c302c33313030302c3930307d0d0afffefc"));
+
}
}