aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAnton Tananaev <anton.tananaev@gmail.com>2022-09-18 10:49:28 -0700
committerGitHub <noreply@github.com>2022-09-18 10:49:28 -0700
commitb118bd4cc8a64e768370ef19061e0f968136cf18 (patch)
tree0f88d08b2e51d5285dde5ed7acdace5293be8373 /src
parentd55ae464806430ee57bbd56737f024ead95748a2 (diff)
parent8532bffcec8e239c6699845e09e63da927f51d9a (diff)
downloadtrackermap-server-b118bd4cc8a64e768370ef19061e0f968136cf18.tar.gz
trackermap-server-b118bd4cc8a64e768370ef19061e0f968136cf18.tar.bz2
trackermap-server-b118bd4cc8a64e768370ef19061e0f968136cf18.zip
Merge branch 'master' into g1rus-dev
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/traccar/api/security/PermissionsService.java4
-rw-r--r--src/main/java/org/traccar/model/ExtendedModel.java5
-rw-r--r--src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java26
-rw-r--r--src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java12
-rw-r--r--src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java12
-rw-r--r--src/main/java/org/traccar/protocol/NavisProtocolDecoder.java9
-rw-r--r--src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java20
-rw-r--r--src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java32
-rw-r--r--src/main/java/org/traccar/protocol/NdtpV6Protocol.java39
-rw-r--r--src/main/java/org/traccar/protocol/NdtpV6ProtocolDecoder.java203
-rw-r--r--src/main/java/org/traccar/protocol/NdtpV6ProtocolEncoder.java42
-rw-r--r--src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java58
-rw-r--r--src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java49
-rw-r--r--src/main/java/org/traccar/protocol/ThurayaProtocol.java39
-rw-r--r--src/main/java/org/traccar/protocol/ThurayaProtocolDecoder.java195
-rw-r--r--src/main/java/org/traccar/session/cache/CacheManager.java2
-rw-r--r--src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java8
-rw-r--r--src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java4
-rw-r--r--src/test/java/org/traccar/protocol/MeitrackProtocolDecoderTest.java3
-rw-r--r--src/test/java/org/traccar/protocol/PiligrimProtocolDecoderTest.java4
-rw-r--r--src/test/java/org/traccar/protocol/TeltonikaProtocolDecoderTest.java3
-rw-r--r--src/test/java/org/traccar/protocol/ThurayaProtocolDecoderTest.java24
22 files changed, 758 insertions, 35 deletions
diff --git a/src/main/java/org/traccar/api/security/PermissionsService.java b/src/main/java/org/traccar/api/security/PermissionsService.java
index a494c0257..ddfaaab94 100644
--- a/src/main/java/org/traccar/api/security/PermissionsService.java
+++ b/src/main/java/org/traccar/api/security/PermissionsService.java
@@ -170,8 +170,10 @@ public class PermissionsService {
|| before.getFixedEmail() != after.getFixedEmail()) {
if (userId == after.getId()) {
checkAdmin(userId);
- } else {
+ } else if (after.getId() > 0) {
checkUser(userId, after.getId());
+ } else {
+ checkManager(userId);
}
}
if (before.getFixedEmail() && !before.getEmail().equals(after.getEmail())) {
diff --git a/src/main/java/org/traccar/model/ExtendedModel.java b/src/main/java/org/traccar/model/ExtendedModel.java
index 0fa1856d1..ef2e3b68f 100644
--- a/src/main/java/org/traccar/model/ExtendedModel.java
+++ b/src/main/java/org/traccar/model/ExtendedModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2022 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.
@@ -17,6 +17,7 @@ package org.traccar.model;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Objects;
public class ExtendedModel extends BaseModel {
@@ -31,7 +32,7 @@ public class ExtendedModel extends BaseModel {
}
public void setAttributes(Map<String, Object> attributes) {
- this.attributes = attributes;
+ this.attributes = Objects.requireNonNullElseGet(attributes, LinkedHashMap::new);
}
public void set(String key, Boolean value) {
diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java
index 741f4b35a..a9d77b46e 100644
--- a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java
@@ -34,6 +34,10 @@ import org.traccar.model.WifiAccessPoint;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
import java.util.regex.Pattern;
public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
@@ -78,7 +82,7 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
.text("$$")
.number("d+,") // length
.number("(d+),") // imei
- .number("x+,") // index
+ .number("(x+),") // index
.text("A03,") // type
.number("(d+)?,") // alarm
.number("(dd)(dd)(dd)") // date (yymmdd)
@@ -137,16 +141,20 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
.number("xx")
.compile();
- private void requestPhoto(Channel channel, SocketAddress socketAddress, String imei, String file) {
+ private void sendResponse(Channel channel, SocketAddress remoteAddress, String imei, String content) {
if (channel != null) {
- String content = "1,D06," + file + "," + photo.writerIndex() + "," + Math.min(1024, photo.writableBytes());
int length = 1 + imei.length() + 1 + content.length();
String response = String.format("##%02d,%s,%s*", length, imei, content);
response += Checksum.sum(response) + "\r\n";
- channel.writeAndFlush(new NetworkMessage(response, socketAddress));
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
}
}
+ private void requestPhoto(Channel channel, SocketAddress remoteAddress, String imei, String file) {
+ String content = "1,D06," + file + "," + photo.writerIndex() + "," + Math.min(1024, photo.writableBytes());
+ sendResponse(channel, remoteAddress, imei, content);
+ }
+
private String decodeAlarm(Integer alarm) {
if (alarm != null) {
switch (alarm) {
@@ -200,11 +208,14 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
return null;
}
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ String imei = parser.next();
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei);
if (deviceSession == null) {
return null;
}
+ String index = parser.next();
+
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
@@ -243,6 +254,11 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
position.setNetwork(network);
+ DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String response = index + ",A03," + dateFormat.format(new Date());
+ sendResponse(channel, remoteAddress, imei, response);
+
return position;
}
diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
index b65e4a4b8..7eeee5efb 100644
--- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
@@ -411,9 +411,12 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
case 0x11:
return Position.ALARM_POWER_OFF;
case 0x13:
+ case 0x25:
return Position.ALARM_TAMPERING;
case 0x14:
return Position.ALARM_DOOR;
+ case 0x23:
+ return Position.ALARM_FALL_DOWN;
case 0x29:
return Position.ALARM_ACCELERATION;
case 0x30:
@@ -423,8 +426,6 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
return Position.ALARM_CORNERING;
case 0x2C:
return Position.ALARM_ACCIDENT;
- case 0x23:
- return Position.ALARM_FALL_DOWN;
default:
return null;
}
@@ -986,6 +987,13 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
} else if (subType == 0x05) {
+ if (buf.readableBytes() >= 6 + 1 + 6) {
+ DateBuilder dateBuilder = new DateBuilder((TimeZone) deviceSession.get(DeviceSession.KEY_TIMEZONE))
+ .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte())
+ .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte());
+ position.setDeviceTime(dateBuilder.getDate());
+ }
+
int flags = buf.readUnsignedByte();
position.set(Position.KEY_DOOR, BitUtil.check(flags, 0));
position.set(Position.PREFIX_IO + 1, BitUtil.check(flags, 2));
diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
index 5393c6f74..0639b9dcf 100644
--- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
@@ -513,6 +513,18 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_MOTION, BitUtil.check(deviceStatus, 2));
position.set("cover", BitUtil.check(deviceStatus, 3));
break;
+ case 0xE6:
+ while (buf.readerIndex() < endIndex) {
+ int sensorIndex = buf.readUnsignedByte();
+ buf.skipBytes(6); // mac
+ position.set(
+ Position.PREFIX_TEMP + sensorIndex,
+ buf.readUnsignedByte() + buf.readUnsignedByte() * 0.01);
+ position.set(
+ "humidity" + sensorIndex,
+ buf.readUnsignedByte() + buf.readUnsignedByte() * 0.01);
+ }
+ break;
case 0xEB:
if (buf.getUnsignedShort(buf.readerIndex()) > 200) {
Network network = new Network();
diff --git a/src/main/java/org/traccar/protocol/NavisProtocolDecoder.java b/src/main/java/org/traccar/protocol/NavisProtocolDecoder.java
index 53631bd4e..77158b315 100644
--- a/src/main/java/org/traccar/protocol/NavisProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/NavisProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2022 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.
@@ -24,7 +24,6 @@ import org.traccar.NetworkMessage;
import org.traccar.Protocol;
import org.traccar.helper.BitUtil;
import org.traccar.helper.Checksum;
-import org.traccar.helper.Checksum.Algorithm;
import org.traccar.helper.DateBuilder;
import org.traccar.helper.UnitsConverter;
import org.traccar.model.Position;
@@ -591,10 +590,8 @@ public class NavisProtocolDecoder extends BaseProtocolDecoder {
private void sendFlexReply(Channel channel, ByteBuf data) {
if (channel != null) {
- ByteBuf cs = Unpooled.buffer(1);
- cs.writeByte(Checksum.crc8(new Algorithm(8, 0x31, 0xFF, false, false, 0x00), data.nioBuffer()));
-
- channel.writeAndFlush(new NetworkMessage(Unpooled.wrappedBuffer(data, cs), channel.remoteAddress()));
+ data.writeByte(Checksum.crc8(Checksum.CRC8_EGTS, data.nioBuffer()));
+ channel.writeAndFlush(new NetworkMessage(data, channel.remoteAddress()));
}
}
diff --git a/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java b/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java
index 9122eb362..08b1a8d0f 100644
--- a/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2021 - 2022 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.
@@ -196,7 +196,7 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder {
for (int j = 0; j < bits.length(); j++) {
if (bits.get(j)) {
- int value = 0;
+ int value;
switch (j + 1) {
case 1:
@@ -278,11 +278,11 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder {
case 42:
case 43:
value = buf.readUnsignedShortLE();
- position.set("rs485Fuel" + (j + 2 - 38), (value < 65500) ? value : null);
+ position.set("fuel" + (j + 2 - 38), (value < 65500) ? value : null);
break;
case 44:
value = buf.readUnsignedShortLE();
- position.set("rs232Fuel", (value < 65500) ? value : null);
+ position.set(Position.KEY_FUEL_LEVEL, (value < 65500) ? value : null);
break;
case 45:
case 46:
@@ -293,7 +293,17 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder {
case 51:
case 52:
value = buf.readByte();
- position.set(Position.PREFIX_TEMP + (j + 2 - 45), (value != 0x80) ? value : null);
+ position.set(
+ Position.PREFIX_TEMP + (j + 2 - 45),
+ (value != (byte) 0x80) ? value : null);
+ break;
+ case 78:
+ case 79:
+ case 80:
+ case 81:
+ case 82:
+ case 83:
+ position.set("fuelTemp" + (j + 2 - 78), (int) buf.readByte());
break;
default:
buf.skipBytes(getItemLength(j + 1));
diff --git a/src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java b/src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java
new file mode 100644
index 000000000..5b610013a
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 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 org.traccar.BaseFrameDecoder;
+
+public class NdtpV6FrameDecoder extends BaseFrameDecoder {
+
+ @Override
+ protected Object decode(
+ ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception {
+
+ return buf;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/NdtpV6Protocol.java b/src/main/java/org/traccar/protocol/NdtpV6Protocol.java
new file mode 100644
index 000000000..ce0dbbef2
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/NdtpV6Protocol.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 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.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 NdtpV6Protocol extends BaseProtocol {
+
+ @Inject
+ public NdtpV6Protocol(Config config) {
+ addServer(new TrackerServer(config, getName(), false) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new StringEncoder());
+ pipeline.addLast(new NdtpV6ProtocolDecoder(NdtpV6Protocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/NdtpV6ProtocolDecoder.java b/src/main/java/org/traccar/protocol/NdtpV6ProtocolDecoder.java
new file mode 100644
index 000000000..35cb0bae8
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/NdtpV6ProtocolDecoder.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2022 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.buffer.Unpooled;
+import io.netty.channel.Channel;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.NetworkMessage;
+import org.traccar.Protocol;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.Checksum;
+import org.traccar.helper.UnitsConverter;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.net.SocketAddress;
+import java.util.Date;
+
+public class NdtpV6ProtocolDecoder extends BaseProtocolDecoder {
+
+ public NdtpV6ProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ private static final byte[] SIGNATURE = {0x7E, 0x7E};
+
+ private static final int NPL_FLAG_CRC = 2;
+ private static final int NPH_RESULT_OK = 0x00000000;
+ private static final int NPL_TYPE_NPH = 2;
+ private static final int NPL_ADDRESS_SERVER = 0;
+
+ private static final int NPH_RESULT = 0;
+
+ private static final int NPH_SRV_GENERIC_CONTROLS = 0;
+ private static final int NPH_SRV_NAVDATA = 1;
+
+ private static final int NPH_SGC_RESULT = NPH_RESULT;
+ private static final int NPH_SGC_CONN_REQUEST = 100;
+
+ private static final int NPH_SND_RESULT = NPH_RESULT;
+
+ private void sendResponse(
+ Channel channel, int serviceId, long requestId) {
+
+ ByteBuf content = Unpooled.buffer();
+ content.writeShortLE(serviceId);
+ content.writeIntLE(NPH_SND_RESULT);
+ content.writeIntLE((int) requestId);
+ content.writeIntLE(NPH_RESULT_OK);
+
+ ByteBuf response = Unpooled.buffer();
+ response.writeBytes(SIGNATURE);
+ response.writeShortLE(content.readableBytes());
+ response.writeShortLE(NPL_FLAG_CRC); // flags
+ response.writeShort(Checksum.crc16(Checksum.CRC16_MODBUS, content.nioBuffer()));
+ response.writeByte(NPL_TYPE_NPH); // type
+ response.writeIntLE(NPL_ADDRESS_SERVER); // peer address
+ response.writeShortLE(0); // request id
+ response.writeBytes(content);
+ content.release();
+
+ channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress()));
+ }
+
+ private static final short MAIN_NAV_DATA = 0;
+ private static final short ADDITIONAL_NAV_DATA = 2;
+
+ private void decodeData(ByteBuf buf, Position position) {
+
+ short itemType;
+ short itemIndex;
+
+ itemType = buf.readUnsignedByte();
+ itemIndex = buf.readUnsignedByte();
+ if (itemType == MAIN_NAV_DATA && (itemIndex == 0 || itemIndex == 1)) {
+
+ position.setTime(new Date(buf.readUnsignedIntLE() * 1000));
+ position.setLongitude(buf.readIntLE() / 10000000.0);
+ position.setLatitude(buf.readIntLE() / 10000000.0);
+
+ short flags = buf.readUnsignedByte();
+ position.setValid(BitUtil.check(flags, 7));
+ if (BitUtil.check(flags, 1)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_GENERAL);
+ }
+
+ position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 20);
+ position.set(Position.KEY_OBD_SPEED, buf.readUnsignedShortLE());
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE()));
+ position.setCourse(buf.readUnsignedShortLE());
+
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedShortLE());
+ position.setAltitude(buf.readShortLE());
+ position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
+ position.set(Position.KEY_PDOP, buf.readUnsignedByte());
+ }
+
+ itemType = buf.readUnsignedByte();
+ itemIndex = buf.readUnsignedByte();
+ if (itemType == ADDITIONAL_NAV_DATA && itemIndex == 0) {
+
+ position.set(Position.KEY_BATTERY_LEVEL, Math.max((buf.readUnsignedShortLE() - 3600) / 6, 100));
+ position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShortLE());
+ position.set(Position.PREFIX_ADC + 3, buf.readUnsignedShortLE());
+ position.set(Position.PREFIX_ADC + 4, buf.readUnsignedShortLE());
+
+ buf.readUnsignedByte(); // inputs
+ buf.readUnsignedByte(); // outputs
+ buf.readUnsignedShortLE(); // in1 count
+ buf.readUnsignedShortLE(); // in2 count
+ buf.readUnsignedShortLE(); // in3 count
+ buf.readUnsignedShortLE(); // in4 count
+ buf.readUnsignedIntLE(); // track length
+
+ position.set(Position.KEY_ANTENNA, buf.readUnsignedByte());
+ position.set(Position.KEY_GPS, buf.readUnsignedByte());
+ position.set(Position.KEY_ACCELERATION, buf.readUnsignedByte());
+ position.set(Position.KEY_POWER, buf.readUnsignedByte() * 200);
+ }
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ ByteBuf buf = (ByteBuf) msg;
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
+
+ buf.skipBytes(2); // signature
+ buf.readUnsignedShortLE(); // length
+ buf.readUnsignedShortLE(); // connection flags
+ buf.readUnsignedShortLE(); // checksum
+ buf.readUnsignedByte(); // type
+ buf.readUnsignedIntLE(); // address
+ buf.readUnsignedShortLE(); // identification
+
+ int serviceId = buf.readUnsignedShortLE();
+ int serviceType = buf.readUnsignedShortLE();
+ buf.readUnsignedShortLE(); // request flags
+ long requestId = buf.readUnsignedIntLE();
+
+ if (deviceSession == null && serviceId == NPH_SRV_GENERIC_CONTROLS && serviceType == NPH_SGC_CONN_REQUEST) {
+
+ buf.readUnsignedShortLE(); // version major
+ buf.readUnsignedShortLE(); // version minor
+ buf.readUnsignedShortLE(); // connection flags
+
+ int deviceId = buf.readUnsignedShortLE();
+ Position position = new Position(getProtocolName());
+ deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(deviceId));
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ if (channel != null) {
+ sendResponse(channel, serviceId, requestId);
+ }
+
+ position.set(Position.KEY_RESULT, String.valueOf(NPH_SGC_RESULT));
+ position.setTime(new Date());
+ getLastLocation(position, new Date());
+ position.setValid(false);
+
+ return position;
+
+ }
+
+ if (serviceId == NPH_SRV_NAVDATA) {
+
+ deviceSession = getDeviceSession(channel, remoteAddress);
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ if (channel != null) {
+ sendResponse(channel, serviceId, requestId);
+ }
+
+ decodeData(buf, position);
+
+ return position;
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/NdtpV6ProtocolEncoder.java b/src/main/java/org/traccar/protocol/NdtpV6ProtocolEncoder.java
new file mode 100644
index 000000000..7aac8658a
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/NdtpV6ProtocolEncoder.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 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 org.traccar.BaseProtocolEncoder;
+import org.traccar.Protocol;
+import org.traccar.model.Command;
+
+public class NdtpV6ProtocolEncoder extends BaseProtocolEncoder {
+
+ public NdtpV6ProtocolEncoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ @Override
+ protected Object encodeCommand(Command command) {
+ switch (command.getType()) {
+ case Command.TYPE_IDENTIFICATION:
+ return "BB+IDNT";
+ case Command.TYPE_REBOOT_DEVICE:
+ return "BB+RESET";
+ case Command.TYPE_POSITION_SINGLE:
+ return "BB+RRCD";
+ default:
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java b/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java
index 244df6806..34c879cb8 100644
--- a/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2014 - 2022 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.
@@ -22,16 +22,19 @@ import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.QueryStringDecoder;
import org.traccar.BaseHttpProtocolDecoder;
-import org.traccar.session.DeviceSession;
import org.traccar.Protocol;
import org.traccar.helper.BitUtil;
import org.traccar.helper.DateBuilder;
+import org.traccar.helper.Parser;
+import org.traccar.helper.PatternBuilder;
import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
+import java.util.regex.Pattern;
public class PiligrimProtocolDecoder extends BaseHttpProtocolDecoder {
@@ -47,6 +50,21 @@ public class PiligrimProtocolDecoder extends BaseHttpProtocolDecoder {
public static final int MSG_GPS_SENSORS = 0xF2;
public static final int MSG_EVENTS = 0xF3;
+ private static final Pattern PATTERN = new PatternBuilder()
+ .expression("[^$]+")
+ .text("$GPRMC,")
+ .number("(dd)(dd)(dd).d+,") // time (hhmmss)
+ .expression("([AV]),") // validity
+ .number("(dd)(dd.d+),") // latitude
+ .expression("([NS]),")
+ .number("(d{2,3})(dd.d+),") // longitude
+ .expression("([EW]),")
+ .number("(d+.d+),") // speed
+ .number("(d+.d+),") // course
+ .number("(dd)(dd)(dd),") // date (ddmmyy)
+ .any()
+ .compile();
+
@Override
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
@@ -150,6 +168,42 @@ public class PiligrimProtocolDecoder extends BaseHttpProtocolDecoder {
}
return positions;
+
+ } else if (uri.startsWith("/push.do")) {
+
+ sendResponse(channel, "PUSH.DO: OK");
+
+ String sentence = request.content().toString(StandardCharsets.US_ASCII);
+
+ String[] parts = sentence.split("&");
+ String phone = parts[1].substring(16);
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, phone);
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Parser parser = new Parser(PATTERN, parts[2]);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ DateBuilder dateBuilder = new DateBuilder()
+ .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt());
+
+ position.setValid(parser.next().equals("A"));
+ position.setLatitude(parser.nextCoordinate());
+ position.setLongitude(parser.nextCoordinate());
+ position.setSpeed(parser.nextDouble());
+ position.setCourse(parser.nextDouble());
+
+ dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt());
+ position.setTime(dateBuilder.getDate());
+
+ return position;
+
}
return null;
diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java
index 89124cb22..4671a1088 100644
--- a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java
@@ -325,6 +325,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
if (codec == CODEC_GH3000) {
decodeGh3000Parameter(position, id, buf, length);
} else {
+ int index = buf.readerIndex();
boolean decoded = false;
for (var entry : PARAMETERS.getOrDefault(id, new HashMap<>()).entrySet()) {
if (entry.getKey() == null || model != null && entry.getKey().contains(model)) {
@@ -333,22 +334,48 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
break;
}
}
- if (!decoded) {
+ if (decoded) {
+ buf.readerIndex(index + length);
+ } else {
position.set(Position.PREFIX_IO + id, readValue(buf, length));
}
}
}
- private void decodeNetwork(Position position) {
- Integer cid = (Integer) position.getAttributes().remove("cid");
- Integer lac = (Integer) position.getAttributes().remove("lac");
- if (cid != null && lac != null) {
- CellTower cellTower = CellTower.fromLacCid(getConfig(), lac, cid);
- long operator = position.getInteger(Position.KEY_OPERATOR);
- if (operator >= 1000) {
- cellTower.setOperator(operator);
+ private void decodeCell(
+ Position position, Network network, String mncKey, String lacKey, String cidKey, String rssiKey) {
+ if (position.hasAttribute(mncKey) && position.hasAttribute(lacKey) && position.hasAttribute(cidKey)) {
+ CellTower cellTower = CellTower.from(
+ getConfig().getInteger(Keys.GEOLOCATION_MCC),
+ (Integer) position.getAttributes().remove(mncKey),
+ (Integer) position.getAttributes().remove(lacKey),
+ (Integer) position.getAttributes().remove(cidKey));
+ cellTower.setSignalStrength((Integer) position.getAttributes().remove(rssiKey));
+ network.addCellTower(cellTower);
+ }
+ }
+
+ private void decodeNetwork(Position position, String model) {
+ if ("TAT100".equals(model)) {
+ Network network = new Network();
+ decodeCell(position, network, "io1200", "io287", "io288", "io289");
+ decodeCell(position, network, "io1201", "io290", "io291", "io292");
+ decodeCell(position, network, "io1202", "io293", "io294", "io295");
+ decodeCell(position, network, "io1203", "io296", "io297", "io298");
+ if (network.getCellTowers() != null) {
+ position.setNetwork(network);
+ }
+ } else {
+ Integer cid = (Integer) position.getAttributes().remove("cid");
+ Integer lac = (Integer) position.getAttributes().remove("lac");
+ if (cid != null && lac != null) {
+ CellTower cellTower = CellTower.fromLacCid(getConfig(), lac, cid);
+ long operator = position.getInteger(Position.KEY_OPERATOR);
+ if (operator >= 1000) {
+ cellTower.setOperator(operator);
+ }
+ position.setNetwork(new Network(cellTower));
}
- position.setNetwork(new Network(cellTower));
}
}
@@ -542,7 +569,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
}
}
- decodeNetwork(position);
+ decodeNetwork(position, model);
}
diff --git a/src/main/java/org/traccar/protocol/ThurayaProtocol.java b/src/main/java/org/traccar/protocol/ThurayaProtocol.java
new file mode 100644
index 000000000..f709a1183
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/ThurayaProtocol.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 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 org.traccar.config.Config;
+
+import javax.inject.Inject;
+
+public class ThurayaProtocol extends BaseProtocol {
+
+ @Inject
+ public ThurayaProtocol(Config config) {
+ addServer(new TrackerServer(config, getName(), false) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 2, -4, 0));
+ pipeline.addLast(new ThurayaProtocolDecoder(ThurayaProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/ThurayaProtocolDecoder.java b/src/main/java/org/traccar/protocol/ThurayaProtocolDecoder.java
new file mode 100644
index 000000000..a287ece34
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/ThurayaProtocolDecoder.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2022 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.buffer.Unpooled;
+import io.netty.channel.Channel;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.NetworkMessage;
+import org.traccar.Protocol;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.DateBuilder;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ThurayaProtocolDecoder extends BaseProtocolDecoder {
+
+ public ThurayaProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ public static final int MSG_EVENT = 0x5101;
+ public static final int MSG_PERIODIC_REPORT = 0x7101;
+ public static final int MSG_SETTING_RESPONSE = 0x8115;
+ public static final int MSG_ACK = 0x9901;
+
+ private static int checksum(ByteBuffer buf) {
+ int crc = 0;
+ while (buf.hasRemaining()) {
+ crc += buf.get();
+ }
+ crc = ~crc;
+ crc += 1;
+ return crc;
+ }
+
+ private void sendResponse(Channel channel, SocketAddress remoteAddress, long id, int type) {
+ if (channel != null) {
+ ByteBuf response = Unpooled.buffer();
+ response.writeCharSequence("#T", StandardCharsets.US_ASCII);
+ response.writeShort(15); // length
+ response.writeShort(MSG_ACK);
+ response.writeInt((int) id);
+ response.writeShort(type);
+ response.writeShort(1); // server ok
+ response.writeShort(checksum(response.nioBuffer()));
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
+ }
+ }
+
+ private void decodeLocation(ByteBuf buf, Position position) {
+
+ position.setValid(true);
+
+ DateBuilder dateBuilder = new DateBuilder();
+
+ int date = buf.readInt();
+ dateBuilder.setDay(date % 100);
+ date /= 100;
+ dateBuilder.setMonth(date % 100);
+ date /= 100;
+ dateBuilder.setYear(date);
+
+ int time = buf.readInt();
+ dateBuilder.setSecond(time % 100);
+ time /= 100;
+ dateBuilder.setMinute(time % 100);
+ time /= 100;
+ dateBuilder.setHour(time);
+
+ position.setTime(dateBuilder.getDate());
+
+ position.setLongitude(buf.readInt() / 1000000.0);
+ position.setLatitude(buf.readInt() / 1000000.0);
+
+ int data = buf.readUnsignedShort();
+
+ int ignition = BitUtil.from(data, 12);
+ if (ignition == 1) {
+ position.set(Position.KEY_IGNITION, true);
+ } else if (ignition == 2) {
+ position.set(Position.KEY_IGNITION, false);
+ }
+
+ position.setCourse(BitUtil.to(data, 12));
+ position.setSpeed(buf.readShort());
+
+ position.set(Position.KEY_RPM, buf.readShort());
+
+ position.set("data", readString(buf));
+ }
+
+ private String decodeAlarm(int event) {
+ switch (event) {
+ case 10:
+ return Position.ALARM_VIBRATION;
+ case 11:
+ return Position.ALARM_OVERSPEED;
+ case 12:
+ return Position.ALARM_POWER_CUT;
+ case 13:
+ return Position.ALARM_LOW_BATTERY;
+ case 18:
+ return Position.ALARM_GPS_ANTENNA_CUT;
+ case 20:
+ return Position.ALARM_ACCELERATION;
+ case 21:
+ return Position.ALARM_BRAKING;
+ default:
+ return null;
+ }
+ }
+
+ private String readString(ByteBuf buf) {
+ int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0);
+ CharSequence value = buf.readCharSequence(endIndex - buf.readerIndex(), StandardCharsets.US_ASCII);
+ buf.readUnsignedByte(); // delimiter
+ return value.toString();
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ ByteBuf buf = (ByteBuf) msg;
+
+ buf.skipBytes(2); // service
+ buf.readUnsignedShort(); // length
+ int type = buf.readUnsignedShort();
+ long id = buf.readUnsignedInt();
+
+ sendResponse(channel, remoteAddress, id, type);
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(id));
+ if (deviceSession == null) {
+ return null;
+ }
+
+ if (type == MSG_EVENT) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ decodeLocation(buf, position);
+
+ int event = buf.readUnsignedByte();
+ position.set(Position.KEY_ALARM, decodeAlarm(event));
+ position.set(Position.KEY_EVENT, event);
+ position.set("eventData", readString(buf));
+
+ return position;
+
+ } else if (type == MSG_PERIODIC_REPORT) {
+
+ List<Position> positions = new LinkedList<>();
+
+ int count = buf.readUnsignedByte();
+ for (int i = 0; i < count; i++) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ decodeLocation(buf, position);
+
+ positions.add(position);
+
+ }
+
+ return positions;
+
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/traccar/session/cache/CacheManager.java b/src/main/java/org/traccar/session/cache/CacheManager.java
index 6f6b648fa..64397b368 100644
--- a/src/main/java/org/traccar/session/cache/CacheManager.java
+++ b/src/main/java/org/traccar/session/cache/CacheManager.java
@@ -132,7 +132,7 @@ public class CacheManager implements BroadcastInterface {
lock.readLock().lock();
var users = deviceLinks.get(deviceId).get(User.class).stream()
.collect(Collectors.toUnmodifiableSet());
- return notificationUsers.get(notificationId).stream()
+ return notificationUsers.getOrDefault(notificationId, new LinkedList<>()).stream()
.filter(user -> users.contains(user.getId()))
.collect(Collectors.toUnmodifiableList());
} finally {
diff --git a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java
index f3f47a104..1570dcf32 100644
--- a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java
@@ -17,6 +17,14 @@ public class Gt06ProtocolDecoderTest extends ProtocolTest {
verifyNull(decoder, binary(
"78780D01086471700328358100093F040D0A"));
+ verifyAttribute(decoder, binary(
+ "7979000d940516090908081c030defbd2d0d0a"),
+ Position.KEY_DOOR, true);
+
+ verifyAttribute(decoder, binary(
+ "78782516160908063736c0006e70110442fc800000000800000000000000000300002512000473fb0d0a"),
+ Position.KEY_ALARM, Position.ALARM_TAMPERING);
+
verifyPosition(decoder, binary(
"78782e2416061a103600c80275298404a0a24000184602d4023a49006f060104ed01940000086508004139765000be7d640d0a"));
diff --git a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
index 7322185d5..3661a0202 100644
--- a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
@@ -12,6 +12,10 @@ public class HuabaoProtocolDecoderTest extends ProtocolTest {
var decoder = inject(new HuabaoProtocolDecoder(null));
verifyAttribute(decoder, binary(
+ "7e0200005e01229130231209e300000000000c002300d264a305ff322300160000000022091514493503020000a70400000000ac0400000000e5020003e62c01bc5729009ca319bbff0002dd34020754fe1a83393c03bc572900ce371a6133d704dd34020751551d00fefb9a7e"),
+ Position.PREFIX_TEMP + 4, 29.0);
+
+ verifyAttribute(decoder, binary(
"7E0200008201215233475100030000000000000003015A7F6106CF8CEC003D0000000021071311481901040000005630011931011AE10200755D3D0601CC0024990A7dA0032301CC002499099B2941FC01CC002499099B29430B01CC0024990A7dA0290601CC0024990A7dA015FD01CC0026220994506BFFFE157C010400000001F10C000000000000000000000000997E"),
Position.KEY_ALARM, Position.ALARM_ACCELERATION);
diff --git a/src/test/java/org/traccar/protocol/MeitrackProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/MeitrackProtocolDecoderTest.java
index 09ab0440d..0fc51b8b6 100644
--- a/src/test/java/org/traccar/protocol/MeitrackProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/MeitrackProtocolDecoderTest.java
@@ -11,6 +11,9 @@ public class MeitrackProtocolDecoderTest extends ProtocolTest {
var decoder = inject(new MeitrackProtocolDecoder(null));
+ verifyPositions(decoder, binary(
+ "2424423233322c3836323039303035303030323831332c4343452c0400000003004400110004050006000700fe6962060800000900000a00000b00001aef044023000602d65fbcfd03173b9c0804cc76ae2a0c14ae1b000d00aa0d001c01000000014b030101003f00100004050006000700fe695f060800000900000a00000b00001aea044016000502d65fbcfd03173b9c0804cf76ae2a0c14ae1b000d03aa0d00014b030101003f00100004050006000700fe695f060800000900000a00000b00001aed044001000502d65fbcfd03173b9c0804d076ae2a0c14ae1b000d04aa0d00014b030101002a30460d0a"));
+
verifyAttribute(decoder, buffer(
"$$F160,861412043027965,AAA,22,45.499458,-82.493581,220718171428,V,0,0,0,0,0.0,0,227940,119812,302|220|D8D6|086E1B2B,0000,0000|0000|0000|0191|0573,,,3,,002134,0,0*FA"),
Position.KEY_POWER, 13.95);
diff --git a/src/test/java/org/traccar/protocol/PiligrimProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/PiligrimProtocolDecoderTest.java
index 475ac0125..0dd00462d 100644
--- a/src/test/java/org/traccar/protocol/PiligrimProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/PiligrimProtocolDecoderTest.java
@@ -15,6 +15,10 @@ public class PiligrimProtocolDecoderTest extends ProtocolTest {
"/bingps?imei=868204005544720&csq=18&vout=00&vin=4050&dataid=00000000",
binary("fff2200d4110061a32354f3422310062000a0005173b0000a101000300005e00fff2200d4110100932354f2b22310042000b000e173b00009f01000700006000")));
+ verifyPosition(decoder, request(HttpMethod.POST,
+ "/push.do",
+ buffer("&phoneNumber=%2B+78000000000&message=ALARM KEY; $GPRMC,180752.000,A,5314.0857,N,03421.8173,E,0.00,104.74,220722,,,A,V* 29,05; GSM: 250-01 0b54-0519,1c30,3e96,3ebe,412e 25; S; Batt: 405,M")));
+
}
}
diff --git a/src/test/java/org/traccar/protocol/TeltonikaProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/TeltonikaProtocolDecoderTest.java
index 994a55109..197391d7f 100644
--- a/src/test/java/org/traccar/protocol/TeltonikaProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/TeltonikaProtocolDecoderTest.java
@@ -15,6 +15,9 @@ public class TeltonikaProtocolDecoderTest extends ProtocolTest {
"000F313233343536373839303132333435"));
verifyPositions(decoder, binary(
+ "00000000000004258e0400000182a701b49301d5d90ab7ebe4aae101be003d12000000f7003d000e00f70100ef0000f00000500500150200c800004501000100001d00001400001600001700007157010701001d00b5000b00b60006004230400018000000cd223f00ce741700430f8900440000000d00010011ffe50012001f0013ffce000f03e800190bb8001a0bb8001b0bb8001c0bb800560bb800680bb8006a0bb8006c0bb8010e0000011100000114000001170000014f0000015000000151000001520000000a00f100011d2a00c700000000001000b9addc000c0000acb600040000000001320000000001330000000001340000000001350000000001c1000000000003000b000000d14675f36000ee0000000000000000000e0000000003fd509f0005014b0000014c0000014d0000014e0000018300222d3333373333382e0100000053a6fb624588040001ba86064f0eae51c0fdaf4d3de500000182a701b82001d5d90ab7ebe4aae101be003d12000000f0003c000d00ef0000f00100500500150200c800004501000100001d00001400001600001700007152010701001d00b5000900b60006004217e50018000000cd223f00ce741700430f5c00440000000d00010011fed60012fd1d0013f1f2000f03e800190bb8001a0bb8001b0bb8001c0bb800560bb800680bb8006a0bb8006c0bb8010e0000011100000114000001170000014f0000015000000151000001520000000a00f100011d2a00c700000000001000b9addc000c0000acb600040000000001320000000001330000000001340000000001350000000001c1000000000003000b000000d14675f36000ee0000000000000000000e0000000003fd509f0005014b0000014c0000014d0000014e0000018300222d3333373333352e353833332d303730373139362e323333332b3030302e3434362f00000182a701bc0801d5d90ab7ebe4aae101be003d12000000fc003d000e00ef0000f00100500500150200c800004501000100001d0000140000160000170000714d01070100fc01001d00b5000900b60006004217e50018000000cd223f00ce741700430f5c00440000000d00010011fed60012fd1d0013f1f2000f03e800190bb8001a0bb8001b0bb8001c0bb800560bb800680bb8006a0bb8006c0bb8010e0000011100000114000001170000014f0000015000000151000001520000000a00f100011d2a00c700000000001000b9addc000c0000acb600040000000001320000000001330000000001340000000001350000000001c1000000000003000b000000d14675f36000ee0000000000000000000e0000000003fd509f0005014b0000014c0000014d0000014e0000018300222d3333373333352e353833332d303730373139362e323333332b3030302e3434362f00000182a7018d8d01d5d8ffa6ebe4a0ca01be006111000000f70001000100f70500000000000000000400003a10"));
+
+ verifyPositions(decoder, binary(
"00000000000003831004000001735ace37f80000e3b9331c71e290006900e211005100fd072e1600010100160300470300f00100150400b20000c80000ef01009000004f00005101005201005300005538006e00006f00007a03007d00007f5600890000fd0200fe1f09004326b00044000000b5000b00b6000600427029001800540046015d00ce4ec10080000f0f00f10000515400cd007404ab00d80f5022a1005000000054005400000000005600015568005700000060005800000420006800001113006d303330300071fffd8c85008700000020008800000002008a000155f5008b0000b86000000001735ace3ca80000e3b08a1c71dd29006900e311005100fd072e1600010100160300470300f00100150400b20000c80000ef01009000004f00005101005201005300005537006e00006f00007a03007d00007f5600890000fd0200fe1d09004326ac0044000000b5000b00b600060042701f001800540046015d00ce4ec10080000f0f00f10000515400cd007404ab00d80f5022ce00500000005400540000000000560001556800570000006000580000041f006800001113006d303330300071fffd8c85008700000020008800000002008a000155f5008b0000b86000000001735ace3fc80000e3a7c01c71d7c2006900e311005100fd072e1600010100160300470300f00100150400b20000c80000ef01009000004f00005101005201005300005537006e00006f00007a03007d00007f5600890000fd0200fe2309004326ac0044000000b5000b00b6000600427015001800540046015e00ce4ec10080000f0f00f10000515400cd007404ab00d80f5022e700500000005400540000000000560001556800570000006000580000041f006800001113006d303330300071fffd8c85008700000020008800000002008a000155f5008b0000b86000000001735ace3ffa0000e3a7c01c71d7c2006900e311005100fd072e1600010100160300470300f00100150400b20000c80000ef01009000004f00005101005201005300005537006e00006f00007a03007d00007f5600890000fd0300fe2309004326ac0044000000b5000b00b6000600427015001800540046015e00ce4ec10080000f0f00f10000515400cd007404ab00d80f5022e700500000005400540000000000560001556800570000006000580000041f006800001113006d303330300071fffd8c85008700000020008800000002008a000155f5008b0000b86000040000eb85"));
verifyPositions(decoder, binary(
diff --git a/src/test/java/org/traccar/protocol/ThurayaProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/ThurayaProtocolDecoderTest.java
new file mode 100644
index 000000000..90431fa24
--- /dev/null
+++ b/src/test/java/org/traccar/protocol/ThurayaProtocolDecoderTest.java
@@ -0,0 +1,24 @@
+package org.traccar.protocol;
+
+import org.junit.Test;
+import org.traccar.ProtocolTest;
+
+public class ThurayaProtocolDecoderTest extends ProtocolTest {
+
+ @Test
+ public void testDecode() throws Exception {
+
+ var decoder = inject(new ThurayaProtocolDecoder(null));
+
+ verifyPositions(decoder, binary(
+ "235400437101072bca3c0201348b9a00014c9f085493fc02200c5411470000000042323a300001348b9a00014d03085493ea02200c5010000000000042323a3000f2c1"));
+
+ verifyPosition(decoder, binary(
+ "2354002b5101072bca3c01348b9a00013fba000000000000000010000000000042323a3000174f4e00f9de"));
+
+ verifyNull(decoder, binary(
+ "235400d88115071e37d691030133342e3233362e3133302e3637000000001e56313030320030000700080102030405060708020101010101020201030103030302020000007800000078000004b000001c20050a64000015b3800015b374657374696e67003132333435360002010f28393031303539383938303134373738000043383a592c43373a592c43333a592c43323a592c43313a592c42353a592c42343a592c42323a592c42313a592c41323a592c41313a590045313a592c45373a590065746973616c61742e61650047455400322e3130d6de"));
+
+ }
+
+}