diff options
66 files changed, 1411 insertions, 175 deletions
diff --git a/build.gradle b/build.gradle index d9c70fc15..b75ad6ff4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id "java" id "checkstyle" - id "com.google.protobuf" version "0.8.16" + id "com.google.protobuf" version "0.8.18" id "org.kordamp.gradle.project-enforcer" version "0.9.0" } @@ -11,10 +11,10 @@ repositories { ext { guiceVersion = "5.0.1" - jettyVersion = "10.0.6" // jetty 11 javax to jakarta - jerseyVersion = "2.34" // jersey 3 javax to jakarta + jettyVersion = "10.0.7" // jetty 11 javax to jakarta + jerseyVersion = "2.35" // jersey 3 javax to jakarta jacksonVersion = "2.12.2" // same version as jersey-media-json-jackson dependency - protobufVersion = "3.17.3" + protobufVersion = "3.19.3" } sourceCompatibility = "11" @@ -22,7 +22,7 @@ compileJava.options.encoding = "UTF-8" jar.destinationDirectory = file("$projectDir/target") checkstyle { - toolVersion = "8.26" + toolVersion = "9.2.1" configFile = "gradle/checkstyle.xml" as File checkstyleTest.enabled = false } @@ -42,13 +42,13 @@ enforce { dependencies { implementation "commons-codec:commons-codec:1.15" - implementation "com.h2database:h2:1.4.200" - implementation "mysql:mysql-connector-java:8.0.26" - implementation "org.postgresql:postgresql:42.2.23" - implementation "com.microsoft.sqlserver:mssql-jdbc:9.4.0.jre11" - implementation "com.zaxxer:HikariCP:5.0.0" + implementation "com.h2database:h2:2.0.206" + implementation "mysql:mysql-connector-java:8.0.27" + implementation "org.postgresql:postgresql:42.3.1" + implementation "com.microsoft.sqlserver:mssql-jdbc:9.4.1.jre11" + implementation "com.zaxxer:HikariCP:5.0.1" implementation "io.netty:netty-all:4.1.66.Final" - implementation "org.slf4j:slf4j-jdk14:2.0.0-alpha4" + implementation "org.slf4j:slf4j-jdk14:2.0.0-alpha6" implementation "com.google.inject:guice:$guiceVersion" implementation "com.google.inject.extensions:guice-assistedinject:$guiceVersion" implementation "org.owasp.encoder:encoder:1.2.3" @@ -64,24 +64,24 @@ dependencies { implementation "org.glassfish.jersey.inject:jersey-hk2:$jerseyVersion" implementation "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jacksonVersion" implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr353:$jacksonVersion" - implementation "org.liquibase:liquibase-core:4.4.3" + implementation "org.liquibase:liquibase-core:4.7.0" implementation "com.sun.mail:javax.mail:1.6.2" implementation "org.jxls:jxls:2.4.7" // needs upgrade (wait for jexl 4) implementation "org.jxls:jxls-poi:1.0.16" // needs upgrade (wait for jexl 4) implementation "org.apache.velocity:velocity:1.7" implementation "org.apache.velocity:velocity-tools:2.0" implementation "org.apache.commons:commons-collections4:4.4" - implementation "org.mnode.ical4j:ical4j:3.0.29" + implementation "org.mnode.ical4j:ical4j:3.1.2" implementation "org.locationtech.spatial4j:spatial4j:0.8" - implementation "org.locationtech.jts:jts-core:1.18.1" - implementation "net.java.dev.jna:jna-platform:5.8.0" - implementation "com.github.jnr:jnr-posix:3.1.7" + implementation "org.locationtech.jts:jts-core:1.18.2" + implementation "net.java.dev.jna:jna-platform:5.10.0" + implementation "com.github.jnr:jnr-posix:3.1.15" implementation "com.google.protobuf:protobuf-java:$protobufVersion" implementation "javax.xml.bind:jaxb-api:2.3.1" implementation "com.sun.xml.bind:jaxb-core:3.0.2" implementation "com.sun.xml.bind:jaxb-impl:3.0.2" implementation "javax.activation:activation:1.1.1" - implementation 'com.amazonaws:aws-java-sdk-sns:1.12.47' + implementation "com.amazonaws:aws-java-sdk-sns:1.12.141" testImplementation "junit:junit:4.13.2" } @@ -95,7 +95,7 @@ jar { manifest { attributes( "Main-Class": "org.traccar.Main", - "Implementation-Version": "4.14", + "Implementation-Version": "4.15", "Class-Path": configurations.runtimeClasspath.files.collect { "lib/$it.name" }.join(" ")) } } diff --git a/schema/changelog-4.16.xml b/schema/changelog-4.16.xml new file mode 100644 index 000000000..0b6493f03 --- /dev/null +++ b/schema/changelog-4.16.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<databaseChangeLog + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog + http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd" + logicalFilePath="changelog-4.16"> + + <changeSet author="author" id="changelog-4.16"> + + <addColumn tableName="tc_servers"> + <column name="disablereports" type="BOOLEAN" defaultValueBoolean="false" /> + </addColumn> + + <addColumn tableName="tc_users"> + <column name="disablereports" type="BOOLEAN" defaultValueBoolean="false" /> + </addColumn> + + </changeSet> + +</databaseChangeLog> diff --git a/schema/changelog-master.xml b/schema/changelog-master.xml index 245367de3..354037cdd 100644 --- a/schema/changelog-master.xml +++ b/schema/changelog-master.xml @@ -29,5 +29,6 @@ <include file="changelog-4.11.xml" relativeToChangelogFile="true" /> <include file="changelog-4.13.xml" relativeToChangelogFile="true" /> <include file="changelog-4.15.xml" relativeToChangelogFile="true" /> + <include file="changelog-4.16.xml" relativeToChangelogFile="true" /> </databaseChangeLog> diff --git a/setup/default.xml b/setup/default.xml index fdb4cbf3b..0d8133627 100644 --- a/setup/default.xml +++ b/setup/default.xml @@ -18,6 +18,8 @@ <entry key='geocoder.type'>nominatim</entry> <entry key='geocoder.url'>https://us1.locationiq.com/v1/reverse.php</entry> <entry key='geocoder.key'>pk.689d849289c8c63708068b2ff1f63b2d</entry> + <entry key='geocoder.onRequest'>true</entry> + <entry key='geocoder.ignorePositions'>true</entry> <entry key='logger.level'>info</entry> <entry key='logger.file'>./logs/tracker-server.log</entry> @@ -309,5 +311,6 @@ <entry key='flexapi.port'>5235</entry> <entry key='dsf22.port'>5236</entry> <entry key='jido.port'>5237</entry> + <entry key='armoli.port'>5238</entry> </properties> diff --git a/setup/environment.sh b/setup/environment.sh index cb80a296a..86a50cc85 100644 --- a/setup/environment.sh +++ b/setup/environment.sh @@ -22,6 +22,6 @@ export PATH=$PATH:~/bin/Sencha/Cmd/ cd traccar/setup wget http://files.jrsoftware.org/is/5/isetup-5.5.6.exe -wget https://github.com/ojdkbuild/ojdkbuild/releases/download/java-11-openjdk-debug-11.0.12.7-1/java-11-openjdk-debug-11.0.12.7-1.windows.ojdkbuild.x86_64.zip -wget https://github.com/ojdkbuild/contrib_jdk11u-ci/releases/download/jdk-11.0.12%2B7/jdk-11.0.12-ojdkbuild-linux-x64.zip -wget https://github.com/ojdkbuild/contrib_jdk11u-arm32-ci/releases/download/jdk-11.0.12%2B7/jdk-11.0.12-ojdkbuild-linux-armhf.zip +wget https://github.com/ojdkbuild/ojdkbuild/releases/download/java-11-openjdk-11.0.13.8-1/java-11-openjdk-11.0.13.8-1.windows.ojdkbuild.x86_64.zip +wget https://github.com/ojdkbuild/contrib_jdk11u-ci/releases/download/jdk-11.0.13%2B8/jdk-11.0.13-ojdkbuild-linux-x64.zip +wget https://github.com/ojdkbuild/contrib_jdk11u-arm32-ci/releases/download/jdk-11.0.13%2B8/jdk-11.0.13-ojdkbuild-linux-armhf.zip diff --git a/setup/traccar.iss b/setup/traccar.iss index 316bc757b..3ffab1289 100644 --- a/setup/traccar.iss +++ b/setup/traccar.iss @@ -1,6 +1,6 @@ [Setup] AppName=Traccar -AppVersion=4.14 +AppVersion=4.15 DefaultDirName={pf}\Traccar OutputBaseFilename=traccar-setup ArchitecturesInstallIn64BitMode=x64 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, diff --git a/src/test/java/org/traccar/ProtocolTest.java b/src/test/java/org/traccar/ProtocolTest.java index c40a15dcc..353593c29 100644 --- a/src/test/java/org/traccar/ProtocolTest.java +++ b/src/test/java/org/traccar/ProtocolTest.java @@ -4,9 +4,11 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import org.traccar.helper.DataConverter; import org.traccar.model.CellTower; @@ -89,6 +91,10 @@ public class ProtocolTest extends BaseTest { return new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, method, url, Unpooled.buffer(), headers, new DefaultHttpHeaders()); } + protected DefaultFullHttpResponse response(ByteBuf data) { + return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, data); + } + protected void verifyNotNull(BaseProtocolDecoder decoder, Object object) throws Exception { assertNotNull(decoder.decode(null, null, object)); } diff --git a/src/test/java/org/traccar/geocoder/GeocoderTest.java b/src/test/java/org/traccar/geocoder/GeocoderTest.java index 380980d2b..9134194f2 100644 --- a/src/test/java/org/traccar/geocoder/GeocoderTest.java +++ b/src/test/java/org/traccar/geocoder/GeocoderTest.java @@ -105,9 +105,17 @@ public class GeocoderTest { @Ignore @Test public void testMapTiler() { - Geocoder geocoder = new MapTilerGeocoder("mnbnwLErpdspq13f0kC6", 0, new AddressFormat()); + Geocoder geocoder = new MapTilerGeocoder("", 0, new AddressFormat()); String address = geocoder.getAddress(40.733, -73.989, null); assertEquals("East 13th Street, New York City, New York, United States", address); } + @Ignore + @Test + public void testGeoapify() { + Geocoder geocoder = new GeoapifyGeocoder("", null, 0, new AddressFormat()); + String address = geocoder.getAddress(40.733, -73.989, null); + assertEquals("114 East 13th Street, New York, New York, US", address); + } + } diff --git a/src/test/java/org/traccar/protocol/ArmoliProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/ArmoliProtocolDecoderTest.java new file mode 100644 index 000000000..da2542b34 --- /dev/null +++ b/src/test/java/org/traccar/protocol/ArmoliProtocolDecoderTest.java @@ -0,0 +1,36 @@ +package org.traccar.protocol; + +import org.junit.Test; +import org.traccar.ProtocolTest; +import org.traccar.model.Position; + +public class ArmoliProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + var decoder = new ArmoliProtocolDecoder(null); + + verifyAttribute(decoder, text( + "[M869867039550712160821153237N41.033508E029.2697032F00036000000410006B336FFFFG458563@A6D>04410C2482>03410F56>03412F19>0441210000>034130FF>0441313A7>03410D30>04411F01B6>0341048C>04410C1C98];"), + Position.KEY_RPM, 1830L); + + verifyPosition(decoder, text( + "[M869867038698074210122125205N38.735641E035.4727751E003340000000C00000E9E07FF:106AG505283H60E]")); + + verifyAttribute(decoder, text( + "[W869867038698074,O,1234,2657,1]"), + Position.KEY_RESULT, "O,1234,2657,1"); + + verifyNull(decoder, text( + "[Q010001088610010024363698990011101070608200,05XXXXXXXXX,10.49.182.53,C,1,20,19,0]")); + + verifyPosition(decoder, text( + "[M860906041293587100122061310N40.792751E029.4313092801143000000010003513209FFGC18080H8DA#E209C4]")); + + verifyNull(decoder, text( + "[L866104027971681]")); + + } + +} diff --git a/src/test/java/org/traccar/protocol/Dsf22ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Dsf22ProtocolDecoderTest.java index 96cd78f03..4089c208c 100644 --- a/src/test/java/org/traccar/protocol/Dsf22ProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Dsf22ProtocolDecoderTest.java @@ -1,10 +1,8 @@ package org.traccar.protocol; -import org.junit.Ignore; import org.junit.Test; import org.traccar.ProtocolTest; -@Ignore public class Dsf22ProtocolDecoderTest extends ProtocolTest { @Test @@ -13,6 +11,9 @@ public class Dsf22ProtocolDecoderTest extends ProtocolTest { var decoder = new Dsf22ProtocolDecoder(null); verifyPositions(decoder, binary( + "4642a82d01c8f6aa1af1792c0c1411eb61001e0000")); + + verifyPositions(decoder, binary( "4642000101A8EE5F0ECA5FF421B33F524E32610401")); verifyPositions(decoder, binary( diff --git a/src/test/java/org/traccar/protocol/EelinkProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/EelinkProtocolDecoderTest.java index d0c683abf..c1cc3c39a 100644 --- a/src/test/java/org/traccar/protocol/EelinkProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/EelinkProtocolDecoderTest.java @@ -14,6 +14,9 @@ public class EelinkProtocolDecoderTest extends ProtocolTest { verifyPositions(decoder, binary( "454c029249a50354679090044671676712004321315f3cf43503fc94d3760c79328a0129000000000a01f9000190330905580d2e046f118a04ec00000000ccc7086c02fe000000000000000000000000000000000000676712004321325f3cf43e03fc94d3760c79328a0129000000000901f9000190330905580d2e046f117b04ec00000000ccc7086d02ff000000000000000000000000000000000000676712004321335f3cf44703fc94d3760c79328a0129000000000901f9000190330905580d2e046f117f04ec00000000ccc7086d02ff000000000000000000000000000000000000676712004321345f3cf45303fc94d3760c79328a0129000000000901f9000190330905580d2e046f119d04ec00000000ccc7086d02ff000000000000000000000000000000000000676712004321355f3cf45c03fc94d3760c79328a0129000000000801f9000190330905580d2e046f11a304ec00000000ccc7086d02ff000000000000000000000000000000000000676712004321365f3cf46603fc94d3760c79328a0129000000000801f9000190330905580d2e046f118804df00000000ccc7086d02ff000000000000000000000000000000000000676712004321375f3cf47103fc94d3760c79328a0129000000000901f9000190330905580d2e046f119704ec00000000ccc7086d02ff000000000000000000000000000000000000676712004321385f3cf47a03fc94d3760c79328a0129000000000901f9000190330905580d2e046f118204ec00000000ccc7086e0300000000000000000000000000000000000000676712004321395f3cf48303fc94d3760c79328a0129000000000901f9000190330905580d2e046f117604df00000000ccc7086e0300000000000000000000000000000000000000")); + verifyPosition(decoder, binary( + "6767120056096661d38e0091fbf0aa3a0f8fa08500060051015f09002542e50e7ea6080101f90001304e304e0818390d000000c524c2ae0699102b00000000000115b0040504050000000014000000000000000000000000000002")); + verifyAttribute(decoder, binary( "676714001500035f74a2940201360104591100a7160122250400"), Position.KEY_ALARM, Position.ALARM_REMOVING); @@ -42,9 +45,6 @@ public class EelinkProtocolDecoderTest extends ProtocolTest { verifyPosition(decoder, binary( "676714002414B05AD43A7D03026B92B10C395499FFD7000000000701CC00002495000014203604067B")); - verifyNotNull(decoder, binary( - "676714004F14B0E68CAFE58AA8E68AA5E8ADA621E5B9BFE4B89CE79C81E6B7B1E59CB3E5B882E58D97E5B1B1E58CBAE696B0E8A5BFE8B7AF3138EFBC88E8B79DE5AE87E998B3E5A4A7E58EA63230E7B1B3EFBC89")); - verifyPosition(decoder, binary( "676780005a000001000000004c61743a4e33312e38333935352c4c6f6e3a5738322e36313334362c436f757273653a302e30302c53706565643a302e30306b6d2f682c4461746554696d653a323031372d31322d30322031313a32393a3433")); diff --git a/src/test/java/org/traccar/protocol/FlexApiProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/FlexApiProtocolDecoderTest.java index a276a01e9..c819cd8bf 100644 --- a/src/test/java/org/traccar/protocol/FlexApiProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/FlexApiProtocolDecoderTest.java @@ -11,9 +11,12 @@ public class FlexApiProtocolDecoderTest extends ProtocolTest { var decoder = new FlexApiProtocolDecoder(null); verifyAttributes(decoder, text( + "${\"topic\":\"v1/VF3102021113001/motion/info\",\"payload\":{\"motion.ts\":1641885877,\"motion.ax\":0.006344,\"motion.ay\":0.289384,\"motion.az\":-0.939156,\"motion.gx\":0.420000,\"motion.gy\":0.420000,\"motion.gz\":-0.280000}}xx")); + + verifyAttributes(decoder, text( "${\"topic\":\"v1/VF3102021113001/gnss/info\",\"payload\":{\"gnss.ts\":1639713510,\"gnss.latitude\":30.587509,\"gnss.longitude\":104.053650,\"gnss.altitude\":391,\"gnss.speed\":0,\"gnss.heading\":0,\"gnss.hdop\":1.100000,\"gnss.fix\":4,\"gnss.num_sv\":10}}xx")); - verifyNull(decoder, text( + verifyAttributes(decoder, text( "${\"topic\":\"v1/VF3102021113001/cellular1/info\",\"payload\":{\"modem1.ts\":1639713510,\"modem1.imei\":\"863674047326655\",\"modem1.imsi\":\"\",\"modem1.iccid\":\"\",\"modem1.phone_num\":\"\",\"modem1.signal_lvl\":0,\"modem1.reg_status\":0,\"modem1.operator\":\"\",\"modem1.network\":0,\"modem1.lac\":\"\",\"modem1.cell_id\":\"\",\"modem1.rssi\":0,\"modem1.rsrp\":0,\"modem1.rsrq\":0,\"cellular1.status\":2,\"cellular1.ip\":\"0.0.0.0\",\"cellular1.netmask\":\"255.255.255.255\",\"cellular1.gateway\":\"0.0.0.0\",\"cellular1.dns1\":\"0.0.0.0\",\"cellular1.up_at\":602}}xx")); verifyAttributes(decoder, text( @@ -25,16 +28,16 @@ public class FlexApiProtocolDecoderTest extends ProtocolTest { verifyPosition(decoder, text( "${\"topic\":\"v1/VF3102021111601/gnss/info\",\"payload\":{\"time\":1637225390,\"lat\":30.587942,\"log\":104.053543,\"gnss.altitude\":480.399994,\"gnss.speed\":0,\"gnss.heading\":0,\"gnss.hdop\":0.900000,\"gnss.fix\":4,\"gnss.num_sv\":11}}xx")); - verifyNull(decoder, text( + verifyAttributes(decoder, text( "${\"topic\":\"v1/VF3102021111601/motion/info\",\"payload\":{\"motion.ts\":1637225450,\"motion.ax\":0.009272,\"motion.ay\":0.278404,\"motion.az\":-0.941596,\"motion.gx\":0.420000,\"motion.gy\":-0.490000,\"motion.gz\":0.140000}}xx")); - verifyNull(decoder, text( + verifyAttributes(decoder, text( "${\"topic\":\"v1/VF3102021111601/sysinfo/info\",\"payload\":{\"sysinfo.ts\":1637224740,\"sysinfo.model_name\":\"310\",\"sysinfo.oem_name\":\"inhand\",\"sysinfo.serial_number\":\"VF3102021111601\",\"sysinfo.firmware_version\":\"VT3_V1.1.32\",\"sysinfo.product_number\":\"FQ58\",\"sysinfo.description\":\"www.inhand.com.cn\"}}xx")); - verifyNull(decoder, text( + verifyAttributes(decoder, text( "${\"topic\":\"v1/VF3102021111601/io/info\",\"payload\":{\"io.ts\":1637227722,\"io.AI1\":0,\"io.DI1\":1,\"io.DI2\":0,\"io.DI3\":0,\"io.DI4\":0,\"io.DI1_pullup\":0,\"io.DI2_pullup\":0,\"io.DI3_pullup\":0,\"io.DI4_pullup\":0,\"io.DO1\":0,\"io.DO2\":0,\"io.DO3\":0,\"io.IGT\":1}}xx")); - verifyNull(decoder, text( + verifyAttributes(decoder, text( "${\"topic\":\"v1/VF3102021111601/cellular1/info\",\"payload\":{\"modem1.ts\":1637225330,\"modem1.imei\":\"863674047324999\",\"modem1.imsi\":\"460111150414721\",\"modem1.iccid\":\"89860319482086580401\",\"modem1.phone_num\":\"\",\"modem1.signal_lvl\":25,\"modem1.reg_status\":1,\"modem1.operator\":\"46011\",\"modem1.network\":3,\"modem1.lac\":\"EA00\",\"modem1.cell_id\":\"E779B81\",\"modem1.rssi\":0,\"modem1.rsrp\":0,\"modem1.rsrq\":0,\"cellular1.status\":3,\"cellular1.ip\":\"10.136.143.193\",\"cellular1.netmask\":\"255.255.255.255\",\"cellular1.gateway\":\"10.64.64.64\",\"cellular1.dns1\":\"223.5.5.5\",\"cellular1.up_at\":450}}xx")); } diff --git a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java index 8978f64e5..9fab7e010 100644 --- a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java @@ -11,6 +11,10 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest { var decoder = new Gl200TextProtocolDecoder(null); + verifyAttribute(decoder, buffer( + "+RESP:GTFRI,423031,866873025895726,,0,1,1,0,1,16,0.0,351,51.6,121.391063,31.164633,20181212072535,460,00,1877,DAE,00,3,85,20181212072535,002C$"), + Position.KEY_BATTERY_LEVEL, 85); + verifyAttributes(decoder, buffer( "+RESP:GTINF,DC0103,865284049247079,gv600mg,21,89883070000007211665,22,0,11,12913,12917,4.26,0,1,,,20210216154607,1,79,,01,00,,,20210216104606,1EBE$")); diff --git a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java index d2d090c04..8aadb7fe3 100644 --- a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java @@ -18,6 +18,9 @@ public class Gt06ProtocolDecoderTest extends ProtocolTest { "78780D01086471700328358100093F040D0A")); verifyNotNull(decoder, binary( + "78783B2810010D02020201CC00287D001F713E287D001F7231287D001E232D287D001F4018000000000000000000000000000000000000FF00020005B14B0D0A")); + + verifyNotNull(decoder, binary( "78782111150b0b022c30c804b7af7808810cb0003c00012e02d075df0084890c000679950d0a")); verifyNotNull(decoder, binary( diff --git a/src/test/java/org/traccar/protocol/H02ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/H02ProtocolDecoderTest.java index a0462e675..ad5f82176 100644 --- a/src/test/java/org/traccar/protocol/H02ProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/H02ProtocolDecoderTest.java @@ -12,6 +12,9 @@ public class H02ProtocolDecoderTest extends ProtocolTest { var decoder = new H02ProtocolDecoder(null); verifyPosition(decoder, buffer( + "*HQ,5905101893,V1,105759,A,37573392,S,145037022,E,000.00,173,280122,FF7FFBFF,,,9059e2c,8232,4#")); + + verifyPosition(decoder, buffer( "*HQ,4970105243,V1,104000,A,2235.1777,N,11357.8913,E,000.27,235,130721,FFFFFBFF,460,11,d18e105,7752,6#")); verifyAttribute(decoder, buffer( diff --git a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java index 238799fac..7aaec33e7 100644 --- a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java @@ -11,6 +11,9 @@ public class HuabaoProtocolDecoderTest extends ProtocolTest { var decoder = new HuabaoProtocolDecoder(null); + verifyNull(decoder, buffer( + "(794104004140,1,001,BASE,2,TIME)")); + verifyNull(decoder, binary( "7E01000021013345678906000F002C012F373031313142534A2D4D3742203030303030303001D4C1423838383838B47E")); diff --git a/src/test/java/org/traccar/protocol/MictrackProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/MictrackProtocolDecoderTest.java index 4c17bf1f8..ca8b67a46 100644 --- a/src/test/java/org/traccar/protocol/MictrackProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/MictrackProtocolDecoderTest.java @@ -11,6 +11,9 @@ public class MictrackProtocolDecoderTest extends ProtocolTest { var decoder = new MictrackProtocolDecoder(null); + verifyAttributes(decoder, text( + "MT;5;867035041396795;Y1;220111085741+test,8c:53:c3:db:e7:26,-58,jiuide-842,80:26:89:f0:5e:4f,-74,jiu2ide 403,94:e4:4b:0a:31:08,-75,jiu3ide,7a:91:e9:50:26:0b,-85,CNet-9rNe,78:91:e9:40:26:0b,-87+0+4092+1")); + verifyAttribute(decoder, text( "867035041390699 netlock=Success!"), Position.KEY_RESULT, "netlock=Success"); diff --git a/src/test/java/org/traccar/protocol/OrbcommProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/OrbcommProtocolDecoderTest.java new file mode 100644 index 000000000..af35505d5 --- /dev/null +++ b/src/test/java/org/traccar/protocol/OrbcommProtocolDecoderTest.java @@ -0,0 +1,24 @@ +package org.traccar.protocol; + +import org.junit.Test; +import org.traccar.ProtocolTest; + +public class OrbcommProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + var decoder = new OrbcommProtocolDecoder(null); + + verifyNull(decoder, response( + buffer("{\"ErrorID\":0,\"NextStartUTC\":\"\",\"Messages\":null}"))); + + verifyPositions(decoder, response( + buffer("{\"ErrorID\":0,\"NextStartUTC\":\"2022-01-20 21:17:19\",\"Messages\":[{\"ID\":21545955455454,\"MessageUTC\":\"2022-01-20 21:17:19\",\"ReceiveUTC\":\"2022-01-20 21:17:19\",\"SIN\":19,\"MobileID\":\"01097623SKY2C68\",\"Payload\":{\"Name\":\"simpleReport\",\"SIN\":19,\"MIN\":1,\"Fields\":[{\"Name\":\"latitude\",\"Value\":\"2717900\",\"Type\":\"signedint\"},{\"Name\":\"longitude\",\"Value\":\"-4555211\",\"Type\":\"signedint\"},{\"Name\":\"speed\",\"Value\":\"0\",\"Type\":\"unsignedint\"},{\"Name\":\"heading\",\"Value\":\"1439\",\"Type\":\"unsignedint\"}]},\"RegionName\":\"\",\"OTAMessageSize\":17,\"CustomerID\":0,\"Transport\":1,\"MobileOwnerID\":60000934}]}"))); + + verifyPositions(decoder, false, response( + buffer("{\"ErrorID\":0,\"NextStartUTC\":\"2016-10-13 15:19:59\",\"Messages\":[{\"ID\":120213064,\"MessageUTC\":\"2016-10-12 12:42:01\",\"ReceiveUTC\":\"2016-10-12 12:42:01\",\"SIN\":0,\"MobileID\":\"01173096SKY0E45\",\"Payload\":{\"Name\":\"modemRegistration\",\"SIN\":0,\"MIN\":0,\"Fields\":[{\"Name\":\"hardwareMajorVersion\",\"Value\":\"4\"},{\"Name\":\"hardwareMinorVersion\",\"Value\":\"2\"},{\"Name\":\"softwareMajorVersion\",\"Value\":\"13\"},{\"Name\":\"softwareMinorVersion\",\"Value\":\"1\"},{\"Name\":\"product\",\"Value\":\"4\"},{\"Name\":\"wakeupPeriod\",\"Value\":\"None\"},{\"Name\":\"lastResetReason\",\"Value\":\"Software\"},{\"Name\":\"virtualCarrier\",\"Value\":\"6\"},{\"Name\":\"beam\",\"Value\":\"1\"},{\"Name\":\"vain\",\"Value\":\"0\"},{\"Name\":\"reserved\",\"Value\":\"0\"},{\"Name\":\"operatorTxState\",\"Value\":\"0\"},{\"Name\":\"userTxState\",\"Value\":\"0\"},{\"Name\":\"broadcastIDCount\",\"Value\":\"0\"}],\"RegionName\":\"AMERRB11\",\"OTAMessageSize\":15,\"CustomerID\":0}}]}"))); + + } + +} diff --git a/src/test/java/org/traccar/protocol/S168ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/S168ProtocolDecoderTest.java index d4918e121..6bdf19182 100644 --- a/src/test/java/org/traccar/protocol/S168ProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/S168ProtocolDecoderTest.java @@ -10,6 +10,9 @@ public class S168ProtocolDecoderTest extends ProtocolTest { var decoder = new S168ProtocolDecoder(null); + verifyPosition(decoder, text( + "S168#358511039001705#003a#01ca#LOCA:G;CELL:6,1cc,0,2479,de11150,2e,2479,d6e4546,31,2479,d6e4547,39,778c,787cc30,39,778c,787cc31,40,253f,6195502,32;GDATA:A,5,220117220950,22.779583,113.820633,5,296,35;ALERT:0080;STATUS:98,73;;FARM:0,0009,0000,010a,20220117220950;WIFI:10,CC-08-FB-A5-49-B3,-28,08-40-F3-7F-6C-A9,-59,A4-29-40-65-2C-42,-74,80-89-17-A5-6F-7B,-82,80-EA-07-82-93-C6,-82,FC-37-2B-34-D6-A1,-83,34-6B-5B-A9-49-15,-83,BC-46-99-B3-51-10,-84,BC-54-FC-53-0A-D1,-84,3C-CD-57-67-D1-32,-85")); + verifyNull(decoder, text( "S168#358511139046180#00c9#0009#SYNC:0000")); diff --git a/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java index 5d22344fa..6c2d39940 100644 --- a/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java @@ -12,6 +12,9 @@ public class StartekProtocolDecoderTest extends ProtocolTest { var decoder = new StartekProtocolDecoder(null); verifyPosition(decoder, text( + "&&R187,860294046453690,000,0,,220105160656,A,22.994986,72.499711,15,0.9,2,222,55,121135784,404|98|147B|0000376A,24,0000001F,02,00,052E|01A3|0000|0000,1,010000|020000,,853|6|10|105|73|41|125|34|52")); + + verifyPosition(decoder, text( "&&o142,860262050066062,000,27,,211111070826,V,28.653435,-106.077455,0,0.0,0,151,1412,918,0|0|4708|01402D19,6,0000001A,02,00,04C0|016C|0000|0000,1,,,BB")); verifyPosition(decoder, text( diff --git a/src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java index a9720f437..098758728 100644 --- a/src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java @@ -1,5 +1,6 @@ package org.traccar.protocol; +import org.junit.Ignore; import org.junit.Test; import org.traccar.ProtocolTest; import org.traccar.model.Position; @@ -257,4 +258,16 @@ public class SuntechProtocolDecoderTest extends ProtocolTest { } + @Ignore + @Test + public void testDecodeCrash() throws Exception { + + var decoder = new SuntechProtocolDecoder(null); + + verifyAttribute(decoder, binary( + "4352523b303931303030303036333b313b313b303135303b16011c150f0ad82f6c0000000000ae037085fbff7700fd00faff6300f30000006800fb000d007100fa00f32f6c00000000005e044a80fcff6f000301e1ff5d00e900e1ff6400e600f4ff5b00ec000a306c00000000002104248306006c000501fcff5b00e00001006e000101eeff4e00e10022306c00000000005c041a7e00006a00010100005d00f800b5ff7cffdf0050009300fc003b44350d"), + Position.KEY_G_SENSOR, ""); + + } + } diff --git a/swagger.json b/swagger.json index 5a77e4bf9..9882c92d7 100644 --- a/swagger.json +++ b/swagger.json @@ -2,7 +2,7 @@ "openapi": "3.0.1", "info": { "title": "Traccar", - "version": "4.14", + "version": "4.15", "description": "Traccar GPS tracking server API documentation. To use the API you need to have a server instance. For testing purposes you can use one of free [demo servers](https://www.traccar.org/demo-server/). For production use you can install your own server or get a [subscription service](https://www.traccar.org/product/tracking-server/).", "contact": { "name": "Traccar Support", diff --git a/tools/test-integration.py b/tools/test-integration.py index 55bbf389f..6251b0d97 100755 --- a/tools/test-integration.py +++ b/tools/test-integration.py @@ -8,6 +8,7 @@ import urllib2 import json import socket import time +import threading messages = { 'gps103' : 'imei:123456789012345,help me,1201011201,,F,120100.000,A,6000.0000,N,13000.0000,E,0.00,;', @@ -141,6 +142,7 @@ def send_message(port, message): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('127.0.0.1', port)) s.send(message) + time.sleep(5) s.close() def get_protocols(cookie, device_id): @@ -155,40 +157,41 @@ def get_protocols(cookie, device_id): protocols.append(position['protocol']) return protocols -ports = load_ports() +if __name__ == "__main__": + ports = load_ports() -cookie = login() -remove_devices(cookie) - -devices = { - '123456789012345' : add_device(cookie, '123456789012345'), - '123456789012' : add_device(cookie, '123456789012'), - '1234567890' : add_device(cookie, '1234567890'), - '123456' : add_device(cookie, '123456'), - '1234' : add_device(cookie, '1234') -} + cookie = login() + remove_devices(cookie) + devices = { + '123456789012345' : add_device(cookie, '123456789012345'), + '123456789012' : add_device(cookie, '123456789012'), + '1234567890' : add_device(cookie, '1234567890'), + '123456' : add_device(cookie, '123456'), + '1234' : add_device(cookie, '1234') + } -all = set(ports.keys()) -protocols = set(messages.keys()) + all = set(ports.keys()) + protocols = set(messages.keys()) -print 'Total: %d' % len(all) -print 'Missing: %d' % len(all - protocols) -print 'Covered: %d' % len(protocols) + print 'Total: %d' % len(all) + print 'Missing: %d' % len(all - protocols) + print 'Covered: %d' % len(protocols) -#if all - protocols: -# print '\nMissing: %s\n' % repr(list((all - protocols))) + #if all - protocols: + # print '\nMissing: %s\n' % repr(list((all - protocols))) -for protocol in messages: - send_message(ports[protocol], messages[protocol]) + for protocol in messages: + thread = threading.Thread(target = send_message, args = (ports[protocol], messages[protocol])) + thread.start() -time.sleep(10) + time.sleep(10) -for device in devices: - protocols -= set(get_protocols(cookie, devices[device])) + for device in devices: + protocols -= set(get_protocols(cookie, devices[device])) -print 'Success: %d' % (len(messages) - len(protocols)) -print 'Failed: %d' % len(protocols) + print 'Success: %d' % (len(messages) - len(protocols)) + print 'Failed: %d' % len(protocols) -if protocols: - print '\nFailed: %s' % repr(list(protocols)) + if protocols: + print '\nFailed: %s' % repr(list(protocols)) diff --git a/traccar-web b/traccar-web -Subproject aac829df866d4975fa52e2d42b4d197ec1f05d3 +Subproject 9e1c0b44189136ec6abf05720c698027a689fa0 |