aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/org/traccar/BasePipelineFactory.java18
-rw-r--r--src/main/java/org/traccar/BaseProtocol.java12
-rw-r--r--src/main/java/org/traccar/BaseProtocolPoller.java55
-rw-r--r--src/main/java/org/traccar/MainModule.java5
-rw-r--r--src/main/java/org/traccar/Protocol.java2
-rw-r--r--src/main/java/org/traccar/ServerManager.java19
-rw-r--r--src/main/java/org/traccar/TrackerClient.java125
-rw-r--r--src/main/java/org/traccar/TrackerConnector.java32
-rw-r--r--src/main/java/org/traccar/TrackerServer.java57
-rw-r--r--src/main/java/org/traccar/api/resource/PositionResource.java3
-rw-r--r--src/main/java/org/traccar/api/resource/ReportResource.java12
-rw-r--r--src/main/java/org/traccar/config/Keys.java38
-rw-r--r--src/main/java/org/traccar/database/PermissionsManager.java16
-rw-r--r--src/main/java/org/traccar/geocoder/GeoapifyGeocoder.java81
-rw-r--r--src/main/java/org/traccar/handler/FilterHandler.java2
-rw-r--r--src/main/java/org/traccar/handler/OpenChannelHandler.java14
-rw-r--r--src/main/java/org/traccar/model/Calendar.java2
-rw-r--r--src/main/java/org/traccar/model/Server.java12
-rw-r--r--src/main/java/org/traccar/model/User.java10
-rw-r--r--src/main/java/org/traccar/protocol/ArmoliProtocol.java40
-rw-r--r--src/main/java/org/traccar/protocol/ArmoliProtocolDecoder.java151
-rw-r--r--src/main/java/org/traccar/protocol/ArmoliProtocolPoller.java35
-rw-r--r--src/main/java/org/traccar/protocol/Dsf22ProtocolDecoder.java13
-rw-r--r--src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java20
-rw-r--r--src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java62
-rw-r--r--src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java21
-rw-r--r--src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java9
-rw-r--r--src/main/java/org/traccar/protocol/H02ProtocolDecoder.java34
-rw-r--r--src/main/java/org/traccar/protocol/H02ProtocolEncoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java17
-rw-r--r--src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java25
-rw-r--r--src/main/java/org/traccar/protocol/MiniFinderProtocol.java4
-rw-r--r--src/main/java/org/traccar/protocol/OrbcommProtocol.java40
-rw-r--r--src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java121
-rw-r--r--src/main/java/org/traccar/protocol/OrbcommProtocolPoller.java74
-rw-r--r--src/main/java/org/traccar/protocol/S168ProtocolDecoder.java5
-rw-r--r--src/main/java/org/traccar/protocol/StartekProtocolDecoder.java30
-rw-r--r--src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java97
-rw-r--r--src/main/java/org/traccar/protocol/T800xProtocolDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/TotemProtocolEncoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/WatchProtocolDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/WondexProtocolEncoder.java2
42 files changed, 1209 insertions, 114 deletions
diff --git a/src/main/java/org/traccar/BasePipelineFactory.java b/src/main/java/org/traccar/BasePipelineFactory.java
index c9f3a2346..89ef76a80 100644
--- a/src/main/java/org/traccar/BasePipelineFactory.java
+++ b/src/main/java/org/traccar/BasePipelineFactory.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.
@@ -54,12 +54,12 @@ import java.util.Map;
public abstract class BasePipelineFactory extends ChannelInitializer<Channel> {
- private final TrackerServer server;
+ private final TrackerConnector connector;
private final String protocol;
private int timeout;
- public BasePipelineFactory(TrackerServer server, String protocol) {
- this.server = server;
+ public BasePipelineFactory(TrackerConnector connector, String protocol) {
+ this.connector = connector;
this.protocol = protocol;
timeout = Context.getConfig().getInteger(Keys.PROTOCOL_TIMEOUT.withPrefix(protocol));
if (timeout == 0) {
@@ -67,10 +67,12 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> {
}
}
+ protected abstract void addTransportHandlers(PipelineBuilder pipeline);
+
protected abstract void addProtocolHandlers(PipelineBuilder pipeline);
@SafeVarargs
- private final void addHandlers(ChannelPipeline pipeline, Class<? extends ChannelHandler>... handlerClasses) {
+ private void addHandlers(ChannelPipeline pipeline, Class<? extends ChannelHandler>... handlerClasses) {
for (Class<? extends ChannelHandler> handlerClass : handlerClasses) {
if (handlerClass != null) {
pipeline.addLast(Main.getInjector().getInstance(handlerClass));
@@ -97,10 +99,12 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> {
protected void initChannel(Channel channel) {
final ChannelPipeline pipeline = channel.pipeline();
- if (timeout > 0 && !server.isDatagram()) {
+ addTransportHandlers(pipeline::addLast);
+
+ if (timeout > 0 && !connector.isDatagram()) {
pipeline.addLast(new IdleStateHandler(timeout, 0, 0));
}
- pipeline.addLast(new OpenChannelHandler(server));
+ pipeline.addLast(new OpenChannelHandler(connector));
pipeline.addLast(new NetworkMessageHandler());
pipeline.addLast(new StandardLoggingHandler(protocol));
diff --git a/src/main/java/org/traccar/BaseProtocol.java b/src/main/java/org/traccar/BaseProtocol.java
index bd3391822..52d34dc44 100644
--- a/src/main/java/org/traccar/BaseProtocol.java
+++ b/src/main/java/org/traccar/BaseProtocol.java
@@ -35,7 +35,7 @@ public abstract class BaseProtocol implements Protocol {
private final String name;
private final Set<String> supportedDataCommands = new HashSet<>();
private final Set<String> supportedTextCommands = new HashSet<>();
- private final List<TrackerServer> serverList = new LinkedList<>();
+ private final List<TrackerConnector> connectorList = new LinkedList<>();
private StringProtocolEncoder textCommandEncoder = null;
@@ -54,12 +54,16 @@ public abstract class BaseProtocol implements Protocol {
}
protected void addServer(TrackerServer server) {
- serverList.add(server);
+ connectorList.add(server);
+ }
+
+ protected void addClient(TrackerClient client) {
+ connectorList.add(client);
}
@Override
- public Collection<TrackerServer> getServerList() {
- return serverList;
+ public Collection<TrackerConnector> getConnectorList() {
+ return connectorList;
}
public void setSupportedDataCommands(String... commands) {
diff --git a/src/main/java/org/traccar/BaseProtocolPoller.java b/src/main/java/org/traccar/BaseProtocolPoller.java
new file mode 100644
index 000000000..be6556374
--- /dev/null
+++ b/src/main/java/org/traccar/BaseProtocolPoller.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelDuplexHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.util.concurrent.Future;
+
+import java.net.SocketAddress;
+import java.util.concurrent.TimeUnit;
+
+public abstract class BaseProtocolPoller extends ChannelDuplexHandler {
+
+ private final long interval;
+ private Future<?> timeout;
+
+ public BaseProtocolPoller(long interval) {
+ this.interval = interval;
+ }
+
+ protected abstract void sendRequest(Channel channel, SocketAddress remoteAddress);
+
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ super.channelActive(ctx);
+ if (interval > 0) {
+ timeout = ctx.executor().scheduleAtFixedRate(
+ () -> sendRequest(ctx.channel(), ctx.channel().remoteAddress()), 0, interval, TimeUnit.SECONDS);
+ }
+ }
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ super.channelInactive(ctx);
+ if (timeout != null) {
+ timeout.cancel(false);
+ timeout = null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java
index 11100f66e..aaa82adfc 100644
--- a/src/main/java/org/traccar/MainModule.java
+++ b/src/main/java/org/traccar/MainModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 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.
@@ -34,6 +34,7 @@ import org.traccar.geocoder.AddressFormat;
import org.traccar.geocoder.BanGeocoder;
import org.traccar.geocoder.BingMapsGeocoder;
import org.traccar.geocoder.FactualGeocoder;
+import org.traccar.geocoder.GeoapifyGeocoder;
import org.traccar.geocoder.GeocodeFarmGeocoder;
import org.traccar.geocoder.GeocodeXyzGeocoder;
import org.traccar.geocoder.Geocoder;
@@ -197,6 +198,8 @@ public class MainModule extends AbstractModule {
return new MapboxGeocoder(key, cacheSize, addressFormat);
case "maptiler":
return new MapTilerGeocoder(key, cacheSize, addressFormat);
+ case "geoapify":
+ return new GeoapifyGeocoder(key, language, cacheSize, addressFormat);
default:
return new GoogleGeocoder(key, language, cacheSize, addressFormat);
}
diff --git a/src/main/java/org/traccar/Protocol.java b/src/main/java/org/traccar/Protocol.java
index aea69b353..bc9c99557 100644
--- a/src/main/java/org/traccar/Protocol.java
+++ b/src/main/java/org/traccar/Protocol.java
@@ -25,7 +25,7 @@ public interface Protocol {
String getName();
- Collection<TrackerServer> getServerList();
+ Collection<TrackerConnector> getConnectorList();
Collection<String> getSupportedDataCommands();
diff --git a/src/main/java/org/traccar/ServerManager.java b/src/main/java/org/traccar/ServerManager.java
index 935a821aa..0db786bdb 100644
--- a/src/main/java/org/traccar/ServerManager.java
+++ b/src/main/java/org/traccar/ServerManager.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.
@@ -22,6 +22,7 @@ import org.traccar.config.Keys;
import java.io.File;
import java.io.IOException;
import java.net.BindException;
+import java.net.ConnectException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
@@ -39,7 +40,7 @@ public class ServerManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ServerManager.class);
- private final List<TrackerServer> serverList = new LinkedList<>();
+ private final List<TrackerConnector> connectorList = new LinkedList<>();
private final Map<String, BaseProtocol> protocolList = new ConcurrentHashMap<>();
private void loadPackage(String packageName) throws IOException, URISyntaxException, ReflectiveOperationException {
@@ -75,7 +76,7 @@ public class ServerManager {
if (BaseProtocol.class.isAssignableFrom(protocolClass) && Context.getConfig().hasKey(
Keys.PROTOCOL_PORT.withPrefix(BaseProtocol.nameFromClass(protocolClass)))) {
BaseProtocol protocol = (BaseProtocol) protocolClass.getDeclaredConstructor().newInstance();
- serverList.addAll(protocol.getServerList());
+ connectorList.addAll(protocol.getConnectorList());
protocolList.put(protocol.getName(), protocol);
}
}
@@ -90,18 +91,20 @@ public class ServerManager {
}
public void start() throws Exception {
- for (TrackerServer server: serverList) {
+ for (TrackerConnector connector: connectorList) {
try {
- server.start();
+ connector.start();
} catch (BindException e) {
- LOGGER.warn("Port {} is disabled due to conflict", server.getPort());
+ LOGGER.warn("Port disabled due to conflict", e);
+ } catch (ConnectException e) {
+ LOGGER.warn("Connection failed", e);
}
}
}
public void stop() {
- for (TrackerServer server: serverList) {
- server.stop();
+ for (TrackerConnector connector: connectorList) {
+ connector.stop();
}
GlobalTimer.release();
}
diff --git a/src/main/java/org/traccar/TrackerClient.java b/src/main/java/org/traccar/TrackerClient.java
new file mode 100644
index 000000000..dda02f909
--- /dev/null
+++ b/src/main/java/org/traccar/TrackerClient.java
@@ -0,0 +1,125 @@
+/*
+ * 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;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.group.ChannelGroup;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import io.netty.util.concurrent.GlobalEventExecutor;
+import org.traccar.config.Keys;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import java.util.concurrent.TimeUnit;
+
+public abstract class TrackerClient implements TrackerConnector {
+
+ private final boolean secure;
+ private final long interval;
+
+ private final Bootstrap bootstrap;
+
+ private final int port;
+ private final String address;
+ private final String[] devices;
+
+ private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
+
+ @Override
+ public boolean isDatagram() {
+ return false;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return secure;
+ }
+
+ public TrackerClient(String protocol) {
+
+ secure = Context.getConfig().getBoolean(Keys.PROTOCOL_SSL.withPrefix(protocol));
+ interval = Context.getConfig().getLong(Keys.PROTOCOL_INTERVAL.withPrefix(protocol));
+ address = Context.getConfig().getString(Keys.PROTOCOL_ADDRESS.withPrefix(protocol));
+ port = Context.getConfig().getInteger(Keys.PROTOCOL_PORT.withPrefix(protocol), secure ? 443 : 80);
+ devices = Context.getConfig().getString(Keys.PROTOCOL_DEVICES.withPrefix(protocol)).split("[, ]");
+
+ BasePipelineFactory pipelineFactory = new BasePipelineFactory(this, protocol) {
+ @Override
+ protected void addTransportHandlers(PipelineBuilder pipeline) {
+ try {
+ if (isSecure()) {
+ SSLEngine engine = SSLContext.getDefault().createSSLEngine();
+ engine.setUseClientMode(true);
+ pipeline.addLast(new SslHandler(engine));
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline) {
+ try {
+ TrackerClient.this.addProtocolHandlers(pipeline);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+
+ bootstrap = new Bootstrap()
+ .group(EventLoopGroupFactory.getWorkerGroup())
+ .channel(NioSocketChannel.class)
+ .handler(pipelineFactory);
+ }
+
+ protected abstract void addProtocolHandlers(PipelineBuilder pipeline) throws Exception;
+
+ public String[] getDevices() {
+ return devices;
+ }
+
+ @Override
+ public ChannelGroup getChannelGroup() {
+ return channelGroup;
+ }
+
+ @Override
+ public void start() throws Exception {
+ bootstrap.connect(address, port)
+ .syncUninterruptibly().channel().closeFuture().addListener(new GenericFutureListener<>() {
+ @Override
+ public void operationComplete(Future<? super Void> future) {
+ if (interval > 0) {
+ GlobalEventExecutor.INSTANCE.schedule(() -> {
+ bootstrap.connect(address, port)
+ .syncUninterruptibly().channel().closeFuture().addListener(this);
+ }, interval, TimeUnit.SECONDS);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void stop() {
+ channelGroup.close().awaitUninterruptibly();
+ }
+
+}
diff --git a/src/main/java/org/traccar/TrackerConnector.java b/src/main/java/org/traccar/TrackerConnector.java
new file mode 100644
index 000000000..9e2d27ae5
--- /dev/null
+++ b/src/main/java/org/traccar/TrackerConnector.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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;
+
+import io.netty.channel.group.ChannelGroup;
+
+public interface TrackerConnector {
+
+ boolean isDatagram();
+
+ boolean isSecure();
+
+ ChannelGroup getChannelGroup();
+
+ void start() throws Exception;
+
+ void stop();
+
+}
diff --git a/src/main/java/org/traccar/TrackerServer.java b/src/main/java/org/traccar/TrackerServer.java
index 59ba123e2..8e2fce616 100644
--- a/src/main/java/org/traccar/TrackerServer.java
+++ b/src/main/java/org/traccar/TrackerServer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2018 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.
@@ -23,28 +23,58 @@ import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.traccar.config.Keys;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
import java.net.InetSocketAddress;
-public abstract class TrackerServer {
+public abstract class TrackerServer implements TrackerConnector {
private final boolean datagram;
+ private final boolean secure;
+
+ @SuppressWarnings("rawtypes")
private final AbstractBootstrap bootstrap;
+ private final int port;
+ private final String address;
+
+ private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
+
+ @Override
public boolean isDatagram() {
return datagram;
}
+ @Override
+ public boolean isSecure() {
+ return secure;
+ }
+
public TrackerServer(boolean datagram, String protocol) {
this.datagram = datagram;
+ secure = Context.getConfig().getBoolean(Keys.PROTOCOL_SSL.withPrefix(protocol));
address = Context.getConfig().getString(Keys.PROTOCOL_ADDRESS.withPrefix(protocol));
port = Context.getConfig().getInteger(Keys.PROTOCOL_PORT.withPrefix(protocol));
BasePipelineFactory pipelineFactory = new BasePipelineFactory(this, protocol) {
@Override
+ protected void addTransportHandlers(PipelineBuilder pipeline) {
+ try {
+ if (isSecure()) {
+ SSLEngine engine = SSLContext.getDefault().createSSLEngine();
+ pipeline.addLast(new SslHandler(engine));
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
protected void addProtocolHandlers(PipelineBuilder pipeline) {
TrackerServer.this.addProtocolHandlers(pipeline);
}
@@ -52,14 +82,14 @@ public abstract class TrackerServer {
if (datagram) {
- this.bootstrap = new Bootstrap()
+ bootstrap = new Bootstrap()
.group(EventLoopGroupFactory.getWorkerGroup())
.channel(NioDatagramChannel.class)
.handler(pipelineFactory);
} else {
- this.bootstrap = new ServerBootstrap()
+ bootstrap = new ServerBootstrap()
.group(EventLoopGroupFactory.getBossGroup(), EventLoopGroupFactory.getWorkerGroup())
.channel(NioServerSocketChannel.class)
.childHandler(pipelineFactory);
@@ -69,32 +99,20 @@ public abstract class TrackerServer {
protected abstract void addProtocolHandlers(PipelineBuilder pipeline);
- private int port;
-
public int getPort() {
return port;
}
- public void setPort(int port) {
- this.port = port;
- }
-
- private String address;
-
public String getAddress() {
return address;
}
- public void setAddress(String address) {
- this.address = address;
- }
-
- private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
-
+ @Override
public ChannelGroup getChannelGroup() {
return channelGroup;
}
+ @Override
public void start() throws Exception {
InetSocketAddress endpoint;
if (address == null) {
@@ -103,12 +121,13 @@ public abstract class TrackerServer {
endpoint = new InetSocketAddress(address, port);
}
- Channel channel = bootstrap.bind(endpoint).sync().channel();
+ Channel channel = bootstrap.bind(endpoint).syncUninterruptibly().channel();
if (channel != null) {
getChannelGroup().add(channel);
}
}
+ @Override
public void stop() {
channelGroup.close().awaitUninterruptibly();
}
diff --git a/src/main/java/org/traccar/api/resource/PositionResource.java b/src/main/java/org/traccar/api/resource/PositionResource.java
index 998d59706..53157197b 100644
--- a/src/main/java/org/traccar/api/resource/PositionResource.java
+++ b/src/main/java/org/traccar/api/resource/PositionResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2020 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.
@@ -55,6 +55,7 @@ public class PositionResource extends BaseResource {
} else {
Context.getPermissionsManager().checkDevice(getUserId(), deviceId);
if (from != null && to != null) {
+ Context.getPermissionsManager().checkDisableReports(getUserId());
return Context.getDataManager().getPositions(deviceId, from, to);
} else {
return Collections.singleton(Context.getDeviceManager().getLastPosition(deviceId));
diff --git a/src/main/java/org/traccar/api/resource/ReportResource.java b/src/main/java/org/traccar/api/resource/ReportResource.java
index 7347bfd64..23ffaf54c 100644
--- a/src/main/java/org/traccar/api/resource/ReportResource.java
+++ b/src/main/java/org/traccar/api/resource/ReportResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
* Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -99,6 +99,7 @@ public class ReportResource extends BaseResource {
public Collection<Position> getRoute(
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException {
+ Context.getPermissionsManager().checkDisableReports(getUserId());
LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds);
return Route.getObjects(getUserId(), deviceIds, groupIds, from, to);
}
@@ -110,6 +111,7 @@ public class ReportResource extends BaseResource {
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail)
throws SQLException, IOException {
+ Context.getPermissionsManager().checkDisableReports(getUserId());
return executeReport(getUserId(), mail, stream -> {
LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds);
Route.getExcel(stream, getUserId(), deviceIds, groupIds, from, to);
@@ -122,6 +124,7 @@ public class ReportResource extends BaseResource {
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("type") final List<String> types,
@QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException {
+ Context.getPermissionsManager().checkDisableReports(getUserId());
LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds);
return Events.getObjects(getUserId(), deviceIds, groupIds, types, from, to);
}
@@ -134,6 +137,7 @@ public class ReportResource extends BaseResource {
@QueryParam("type") final List<String> types,
@QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail)
throws SQLException, IOException {
+ Context.getPermissionsManager().checkDisableReports(getUserId());
return executeReport(getUserId(), mail, stream -> {
LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds);
Events.getExcel(stream, getUserId(), deviceIds, groupIds, types, from, to);
@@ -146,6 +150,7 @@ public class ReportResource extends BaseResource {
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily)
throws SQLException {
+ Context.getPermissionsManager().checkDisableReports(getUserId());
LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds);
return Summary.getObjects(getUserId(), deviceIds, groupIds, from, to, daily);
}
@@ -158,6 +163,7 @@ public class ReportResource extends BaseResource {
@QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily,
@QueryParam("mail") boolean mail)
throws SQLException, IOException {
+ Context.getPermissionsManager().checkDisableReports(getUserId());
return executeReport(getUserId(), mail, stream -> {
LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds);
Summary.getExcel(stream, getUserId(), deviceIds, groupIds, from, to, daily);
@@ -170,6 +176,7 @@ public class ReportResource extends BaseResource {
public Collection<TripReport> getTrips(
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException {
+ Context.getPermissionsManager().checkDisableReports(getUserId());
LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds);
return Trips.getObjects(getUserId(), deviceIds, groupIds, from, to);
}
@@ -181,6 +188,7 @@ public class ReportResource extends BaseResource {
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail)
throws SQLException, IOException {
+ Context.getPermissionsManager().checkDisableReports(getUserId());
return executeReport(getUserId(), mail, stream -> {
LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds);
Trips.getExcel(stream, getUserId(), deviceIds, groupIds, from, to);
@@ -193,6 +201,7 @@ public class ReportResource extends BaseResource {
public Collection<StopReport> getStops(
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException {
+ Context.getPermissionsManager().checkDisableReports(getUserId());
LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds);
return Stops.getObjects(getUserId(), deviceIds, groupIds, from, to);
}
@@ -204,6 +213,7 @@ public class ReportResource extends BaseResource {
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail)
throws SQLException, IOException {
+ Context.getPermissionsManager().checkDisableReports(getUserId());
return executeReport(getUserId(), mail, stream -> {
LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds);
Stops.getExcel(stream, getUserId(), deviceIds, groupIds, from, to);
diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java
index e8e0ff207..cb3bd4de8 100644
--- a/src/main/java/org/traccar/config/Keys.java
+++ b/src/main/java/org/traccar/config/Keys.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.
@@ -38,6 +38,28 @@ public final class Keys {
Collections.singletonList(KeyType.GLOBAL));
/**
+ * List of devices for polling protocols. List should contain unique ids separated by commas. Used only for polling
+ * protocols.
+ */
+ public static final ConfigSuffix<String> PROTOCOL_DEVICES = new ConfigSuffix<>(
+ ".devices",
+ Collections.singletonList(KeyType.GLOBAL));
+
+ /**
+ * Polling interval in seconds. Used only for polling protocols.
+ */
+ public static final ConfigSuffix<Long> PROTOCOL_INTERVAL = new ConfigSuffix<>(
+ ".interval",
+ Collections.singletonList(KeyType.GLOBAL));
+
+ /**
+ * Enable SSL support for the protocol. Not all protocols support this.
+ */
+ public static final ConfigSuffix<Boolean> PROTOCOL_SSL = new ConfigSuffix<>(
+ ".ssl",
+ Collections.singletonList(KeyType.GLOBAL));
+
+ /**
* Connection timeout value in seconds. Because sometimes there is no way to detect lost TCP connection old
* connections stay in open state. On most systems there is a limit on number of open connection, so this leads to
* problems with establishing new connections when number of devices is high or devices data connections are
@@ -176,6 +198,20 @@ public final class Keys {
Collections.singletonList(KeyType.GLOBAL));
/**
+ * ORBCOMM API access id.
+ */
+ public static final ConfigKey<String> ORBCOMM_ACCESS_ID = new ConfigKey<>(
+ "orbcomm.accessId",
+ Collections.singletonList(KeyType.GLOBAL));
+
+ /**
+ * ORBCOMM API password.
+ */
+ public static final ConfigKey<String> ORBCOMM_PASSWORD = new ConfigKey<>(
+ "orbcomm.password",
+ Collections.singletonList(KeyType.GLOBAL));
+
+ /**
* Skip device connection session cache. Global configuration.
*/
public static final ConfigKey<Boolean> DECODER_IGNORE_SESSIONS_CACHE = new ConfigKey<>(
diff --git a/src/main/java/org/traccar/database/PermissionsManager.java b/src/main/java/org/traccar/database/PermissionsManager.java
index 32464cf90..ab841a521 100644
--- a/src/main/java/org/traccar/database/PermissionsManager.java
+++ b/src/main/java/org/traccar/database/PermissionsManager.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.
@@ -274,6 +274,11 @@ public class PermissionsManager {
return user != null && user.getLimitCommands();
}
+ public boolean getUserDisableReport(long userId) {
+ User user = getUser(userId);
+ return user != null && user.getDisableReports();
+ }
+
public void checkReadonly(long userId) throws SecurityException {
if (!getUserAdmin(userId) && (server.getReadonly() || getUserReadonly(userId))) {
throw new SecurityException("Account is readonly");
@@ -292,6 +297,12 @@ public class PermissionsManager {
}
}
+ public void checkDisableReports(long userId) throws SecurityException {
+ if (!getUserAdmin(userId) && (server.getDisableReports() || getUserDisableReport(userId))) {
+ throw new SecurityException("Account has reports disabled");
+ }
+ }
+
public void checkUserDeviceCommand(long userId, long deviceId, long commandId) throws SecurityException {
if (!getUserAdmin(userId) && Context.getCommandsManager().checkDeviceCommand(deviceId, commandId)) {
throw new SecurityException("Command can not be sent to this device");
@@ -326,7 +337,8 @@ public class PermissionsManager {
if (before.getReadonly() != after.getReadonly()
|| before.getDeviceReadonly() != after.getDeviceReadonly()
|| before.getDisabled() != after.getDisabled()
- || before.getLimitCommands() != after.getLimitCommands()) {
+ || before.getLimitCommands() != after.getLimitCommands()
+ || before.getDisableReports() != after.getDisableReports()) {
if (userId == after.getId()) {
checkAdmin(userId);
}
diff --git a/src/main/java/org/traccar/geocoder/GeoapifyGeocoder.java b/src/main/java/org/traccar/geocoder/GeoapifyGeocoder.java
new file mode 100644
index 000000000..ef0e4c8bd
--- /dev/null
+++ b/src/main/java/org/traccar/geocoder/GeoapifyGeocoder.java
@@ -0,0 +1,81 @@
+/*
+ * 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.geocoder;
+
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+
+public class GeoapifyGeocoder extends JsonGeocoder {
+
+ private static String formatUrl(String key, String language) {
+ String url = "https://api.geoapify.com/v1/geocode/reverse?format=json&lat=%f&lon=%f";
+ if (key != null) {
+ url += "&apiKey=" + key;
+ }
+ if (language != null) {
+ url += "&lang=" + language;
+ }
+ return url;
+ }
+
+ public GeoapifyGeocoder(String key, String language, int cacheSize, AddressFormat addressFormat) {
+ super(formatUrl(key, language), cacheSize, addressFormat);
+ }
+
+ @Override
+ public Address parseAddress(JsonObject json) {
+ JsonArray results = json.getJsonArray("results");
+ if (results.size() > 0) {
+ JsonObject result = results.getJsonObject(0);
+
+ Address address = new Address();
+
+ if (json.containsKey("formatted")) {
+ address.setFormattedAddress(json.getString("formatted"));
+ }
+
+ if (result.containsKey("housenumber")) {
+ address.setHouse(result.getString("housenumber"));
+ }
+ if (result.containsKey("street")) {
+ address.setStreet(result.getString("street"));
+ }
+ if (result.containsKey("suburb")) {
+ address.setSuburb(result.getString("suburb"));
+ }
+ if (result.containsKey("city")) {
+ address.setSettlement(result.getString("city"));
+ }
+ if (result.containsKey("district")) {
+ address.setDistrict(result.getString("district"));
+ }
+ if (result.containsKey("state")) {
+ address.setState(result.getString("state"));
+ }
+ if (result.containsKey("country_code")) {
+ address.setCountry(result.getString("country_code").toUpperCase());
+ }
+ if (result.containsKey("postcode")) {
+ address.setPostcode(result.getString("postcode"));
+ }
+
+ return address;
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/traccar/handler/FilterHandler.java b/src/main/java/org/traccar/handler/FilterHandler.java
index 049512765..2b850c755 100644
--- a/src/main/java/org/traccar/handler/FilterHandler.java
+++ b/src/main/java/org/traccar/handler/FilterHandler.java
@@ -181,7 +181,7 @@ public class FilterHandler extends BaseDataHandler {
} else {
preceding = getLastReceivedPosition(deviceId);
}
- if (filterDuplicate(position, preceding)) {
+ if (filterDuplicate(position, preceding) && !skipLimit(position, preceding) && !skipAttributes(position)) {
filterType.append("Duplicate ");
}
if (filterStatic(position) && !skipLimit(position, preceding) && !skipAttributes(position)) {
diff --git a/src/main/java/org/traccar/handler/OpenChannelHandler.java b/src/main/java/org/traccar/handler/OpenChannelHandler.java
index d09d617ab..e416f35ae 100644
--- a/src/main/java/org/traccar/handler/OpenChannelHandler.java
+++ b/src/main/java/org/traccar/handler/OpenChannelHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,26 +17,26 @@ package org.traccar.handler;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
-import org.traccar.TrackerServer;
+import org.traccar.TrackerConnector;
public class OpenChannelHandler extends ChannelDuplexHandler {
- private final TrackerServer server;
+ private final TrackerConnector connector;
- public OpenChannelHandler(TrackerServer server) {
- this.server = server;
+ public OpenChannelHandler(TrackerConnector connector) {
+ this.connector = connector;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
- server.getChannelGroup().add(ctx.channel());
+ connector.getChannelGroup().add(ctx.channel());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
- server.getChannelGroup().remove(ctx.channel());
+ connector.getChannelGroup().remove(ctx.channel());
}
}
diff --git a/src/main/java/org/traccar/model/Calendar.java b/src/main/java/org/traccar/model/Calendar.java
index 1010325b6..0b45ca6c8 100644
--- a/src/main/java/org/traccar/model/Calendar.java
+++ b/src/main/java/org/traccar/model/Calendar.java
@@ -20,7 +20,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.filter.Filter;
-import net.fortuna.ical4j.filter.PeriodRule;
+import net.fortuna.ical4j.filter.predicate.PeriodRule;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.Period;
import net.fortuna.ical4j.model.component.CalendarComponent;
diff --git a/src/main/java/org/traccar/model/Server.java b/src/main/java/org/traccar/model/Server.java
index 7bdb53b22..03d087cac 100644
--- a/src/main/java/org/traccar/model/Server.java
+++ b/src/main/java/org/traccar/model/Server.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 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.
@@ -152,6 +152,16 @@ public class Server extends ExtendedModel {
this.limitCommands = limitCommands;
}
+ private boolean disableReports;
+
+ public boolean getDisableReports() {
+ return disableReports;
+ }
+
+ public void setDisableReports(boolean disableReports) {
+ this.disableReports = disableReports;
+ }
+
private String poiLayer;
public String getPoiLayer() {
diff --git a/src/main/java/org/traccar/model/User.java b/src/main/java/org/traccar/model/User.java
index 976b6aac0..359bdc2c2 100644
--- a/src/main/java/org/traccar/model/User.java
+++ b/src/main/java/org/traccar/model/User.java
@@ -224,6 +224,16 @@ public class User extends ExtendedModel {
private String poiLayer;
+ private boolean disableReports;
+
+ public boolean getDisableReports() {
+ return disableReports;
+ }
+
+ public void setDisableReports(boolean disableReports) {
+ this.disableReports = disableReports;
+ }
+
public String getPoiLayer() {
return poiLayer;
}
diff --git a/src/main/java/org/traccar/protocol/ArmoliProtocol.java b/src/main/java/org/traccar/protocol/ArmoliProtocol.java
new file mode 100644
index 000000000..5f36012af
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/ArmoliProtocol.java
@@ -0,0 +1,40 @@
+/*
+ * 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 ArmoliProtocol extends BaseProtocol {
+
+ public ArmoliProtocol() {
+ addServer(new TrackerServer(false, getName()) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline) {
+ pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ";;", ";\r", ";"));
+ pipeline.addLast(new StringEncoder());
+ pipeline.addLast(new StringDecoder());
+ pipeline.addLast(new ArmoliProtocolDecoder(ArmoliProtocol.this));
+ pipeline.addLast(new ArmoliProtocolPoller(ArmoliProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/ArmoliProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArmoliProtocolDecoder.java
new file mode 100644
index 000000000..50af039d6
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/ArmoliProtocolDecoder.java
@@ -0,0 +1,151 @@
+/*
+ * 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.NetworkMessage;
+import org.traccar.Protocol;
+import org.traccar.helper.ObdDecoder;
+import org.traccar.helper.Parser;
+import org.traccar.helper.PatternBuilder;
+import org.traccar.helper.UnitsConverter;
+import org.traccar.model.Position;
+
+import java.net.SocketAddress;
+import java.util.regex.Pattern;
+
+public class ArmoliProtocolDecoder extends BaseProtocolDecoder {
+
+ public ArmoliProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ private static final Pattern PATTERN = new PatternBuilder()
+ .text("[M") // start
+ .number("(d{15})") // imei
+ .number("(dd)(dd)(dd)") // date (ddmmyy)
+ .number("(dd)(dd)(dd)") // time (hhmmss)
+ .number("([NS])(dd.d{6})") // latitude
+ .number("([EW])(ddd.d{6})") // longitude
+ .number("(d)") // valid
+ .number("(x)") // satellites
+ .number("(xx)") // speed
+ .number("(ddd)") // course
+ .number("(xxx)") // adc 1
+ .number("(xxx)") // adc 2
+ .number("(xx)") // status
+ .number("(xx)") // max speed
+ .number("(x{6})") // distance
+ .number("(dd)?") // hdop
+ .number("x{4}") // idle
+ .number(":(x+)").optional() // alarms
+ .number("G(x{6})").optional() // g-sensor
+ .number("H(x{3})").optional() // power
+ .number("E(x{3})").optional() // battery
+ .number("!(x+)").optional() // driver
+ .expression("@A([>0-9A-F]+)").optional() // obd
+ .any()
+ .compile();
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ String sentence = (String) msg;
+ char type = sentence.charAt(1);
+
+ Position position = new Position(getProtocolName());
+ DeviceSession deviceSession;
+
+ if (type != 'M') {
+ if (type == 'W') {
+ deviceSession = getDeviceSession(channel, remoteAddress);
+ if (deviceSession != null) {
+ position.setDeviceId(deviceSession.getDeviceId());
+ getLastLocation(position, null);
+ position.set(
+ Position.KEY_RESULT,
+ sentence.substring(sentence.indexOf(',') + 1, sentence.length() - 1));
+ return position;
+ }
+ } else if (channel != null && (type == 'Q' || type == 'L')) {
+ channel.writeAndFlush(new NetworkMessage("[TX,];;", remoteAddress));
+ }
+ return null;
+ }
+
+ Parser parser = new Parser(PATTERN, (String) msg);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS));
+ position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG));
+ position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG));
+ position.setValid(parser.nextInt() > 0);
+
+ position.set(Position.KEY_SATELLITES, parser.nextHexInt());
+
+ position.setSpeed(UnitsConverter.knotsFromKph(parser.nextHexInt()));
+ position.setCourse(parser.nextInt());
+
+ position.set(Position.PREFIX_ADC + 1, parser.nextHexInt() / 27.0 * 1000);
+ position.set(Position.PREFIX_ADC + 1, parser.nextHexInt() / 27.0 * 1000);
+ position.set(Position.KEY_STATUS, parser.nextHexInt());
+ position.set("maxSpeed", parser.nextHexInt());
+ position.set(Position.KEY_ODOMETER, parser.nextHexInt());
+
+ if (parser.hasNext()) {
+ position.set(Position.KEY_HDOP, parser.nextInt() * 0.1);
+ }
+ if (parser.hasNext()) {
+ position.set("alarms", parser.next());
+ }
+ if (parser.hasNext()) {
+ position.set(Position.KEY_G_SENSOR, parser.next());
+ }
+ if (parser.hasNext()) {
+ position.set(Position.KEY_POWER, parser.nextHexInt() * 0.01);
+ }
+ if (parser.hasNext()) {
+ position.set(Position.KEY_BATTERY, parser.nextHexInt() * 0.01);
+ }
+ if (parser.hasNext()) {
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next());
+ }
+ if (parser.hasNext()) {
+ String[] values = parser.next().split(">");
+ for (int i = 1; i < values.length; i++) {
+ String value = values[i];
+ position.add(ObdDecoder.decodeData(
+ Integer.parseInt(value.substring(4, 6), 16),
+ Long.parseLong(value.substring(6), 16), true));
+ }
+ }
+
+ return position;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/ArmoliProtocolPoller.java b/src/main/java/org/traccar/protocol/ArmoliProtocolPoller.java
new file mode 100644
index 000000000..f7bb9f593
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/ArmoliProtocolPoller.java
@@ -0,0 +1,35 @@
+/*
+ * 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.BaseProtocolPoller;
+import org.traccar.Protocol;
+
+import java.net.SocketAddress;
+
+public class ArmoliProtocolPoller extends BaseProtocolPoller {
+
+ public ArmoliProtocolPoller(Protocol protocol) {
+ super(180000);
+ }
+
+ @Override
+ protected void sendRequest(Channel channel, SocketAddress remoteAddress) {
+ channel.writeAndFlush("[TX,];;");
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/Dsf22ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Dsf22ProtocolDecoder.java
index d5a9df7bc..3ef960f12 100644
--- a/src/main/java/org/traccar/protocol/Dsf22ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Dsf22ProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package org.traccar.protocol;
import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
@@ -46,7 +45,7 @@ public class Dsf22ProtocolDecoder extends BaseProtocolDecoder {
buf.skipBytes(2); // header
- String id = ByteBufUtil.hexDump(buf.readSlice(2));
+ String id = String.valueOf(buf.readUnsignedShortLE());
DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id);
if (deviceSession == null) {
return null;
@@ -61,12 +60,12 @@ public class Dsf22ProtocolDecoder extends BaseProtocolDecoder {
position.setDeviceId(deviceSession.getDeviceId());
position.setValid(true);
- position.setLatitude(buf.readInt());
- position.setLongitude(buf.readInt());
- position.setTime(new Date(946684800000L + buf.readUnsignedInt()));
+ position.setLatitude(buf.readIntLE() / 10000000.0);
+ position.setLongitude(buf.readIntLE() / 10000000.0);
+ position.setTime(new Date(buf.readUnsignedIntLE() * 1000));
position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte()));
- position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShort() * 0.001);
+ position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShortLE() * 0.001);
int status = buf.readUnsignedByte();
position.set(Position.KEY_IGNITION, BitUtil.check(status, 0));
diff --git a/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java
index 8fe12fe69..9856ad999 100644
--- a/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2014 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -218,6 +218,24 @@ public class EelinkProtocolDecoder extends BaseProtocolDecoder {
buf.skipBytes(7); // bss2
}
+ if (BitUtil.check(flags, 7)) {
+ buf.readUnsignedByte(); // radio access technology
+ int count = buf.readUnsignedByte();
+ if (count > 0) {
+ buf.readUnsignedShort(); // mcc
+ buf.readUnsignedShort(); // mnc
+ buf.readUnsignedShort(); // lac
+ buf.readUnsignedShort(); // tac
+ buf.readUnsignedInt(); // cid
+ buf.readUnsignedShort(); // ta
+ }
+ for (int i = 0; i < count; i++) {
+ buf.readUnsignedShort(); // physical cid
+ buf.readUnsignedShort(); // e-arfcn
+ buf.readUnsignedByte(); // rssi
+ }
+ }
+
if (type == MSG_WARNING) {
position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte()));
diff --git a/src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java
index d4d539a9e..20ff78c21 100644
--- a/src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@ import io.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
import org.traccar.DeviceSession;
import org.traccar.Protocol;
+import org.traccar.model.CellTower;
+import org.traccar.model.Network;
import org.traccar.model.Position;
import javax.json.Json;
@@ -52,7 +54,7 @@ public class FlexApiProtocolDecoder extends BaseProtocolDecoder {
JsonObject payload = root.getJsonObject("payload");
- if (topic.contains("gnss")) {
+ if (topic.contains("/gnss/")) {
position.setValid(true);
@@ -72,7 +74,25 @@ public class FlexApiProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_SATELLITES, payload.getInt("gnss.num_sv"));
- } else if (topic.contains("obd")) {
+ } else if (topic.contains("/cellular1/")) {
+
+ getLastLocation(position, new Date(payload.getInt("modem1.ts") * 1000L));
+
+ position.set("imei", payload.getString("modem1.imei"));
+ position.set("imsi", payload.getString("modem1.imsi"));
+ position.set(Position.KEY_ICCID, payload.getString("modem1.iccid"));
+
+ String operator = payload.getString("modem1.operator");
+ if (!operator.isEmpty()) {
+ position.setNetwork(new Network(CellTower.from(
+ Integer.parseInt(operator.substring(0, 3)),
+ Integer.parseInt(operator.substring(3)),
+ Integer.parseInt(payload.getString("modem1.lac"), 16),
+ Integer.parseInt(payload.getString("modem1.cell_id"), 16),
+ payload.getInt("modem1.rssi"))));
+ }
+
+ } else if (topic.contains("/obd/")) {
getLastLocation(position, new Date(payload.getInt("obd.ts") * 1000L));
@@ -89,6 +109,42 @@ public class FlexApiProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_VIN, payload.getString("obd.vin"));
}
+ } else if (topic.contains("/motion/")) {
+
+ getLastLocation(position, new Date(payload.getInt("motion.ts") * 1000L));
+
+ position.set("ax", payload.getJsonNumber("motion.ax").doubleValue());
+ position.set("ay", payload.getJsonNumber("motion.ay").doubleValue());
+ position.set("az", payload.getJsonNumber("motion.az").doubleValue());
+ position.set("gx", payload.getJsonNumber("motion.gx").doubleValue());
+ position.set("gy", payload.getJsonNumber("motion.gy").doubleValue());
+ position.set("gz", payload.getJsonNumber("motion.gz").doubleValue());
+
+ } else if (topic.contains("/io/")) {
+
+ getLastLocation(position, new Date(payload.getInt("io.ts") * 1000L));
+
+ if (payload.containsKey("io.IGN")) {
+ position.set(Position.KEY_IGNITION, payload.getInt("io.IGN") > 0);
+ }
+
+ for (String key : payload.keySet()) {
+ if (key.startsWith("io.AI")) {
+ position.set(Position.PREFIX_ADC + key.substring(5), payload.getJsonNumber(key).doubleValue());
+ } else if (key.startsWith("io.DI") && !key.endsWith("_pullup")) {
+ position.set(Position.PREFIX_IN + key.substring(5), payload.getInt(key) > 0);
+ } else if (key.startsWith("io.DO")) {
+ position.set(Position.PREFIX_OUT + key.substring(5), payload.getInt(key) > 0);
+ }
+ }
+
+ } else if (topic.contains("/sysinfo/")) {
+
+ getLastLocation(position, new Date(payload.getInt("sysinfo.ts") * 1000L));
+
+ position.set("serial", payload.getString("sysinfo.serial_number"));
+ position.set(Position.KEY_VERSION_FW, payload.getString("sysinfo.firmware_version"));
+
} else {
return null;
diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
index 683ba476e..7ce0c425d 100644
--- a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
@@ -139,7 +139,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
.number("(x+)?,") // lac
.number("(x+)?,") // cid
.groupEnd()
- .number("(?:d+|(d+.d))?,") // odometer
+ .number("(?:d+|(d+.d))?,") // rssi / odometer
.compile();
private static final Pattern PATTERN_OBD = new PatternBuilder()
@@ -184,7 +184,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
.expression("(?:([0-9A-Z]{17}),)?") // vin
.expression("[^,]*,") // device name
.number("(d+)?,") // power
- .number("d{1,2},").optional() // report type
+ .number("(d{1,2}),").optional() // report type
.number("d{1,2},").optional() // count
.number("d*,").optional() // reserved
.number("(d+),").optional() // battery
@@ -208,11 +208,11 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
.number("(?:d+.?d*|Inf|NaN)?,") // fuel consumption
.number("(d+)?,") // fuel level
.or()
- .number("(d{1,7}.d)?,").optional() // odometer
- .number("(d{1,3})?,") // battery
- .or()
.number("(-?d),") // rssi
.number("(d{1,3}),") // battery
+ .or()
+ .number("(d{1,7}.d)?,").optional() // odometer
+ .number("(d{1,3})?,") // battery
.groupEnd()
.any()
.number("(dddd)(dd)(dd)") // date (yyyymmdd)
@@ -835,6 +835,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
String vin = parser.next();
Integer power = parser.nextInt();
+ Integer reportType = parser.nextInt();
Integer battery = parser.nextInt();
Parser itemParser = new Parser(PATTERN_LOCATION, parser.next());
@@ -877,12 +878,18 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_RPM, parser.nextInt());
position.set(Position.KEY_FUEL_LEVEL, parser.nextInt());
+ if (parser.hasNext(2)) {
+ if (reportType != null) {
+ position.set(Position.KEY_MOTION, BitUtil.check(reportType, 0));
+ position.set(Position.KEY_CHARGE, BitUtil.check(reportType, 1));
+ }
+ position.set(Position.KEY_RSSI, parser.nextInt());
+ position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt());
+ }
if (parser.hasNext()) {
position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000);
}
position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt());
- position.set(Position.KEY_RSSI, parser.nextInt());
- position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt());
decodeDeviceTime(position, parser);
if (ignoreFixTime) {
diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
index 0dcdab892..ec09f371b 100644
--- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
@@ -70,6 +70,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_GPS_LBS_STATUS_3 = 0x27;
public static final int MSG_LBS_MULTIPLE_1 = 0x28;
public static final int MSG_LBS_MULTIPLE_2 = 0x2E;
+ public static final int MSG_LBS_MULTIPLE_3 = 0x24;
public static final int MSG_LBS_WIFI = 0x2C;
public static final int MSG_LBS_EXTEND = 0x18;
public static final int MSG_LBS_STATUS = 0x19;
@@ -699,8 +700,9 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
return null; // space10x multi-lbs message
- } else if (type == MSG_LBS_MULTIPLE_1 || type == MSG_LBS_MULTIPLE_2 || type == MSG_LBS_EXTEND
- || type == MSG_LBS_WIFI || type == MSG_LBS_2 || type == MSG_WIFI_3 || type == MSG_WIFI_5) {
+ } 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
+ || type == MSG_WIFI_3 || type == MSG_WIFI_5) {
boolean longFormat = type == MSG_LBS_2 || type == MSG_WIFI_3 || type == MSG_WIFI_5;
@@ -726,7 +728,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
buf.readUnsignedByte(); // time leads
- if (type != MSG_LBS_MULTIPLE_1 && type != MSG_LBS_MULTIPLE_2 && type != MSG_LBS_2) {
+ if (type != MSG_LBS_MULTIPLE_1 && type != MSG_LBS_MULTIPLE_2 && type != MSG_LBS_MULTIPLE_3
+ && type != MSG_LBS_2) {
int wifiCount = buf.readUnsignedByte();
for (int i = 0; i < wifiCount; i++) {
String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:");
diff --git a/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java b/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java
index dd7141a2c..10a272bff 100644
--- a/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/H02ProtocolDecoder.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.
@@ -176,17 +176,19 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder {
.number("(d+),") // coding scheme
.groupEnd()
.groupBegin()
- .number("-(d+)-(d+.d+),") // latitude
+ .number("-(d+)-(d+.d+),([NS]),") // latitude
.or()
- .number("(d+)(dd.d+),") // latitude
+ .number("(d+)(dd.d+),([NS]),") // latitude
+ .or()
+ .number("(d+)(dd)(d{4}),([NS]),") // latitude
.groupEnd()
- .expression("([NS]),")
.groupBegin()
- .number("-(d+)-(d+.d+),") // longitude
+ .number("-(d+)-(d+.d+),([EW]),") // longitude
.or()
- .number("(d+)(dd.d+),") // longitude
+ .number("(d+)(dd.d+),([EW]),") // longitude
+ .or()
+ .number("(d+)(dd)(d{4}),([EW]),") // longitude
.groupEnd()
- .expression("([EW]),")
.number(" *(d+.?d*),") // speed
.number("(d+.?d*)?,") // course
.number("(?:d+,)?") // battery
@@ -349,19 +351,25 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder {
position.setValid(true);
}
- if (parser.hasNext(2)) {
- position.setLatitude(-parser.nextCoordinate());
+ if (parser.hasNext(3)) {
+ position.setLatitude(parser.nextCoordinate());
}
- if (parser.hasNext(2)) {
+ if (parser.hasNext(3)) {
position.setLatitude(parser.nextCoordinate());
}
+ if (parser.hasNext(4)) {
+ position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN_HEM));
+ }
- if (parser.hasNext(2)) {
- position.setLongitude(-parser.nextCoordinate());
+ if (parser.hasNext(3)) {
+ position.setLongitude(parser.nextCoordinate());
}
- if (parser.hasNext(2)) {
+ if (parser.hasNext(3)) {
position.setLongitude(parser.nextCoordinate());
}
+ if (parser.hasNext(4)) {
+ position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN_HEM));
+ }
position.setSpeed(parser.nextDouble(0));
position.setCourse(parser.nextDouble(0));
diff --git a/src/main/java/org/traccar/protocol/H02ProtocolEncoder.java b/src/main/java/org/traccar/protocol/H02ProtocolEncoder.java
index 7a765332c..8f1a8c042 100644
--- a/src/main/java/org/traccar/protocol/H02ProtocolEncoder.java
+++ b/src/main/java/org/traccar/protocol/H02ProtocolEncoder.java
@@ -6,7 +6,7 @@
* 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
+ * 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,
diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
index aa85ea061..0ae08af37 100644
--- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
@@ -34,7 +34,10 @@ import org.traccar.model.Position;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.Calendar;
+import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;
@@ -149,7 +152,19 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
ByteBuf buf = (ByteBuf) msg;
if (buf.getByte(buf.readerIndex()) == '(') {
- return decodeResult(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII));
+ String sentence = buf.toString(StandardCharsets.US_ASCII);
+ if (sentence.contains("BASE,2")) {
+ DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String response = sentence.replace("TIME", dateFormat.format(new Date()));
+ if (channel != null) {
+ channel.writeAndFlush(new NetworkMessage(
+ Unpooled.copiedBuffer(response, StandardCharsets.US_ASCII), remoteAddress));
+ }
+ return null;
+ } else {
+ return decodeResult(channel, remoteAddress, sentence);
+ }
}
buf.readUnsignedByte(); // start marker
diff --git a/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java
index 652ba3f6a..c72a742b9 100644
--- a/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org)
* Copyright 2020 Roeland Boeters (roeland@geodelta.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -114,14 +114,18 @@ public class MictrackProtocolDecoder extends BaseProtocolDecoder {
}
}
- private void decodeWifi(Network network, String data) {
+ private void decodeWifi(Network network, String data, boolean hasSsid) {
String[] values = data.split(",");
- for (int i = 0; i < values.length / 2; i++) {
- network.addWifiAccessPoint(WifiAccessPoint.from(values[i * 2], Integer.parseInt(values[i * 2 + 1])));
+ int step = hasSsid ? 3 : 2;
+ int offset = hasSsid ? 1 : 0;
+ for (int i = 0; i < values.length / step; i++) {
+ network.addWifiAccessPoint(WifiAccessPoint.from(
+ values[i * step + offset], Integer.parseInt(values[i * step + offset + 1])));
}
}
- private void decodeNetwork(Position position, String data, boolean hasWifi, boolean hasCell) throws ParseException {
+ private void decodeNetwork(
+ Position position, String data, boolean hasWifi, boolean hasSsid, boolean hasCell) throws ParseException {
int index = 0;
String[] values = data.split("\\+");
@@ -130,7 +134,7 @@ public class MictrackProtocolDecoder extends BaseProtocolDecoder {
Network network = new Network();
if (hasWifi) {
- decodeWifi(network, values[index++]);
+ decodeWifi(network, values[index++], hasSsid);
}
if (hasCell) {
@@ -231,19 +235,22 @@ public class MictrackProtocolDecoder extends BaseProtocolDecoder {
decodeLocation(position, fragments[4]);
break;
case "R1":
- decodeNetwork(position, fragments[4], true, false);
+ decodeNetwork(position, fragments[4], true, false, false);
break;
case "R2":
case "R3":
- decodeNetwork(position, fragments[4], false, true);
+ decodeNetwork(position, fragments[4], false, false, true);
break;
case "R12":
case "R13":
- decodeNetwork(position, fragments[4], true, true);
+ decodeNetwork(position, fragments[4], true, false, true);
break;
case "RH":
decodeStatus(position, fragments[4]);
break;
+ case "Y1":
+ decodeNetwork(position, fragments[4], true, true, false);
+ break;
default:
return null;
}
diff --git a/src/main/java/org/traccar/protocol/MiniFinderProtocol.java b/src/main/java/org/traccar/protocol/MiniFinderProtocol.java
index 82534ecd8..0cc9598ed 100644
--- a/src/main/java/org/traccar/protocol/MiniFinderProtocol.java
+++ b/src/main/java/org/traccar/protocol/MiniFinderProtocol.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.
@@ -41,7 +41,7 @@ public class MiniFinderProtocol extends BaseProtocol {
addServer(new TrackerServer(false, getName()) {
@Override
protected void addProtocolHandlers(PipelineBuilder pipeline) {
- pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ';'));
+ pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ";\0", ";"));
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
pipeline.addLast(new MiniFinderProtocolEncoder(MiniFinderProtocol.this));
diff --git a/src/main/java/org/traccar/protocol/OrbcommProtocol.java b/src/main/java/org/traccar/protocol/OrbcommProtocol.java
new file mode 100644
index 000000000..bdfce3b1e
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/OrbcommProtocol.java
@@ -0,0 +1,40 @@
+/*
+ * 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.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpRequestEncoder;
+import io.netty.handler.codec.http.HttpResponseDecoder;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerClient;
+
+public class OrbcommProtocol extends BaseProtocol {
+
+ public OrbcommProtocol() {
+ addClient(new TrackerClient(getName()) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline) {
+ pipeline.addLast(new HttpRequestEncoder());
+ pipeline.addLast(new HttpResponseDecoder());
+ pipeline.addLast(new HttpObjectAggregator(65535));
+ pipeline.addLast(new OrbcommProtocolDecoder(OrbcommProtocol.this));
+ pipeline.addLast(new OrbcommProtocolPoller(OrbcommProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java b/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java
new file mode 100644
index 000000000..aff722c46
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java
@@ -0,0 +1,121 @@
+/*
+ * 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 io.netty.handler.codec.http.FullHttpResponse;
+import org.traccar.BasePipelineFactory;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.DeviceSession;
+import org.traccar.Protocol;
+import org.traccar.helper.UnitsConverter;
+import org.traccar.model.Position;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonValue;
+import java.io.StringReader;
+import java.net.SocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.LinkedList;
+import java.util.TimeZone;
+
+public class OrbcommProtocolDecoder extends BaseProtocolDecoder {
+
+ public OrbcommProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ FullHttpResponse response = (FullHttpResponse) msg;
+ String content = response.content().toString(StandardCharsets.UTF_8);
+ JsonObject json = Json.createReader(new StringReader(content)).readObject();
+
+ if (channel != null && !json.getString("NextStartUTC").isEmpty()) {
+ OrbcommProtocolPoller poller =
+ BasePipelineFactory.getHandler(channel.pipeline(), OrbcommProtocolPoller.class);
+ if (poller != null) {
+ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ poller.setStartTime(dateFormat.parse(json.getString("NextStartUTC")));
+ }
+ }
+
+ if (json.get("Messages").getValueType() == JsonValue.ValueType.NULL) {
+ return null;
+ }
+
+ LinkedList<Position> positions = new LinkedList<>();
+
+ JsonArray messages = json.getJsonArray("Messages");
+ for (int i = 0; i < messages.size(); i++) {
+ JsonObject message = messages.getJsonObject(i);
+ DeviceSession deviceSession = getDeviceSession(
+ channel, remoteAddress, true, message.getJsonNumber("ID").toString());
+ if (deviceSession != null) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ position.setDeviceTime(dateFormat.parse(message.getString("MessageUTC")));
+
+ JsonArray fields = message.getJsonObject("Payload").getJsonArray("Fields");
+ for (int j = 0; j < fields.size(); j++) {
+ JsonObject field = fields.getJsonObject(j);
+ String value = field.getString("Value");
+ switch (field.getString("Name")) {
+ case "latitude":
+ position.setLatitude(Integer.parseInt(value) / 60000.0);
+ break;
+ case "longitude":
+ position.setLongitude(Integer.parseInt(value) / 60000.0);
+ break;
+ case "speed":
+ position.setSpeed(UnitsConverter.knotsFromKph(Integer.parseInt(value)));
+ break;
+ case "heading":
+ position.setCourse(Integer.parseInt(value) * 0.1);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (position.getLatitude() != 0 && position.getLongitude() != 0) {
+ position.setValid(true);
+ position.setFixTime(position.getDeviceTime());
+ } else {
+ getLastLocation(position, position.getDeviceTime());
+ }
+
+ positions.add(position);
+
+ }
+ }
+
+ return positions.isEmpty() ? null : positions;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/OrbcommProtocolPoller.java b/src/main/java/org/traccar/protocol/OrbcommProtocolPoller.java
new file mode 100644
index 000000000..6a2d7a92d
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/OrbcommProtocolPoller.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.QueryStringEncoder;
+import org.traccar.BaseProtocolPoller;
+import org.traccar.Context;
+import org.traccar.Protocol;
+import org.traccar.config.Keys;
+
+import java.net.SocketAddress;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class OrbcommProtocolPoller extends BaseProtocolPoller {
+
+ private final String accessId;
+ private final String password;
+ private final String host;
+
+ private Date startTime = new Date();
+
+ public void setStartTime(Date startTime) {
+ this.startTime = startTime;
+ }
+
+ public OrbcommProtocolPoller(Protocol protocol) {
+ super(Context.getConfig().getLong(Keys.PROTOCOL_INTERVAL.withPrefix(protocol.getName())));
+ accessId = Context.getConfig().getString(Keys.ORBCOMM_ACCESS_ID);
+ password = Context.getConfig().getString(Keys.ORBCOMM_PASSWORD);
+ host = Context.getConfig().getString(Keys.PROTOCOL_ADDRESS.withPrefix(protocol.getName()));
+ }
+
+ @Override
+ protected void sendRequest(Channel channel, SocketAddress remoteAddress) {
+
+ QueryStringEncoder encoder = new QueryStringEncoder("/GLGW/2/RestMessages.svc/JSON/get_return_messages/");
+ encoder.addParam("access_id", accessId);
+ encoder.addParam("password", password);
+
+ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ encoder.addParam("start_utc", dateFormat.format(startTime));
+
+ HttpRequest request = new DefaultFullHttpRequest(
+ HttpVersion.HTTP_1_1, HttpMethod.GET, encoder.toString(), Unpooled.buffer());
+ request.headers().add(HttpHeaderNames.HOST, host);
+ request.headers().add(HttpHeaderNames.CONTENT_LENGTH, 0);
+ channel.writeAndFlush(request);
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java b/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java
index 71aff1a65..685364483 100644
--- a/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/S168ProtocolDecoder.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.
@@ -51,6 +51,9 @@ public class S168ProtocolDecoder extends BaseProtocolDecoder {
String content = values[4];
String[] fragments = content.split(";");
for (String fragment : fragments) {
+ if (fragment.isEmpty()) {
+ continue;
+ }
int dataIndex = fragment.indexOf(':');
String type = fragment.substring(0, dataIndex);
diff --git a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java
index 042518cb2..c1869def2 100644
--- a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -70,14 +70,23 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
.number("(x+),") // outputs
.number("(x+)|") // power
.number("(x+)") // battery
- .groupBegin()
- .text("|")
- .expression("([^,]+)").optional() // adc
+ .expression("([^,]+)?") // adc
.groupBegin()
.text(",")
.number("d,") // extended
.expression("([^,]+)?,") // fuel
- .expression("([^,]+)?,?") // temperature
+ .expression("([^,]+)?") // temperature
+ .groupBegin()
+ .text(",")
+ .number("(d+)|") // rpm
+ .number("(d+)|") // engine load
+ .number("d+|") // maf flow
+ .number("d+|") // intake pressure
+ .number("d+|") // intake temperature
+ .number("(d+)|") // throttle
+ .number("(d+)|") // coolant temperature
+ .number("(d+)|") // instant fuel
+ .number("(d+)") // fuel level
.groupEnd("?")
.groupEnd("?")
.any()
@@ -181,7 +190,7 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
if (parser.hasNext()) {
String[] adc = parser.next().split("\\|");
- for (int i = 0; i < adc.length; i++) {
+ for (int i = 1; i < adc.length; i++) {
position.set(Position.PREFIX_ADC + (i + 1), Integer.parseInt(adc[i], 16) * 0.01);
}
}
@@ -208,6 +217,15 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
}
}
+ if (parser.hasNext(6)) {
+ position.set(Position.KEY_RPM, parser.nextInt());
+ position.set(Position.KEY_ENGINE_LOAD, parser.nextInt());
+ position.set(Position.KEY_THROTTLE, parser.nextInt());
+ position.set(Position.KEY_COOLANT_TEMP, parser.nextInt() - 40);
+ position.set(Position.KEY_FUEL_CONSUMPTION, parser.nextInt() * 0.1);
+ position.set(Position.KEY_FUEL_LEVEL, parser.nextInt());
+ }
+
return position;
}
diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java
index 2d00ea81e..2f8e50c5e 100644
--- a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.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.
@@ -17,6 +17,7 @@ package org.traccar.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
import org.traccar.Context;
@@ -34,6 +35,11 @@ import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
import java.util.TimeZone;
public class SuntechProtocolDecoder extends BaseProtocolDecoder {
@@ -46,6 +52,8 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
private boolean includeRpm;
private boolean includeTemp;
+ private ByteBuf crash;
+
public SuntechProtocolDecoder(Protocol protocol) {
super(protocol);
}
@@ -732,6 +740,89 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private Collection<Position> decodeCrashReport(Channel channel, SocketAddress remoteAddress, ByteBuf buf) {
+
+ if (buf.getByte(buf.readerIndex() + 3) != ';') {
+ return null;
+ }
+
+ String[] values = buf.readCharSequence(23, StandardCharsets.US_ASCII).toString().split(";");
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[1]);
+ if (deviceSession == null) {
+ return null;
+ }
+
+ int currentIndex = Integer.parseInt(values[2]);
+ int totalIndex = Integer.parseInt(values[3]);
+
+ if (crash == null) {
+ crash = Unpooled.buffer();
+ }
+
+ crash.writeBytes(buf.readSlice(buf.readableBytes() - 3));
+
+ if (currentIndex == totalIndex) {
+
+ LinkedList<Position> positions = new LinkedList<>();
+
+ Date crashTime = new DateBuilder()
+ .setDate(crash.readUnsignedByte(), crash.readUnsignedByte(), crash.readUnsignedByte())
+ .setTime(crash.readUnsignedByte(), crash.readUnsignedByte(), crash.readUnsignedByte())
+ .getDate();
+
+ List<Date> times = Arrays.asList(
+ new Date(crashTime.getTime() - 3000),
+ new Date(crashTime.getTime() - 2000),
+ new Date(crashTime.getTime() - 1000),
+ new Date(crashTime.getTime() + 1000));
+
+ for (Date time : times) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.setValid(true);
+ position.setTime(time);
+ position.setLatitude(crash.readIntLE() * 0.0000001);
+ position.setLongitude(crash.readIntLE() * 0.0000001);
+ position.setSpeed(UnitsConverter.knotsFromKph(crash.readUnsignedShort() * 0.01));
+ position.setCourse(crash.readUnsignedShort() * 0.01);
+
+ StringBuilder value = new StringBuilder("[");
+ for (int i = 0; i < 100; i++) {
+ if (value.length() > 1) {
+ value.append(",");
+ }
+ value.append("[");
+ value.append(crash.readShortLE());
+ value.append(",");
+ value.append(crash.readShortLE());
+ value.append(",");
+ value.append(crash.readShortLE());
+ value.append("]");
+ }
+ value.append("]");
+
+ position.set(Position.KEY_G_SENSOR, value.toString());
+
+ positions.add(position);
+
+ }
+
+ crash.release();
+ crash = null;
+
+ return positions;
+
+ } else {
+
+ return null;
+
+ }
+
+ }
+
@Override
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
@@ -747,7 +838,9 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
String[] values = buf.toString(StandardCharsets.US_ASCII).split(";");
prefix = values[0];
- if (prefix.length() < 5) {
+ if (prefix.equals("CRR")) {
+ return decodeCrashReport(channel, remoteAddress, buf);
+ } else if (prefix.length() < 5) {
return decodeUniversal(channel, remoteAddress, values);
} else if (prefix.endsWith("HTE")) {
return decodeTravelReport(channel, remoteAddress, values);
diff --git a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java
index b15688df0..d554c2999 100644
--- a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java
@@ -411,7 +411,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder {
if (BitUtil.check(status, 6)) {
- position.setValid(!BitUtil.check(status, 7));
+ position.setValid(true);
position.setTime(readDate(buf));
position.setAltitude(buf.readFloatLE());
position.setLongitude(buf.readFloatLE());
diff --git a/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java b/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java
index 6792d61a5..4b22ade03 100644
--- a/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java
+++ b/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java
@@ -6,7 +6,7 @@
* 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
+ * 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,
diff --git a/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java b/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java
index 4990cfd65..cf58b0fed 100644
--- a/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java
@@ -89,6 +89,8 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder {
return Position.ALARM_GEOFENCE_EXIT;
} else if (BitUtil.check(status, 2)) {
return Position.ALARM_GEOFENCE_ENTER;
+ } else if (BitUtil.check(status, 14)) {
+ return Position.ALARM_POWER_CUT;
} else if (BitUtil.check(status, 16)) {
return Position.ALARM_SOS;
} else if (BitUtil.check(status, 17)) {
diff --git a/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java b/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java
index 21f1ee321..fb213dc40 100644
--- a/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java
+++ b/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java
@@ -5,7 +5,7 @@
* 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
+ * 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,