aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/traccar/BaseProtocolEncoder.java3
-rw-r--r--src/main/java/org/traccar/Context.java15
-rw-r--r--src/main/java/org/traccar/LifecycleObject.java21
-rw-r--r--src/main/java/org/traccar/Main.java40
-rw-r--r--src/main/java/org/traccar/MainEventHandler.java17
-rw-r--r--src/main/java/org/traccar/ServerManager.java4
-rw-r--r--src/main/java/org/traccar/TrackerConnector.java6
-rw-r--r--src/main/java/org/traccar/api/resource/ServerResource.java9
-rw-r--r--src/main/java/org/traccar/broadcast/BroadcastMessage.java42
-rw-r--r--src/main/java/org/traccar/broadcast/BroadcastService.java100
-rw-r--r--src/main/java/org/traccar/broadcast/DeviceStatus.java52
-rw-r--r--src/main/java/org/traccar/config/Keys.java22
-rw-r--r--src/main/java/org/traccar/handler/StandardLoggingHandler.java8
-rw-r--r--src/main/java/org/traccar/helper/NetworkUtil.java31
-rw-r--r--src/main/java/org/traccar/helper/Parser.java5
-rw-r--r--src/main/java/org/traccar/model/Command.java3
-rw-r--r--src/main/java/org/traccar/protocol/EnvotechProtocol.java39
-rw-r--r--src/main/java/org/traccar/protocol/EnvotechProtocolDecoder.java116
-rw-r--r--src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java17
-rw-r--r--src/main/java/org/traccar/protocol/GotopProtocolDecoder.java19
-rw-r--r--src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java839
-rw-r--r--src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java162
-rw-r--r--src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java18
-rw-r--r--src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java10
-rw-r--r--src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java15
-rw-r--r--src/main/java/org/traccar/protocol/StartekProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java7
-rw-r--r--src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java92
-rw-r--r--src/main/java/org/traccar/protocol/WliProtocolDecoder.java74
-rw-r--r--src/main/java/org/traccar/schedule/ScheduleManager.java13
-rw-r--r--src/main/java/org/traccar/schedule/TaskHealthCheck.java (renamed from src/main/java/org/traccar/api/HealthCheckService.java)53
-rw-r--r--src/main/java/org/traccar/web/WebServer.java7
-rw-r--r--src/test/java/org/traccar/protocol/EnvotechProtocolDecoderTest.java31
-rw-r--r--src/test/java/org/traccar/protocol/GotopProtocolDecoderTest.java14
-rw-r--r--src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java4
-rw-r--r--src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java7
-rw-r--r--src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java3
-rw-r--r--src/test/java/org/traccar/protocol/RuptelaProtocolDecoderTest.java3
-rw-r--r--src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java5
-rw-r--r--src/test/java/org/traccar/protocol/Tk103ProtocolEncoderTest.java2
-rw-r--r--src/test/java/org/traccar/protocol/WliProtocolDecoderTest.java3
41 files changed, 1355 insertions, 580 deletions
diff --git a/src/main/java/org/traccar/BaseProtocolEncoder.java b/src/main/java/org/traccar/BaseProtocolEncoder.java
index b6df07b98..71fa61e4e 100644
--- a/src/main/java/org/traccar/BaseProtocolEncoder.java
+++ b/src/main/java/org/traccar/BaseProtocolEncoder.java
@@ -21,6 +21,7 @@ import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.traccar.helper.NetworkUtil;
import org.traccar.model.Command;
public abstract class BaseProtocolEncoder extends ChannelOutboundHandlerAdapter {
@@ -62,7 +63,7 @@ public abstract class BaseProtocolEncoder extends ChannelOutboundHandlerAdapter
Object encodedCommand = encodeCommand(ctx.channel(), command);
StringBuilder s = new StringBuilder();
- s.append("[").append(ctx.channel().id().asShortText()).append("] ");
+ s.append("[").append(NetworkUtil.session(ctx.channel())).append("] ");
s.append("id: ").append(getUniqueId(command.getDeviceId())).append(", ");
s.append("command type: ").append(command.getType()).append(" ");
if (encodedCommand != null) {
diff --git a/src/main/java/org/traccar/Context.java b/src/main/java/org/traccar/Context.java
index aeba9c4c9..ee14f8a1a 100644
--- a/src/main/java/org/traccar/Context.java
+++ b/src/main/java/org/traccar/Context.java
@@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr353.JSR353Module;
import org.apache.velocity.app.VelocityEngine;
import org.eclipse.jetty.util.URIUtil;
+import org.traccar.broadcast.BroadcastService;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.database.AttributesManager;
@@ -171,6 +172,12 @@ public final class Context {
return scheduleManager;
}
+ private static BroadcastService broadcastService;
+
+ public static BroadcastService getBroadcastService() {
+ return broadcastService;
+ }
+
private static GeofenceManager geofenceManager;
public static GeofenceManager getGeofenceManager() {
@@ -287,7 +294,9 @@ public final class Context {
}
objectMapper = new ObjectMapper();
- objectMapper.registerModule(new SanitizerModule());
+ if (config.getBoolean(Keys.WEB_SANITIZE)) {
+ objectMapper.registerModule(new SanitizerModule());
+ }
objectMapper.registerModule(new JSR353Module());
objectMapper.setConfig(
objectMapper.getSerializationConfig().without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS));
@@ -335,6 +344,10 @@ public final class Context {
serverManager = new ServerManager();
scheduleManager = new ScheduleManager();
+ if (config.hasKey(Keys.BROADCAST_ADDRESS)) {
+ broadcastService = new BroadcastService(config, objectMapper);
+ }
+
if (config.hasKey(Keys.EVENT_FORWARD_URL)) {
eventForwarder = new EventForwarder();
}
diff --git a/src/main/java/org/traccar/LifecycleObject.java b/src/main/java/org/traccar/LifecycleObject.java
new file mode 100644
index 000000000..7af21d528
--- /dev/null
+++ b/src/main/java/org/traccar/LifecycleObject.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar;
+
+public interface LifecycleObject {
+ void start() throws Exception;
+ void stop();
+}
diff --git a/src/main/java/org/traccar/Main.java b/src/main/java/org/traccar/Main.java
index 63e5c1f90..2eaf394af 100644
--- a/src/main/java/org/traccar/Main.java
+++ b/src/main/java/org/traccar/Main.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ import com.google.inject.Guice;
import com.google.inject.Injector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.traccar.api.HealthCheckService;
import java.io.File;
import java.lang.management.ManagementFactory;
@@ -27,8 +26,10 @@ import java.lang.management.MemoryMXBean;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Locale;
-import java.util.Timer;
public final class Main {
@@ -107,14 +108,6 @@ public final class Main {
}
}
- private static void scheduleHealthCheck() {
- HealthCheckService service = new HealthCheckService();
- if (service.isEnabled()) {
- new Timer().scheduleAtFixedRate(
- service.createTask(), service.getPeriod(), service.getPeriod());
- }
- }
-
public static void run(String configFile) {
try {
Context.init(configFile);
@@ -123,24 +116,29 @@ public final class Main {
LOGGER.info("Version: " + Main.class.getPackage().getImplementationVersion());
LOGGER.info("Starting server...");
- Context.getServerManager().start();
- if (Context.getWebServer() != null) {
- Context.getWebServer().start();
- }
- Context.getScheduleManager().start();
+ List<LifecycleObject> services = new LinkedList<>();
+ services.add(Context.getServerManager());
+ services.add(Context.getWebServer());
+ services.add(Context.getScheduleManager());
+ services.add(Context.getBroadcastService());
- scheduleHealthCheck();
+ for (LifecycleObject service : services) {
+ if (service != null) {
+ service.start();
+ }
+ }
Thread.setDefaultUncaughtExceptionHandler((t, e) -> LOGGER.error("Thread exception", e));
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
LOGGER.info("Shutting down server...");
- Context.getScheduleManager().stop();
- if (Context.getWebServer() != null) {
- Context.getWebServer().stop();
+ Collections.reverse(services);
+ for (LifecycleObject service : services) {
+ if (service != null) {
+ service.stop();
+ }
}
- Context.getServerManager().stop();
}));
} catch (Exception e) {
LOGGER.error("Main method error", e);
diff --git a/src/main/java/org/traccar/MainEventHandler.java b/src/main/java/org/traccar/MainEventHandler.java
index a3f6f4105..f1f2527bc 100644
--- a/src/main/java/org/traccar/MainEventHandler.java
+++ b/src/main/java/org/traccar/MainEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
import org.traccar.config.Keys;
import org.traccar.database.StatisticsManager;
import org.traccar.helper.DateUtil;
+import org.traccar.helper.NetworkUtil;
import org.traccar.model.Position;
import org.traccar.storage.StorageException;
@@ -64,7 +65,7 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter {
String uniqueId = Context.getIdentityManager().getById(position.getDeviceId()).getUniqueId();
StringBuilder builder = new StringBuilder();
- builder.append(formatChannel(ctx.channel())).append(" ");
+ builder.append("[").append(NetworkUtil.session(ctx.channel())).append("] ");
builder.append("id: ").append(uniqueId);
for (String attribute : logAttributes) {
switch (attribute) {
@@ -113,20 +114,16 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter {
}
}
- private static String formatChannel(Channel channel) {
- return String.format("[%s]", channel.id().asShortText());
- }
-
@Override
public void channelActive(ChannelHandlerContext ctx) {
if (!(ctx.channel() instanceof DatagramChannel)) {
- LOGGER.info(formatChannel(ctx.channel()) + " connected");
+ LOGGER.info("[{}] connected", NetworkUtil.session(ctx.channel()));
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
- LOGGER.info(formatChannel(ctx.channel()) + " disconnected");
+ LOGGER.info("[{}] disconnected", NetworkUtil.session(ctx.channel()));
closeChannel(ctx.channel());
if (BasePipelineFactory.getHandler(ctx.pipeline(), HttpRequestDecoder.class) == null
@@ -140,14 +137,14 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter {
while (cause.getCause() != null && cause.getCause() != cause) {
cause = cause.getCause();
}
- LOGGER.warn(formatChannel(ctx.channel()) + " error", cause);
+ LOGGER.info("[{}] error", NetworkUtil.session(ctx.channel()), cause);
closeChannel(ctx.channel());
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
- LOGGER.info(formatChannel(ctx.channel()) + " timed out");
+ LOGGER.info("[{}] timed out", NetworkUtil.session(ctx.channel()));
closeChannel(ctx.channel());
}
}
diff --git a/src/main/java/org/traccar/ServerManager.java b/src/main/java/org/traccar/ServerManager.java
index 2e2cf7cff..15faf9f2b 100644
--- a/src/main/java/org/traccar/ServerManager.java
+++ b/src/main/java/org/traccar/ServerManager.java
@@ -29,7 +29,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
-public class ServerManager {
+public class ServerManager implements LifecycleObject {
private static final Logger LOGGER = LoggerFactory.getLogger(ServerManager.class);
@@ -50,6 +50,7 @@ public class ServerManager {
return protocolList.get(name);
}
+ @Override
public void start() throws Exception {
for (TrackerConnector connector: connectorList) {
try {
@@ -62,6 +63,7 @@ public class ServerManager {
}
}
+ @Override
public void stop() {
for (TrackerConnector connector: connectorList) {
connector.stop();
diff --git a/src/main/java/org/traccar/TrackerConnector.java b/src/main/java/org/traccar/TrackerConnector.java
index 9e2d27ae5..fc6e93399 100644
--- a/src/main/java/org/traccar/TrackerConnector.java
+++ b/src/main/java/org/traccar/TrackerConnector.java
@@ -17,7 +17,7 @@ package org.traccar;
import io.netty.channel.group.ChannelGroup;
-public interface TrackerConnector {
+public interface TrackerConnector extends LifecycleObject {
boolean isDatagram();
@@ -25,8 +25,4 @@ public interface TrackerConnector {
ChannelGroup getChannelGroup();
- void start() throws Exception;
-
- void stop();
-
}
diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java
index 2d17d5e47..f238e8905 100644
--- a/src/main/java/org/traccar/api/resource/ServerResource.java
+++ b/src/main/java/org/traccar/api/resource/ServerResource.java
@@ -34,6 +34,9 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.TimeZone;
@Path("server")
@Produces(MediaType.APPLICATION_JSON)
@@ -67,4 +70,10 @@ public class ServerResource extends BaseResource {
}
}
+ @Path("timezones")
+ @GET
+ public Collection<String> timezones() {
+ return Arrays.asList(TimeZone.getAvailableIDs());
+ }
+
}
diff --git a/src/main/java/org/traccar/broadcast/BroadcastMessage.java b/src/main/java/org/traccar/broadcast/BroadcastMessage.java
new file mode 100644
index 000000000..6b103f373
--- /dev/null
+++ b/src/main/java/org/traccar/broadcast/BroadcastMessage.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.broadcast;
+
+import org.traccar.model.Position;
+
+public class BroadcastMessage {
+
+ private DeviceStatus deviceStatus;
+
+ public DeviceStatus getDeviceStatus() {
+ return deviceStatus;
+ }
+
+ public void setDeviceStatus(DeviceStatus deviceStatus) {
+ this.deviceStatus = deviceStatus;
+ }
+
+ private Position position;
+
+ public Position getPosition() {
+ return position;
+ }
+
+ public void setPosition(Position position) {
+ this.position = position;
+ }
+
+}
diff --git a/src/main/java/org/traccar/broadcast/BroadcastService.java b/src/main/java/org/traccar/broadcast/BroadcastService.java
new file mode 100644
index 000000000..b17857fcd
--- /dev/null
+++ b/src/main/java/org/traccar/broadcast/BroadcastService.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.broadcast;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.LifecycleObject;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class BroadcastService implements LifecycleObject {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(BroadcastService.class);
+
+ private final ObjectMapper objectMapper;
+
+ private final InetAddress address;
+ private final int port;
+
+ private DatagramSocket publisherSocket;
+
+ private final ExecutorService service = Executors.newSingleThreadExecutor();
+ private final byte[] receiverBuffer = new byte[4096];
+
+ @Inject
+ public BroadcastService(Config config, ObjectMapper objectMapper) throws IOException {
+ this.objectMapper = objectMapper;
+ address = InetAddress.getByName(config.getString(Keys.BROADCAST_ADDRESS));
+ port = config.getInteger(Keys.BROADCAST_PORT);
+ }
+
+ public void sendMessage(BroadcastMessage message) throws IOException {
+ byte[] buffer = objectMapper.writeValueAsString(message).getBytes(StandardCharsets.UTF_8);
+ DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, port);
+ publisherSocket.send(packet);
+ }
+
+ private void handleMessage(BroadcastMessage message) {
+ if (message.getDeviceStatus() != null) {
+ LOGGER.info("Broadcast received device {}", message.getDeviceStatus().getDeviceId());
+ } else if (message.getPosition() != null) {
+ LOGGER.info("Broadcast received position {}", message.getPosition().getDeviceId());
+ }
+ }
+
+ @Override
+ public void start() throws IOException {
+ service.submit(receiver);
+ publisherSocket = new DatagramSocket();
+ }
+
+ @Override
+ public void stop() {
+ publisherSocket.close();
+ service.shutdown();
+ }
+
+ private final Runnable receiver = new Runnable() {
+ @Override
+ public void run() {
+ try (MulticastSocket socket = new MulticastSocket(port)) {
+ socket.joinGroup(address);
+ while (!service.isShutdown()) {
+ DatagramPacket packet = new DatagramPacket(receiverBuffer, receiverBuffer.length);
+ socket.receive(packet);
+ String data = new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8);
+ handleMessage(objectMapper.readValue(data, BroadcastMessage.class));
+ }
+ socket.leaveGroup(address);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+
+}
diff --git a/src/main/java/org/traccar/broadcast/DeviceStatus.java b/src/main/java/org/traccar/broadcast/DeviceStatus.java
new file mode 100644
index 000000000..4f0143319
--- /dev/null
+++ b/src/main/java/org/traccar/broadcast/DeviceStatus.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.broadcast;
+
+import java.util.Date;
+
+public class DeviceStatus {
+
+ private long deviceId;
+
+ public long getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(long deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ private String status;
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ private Date lastUpdate;
+
+ public Date getLastUpdate() {
+ return this.lastUpdate;
+ }
+
+ public void setLastUpdate(Date lastUpdate) {
+ this.lastUpdate = lastUpdate;
+ }
+
+}
diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java
index 1341f4143..bb3ea393a 100644
--- a/src/main/java/org/traccar/config/Keys.java
+++ b/src/main/java/org/traccar/config/Keys.java
@@ -553,6 +553,14 @@ public final class Keys {
Collections.singletonList(KeyType.GLOBAL));
/**
+ * Sanitize all strings returned via API. This is needed to fix XSS issues in the old web interface. New React-based
+ * interface doesn't require this.
+ */
+ public static final ConfigKey<Boolean> WEB_SANITIZE = new ConfigKey<>(
+ "web.sanitize",
+ Collections.singletonList(KeyType.GLOBAL));
+
+ /**
* Path to the web app folder.
*/
public static final ConfigKey<String> WEB_PATH = new ConfigKey<>(
@@ -1316,4 +1324,18 @@ public final class Keys {
Collections.singletonList(KeyType.GLOBAL),
"time,position,speed,course,accuracy,result");
+ /**
+ * Multicast address for broadcasting synchronization events.
+ */
+ public static final ConfigKey<String> BROADCAST_ADDRESS = new ConfigKey<>(
+ "broadcast.address",
+ Collections.singletonList(KeyType.GLOBAL));
+
+ /**
+ * Multicast port for broadcasting synchronization events.
+ */
+ public static final ConfigKey<Integer> BROADCAST_PORT = new ConfigKey<>(
+ "broadcast.port",
+ Collections.singletonList(KeyType.GLOBAL));
+
}
diff --git a/src/main/java/org/traccar/handler/StandardLoggingHandler.java b/src/main/java/org/traccar/handler/StandardLoggingHandler.java
index 13c5f8281..84492e2a5 100644
--- a/src/main/java/org/traccar/handler/StandardLoggingHandler.java
+++ b/src/main/java/org/traccar/handler/StandardLoggingHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import io.netty.channel.ChannelPromise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.NetworkMessage;
+import org.traccar.helper.NetworkUtil;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@@ -63,7 +64,7 @@ public class StandardLoggingHandler extends ChannelDuplexHandler {
public void log(ChannelHandlerContext ctx, boolean downstream, SocketAddress remoteAddress, ByteBuf buf) {
StringBuilder message = new StringBuilder();
- message.append("[").append(ctx.channel().id().asShortText()).append(": ");
+ message.append("[").append(NetworkUtil.session(ctx.channel())).append(": ");
message.append(protocol);
if (downstream) {
message.append(" > ");
@@ -76,9 +77,8 @@ public class StandardLoggingHandler extends ChannelDuplexHandler {
} else {
message.append("unknown");
}
- message.append("]");
+ message.append("] ");
- message.append(" HEX: ");
message.append(ByteBufUtil.hexDump(buf));
LOGGER.info(message.toString());
diff --git a/src/main/java/org/traccar/helper/NetworkUtil.java b/src/main/java/org/traccar/helper/NetworkUtil.java
new file mode 100644
index 000000000..2fe3487da
--- /dev/null
+++ b/src/main/java/org/traccar/helper/NetworkUtil.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.helper;
+
+import io.netty.channel.Channel;
+import io.netty.channel.socket.DatagramChannel;
+
+public final class NetworkUtil {
+
+ private NetworkUtil() {
+ }
+
+ public static String session(Channel channel) {
+ char transport = channel instanceof DatagramChannel ? 'U' : 'T';
+ return transport + channel.id().asShortText();
+ }
+
+}
diff --git a/src/main/java/org/traccar/helper/Parser.java b/src/main/java/org/traccar/helper/Parser.java
index 75106e2ba..22e98ded1 100644
--- a/src/main/java/org/traccar/helper/Parser.java
+++ b/src/main/java/org/traccar/helper/Parser.java
@@ -155,6 +155,7 @@ public class Parser {
public enum CoordinateFormat {
DEG_DEG,
+ DEG_DEG_HEM,
DEG_HEM,
DEG_MIN_MIN,
DEG_MIN_HEM,
@@ -173,6 +174,10 @@ public class Parser {
case DEG_DEG:
coordinate = Double.parseDouble(next() + '.' + next());
break;
+ case DEG_DEG_HEM:
+ coordinate = Double.parseDouble(next() + '.' + next());
+ hemisphere = next();
+ break;
case DEG_HEM:
coordinate = nextDouble(0);
hemisphere = next();
diff --git a/src/main/java/org/traccar/model/Command.java b/src/main/java/org/traccar/model/Command.java
index 03961c7b2..49486bdbc 100644
--- a/src/main/java/org/traccar/model/Command.java
+++ b/src/main/java/org/traccar/model/Command.java
@@ -58,11 +58,10 @@ public class Command extends Message implements Cloneable {
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";
- public static final String TYPE_ALARM_GEOFENCE = "movementAlarm";
+ public static final String TYPE_ALARM_GEOFENCE = "alarmGeofence";
public static final String TYPE_ALARM_BATTERY = "alarmBattery";
public static final String TYPE_ALARM_SOS = "alarmSos";
public static final String TYPE_ALARM_REMOVE = "alarmRemove";
diff --git a/src/main/java/org/traccar/protocol/EnvotechProtocol.java b/src/main/java/org/traccar/protocol/EnvotechProtocol.java
new file mode 100644
index 000000000..8eb71ee6b
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/EnvotechProtocol.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.handler.codec.string.StringDecoder;
+import io.netty.handler.codec.string.StringEncoder;
+import org.traccar.BaseProtocol;
+import org.traccar.CharacterDelimiterFrameDecoder;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+
+public class EnvotechProtocol extends BaseProtocol {
+
+ public EnvotechProtocol() {
+ addServer(new TrackerServer(false, getName()) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline) {
+ pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#'));
+ pipeline.addLast(new StringEncoder());
+ pipeline.addLast(new StringDecoder());
+ pipeline.addLast(new EnvotechProtocolDecoder(EnvotechProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/EnvotechProtocolDecoder.java b/src/main/java/org/traccar/protocol/EnvotechProtocolDecoder.java
new file mode 100644
index 000000000..65d5e3859
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/EnvotechProtocolDecoder.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.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.model.Position;
+
+import java.net.SocketAddress;
+import java.util.regex.Pattern;
+
+public class EnvotechProtocolDecoder extends BaseProtocolDecoder {
+
+ public EnvotechProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ private static final Pattern PATTERN = new PatternBuilder()
+ .text("$")
+ .number("dd") // mode
+ .expression("...,") // hardware
+ .number("(x+),") // event
+ .number("x+,") // group
+ .number("(x+),") // device id
+ .number("(dd)(dd)(dd)") // date (ddmmyy)
+ .number("(dd)(dd)(dd),") // time (hhmmss)
+ .number("xx") // connection status
+ .number("(dd)") // rssi
+ .number("d{5},") // mcc
+ .number("(ddd)") // power
+ .number("(ddd),") // battery
+ .number("(xx)") // inputs
+ .number("(xx),") // outputs
+ .number("(xxx)?") // fuel
+ .number("(xxx)?,") // weight
+ .number("(x{8}),") // status
+ .expression("[^']*'")
+ .number("(dd)(dd)(dd)") // date (ddmmyy)
+ .number("(dd)(dd)(dd)") // time (hhmmss)
+ .number("(d)") // fix
+ .number("(d+)(d{5})([NS])") // latitude
+ .number("(d+)(d{5})([EW])") // longitude
+ .number("(ddd)") // speed
+ .number("(ddd)") // course
+ .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;
+ }
+
+ Position position = new Position(getProtocolName());
+
+ int event = parser.nextHexInt();
+ switch (event) {
+ case 0x60:
+ position.set(Position.KEY_ALARM, Position.ALARM_LOCK);
+ break;
+ case 0x61:
+ position.set(Position.KEY_ALARM, Position.ALARM_UNLOCK);
+ break;
+ default:
+ break;
+ }
+ position.set(Position.KEY_EVENT, event);
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.setDeviceTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS));
+
+ position.set(Position.KEY_RSSI, parser.nextInt());
+ position.set(Position.KEY_POWER, parser.nextInt() * 0.01);
+ position.set(Position.KEY_BATTERY, parser.nextInt() * 0.01);
+ position.set(Position.KEY_INPUT, parser.nextHexInt());
+ position.set(Position.PREFIX_OUT, parser.nextHexInt());
+ position.set(Position.KEY_FUEL_LEVEL, parser.nextHexInt());
+ position.set("weight", parser.nextHexInt());
+ position.set(Position.KEY_STATUS, parser.nextHexLong());
+
+ position.setFixTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS));
+ position.setValid(parser.nextInt() > 0);
+ position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_DEG_HEM));
+ position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_DEG_HEM));
+ position.setSpeed(parser.nextInt());
+ position.setCourse(parser.nextInt());
+
+ return position;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java
index 83ca74ce5..281a8a84f 100644
--- a/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,11 +54,11 @@ public class FlespiProtocolDecoder extends BaseHttpProtocolDecoder {
List<Position> positions = new LinkedList<>();
for (int i = 0; i < result.size(); i++) {
JsonObject message = result.getJsonObject(i);
- JsonString ident = message.getJsonString("ident");
- if (ident == null) {
+ JsonString identifier = message.getJsonString("ident");
+ if (identifier == null) {
continue;
}
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, ident.getString());
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, identifier.getString());
if (deviceSession == null) {
continue;
}
@@ -228,6 +228,15 @@ public class FlespiProtocolDecoder extends BaseHttpProtocolDecoder {
position.set(Position.KEY_ALARM, Position.ALARM_BONNET);
}
return true;
+ case "custom.wln_accel_max":
+ position.set("maxAcceleration", ((JsonNumber) value).doubleValue());
+ return true;
+ case "custom.wln_brk_max":
+ position.set("maxBraking", ((JsonNumber) value).doubleValue());
+ return true;
+ case "custom.wln_crn_max":
+ position.set("maxCornering", ((JsonNumber) value).doubleValue());
+ return true;
default:
return false;
}
diff --git a/src/main/java/org/traccar/protocol/GotopProtocolDecoder.java b/src/main/java/org/traccar/protocol/GotopProtocolDecoder.java
index a867451aa..0f8d29228 100644
--- a/src/main/java/org/traccar/protocol/GotopProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/GotopProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org)
+ * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ public class GotopProtocolDecoder extends BaseProtocolDecoder {
private static final Pattern PATTERN = new PatternBuilder()
.number("(d+),") // imei
- .expression("[^,]+,") // type
+ .expression("([^,]+),") // type
.expression("([AV]),") // validity
.number("DATE:(dd)(dd)(dd),") // date (yyddmm)
.number("TIME:(dd)(dd)(dd),") // time (hhmmss)
@@ -56,14 +56,25 @@ public class GotopProtocolDecoder extends BaseProtocolDecoder {
return null;
}
- Position position = new Position(getProtocolName());
-
DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
if (deviceSession == null) {
return null;
}
+
+ Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
+ String type = parser.next();
+ if (type.equals("CMD-KEY")) {
+ position.set(Position.KEY_ALARM, Position.ALARM_SOS);
+ } else if (type.startsWith("ALM-B")) {
+ if (Character.getNumericValue(type.charAt(5)) % 2 > 0) {
+ position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_ENTER);
+ } else {
+ position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_EXIT);
+ }
+ }
+
position.setValid(parser.next().equals("A"));
position.setTime(parser.nextDateTime());
diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
index 4d3448e84..89931f614 100644
--- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
-Supp * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -75,15 +75,15 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_LBS_EXTEND = 0x18;
public static final int MSG_LBS_STATUS = 0x19;
public static final int MSG_GPS_PHONE = 0x1A;
- public static final int MSG_GPS_LBS_EXTEND = 0x1E;
- public static final int MSG_HEARTBEAT = 0x23;
- public static final int MSG_ADDRESS_REQUEST = 0x2A;
- public static final int MSG_ADDRESS_RESPONSE = 0x97;
- public static final int MSG_GPS_LBS_5 = 0x31;
- public static final int MSG_GPS_LBS_STATUS_4 = 0x32;
- public static final int MSG_WIFI_5 = 0x33;
- public static final int MSG_AZ735_GPS = 0x32; // only extended
- public static final int MSG_AZ735_ALARM = 0x33; // only extended
+ public static final int MSG_GPS_LBS_EXTEND = 0x1E; // JI09
+ public static final int MSG_HEARTBEAT = 0x23; // GK310
+ public static final int MSG_ADDRESS_REQUEST = 0x2A; // GK310
+ public static final int MSG_ADDRESS_RESPONSE = 0x97; // GK310
+ public static final int MSG_GPS_LBS_5 = 0x31; // AZ735
+ public static final int MSG_GPS_LBS_STATUS_4 = 0x32; // AZ735
+ public static final int MSG_WIFI_5 = 0x33; // AZ735
+ public static final int MSG_AZ735_GPS = 0x32; // AZ735 / only extended
+ public static final int MSG_AZ735_ALARM = 0x33; // AZ735 / only extended
public static final int MSG_X1_GPS = 0x34;
public static final int MSG_X1_PHOTO_INFO = 0x35;
public static final int MSG_X1_PHOTO_DATA = 0x36;
@@ -93,25 +93,61 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_COMMAND_0 = 0x80;
public static final int MSG_COMMAND_1 = 0x81;
public static final int MSG_COMMAND_2 = 0x82;
- public static final int MSG_TIME_REQUEST = 0x8A;
+ public static final int MSG_TIME_REQUEST = 0x8A; // GK310
public static final int MSG_INFO = 0x94;
public static final int MSG_SERIAL = 0x9B;
public static final int MSG_STRING_INFO = 0x21;
- public static final int MSG_GPS_2 = 0xA0;
- public static final int MSG_LBS_2 = 0xA1;
- public static final int MSG_WIFI_3 = 0xA2;
- public static final int MSG_FENCE_SINGLE = 0xA3;
- public static final int MSG_FENCE_MULTI = 0xA4;
- public static final int MSG_LBS_ALARM = 0xA5;
- public static final int MSG_LBS_ADDRESS = 0xA7;
- public static final int MSG_OBD = 0x8C;
- public static final int MSG_DTC = 0x65;
- public static final int MSG_PID = 0x66;
- public static final int MSG_BMS = 0x20;
- public static final int MSG_MULTIMEDIA = 0x21;
- public static final int MSG_BMS_2 = 0x40;
- public static final int MSG_MULTIMEDIA_2 = 0x41;
- public static final int MSG_ALARM = 0x95;
+ public static final int MSG_GPS_2 = 0xA0; // GK310
+ public static final int MSG_LBS_2 = 0xA1; // GK310
+ public static final int MSG_WIFI_3 = 0xA2; // GK310
+ public static final int MSG_FENCE_SINGLE = 0xA3; // GK310
+ public static final int MSG_FENCE_MULTI = 0xA4; // GK310
+ public static final int MSG_LBS_ALARM = 0xA5; // GK310
+ public static final int MSG_LBS_ADDRESS = 0xA7; // GK310
+ public static final int MSG_OBD = 0x8C; // FM08ABC
+ public static final int MSG_DTC = 0x65; // FM08ABC
+ public static final int MSG_PID = 0x66; // FM08ABC
+ public static final int MSG_BMS = 0x40; // WD-209
+ public static final int MSG_MULTIMEDIA = 0x41; // WD-209
+ public static final int MSG_ALARM = 0x95; // JC100
+
+ private enum Variant {
+ VXT01,
+ WANWAY_S20,
+ GT06E_CARD,
+ BENWAY,
+ S5,
+ SPACE10X,
+ STANDARD,
+ }
+
+ private Variant variant;
+
+ private static final Pattern PATTERN_FUEL = new PatternBuilder()
+ .text("!AIOIL,")
+ .number("d+,") // device address
+ .number("d+.d+,") // output value
+ .number("(d+.d+),") // temperature
+ .expression("[^,]+,") // version
+ .number("dd") // back wave
+ .number("d") // software status code
+ .number("d,") // hardware status code
+ .number("(d+.d+),") // measured value
+ .expression("[01],") // movement status
+ .number("d+,") // excited wave times
+ .number("xx") // checksum
+ .compile();
+
+ private static final Pattern PATTERN_LOCATION = new PatternBuilder()
+ .text("Current position!")
+ .number("Lat:([NS])(d+.d+),") // latitude
+ .number("Lon:([EW])(d+.d+),") // longitude
+ .text("Course:").number("(d+.d+),") // course
+ .text("Speed:").number("(d+.d+),") // speed
+ .text("DateTime:")
+ .number("(dddd)-(dd)-(dd) +") // date
+ .number("(dd):(dd):(dd)") // time
+ .compile();
private static boolean isSupported(int type) {
return hasGps(type) || hasLbs(type) || hasStatus(type);
@@ -311,7 +347,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
return true;
}
- private boolean decodeStatus(Position position, ByteBuf buf, boolean batteryLevel) {
+ private void decodeStatus(Position position, ByteBuf buf, boolean batteryLevel) {
int status = buf.readUnsignedByte();
@@ -333,8 +369,15 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
case 4:
position.set(Position.KEY_ALARM, Position.ALARM_SOS);
break;
+ case 6:
+ position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE);
+ break;
case 7:
- position.set(Position.KEY_ALARM, Position.ALARM_REMOVING);
+ if (variant == Variant.VXT01) {
+ position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED);
+ } else {
+ position.set(Position.KEY_ALARM, Position.ALARM_REMOVING);
+ }
break;
default:
break;
@@ -346,9 +389,6 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01);
}
position.set(Position.KEY_RSSI, buf.readUnsignedByte());
- position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte()));
-
- return true;
}
private String decodeAlarm(short value) {
@@ -391,72 +431,20 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
}
}
- private static final Pattern PATTERN_FUEL = new PatternBuilder()
- .text("!AIOIL,")
- .number("d+,") // device address
- .number("d+.d+,") // output value
- .number("(d+.d+),") // temperature
- .expression("[^,]+,") // version
- .number("dd") // back wave
- .number("d") // software status code
- .number("d,") // hardware status code
- .number("(d+.d+),") // measured value
- .expression("[01],") // movement status
- .number("d+,") // excited wave times
- .number("xx") // checksum
- .compile();
-
- private Position decodeFuelData(Position position, String sentence) {
- Parser parser = new Parser(PATTERN_FUEL, sentence);
- if (!parser.matches()) {
- return null;
- }
-
- position.set(Position.PREFIX_TEMP + 1, parser.nextDouble(0));
- position.set(Position.KEY_FUEL_LEVEL, parser.nextDouble(0));
-
- return position;
- }
-
- private static final Pattern PATTERN_LOCATION = new PatternBuilder()
- .text("Current position!")
- .number("Lat:([NS])(d+.d+),") // latitude
- .number("Lon:([EW])(d+.d+),") // longitude
- .text("Course:").number("(d+.d+),") // course
- .text("Speed:").number("(d+.d+),") // speed
- .text("DateTime:")
- .number("(dddd)-(dd)-(dd) +") // date
- .number("(dd):(dd):(dd)") // time
- .compile();
-
- private Position decodeLocationString(Position position, String sentence) {
- Parser parser = new Parser(PATTERN_LOCATION, sentence);
- if (!parser.matches()) {
- return null;
- }
-
- position.setValid(true);
- position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG));
- position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG));
- position.setCourse(parser.nextDouble());
- position.setSpeed(parser.nextDouble());
- position.setTime(parser.nextDateTime(Parser.DateTimeFormat.YMD_HMS));
-
- return position;
- }
-
- private Object decodeBasic(Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws Exception {
+ private Object decodeBasic(Channel channel, SocketAddress remoteAddress, ByteBuf buf) {
int length = buf.readUnsignedByte();
int dataLength = length - 5;
int type = buf.readUnsignedByte();
+ Position position = new Position(getProtocolName());
DeviceSession deviceSession = null;
if (type != MSG_LOGIN) {
deviceSession = getDeviceSession(channel, remoteAddress);
if (deviceSession == null) {
return null;
}
+ position.setDeviceId(deviceSession.getDeviceId());
if (deviceSession.getTimeZone() == null) {
deviceSession.setTimeZone(getTimeZone(deviceSession.getDeviceId()));
}
@@ -487,17 +475,15 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
deviceSession.setTimeZone(timeZone);
}
}
-
}
if (deviceSession != null) {
sendResponse(channel, false, type, buf.getShort(buf.writerIndex() - 6), null);
}
- } else if (type == MSG_HEARTBEAT) {
+ return null;
- Position position = new Position(getProtocolName());
- position.setDeviceId(deviceSession.getDeviceId());
+ } else if (type == MSG_HEARTBEAT) {
getLastLocation(position, null);
@@ -526,6 +512,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
content.writeBytes(response.getBytes(StandardCharsets.US_ASCII));
sendResponse(channel, true, MSG_ADDRESS_RESPONSE, 0, content);
+ return null;
+
} else if (type == MSG_TIME_REQUEST) {
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
@@ -538,40 +526,9 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
content.writeByte(calendar.get(Calendar.SECOND));
sendResponse(channel, false, MSG_TIME_REQUEST, 0, content);
- } else if (type == MSG_X1_GPS || type == MSG_X1_PHOTO_INFO) {
-
- return decodeX1(channel, buf, deviceSession, type);
-
- } else if (type == MSG_WIFI || type == MSG_WIFI_2 || type == MSG_WIFI_4) {
-
- return decodeWifi(channel, buf, deviceSession, type);
-
- } else if (type == MSG_INFO) {
-
- Position position = new Position(getProtocolName());
- position.setDeviceId(deviceSession.getDeviceId());
-
- getLastLocation(position, null);
-
- position.set(Position.KEY_POWER, buf.readShort() * 0.01);
-
- return position;
-
- } else {
-
- return decodeBasicOther(channel, buf, deviceSession, type, dataLength);
-
- }
-
- return null;
- }
-
- private Object decodeX1(Channel channel, ByteBuf buf, DeviceSession deviceSession, int type) {
-
- if (type == MSG_X1_GPS) {
+ return null;
- Position position = new Position(getProtocolName());
- position.setDeviceId(deviceSession.getDeviceId());
+ } else if (type == MSG_X1_GPS) {
buf.readUnsignedInt(); // data and alarm
@@ -619,86 +576,77 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
photos.put(pictureId, photo);
sendPhotoRequest(channel, pictureId);
- }
-
- return null;
- }
+ return null;
- private Object decodeWifi(Channel channel, ByteBuf buf, DeviceSession deviceSession, int type) {
+ } else if (type == MSG_WIFI || type == MSG_WIFI_2 || type == MSG_WIFI_4) {
- Position position = new Position(getProtocolName());
- position.setDeviceId(deviceSession.getDeviceId());
+ ByteBuf time = buf.readSlice(6);
+ DateBuilder dateBuilder = new DateBuilder()
+ .setYear(BcdUtil.readInteger(time, 2))
+ .setMonth(BcdUtil.readInteger(time, 2))
+ .setDay(BcdUtil.readInteger(time, 2))
+ .setHour(BcdUtil.readInteger(time, 2))
+ .setMinute(BcdUtil.readInteger(time, 2))
+ .setSecond(BcdUtil.readInteger(time, 2));
+ getLastLocation(position, dateBuilder.getDate());
- ByteBuf time = buf.readSlice(6);
- DateBuilder dateBuilder = new DateBuilder()
- .setYear(BcdUtil.readInteger(time, 2))
- .setMonth(BcdUtil.readInteger(time, 2))
- .setDay(BcdUtil.readInteger(time, 2))
- .setHour(BcdUtil.readInteger(time, 2))
- .setMinute(BcdUtil.readInteger(time, 2))
- .setSecond(BcdUtil.readInteger(time, 2));
- getLastLocation(position, dateBuilder.getDate());
-
- Network network = new Network();
-
- int wifiCount;
- if (type == MSG_WIFI_4) {
- wifiCount = buf.readUnsignedByte();
- } else {
- wifiCount = buf.getUnsignedByte(2);
- }
+ Network network = new Network();
- for (int i = 0; i < wifiCount; i++) {
+ int wifiCount;
if (type == MSG_WIFI_4) {
- buf.skipBytes(2);
+ wifiCount = buf.readUnsignedByte();
+ } else {
+ wifiCount = buf.getUnsignedByte(2);
}
- WifiAccessPoint wifiAccessPoint = new WifiAccessPoint();
- wifiAccessPoint.setMacAddress(String.format("%02x:%02x:%02x:%02x:%02x:%02x",
- buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte(),
- buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()));
- if (type != MSG_WIFI_4) {
- wifiAccessPoint.setSignalStrength((int) buf.readUnsignedByte());
+
+ for (int i = 0; i < wifiCount; i++) {
+ if (type == MSG_WIFI_4) {
+ buf.skipBytes(2);
+ }
+ WifiAccessPoint wifiAccessPoint = new WifiAccessPoint();
+ wifiAccessPoint.setMacAddress(String.format("%02x:%02x:%02x:%02x:%02x:%02x",
+ buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte(),
+ buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()));
+ if (type != MSG_WIFI_4) {
+ wifiAccessPoint.setSignalStrength((int) buf.readUnsignedByte());
+ }
+ network.addWifiAccessPoint(wifiAccessPoint);
}
- network.addWifiAccessPoint(wifiAccessPoint);
- }
- if (type != MSG_WIFI_4) {
+ if (type != MSG_WIFI_4) {
- int cellCount = buf.readUnsignedByte();
- int mcc = buf.readUnsignedShort();
- int mnc = buf.readUnsignedByte();
- for (int i = 0; i < cellCount; i++) {
- network.addCellTower(CellTower.from(
- mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedShort(), buf.readUnsignedByte()));
- }
+ int cellCount = buf.readUnsignedByte();
+ int mcc = buf.readUnsignedShort();
+ int mnc = buf.readUnsignedByte();
+ for (int i = 0; i < cellCount; i++) {
+ network.addCellTower(CellTower.from(
+ mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedShort(), buf.readUnsignedByte()));
+ }
- if (channel != null) {
- ByteBuf response = Unpooled.buffer();
- response.writeShort(0x7878);
- response.writeByte(0);
- response.writeByte(type);
- response.writeBytes(time.resetReaderIndex());
- response.writeByte('\r');
- response.writeByte('\n');
- channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress()));
- }
+ if (channel != null) {
+ ByteBuf response = Unpooled.buffer();
+ response.writeShort(0x7878);
+ response.writeByte(0);
+ response.writeByte(type);
+ response.writeBytes(time.resetReaderIndex());
+ response.writeByte('\r');
+ response.writeByte('\n');
+ channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress()));
+ }
- }
+ }
- position.setNetwork(network);
+ position.setNetwork(network);
- return position;
- }
+ return position;
- private Object decodeBasicOther(
- Channel channel, ByteBuf buf, DeviceSession deviceSession, int type, int dataLength) {
+ } else if (type == MSG_INFO) {
- Position position = new Position(getProtocolName());
- position.setDeviceId(deviceSession.getDeviceId());
+ getLastLocation(position, null);
- if (type == MSG_LBS_STATUS && dataLength >= 18) {
+ position.set(Position.KEY_POWER, buf.readShort() * 0.01);
- return null; // space10x multi-lbs message
+ return position;
} else if (type == MSG_LBS_MULTIPLE_1 || type == MSG_LBS_MULTIPLE_2 || type == MSG_LBS_MULTIPLE_3
|| type == MSG_LBS_EXTEND || type == MSG_LBS_WIFI || type == MSG_LBS_2
@@ -712,9 +660,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
getLastLocation(position, dateBuilder.getDate());
- boolean hasCellCount = type == MSG_LBS_MULTIPLE_3 && dataLength == 44;
-
- if (hasCellCount) {
+ if (variant == Variant.WANWAY_S20) {
buf.readUnsignedByte(); // ta
}
@@ -722,7 +668,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
int mnc = BitUtil.check(mcc, 15) ? buf.readUnsignedShort() : buf.readUnsignedByte();
Network network = new Network();
- int cellCount = hasCellCount ? buf.readUnsignedByte() : type == MSG_WIFI_5 ? 6 : 7;
+ int cellCount = variant == Variant.WANWAY_S20 ? buf.readUnsignedByte() : type == MSG_WIFI_5 ? 6 : 7;
for (int i = 0; i < cellCount; i++) {
int lac = longFormat ? buf.readInt() : buf.readUnsignedShort();
int cid = longFormat ? (int) buf.readLong() : buf.readUnsignedMedium();
@@ -732,7 +678,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
}
}
- if (!hasCellCount) {
+ if (variant != Variant.WANWAY_S20) {
buf.readUnsignedByte(); // ta
}
@@ -764,7 +710,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
}
}
- } else if (type == MSG_BMS || type == MSG_BMS_2) {
+ } else if (type == MSG_BMS) {
buf.skipBytes(8); // serial number
@@ -796,15 +742,121 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
return position;
+ } else if (type == MSG_STATUS && buf.readableBytes() == 22) {
+
+ getLastLocation(position, null);
+
+ buf.readUnsignedByte(); // information content
+ buf.readUnsignedShort(); // satellites
+ buf.readUnsignedByte(); // alarm
+ buf.readUnsignedByte(); // language
+
+ position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte());
+
+ buf.readUnsignedByte(); // working mode
+ buf.readUnsignedShort(); // working voltage
+ buf.readUnsignedByte(); // reserved
+ buf.readUnsignedShort(); // working times
+ buf.readUnsignedShort(); // working time
+
+ int value = buf.readUnsignedShort();
+ double temperature = BitUtil.to(value, 15) * 0.1;
+ position.set(Position.PREFIX_TEMP + 1, BitUtil.check(value, 15) ? temperature : -temperature);
+
} else if (isSupported(type)) {
- if (type == MSG_STATUS && buf.readableBytes() == 22) {
- decodeHeartbeat(buf, position);
+ if (type == MSG_LBS_STATUS && variant == Variant.SPACE10X) {
+ return null; // multi-lbs message
+ }
+
+ if (hasGps(type)) {
+ decodeGps(position, buf, false, deviceSession.getTimeZone());
} else {
- decodeBasicUniversal(buf, deviceSession, type, position);
+ getLastLocation(position, null);
+ }
+
+ if (hasLbs(type)) {
+ decodeLbs(position, buf, type, hasStatus(type));
+ }
+
+ if (hasStatus(type)) {
+ decodeStatus(position, buf, true);
+ position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte()));
+ }
+
+ if (type == MSG_GPS_LBS_1) {
+ if (variant == Variant.GT06E_CARD) {
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());
+ String data = buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString();
+ buf.readUnsignedByte(); // alarm
+ buf.readUnsignedByte(); // swiped
+ position.set("driverLicense", data.trim());
+ } else if (variant == Variant.BENWAY) {
+ int mask = buf.readUnsignedShort();
+ position.set(Position.KEY_IGNITION, BitUtil.check(mask, 8 + 7));
+ position.set(Position.PREFIX_IN + 2, BitUtil.check(mask, 8 + 6));
+ if (BitUtil.check(mask, 8 + 4)) {
+ int value = BitUtil.to(mask, 8 + 1);
+ if (BitUtil.check(mask, 8 + 1)) {
+ value = -value;
+ }
+ position.set(Position.PREFIX_TEMP + 1, value);
+ } else {
+ int value = BitUtil.to(mask, 8 + 2);
+ if (BitUtil.check(mask, 8 + 5)) {
+ position.set(Position.PREFIX_ADC + 1, value);
+ } else {
+ position.set(Position.PREFIX_ADC + 1, value * 0.1);
+ }
+ }
+ } else if (variant == Variant.VXT01) {
+ decodeStatus(position, buf, false);
+ buf.readUnsignedByte(); // alarm extension
+ } else if (variant == Variant.S5) {
+ decodeStatus(position, buf, false);
+ position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte()));
+ position.set("oil", buf.readUnsignedShort());
+ int temperature = buf.readUnsignedByte();
+ if (BitUtil.check(temperature, 7)) {
+ temperature = -BitUtil.to(temperature, 7);
+ }
+ position.set(Position.PREFIX_TEMP + 1, temperature);
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 10);
+ }
+ }
+
+ if ((type == MSG_GPS_LBS_2 || type == MSG_GPS_LBS_3 || type == MSG_GPS_LBS_4)
+ && buf.readableBytes() >= 3 + 6) {
+ position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0);
+ position.set(Position.KEY_EVENT, buf.readUnsignedByte()); // reason
+ position.set(Position.KEY_ARCHIVE, buf.readUnsignedByte() > 0);
+ }
+
+ if (type == MSG_GPS_LBS_3) {
+ int module = buf.readUnsignedShort();
+ int subLength = buf.readUnsignedByte();
+ switch (module) {
+ case 0x0027:
+ position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01);
+ break;
+ case 0x002E:
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());
+ break;
+ case 0x003B:
+ position.setAccuracy(buf.readUnsignedShort() * 0.01);
+ break;
+ default:
+ buf.skipBytes(subLength);
+ break;
+ }
+ }
+
+ if (buf.readableBytes() == 4 + 6) {
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());
}
} else if (type == MSG_ALARM) {
+
boolean extendedAlarm = dataLength > 7;
if (extendedAlarm) {
decodeGps(position, buf, false, false, false, deviceSession.getTimeZone());
@@ -841,6 +893,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_ALARM, Position.ALARM_GENERAL);
break;
}
+
} else {
if (dataLength > 0) {
@@ -866,114 +919,6 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
return position;
}
- private void decodeHeartbeat(ByteBuf buf, Position position) {
-
- getLastLocation(position, null);
-
- buf.readUnsignedByte(); // information content
- buf.readUnsignedShort(); // satellites
- buf.readUnsignedByte(); // alarm
- buf.readUnsignedByte(); // language
-
- position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte());
-
- buf.readUnsignedByte(); // working mode
- buf.readUnsignedShort(); // working voltage
- buf.readUnsignedByte(); // reserved
- buf.readUnsignedShort(); // working times
- buf.readUnsignedShort(); // working time
-
- int value = buf.readUnsignedShort();
- double temperature = BitUtil.to(value, 15) * 0.1;
- position.set(Position.PREFIX_TEMP + 1, BitUtil.check(value, 15) ? temperature : -temperature);
-
- }
-
- private void decodeBasicUniversal(ByteBuf buf, DeviceSession deviceSession, int type, Position position) {
-
- if (hasGps(type)) {
- decodeGps(position, buf, false, deviceSession.getTimeZone());
- } else {
- getLastLocation(position, null);
- }
-
- if (hasLbs(type)) {
- decodeLbs(position, buf, type, hasStatus(type));
- }
-
- if (hasStatus(type)) {
- decodeStatus(position, buf, true);
- }
-
- if (type == MSG_GPS_LBS_1 && buf.readableBytes() > 75 + 6) {
- position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());
- String data = buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString();
- buf.readUnsignedByte(); // alarm
- buf.readUnsignedByte(); // swiped
- position.set("driverLicense", data.trim());
- }
-
- if (type == MSG_GPS_LBS_1 && buf.readableBytes() == 18) {
- decodeStatus(position, buf, false);
- position.set("oil", buf.readUnsignedShort());
- int temperature = buf.readUnsignedByte();
- if (BitUtil.check(temperature, 7)) {
- temperature = -BitUtil.to(temperature, 7);
- }
- position.set(Position.PREFIX_TEMP + 1, temperature);
- position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 10);
- }
-
- if (type == MSG_GPS_LBS_1 && buf.readableBytes() == 2 + 6) {
- int mask = buf.readUnsignedShort();
- position.set(Position.KEY_IGNITION, BitUtil.check(mask, 8 + 7));
- position.set(Position.PREFIX_IN + 2, BitUtil.check(mask, 8 + 6));
- if (BitUtil.check(mask, 8 + 4)) {
- int value = BitUtil.to(mask, 8 + 1);
- if (BitUtil.check(mask, 8 + 1)) {
- value = -value;
- }
- position.set(Position.PREFIX_TEMP + 1, value);
- } else {
- int value = BitUtil.to(mask, 8 + 2);
- if (BitUtil.check(mask, 8 + 5)) {
- position.set(Position.PREFIX_ADC + 1, value);
- } else {
- position.set(Position.PREFIX_ADC + 1, value * 0.1);
- }
- }
- }
-
- if ((type == MSG_GPS_LBS_2 || type == MSG_GPS_LBS_3 || type == MSG_GPS_LBS_4) && buf.readableBytes() >= 3 + 6) {
- position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0);
- position.set(Position.KEY_EVENT, buf.readUnsignedByte()); // reason
- position.set(Position.KEY_ARCHIVE, buf.readUnsignedByte() > 0);
- }
-
- if (type == MSG_GPS_LBS_3) {
- int module = buf.readUnsignedShort();
- int length = buf.readUnsignedByte();
- switch (module) {
- case 0x0027:
- position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01);
- break;
- case 0x002E:
- position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());
- break;
- case 0x003B:
- position.setAccuracy(buf.readUnsignedShort() * 0.01);
- break;
- default:
- buf.skipBytes(length);
- break;
- }
- }
-
- if (buf.readableBytes() == 4 + 6) {
- position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());
- }
- }
-
private Object decodeExtended(Channel channel, SocketAddress remoteAddress, ByteBuf buf) {
DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
@@ -1001,7 +946,16 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
data = buf.readSlice(buf.readableBytes() - 6).toString(StandardCharsets.UTF_16BE);
}
- if (decodeLocationString(position, data) == null) {
+ Parser parser = new Parser(PATTERN_LOCATION, data);
+
+ if (parser.matches()) {
+ position.setValid(true);
+ position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG));
+ position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG));
+ position.setCourse(parser.nextDouble());
+ position.setSpeed(parser.nextDouble());
+ position.setTime(parser.nextDateTime(Parser.DateTimeFormat.YMD_HMS));
+ } else {
getLastLocation(position, null);
position.set(Position.KEY_RESULT, data);
}
@@ -1015,31 +969,50 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
getLastLocation(position, null);
if (subType == 0x00) {
+
position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort() * 0.01);
return position;
+
} else if (subType == 0x05) {
+
int flags = buf.readUnsignedByte();
position.set(Position.KEY_DOOR, BitUtil.check(flags, 0));
position.set(Position.PREFIX_IO + 1, BitUtil.check(flags, 2));
return position;
+
} else if (subType == 0x0a) {
+
buf.skipBytes(8); // imei
buf.skipBytes(8); // imsi
position.set(Position.KEY_ICCID, ByteBufUtil.hexDump(buf.readSlice(10)).replaceAll("f", ""));
return position;
+
} else if (subType == 0x0d) {
+
if (buf.getByte(buf.readerIndex()) != '!') {
buf.skipBytes(6);
}
- return decodeFuelData(position, buf.toString(
+
+ Parser parser = new Parser(PATTERN_FUEL, buf.toString(
buf.readerIndex(), buf.readableBytes() - 4 - 2, StandardCharsets.US_ASCII));
+ if (!parser.matches()) {
+ return null;
+ }
+
+ position.set(Position.PREFIX_TEMP + 1, parser.nextDouble(0));
+ position.set(Position.KEY_FUEL_LEVEL, parser.nextDouble(0));
+
+ return position;
+
} else if (subType == 0x1b) {
+
buf.readUnsignedByte(); // header
buf.readUnsignedByte(); // type
position.set(Position.KEY_DRIVER_UNIQUE_ID, ByteBufUtil.hexDump(buf.readSlice(4)));
buf.readUnsignedByte(); // checksum
buf.readUnsignedByte(); // footer
return position;
+
}
} else if (type == MSG_X1_PHOTO_DATA) {
@@ -1154,132 +1127,111 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
} else if (type == MSG_GPS_MODULAR) {
- return decodeExtendedModular(channel, buf, deviceSession);
-
- } else {
-
- return decodeExtendedOther(channel, buf, deviceSession, type);
-
- }
-
- return null;
- }
+ while (buf.readableBytes() > 6) {
+ int moduleType = buf.readUnsignedShort();
+ int moduleLength = buf.readUnsignedShort();
- private Object decodeExtendedModular(Channel channel, ByteBuf buf, DeviceSession deviceSession) {
-
- Position position = new Position(getProtocolName());
- position.setDeviceId(deviceSession.getDeviceId());
-
- while (buf.readableBytes() > 6) {
- int moduleType = buf.readUnsignedShort();
- int moduleLength = buf.readUnsignedShort();
-
- switch (moduleType) {
- case 0x03:
- position.set(Position.KEY_ICCID, ByteBufUtil.hexDump(buf.readSlice(10)));
- break;
- case 0x09:
- position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
- break;
- case 0x0a:
- position.set(Position.KEY_SATELLITES_VISIBLE, buf.readUnsignedByte());
- break;
- case 0x11:
- CellTower cellTower = CellTower.from(
- buf.readUnsignedShort(),
- buf.readUnsignedShort(),
- buf.readUnsignedShort(),
- buf.readUnsignedMedium(),
- buf.readUnsignedByte());
- if (cellTower.getCellId() > 0) {
- position.setNetwork(new Network(cellTower));
- }
- break;
- case 0x18:
- position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01);
- break;
- case 0x28:
- position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1);
- break;
- case 0x29:
- position.set(Position.KEY_INDEX, buf.readUnsignedInt());
- break;
- case 0x2a:
- int input = buf.readUnsignedByte();
- position.set(Position.KEY_DOOR, BitUtil.to(input, 4) > 0);
- position.set("tamper", BitUtil.from(input, 4) > 0);
- break;
- case 0x2b:
- int event = buf.readUnsignedByte();
- switch (event) {
- case 0x11:
- position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY);
- break;
- case 0x12:
- position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER);
- break;
- case 0x13:
- position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT);
- break;
- case 0x14:
- position.set(Position.KEY_ALARM, Position.ALARM_REMOVING);
- break;
- default:
- break;
- }
- position.set(Position.KEY_EVENT, event);
- break;
- case 0x2e:
- position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE());
- break;
- case 0x33:
- position.setTime(new Date(buf.readUnsignedInt() * 1000));
- position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
- position.setAltitude(buf.readShort());
-
- double latitude = buf.readUnsignedInt() / 60.0 / 30000.0;
- double longitude = buf.readUnsignedInt() / 60.0 / 30000.0;
- position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte()));
-
- int flags = buf.readUnsignedShort();
- position.setCourse(BitUtil.to(flags, 10));
- position.setValid(BitUtil.check(flags, 12));
-
- if (!BitUtil.check(flags, 10)) {
- latitude = -latitude;
- }
- if (BitUtil.check(flags, 11)) {
- longitude = -longitude;
- }
-
- position.setLatitude(latitude);
- position.setLongitude(longitude);
- break;
- case 0x34:
- position.set(Position.KEY_EVENT, buf.readUnsignedByte());
- buf.readUnsignedIntLE(); // time
- buf.skipBytes(buf.readUnsignedByte()); // content
- break;
- default:
- buf.skipBytes(moduleLength);
- break;
+ switch (moduleType) {
+ case 0x03:
+ position.set(Position.KEY_ICCID, ByteBufUtil.hexDump(buf.readSlice(10)));
+ break;
+ case 0x09:
+ position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
+ break;
+ case 0x0a:
+ position.set(Position.KEY_SATELLITES_VISIBLE, buf.readUnsignedByte());
+ break;
+ case 0x11:
+ CellTower cellTower = CellTower.from(
+ buf.readUnsignedShort(),
+ buf.readUnsignedShort(),
+ buf.readUnsignedShort(),
+ buf.readUnsignedMedium(),
+ buf.readUnsignedByte());
+ if (cellTower.getCellId() > 0) {
+ position.setNetwork(new Network(cellTower));
+ }
+ break;
+ case 0x18:
+ position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01);
+ break;
+ case 0x28:
+ position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1);
+ break;
+ case 0x29:
+ position.set(Position.KEY_INDEX, buf.readUnsignedInt());
+ break;
+ case 0x2a:
+ int input = buf.readUnsignedByte();
+ position.set(Position.KEY_DOOR, BitUtil.to(input, 4) > 0);
+ position.set("tamper", BitUtil.from(input, 4) > 0);
+ break;
+ case 0x2b:
+ int event = buf.readUnsignedByte();
+ switch (event) {
+ case 0x11:
+ position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY);
+ break;
+ case 0x12:
+ position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER);
+ break;
+ case 0x13:
+ position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT);
+ break;
+ case 0x14:
+ position.set(Position.KEY_ALARM, Position.ALARM_REMOVING);
+ break;
+ default:
+ break;
+ }
+ position.set(Position.KEY_EVENT, event);
+ break;
+ case 0x2e:
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE());
+ break;
+ case 0x33:
+ position.setTime(new Date(buf.readUnsignedInt() * 1000));
+ position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
+ position.setAltitude(buf.readShort());
+
+ double latitude = buf.readUnsignedInt() / 60.0 / 30000.0;
+ double longitude = buf.readUnsignedInt() / 60.0 / 30000.0;
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte()));
+
+ int flags = buf.readUnsignedShort();
+ position.setCourse(BitUtil.to(flags, 10));
+ position.setValid(BitUtil.check(flags, 12));
+
+ if (!BitUtil.check(flags, 10)) {
+ latitude = -latitude;
+ }
+ if (BitUtil.check(flags, 11)) {
+ longitude = -longitude;
+ }
+
+ position.setLatitude(latitude);
+ position.setLongitude(longitude);
+ break;
+ case 0x34:
+ position.set(Position.KEY_EVENT, buf.readUnsignedByte());
+ buf.readUnsignedIntLE(); // time
+ buf.skipBytes(buf.readUnsignedByte()); // content
+ break;
+ default:
+ buf.skipBytes(moduleLength);
+ break;
+ }
}
- }
-
- if (position.getFixTime() == null) {
- getLastLocation(position, null);
- }
-
- sendResponse(channel, false, MSG_GPS_MODULAR, buf.readUnsignedShort(), null);
- return position;
- }
+ if (position.getFixTime() == null) {
+ getLastLocation(position, null);
+ }
- private Object decodeExtendedOther(Channel channel, ByteBuf buf, DeviceSession deviceSession, int type) {
+ sendResponse(channel, false, MSG_GPS_MODULAR, buf.readUnsignedShort(), null);
- Position position = null;
+ return position;
- if (type == MSG_MULTIMEDIA || type == MSG_MULTIMEDIA_2) {
+ } else if (type == MSG_MULTIMEDIA) {
buf.skipBytes(8); // serial number
long timestamp = buf.readUnsignedInt() * 1000;
@@ -1354,21 +1306,52 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
return null;
}
+ private void decodeVariant(ByteBuf buf) {
+ int header = buf.getUnsignedShort(buf.readerIndex());
+ int length;
+ int type;
+ if (header == 0x7878) {
+ length = buf.getUnsignedByte(buf.readerIndex() + 2);
+ type = buf.getUnsignedByte(buf.readerIndex() + 2 + 1);
+ } else {
+ length = buf.getUnsignedShort(buf.readerIndex() + 2);
+ type = buf.getUnsignedByte(buf.readerIndex() + 2 + 2);
+ }
+
+ if (header == 0x7878 && type == MSG_GPS_LBS_1 && length == 0x24) {
+ variant = Variant.VXT01;
+ } else if (header == 0x7878 && type == MSG_GPS_LBS_STATUS_1 && length == 0x24) {
+ variant = Variant.VXT01;
+ } else if (header == 0x7878 && type == MSG_LBS_MULTIPLE_3 && length == 0x31) {
+ variant = Variant.WANWAY_S20;
+ } else if (header == 0x7878 && type == MSG_GPS_LBS_1 && length >= 0x71) {
+ variant = Variant.GT06E_CARD;
+ } else if (header == 0x7878 && type == MSG_GPS_LBS_1 && length == 0x21) {
+ variant = Variant.BENWAY;
+ } else if (header == 0x7878 && type == MSG_GPS_LBS_1 && length == 0x2b) {
+ variant = Variant.S5;
+ } else if (header == 0x7878 && type == MSG_LBS_STATUS && length >= 0x17) {
+ variant = Variant.SPACE10X;
+ } else {
+ variant = Variant.STANDARD;
+ }
+ }
+
@Override
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
+ decodeVariant(buf);
+
int header = buf.readShort();
if (header == 0x7878) {
return decodeBasic(channel, remoteAddress, buf);
- } else if (header == 0x7979) {
+ } else {
return decodeExtended(channel, remoteAddress, buf);
}
-
- return null;
}
}
diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
index 0ae08af37..c280ee9b2 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 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,6 +64,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_TIME_SYNC_REQUEST = 0x0109;
public static final int MSG_TIME_SYNC_RESPONSE = 0x8109;
public static final int MSG_PHOTO = 0x8888;
+ public static final int MSG_TRANSPARENT = 0x0900;
public static final int RESULT_SUCCESS = 0;
@@ -122,6 +123,9 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
|| BitUtil.check(value, 10) || BitUtil.check(value, 11)) {
return Position.ALARM_FAULT;
}
+ if (BitUtil.check(value, 7)) {
+ return Position.ALARM_LOW_BATTERY;
+ }
if (BitUtil.check(value, 8)) {
return Position.ALARM_POWER_OFF;
}
@@ -145,6 +149,17 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
return BitUtil.check(value, 15) ? -BitUtil.to(value, 15) : BitUtil.to(value, 15);
}
+ private Date readDate(ByteBuf buf, TimeZone timeZone) {
+ DateBuilder dateBuilder = new DateBuilder(timeZone)
+ .setYear(BcdUtil.readInteger(buf, 2))
+ .setMonth(BcdUtil.readInteger(buf, 2))
+ .setDay(BcdUtil.readInteger(buf, 2))
+ .setHour(BcdUtil.readInteger(buf, 2))
+ .setMinute(BcdUtil.readInteger(buf, 2))
+ .setSecond(BcdUtil.readInteger(buf, 2));
+ return dateBuilder.getDate();
+ }
+
@Override
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
@@ -264,6 +279,10 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
return position;
+ } else if (type == MSG_TRANSPARENT) {
+
+ return decodeTransparent(deviceSession, buf);
+
}
return null;
@@ -351,12 +370,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
}
}
- private Position decodeLocation(DeviceSession deviceSession, ByteBuf buf) {
-
- Position position = new Position(getProtocolName());
- position.setDeviceId(deviceSession.getDeviceId());
-
- position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedInt()));
+ private void decodeCoordinates(Position position, ByteBuf buf) {
int status = buf.readInt();
@@ -379,19 +393,21 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
} else {
position.setLongitude(lon);
}
+ }
+
+ private Position decodeLocation(DeviceSession deviceSession, ByteBuf buf) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedInt()));
+
+ decodeCoordinates(position, buf);
position.setAltitude(buf.readShort());
position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() * 0.1));
position.setCourse(buf.readUnsignedShort());
-
- DateBuilder dateBuilder = new DateBuilder(deviceSession.getTimeZone())
- .setYear(BcdUtil.readInteger(buf, 2))
- .setMonth(BcdUtil.readInteger(buf, 2))
- .setDay(BcdUtil.readInteger(buf, 2))
- .setHour(BcdUtil.readInteger(buf, 2))
- .setMinute(BcdUtil.readInteger(buf, 2))
- .setSecond(BcdUtil.readInteger(buf, 2));
- position.setTime(dateBuilder.getDate());
+ position.setTime(readDate(buf, deviceSession.getTimeZone()));
if (buf.readableBytes() == 20) {
@@ -606,4 +622,118 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
return positions;
}
+ private Position decodeTransparent(DeviceSession deviceSession, ByteBuf buf) {
+
+ int type = buf.readUnsignedByte();
+
+ if (type == 0xF0) {
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ Date time = readDate(buf, deviceSession.getTimeZone());
+
+ if (buf.readUnsignedByte() > 0) {
+ position.set(Position.KEY_ARCHIVE, true);
+ }
+
+ buf.readUnsignedByte(); // vehicle type
+
+ int count;
+ int subtype = buf.readUnsignedByte();
+ switch (subtype) {
+ case 0x01:
+ count = buf.readUnsignedByte();
+ for (int i = 0; i < count; i++) {
+ int id = buf.readUnsignedShort();
+ int length = buf.readUnsignedByte();
+ switch (id) {
+ case 0x0102:
+ case 0x0528:
+ case 0x0546:
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 100);
+ break;
+ case 0x0103:
+ position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedInt() * 0.01);
+ break;
+ case 0x052A:
+ position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShort() * 0.01);
+ break;
+ case 0x0105:
+ case 0x052C:
+ position.set(Position.KEY_FUEL_USED, buf.readUnsignedInt() * 0.01);
+ break;
+ case 0x014A:
+ case 0x0537:
+ case 0x0538:
+ case 0x0539:
+ position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort() * 0.01);
+ break;
+ default:
+ switch (length) {
+ case 1:
+ position.set(Position.PREFIX_IO + id, buf.readUnsignedByte());
+ break;
+ case 2:
+ position.set(Position.PREFIX_IO + id, buf.readUnsignedShort());
+ break;
+ case 4:
+ position.set(Position.PREFIX_IO + id, buf.readUnsignedInt());
+ break;
+ default:
+ buf.skipBytes(length);
+ break;
+ }
+ break;
+ }
+ }
+ decodeCoordinates(position, buf);
+ position.setTime(time);
+ break;
+ case 0x03:
+ count = buf.readUnsignedByte();
+ for (int i = 0; i < count; i++) {
+ int id = buf.readUnsignedShort();
+ int length = buf.readUnsignedByte();
+ switch (id) {
+ case 0x1A:
+ position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION);
+ break;
+ case 0x1B:
+ position.set(Position.KEY_ALARM, Position.ALARM_BRAKING);
+ break;
+ case 0x1C:
+ position.set(Position.KEY_ALARM, Position.ALARM_CORNERING);
+ break;
+ case 0x1D:
+ case 0x1E:
+ case 0x1F:
+ position.set(Position.KEY_ALARM, Position.ALARM_LANE_CHANGE);
+ break;
+ case 0x23:
+ position.set(Position.KEY_ALARM, Position.ALARM_FATIGUE_DRIVING);
+ break;
+ default:
+ break;
+ }
+ buf.skipBytes(length);
+ }
+ decodeCoordinates(position, buf);
+ position.setTime(time);
+ break;
+ case 0x0B:
+ if (buf.readUnsignedByte() > 0) {
+ position.set(Position.KEY_VIN, buf.readCharSequence(17, StandardCharsets.US_ASCII).toString());
+ }
+ getLastLocation(position, time);
+ break;
+ default:
+ return null;
+ }
+
+ return position;
+ }
+
+ return null;
+ }
+
}
diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java
index a9cc1de3a..013e297c0 100644
--- a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java
@@ -404,7 +404,8 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
int paramCount = buf.readUnsignedByte();
for (int j = 0; j < paramCount; j++) {
- int id = buf.readUnsignedByte();
+ boolean extension = buf.getUnsignedByte(buf.readerIndex()) == 0xFE;
+ int id = extension ? buf.readUnsignedShort() : buf.readUnsignedByte();
switch (id) {
case 0x01:
position.set(Position.KEY_EVENT, buf.readUnsignedByte());
@@ -430,6 +431,9 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
case 0x9D:
position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte());
break;
+ case 0xFE69:
+ position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte());
+ break;
default:
buf.readUnsignedByte();
break;
@@ -438,7 +442,8 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
paramCount = buf.readUnsignedByte();
for (int j = 0; j < paramCount; j++) {
- int id = buf.readUnsignedByte();
+ boolean extension = buf.getUnsignedByte(buf.readerIndex()) == 0xFE;
+ int id = extension ? buf.readUnsignedShort() : buf.readUnsignedByte();
switch (id) {
case 0x08:
position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE()));
@@ -491,7 +496,8 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
paramCount = buf.readUnsignedByte();
for (int j = 0; j < paramCount; j++) {
- int id = buf.readUnsignedByte();
+ boolean extension = buf.getUnsignedByte(buf.readerIndex()) == 0xFE;
+ int id = extension ? buf.readUnsignedShort() : buf.readUnsignedByte();
switch (id) {
case 0x02:
position.setLatitude(buf.readIntLE() * 0.000001);
@@ -523,7 +529,11 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
paramCount = buf.readUnsignedByte();
for (int j = 0; j < paramCount; j++) {
- buf.readUnsignedByte(); // id
+ if (buf.getUnsignedByte(buf.readerIndex()) == 0xFE) {
+ buf.readUnsignedShort(); // extension id
+ } else {
+ buf.readUnsignedByte(); // id
+ }
buf.skipBytes(buf.readUnsignedByte()); // value
}
diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java
index 641a45864..c63226c80 100644
--- a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -263,6 +263,14 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
buf.readUnsignedInt(); // timestamp
position.set(Position.KEY_STEPS, buf.readUnsignedInt());
break;
+ case 0x31:
+ int i = 1;
+ while (buf.readerIndex() < endIndex) {
+ position.set("activity" + i + "Time", buf.readUnsignedInt());
+ position.set("activity" + i, buf.readUnsignedInt());
+ i += 1;
+ }
+ break;
case 0x40:
buf.readUnsignedIntLE(); // timestamp
int heartRate = buf.readUnsignedByte();
diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java
index 2812d22ff..5a0383358 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 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_SMS_VIA_GPRS_RESPONSE = 7;
public static final int MSG_SMS_VIA_GPRS = 8;
public static final int MSG_DTCS = 9;
+ public static final int MSG_IDENTIFICATION = 15;
public static final int MSG_SET_IO = 17;
public static final int MSG_FILES = 37;
public static final int MSG_EXTENDED_RECORDS = 68;
@@ -306,6 +307,18 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder {
return null;
+ } else if (type == MSG_IDENTIFICATION) {
+
+ ByteBuf content = Unpooled.buffer();
+ content.writeByte(1);
+ ByteBuf response = RuptelaProtocolEncoder.encodeContent(type, content);
+ content.release();
+ if (channel != null) {
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
+ }
+
+ return null;
+
} else {
return decodeCommandResponse(deviceSession, type, buf);
diff --git a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java
index c1869def2..8a7b5cec7 100644
--- a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java
@@ -42,7 +42,6 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
.number("d+,") // length
.number("(d+),") // imei
.expression("(.+)") // content
- .number("xx") // checksum
.compile();
private static final Pattern PATTERN_POSITION = new PatternBuilder()
@@ -123,6 +122,9 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
}
String content = parser.next();
+ if (content.charAt(content.length() - 2 - 1) != '|') {
+ content = content.substring(0, content.length() - 2);
+ }
if (content.length() < 100) {
Position position = new Position(getProtocolName());
diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java
index 2f8e50c5e..8926f427e 100644
--- a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java
@@ -44,6 +44,7 @@ import java.util.TimeZone;
public class SuntechProtocolDecoder extends BaseProtocolDecoder {
+ private boolean universal;
private String prefix;
private int protocolType;
@@ -58,6 +59,10 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
super(protocol);
}
+ public boolean getUniversal() {
+ return universal;
+ }
+
public String getPrefix() {
return prefix;
}
@@ -831,6 +836,7 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
if (buf.getByte(buf.readerIndex() + 1) == 0) {
+ universal = true;
return decodeBinary(channel, remoteAddress, buf);
} else {
@@ -841,6 +847,7 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
if (prefix.equals("CRR")) {
return decodeCrashReport(channel, remoteAddress, buf);
} else if (prefix.length() < 5) {
+ universal = true;
return decodeUniversal(channel, remoteAddress, values);
} else if (prefix.endsWith("HTE")) {
return decodeTravelReport(channel, remoteAddress, values);
diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java
index 3b4995110..597acaae8 100644
--- a/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java
+++ b/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,49 +27,95 @@ public class SuntechProtocolEncoder extends StringProtocolEncoder {
super(protocol);
}
- private String getPrefix(Channel channel) {
- String prefix = "SA200CMD";
+ @Override
+ protected Object encodeCommand(Channel channel, Command command) {
+
+ boolean universal = false;
+ String prefix = "SA200";
if (channel != null) {
SuntechProtocolDecoder protocolDecoder =
BasePipelineFactory.getHandler(channel.pipeline(), SuntechProtocolDecoder.class);
if (protocolDecoder != null) {
+ universal = protocolDecoder.getUniversal();
String decoderPrefix = protocolDecoder.getPrefix();
if (decoderPrefix != null && decoderPrefix.length() > 5) {
- prefix = decoderPrefix.substring(0, decoderPrefix.length() - 3) + "CMD";
+ prefix = decoderPrefix.substring(0, decoderPrefix.length() - 3);
}
}
}
- return prefix;
- }
-
- @Override
- protected Object encodeCommand(Channel channel, Command command) {
- String prefix = getPrefix(channel);
+ if (universal) {
+ return encodeUniversalCommand(channel, command);
+ } else {
+ return encodeLegacyCommand(channel, prefix, command);
+ }
+ }
+ protected Object encodeUniversalCommand(Channel channel, Command command) {
switch (command.getType()) {
case Command.TYPE_REBOOT_DEVICE:
- return formatCommand(command, prefix + ";%s;02;Reboot\r", Command.KEY_UNIQUE_ID);
+ return formatCommand(command, "CMD;%s;03;03\r", Command.KEY_UNIQUE_ID);
case Command.TYPE_POSITION_SINGLE:
- return formatCommand(command, prefix + ";%s;02;\r", Command.KEY_UNIQUE_ID);
+ return formatCommand(command, "CMD;%s;03;01\r", Command.KEY_UNIQUE_ID);
case Command.TYPE_OUTPUT_CONTROL:
- if (command.getAttributes().containsKey(Command.KEY_DATA)) {
- if (command.getAttributes().get(Command.KEY_DATA).equals("1")) {
- return formatCommand(command, prefix + ";%s;02;Enable%s\r",
- Command.KEY_UNIQUE_ID, Command.KEY_INDEX);
- } else {
- return formatCommand(command, prefix + ";%s;02;Disable%s\r",
- Command.KEY_UNIQUE_ID, Command.KEY_INDEX);
+ if (command.getAttributes().get(Command.KEY_DATA).equals("1")) {
+ switch (command.getString(Command.KEY_INDEX)) {
+ case "1":
+ return formatCommand(command, "CMD;%s;04;01\r", Command.KEY_UNIQUE_ID);
+ case "2":
+ return formatCommand(command, "CMD;%s;04;03\r", Command.KEY_UNIQUE_ID);
+ case "3":
+ return formatCommand(command, "CMD;%s;04;09\r", Command.KEY_UNIQUE_ID);
+ default:
+ return null;
}
+ } else {
+ switch (command.getString(Command.KEY_INDEX)) {
+ case "1":
+ return formatCommand(command, "CMD;%s;04;02\r", Command.KEY_UNIQUE_ID);
+ case "2":
+ return formatCommand(command, "CMD;%s;04;04\r", Command.KEY_UNIQUE_ID);
+ case "3":
+ return formatCommand(command, "CMD;%s;04;10\r", Command.KEY_UNIQUE_ID);
+ default:
+ return null;
+ }
+ }
+ case Command.TYPE_ENGINE_STOP:
+ return formatCommand(command, "CMD;%s;04;01\r", Command.KEY_UNIQUE_ID);
+ case Command.TYPE_ENGINE_RESUME:
+ return formatCommand(command, "CMD;%s;02;02\r", Command.KEY_UNIQUE_ID);
+ case Command.TYPE_ALARM_ARM:
+ return formatCommand(command, "CMD;%s;04;03\r", Command.KEY_UNIQUE_ID);
+ case Command.TYPE_ALARM_DISARM:
+ return formatCommand(command, "CMD;%s;04;04\r", Command.KEY_UNIQUE_ID);
+ default:
+ return null;
+ }
+ }
+
+ protected Object encodeLegacyCommand(Channel channel, String prefix, Command command) {
+ switch (command.getType()) {
+ case Command.TYPE_REBOOT_DEVICE:
+ return formatCommand(command, prefix + "CMD;%s;02;Reboot\r", Command.KEY_UNIQUE_ID);
+ case Command.TYPE_POSITION_SINGLE:
+ return formatCommand(command, prefix + "CMD;%s;02;\r", Command.KEY_UNIQUE_ID);
+ case Command.TYPE_OUTPUT_CONTROL:
+ if (command.getAttributes().get(Command.KEY_DATA).equals("1")) {
+ return formatCommand(command, prefix + "CMD;%s;02;Enable%s\r",
+ Command.KEY_UNIQUE_ID, Command.KEY_INDEX);
+ } else {
+ return formatCommand(command, prefix + "CMD;%s;02;Disable%s\r",
+ Command.KEY_UNIQUE_ID, Command.KEY_INDEX);
}
case Command.TYPE_ENGINE_STOP:
- return formatCommand(command, prefix + ";%s;02;Enable1\r", Command.KEY_UNIQUE_ID);
+ return formatCommand(command, prefix + "CMD;%s;02;Enable1\r", Command.KEY_UNIQUE_ID);
case Command.TYPE_ENGINE_RESUME:
- return formatCommand(command, prefix + ";%s;02;Disable1\r", Command.KEY_UNIQUE_ID);
+ return formatCommand(command, prefix + "CMD;%s;02;Disable1\r", Command.KEY_UNIQUE_ID);
case Command.TYPE_ALARM_ARM:
- return formatCommand(command, prefix + ";%s;02;Enable2\r", Command.KEY_UNIQUE_ID);
+ return formatCommand(command, prefix + "CMD;%s;02;Enable2\r", Command.KEY_UNIQUE_ID);
case Command.TYPE_ALARM_DISARM:
- return formatCommand(command, prefix + ";%s;02;Disable2\r", Command.KEY_UNIQUE_ID);
+ return formatCommand(command, prefix + "CMD;%s;02;Disable2\r", Command.KEY_UNIQUE_ID);
default:
return null;
}
diff --git a/src/main/java/org/traccar/protocol/WliProtocolDecoder.java b/src/main/java/org/traccar/protocol/WliProtocolDecoder.java
index 0e2a0a65e..976d4fb27 100644
--- a/src/main/java/org/traccar/protocol/WliProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/WliProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,8 @@ import org.traccar.DeviceSession;
import org.traccar.Protocol;
import org.traccar.helper.DateBuilder;
import org.traccar.helper.UnitsConverter;
+import org.traccar.model.CellTower;
+import org.traccar.model.Network;
import org.traccar.model.Position;
import java.net.SocketAddress;
@@ -41,9 +43,9 @@ public class WliProtocolDecoder extends BaseProtocolDecoder {
ByteBuf buf = (ByteBuf) msg;
buf.readUnsignedByte(); // header
- int type = buf.readUnsignedByte();
+ int clazz = buf.readUnsignedByte();
- if (type == '1') {
+ if (clazz == '1') {
DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
if (deviceSession == null) {
@@ -53,11 +55,13 @@ public class WliProtocolDecoder extends BaseProtocolDecoder {
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
+ CellTower cellTower = new CellTower();
+
position.set(Position.KEY_INDEX, buf.readUnsignedShort());
buf.readUnsignedShort(); // length
buf.readUnsignedShort(); // checksum
- buf.readUnsignedByte(); // application message type
+ int type = buf.readUnsignedByte();
buf.readUnsignedByte(); // delimiter
while (buf.readableBytes() > 1) {
@@ -95,18 +99,56 @@ public class WliProtocolDecoder extends BaseProtocolDecoder {
String value = buf.readCharSequence(
endIndex - buf.readerIndex(), StandardCharsets.US_ASCII).toString();
- switch (fieldNumber) {
- case 246:
- String[] values = value.split(",");
- position.set(Position.KEY_POWER, Integer.parseInt(values[2]) * 0.01);
- position.set(Position.KEY_BATTERY, Integer.parseInt(values[3]) * 0.01);
+ int networkFieldsOffset;
+ switch (type) {
+ case 0xE4:
+ networkFieldsOffset = 10;
+ break;
+ case 0xCB:
+ networkFieldsOffset = 80;
break;
- case 255:
- position.setDeviceTime(new Date(Long.parseLong(value) * 1000));
+ case 0x1E:
+ networkFieldsOffset = 182;
break;
+ case 0xC9:
default:
+ networkFieldsOffset = 35;
break;
}
+ if (fieldNumber - networkFieldsOffset >= 0 && fieldNumber - networkFieldsOffset < 10) {
+ switch (fieldNumber - networkFieldsOffset) {
+ case 0:
+ cellTower.setMobileCountryCode(Integer.parseInt(value));
+ break;
+ case 1:
+ cellTower.setMobileNetworkCode(Integer.parseInt(value));
+ break;
+ case 2:
+ cellTower.setLocationAreaCode(Integer.parseInt(value));
+ break;
+ case 3:
+ cellTower.setCellId(Long.parseLong(value));
+ break;
+ case 4:
+ cellTower.setSignalStrength(Integer.parseInt(value));
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (fieldNumber) {
+ case 246:
+ String[] values = value.split(",");
+ position.set(Position.KEY_POWER, Integer.parseInt(values[2]) * 0.01);
+ position.set(Position.KEY_BATTERY, Integer.parseInt(values[3]) * 0.01);
+ break;
+ case 255:
+ position.setDeviceTime(new Date(Long.parseLong(value) * 1000));
+ break;
+ default:
+ break;
+ }
+ }
}
@@ -114,13 +156,21 @@ public class WliProtocolDecoder extends BaseProtocolDecoder {
}
+ if (type == 0xE4) {
+ getLastLocation(position, position.getDeviceTime());
+ }
+
+ if (cellTower.getCellId() != null) {
+ position.setNetwork(new Network(cellTower));
+ }
+
if (!position.getValid()) {
getLastLocation(position, position.getDeviceTime());
}
return position;
- } else if (type == '2') {
+ } else if (clazz == '2') {
String id = buf.toString(buf.readerIndex(), buf.readableBytes() - 1, StandardCharsets.US_ASCII);
getDeviceSession(channel, remoteAddress, id.substring("wli:".length()));
diff --git a/src/main/java/org/traccar/schedule/ScheduleManager.java b/src/main/java/org/traccar/schedule/ScheduleManager.java
index 5d5054100..7a3d33b85 100644
--- a/src/main/java/org/traccar/schedule/ScheduleManager.java
+++ b/src/main/java/org/traccar/schedule/ScheduleManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,29 +15,30 @@
*/
package org.traccar.schedule;
+import org.traccar.LifecycleObject;
+
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
-public class ScheduleManager {
+public class ScheduleManager implements LifecycleObject {
private ScheduledExecutorService executor;
+ @Override
public void start() {
-
executor = Executors.newSingleThreadScheduledExecutor();
new TaskDeviceInactivityCheck().schedule(executor);
new TaskWebSocketKeepalive().schedule(executor);
-
+ new TaskHealthCheck().schedule(executor);
}
+ @Override
public void stop() {
-
if (executor != null) {
executor.shutdown();
executor = null;
}
-
}
}
diff --git a/src/main/java/org/traccar/api/HealthCheckService.java b/src/main/java/org/traccar/schedule/TaskHealthCheck.java
index 0182cc358..087cd3e63 100644
--- a/src/main/java/org/traccar/api/HealthCheckService.java
+++ b/src/main/java/org/traccar/schedule/TaskHealthCheck.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.api;
+package org.traccar.schedule;
import com.sun.jna.Library;
import com.sun.jna.Native;
@@ -22,18 +22,19 @@ import org.slf4j.LoggerFactory;
import org.traccar.Context;
import org.traccar.config.Keys;
-import java.util.TimerTask;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
-public class HealthCheckService {
+public class TaskHealthCheck implements Runnable {
- private static final Logger LOGGER = LoggerFactory.getLogger(HealthCheckService.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(TaskHealthCheck.class);
private SystemD systemD;
private boolean enabled;
private long period;
- public HealthCheckService() {
+ public TaskHealthCheck() {
if (!Context.getConfig().getBoolean(Keys.WEB_DISABLE_HEALTH_CHECK)
&& System.getProperty("os.name").toLowerCase().startsWith("linux")) {
try {
@@ -52,36 +53,30 @@ public class HealthCheckService {
}
}
- public boolean isEnabled() {
- return enabled;
- }
-
- public long getPeriod() {
- return period;
- }
-
private String getUrl() {
String address = Context.getConfig().getString(Keys.WEB_ADDRESS, "localhost");
int port = Context.getConfig().getInteger(Keys.WEB_PORT);
return "http://" + address + ":" + port + "/api/server";
}
- public TimerTask createTask() {
- return new TimerTask() {
- @Override
- public void run() {
- LOGGER.debug("Health check running");
- int status = Context.getClient().target(getUrl()).request().get().getStatus();
- if (status == 200) {
- int result = systemD.sd_notify(0, "WATCHDOG=1");
- if (result < 0) {
- LOGGER.warn("Health check notify error {}", result);
- }
- } else {
- LOGGER.warn("Health check failed with status {}", status);
- }
+ public void schedule(ScheduledExecutorService executor) {
+ if (enabled) {
+ executor.scheduleAtFixedRate(this, period, period, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Override
+ public void run() {
+ LOGGER.debug("Health check running");
+ int status = Context.getClient().target(getUrl()).request().get().getStatus();
+ if (status == 200) {
+ int result = systemD.sd_notify(0, "WATCHDOG=1");
+ if (result < 0) {
+ LOGGER.warn("Health check notify error {}", result);
}
- };
+ } else {
+ LOGGER.warn("Health check failed with status {}", status);
+ }
}
interface SystemD extends Library {
diff --git a/src/main/java/org/traccar/web/WebServer.java b/src/main/java/org/traccar/web/WebServer.java
index 932781156..0ed5d013e 100644
--- a/src/main/java/org/traccar/web/WebServer.java
+++ b/src/main/java/org/traccar/web/WebServer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,6 +46,7 @@ import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.Context;
+import org.traccar.LifecycleObject;
import org.traccar.Main;
import org.traccar.api.DateParameterConverterProvider;
import org.traccar.config.Config;
@@ -69,7 +70,7 @@ import java.io.Writer;
import java.net.InetSocketAddress;
import java.util.EnumSet;
-public class WebServer {
+public class WebServer implements LifecycleObject {
private static final Logger LOGGER = LoggerFactory.getLogger(WebServer.class);
@@ -238,6 +239,7 @@ public class WebServer {
}
}
+ @Override
public void start() {
try {
server.start();
@@ -246,6 +248,7 @@ public class WebServer {
}
}
+ @Override
public void stop() {
try {
server.stop();
diff --git a/src/test/java/org/traccar/protocol/EnvotechProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/EnvotechProtocolDecoderTest.java
new file mode 100644
index 000000000..0809a1e9a
--- /dev/null
+++ b/src/test/java/org/traccar/protocol/EnvotechProtocolDecoderTest.java
@@ -0,0 +1,31 @@
+package org.traccar.protocol;
+
+import org.junit.Test;
+import org.traccar.ProtocolTest;
+
+public class EnvotechProtocolDecoderTest extends ProtocolTest {
+
+ @Test
+ public void testDecode() throws Exception {
+
+ var decoder = new EnvotechProtocolDecoder(null);
+
+ verifyPosition(decoder, text(
+ "$80IVM,03,E002215,E002215,110422061936,672763902,126423,0180,000000,00018600,0.0000'11042206193710406325S03966094E000118*42D6#"));
+
+ verifyPosition(decoder, text(
+ "$80SLM,62,000F,F015727,020522053200,611000000,000391,C080,000,80028900,85010000'02052205320110399426S03970217E000145*E0C2#"));
+
+ verifyPosition(decoder, text(
+ "$80SLM,02,F,AB0010,130410155921,431750216,000040,0000,,00000000,'13041015592110476673N10111459E001281*2A"),
+ position("2010-04-13 15:59:21.000", true, 4.76673, 101.11459));
+
+ verifyPosition(decoder, text(
+ "$80SLM,82,F,AB0010,130410155921,431750216,000040,0000,,00000000,'13041015592110476673N10111459E001281@B0,F,C456,038,00,M234567,,,1A2A3A4A5A6A*4E"));
+
+ verifyPosition(decoder, text(
+ "$80SLM,60,000F,F016109,290322100445,AF2463902,000406,0000,000,00018780,54000000'29032209493500406302S03966062E000348*E637"));
+
+ }
+
+}
diff --git a/src/test/java/org/traccar/protocol/GotopProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/GotopProtocolDecoderTest.java
index bae187959..373858c79 100644
--- a/src/test/java/org/traccar/protocol/GotopProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/GotopProtocolDecoderTest.java
@@ -2,6 +2,7 @@ package org.traccar.protocol;
import org.junit.Test;
import org.traccar.ProtocolTest;
+import org.traccar.model.Position;
public class GotopProtocolDecoderTest extends ProtocolTest {
@@ -12,7 +13,18 @@ public class GotopProtocolDecoderTest extends ProtocolTest {
verifyNull(decoder, text(
""));
-
+
+ verifyAttribute(decoder, text(
+ "867688033841044,ALM-B1-1,A,DATE:190622,TIME:144956,LAT:22.7193976N,LON:114.3878200E,Speed:000.1,100-22,03.72"),
+ Position.KEY_ALARM, Position.ALARM_GEOFENCE_ENTER);
+
+ verifyAttribute(decoder, text(
+ "860264050778076,CMD-KEY,V,DATE:220516,TIME:090224,LAT:48.7592616N,LON:003.4658121W,Speed:000.0,057-21,01.60"),
+ Position.KEY_ALARM, Position.ALARM_SOS);
+
+ verifyPosition(decoder, text(
+ "860264050778076,CMD-T,A,DATE:220516,TIME:100627,LAT:48.7636978N,LON:003.4652398W,Speed:051.4,077-24,01.00,WIFI:{84-a0-6e-94-42-5e&-47,b2-22-7a-56-5a-2a&-48,b4-b0-24-09-91-d4&-76,18-3c-b7-1f-da-67&-83,08-86-3b-94-78-44&-85,40-24-b2-c5-0f-5e&-87,b0-e5-ed-4e-06-61&-87,60-1d-9d-a6-d2-5d&-88,14-eb-b6-a5-55-8c&-88,40-18-b1-d7-28-54&-88},BLE:{1f-cf-4d-3c-61-bf&-91,39-82-d0-ba-34-69&-57,df-3a-31-ac-ad-72&-73,7c-d9-f4-11-0b-a0&-78,03-b5-9f-45-bd-0d&-88}"));
+
verifyNull(decoder, text(
"353327020412763,CMD-X"));
diff --git a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java
index ad65ec960..b65b6709e 100644
--- a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java
@@ -17,6 +17,10 @@ public class Gt06ProtocolDecoderTest extends ProtocolTest {
verifyNull(decoder, binary(
"78780D01086471700328358100093F040D0A"));
+ verifyAttribute(decoder, binary(
+ "7878241216040e102c22cf00915ffb04c6016300195a02d402283b00753f400571040001dda4880d0a"),
+ Position.KEY_IGNITION, false);
+
verifyNotNull(decoder, binary(
"787831241603060c231e000194620213ee00606a3413ee0060692e000000000000000000000000000000000000000000003a0cb70d0a"));
diff --git a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
index 7aaec33e7..d61dae315 100644
--- a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
@@ -14,10 +14,17 @@ public class HuabaoProtocolDecoderTest extends ProtocolTest {
verifyNull(decoder, buffer(
"(794104004140,1,001,BASE,2,TIME)"));
+ verifyPosition(decoder, binary(
+ "7E0900005A4E5DE66FBA2200C4F02204280610090002010D052B0150052C0400129009052D016B052E014F05300231BA053502000005360203B8053802000005390200AD053D0201EA05440150054604009899D90545040000001E000C00030160A85C06D1C1389C7E"));
+
verifyNull(decoder, binary(
"7E01000021013345678906000F002C012F373031313142534A2D4D3742203030303030303001D4C1423838383838B47E"));
verifyAttribute(decoder, binary(
+ "7E020000480123456789010013000000800000000301597BC506CBFF6600EB00000155210726203531010400000000EB24000C00B28986047701207027150200060089FFFFEFFF0004002D0E10000600C5FFFFFFEF697E"),
+ Position.KEY_ALARM, Position.ALARM_LOW_BATTERY);
+
+ verifyAttribute(decoder, binary(
"7e0200008e01917159043700b300000000800000030158990606ca0fd7000400000000211129111705010400000000cc14383938363037423831303230393031363239363830010d8001aa81021388820200858301148401aa8502189b8601338702007e8801338901148a0200998b1131323334353637383941424344454647488c04000200a88d0200828e0114a00b50353338662c5530323966037e"),
Position.KEY_DTCS, "P538f U029f");
diff --git a/src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java
index ab23f277a..1813a5370 100644
--- a/src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java
@@ -11,6 +11,9 @@ public class Minifinder2ProtocolDecoderTest extends ProtocolTest {
var decoder = new Minifinder2ProtocolDecoder(null);
verifyPositions(decoder, binary(
+ "AB103D0035A700000110013836373733303035333430333237390924AC5783620103C250162030CC5F0D5002FB432D00AF005A3158006D0A00000B0931EC5783620A000000"));
+
+ verifyPositions(decoder, binary(
"ab10350015ae59010110013836333932313033333836353231360924723a12610042535a182ac0f6b4f2923100c900af02215c2b9bfb5461736b4c4d53"));
verifyNull(decoder, binary(
diff --git a/src/test/java/org/traccar/protocol/RuptelaProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/RuptelaProtocolDecoderTest.java
index 64ac6a57e..eca7518a7 100644
--- a/src/test/java/org/traccar/protocol/RuptelaProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/RuptelaProtocolDecoderTest.java
@@ -10,6 +10,9 @@ public class RuptelaProtocolDecoderTest extends ProtocolTest {
var decoder = new RuptelaProtocolDecoder(null);
+ verifyNull(decoder, binary(
+ "002e000316d53d58d6020f4573303430302e30332e36382e30340000c2b3090d0e950000827b000003e80000003c003c1681"));
+
verifyPositions(decoder, binary(
"00800003167d765c155d01000160cd0a310000faae43f7176ee45702332b0c12000006070d05007300cfff260082008600870088000f00d7021100d801c900061d0000c500001e0e988300008900008b000002d0000c9bca720c889a0b047e00000000000000007f0000000000000000800000000000000000810000000000000000a341"));
diff --git a/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java
index 6c2d39940..cea079156 100644
--- a/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java
@@ -11,8 +11,9 @@ public class StartekProtocolDecoderTest extends ProtocolTest {
var decoder = new StartekProtocolDecoder(null);
- verifyPosition(decoder, text(
- "&&R187,860294046453690,000,0,,220105160656,A,22.994986,72.499711,15,0.9,2,222,55,121135784,404|98|147B|0000376A,24,0000001F,02,00,052E|01A3|0000|0000,1,010000|020000,,853|6|10|105|73|41|125|34|52"));
+ verifyAttribute(decoder, text(
+ "&&R187,860294046453690,000,0,,220105160656,A,22.994986,72.499711,15,0.9,2,222,55,121135784,404|98|147B|0000376A,24,0000001F,02,00,052E|01A3|0000|0000,1,010000|020000,,853|6|10|105|73|41|125|34|52"),
+ Position.KEY_FUEL_LEVEL, 52);
verifyPosition(decoder, text(
"&&o142,860262050066062,000,27,,211111070826,V,28.653435,-106.077455,0,0.0,0,151,1412,918,0|0|4708|01402D19,6,0000001A,02,00,04C0|016C|0000|0000,1,,,BB"));
diff --git a/src/test/java/org/traccar/protocol/Tk103ProtocolEncoderTest.java b/src/test/java/org/traccar/protocol/Tk103ProtocolEncoderTest.java
index c34674128..d507f3c4f 100644
--- a/src/test/java/org/traccar/protocol/Tk103ProtocolEncoderTest.java
+++ b/src/test/java/org/traccar/protocol/Tk103ProtocolEncoderTest.java
@@ -271,7 +271,7 @@ public class Tk103ProtocolEncoderTest extends ProtocolTest {
Command command = new Command();
command.setDeviceId(1);
command.setType(Command.TYPE_SOS_NUMBER);
- command.set(Command.KEY_INDEX, "0");
+ command.set(Command.KEY_INDEX, 0);
command.set(Command.KEY_PHONE, "+55555555555");
command.set(Command.KEY_DEVICE_PASSWORD, "232323");
diff --git a/src/test/java/org/traccar/protocol/WliProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/WliProtocolDecoderTest.java
index 6f86a9fd9..77184f86a 100644
--- a/src/test/java/org/traccar/protocol/WliProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/WliProtocolDecoderTest.java
@@ -13,6 +13,9 @@ public class WliProtocolDecoderTest extends ProtocolTest {
verifyNull(decoder, binary(
"0232776c693a30343930333332303332343103"));
+ verifyAttributes(decoder, binary(
+ "0231000101536c27e40001003400dbd20030363a34323a303000dbd30030352f30322f31390004002d3100060030000a00343235000b003031000c003130343232000d003336343733000e003239000f0037001000312c312c312c312c31302e3230322e33342e33312c36353533352c302c39392c39392c3235352c3235352c3235352c323535001100300013003000140033001500343237001600333700170031001800313038001900320031003000320030004500302c323031392f30352f30322c30363a34313a34312c323031392f30352f30322c30363a33353a303800f100342c302c332c302c2d312c2d3100f2003300f3003100f50038363634323530333137303639323400f600312c302c302c3431322c3000f70038343437373200f800302c3330302c302c302c302c302c2c2c2c2c2c302c3000f9003300fa00393100fb0032313100fc0032313000ff00313535363737393332300003"));
+
verifyPosition(decoder, binary(
"0231000101bba758c900010034000500ff001001258fc9013e80ed00001183350101e20006003200090030000a0032000b003331000c0031000d00343438000e003530000f003100100031303800130032001b003134001c0033392c33352c32382c33382c34302c33372c33332c33382c33352c34322c33372c3335001d003130001e0038002300343235002400303100250031303432320026003336343733002700323800280037002900312c312c312c312c31302e3232352e3135312e3230342c36353533352c302c39392c39392c3235352c3235352c3235352c323535002a0030002c0030003000300032003000330031003400ff001c0214130502061b0101258fc9013e80ed000001e2000000000000004d004500302c323031392f30352f30322c30363a33333a30302c323031392f30352f30322c30363a32363a3230005a003000f100352c302c342c302c2d312c2d3100f2003300f3003100f50038363634323530333137303639323400f600312c302c302c3431322c3000f70038343437373200f80032312c31312c302c302c302c302c2c2c2c2c2c302c3000f9003300fa00393100fb0032313100fc0032313000ff00313535363737383533340003"));