diff options
51 files changed, 1388 insertions, 593 deletions
diff --git a/build.gradle b/build.gradle index 0b3f54355..25c8a32e3 100644 --- a/build.gradle +++ b/build.gradle @@ -95,7 +95,7 @@ jar { manifest { attributes( "Main-Class": "org.traccar.Main", - "Implementation-Version": "4.15", + "Implementation-Version": "5.0", "Class-Path": configurations.runtimeClasspath.files.collect { "lib/$it.name" }.join(" ")) } } @@ -21,4 +21,7 @@ <entry key='atrack.custom'>true</entry> <entry key='intellitrac.port'>6037</entry> + <!--<entry key='broadcast.address'>230.0.0.0</entry> + <entry key='broadcast.port'>5000</entry>--> + </properties> diff --git a/schema/changelog-4.16.xml b/schema/changelog-5.0.xml index adf8d4fc6..0e8474eef 100644 --- a/schema/changelog-4.16.xml +++ b/schema/changelog-5.0.xml @@ -4,9 +4,15 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd" - logicalFilePath="changelog-4.16"> + logicalFilePath="changelog-5.0"> - <changeSet author="author" id="changelog-4.16"> + <changeSet author="author" id="changelog-5.0"> + + <preConditions onFail="MARK_RAN"> + <not> + <columnExists tableName="tc_servers" columnName="disablereports" /> + </not> + </preConditions> <addColumn tableName="tc_servers"> <column name="disablereports" type="BOOLEAN" defaultValueBoolean="false" /> @@ -16,11 +22,11 @@ <column name="disablereports" type="BOOLEAN" defaultValueBoolean="false" /> </addColumn> - <renameColumn tableName="tc_orders" oldColumnName="toAddress" newColumnName="toaddresstmp" /> - <renameColumn tableName="tc_orders" oldColumnName="fromAddress" newColumnName="fromaddresstmp" /> + <renameColumn tableName="tc_orders" oldColumnName="toAddress" newColumnName="toaddresstmp" columnDataType="VARCHAR(512)" /> + <renameColumn tableName="tc_orders" oldColumnName="fromAddress" newColumnName="fromaddresstmp" columnDataType="VARCHAR(512)" /> - <renameColumn tableName="tc_orders" oldColumnName="toaddresstmp" newColumnName="toaddress" /> - <renameColumn tableName="tc_orders" oldColumnName="fromaddresstmp" newColumnName="fromaddress" /> + <renameColumn tableName="tc_orders" oldColumnName="toaddresstmp" newColumnName="toaddress" columnDataType="VARCHAR(512)" /> + <renameColumn tableName="tc_orders" oldColumnName="fromaddresstmp" newColumnName="fromaddress" columnDataType="VARCHAR(512)" /> </changeSet> diff --git a/schema/changelog-master.xml b/schema/changelog-master.xml index d79e084f6..bb2b3e2f3 100644 --- a/schema/changelog-master.xml +++ b/schema/changelog-master.xml @@ -29,6 +29,7 @@ <include file="changelog-4.11.xml" relativeToChangelogFile="true" /> <include file="changelog-4.13.xml" relativeToChangelogFile="true" /> <include file="changelog-4.15.xml" relativeToChangelogFile="true" /> - <include file="changelog-4.16.xml" relativeToChangelogFile="true" /> + + <include file="changelog-5.0.xml" relativeToChangelogFile="true" /> </databaseChangeLog> diff --git a/setup/default.xml b/setup/default.xml index 306421675..1f89ae3d8 100644 --- a/setup/default.xml +++ b/setup/default.xml @@ -12,6 +12,7 @@ <entry key='web.port'>8082</entry> <entry key='web.path'>./web</entry> + <entry key='web.sanitize'>true</entry> <entry key='web.persistSession'>false</entry> <entry key='geocoder.enable'>true</entry> @@ -283,5 +284,6 @@ <entry key='jido.port'>5237</entry> <entry key='armoli.port'>5238</entry> <entry key='teratrack.port'>5239</entry> + <entry key='envotech.port'>5240</entry> </properties> diff --git a/setup/traccar.iss b/setup/traccar.iss index 3ffab1289..64137f8d6 100644 --- a/setup/traccar.iss +++ b/setup/traccar.iss @@ -1,6 +1,6 @@ [Setup] AppName=Traccar -AppVersion=4.15 +AppVersion=5.0 DefaultDirName={pf}\Traccar OutputBaseFilename=traccar-setup ArchitecturesInstallIn64BitMode=x64 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")); diff --git a/swagger.json b/swagger.json index 9882c92d7..77100dd32 100644 --- a/swagger.json +++ b/swagger.json @@ -2,7 +2,7 @@ "openapi": "3.0.1", "info": { "title": "Traccar", - "version": "4.15", + "version": "5.0", "description": "Traccar GPS tracking server API documentation. To use the API you need to have a server instance. For testing purposes you can use one of free [demo servers](https://www.traccar.org/demo-server/). For production use you can install your own server or get a [subscription service](https://www.traccar.org/product/tracking-server/).", "contact": { "name": "Traccar Support", @@ -2891,6 +2891,10 @@ "type": "integer", "description": "Geofence Id, can be second parameter only" }, + "notificationId": { + "type": "integer", + "description": "Notification Id, can be second parameter only" + }, "calendarId": { "type": "integer", "description": "Calendar Id, can be second parameter only and only in combination with userId" diff --git a/tools/test-generator.py b/tools/test-generator.py index ca7871e1d..b8a06ac32 100755 --- a/tools/test-generator.py +++ b/tools/test-generator.py @@ -35,12 +35,14 @@ for i in range(0, len(waypoints)): lon = lon1 + (lon2 - lon1) * j / count points.append((lat, lon)) -def send(conn, lat, lon, course, speed, alarm, ignition, accuracy, rpm, fuel, driverUniqueId): - params = (('id', id), ('timestamp', int(time.time())), ('lat', lat), ('lon', lon), ('bearing', course), ('speed', speed)) +def send(conn, lat, lon, course, speed, battery, alarm, ignition, accuracy, rpm, fuel, driverUniqueId): + params = (('id', id), ('timestamp', int(time.time())), ('lat', lat), ('lon', lon), ('bearing', course), ('speed', speed), ('batt', battery)) if alarm: params = params + (('alarm', 'sos'),) if ignition: params = params + (('ignition', 'true'),) + else: + params = params + (('ignition', 'false'),) if accuracy: params = params + (('accuracy', accuracy),) if rpm: @@ -70,11 +72,12 @@ while True: (lat2, lon2) = points[(index + 1) % len(points)] speed = device_speed if (index % len(points)) != 0 else 0 alarm = (index % 10) == 0 + battery = random.randint(0, 100) ignition = (index % len(points)) != 0 accuracy = 100 if (index % 10) == 0 else 0 rpm = random.randint(500, 4000) fuel = random.randint(0, 80) driverUniqueId = driver_id if (index % len(points)) == 0 else False - send(conn, lat1, lon1, course(lat1, lon1, lat2, lon2), speed, alarm, ignition, accuracy, rpm, fuel, driverUniqueId) + send(conn, lat1, lon1, course(lat1, lon1, lat2, lon2), speed, battery, alarm, ignition, accuracy, rpm, fuel, driverUniqueId) time.sleep(period) index += 1 diff --git a/tools/test-map.py b/tools/test-map.py index c289df605..ab4466b09 100755 --- a/tools/test-map.py +++ b/tools/test-map.py @@ -38,6 +38,7 @@ conn = httplib.HTTPConnection(server) for i in range(devices): device_id = "{0:0>6}".format(i) add_device(cookie, device_id) + send_message(conn, device_id) while True: device_id = "{0:0>6}".format(random.randint(0, devices)) diff --git a/traccar-web b/traccar-web -Subproject 9e1c0b44189136ec6abf05720c698027a689fa0 +Subproject d6445273b78fef8e5a081402f6ca6eb69ea952b |