diff options
Diffstat (limited to 'src/main/java')
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, |