aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/traccar/Context.java3
-rw-r--r--src/main/java/org/traccar/api/resource/PasswordResource.java81
-rw-r--r--src/main/java/org/traccar/config/Keys.java22
-rw-r--r--src/main/java/org/traccar/database/MailManager.java6
-rw-r--r--src/main/java/org/traccar/database/NotificationManager.java2
-rw-r--r--src/main/java/org/traccar/handler/events/AlertEventHandler.java2
-rw-r--r--src/main/java/org/traccar/handler/events/CommandResultEventHandler.java2
-rw-r--r--src/main/java/org/traccar/handler/events/DriverEventHandler.java2
-rw-r--r--src/main/java/org/traccar/handler/events/FuelDropEventHandler.java2
-rw-r--r--src/main/java/org/traccar/handler/events/GeofenceEventHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/events/IgnitionEventHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java2
-rw-r--r--src/main/java/org/traccar/handler/events/MotionEventHandler.java2
-rw-r--r--src/main/java/org/traccar/handler/events/OverspeedEventHandler.java2
-rw-r--r--src/main/java/org/traccar/model/Command.java2
-rw-r--r--src/main/java/org/traccar/model/Event.java20
-rw-r--r--src/main/java/org/traccar/model/Server.java8
-rw-r--r--src/main/java/org/traccar/notification/NotificationFormatter.java61
-rw-r--r--src/main/java/org/traccar/notification/TextTemplateFormatter.java91
-rw-r--r--src/main/java/org/traccar/notificators/NotificatorFirebase.java3
-rw-r--r--src/main/java/org/traccar/protocol/AdmProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java94
-rw-r--r--src/main/java/org/traccar/protocol/DmtProtocolDecoder.java7
-rw-r--r--src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java24
-rw-r--r--src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java48
-rw-r--r--src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java177
-rw-r--r--src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java26
-rw-r--r--src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java12
-rw-r--r--src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java53
-rw-r--r--src/main/java/org/traccar/protocol/IotmProtocolDecoder.java207
-rw-r--r--src/main/java/org/traccar/protocol/KhdProtocol.java10
-rw-r--r--src/main/java/org/traccar/protocol/KhdProtocolDecoder.java12
-rw-r--r--src/main/java/org/traccar/protocol/KhdProtocolEncoder.java36
-rw-r--r--src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java6
-rw-r--r--src/main/java/org/traccar/protocol/NavtelecomProtocol.java35
-rw-r--r--src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java72
-rw-r--r--src/main/java/org/traccar/protocol/RstProtocolDecoder.java6
-rw-r--r--src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java12
-rw-r--r--src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java35
-rw-r--r--src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java8
-rw-r--r--src/main/java/org/traccar/protocol/StartekProtocol.java39
-rw-r--r--src/main/java/org/traccar/protocol/StartekProtocolDecoder.java123
-rw-r--r--src/main/java/org/traccar/protocol/TopinProtocolDecoder.java18
-rw-r--r--src/main/java/org/traccar/sms/SnsSmsClient.java82
-rw-r--r--src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java3
-rw-r--r--src/test/java/org/traccar/protocol/DmtProtocolDecoderTest.java3
-rw-r--r--src/test/java/org/traccar/protocol/FifotrackProtocolDecoderTest.java4
-rw-r--r--src/test/java/org/traccar/protocol/FreematicsProtocolDecoderTest.java9
-rw-r--r--src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java6
-rw-r--r--src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java4
-rw-r--r--src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java6
-rw-r--r--src/test/java/org/traccar/protocol/KhdProtocolEncoderTest.java5
-rw-r--r--src/test/java/org/traccar/protocol/NavtelecomProtocolDecoderTest.java18
-rw-r--r--src/test/java/org/traccar/protocol/SiwiProtocolDecoderTest.java9
-rw-r--r--src/test/java/org/traccar/protocol/StarLinkProtocolDecoderTest.java6
-rw-r--r--src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java21
-rw-r--r--src/test/java/org/traccar/protocol/TopinProtocolDecoderTest.java3
58 files changed, 1287 insertions, 281 deletions
diff --git a/src/main/java/org/traccar/Context.java b/src/main/java/org/traccar/Context.java
index 3ba8843f0..fe494dabf 100644
--- a/src/main/java/org/traccar/Context.java
+++ b/src/main/java/org/traccar/Context.java
@@ -60,6 +60,7 @@ import org.traccar.reports.model.TripsConfig;
import org.traccar.schedule.ScheduleManager;
import org.traccar.sms.HttpSmsClient;
import org.traccar.sms.SmsManager;
+import org.traccar.sms.SnsSmsClient;
import org.traccar.web.WebServer;
import javax.ws.rs.client.Client;
@@ -317,6 +318,8 @@ public final class Context {
if (config.hasKey(Keys.SMS_HTTP_URL)) {
smsManager = new HttpSmsClient();
+ } else if (config.hasKey(Keys.SMS_AWS_REGION)) {
+ smsManager = new SnsSmsClient();
}
initEventsModule();
diff --git a/src/main/java/org/traccar/api/resource/PasswordResource.java b/src/main/java/org/traccar/api/resource/PasswordResource.java
new file mode 100644
index 000000000..20e8d768d
--- /dev/null
+++ b/src/main/java/org/traccar/api/resource/PasswordResource.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2021 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.api.resource;
+
+import org.apache.velocity.VelocityContext;
+import org.traccar.Context;
+import org.traccar.api.BaseResource;
+import org.traccar.model.User;
+import org.traccar.notification.FullMessage;
+import org.traccar.notification.TextTemplateFormatter;
+
+import javax.annotation.security.PermitAll;
+import javax.mail.MessagingException;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.sql.SQLException;
+import java.util.UUID;
+
+@Path("password")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+public class PasswordResource extends BaseResource {
+
+ private static final String PASSWORD_RESET_TOKEN = "passwordToken";
+
+ @Path("reset")
+ @PermitAll
+ @POST
+ public Response reset(@FormParam("email") String email) throws SQLException, MessagingException {
+ for (long userId : Context.getUsersManager().getAllItems()) {
+ User user = Context.getUsersManager().getById(userId);
+ if (email.equals(user.getEmail())) {
+ String token = UUID.randomUUID().toString().replaceAll("-", "");
+ user.set(PASSWORD_RESET_TOKEN, token);
+ Context.getUsersManager().updateItem(user);
+ VelocityContext velocityContext = TextTemplateFormatter.prepareContext(null);
+ velocityContext.put("token", token);
+ FullMessage message = TextTemplateFormatter.formatFullMessage(velocityContext, "passwordReset");
+ Context.getMailManager().sendMessage(userId, message.getSubject(), message.getBody());
+ break;
+ }
+ }
+ return Response.ok().build();
+ }
+
+ @Path("update")
+ @PermitAll
+ @POST
+ public Response update(
+ @FormParam("token") String token, @FormParam("password") String password) throws SQLException {
+ for (long userId : Context.getUsersManager().getAllItems()) {
+ User user = Context.getUsersManager().getById(userId);
+ if (token.equals(user.getString(PASSWORD_RESET_TOKEN))) {
+ user.getAttributes().remove(PASSWORD_RESET_TOKEN);
+ user.setPassword(password);
+ Context.getUsersManager().updateItem(user);
+ return Response.ok().build();
+ }
+ }
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+
+}
diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java
index 5f8b36c6d..d2e5bbd99 100644
--- a/src/main/java/org/traccar/config/Keys.java
+++ b/src/main/java/org/traccar/config/Keys.java
@@ -688,6 +688,28 @@ public final class Keys {
Collections.singletonList(KeyType.GLOBAL));
/**
+ * AWS Access Key with SNS permission.
+ */
+ public static final ConfigKey<String> SMS_AWS_ACCESS = new ConfigKey<>(
+ "sms.aws.access",
+ Collections.singletonList(KeyType.GLOBAL));
+
+ /**
+ * AWS Secret Access Key with SNS permission.
+ */
+ public static final ConfigKey<String> SMS_AWS_SECRET = new ConfigKey<>(
+ "sms.aws.secret",
+ Collections.singletonList(KeyType.GLOBAL));
+
+ /**
+ * AWS Region for SNS service.
+ * Make sure to use regions that are supported for messaging.
+ */
+ public static final ConfigKey<String> SMS_AWS_REGION = new ConfigKey<>(
+ "sms.aws.region",
+ Collections.singletonList(KeyType.GLOBAL));
+
+ /**
* Traccar notification API key.
*/
public static final ConfigKey<String> NOTIFICATOR_TRACCAR_KEY = new ConfigKey<>(
diff --git a/src/main/java/org/traccar/database/MailManager.java b/src/main/java/org/traccar/database/MailManager.java
index 8a2f002cd..d94f55cda 100644
--- a/src/main/java/org/traccar/database/MailManager.java
+++ b/src/main/java/org/traccar/database/MailManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org)
* Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -87,6 +87,10 @@ public final class MailManager {
return properties;
}
+ public boolean getEmailEnabled() {
+ return Context.getConfig().hasKey("mail.smtp.host");
+ }
+
public void sendMessage(
long userId, String subject, String body) throws MessagingException {
sendMessage(userId, subject, body, null);
diff --git a/src/main/java/org/traccar/database/NotificationManager.java b/src/main/java/org/traccar/database/NotificationManager.java
index ccad192f6..c4fc48ebf 100644
--- a/src/main/java/org/traccar/database/NotificationManager.java
+++ b/src/main/java/org/traccar/database/NotificationManager.java
@@ -90,7 +90,7 @@ public class NotificationManager extends ExtendedObjectManager<Notification> {
usersToForward.add(userId);
}
final Set<String> notificators = new HashSet<>();
- for (long notificationId : getEffectiveNotifications(userId, deviceId, event.getServerTime())) {
+ for (long notificationId : getEffectiveNotifications(userId, deviceId, event.getEventTime())) {
Notification notification = getById(notificationId);
if (getById(notificationId).getType().equals(event.getType())) {
boolean filter = false;
diff --git a/src/main/java/org/traccar/handler/events/AlertEventHandler.java b/src/main/java/org/traccar/handler/events/AlertEventHandler.java
index 0b7c8d23e..05dbc516e 100644
--- a/src/main/java/org/traccar/handler/events/AlertEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/AlertEventHandler.java
@@ -48,7 +48,7 @@ public class AlertEventHandler extends BaseEventHandler {
}
}
if (!ignoreAlert) {
- Event event = new Event(Event.TYPE_ALARM, position.getDeviceId(), position.getId());
+ Event event = new Event(Event.TYPE_ALARM, position);
event.set(Position.KEY_ALARM, (String) alarm);
return Collections.singletonMap(event, position);
}
diff --git a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java
index cfe676653..9b7ff554e 100644
--- a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java
@@ -29,7 +29,7 @@ public class CommandResultEventHandler extends BaseEventHandler {
protected Map<Event, Position> analyzePosition(Position position) {
Object commandResult = position.getAttributes().get(Position.KEY_RESULT);
if (commandResult != null) {
- Event event = new Event(Event.TYPE_COMMAND_RESULT, position.getDeviceId(), position.getId());
+ Event event = new Event(Event.TYPE_COMMAND_RESULT, position);
event.set(Position.KEY_RESULT, (String) commandResult);
return Collections.singletonMap(event, position);
}
diff --git a/src/main/java/org/traccar/handler/events/DriverEventHandler.java b/src/main/java/org/traccar/handler/events/DriverEventHandler.java
index 994df93fa..6fdf4246b 100644
--- a/src/main/java/org/traccar/handler/events/DriverEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/DriverEventHandler.java
@@ -46,7 +46,7 @@ public class DriverEventHandler extends BaseEventHandler {
oldDriverUniqueId = lastPosition.getString(Position.KEY_DRIVER_UNIQUE_ID);
}
if (!driverUniqueId.equals(oldDriverUniqueId)) {
- Event event = new Event(Event.TYPE_DRIVER_CHANGED, position.getDeviceId(), position.getId());
+ Event event = new Event(Event.TYPE_DRIVER_CHANGED, position);
event.set(Position.KEY_DRIVER_UNIQUE_ID, driverUniqueId);
return Collections.singletonMap(event, position);
}
diff --git a/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java b/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java
index bc1426b86..343a17311 100644
--- a/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java
@@ -57,7 +57,7 @@ public class FuelDropEventHandler extends BaseEventHandler {
double drop = lastPosition.getDouble(Position.KEY_FUEL_LEVEL)
- position.getDouble(Position.KEY_FUEL_LEVEL);
if (drop >= fuelDropThreshold) {
- Event event = new Event(Event.TYPE_DEVICE_FUEL_DROP, position.getDeviceId(), position.getId());
+ Event event = new Event(Event.TYPE_DEVICE_FUEL_DROP, position);
event.set(ATTRIBUTE_FUEL_DROP_THRESHOLD, fuelDropThreshold);
return Collections.singletonMap(event, position);
}
diff --git a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java
index 067c97957..f4807e56b 100644
--- a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java
@@ -69,7 +69,7 @@ public class GeofenceEventHandler extends BaseEventHandler {
long calendarId = geofenceManager.getById(geofenceId).getCalendarId();
Calendar calendar = calendarId != 0 ? calendarManager.getById(calendarId) : null;
if (calendar == null || calendar.checkMoment(position.getFixTime())) {
- Event event = new Event(Event.TYPE_GEOFENCE_EXIT, position.getDeviceId(), position.getId());
+ Event event = new Event(Event.TYPE_GEOFENCE_EXIT, position);
event.setGeofenceId(geofenceId);
events.put(event, position);
}
@@ -78,7 +78,7 @@ public class GeofenceEventHandler extends BaseEventHandler {
long calendarId = geofenceManager.getById(geofenceId).getCalendarId();
Calendar calendar = calendarId != 0 ? calendarManager.getById(calendarId) : null;
if (calendar == null || calendar.checkMoment(position.getFixTime())) {
- Event event = new Event(Event.TYPE_GEOFENCE_ENTER, position.getDeviceId(), position.getId());
+ Event event = new Event(Event.TYPE_GEOFENCE_ENTER, position);
event.setGeofenceId(geofenceId);
events.put(event, position);
}
diff --git a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java
index ec133bafc..69df9a46b 100644
--- a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java
@@ -52,10 +52,10 @@ public class IgnitionEventHandler extends BaseEventHandler {
if (ignition && !oldIgnition) {
result = Collections.singletonMap(
- new Event(Event.TYPE_IGNITION_ON, position.getDeviceId(), position.getId()), position);
+ new Event(Event.TYPE_IGNITION_ON, position), position);
} else if (!ignition && oldIgnition) {
result = Collections.singletonMap(
- new Event(Event.TYPE_IGNITION_OFF, position.getDeviceId(), position.getId()), position);
+ new Event(Event.TYPE_IGNITION_OFF, position), position);
}
}
}
diff --git a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java
index 93ae74142..0f960ad1f 100644
--- a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java
@@ -58,7 +58,7 @@ public class MaintenanceEventHandler extends BaseEventHandler {
if (oldValue != 0.0 && newValue != 0.0
&& (long) ((oldValue - maintenance.getStart()) / maintenance.getPeriod())
< (long) ((newValue - maintenance.getStart()) / maintenance.getPeriod())) {
- Event event = new Event(Event.TYPE_MAINTENANCE, position.getDeviceId(), position.getId());
+ Event event = new Event(Event.TYPE_MAINTENANCE, position);
event.setMaintenanceId(maintenanceId);
event.set(maintenance.getType(), newValue);
events.put(event, position);
diff --git a/src/main/java/org/traccar/handler/events/MotionEventHandler.java b/src/main/java/org/traccar/handler/events/MotionEventHandler.java
index 9ec02ccfb..db276f32b 100644
--- a/src/main/java/org/traccar/handler/events/MotionEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/MotionEventHandler.java
@@ -45,7 +45,7 @@ public class MotionEventHandler extends BaseEventHandler {
private Map<Event, Position> newEvent(DeviceState deviceState, boolean newMotion) {
String eventType = newMotion ? Event.TYPE_DEVICE_MOVING : Event.TYPE_DEVICE_STOPPED;
Position position = deviceState.getMotionPosition();
- Event event = new Event(eventType, position.getDeviceId(), position.getId());
+ Event event = new Event(eventType, position);
deviceState.setMotionState(newMotion);
deviceState.setMotionPosition(null);
return Collections.singletonMap(event, position);
diff --git a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java
index c396b28e9..347ad9005 100644
--- a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java
@@ -53,7 +53,7 @@ public class OverspeedEventHandler extends BaseEventHandler {
private Map<Event, Position> newEvent(DeviceState deviceState, double speedLimit) {
Position position = deviceState.getOverspeedPosition();
- Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, position.getDeviceId(), position.getId());
+ Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, position);
event.set(ATTRIBUTE_SPEED, deviceState.getOverspeedPosition().getSpeed());
event.set(ATTRIBUTE_SPEED_LIMIT, speedLimit);
event.setGeofenceId(deviceState.getOverspeedGeofenceId());
diff --git a/src/main/java/org/traccar/model/Command.java b/src/main/java/org/traccar/model/Command.java
index 099fb152d..99930d1e6 100644
--- a/src/main/java/org/traccar/model/Command.java
+++ b/src/main/java/org/traccar/model/Command.java
@@ -36,6 +36,7 @@ public class Command extends Message implements Cloneable {
public static final String TYPE_REQUEST_PHOTO = "requestPhoto";
public static final String TYPE_POWER_OFF = "powerOff";
public static final String TYPE_REBOOT_DEVICE = "rebootDevice";
+ public static final String TYPE_FACTORY_RESET = "factoryReset";
public static final String TYPE_SEND_SMS = "sendSms";
public static final String TYPE_SEND_USSD = "sendUssd";
public static final String TYPE_SOS_NUMBER = "sosNumber";
@@ -54,6 +55,7 @@ public class Command extends Message implements Cloneable {
public static final String TYPE_SET_ODOMETER = "setOdometer";
public static final String TYPE_GET_MODEM_STATUS = "getModemStatus";
public static final String TYPE_GET_DEVICE_STATUS = "getDeviceStatus";
+ public static final String TYPE_SET_SPEED_LIMIT = "setSpeedLimit";
public static final String TYPE_MODE_POWER_SAVING = "modePowerSaving";
public static final String TYPE_MODE_DEEP_SLEEP = "modeDeepSleep";
diff --git a/src/main/java/org/traccar/model/Event.java b/src/main/java/org/traccar/model/Event.java
index 5eee2a0a0..a7a134ecf 100644
--- a/src/main/java/org/traccar/model/Event.java
+++ b/src/main/java/org/traccar/model/Event.java
@@ -19,15 +19,17 @@ import java.util.Date;
public class Event extends Message {
- public Event(String type, long deviceId, long positionId) {
- this(type, deviceId);
- setPositionId(positionId);
+ public Event(String type, Position position) {
+ setType(type);
+ setPositionId(position.getId());
+ setDeviceId(position.getDeviceId());
+ eventTime = position.getDeviceTime();
}
public Event(String type, long deviceId) {
setType(type);
setDeviceId(deviceId);
- this.serverTime = new Date();
+ eventTime = new Date();
}
public Event() {
@@ -62,14 +64,14 @@ public class Event extends Message {
public static final String TYPE_DRIVER_CHANGED = "driverChanged";
- private Date serverTime;
+ private Date eventTime;
- public Date getServerTime() {
- return serverTime;
+ public Date getEventTime() {
+ return eventTime;
}
- public void setServerTime(Date serverTime) {
- this.serverTime = serverTime;
+ public void setEventTime(Date eventTime) {
+ this.eventTime = eventTime;
}
private long positionId;
diff --git a/src/main/java/org/traccar/model/Server.java b/src/main/java/org/traccar/model/Server.java
index e84943efc..7bdb53b22 100644
--- a/src/main/java/org/traccar/model/Server.java
+++ b/src/main/java/org/traccar/model/Server.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2021 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.
@@ -16,6 +16,7 @@
package org.traccar.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import org.traccar.Context;
import org.traccar.database.QueryIgnore;
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -176,4 +177,9 @@ public class Server extends ExtendedModel {
return getClass().getPackage().getImplementationVersion();
}
+ @QueryIgnore
+ public Boolean getEmailEnabled() {
+ return Context.getMailManager().getEmailEnabled();
+ }
+
}
diff --git a/src/main/java/org/traccar/notification/NotificationFormatter.java b/src/main/java/org/traccar/notification/NotificationFormatter.java
index 2f8100226..dabc75b8b 100644
--- a/src/main/java/org/traccar/notification/NotificationFormatter.java
+++ b/src/main/java/org/traccar/notification/NotificationFormatter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org)
* Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +16,7 @@
*/
package org.traccar.notification;
-import java.io.StringWriter;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Paths;
-import java.util.Locale;
-
-import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
-import org.apache.velocity.exception.ResourceNotFoundException;
-import org.apache.velocity.tools.generic.DateTool;
-import org.apache.velocity.tools.generic.NumberTool;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.traccar.Context;
import org.traccar.model.Device;
import org.traccar.model.Event;
@@ -37,8 +26,6 @@ import org.traccar.reports.ReportUtils;
public final class NotificationFormatter {
- private static final Logger LOGGER = LoggerFactory.getLogger(NotificationFormatter.class);
-
private NotificationFormatter() {
}
@@ -47,8 +34,8 @@ public final class NotificationFormatter {
User user = Context.getPermissionsManager().getUser(userId);
Device device = Context.getIdentityManager().getById(event.getDeviceId());
- VelocityContext velocityContext = new VelocityContext();
- velocityContext.put("user", user);
+ VelocityContext velocityContext = TextTemplateFormatter.prepareContext(user);
+
velocityContext.put("device", device);
velocityContext.put("event", event);
if (position != null) {
@@ -67,52 +54,18 @@ public final class NotificationFormatter {
if (driverUniqueId != null) {
velocityContext.put("driver", Context.getDriversManager().getDriverByUniqueId(driverUniqueId));
}
- velocityContext.put("webUrl", Context.getVelocityEngine().getProperty("web.url"));
- velocityContext.put("dateTool", new DateTool());
- velocityContext.put("numberTool", new NumberTool());
- velocityContext.put("timezone", ReportUtils.getTimezone(userId));
- velocityContext.put("locale", Locale.getDefault());
- return velocityContext;
- }
-
- public static Template getTemplate(Event event, String path) {
- String templateFilePath;
- Template template;
-
- try {
- templateFilePath = Paths.get(path, event.getType() + ".vm").toString();
- template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name());
- } catch (ResourceNotFoundException error) {
- LOGGER.warn("Notification template error", error);
- templateFilePath = Paths.get(path, "unknown.vm").toString();
- template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name());
- }
- return template;
+ return velocityContext;
}
public static FullMessage formatFullMessage(long userId, Event event, Position position) {
VelocityContext velocityContext = prepareContext(userId, event, position);
- String formattedMessage = formatMessage(velocityContext, userId, event, position, "full");
-
- return new FullMessage((String) velocityContext.get("subject"), formattedMessage);
+ return TextTemplateFormatter.formatFullMessage(velocityContext, event.getType());
}
public static String formatShortMessage(long userId, Event event, Position position) {
- return formatMessage(null, userId, event, position, "short");
- }
-
- private static String formatMessage(VelocityContext vc, Long userId, Event event, Position position,
- String templatePath) {
-
- VelocityContext velocityContext = vc;
- if (velocityContext == null) {
- velocityContext = prepareContext(userId, event, position);
- }
- StringWriter writer = new StringWriter();
- getTemplate(event, templatePath).merge(velocityContext, writer);
-
- return writer.toString();
+ VelocityContext velocityContext = prepareContext(userId, event, position);
+ return TextTemplateFormatter.formatShortMessage(velocityContext, event.getType());
}
}
diff --git a/src/main/java/org/traccar/notification/TextTemplateFormatter.java b/src/main/java/org/traccar/notification/TextTemplateFormatter.java
new file mode 100644
index 000000000..c7cac2d4d
--- /dev/null
+++ b/src/main/java/org/traccar/notification/TextTemplateFormatter.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2021 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.notification;
+
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.tools.generic.DateTool;
+import org.apache.velocity.tools.generic.NumberTool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.Context;
+import org.traccar.model.User;
+import org.traccar.reports.ReportUtils;
+
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
+import java.util.Locale;
+
+public final class TextTemplateFormatter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(TextTemplateFormatter.class);
+
+ private TextTemplateFormatter() {
+ }
+
+ public static VelocityContext prepareContext(User user) {
+
+ VelocityContext velocityContext = new VelocityContext();
+
+ if (user != null) {
+ velocityContext.put("user", user);
+ velocityContext.put("timezone", ReportUtils.getTimezone(user.getId()));
+ }
+
+ velocityContext.put("webUrl", Context.getVelocityEngine().getProperty("web.url"));
+ velocityContext.put("dateTool", new DateTool());
+ velocityContext.put("numberTool", new NumberTool());
+ velocityContext.put("locale", Locale.getDefault());
+
+ return velocityContext;
+ }
+
+ public static Template getTemplate(String name, String path) {
+
+ String templateFilePath;
+ Template template;
+
+ try {
+ templateFilePath = Paths.get(path, name + ".vm").toString();
+ template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name());
+ } catch (ResourceNotFoundException error) {
+ LOGGER.warn("Notification template error", error);
+ templateFilePath = Paths.get(path, "unknown.vm").toString();
+ template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name());
+ }
+ return template;
+ }
+
+ public static FullMessage formatFullMessage(VelocityContext velocityContext, String name) {
+ String formattedMessage = formatMessage(velocityContext, name, "full");
+ return new FullMessage((String) velocityContext.get("subject"), formattedMessage);
+ }
+
+ public static String formatShortMessage(VelocityContext velocityContext, String name) {
+ return formatMessage(velocityContext, name, "short");
+ }
+
+ private static String formatMessage(
+ VelocityContext velocityContext, String name, String templatePath) {
+
+ StringWriter writer = new StringWriter();
+ getTemplate(name, templatePath).merge(velocityContext, writer);
+ return writer.toString();
+ }
+
+}
diff --git a/src/main/java/org/traccar/notificators/NotificatorFirebase.java b/src/main/java/org/traccar/notificators/NotificatorFirebase.java
index 89cdbcb14..78d5da1e2 100644
--- a/src/main/java/org/traccar/notificators/NotificatorFirebase.java
+++ b/src/main/java/org/traccar/notificators/NotificatorFirebase.java
@@ -39,6 +39,8 @@ public class NotificatorFirebase extends Notificator {
public static class Notification {
@JsonProperty("body")
private String body;
+ @JsonProperty("sound")
+ private String sound;
}
public static class Message {
@@ -66,6 +68,7 @@ public class NotificatorFirebase extends Notificator {
Notification notification = new Notification();
notification.body = NotificationFormatter.formatShortMessage(userId, event, position).trim();
+ notification.sound = "default";
Message message = new Message();
message.tokens = user.getString("notificationTokens").split("[, ]");
diff --git a/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java b/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java
index 31064286e..7e3478704 100644
--- a/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2021 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.
@@ -97,7 +97,7 @@ public class AdmProtocolDecoder extends BaseProtocolDecoder {
if (BitUtil.check(type, 5)) {
for (int i = 1; i <= 3; i++) {
- buf.readUnsignedShortLE(); // fuel level
+ position.set("fuel" + i, buf.readUnsignedShortLE());
}
for (int i = 1; i <= 3; i++) {
position.set(Position.PREFIX_TEMP + i, buf.readUnsignedByte());
diff --git a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java
index ff7ef6c4a..186b81470 100644
--- a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2013 - 2021 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.
@@ -16,6 +16,7 @@
package org.traccar.protocol;
import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
@@ -24,6 +25,8 @@ import org.traccar.DeviceSession;
import org.traccar.NetworkMessage;
import org.traccar.Protocol;
import org.traccar.config.Keys;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.DataConverter;
import org.traccar.helper.DateBuilder;
import org.traccar.helper.Parser;
import org.traccar.helper.PatternBuilder;
@@ -115,6 +118,89 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder {
return result;
}
+ private void decodeBeaconData(Position position, int mode, int mask, ByteBuf data) {
+ int i = 1;
+ while (data.isReadable()) {
+ if (BitUtil.check(mask, 7)) {
+ position.set("tag" + i + "Id", ByteBufUtil.hexDump(data.readSlice(6)));
+ }
+ switch (mode) {
+ case 1:
+ if (BitUtil.check(mask, 6)) {
+ data.readUnsignedShort(); // major
+ }
+ if (BitUtil.check(mask, 5)) {
+ data.readUnsignedShort(); // minor
+ }
+ if (BitUtil.check(mask, 4)) {
+ data.readUnsignedByte(); // tx power
+ }
+ if (BitUtil.check(mask, 3)) {
+ position.set("tag" + i + "Rssi", data.readUnsignedByte());
+ }
+ break;
+ case 2:
+ if (BitUtil.check(mask, 6)) {
+ data.readUnsignedShort(); // battery voltage
+ }
+ if (BitUtil.check(mask, 5)) {
+ position.set("tag" + i + "Temp", data.readUnsignedShort());
+ }
+ if (BitUtil.check(mask, 4)) {
+ data.readUnsignedByte(); // tx power
+ }
+ if (BitUtil.check(mask, 3)) {
+ position.set("tag" + i + "Rssi", data.readUnsignedByte());
+ }
+ break;
+ case 3:
+ if (BitUtil.check(mask, 6)) {
+ position.set("tag" + i + "Humidity", data.readUnsignedShort());
+ }
+ if (BitUtil.check(mask, 5)) {
+ position.set("tag" + i + "Temp", data.readUnsignedShort());
+ }
+ if (BitUtil.check(mask, 3)) {
+ position.set("tag" + i + "Rssi", data.readUnsignedByte());
+ }
+ if (BitUtil.check(mask, 2)) {
+ data.readUnsignedShort();
+ }
+ break;
+ case 4:
+ if (BitUtil.check(mask, 6)) {
+ int hardwareId = data.readUnsignedByte();
+ if (BitUtil.check(mask, 5)) {
+ switch (hardwareId) {
+ case 1:
+ case 4:
+ data.skipBytes(11); // fuel
+ break;
+ case 2:
+ data.skipBytes(2); // temperature
+ break;
+ case 3:
+ data.skipBytes(6); // temperature and luminosity
+ break;
+ case 5:
+ data.skipBytes(10); // temperature, humidity, luminosity and pressure
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if (BitUtil.check(mask, 4)) {
+ data.skipBytes(9); // name
+ }
+ break;
+ default:
+ break;
+ }
+ i += 1;
+ }
+ }
+
private void readTextCustomData(Position position, String data, String form) {
CellTower cellTower = new CellTower();
String[] keys = form.substring(1).split("%");
@@ -208,6 +294,12 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder {
case "MT":
position.set(Position.KEY_MOTION, Integer.parseInt(values[i]) > 0);
break;
+ case "BC":
+ String[] beaconValues = values[i].split(":");
+ decodeBeaconData(
+ position, Integer.parseInt(beaconValues[0]), Integer.parseInt(beaconValues[1]),
+ Unpooled.wrappedBuffer(DataConverter.parseHex(beaconValues[2])));
+ break;
default:
break;
}
diff --git a/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java b/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java
index 75fdc3253..96b06557a 100644
--- a/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 2021 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.
@@ -197,7 +197,8 @@ public class DmtProtocolDecoder extends BaseProtocolDecoder {
} else if (fieldId == 6) {
while (buf.readerIndex() < fieldEnd) {
- switch (buf.readUnsignedByte()) {
+ int number = buf.readUnsignedByte();
+ switch (number) {
case 1:
position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001);
break;
@@ -214,7 +215,7 @@ public class DmtProtocolDecoder extends BaseProtocolDecoder {
position.set("solarPower", buf.readUnsignedShortLE() * 0.001);
break;
default:
- buf.readUnsignedShortLE(); // other
+ position.set(Position.PREFIX_IO + number, buf.readUnsignedShortLE());
break;
}
}
diff --git a/src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java b/src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java
index 56d9314b2..e882c2378 100644
--- a/src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2020 - 2021 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,9 +17,11 @@ package org.traccar.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
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.UnitsConverter;
import org.traccar.model.Position;
@@ -43,7 +45,7 @@ public class DolphinProtocolDecoder extends BaseProtocolDecoder {
ByteBuf buf = (ByteBuf) msg;
buf.readUnsignedShort(); // header
- buf.readUnsignedIntLE(); // index
+ int index = (int) buf.readUnsignedIntLE();
buf.readUnsignedShort(); // version
buf.readUnsignedShort(); // flags
int type = buf.readUnsignedShortLE();
@@ -61,6 +63,24 @@ public class DolphinProtocolDecoder extends BaseProtocolDecoder {
DolphinMessages.DataPackRequest message = DolphinMessages.DataPackRequest.parseFrom(
ByteBufUtil.getBytes(buf, buf.readerIndex(), length, false));
+ if (channel != null) {
+ byte[] responseData = DolphinMessages.DataPackResponse.newBuilder()
+ .setResponse(DolphinMessages.DataPackResponseCode.DataPack_OK)
+ .build()
+ .toByteArray();
+
+ ByteBuf response = Unpooled.buffer();
+ response.writeShort(0xABAB); // header
+ response.writeIntLE(index);
+ response.writeShort(0); // flags
+ response.writeShortLE(DolphinMessages.MessageType.DataPack_Response.getNumber());
+ response.writeIntLE(responseData.length);
+ response.writeIntLE(0); // reserved
+ response.writeBytes(responseData);
+
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
+ }
+
List<Position> positions = new LinkedList<>();
for (int i = 0; i < message.getPointsCount(); i++) {
diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java
index fe42a44d7..9bed63266 100644
--- a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2021 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.
@@ -95,6 +95,16 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
.number("(d+),") // size
.compile();
+ private static final Pattern PATTERN_RESULT = new PatternBuilder()
+ .text("$$")
+ .number("d+,") // length
+ .number("(d+),") // imei
+ .any()
+ .expression(",([A-Z]+)") // result
+ .text("*")
+ .number("xx")
+ .compile();
+
private void requestPhoto(Channel channel, SocketAddress socketAddress, String imei, String file) {
if (channel != null) {
String content = "1,D06," + file + "," + photo.writerIndex() + "," + Math.min(1024, photo.writableBytes());
@@ -203,6 +213,27 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private Object decodeResult(
+ Channel channel, SocketAddress remoteAddress, String sentence) {
+
+ Parser parser = new Parser(PATTERN_RESULT, sentence);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.set(Position.KEY_RESULT, parser.next());
+
+ return position;
+ }
+
@Override
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
@@ -213,7 +244,12 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
typeIndex = buf.indexOf(typeIndex, buf.writerIndex(), (byte) ',') + 1;
String type = buf.toString(typeIndex, 3, StandardCharsets.US_ASCII);
- if (type.equals("D05")) {
+ if (type.startsWith("B")) {
+
+ return decodeResult(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII));
+
+ } else if (type.equals("D05")) {
+
String sentence = buf.toString(StandardCharsets.US_ASCII);
Parser parser = new Parser(PATTERN_PHOTO, sentence);
if (parser.matches()) {
@@ -223,7 +259,9 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
photo = Unpooled.buffer(length);
requestPhoto(channel, remoteAddress, imei, photoId);
}
+
} else if (type.equals("D06")) {
+
if (photo == null) {
return null;
}
@@ -251,9 +289,11 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
return position;
}
}
+
} else {
- String sentence = buf.toString(StandardCharsets.US_ASCII);
- return decodeLocation(channel, remoteAddress, sentence);
+
+ return decodeLocation(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII));
+
}
return null;
diff --git a/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java b/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java
index b6d7f4e45..aded35823 100644
--- a/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2021 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.
@@ -75,9 +75,9 @@ public class FreematicsProtocolDecoder extends BaseProtocolDecoder {
}
private Object decodePosition(
- Channel channel, SocketAddress remoteAddress, String sentence) throws Exception {
+ Channel channel, SocketAddress remoteAddress, String sentence, String id) {
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id);
if (deviceSession == null) {
return null;
}
@@ -88,94 +88,104 @@ public class FreematicsProtocolDecoder extends BaseProtocolDecoder {
for (String pair : sentence.split(",")) {
String[] data = pair.split("[=:]");
- int key = Integer.parseInt(data[0], 16);
+ int key;
+ try {
+ key = Integer.parseInt(data[0], 16);
+ } catch (NumberFormatException e) {
+ continue;
+ }
String value = data[1];
- switch (key) {
- case 0x0:
- if (position != null) {
- position.setTime(dateBuilder.getDate());
- positions.add(position);
- }
- position = new Position(getProtocolName());
- position.setDeviceId(deviceSession.getDeviceId());
- position.setValid(true);
- dateBuilder = new DateBuilder(new Date());
- break;
- case 0x11:
- value = ("000000" + value).substring(value.length());
- dateBuilder.setDateReverse(
- Integer.parseInt(value.substring(0, 2)),
- Integer.parseInt(value.substring(2, 4)),
- Integer.parseInt(value.substring(4)));
- break;
- case 0x10:
- value = ("00000000" + value).substring(value.length());
- dateBuilder.setTime(
- Integer.parseInt(value.substring(0, 2)),
- Integer.parseInt(value.substring(2, 4)),
- Integer.parseInt(value.substring(4, 6)),
- Integer.parseInt(value.substring(6)) * 10);
- break;
- case 0xA:
- position.setLatitude(Double.parseDouble(value));
- break;
- case 0xB:
- position.setLongitude(Double.parseDouble(value));
- break;
- case 0xC:
- position.setAltitude(Double.parseDouble(value));
- break;
- case 0xD:
- position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(value)));
- break;
- case 0xE:
- position.setCourse(Integer.parseInt(value));
- break;
- case 0xF:
- position.set(Position.KEY_SATELLITES, Integer.parseInt(value));
- break;
- case 0x12:
- position.set(Position.KEY_HDOP, Integer.parseInt(value));
- break;
- case 0x20:
- position.set(Position.KEY_ACCELERATION, value);
- break;
- case 0x24:
- position.set(Position.KEY_BATTERY, Integer.parseInt(value) * 0.01);
- break;
- case 0x81:
- position.set(Position.KEY_RSSI, Integer.parseInt(value));
- break;
- case 0x82:
- position.set(Position.KEY_DEVICE_TEMP, Integer.parseInt(value) * 0.1);
- break;
- case 0x104:
- position.set(Position.KEY_ENGINE_LOAD, Integer.parseInt(value));
- break;
- case 0x105:
- position.set(Position.KEY_COOLANT_TEMP, Integer.parseInt(value));
- break;
- case 0x10c:
- position.set(Position.KEY_RPM, Integer.parseInt(value));
- break;
- case 0x10d:
- position.set(Position.KEY_OBD_SPEED, UnitsConverter.knotsFromKph(Integer.parseInt(value)));
- break;
- case 0x111:
- position.set(Position.KEY_THROTTLE, Integer.parseInt(value));
- break;
- default:
- position.set(Position.PREFIX_IO + key, value);
- break;
+ if (key == 0x0) {
+ if (position != null) {
+ position.setTime(dateBuilder.getDate());
+ positions.add(position);
+ }
+ position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+ dateBuilder = new DateBuilder(new Date());
+ } else if (position != null) {
+ switch (key) {
+ case 0x11:
+ value = ("000000" + value).substring(value.length());
+ dateBuilder.setDateReverse(
+ Integer.parseInt(value.substring(0, 2)),
+ Integer.parseInt(value.substring(2, 4)),
+ Integer.parseInt(value.substring(4)));
+ break;
+ case 0x10:
+ value = ("00000000" + value).substring(value.length());
+ dateBuilder.setTime(
+ Integer.parseInt(value.substring(0, 2)),
+ Integer.parseInt(value.substring(2, 4)),
+ Integer.parseInt(value.substring(4, 6)),
+ Integer.parseInt(value.substring(6)) * 10);
+ break;
+ case 0xA:
+ position.setValid(true);
+ position.setLatitude(Double.parseDouble(value));
+ break;
+ case 0xB:
+ position.setValid(true);
+ position.setLongitude(Double.parseDouble(value));
+ break;
+ case 0xC:
+ position.setAltitude(Double.parseDouble(value));
+ break;
+ case 0xD:
+ position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(value)));
+ break;
+ case 0xE:
+ position.setCourse(Integer.parseInt(value));
+ break;
+ case 0xF:
+ position.set(Position.KEY_SATELLITES, Integer.parseInt(value));
+ break;
+ case 0x12:
+ position.set(Position.KEY_HDOP, Integer.parseInt(value));
+ break;
+ case 0x20:
+ position.set(Position.KEY_ACCELERATION, value);
+ break;
+ case 0x24:
+ position.set(Position.KEY_BATTERY, Integer.parseInt(value) * 0.01);
+ break;
+ case 0x81:
+ position.set(Position.KEY_RSSI, Integer.parseInt(value));
+ break;
+ case 0x82:
+ position.set(Position.KEY_DEVICE_TEMP, Integer.parseInt(value) * 0.1);
+ break;
+ case 0x104:
+ position.set(Position.KEY_ENGINE_LOAD, Integer.parseInt(value));
+ break;
+ case 0x105:
+ position.set(Position.KEY_COOLANT_TEMP, Integer.parseInt(value));
+ break;
+ case 0x10c:
+ position.set(Position.KEY_RPM, Integer.parseInt(value));
+ break;
+ case 0x10d:
+ position.set(Position.KEY_OBD_SPEED, UnitsConverter.knotsFromKph(Integer.parseInt(value)));
+ break;
+ case 0x111:
+ position.set(Position.KEY_THROTTLE, Integer.parseInt(value));
+ break;
+ default:
+ position.set(Position.PREFIX_IO + key, value);
+ break;
+ }
}
}
if (position != null) {
+ if (!position.getValid()) {
+ getLastLocation(position, null);
+ }
position.setTime(dateBuilder.getDate());
positions.add(position);
}
- return positions;
+ return positions.isEmpty() ? null : positions;
}
@Override
@@ -187,12 +197,13 @@ public class FreematicsProtocolDecoder extends BaseProtocolDecoder {
int endIndex = sentence.indexOf('*');
if (startIndex > 0 && endIndex > 0) {
+ String id = sentence.substring(0, startIndex);
sentence = sentence.substring(startIndex + 1, endIndex);
if (sentence.startsWith("EV")) {
return decodeEvent(channel, remoteAddress, sentence);
} else {
- return decodePosition(channel, remoteAddress, sentence);
+ return decodePosition(channel, remoteAddress, sentence, id);
}
}
diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
index d438aa33d..126656361 100644
--- a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2021 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.
@@ -75,7 +75,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
.expression("(?:[0-9Ff]{20})?,") // iccid
.number("(d{1,2}),") // rssi
.number("d{1,2},")
- .expression("[01],") // external power
+ .expression("[01]{1,2},") // external power
.number("([d.]+)?,") // odometer or external power
.number("d*,") // backup battery or lightness
.number("(d+.d+),") // battery
@@ -97,6 +97,8 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
.number("(xx)?,") // digital output
.number("[-+]dddd,") // timezone
.expression("[01],") // daylight saving
+ .or()
+ .any()
.groupEnd()
.number("(dddd)(dd)(dd)") // date (yyyymmdd)
.number("(dd)(dd)(dd),") // time (hhmmss)
@@ -237,8 +239,14 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
.number("(d{5}:dd:dd)?,") // hour meter
.number("(x+)?,") // adc 1
.number("(x+)?,").optional() // adc 2
+ .groupBegin()
+ .number("(x+)?,") // adc 3
+ .number("(xx),") // inputs
+ .number("(xx),") // outputs
+ .or()
.number("(d{1,3})?,") // battery
.number("(?:(xx)(xx)(xx))?,") // device status
+ .groupEnd()
.expression("(.*)") // additional data
.or()
.number("d*,,")
@@ -920,15 +928,21 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_POWER, power * 0.001);
}
- if (parser.hasNext(9)) {
+ if (parser.hasNext(12)) {
position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000);
position.set(Position.KEY_HOURS, parseHours(parser.next()));
position.set(Position.PREFIX_ADC + 1, parser.next());
position.set(Position.PREFIX_ADC + 2, parser.next());
- position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt());
-
- decodeStatus(position, parser);
+ position.set(Position.PREFIX_ADC + 3, parser.next());
+ if (parser.hasNext(2)) {
+ position.set(Position.KEY_INPUT, parser.nextHexInt());
+ position.set(Position.KEY_OUTPUT, parser.nextHexInt());
+ }
+ if (parser.hasNext(4)) {
+ position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt());
+ decodeStatus(position, parser);
+ }
int index = 0;
String[] data = parser.next().split(",");
diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
index c5e8809e3..b7d6d2595 100644
--- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
@@ -838,7 +838,9 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
buf.readUnsignedShort(); // satellites
buf.readUnsignedByte(); // alarm
buf.readUnsignedByte(); // language
- buf.readUnsignedByte(); // battery
+
+ position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte());
+
buf.readUnsignedByte(); // working mode
buf.readUnsignedShort(); // working voltage
buf.readUnsignedByte(); // reserved
diff --git a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java
index 3ad70bdca..2e1ddf5f2 100644
--- a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2021 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.
@@ -234,8 +234,14 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder {
int length = buf.readUnsignedShort() - 4;
switch (subtype) {
case 0x0001:
- position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedByte() - 40);
- position.set(Position.KEY_RPM, buf.readUnsignedShort());
+ int coolantTemperature = buf.readUnsignedByte() - 40;
+ if (coolantTemperature <= 215) {
+ position.set(Position.KEY_COOLANT_TEMP, coolantTemperature);
+ }
+ int rpm = buf.readUnsignedShort();
+ if (rpm <= 65535) {
+ position.set(Position.KEY_RPM, rpm);
+ }
position.set("averageSpeed", buf.readUnsignedByte());
buf.readUnsignedShort(); // interval fuel consumption
position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort() * 0.01);
diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
index a3d16cb62..675a08aef 100644
--- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2021 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.
@@ -45,6 +45,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_GENERAL_RESPONSE = 0x8001;
public static final int MSG_GENERAL_RESPONSE_2 = 0x4401;
+ public static final int MSG_HEARTBEAT = 0x0002;
public static final int MSG_TERMINAL_REGISTER = 0x0100;
public static final int MSG_TERMINAL_REGISTER_RESPONSE = 0x8100;
public static final int MSG_TERMINAL_CONTROL = 0x8105;
@@ -171,7 +172,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
formatMessage(MSG_TERMINAL_REGISTER_RESPONSE, id, false, response), remoteAddress));
}
- } else if (type == MSG_TERMINAL_AUTH) {
+ } else if (type == MSG_TERMINAL_AUTH || type == MSG_HEARTBEAT) {
sendGeneralResponse(channel, remoteAddress, id, type, index);
@@ -334,24 +335,36 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
position.set("cover", BitUtil.check(deviceStatus, 3));
break;
case 0xEB:
- while (buf.readerIndex() < endIndex) {
- int extendedLength = buf.readUnsignedShort();
- int extendedType = buf.readUnsignedShort();
- switch (extendedType) {
- case 0x0001:
- position.set("fuel1", buf.readUnsignedShort() * 0.1);
- buf.readUnsignedByte(); // unused
- break;
- case 0x0023:
- position.set("fuel2", Double.parseDouble(
- buf.readCharSequence(6, StandardCharsets.US_ASCII).toString()));
- break;
- case 0x00CE:
- position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01);
- break;
- default:
- buf.skipBytes(extendedLength - 2);
- break;
+ if (buf.getUnsignedShort(buf.readerIndex()) > 200) {
+ Network network = new Network();
+ int mcc = buf.readUnsignedShort();
+ int mnc = buf.readUnsignedByte();
+ while (buf.readerIndex() < endIndex) {
+ network.addCellTower(CellTower.from(
+ mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedShort(),
+ buf.readUnsignedByte()));
+ }
+ position.setNetwork(network);
+ } else {
+ while (buf.readerIndex() < endIndex) {
+ int extendedLength = buf.readUnsignedShort();
+ int extendedType = buf.readUnsignedShort();
+ switch (extendedType) {
+ case 0x0001:
+ position.set("fuel1", buf.readUnsignedShort() * 0.1);
+ buf.readUnsignedByte(); // unused
+ break;
+ case 0x0023:
+ position.set("fuel2", Double.parseDouble(
+ buf.readCharSequence(6, StandardCharsets.US_ASCII).toString()));
+ break;
+ case 0x00CE:
+ position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01);
+ break;
+ default:
+ buf.skipBytes(extendedLength - 2);
+ break;
+ }
}
}
break;
diff --git a/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java b/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java
index bbb63fb65..9c94ffd4b 100644
--- a/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java
@@ -84,6 +84,156 @@ public class IotmProtocolDecoder extends BaseProtocolDecoder {
}
}
+ private void decodeSensor(Position position, ByteBuf record, int sensorType, int sensorId) {
+ String key;
+ switch (sensorId) {
+ case 0x0002:
+ position.set(Position.KEY_MOTION, sensorType > 0);
+ break;
+ case 0x0008:
+ case 0x009B:
+ if (sensorType > 0) {
+ position.set(Position.KEY_ALARM, Position.ALARM_JAMMING);
+ }
+ break;
+ case 0x0010:
+ case 0x0011:
+ case 0x0012:
+ case 0x0013:
+ case 0x0014:
+ case 0x0015:
+ key = Position.PREFIX_IN + (sensorId - 0x0010 + 2);
+ position.set(key, sensorType > 0);
+ break;
+ case 0x0062:
+ position.set("doorFL", sensorType > 0);
+ break;
+ case 0x0063:
+ position.set("doorFR", sensorType > 0);
+ break;
+ case 0x0064:
+ position.set("doorRL", sensorType > 0);
+ break;
+ case 0x0065:
+ position.set("doorRR", sensorType > 0);
+ break;
+ case 0x001E:
+ position.set("buttonPresent", sensorType > 0);
+ break;
+ case 0x006D:
+ position.set(Position.KEY_IGNITION, sensorType > 0);
+ break;
+ case 0x008B:
+ position.set("handBrake", sensorType > 0);
+ break;
+ case 0x008C:
+ position.set("footBrake", sensorType > 0);
+ break;
+ case 0x0094:
+ case 0x0095:
+ case 0x0096:
+ key = Position.PREFIX_OUT + (sensorId - 0x0094 + 1);
+ position.set(key, sensorType > 0);
+ break;
+ case 0x009A:
+ position.set(Position.PREFIX_OUT + 4, sensorType > 0);
+ break;
+ case 0x2000:
+ position.set(Position.KEY_OBD_SPEED, record.readUnsignedByte());
+ break;
+ case 0x2001:
+ position.set(Position.KEY_SATELLITES, record.readUnsignedByte());
+ break;
+ case 0x2006:
+ position.set(Position.KEY_THROTTLE, record.readUnsignedByte());
+ break;
+ case 0x2007:
+ position.set(Position.KEY_FUEL_LEVEL, record.readUnsignedByte());
+ break;
+ case 0x2008:
+ position.set(Position.KEY_COOLANT_TEMP, record.readUnsignedByte());
+ break;
+ case 0x2009:
+ position.set("fuel2", record.readUnsignedByte());
+ break;
+ case 0x200A:
+ position.set(Position.KEY_ENGINE_LOAD, record.readUnsignedByte());
+ break;
+ case 0x2041:
+ position.set(Position.KEY_BATTERY_LEVEL, record.readUnsignedByte());
+ break;
+ case 0x3000:
+ position.set(Position.KEY_POWER, record.readUnsignedShortLE() * 0.001);
+ break;
+ case 0x3001:
+ case 0x3002:
+ case 0x3003:
+ key = Position.PREFIX_ADC + (0x3003 - sensorId + 3);
+ position.set(key, record.readUnsignedShortLE() * 0.001);
+ break;
+ case 0x3004:
+ position.set(Position.KEY_BATTERY, record.readUnsignedShortLE() * 0.001);
+ break;
+ case 0x300C:
+ position.set(Position.KEY_RPM, record.readUnsignedShortLE());
+ break;
+ case 0x3021:
+ position.set(Position.KEY_FUEL_CONSUMPTION, record.readUnsignedShortLE() * 0.05);
+ break;
+ case 0x3037:
+ position.set("cargoWeight", record.readUnsignedShortLE() * 2);
+ break;
+ case 0x4001:
+ position.set(Position.KEY_FUEL_USED, record.readUnsignedIntLE());
+ break;
+ case 0x4002:
+ position.set(Position.KEY_HOURS, record.readUnsignedIntLE());
+ break;
+ case 0x4003:
+ position.set(Position.KEY_ODOMETER, record.readUnsignedIntLE() * 5);
+ break;
+ case 0x4063:
+ position.set(Position.KEY_AXLE_WEIGHT, record.readUnsignedIntLE());
+ break;
+ case 0x5000:
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(record.readLongLE()));
+ break;
+ case 0x5004:
+ case 0x5005:
+ case 0x5006:
+ case 0x5007:
+ key = Position.PREFIX_TEMP + (sensorId - 0x5004 + 1);
+ position.set(key, record.readLongLE());
+ break;
+ case 0x500D:
+ position.set("trailerId", String.valueOf(record.readLongLE()));
+ break;
+ case 0xA000:
+ position.set(Position.KEY_DEVICE_TEMP, record.readFloatLE());
+ break;
+ case 0xA001:
+ position.set(Position.KEY_ACCELERATION, record.readFloatLE());
+ break;
+ case 0xA002:
+ position.set("cornering", record.readFloatLE());
+ break;
+ case 0xA017:
+ case 0xA018:
+ case 0xA019:
+ case 0xA01A:
+ key = Position.PREFIX_TEMP + (sensorId - 0xA017 + 1);
+ position.set(key, record.readFloatLE());
+ break;
+ case 0xB002:
+ position.set(Position.KEY_OBD_ODOMETER, record.readDoubleLE());
+ break;
+ default:
+ key = Position.PREFIX_IO + sensorId;
+ position.getAttributes().put(key, readValue(record, sensorType));
+ break;
+ }
+ }
+
@Override
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
@@ -163,62 +313,7 @@ public class IotmProtocolDecoder extends BaseProtocolDecoder {
continue;
}
- String key;
- switch (sensorId) {
- case 0x0008:
- if (sensorType > 0) {
- position.set(Position.KEY_ALARM, Position.ALARM_JAMMING);
- }
- break;
- case 0x0010:
- case 0x0011:
- case 0x0012:
- case 0x0013:
- key = Position.PREFIX_IN + (sensorId - 0x0010 + 2);
- position.set(key, sensorType > 0);
- break;
- case 0x001E:
- position.set("buttonPresent", sensorType > 0);
- break;
- case 0x006D:
- position.set(Position.KEY_IGNITION, sensorType > 0);
- break;
- case 0x3000:
- position.set(Position.KEY_POWER, record.readUnsignedShortLE() * 0.001);
- break;
- case 0x3001:
- case 0x3002:
- case 0x3003:
- key = Position.PREFIX_ADC + (0x3003 - sensorId + 3);
- position.set(key, record.readUnsignedShortLE() * 0.001);
- break;
- case 0x3004:
- position.set(Position.KEY_BATTERY, record.readUnsignedShortLE() * 0.001);
- break;
- case 0x300C:
- position.set(Position.KEY_RPM, record.readUnsignedShortLE());
- break;
- case 0x4003:
- position.set(Position.KEY_ODOMETER, record.readUnsignedIntLE() * 5);
- break;
- case 0x5000:
- position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(record.readLongLE()));
- break;
- case 0xA001:
- position.set(Position.KEY_ACCELERATION, record.readFloatLE());
- break;
- case 0xA002:
- position.set("cornering", record.readFloatLE());
- break;
- case 0xA017:
- key = Position.PREFIX_TEMP + (sensorId - 0xA017 + 1);
- position.set(key, record.readFloatLE());
- break;
- default:
- key = Position.PREFIX_IO + sensorId;
- position.getAttributes().put(key, readValue(record, sensorType));
- break;
- }
+ decodeSensor(position, record, sensorType, sensorId);
}
}
diff --git a/src/main/java/org/traccar/protocol/KhdProtocol.java b/src/main/java/org/traccar/protocol/KhdProtocol.java
index f77f4c311..60a2aea7f 100644
--- a/src/main/java/org/traccar/protocol/KhdProtocol.java
+++ b/src/main/java/org/traccar/protocol/KhdProtocol.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2021 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.
@@ -26,7 +26,13 @@ public class KhdProtocol extends BaseProtocol {
public KhdProtocol() {
setSupportedDataCommands(
Command.TYPE_ENGINE_STOP,
- Command.TYPE_ENGINE_RESUME);
+ Command.TYPE_ENGINE_RESUME,
+ Command.TYPE_GET_VERSION,
+ Command.TYPE_FACTORY_RESET,
+ Command.TYPE_SET_SPEED_LIMIT,
+ Command.TYPE_SET_ODOMETER,
+ Command.TYPE_POSITION_SINGLE);
+
addServer(new TrackerServer(false, getName()) {
@Override
protected void addProtocolHandlers(PipelineBuilder pipeline) {
diff --git a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java
index 16ad616ed..1f3798bd2 100644
--- a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java
@@ -125,11 +125,15 @@ public class KhdProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_ODOMETER, buf.readUnsignedMedium());
position.set(Position.KEY_STATUS, buf.readUnsignedInt());
- position.set(Position.KEY_HDOP, buf.readUnsignedByte());
- position.set(Position.KEY_VDOP, buf.readUnsignedByte());
- position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
- buf.skipBytes(5); // other location data
+ buf.readUnsignedShort();
+ buf.readUnsignedByte();
+ buf.readUnsignedByte();
+ buf.readUnsignedByte();
+ buf.readUnsignedByte();
+ buf.readUnsignedByte();
+
+ position.set(Position.KEY_RESULT, buf.readUnsignedByte());
if (type == MSG_PERIPHERAL) {
diff --git a/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java b/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java
index 4a8df26c8..8aeb9660d 100644
--- a/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java
+++ b/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2021 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,14 +24,19 @@ import org.traccar.Protocol;
public class KhdProtocolEncoder extends BaseProtocolEncoder {
+ public static final int MSG_ON_DEMAND_TRACK = 0x30;
public static final int MSG_CUT_OIL = 0x39;
public static final int MSG_RESUME_OIL = 0x38;
+ public static final int MSG_CHECK_VERSION = 0x3D;
+ public static final int MSG_FACTORY_RESET = 0xC3;
+ public static final int MSG_SET_OVERSPEED = 0x3F;
+ public static final int MSG_DELETE_MILEAGE = 0x66;
public KhdProtocolEncoder(Protocol protocol) {
super(protocol);
}
- private ByteBuf encodeCommand(int command, String uniqueId) {
+ private ByteBuf encodeCommand(int command, String uniqueId, ByteBuf content) {
ByteBuf buf = Unpooled.buffer();
@@ -39,7 +44,12 @@ public class KhdProtocolEncoder extends BaseProtocolEncoder {
buf.writeByte(0x29);
buf.writeByte(command);
- buf.writeShort(6); // size
+
+ int length = 6;
+ if (content != null) {
+ length += content.readableBytes();
+ }
+ buf.writeShort(length);
uniqueId = "00000000".concat(uniqueId);
uniqueId = uniqueId.substring(uniqueId.length() - 8);
@@ -48,6 +58,10 @@ public class KhdProtocolEncoder extends BaseProtocolEncoder {
buf.writeByte(Integer.parseInt(uniqueId.substring(4, 6)) + 0x80);
buf.writeByte(Integer.parseInt(uniqueId.substring(6, 8)));
+ if (content != null) {
+ buf.writeBytes(content);
+ }
+
buf.writeByte(Checksum.xor(buf.nioBuffer()));
buf.writeByte(0x0D); // ending
@@ -61,9 +75,21 @@ public class KhdProtocolEncoder extends BaseProtocolEncoder {
switch (command.getType()) {
case Command.TYPE_ENGINE_STOP:
- return encodeCommand(MSG_CUT_OIL, uniqueId);
+ return encodeCommand(MSG_CUT_OIL, uniqueId, null);
case Command.TYPE_ENGINE_RESUME:
- return encodeCommand(MSG_RESUME_OIL, uniqueId);
+ return encodeCommand(MSG_RESUME_OIL, uniqueId, null);
+ case Command.TYPE_GET_VERSION:
+ return encodeCommand(MSG_CHECK_VERSION, uniqueId, null);
+ case Command.TYPE_FACTORY_RESET:
+ return encodeCommand(MSG_FACTORY_RESET, uniqueId, null);
+ case Command.TYPE_SET_SPEED_LIMIT:
+ ByteBuf content = Unpooled.buffer();
+ content.writeByte(Integer.parseInt(command.getString(Command.KEY_DATA)));
+ return encodeCommand(MSG_RESUME_OIL, uniqueId, content);
+ case Command.TYPE_SET_ODOMETER:
+ return encodeCommand(MSG_DELETE_MILEAGE, uniqueId, null);
+ case Command.TYPE_POSITION_SINGLE:
+ return encodeCommand(MSG_ON_DEMAND_TRACK, uniqueId, null);
default:
return null;
}
diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java
index 89217a39b..68e9e8dd5 100644
--- a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java
@@ -189,13 +189,17 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
case 0x28:
int beaconFlags = buf.readUnsignedByte();
position.set("tagId", ByteBufUtil.hexDump(buf.readSlice(6)));
- buf.readUnsignedByte(); // rssi
+ position.set("tagRssi", buf.readUnsignedByte());
buf.readUnsignedByte(); // 1m rssi
if (BitUtil.check(beaconFlags, 7)) {
position.setLatitude(buf.readIntLE() * 0.0000001);
position.setLongitude(buf.readIntLE() * 0.0000001);
hasLocation = true;
}
+ if (BitUtil.check(beaconFlags, 6)) {
+ position.set("description", buf.readCharSequence(
+ endIndex - buf.readerIndex(), StandardCharsets.US_ASCII).toString());
+ }
break;
case 0x30:
buf.readUnsignedInt(); // timestamp
diff --git a/src/main/java/org/traccar/protocol/NavtelecomProtocol.java b/src/main/java/org/traccar/protocol/NavtelecomProtocol.java
new file mode 100644
index 000000000..c76b42768
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/NavtelecomProtocol.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2021 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;
+
+public class NavtelecomProtocol extends BaseProtocol {
+
+ public NavtelecomProtocol() {
+ addServer(new TrackerServer(false, getName()) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline) {
+ pipeline.addLast(new LengthFieldBasedFrameDecoder(256, 2, 1, 2, 0));
+ pipeline.addLast(new Gt02ProtocolDecoder(NavtelecomProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java b/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java
new file mode 100644
index 000000000..2362b1870
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2021 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.Checksum;
+
+import java.net.SocketAddress;
+import java.nio.charset.StandardCharsets;
+
+public class NavtelecomProtocolDecoder extends BaseProtocolDecoder {
+
+ public NavtelecomProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ ByteBuf buf = (ByteBuf) msg;
+
+ buf.skipBytes(4); // preamble
+ int receiver = buf.readIntLE();
+ int sender = buf.readIntLE();
+ int length = buf.readUnsignedShortLE();
+ buf.readUnsignedByte(); // data checksum
+ buf.readUnsignedByte(); // header checksum
+
+ String sentence = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString();
+
+ if (sentence.startsWith("*>S")) {
+
+ String data = "*<S";
+
+ ByteBuf response = Unpooled.buffer();
+ response.writeCharSequence("@NTC", StandardCharsets.US_ASCII);
+ response.writeIntLE(sender);
+ response.writeIntLE(receiver);
+ response.writeShortLE(data.length());
+ response.writeByte(Checksum.xor(data));
+ response.writeByte(Checksum.xor(response.nioBuffer()));
+ response.writeCharSequence(data, StandardCharsets.US_ASCII);
+
+ if (channel != null) {
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
+ }
+
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java
index 05601ed51..b45fdbb5a 100644
--- a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2021 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.
@@ -80,14 +80,14 @@ public class RstProtocolDecoder extends BaseProtocolDecoder {
return null;
}
- String archive = parser.next();
+ parser.next(); // archive
String model = parser.next();
String firmware = parser.next();
String serial = parser.next();
int index = parser.nextInt();
parser.nextInt(); // type
- if (channel != null && archive.equals("A")) {
+ if (channel != null) {
String response = "RST;A;" + model + ";" + firmware + ";" + serial + ";" + index + ";6;FIM;";
channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
}
diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java
index 227a9ac91..b6378f416 100644
--- a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org)
+ * Copyright 2013 - 2021 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.
@@ -110,6 +110,16 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder {
case 80:
position.set(Position.PREFIX_TEMP + (id - 78), readValue(buf, length, true) * 0.1);
break;
+ case 198:
+ position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED);
+ break;
+ case 199:
+ case 200:
+ position.set(Position.KEY_ALARM, Position.ALARM_BRAKING);
+ break;
+ case 201:
+ position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION);
+ break;
default:
position.set(Position.PREFIX_IO + id, readValue(buf, length, false));
break;
diff --git a/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java b/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java
index 6b97f5fe0..bf8bfab77 100644
--- a/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 2021 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.
@@ -38,12 +38,12 @@ public class SiwiProtocolDecoder extends BaseProtocolDecoder {
.number("(d+),") // device id
.number("d+,") // unit no
.expression("([A-Z]),") // reason
- .number("d+,") // command code
+ .number("d*,") // command code
.number("[^,]*,") // command value
.expression("([01]),") // ignition
.expression("[01],") // power cut
- .expression("[01],") // box open
- .number("d+,") // message key
+ .number("d+,") // flags
+ .number("[^,]+,")
.number("(d+),") // odometer
.number("(d+),") // speed
.number("(d+),") // satellites
@@ -54,6 +54,19 @@ public class SiwiProtocolDecoder extends BaseProtocolDecoder {
.number("(d+),") // course
.number("(dd)(dd)(dd),") // time (hhmmss)
.number("(dd)(dd)(dd),") // date (ddmmyy)
+ .number("d+,") // signal strength
+ .number("d+,") // gsm status
+ .number("d+,") // error code
+ .number("d+,") // internal status
+ .number("(d+),") // battery
+ .number("(d+),") // adc
+ .number("(d+),") // digital inputs
+ .number("(d+),") // sensor 1
+ .number("(d+),") // sensor 2
+ .number("(d+),") // sensor 3
+ .number("(d+),") // sensor 4
+ .expression("([^,]+),") // hw version
+ .expression("([^,]+),") // sw version
.any()
.compile();
@@ -90,6 +103,20 @@ public class SiwiProtocolDecoder extends BaseProtocolDecoder {
position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY, "IST"));
+ position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001);
+ position.set(Position.PREFIX_ADC + 1, parser.nextInt() * 0.01);
+ position.set(Position.KEY_INPUT, parser.nextInt());
+
+ for (int i = 1; i <= 4; i++) {
+ int value = parser.nextInt();
+ if (value != 0) {
+ position.set(Position.PREFIX_IO + i, value);
+ }
+ }
+
+ position.set(Position.KEY_VERSION_HW, parser.next());
+ position.set(Position.KEY_VERSION_FW, parser.next());
+
return position;
}
diff --git a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java
index 882bed81b..82f0e4061 100644
--- a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 2021 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.
@@ -193,6 +193,12 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder {
case "#TVI#":
position.set(Position.KEY_DEVICE_TEMP, Double.parseDouble(data[i]));
break;
+ case "#CFL#":
+ position.set(Position.KEY_FUEL_LEVEL, Integer.parseInt(data[i]));
+ break;
+ case "#CFL2#":
+ position.set("fuel2", Integer.parseInt(data[i]));
+ break;
case "#IN1#":
case "#IN2#":
case "#IN3#":
diff --git a/src/main/java/org/traccar/protocol/StartekProtocol.java b/src/main/java/org/traccar/protocol/StartekProtocol.java
new file mode 100644
index 000000000..d7a4b7d04
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/StartekProtocol.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 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.LineBasedFrameDecoder;
+import io.netty.handler.codec.string.StringDecoder;
+import io.netty.handler.codec.string.StringEncoder;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+
+public class StartekProtocol extends BaseProtocol {
+
+ public StartekProtocol() {
+ addServer(new TrackerServer(false, getName()) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline) {
+ pipeline.addLast(new LineBasedFrameDecoder(1024));
+ pipeline.addLast(new StringEncoder());
+ pipeline.addLast(new StringDecoder());
+ pipeline.addLast(new Ardi01ProtocolDecoder(StartekProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java
new file mode 100644
index 000000000..3868e96fe
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2021 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.channel.Channel;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.DeviceSession;
+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 java.net.SocketAddress;
+import java.util.regex.Pattern;
+
+public class StartekProtocolDecoder extends BaseProtocolDecoder {
+
+ public StartekProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ private static final Pattern PATTERN = new PatternBuilder()
+ .text("&&")
+ .expression(".") // index
+ .number("d+,") // length
+ .number("(d+),") // imei
+ .number("xxx,") // command
+ .number("(d),") // event
+ .expression("[^,]*,") // event data
+ .number("(dd)(dd)(dd)") // date (yyymmdd)
+ .number("(dd)(dd)(dd),") // time (hhmmss)
+ .expression("([AV]),") // valid
+ .number("(-?d+.d+),") // longitude
+ .number("(-?d+.d+),") // latitude
+ .number("(d+),") // satellites
+ .number("(d+.d+),") // hdop
+ .number("(d+),") // speed
+ .number("(d+),") // course
+ .number("(-?d+),") // altitude
+ .number("(d+),") // odometer
+ .number("(d+)|") // mcc
+ .number("(d+)|") // mnc
+ .number("(x+)|") // lac
+ .number("(x+),") // cid
+ .number("(d+),") // rssi
+ .number("(x+),") // status
+ .number("(x+),") // inputs
+ .number("(x+),") // outputs
+ .number("(x+)|") // power
+ .number("(x+)|") // battery
+ .expression("([^,]+),") // adc
+ .number("d,") // extended
+ .any()
+ .compile();
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ Parser parser = new Parser(PATTERN, (String) msg);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.set(Position.KEY_EVENT, parser.nextInt());
+
+ position.setTime(parser.nextDateTime());
+ position.setValid(parser.next().equals("A"));
+ position.setLatitude(parser.nextDouble());
+ position.setLongitude(parser.nextDouble());
+
+ position.set(Position.KEY_SATELLITES, parser.nextInt());
+ position.set(Position.KEY_HDOP, parser.nextDouble());
+
+ position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt()));
+ position.setCourse(parser.nextInt());
+ position.setAltitude(parser.nextInt());
+
+ position.set(Position.KEY_ODOMETER, parser.nextInt());
+
+ position.setNetwork(new Network(CellTower.from(
+ parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt(), parser.nextInt())));
+
+ position.set(Position.KEY_STATUS, parser.nextHexInt());
+ position.set(Position.KEY_INPUT, parser.nextHexInt());
+ position.set(Position.KEY_OUTPUT, parser.nextHexInt());
+
+ position.set(Position.KEY_POWER, parser.nextHexInt() * 0.01);
+ position.set(Position.KEY_BATTERY, parser.nextHexInt() * 0.01);
+
+ String[] adc = parser.next().split("\\|");
+ for (int i = 0; i < adc.length; i++) {
+ position.set(Position.PREFIX_ADC + (i + 1), Integer.parseInt(adc[i], 16) * 0.01);
+ }
+
+ return position;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java
index 4042e348c..fe8de3710 100644
--- a/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2021 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,6 +24,7 @@ import org.traccar.DeviceSession;
import org.traccar.NetworkMessage;
import org.traccar.Protocol;
import org.traccar.helper.BcdUtil;
+import org.traccar.helper.BitUtil;
import org.traccar.helper.DateBuilder;
import org.traccar.helper.UnitsConverter;
import org.traccar.model.CellTower;
@@ -151,6 +152,21 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder {
Gt06ProtocolDecoder.decodeGps(position, buf, false, TimeZone.getTimeZone("UTC"));
+ if (buf.readableBytes() >= 5) {
+ position.setAltitude(buf.readShort());
+
+ int alarms = buf.readUnsignedByte();
+ if (BitUtil.check(alarms, 0)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION);
+ }
+ if (BitUtil.check(alarms, 1)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED);
+ }
+ if (BitUtil.check(alarms, 4)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER);
+ }
+ }
+
ByteBuf content = Unpooled.buffer();
content.writeBytes(time);
sendResponse(channel, length, type, content);
diff --git a/src/main/java/org/traccar/sms/SnsSmsClient.java b/src/main/java/org/traccar/sms/SnsSmsClient.java
new file mode 100644
index 000000000..bdd4104f5
--- /dev/null
+++ b/src/main/java/org/traccar/sms/SnsSmsClient.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2021 Subodh Ranadive (subodhranadive3103@gmail.com)
+ *
+ * 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.sms;
+
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.handlers.AsyncHandler;
+import com.amazonaws.services.sns.AmazonSNSAsync;
+import com.amazonaws.services.sns.AmazonSNSAsyncClientBuilder;
+import com.amazonaws.services.sns.model.MessageAttributeValue;
+import com.amazonaws.services.sns.model.PublishRequest;
+
+import com.amazonaws.services.sns.model.PublishResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.Context;
+import org.traccar.config.Keys;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SnsSmsClient implements SmsManager {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SnsSmsClient.class);
+
+ private final AmazonSNSAsync snsClient;
+
+ public SnsSmsClient() {
+ if (Context.getConfig().hasKey(Keys.SMS_AWS_REGION)
+ && Context.getConfig().hasKey(Keys.SMS_AWS_ACCESS)
+ && Context.getConfig().hasKey(Keys.SMS_AWS_SECRET)) {
+ BasicAWSCredentials awsCredentials =
+ new BasicAWSCredentials(Context.getConfig().getString(Keys.SMS_AWS_ACCESS),
+ Context.getConfig().getString(Keys.SMS_AWS_SECRET));
+ snsClient = AmazonSNSAsyncClientBuilder.standard()
+ .withRegion(Context.getConfig().getString(Keys.SMS_AWS_REGION))
+ .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)).build();
+ } else {
+ throw new RuntimeException("SNS Not Configured Properly. Please provide valid config.");
+ }
+ }
+
+ @Override
+ public void sendMessageSync(String destAddress, String message, boolean command) {
+ Map<String, MessageAttributeValue> smsAttributes = new HashMap<>();
+ smsAttributes.put("AWS.SNS.SMS.SenderID",
+ new MessageAttributeValue().withStringValue("SNS").withDataType("String"));
+ smsAttributes.put("AWS.SNS.SMS.SMSType",
+ new MessageAttributeValue().withStringValue("Transactional").withDataType("String"));
+
+ PublishRequest publishRequest = new PublishRequest().withMessage(message)
+ .withPhoneNumber(destAddress).withMessageAttributes(smsAttributes);
+
+ snsClient.publishAsync(publishRequest, new AsyncHandler<PublishRequest, PublishResult>() {
+ @Override
+ public void onError(Exception exception) {
+ LOGGER.error("SMS send failed", exception);
+ }
+ @Override
+ public void onSuccess(PublishRequest request, PublishResult result) {
+ }
+ });
+ }
+
+ @Override
+ public void sendMessageAsync(String destAddress, String message, boolean command) {
+ sendMessageSync(destAddress, message, command);
+ }
+}
diff --git a/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java
index b329f59f3..dc3f532b6 100644
--- a/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java
@@ -44,6 +44,9 @@ public class AtrackProtocolDecoderTest extends ProtocolTest {
decoder.setCustom(true);
verifyPositions(decoder, buffer(
+ "@P,7A66,153,9022,863003048505515,20210207000103,20210207000103,20210207000103,103939276,1348903,97,2,5628,8,0,0,0,0,,2000,2000,\u001a,%CI%BC,3:224:F128833445E6002C09C6\r\n"));
+
+ 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(
diff --git a/src/test/java/org/traccar/protocol/DmtProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/DmtProtocolDecoderTest.java
index 8977cf194..efcd06218 100644
--- a/src/test/java/org/traccar/protocol/DmtProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/DmtProtocolDecoderTest.java
@@ -13,6 +13,9 @@ public class DmtProtocolDecoderTest extends ProtocolTest {
verifyNull(decoder, binary(
"0255003300001b00003335333232393032373533393235310038393931353030303030303030313330343539340000000403041910780603"));
+ verifyPositions(decoder, binary(
+ "0255047a003d0035020000fbb53a0f030015fbb53a0f3c210e2145deeb051e001a0901940c070302080300000000000300060f011d1003400804180005e80f062e1c3d003602000086b93a0f03001586b93a0fe5421c21bbe1be051700c30901a50d070302080300000000000300060f011d1003bb0804180005e80f062e1c"));
+
verifyPositions(decoder, false, binary(
"025504ab013d00c21a00004829900c0300154929900cbd163617b08a94c7fa003c07032c131a0302080300000000000300060f019b14037e0e0463000558140607213d00c31a0000ca29900c030015ca29900ca3033817bbb895c71401be0603b310190302080300000000000300060f019b14036d0e0463000558140607213d00c41a0000472a900c030015472a900c8d453817423e96c7fa000200040013270302080300000000000300060f019b1403840e0463000546140606213d00c51a0000c52a900c030015c52a900c184c3817c35296c724010400050016180302080300000000000300060f019b1403750e0463000547140606213d00c61a0000462b900c030015462b900cbd8a361703b495c710018c07085a10210302080300000000000300060f019b1403630e0463000546140606213d00c71a0000c52b900c030015c52b900cf6d63517455a94c7e9004c05035a10240302080300000000000300060f019b14036e0e0463000545140606213d00c81a00004b2c900c0300154b2c900c766d3517ddf093c7320107000d00102e0302080300000000000300060f019b1403750e046300054314060521"));
diff --git a/src/test/java/org/traccar/protocol/FifotrackProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/FifotrackProtocolDecoderTest.java
index 48b74c904..08a5ec244 100644
--- a/src/test/java/org/traccar/protocol/FifotrackProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/FifotrackProtocolDecoderTest.java
@@ -11,6 +11,10 @@ public class FifotrackProtocolDecoderTest extends ProtocolTest {
FifotrackProtocolDecoder decoder = new FifotrackProtocolDecoder(null);
+ verifyAttribute(decoder, buffer(
+ "$$25,863003046473534,1,B03,OK*4D"),
+ Position.KEY_RESULT, "OK");
+
verifyPosition(decoder, buffer(
"$$118,863003046473534,258,A01,,201007231735,V,3.067783,101.672858,0,176,96,189890,0,A0,03,0,502|19|5C1|93349F,196|4E0|6C,1,*13"));
diff --git a/src/test/java/org/traccar/protocol/FreematicsProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/FreematicsProtocolDecoderTest.java
index a84c4e357..39d378322 100644
--- a/src/test/java/org/traccar/protocol/FreematicsProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/FreematicsProtocolDecoderTest.java
@@ -10,6 +10,9 @@ public class FreematicsProtocolDecoderTest extends ProtocolTest {
FreematicsProtocolDecoder decoder = new FreematicsProtocolDecoder(null);
+ verifyPositions(decoder, text(
+ "M0ZR4X0#0:204391,11:140221,10:8445000,A:49.215920,B:18.737755,C:410,D:0,E:208,24:1252,20:0;0;0,82:47*B5"));
+
verifyNull(decoder, text(
"1#EV=2,TS=1871902,ID=ESP32305C06C40A24*AC"));
@@ -31,6 +34,12 @@ public class FreematicsProtocolDecoderTest extends ProtocolTest {
verifyPositions(decoder, text(
"1#0=68338,10D=79,30=1010,105=199,10C=4375,104=56,111=62,20=0;-1;95,10=6454200,A=-32.727482,B=150.150301,C=159,D=0,F=5,24=1250*7A"));
+ verifyPositions(decoder, false, text(
+ "M0ZR4X0#0:566624,24:1246,20:0;0;0*D"));
+
+ verifyNull(decoder, text(
+ "M0ZR4X0#DF=4208,SSI=-71,EV=1,TS=20866,ID=M0ZR4X0*9E"));
+
}
}
diff --git a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java
index 915876609..cd8a11b0d 100644
--- a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java
@@ -11,6 +11,12 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest {
Gl200TextProtocolDecoder decoder = new Gl200TextProtocolDecoder(null);
+ verifyAttributes(decoder, buffer(
+ "+RESP:GTINF,DC0103,865284049247079,gv600mg,21,89883070000007211665,22,0,11,12913,12917,4.26,0,1,,,20210216154607,1,79,,01,00,,,20210216104606,1EBE$"));
+
+ verifyPositions(decoder, buffer(
+ "+RESP:GTERI,040A00,862894022579562,gv200,00000002,,10,1,1,96.1,180,749.7,39.222692,24.165463,20210225065756,0420,0004,759C,3360,00,15529.8,,,2789,,01,00,2,2,282BD47A0B000063,1,FFE2,281FDD5D0B000057,1,FFC8,20210225065800,6974$"));
+
verifyAttribute(decoder, buffer(
"+RESP:GTERI,DE0115,865284042104863,gl500m,00000100,0,0,1,2,0.0,0,36.9,-1.844589,52.177779,20201006125701,0234,0015,0135,34A1,19,0,,79,1,,0,20201006125723,184D$"),
Position.KEY_BATTERY_LEVEL, 79);
diff --git a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java
index eeafbbdc4..1f92f5a26 100644
--- a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java
@@ -18,6 +18,10 @@ public class Gt06ProtocolDecoderTest extends ProtocolTest {
"78780D01086471700328358100093F040D0A"));
verifyAttribute(decoder, binary(
+ "7878151330802b00000642014f0008720000802b5ee4d4c90d0a"),
+ Position.KEY_BATTERY_LEVEL, 6);
+
+ verifyAttribute(decoder, binary(
"7878281520000000003c49434349443a38393838323339303030303039373330323635303e00020d446f260d0a"),
Position.KEY_ICCID, "89882390000097302650");
diff --git a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
index 552b5bb2d..e8bd92f8c 100644
--- a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
@@ -14,6 +14,9 @@ public class HuabaoProtocolDecoderTest extends ProtocolTest {
verifyNull(decoder, binary(
"7E01000021013345678906000F002C012F373031313142534A2D4D3742203030303030303001D4C1423838383838B47E"));
+ verifyNotNull(decoder, binary(
+ "7e07040226046110426684002b000601005f0000000000000000000000000000000000000000000021031410530001040000000030011b310101e4020064e50101e60100e7080000000000000000eb2101cc00253510260100000000000000000000000000000000000000000000000000005f00000000004c0001000000000000000000000000000021031410531401040000000030011f310103e4020064e50101e60100e7080000000000000000eb2101cc0025351026012535100f32263d11f931000000000000000000000000000000005f00000000004c0001000000000000000000000000000021031410534001040000000030011f310104e4020064e50101e60100e7080000000000000000eb2101cc002535102601263d11f92d0000000000000000000000000000000000000000005f00000000004c00010000000000000000000000000000210314105350010400000000300118310104e4020064e50101e60100e7080000000000000000eb2101cc002535102601263d11f92e25350f6f2c263d120d2c00000000000000000000005f00000000004c0001000000000000000000000000000021031410540001040000000030011d310105e4020064e50101e60100e7080000000000000000eb2101cc002535102601263d11f93025350f6f2e263d120d2e00000000000000000000003c00000000004c0003015ae3e106c82ab900000010010b21031410540901040000000030011b310105e4020064e50101e60100e7080000000000000000f97e"));
+
verifyAttribute(decoder, binary(
"7e02000033421030000018004c000200000004000201556dcb06c4d41d000c00f100fc210118095853010400000000fe0140ff0c01cc000000002694000055d87a7e"),
Position.KEY_ALARM, Position.ALARM_TAMPERING);
@@ -109,6 +112,9 @@ public class HuabaoProtocolDecoderTest extends ProtocolTest {
verifyPosition(decoder, binary(
"7e020000220014012499170007000000000000400e012af16f02cbd2ba000000000000150101194257010400000077a97e"));
+ verifyNull(decoder, binary(
+ "7e0002000004304832546500b7ca7e"));
+
}
}
diff --git a/src/test/java/org/traccar/protocol/KhdProtocolEncoderTest.java b/src/test/java/org/traccar/protocol/KhdProtocolEncoderTest.java
index 390defe6f..043bf4d9a 100644
--- a/src/test/java/org/traccar/protocol/KhdProtocolEncoderTest.java
+++ b/src/test/java/org/traccar/protocol/KhdProtocolEncoderTest.java
@@ -13,10 +13,13 @@ public class KhdProtocolEncoderTest extends ProtocolTest {
Command command = new Command();
command.setDeviceId(1);
- command.setType(Command.TYPE_ENGINE_STOP);
+ command.setType(Command.TYPE_ENGINE_STOP);
verifyCommand(encoder, command, binary("29293900065981972d5d0d"));
+ command.setType(Command.TYPE_POSITION_SINGLE);
+ verifyCommand(encoder, command, binary("29293000065981972d540d"));
+
}
}
diff --git a/src/test/java/org/traccar/protocol/NavtelecomProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/NavtelecomProtocolDecoderTest.java
new file mode 100644
index 000000000..444752fb7
--- /dev/null
+++ b/src/test/java/org/traccar/protocol/NavtelecomProtocolDecoderTest.java
@@ -0,0 +1,18 @@
+package org.traccar.protocol;
+
+import org.junit.Test;
+import org.traccar.ProtocolTest;
+
+public class NavtelecomProtocolDecoderTest extends ProtocolTest {
+
+ @Test
+ public void testDecode() throws Exception {
+
+ NavtelecomProtocolDecoder decoder = new NavtelecomProtocolDecoder(null);
+
+ verifyNull(decoder, binary(
+ "404e5443010000000000000013004e452a3e533a383636373935303331343130363839"));
+
+ }
+
+}
diff --git a/src/test/java/org/traccar/protocol/SiwiProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/SiwiProtocolDecoderTest.java
index 3a91d8482..79c7d888b 100644
--- a/src/test/java/org/traccar/protocol/SiwiProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/SiwiProtocolDecoderTest.java
@@ -11,6 +11,15 @@ public class SiwiProtocolDecoderTest extends ProtocolTest {
SiwiProtocolDecoder decoder = new SiwiProtocolDecoder(null);
verifyPosition(decoder, text(
+ "$SIWI,868957040831465,44,E,,,1,1,1,16.79,0,0,5,A,17.204447,78.355087,534,44,140955,180221,11,1,15,5,4322,0,0,0,0,0,0,1.0,1.6CPTASF_6.60,0!"));
+
+ verifyPosition(decoder, text(
+ "$SIWI,868957040777510,13,E,,,0,1,1,24.15,1118097,0,18,A,16.080666,80.331451,0,29,142451,180221,24,1,15,5,4282,2269,0,0,0,0,0,1.0,1.6CPASF_6.60,0!"));
+
+ verifyPosition(decoder, text(
+ "$RTPL,1234567890123,45,E,,89917650642221590319,0,1,0,12.36,141313,0,9,A,21.981985,85.221165,804,280,105113,220420,17,1,13,5,4071,1,0,8,0,0,0,1.0,1.2CPLUS_6.52,0!"));
+
+ verifyPosition(decoder, text(
"$SIWI,9803932,23992,E,0,,0,1,1,0,5055,0,5,A,22.289887,70.807192,152,168,102922,090317,28,1,12,5,4098,1,0,13,0,0,0,1.0,3.1CHKS_4.82,0!"));
verifyPosition(decoder, text(
diff --git a/src/test/java/org/traccar/protocol/StarLinkProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/StarLinkProtocolDecoderTest.java
index c15e9e48a..b8c1291c3 100644
--- a/src/test/java/org/traccar/protocol/StarLinkProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/StarLinkProtocolDecoderTest.java
@@ -11,6 +11,12 @@ public class StarLinkProtocolDecoderTest extends ProtocolTest {
StarLinkProtocolDecoder decoder = new StarLinkProtocolDecoder(null);
+ decoder.setFormat("#IMEI#,#EDT#,#PDT#,#LAT#,#LONG#,#SPD#,#IGN#,#ODO#,#DUR#,#TDUR#,#LAC#,#CID#,#VIN#,#VBAT#,#EID#,#EDSC#,#DRV#,#SATU#,#CSS#,#OUT1#,#OUT2#,#CFL#");
+
+ verifyAttribute(decoder, text(
+ "$SLU0AF334,06,14260,352353088342073,210322083942,210322083940,+0937.2191,+04151.8206,000.0,0,006782,,30707,14010,11685,12.994,04.159,01,Location,0,20,64,0,0,12,0,2*7F"),
+ Position.KEY_FUEL_LEVEL, 12);
+
decoder.setFormat("#IMEI#,#EDT#,#EDSC#,#EID#,#PDT#,#LAT#,#LONG#,#SPDK#,#IGNL#,#HEAD#,#ODO#,#DUR#,#TDUR#,#VIN#,#VBAT#,#BATC#,#SATU#,#CSS#,#IN2#,#TVI#,#OUT1#,#OUT2#,#OUT3#,#OUTC#");
verifyAttributes(decoder, text(
diff --git a/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java
new file mode 100644
index 000000000..98e6863e2
--- /dev/null
+++ b/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java
@@ -0,0 +1,21 @@
+package org.traccar.protocol;
+
+import org.junit.Test;
+import org.traccar.ProtocolTest;
+
+public class StartekProtocolDecoderTest extends ProtocolTest {
+
+ @Test
+ public void testDecode() throws Exception {
+
+ StartekProtocolDecoder decoder = new StartekProtocolDecoder(null);
+
+ verifyPosition(decoder, text(
+ "&&A147,021104023195429,000,0,,180106093046,A,22.646430,114.065730,8,0.9,54,86,76,326781,460|0|27B3|0EA7,27,0000000F,02,01,04E2|018C|01C8|0000,1,0104B0,01013D|02813546"));
+
+ verifyPosition(decoder, text(
+ "&&y139,860262050009146,000,0,,210323131512,A,22.678655,114.046223,14,1.1,0,231,71,5,460|0|249F|0099C257,28,0000003D,00,00,0493|0199|0000|0000,1,,33"));
+
+ }
+
+}
diff --git a/src/test/java/org/traccar/protocol/TopinProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/TopinProtocolDecoderTest.java
index 8320b1388..191d27b8a 100644
--- a/src/test/java/org/traccar/protocol/TopinProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/TopinProtocolDecoderTest.java
@@ -17,6 +17,9 @@ public class TopinProtocolDecoderTest extends ProtocolTest {
"78780d0103593390754169634d0d0a"));
verifyPosition(decoder, binary(
+ "78781510120B05030D2498038077200BE2078F0034000102030D0A"));
+
+ verifyPosition(decoder, binary(
"7878200813081A0733211608C8D1710DED1D1608DFFB710E06D51039050100286489000D0A"));
verifyPosition(decoder, binary(