diff options
68 files changed, 1530 insertions, 607 deletions
diff --git a/build.gradle b/build.gradle index 1fa3986fd..b4e11ed98 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ plugins { id "java" id "checkstyle" - id "com.google.protobuf" version "0.8.19" - id "org.kordamp.gradle.project-enforcer" version "0.10.0" + id "com.google.protobuf" version "0.9.3" + id "org.kordamp.gradle.project-enforcer" version "0.13.0" } repositories { @@ -14,7 +14,7 @@ compileJava.options.encoding = "UTF-8" jar.destinationDirectory = file("$projectDir/target") checkstyle { - toolVersion = "10.3.4" + toolVersion = "10.12.0" configFile = "gradle/checkstyle.xml" as File checkstyleTest.enabled = false } @@ -26,13 +26,13 @@ enforce { } ext { - guiceVersion = "5.1.0" + guiceVersion = "6.0.0" jettyVersion = "10.0.15" // jetty 11 javax to jakarta jerseyVersion = "2.39.1" // jersey 3 javax to jakarta jacksonVersion = "2.14.1" // same version as jersey-media-json-jackson dependency - protobufVersion = "3.22.3" + protobufVersion = "3.23.2" jxlsVersion = "2.12.0" - junitVersion = "5.9.2" + junitVersion = "5.9.3" } protobuf { @@ -44,11 +44,12 @@ protobuf { dependencies { implementation "commons-codec:commons-codec:1.15" implementation "com.h2database:h2:2.1.214" - implementation "com.mysql:mysql-connector-j:8.0.32" + implementation "com.mysql:mysql-connector-j:8.0.33" + implementation "org.mariadb.jdbc:mariadb-java-client:3.1.4" implementation "org.postgresql:postgresql:42.6.0" implementation "com.microsoft.sqlserver:mssql-jdbc:12.2.0.jre11" implementation "com.zaxxer:HikariCP:5.0.1" - implementation "io.netty:netty-all:4.1.91.Final" + implementation "io.netty:netty-all:4.1.93.Final" implementation "org.slf4j:slf4j-jdk14:2.0.7" implementation "com.google.inject:guice:$guiceVersion" implementation "com.google.inject.extensions:guice-servlet:$guiceVersion" @@ -67,7 +68,7 @@ dependencies { implementation "org.glassfish.hk2:guice-bridge:2.6.1" // same version as jersey-hk2 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.21.1" + implementation "org.liquibase:liquibase-core:4.22.0" implementation "com.sun.mail:jakarta.mail:1.6.7" implementation "org.apache.commons:commons-jexl3:3.3" implementation "org.jxls:jxls:$jxlsVersion" @@ -82,15 +83,15 @@ dependencies { implementation "com.github.jnr:jnr-posix:3.1.16" implementation "com.google.protobuf:protobuf-java:$protobufVersion" implementation "javax.activation:activation:1.1.1" - implementation "com.amazonaws:aws-java-sdk-sns:1.12.450" + implementation "com.amazonaws:aws-java-sdk-sns:1.12.477" implementation "org.apache.kafka:kafka-clients:3.4.0" implementation "com.hivemq:hivemq-mqtt-client:1.3.1" - implementation "redis.clients:jedis:4.3.2" + implementation "redis.clients:jedis:4.4.1" implementation "com.google.firebase:firebase-admin:9.1.1" - implementation "com.nimbusds:oauth2-oidc-sdk:10.7.1" + implementation "com.nimbusds:oauth2-oidc-sdk:10.9.1" testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-engine:$junitVersion" - testImplementation "org.mockito:mockito-core:5.3.0" + testImplementation "org.mockito:mockito-core:5.3.1" } test { @@ -107,7 +108,7 @@ jar { manifest { attributes( "Main-Class": "org.traccar.Main", - "Implementation-Version": "5.7", + "Implementation-Version": "5.8", "Class-Path": configurations.runtimeClasspath.files.collect { "lib/$it.name" }.join(" ")) } } diff --git a/schema/changelog-5.9.xml b/schema/changelog-5.9.xml new file mode 100644 index 000000000..50a3d3aaa --- /dev/null +++ b/schema/changelog-5.9.xml @@ -0,0 +1,27 @@ +<?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-5.9"> + + <changeSet author="author" id="changelog-5.9"> + + <addColumn tableName="tc_devices"> + <column name="calendarid" type="INT" /> + </addColumn> + + <addForeignKeyConstraint + baseTableName="tc_devices" + baseColumnNames="calendarid" + constraintName="fk_devices_calendarid" + referencedTableName="tc_calendars" + referencedColumnNames="id" + onDelete="SET NULL" + onUpdate="RESTRICT" + /> + + </changeSet> + +</databaseChangeLog> diff --git a/schema/changelog-master.xml b/schema/changelog-master.xml index dd2bcc8a7..331d5ec78 100644 --- a/schema/changelog-master.xml +++ b/schema/changelog-master.xml @@ -39,5 +39,6 @@ <include file="changelog-5.6.xml" relativeToChangelogFile="true" /> <include file="changelog-5.7.xml" relativeToChangelogFile="true" /> <include file="changelog-5.8.xml" relativeToChangelogFile="true" /> + <include file="changelog-5.9.xml" relativeToChangelogFile="true" /> </databaseChangeLog> diff --git a/setup/default.xml b/setup/default.xml index e8dc7aa79..576bfcb6c 100644 --- a/setup/default.xml +++ b/setup/default.xml @@ -289,5 +289,6 @@ <entry key='rftrack.port'>5245</entry> <entry key='vlt.port'>5246</entry> <entry key='transync.port'>5247</entry> + <entry key='t622iridium.port'>5248</entry> </properties> diff --git a/setup/traccar.iss b/setup/traccar.iss index 821c90958..f9988a6f3 100644 --- a/setup/traccar.iss +++ b/setup/traccar.iss @@ -1,6 +1,6 @@ [Setup] AppName=Traccar -AppVersion=5.7 +AppVersion=5.8 DefaultDirName={pf}\Traccar OutputBaseFilename=traccar-setup ArchitecturesInstallIn64BitMode=x64 diff --git a/src/main/java/org/traccar/BaseProtocol.java b/src/main/java/org/traccar/BaseProtocol.java index d19fc307e..1948becc0 100644 --- a/src/main/java/org/traccar/BaseProtocol.java +++ b/src/main/java/org/traccar/BaseProtocol.java @@ -105,7 +105,8 @@ public abstract class BaseProtocol implements Protocol { } else if (command.getType().equals(Command.TYPE_CUSTOM)) { String data = command.getString(Command.KEY_DATA); if (BasePipelineFactory.getHandler(channel.pipeline(), StringEncoder.class) != null) { - channel.writeAndFlush(new NetworkMessage(data, remoteAddress)); + channel.writeAndFlush(new NetworkMessage( + data.replace("\\r", "\r").replace("\\n", "\n"), remoteAddress)); } else { ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(data)); channel.writeAndFlush(new NetworkMessage(buf, remoteAddress)); diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java index 6a2fe21c3..b7bdbc6bf 100644 --- a/src/main/java/org/traccar/MainModule.java +++ b/src/main/java/org/traccar/MainModule.java @@ -28,6 +28,7 @@ import io.netty.util.Timer; import org.apache.velocity.app.VelocityEngine; import org.traccar.broadcast.BroadcastService; import org.traccar.broadcast.MulticastBroadcastService; +import org.traccar.broadcast.RedisBroadcastService; import org.traccar.broadcast.NullBroadcastService; import org.traccar.config.Config; import org.traccar.config.Keys; @@ -340,8 +341,15 @@ public class MainModule extends AbstractModule { @Provides public static BroadcastService provideBroadcastService( Config config, ObjectMapper objectMapper) throws IOException { - if (config.hasKey(Keys.BROADCAST_ADDRESS)) { - return new MulticastBroadcastService(config, objectMapper); + if (config.hasKey(Keys.BROADCAST_TYPE)) { + switch (config.getString(Keys.BROADCAST_TYPE)) { + case "multicast": + return new MulticastBroadcastService(config, objectMapper); + case "redis": + return new RedisBroadcastService(config, objectMapper); + default: + break; + } } return new NullBroadcastService(); } diff --git a/src/main/java/org/traccar/api/BaseObjectResource.java b/src/main/java/org/traccar/api/BaseObjectResource.java index 904781e54..b007b7bcd 100644 --- a/src/main/java/org/traccar/api/BaseObjectResource.java +++ b/src/main/java/org/traccar/api/BaseObjectResource.java @@ -16,6 +16,7 @@ */ package org.traccar.api; +import org.traccar.api.security.ServiceAccountUser; import org.traccar.helper.LogAction; import org.traccar.model.BaseModel; import org.traccar.model.Group; @@ -70,10 +71,13 @@ public abstract class BaseObjectResource<T extends BaseModel> extends BaseResour entity.setId(storage.addObject(entity, new Request(new Columns.Exclude("id")))); LogAction.create(getUserId(), entity); - storage.addPermission(new Permission(User.class, getUserId(), baseClass, entity.getId())); - cacheManager.invalidatePermission(true, User.class, getUserId(), baseClass, entity.getId()); - connectionManager.invalidatePermission(true, User.class, getUserId(), baseClass, entity.getId()); - LogAction.link(getUserId(), User.class, getUserId(), baseClass, entity.getId()); + + if (getUserId() != ServiceAccountUser.ID) { + storage.addPermission(new Permission(User.class, getUserId(), baseClass, entity.getId())); + cacheManager.invalidatePermission(true, User.class, getUserId(), baseClass, entity.getId()); + connectionManager.invalidatePermission(true, User.class, getUserId(), baseClass, entity.getId()); + LogAction.link(getUserId(), User.class, getUserId(), baseClass, entity.getId()); + } return Response.ok(entity).build(); } diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java index f334b7a21..dcb059b69 100644 --- a/src/main/java/org/traccar/api/resource/ServerResource.java +++ b/src/main/java/org/traccar/api/resource/ServerResource.java @@ -16,12 +16,14 @@ package org.traccar.api.resource; import org.traccar.api.BaseResource; +import org.traccar.config.Config; +import org.traccar.config.Keys; import org.traccar.database.OpenIdProvider; -import org.traccar.helper.model.UserUtil; -import org.traccar.mail.MailManager; import org.traccar.geocoder.Geocoder; import org.traccar.helper.Log; import org.traccar.helper.LogAction; +import org.traccar.helper.model.UserUtil; +import org.traccar.mail.MailManager; import org.traccar.model.Server; import org.traccar.model.User; import org.traccar.session.cache.CacheManager; @@ -36,12 +38,20 @@ import javax.annotation.security.PermitAll; import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Collection; import java.util.TimeZone; @@ -52,6 +62,9 @@ import java.util.TimeZone; public class ServerResource extends BaseResource { @Inject + private Config config; + + @Inject private CacheManager cacheManager; @Inject @@ -119,4 +132,23 @@ public class ServerResource extends BaseResource { return Arrays.asList(TimeZone.getAvailableIDs()); } + @Path("file/{path}") + @POST + @Consumes("*/*") + public Response uploadImage(@PathParam("path") String path, File inputFile) throws IOException, StorageException { + permissionsService.checkAdmin(getUserId()); + String root = config.getString(Keys.WEB_OVERRIDE, config.getString(Keys.WEB_PATH)); + + var outputPath = Paths.get(root, path); + var directoryPath = outputPath.getParent(); + if (directoryPath != null) { + Files.createDirectories(directoryPath); + } + + try (var input = new FileInputStream(inputFile); var output = new FileOutputStream(outputPath.toFile())) { + input.transferTo(output); + } + return Response.ok().build(); + } + } diff --git a/src/main/java/org/traccar/api/security/PermissionsService.java b/src/main/java/org/traccar/api/security/PermissionsService.java index 18a376601..38bf48f30 100644 --- a/src/main/java/org/traccar/api/security/PermissionsService.java +++ b/src/main/java/org/traccar/api/security/PermissionsService.java @@ -24,7 +24,7 @@ import org.traccar.model.Group; import org.traccar.model.GroupedModel; import org.traccar.model.ManagedUser; import org.traccar.model.Notification; -import org.traccar.model.ScheduledModel; +import org.traccar.model.Schedulable; import org.traccar.model.Server; import org.traccar.model.User; import org.traccar.model.UserRestrictions; @@ -137,13 +137,13 @@ public class PermissionsService { } } } - if (object instanceof ScheduledModel) { - ScheduledModel after = ((ScheduledModel) object); + if (object instanceof Schedulable) { + Schedulable after = ((Schedulable) object); if (after.getCalendarId() > 0) { - ScheduledModel before = null; + Schedulable before = null; if (!addition) { before = storage.getObject(after.getClass(), new Request( - new Columns.Include("calendarId"), new Condition.Equals("id", after.getId()))); + new Columns.Include("calendarId"), new Condition.Equals("id", object.getId()))); } if (before == null || before.getCalendarId() != after.getCalendarId()) { checkPermission(Calendar.class, userId, after.getCalendarId()); @@ -156,7 +156,7 @@ public class PermissionsService { Notification before = null; if (!addition) { before = storage.getObject(after.getClass(), new Request( - new Columns.Include("commandId"), new Condition.Equals("id", after.getId()))); + new Columns.Include("commandId"), new Condition.Equals("id", object.getId()))); } if (before == null || before.getCommandId() != after.getCommandId()) { checkPermission(Command.class, userId, after.getCommandId()); diff --git a/src/main/java/org/traccar/broadcast/BaseBroadcastService.java b/src/main/java/org/traccar/broadcast/BaseBroadcastService.java new file mode 100644 index 000000000..a95d333f2 --- /dev/null +++ b/src/main/java/org/traccar/broadcast/BaseBroadcastService.java @@ -0,0 +1,118 @@ +/* + * Copyright 2023 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.broadcast; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.traccar.model.BaseModel; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Permission; +import org.traccar.model.Position; + +public abstract class BaseBroadcastService implements BroadcastService { + + private final Set<BroadcastInterface> listeners = new HashSet<>(); + + @Override + public boolean singleInstance() { + return true; + } + + @Override + public void registerListener(BroadcastInterface listener) { + listeners.add(listener); + } + + @Override + public void updateDevice(boolean local, Device device) { + BroadcastMessage message = new BroadcastMessage(); + message.setDevice(device); + sendMessage(message); + } + + @Override + public void updatePosition(boolean local, Position position) { + BroadcastMessage message = new BroadcastMessage(); + message.setPosition(position); + sendMessage(message); + } + + @Override + public void updateEvent(boolean local, long userId, Event event) { + BroadcastMessage message = new BroadcastMessage(); + message.setUserId(userId); + message.setEvent(event); + sendMessage(message); + } + + @Override + public void updateCommand(boolean local, long deviceId) { + BroadcastMessage message = new BroadcastMessage(); + message.setCommandDeviceId(deviceId); + sendMessage(message); + } + + @Override + public void invalidateObject(boolean local, Class<? extends BaseModel> clazz, long id) { + BroadcastMessage message = new BroadcastMessage(); + message.setChanges(Map.of(Permission.getKey(clazz), id)); + sendMessage(message); + } + + @Override + public void invalidatePermission( + boolean local, + Class<? extends BaseModel> clazz1, long id1, + Class<? extends BaseModel> clazz2, long id2) { + BroadcastMessage message = new BroadcastMessage(); + message.setChanges(Map.of(Permission.getKey(clazz1), id1, Permission.getKey(clazz2), id2)); + sendMessage(message); + } + + protected abstract void sendMessage(BroadcastMessage message); + + protected void handleMessage(BroadcastMessage message) { + if (message.getDevice() != null) { + listeners.forEach(listener -> listener.updateDevice(false, message.getDevice())); + } else if (message.getPosition() != null) { + listeners.forEach(listener -> listener.updatePosition(false, message.getPosition())); + } else if (message.getUserId() != null && message.getEvent() != null) { + listeners.forEach(listener -> listener.updateEvent(false, message.getUserId(), message.getEvent())); + } else if (message.getCommandDeviceId() != null) { + listeners.forEach(listener -> listener.updateCommand(false, message.getCommandDeviceId())); + } else if (message.getChanges() != null) { + var iterator = message.getChanges().entrySet().iterator(); + if (iterator.hasNext()) { + var first = iterator.next(); + if (iterator.hasNext()) { + var second = iterator.next(); + listeners.forEach(listener -> listener.invalidatePermission( + false, + Permission.getKeyClass(first.getKey()), first.getValue(), + Permission.getKeyClass(second.getKey()), second.getValue())); + } else { + listeners.forEach(listener -> listener.invalidateObject( + false, + Permission.getKeyClass(first.getKey()), first.getValue())); + } + } + } + } + +} diff --git a/src/main/java/org/traccar/broadcast/MulticastBroadcastService.java b/src/main/java/org/traccar/broadcast/MulticastBroadcastService.java index b1b66f1e3..1c02b319b 100644 --- a/src/main/java/org/traccar/broadcast/MulticastBroadcastService.java +++ b/src/main/java/org/traccar/broadcast/MulticastBroadcastService.java @@ -20,11 +20,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.config.Config; import org.traccar.config.Keys; -import org.traccar.model.BaseModel; -import org.traccar.model.Device; -import org.traccar.model.Event; -import org.traccar.model.Permission; -import org.traccar.model.Position; import java.io.IOException; import java.net.DatagramPacket; @@ -34,13 +29,10 @@ import java.net.InetSocketAddress; import java.net.MulticastSocket; import java.net.NetworkInterface; import java.nio.charset.StandardCharsets; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -public class MulticastBroadcastService implements BroadcastService { +public class MulticastBroadcastService extends BaseBroadcastService { private static final Logger LOGGER = LoggerFactory.getLogger(MulticastBroadcastService.class); @@ -55,8 +47,6 @@ public class MulticastBroadcastService implements BroadcastService { private final ExecutorService service = Executors.newSingleThreadExecutor(); private final byte[] receiverBuffer = new byte[4096]; - private final Set<BroadcastInterface> listeners = new HashSet<>(); - public MulticastBroadcastService(Config config, ObjectMapper objectMapper) throws IOException { this.objectMapper = objectMapper; port = config.getInteger(Keys.BROADCAST_PORT); @@ -76,57 +66,7 @@ public class MulticastBroadcastService implements BroadcastService { } @Override - public void registerListener(BroadcastInterface listener) { - listeners.add(listener); - } - - @Override - public void updateDevice(boolean local, Device device) { - BroadcastMessage message = new BroadcastMessage(); - message.setDevice(device); - sendMessage(message); - } - - @Override - public void updatePosition(boolean local, Position position) { - BroadcastMessage message = new BroadcastMessage(); - message.setPosition(position); - sendMessage(message); - } - - @Override - public void updateEvent(boolean local, long userId, Event event) { - BroadcastMessage message = new BroadcastMessage(); - message.setUserId(userId); - message.setEvent(event); - sendMessage(message); - } - - @Override - public void updateCommand(boolean local, long deviceId) { - BroadcastMessage message = new BroadcastMessage(); - message.setCommandDeviceId(deviceId); - sendMessage(message); - } - - @Override - public void invalidateObject(boolean local, Class<? extends BaseModel> clazz, long id) { - BroadcastMessage message = new BroadcastMessage(); - message.setChanges(Map.of(Permission.getKey(clazz), id)); - sendMessage(message); - } - - @Override - public void invalidatePermission( - boolean local, - Class<? extends BaseModel> clazz1, long id1, - Class<? extends BaseModel> clazz2, long id2) { - BroadcastMessage message = new BroadcastMessage(); - message.setChanges(Map.of(Permission.getKey(clazz1), id1, Permission.getKey(clazz2), id2)); - sendMessage(message); - } - - private void sendMessage(BroadcastMessage message) { + protected void sendMessage(BroadcastMessage message) { try { byte[] buffer = objectMapper.writeValueAsString(message).getBytes(StandardCharsets.UTF_8); DatagramPacket packet = new DatagramPacket(buffer, buffer.length, group); @@ -136,34 +76,6 @@ public class MulticastBroadcastService implements BroadcastService { } } - private void handleMessage(BroadcastMessage message) { - if (message.getDevice() != null) { - listeners.forEach(listener -> listener.updateDevice(false, message.getDevice())); - } else if (message.getPosition() != null) { - listeners.forEach(listener -> listener.updatePosition(false, message.getPosition())); - } else if (message.getUserId() != null && message.getEvent() != null) { - listeners.forEach(listener -> listener.updateEvent(false, message.getUserId(), message.getEvent())); - } else if (message.getCommandDeviceId() != null) { - listeners.forEach(listener -> listener.updateCommand(false, message.getCommandDeviceId())); - } else if (message.getChanges() != null) { - var iterator = message.getChanges().entrySet().iterator(); - if (iterator.hasNext()) { - var first = iterator.next(); - if (iterator.hasNext()) { - var second = iterator.next(); - listeners.forEach(listener -> listener.invalidatePermission( - false, - Permission.getKeyClass(first.getKey()), first.getValue(), - Permission.getKeyClass(second.getKey()), second.getValue())); - } else { - listeners.forEach(listener -> listener.invalidateObject( - false, - Permission.getKeyClass(first.getKey()), first.getValue())); - } - } - } - } - @Override public void start() throws IOException { service.submit(receiver); diff --git a/src/main/java/org/traccar/broadcast/RedisBroadcastService.java b/src/main/java/org/traccar/broadcast/RedisBroadcastService.java new file mode 100644 index 000000000..e87ad5e61 --- /dev/null +++ b/src/main/java/org/traccar/broadcast/RedisBroadcastService.java @@ -0,0 +1,130 @@ +/* + * Copyright 2023 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.broadcast; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; + +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPubSub; +import redis.clients.jedis.exceptions.JedisConnectionException; +import redis.clients.jedis.exceptions.JedisException; + +public class RedisBroadcastService extends BaseBroadcastService { + + private static final Logger LOGGER = LoggerFactory.getLogger(RedisBroadcastService.class); + + private final ObjectMapper objectMapper; + + private final ExecutorService service = Executors.newSingleThreadExecutor(); + + private final String url; + private final String channel = "traccar"; + + private Jedis subscriber; + private Jedis publisher; + + private final String id = UUID.randomUUID().toString(); + + public RedisBroadcastService(Config config, ObjectMapper objectMapper) throws IOException { + this.objectMapper = objectMapper; + url = config.getString(Keys.BROADCAST_ADDRESS); + + try { + subscriber = new Jedis(url); + publisher = new Jedis(url); + subscriber.connect(); + } catch (JedisConnectionException e) { + throw new IOException(e); + } + } + + @Override + public boolean singleInstance() { + return false; + } + + @Override + protected void sendMessage(BroadcastMessage message) { + try { + String payload = id + ":" + objectMapper.writeValueAsString(message); + publisher.publish(channel, payload); + } catch (IOException e) { + LOGGER.warn("Broadcast failed", e); + } catch (JedisConnectionException e) { + LOGGER.warn("Broadcast failed", e); + } + } + + @Override + public void start() throws IOException { + service.submit(receiver); + } + + @Override + public void stop() { + try { + if (subscriber != null) { + subscriber.close(); + subscriber = null; + } + } catch (JedisException e) { + LOGGER.warn("Subscriber close failed", e); + } + try { + if (publisher != null) { + publisher.close(); + publisher = null; + } + } catch (JedisException e) { + LOGGER.warn("Publisher close failed", e); + } + service.shutdown(); + } + + private final Runnable receiver = new Runnable() { + @Override + public void run() { + try { + subscriber.subscribe(new JedisPubSub() { + @Override + public void onMessage(String messageChannel, String message) { + try { + String[] parts = message.split(":", 2); + if (messageChannel.equals(channel) && parts.length == 2 && !id.equals(parts[0])) { + handleMessage(objectMapper.readValue(parts[1], BroadcastMessage.class)); + } + } catch (IOException e) { + LOGGER.warn("Broadcast handleMessage failed", e); + } + } + }, channel); + } catch (JedisConnectionException e) { + throw new RuntimeException(e); + } catch (JedisException e) { + throw new RuntimeException(e); + } + } + }; + +} diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 8246fdeac..6f5360c22 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -238,7 +238,7 @@ public final class Keys { */ public static final ConfigSuffix<String> PROTOCOL_FORMAT = new StringConfigSuffix( ".format", - List.of(KeyType.DEVICE)); + List.of(KeyType.CONFIG, KeyType.DEVICE)); /** * Protocol date format. Used by protocols that have configurable date format. @@ -739,6 +739,14 @@ public final class Keys { List.of(KeyType.CONFIG)); /** + * Maximum API request duration in seconds. + */ + public static final ConfigKey<Integer> WEB_MAX_REQUEST_SECONDS = new IntegerConfigKey( + "web.maxRequestSec", + List.of(KeyType.CONFIG), + 600); + + /** * Sanitize all strings returned via API. This is needed to fix XSS issues in the old web interface. New React-based * interface doesn't require this. */ @@ -761,12 +769,12 @@ public final class Keys { List.of(KeyType.CONFIG)); /** - * WebSocket connection timeout in milliseconds. Default timeout is 10 minutes. + * WebSocket connection timeout in milliseconds. Default timeout is 5 minutes. */ public static final ConfigKey<Long> WEB_TIMEOUT = new LongConfigKey( "web.timeout", List.of(KeyType.CONFIG), - 60000L); + 300000L); /** * Authentication sessions timeout in seconds. By default no timeout. @@ -1328,6 +1336,13 @@ public final class Keys { List.of(KeyType.CONFIG)); /** + * Filter position if the daily limit is exceeded for the device. + */ + public static final ConfigKey<Integer> FILTER_DAILY_LIMIT = new IntegerConfigKey( + "filter.dailyLimit", + List.of(KeyType.CONFIG)); + + /** * If false, the server expects all locations to come sequentially (for each device). Filter checks for duplicates, * distance, speed, or time period only against the location that was last received by server. * If true, the server expects locations to come at random order (since tracking device might go offline). @@ -1756,6 +1771,14 @@ public final class Keys { "time,position,speed,course,accuracy,result"); /** + * Broadcast method. Available options are "multicast" and "redis". By default (if the value is not + * specified or does not matches available options) server disables broadcast. + */ + public static final ConfigKey<String> BROADCAST_TYPE = new StringConfigKey( + "broadcast.type", + List.of(KeyType.CONFIG)); + + /** * Multicast interface. It can be either an IP address or an interface name. */ public static final ConfigKey<String> BROADCAST_INTERFACE = new StringConfigKey( @@ -1763,7 +1786,7 @@ public final class Keys { List.of(KeyType.CONFIG)); /** - * Multicast address for broadcasting synchronization events. + * Multicast address or Redis URL for broadcasting synchronization events. */ public static final ConfigKey<String> BROADCAST_ADDRESS = new StringConfigKey( "broadcast.address", diff --git a/src/main/java/org/traccar/database/StatisticsManager.java b/src/main/java/org/traccar/database/StatisticsManager.java index e0995dabc..e417c8901 100644 --- a/src/main/java/org/traccar/database/StatisticsManager.java +++ b/src/main/java/org/traccar/database/StatisticsManager.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.traccar.api.security.ServiceAccountUser; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.helper.DateUtil; @@ -57,6 +58,7 @@ public class StatisticsManager { private final Set<Long> users = new HashSet<>(); private final Map<Long, String> deviceProtocols = new HashMap<>(); + private final Map<Long, Integer> deviceMessages = new HashMap<>(); private int requests; private int messagesReceived; @@ -100,6 +102,7 @@ public class StatisticsManager { users.clear(); deviceProtocols.clear(); + deviceMessages.clear(); requests = 0; messagesReceived = 0; messagesStored = 0; @@ -147,7 +150,7 @@ public class StatisticsManager { public synchronized void registerRequest(long userId) { checkSplit(); requests += 1; - if (userId != 0) { + if (userId != 0 && userId != ServiceAccountUser.ID) { users.add(userId); } } @@ -162,9 +165,14 @@ public class StatisticsManager { messagesStored += 1; if (deviceId != 0) { deviceProtocols.put(deviceId, protocol); + deviceMessages.merge(deviceId, 1, Integer::sum); } } + public synchronized int messageStoredCount(long deviceId) { + return deviceMessages.getOrDefault(deviceId, 0); + } + public synchronized void registerMail() { checkSplit(); mailSent += 1; diff --git a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java index d384bbffc..8ad4e41e4 100644 --- a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java +++ b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.List; import io.netty.channel.ChannelHandler; import org.apache.commons.jexl3.JexlFeatures; @@ -65,6 +66,10 @@ public class ComputedAttributesHandler extends BaseDataHandler { JexlSandbox sandbox = new JexlSandbox(false); sandbox.allow("com.safe.Functions"); sandbox.allow(Math.class.getName()); + List.of( + Double.class, Float.class, Integer.class, Long.class, Short.class, + Character.class, Boolean.class, String.class, Byte.class) + .forEach((type) -> sandbox.allow(type.getName())); features = new JexlFeatures() .localVar(config.getBoolean(Keys.PROCESSING_COMPUTED_ATTRIBUTES_LOCAL_VARIABLES)) .loops(config.getBoolean(Keys.PROCESSING_COMPUTED_ATTRIBUTES_LOOPS)) diff --git a/src/main/java/org/traccar/handler/FilterHandler.java b/src/main/java/org/traccar/handler/FilterHandler.java index 9ff94deb0..028e4cd09 100644 --- a/src/main/java/org/traccar/handler/FilterHandler.java +++ b/src/main/java/org/traccar/handler/FilterHandler.java @@ -22,8 +22,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.config.Config; import org.traccar.config.Keys; +import org.traccar.database.StatisticsManager; import org.traccar.helper.UnitsConverter; import org.traccar.helper.model.AttributeUtil; +import org.traccar.model.Calendar; import org.traccar.model.Device; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; @@ -57,15 +59,18 @@ public class FilterHandler extends ChannelInboundHandlerAdapter { private final int filterDistance; private final int filterMaxSpeed; private final long filterMinPeriod; + private final int filterDailyLimit; private final boolean filterRelative; private final long skipLimit; private final boolean skipAttributes; private final CacheManager cacheManager; private final Storage storage; + private final StatisticsManager statisticsManager; @Inject - public FilterHandler(Config config, CacheManager cacheManager, Storage storage) { + public FilterHandler( + Config config, CacheManager cacheManager, Storage storage, StatisticsManager statisticsManager) { enabled = config.getBoolean(Keys.FILTER_ENABLE); filterInvalid = config.getBoolean(Keys.FILTER_INVALID); filterZero = config.getBoolean(Keys.FILTER_ZERO); @@ -79,11 +84,13 @@ public class FilterHandler extends ChannelInboundHandlerAdapter { filterDistance = config.getInteger(Keys.FILTER_DISTANCE); filterMaxSpeed = config.getInteger(Keys.FILTER_MAX_SPEED); filterMinPeriod = config.getInteger(Keys.FILTER_MIN_PERIOD) * 1000L; + filterDailyLimit = config.getInteger(Keys.FILTER_DAILY_LIMIT); filterRelative = config.getBoolean(Keys.FILTER_RELATIVE); skipLimit = config.getLong(Keys.FILTER_SKIP_LIMIT) * 1000; skipAttributes = config.getBoolean(Keys.FILTER_SKIP_ATTRIBUTES_ENABLE); this.cacheManager = cacheManager; this.storage = storage; + this.statisticsManager = statisticsManager; } private Position getPrecedingPosition(long deviceId, Date date) throws StorageException { @@ -165,6 +172,13 @@ public class FilterHandler extends ChannelInboundHandlerAdapter { return false; } + private boolean filterDailyLimit(Position position) { + if (filterDailyLimit != 0) { + return statisticsManager.messageStoredCount(position.getDeviceId()) >= filterDailyLimit; + } + return false; + } + private boolean skipLimit(Position position, Position last) { if (skipLimit != 0 && last != null) { return (position.getServerTime().getTime() - last.getServerTime().getTime()) > skipLimit; @@ -210,6 +224,9 @@ public class FilterHandler extends ChannelInboundHandlerAdapter { if (filterApproximate(position)) { filterType.append("Approximate "); } + if (filterDailyLimit(position)) { + filterType.append("DailyLimit "); + } // filter out excessive data long deviceId = position.getDeviceId(); @@ -243,9 +260,16 @@ public class FilterHandler extends ChannelInboundHandlerAdapter { } } + Device device = cacheManager.getObject(Device.class, deviceId); + if (device.getCalendarId() > 0) { + Calendar calendar = cacheManager.getObject(Calendar.class, device.getCalendarId()); + if (!calendar.checkMoment(position.getFixTime())) { + filterType.append("Calendar "); + } + } + if (filterType.length() > 0) { - String uniqueId = cacheManager.getObject(Device.class, deviceId).getUniqueId(); - LOGGER.info("Position filtered by {}filters from device: {}", filterType, uniqueId); + LOGGER.info("Position filtered by {}filters from device: {}", filterType, device.getUniqueId()); return true; } diff --git a/src/main/java/org/traccar/model/Device.java b/src/main/java/org/traccar/model/Device.java index b7cffac49..2c582328e 100644 --- a/src/main/java/org/traccar/model/Device.java +++ b/src/main/java/org/traccar/model/Device.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2023 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,7 +22,19 @@ import org.traccar.storage.StorageName; import java.util.Date; @StorageName("tc_devices") -public class Device extends GroupedModel implements Disableable { +public class Device extends GroupedModel implements Disableable, Schedulable { + + private long calendarId; + + @Override + public long getCalendarId() { + return calendarId; + } + + @Override + public void setCalendarId(long calendarId) { + this.calendarId = calendarId; + } private String name; diff --git a/src/main/java/org/traccar/model/ExtendedModel.java b/src/main/java/org/traccar/model/ExtendedModel.java index 6a4f502f7..d5cd094da 100644 --- a/src/main/java/org/traccar/model/ExtendedModel.java +++ b/src/main/java/org/traccar/model/ExtendedModel.java @@ -91,7 +91,8 @@ public class ExtendedModel extends BaseModel { public String getString(String key, String defaultValue) { if (attributes.containsKey(key)) { - return attributes.get(key).toString(); + Object value = attributes.get(key); + return value != null ? value.toString() : null; } else { return defaultValue; } diff --git a/src/main/java/org/traccar/model/Geofence.java b/src/main/java/org/traccar/model/Geofence.java index 9259028fb..ca6293651 100644 --- a/src/main/java/org/traccar/model/Geofence.java +++ b/src/main/java/org/traccar/model/Geofence.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,19 @@ import org.traccar.storage.StorageName; import java.text.ParseException; @StorageName("tc_geofences") -public class Geofence extends ScheduledModel { +public class Geofence extends ExtendedModel implements Schedulable { + + private long calendarId; + + @Override + public long getCalendarId() { + return calendarId; + } + + @Override + public void setCalendarId(long calendarId) { + this.calendarId = calendarId; + } private String name; diff --git a/src/main/java/org/traccar/model/Notification.java b/src/main/java/org/traccar/model/Notification.java index b6a6e4cf5..6dcd9c9de 100644 --- a/src/main/java/org/traccar/model/Notification.java +++ b/src/main/java/org/traccar/model/Notification.java @@ -24,7 +24,19 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import org.traccar.storage.StorageName; @StorageName("tc_notifications") -public class Notification extends ScheduledModel { +public class Notification extends ExtendedModel implements Schedulable { + + private long calendarId; + + @Override + public long getCalendarId() { + return calendarId; + } + + @Override + public void setCalendarId(long calendarId) { + this.calendarId = calendarId; + } private boolean always; diff --git a/src/main/java/org/traccar/model/Position.java b/src/main/java/org/traccar/model/Position.java index 3ed340703..6685cab95 100644 --- a/src/main/java/org/traccar/model/Position.java +++ b/src/main/java/org/traccar/model/Position.java @@ -42,7 +42,7 @@ public class Position extends Message { public static final String KEY_ODOMETER = "odometer"; // meters public static final String KEY_ODOMETER_SERVICE = "serviceOdometer"; // meters public static final String KEY_ODOMETER_TRIP = "tripOdometer"; // meters - public static final String KEY_HOURS = "hours"; + public static final String KEY_HOURS = "hours"; // milliseconds public static final String KEY_STEPS = "steps"; public static final String KEY_HEART_RATE = "heartRate"; public static final String KEY_INPUT = "input"; diff --git a/src/main/java/org/traccar/model/Report.java b/src/main/java/org/traccar/model/Report.java index 1556ecc9e..2ee7ae288 100644 --- a/src/main/java/org/traccar/model/Report.java +++ b/src/main/java/org/traccar/model/Report.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2022 - 2023 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. @@ -18,7 +18,19 @@ package org.traccar.model; import org.traccar.storage.StorageName; @StorageName("tc_reports") -public class Report extends ScheduledModel { +public class Report extends ExtendedModel implements Schedulable { + + private long calendarId; + + @Override + public long getCalendarId() { + return calendarId; + } + + @Override + public void setCalendarId(long calendarId) { + this.calendarId = calendarId; + } private String type; diff --git a/src/main/java/org/traccar/model/ScheduledModel.java b/src/main/java/org/traccar/model/Schedulable.java index 9e6a4b9a6..331e77583 100644 --- a/src/main/java/org/traccar/model/ScheduledModel.java +++ b/src/main/java/org/traccar/model/Schedulable.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2023 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,15 +16,7 @@ */ package org.traccar.model; -public class ScheduledModel extends ExtendedModel { - - private long calendarId; - - public long getCalendarId() { - return calendarId; - } - - public void setCalendarId(long calendarId) { - this.calendarId = calendarId; - } +public interface Schedulable { + long getCalendarId(); + void setCalendarId(long calendarId); } diff --git a/src/main/java/org/traccar/notification/NotificatorManager.java b/src/main/java/org/traccar/notification/NotificatorManager.java index c3ee7b480..b6ee760f3 100644 --- a/src/main/java/org/traccar/notification/NotificatorManager.java +++ b/src/main/java/org/traccar/notification/NotificatorManager.java @@ -66,7 +66,7 @@ public class NotificatorManager { public Notificator getNotificator(String type) { var clazz = NOTIFICATORS_ALL.get(type); - if (clazz != null) { + if (clazz != null && types.contains(type)) { var notificator = injector.getInstance(clazz); if (notificator != null) { return notificator; diff --git a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java index aa19e9e41..8896dcfb0 100644 --- a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java @@ -614,6 +614,23 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { case "JN5": buf.readUnsignedInt(); // pto fuel break; + case "IN0": + position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0); + break; + case "IN1": + case "IN2": + case "IN3": + position.set(Position.PREFIX_IN + key.charAt(2), buf.readUnsignedByte() > 0); + break; + case "HA": + position.set(Position.KEY_ALARM, buf.readUnsignedByte() > 0 ? Position.ALARM_ACCELERATION : null); + break; + case "HB": + position.set(Position.KEY_ALARM, buf.readUnsignedByte() > 0 ? Position.ALARM_BRAKING : null); + break; + case "HC": + position.set(Position.KEY_ALARM, buf.readUnsignedByte() > 0 ? Position.ALARM_CORNERING : null); + break; default: break; } diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java index e0dd1d62d..c30398d36 100644 --- a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java @@ -64,7 +64,7 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { .number("(d+),") // course .number("(-?d+),") // altitude .number("(d+),") // odometer - .number("d+,") // runtime + .number("(d+),") // engine hours .number("(x+),") // status .number("(x+)?,") // input .number("(x+)?,") // output @@ -235,8 +235,8 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { position.setValid(parser.next().equals("A")); position.setFixTime(position.getDeviceTime()); - position.set(Position.KEY_SATELLITES, parser.nextInt()); position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.set(Position.KEY_SATELLITES, parser.nextInt()); position.setLatitude(parser.nextDouble()); position.setLongitude(parser.nextDouble()); @@ -290,6 +290,7 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { position.setAltitude(parser.nextInt()); position.set(Position.KEY_ODOMETER, parser.nextLong()); + position.set(Position.KEY_HOURS, parser.nextLong() * 1000); long status = parser.nextHexLong(); position.set(Position.KEY_RSSI, BitUtil.between(status, 3, 8)); diff --git a/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java b/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java index 4e5200f37..4d8e7e7ea 100644 --- a/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java @@ -153,7 +153,7 @@ public class FreematicsProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_RSSI, Integer.parseInt(value)); break; case 0x82: - position.set(Position.KEY_DEVICE_TEMP, Integer.parseInt(value) * 0.1); + position.set(Position.KEY_DEVICE_TEMP, Double.parseDouble(value) * 0.1); break; case 0x104: position.set(Position.KEY_ENGINE_LOAD, Integer.parseInt(value)); diff --git a/src/main/java/org/traccar/protocol/GalileoFrameDecoder.java b/src/main/java/org/traccar/protocol/GalileoFrameDecoder.java index c23d26c83..d90e48287 100644 --- a/src/main/java/org/traccar/protocol/GalileoFrameDecoder.java +++ b/src/main/java/org/traccar/protocol/GalileoFrameDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2023 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,7 +22,7 @@ import org.traccar.BaseFrameDecoder; public class GalileoFrameDecoder extends BaseFrameDecoder { - private static final int MESSAGE_MINIMUM_LENGTH = 5; + private static final int MESSAGE_MINIMUM_LENGTH = 6; @Override protected Object decode( @@ -32,9 +32,15 @@ public class GalileoFrameDecoder extends BaseFrameDecoder { return null; } - int length = buf.getUnsignedShortLE(buf.readerIndex() + 1) & 0x7fff; - if (buf.readableBytes() >= (length + MESSAGE_MINIMUM_LENGTH)) { - return buf.readRetainedSlice(length + MESSAGE_MINIMUM_LENGTH); + int length; + if (buf.getByte(buf.readerIndex()) == 0x01 && buf.getUnsignedMedium(buf.readerIndex() + 3) == 0x01001c) { + length = 3 + buf.getUnsignedShort(buf.readerIndex() + 1); + } else { + length = 5 + (buf.getUnsignedShortLE(buf.readerIndex() + 1) & 0x7fff); + } + + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); } return null; diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java index 0135e78b7..911af8d73 100644 --- a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2023 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,9 @@ */ package org.traccar.protocol; +import io.netty.buffer.Unpooled; import org.traccar.BaseProtocolDecoder; +import org.traccar.helper.DataConverter; import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; @@ -68,357 +70,6 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .text("$").optional() .compile(); - private static final Pattern PATTERN_INF = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF):GTINF,") - .number("[0-9A-Z]{2}xxxx,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("(?:[0-9A-Z]{17},)?") // vin - .expression("(?:[^,]+)?,") // device name - .number("(xx),") // state - .expression("(?:[0-9Ff]{20})?,") // iccid - .number("(d{1,2}),") // rssi - .number("d{1,2},") - .expression("[01]{1,2},") // external power - .number("([d.]+)?,") // odometer or external power - .number("d*,") // backup battery or lightness - .number("(d+.d+),") // battery - .expression("([01]),") // charging - .number("(?:d),") // led - .number("(?:d)?,") // gps on need - .number("(?:d)?,") // gps antenna type - .number("(?:d)?,").optional() // gps antenna state - .number("d{14},") // last fix time - .groupBegin() - .number("(d+),") // battery percentage - .number("[d.]*,") // flash type / power - .number("(-?[d.]+)?,,,") // temperature - .or() - .expression("(?:[01])?,").optional() // pin15 mode - .number("(d+)?,") // adc1 - .number("(d+)?,").optional() // adc2 - .number("(xx)?,") // digital input - .number("(xx)?,") // digital output - .number("[-+]dddd,") // timezone - .expression("[01],") // daylight saving - .or() - .any() - .groupEnd() - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd),") // time (hhmmss) - .number("(xxxx)") // counter - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_VER = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF):GTVER,") - .number("[0-9A-Z]{2}xxxx,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .expression("([^,]*),") // device type - .number("(xxxx),") // firmware version - .number("(xxxx),") // hardware version - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd),") // time (hhmmss) - .number("(xxxx)") // counter - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_LOCATION = new PatternBuilder() - .number("(d{1,2}.?d?)?,") // hdop - .number("(d{1,3}.d)?,") // speed - .number("(d{1,3}.?d?)?,") // course - .number("(-?d{1,5}.d)?,") // altitude - .number("(-?d{1,3}.d{6})?,") // longitude - .number("(-?d{1,2}.d{6})?,") // latitude - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(d+)?,") // mcc - .number("(d+)?,") // mnc - .groupBegin() - .number("(d+),") // lac - .number("(d+),") // cid - .or() - .number("(x+)?,") // lac - .number("(x+)?,") // cid - .groupEnd() - .number("(?:d+|(d+.d))?,") // rssi / odometer - .compile(); - - private static final Pattern PATTERN_OBD = new PatternBuilder() - .text("+RESP:GTOBD,") - .number("[0-9A-Z]{2}xxxx,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("(?:[0-9A-Z]{17})?,") // vin - .expression("[^,]{0,20},") // device name - .expression("[01],") // report type - .number("x{1,8},") // report mask - .expression("(?:[0-9A-Z]{17})?,") // vin - .number("[01],") // obd connect - .number("(?:d{1,5})?,") // obd voltage - .number("(?:x{8})?,") // support pids - .number("(d{1,5})?,") // engine rpm - .number("(d{1,3})?,") // speed - .number("(-?d{1,3})?,") // coolant temp - .number("(d+.?d*|Inf|NaN)?,") // fuel consumption - .number("(d{1,5})?,") // dtcs cleared distance - .number("(?:d{1,5})?,") - .expression("([01])?,") // obd connect - .number("(d{1,3})?,") // number of dtcs - .number("(x*),") // dtcs - .number("(d{1,3})?,") // throttle - .number("(?:d{1,3})?,") // engine load - .number("(d{1,3})?,") // fuel level - .expression("(?:[0-9A],)?") // obd protocol - .number("(d+),") // odometer - .expression(PATTERN_LOCATION.pattern()) - .number("(d{1,7}.d)?,") // odometer - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_FRI = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF):GT...,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("(?:([0-9A-Z]{17}),)?") // vin - .expression("[^,]*,") // device name - .number("(d+)?,") // power - .number("(d{1,2}),").optional() // report type - .number("d{1,2},").optional() // count - .number("d*,").optional() // reserved - .number("(d+),").optional() // battery - .expression("((?:") - .expression(PATTERN_LOCATION.pattern()) - .expression(")+)") - .groupBegin() - .number("d{1,2},,") - .number("(d{1,3}),") // battery - .number("[01],") // mode - .number("(?:[01])?,") // motion - .number("(?:-?d{1,2}.d)?,") // temperature - .or() - .number("(d{1,7}.d)?,") // odometer - .number("(d{5}:dd:dd)?,") // hour meter - .number("(x+)?,") // adc 1 - .number("(x+)?,") // adc 2 - .number("(d{1,3})?,") // battery - .number("(?:(xx)(xx)(xx))?,") // device status - .number("(d+)?,") // rpm - .number("(?:d+.?d*|Inf|NaN)?,") // fuel consumption - .number("(d+)?,") // fuel level - .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) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_ERI = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF):GTERI,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("(x{8}),") // mask - .number("(d+)?,") // power - .number("d{1,2},") // report type - .number("d{1,2},") // count - .expression("((?:") - .expression(PATTERN_LOCATION.pattern()) - .expression(")+)") - .groupBegin() - .number("(d{1,7}.d)?,") // odometer - .number("(d{5}:dd:dd)?,") // hour meter - .number("(x+)?,") // adc 1 - .number("(x+)?,").optional() // adc 2 - .groupBegin() - .number("(x+)?,") // adc 3 - .number("(xx),") // inputs - .number("(xx),") // outputs - .or() - .number("(d{1,3})?,") // battery - .number("(?:(xx)(xx)(xx))?,") // device status - .groupEnd() - .expression("(.*)") // additional data - .or() - .number("d*,,") - .number("(d+),") // battery - .any() - .groupEnd() - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_IGN = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF):GTIG[NF],") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("d+,") // ignition off duration - .expression(PATTERN_LOCATION.pattern()) - .number("(d{5}:dd:dd)?,") // hour meter - .number("(d{1,7}.d)?,") // odometer - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_LSW = new PatternBuilder() - .text("+RESP:").expression("GT[LT]SW,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("[01],") // type - .number("([01]),") // state - .expression(PATTERN_LOCATION.pattern()) - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_IDA = new PatternBuilder() - .text("+RESP:GTIDA,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,,") // device name - .number("([^,]+),") // rfid - .expression("[01],") // report type - .number("1,") // count - .expression(PATTERN_LOCATION.pattern()) - .number("(d+.d),") // odometer - .text(",,,,") - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_WIF = new PatternBuilder() - .text("+RESP:GTWIF,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("(d+),") // count - .number("((?:x{12},-?d+,,,,)+),,,,") // wifi - .number("(d{1,3}),") // battery - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_GSM = new PatternBuilder() - .text("+RESP:GTGSM,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("(?:STR|CTN|NMR|RTL),") // fix type - .expression("(.*)") // cells - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_PNA = new PatternBuilder() - .text("+RESP:GT").expression("P[NF]A,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_DAR = new PatternBuilder() - .text("+RESP:GTDAR,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("(d),") // warning type - .number("(d{1,2}),,,") // fatigue degree - .expression(PATTERN_LOCATION.pattern()) - .any() - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF):GT...,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("d*,") - .number("(x{1,2}),") // report type - .number("d{1,2},") // count - .number("d*,").optional() // reserved - .expression(PATTERN_LOCATION.pattern()) - .groupBegin() - .number("(?:(d{1,7}.d)|0)?,").optional() // odometer - .number("(d{1,3})?,") // battery - .or() - .number("(d{1,7}.d)?,") // odometer - .groupEnd() - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)") // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_BASIC = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF)").text(":") - .expression("GT...,") - .number("(?:[0-9A-Z]{2}xxxx)?,").optional() // protocol version - .number("(d{15}|x{14}),") // imei - .any() - .text(",") - .number("(d{1,2})?,") // hdop - .number("(d{1,3}.d)?,") // speed - .number("(d{1,3})?,") // course - .number("(-?d{1,5}.d)?,") // altitude - .number("(-?d{1,3}.d{6})?,") // longitude - .number("(-?d{1,2}.d{6})?,") // latitude - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(d+),") // mcc - .number("(d+),") // mnc - .number("(x+),") // lac - .number("(x+),").optional(4) // cell - .any() - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - private Object decodeAck(Channel channel, SocketAddress remoteAddress, String sentence, String type) { Parser parser = new Parser(PATTERN_ACK, sentence); if (parser.matches()) { @@ -470,13 +121,54 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { private Long parseHours(String hoursString) { if (hoursString != null) { String[] hours = hoursString.split(":"); - return (long) (Integer.parseInt(hours[0]) * 3600 - + (hours.length > 1 ? Integer.parseInt(hours[1]) * 60 : 0) + return (Integer.parseInt(hours[0]) * 3600L + + (hours.length > 1 ? Integer.parseInt(hours[1]) * 60L : 0) + (hours.length > 2 ? Integer.parseInt(hours[2]) : 0)) * 1000; } return null; } + private static final Pattern PATTERN_INF = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GTINF,") + .number("[0-9A-Z]{2}xxxx,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:[0-9A-Z]{17},)?") // vin + .expression("(?:[^,]+)?,") // device name + .number("(xx),") // state + .expression("(?:[0-9Ff]{20})?,") // iccid + .number("(d{1,2}),") // rssi + .number("d{1,2},") + .expression("[01]{1,2},") // external power + .number("([d.]+)?,") // odometer or external power + .number("d*,") // backup battery or lightness + .number("(d+.d+),") // battery + .expression("([01]),") // charging + .number("(?:d),") // led + .number("(?:d)?,") // gps on need + .number("(?:d)?,") // gps antenna type + .number("(?:d)?,").optional() // gps antenna state + .number("d{14},") // last fix time + .groupBegin() + .number("(d+),") // battery percentage + .number("[d.]*,") // flash type / power + .number("(-?[d.]+)?,,,") // temperature + .or() + .expression("(?:[01])?,").optional() // pin15 mode + .number("(d+)?,") // adc1 + .number("(d+)?,").optional() // adc2 + .number("(xx)?,") // digital input + .number("(xx)?,") // digital output + .number("[-+]dddd,") // timezone + .expression("[01],") // daylight saving + .or() + .any() + .groupEnd() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(xxxx)") // counter + .text("$").optional() + .compile(); + private Object decodeInf(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_INF, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -537,6 +229,20 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_VER = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GTVER,") + .number("[0-9A-Z]{2}xxxx,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .expression("([^,]*),") // device type + .number("(xxxx),") // firmware version + .number("(xxxx),") // hardware version + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(xxxx)") // counter + .text("$").optional() + .compile(); + private Object decodeVer(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_VER, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -557,6 +263,28 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { parser.skip(19); } + private static final Pattern PATTERN_LOCATION = new PatternBuilder() + .number("(d{1,2}.?d?)?,") // hdop + .number("(d{1,3}.d)?,") // speed + .number("(d{1,3}.?d?)?,") // course + .number("(-?d{1,5}.d)?,") // altitude + .number("(-?d{1,3}.d{6})?,") // longitude + .number("(-?d{1,2}.d{6})?,") // latitude + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(d+)?,") // mcc + .number("(d+)?,") // mnc + .groupBegin() + .number("(d+),") // lac + .number("(d+),") // cid + .or() + .number("(x+)?,") // lac + .number("(x+)?,") // cid + .groupEnd() + .number("(?:d+|(d+.d))?,") // rssi / odometer + .compile(); + private void decodeLocation(Position position, Parser parser) { Double hdop = parser.nextDouble(); position.setValid(hdop == null || hdop > 0); @@ -591,6 +319,41 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { } } + private static final Pattern PATTERN_OBD = new PatternBuilder() + .text("+RESP:GTOBD,") + .number("[0-9A-Z]{2}xxxx,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:[0-9A-Z]{17})?,") // vin + .expression("[^,]{0,20},") // device name + .expression("[01],") // report type + .number("x{1,8},") // report mask + .expression("(?:[0-9A-Z]{17})?,") // vin + .number("[01],") // obd connect + .number("(?:d{1,5})?,") // obd voltage + .number("(?:x{8})?,") // support pids + .number("(d{1,5})?,") // engine rpm + .number("(d{1,3})?,") // speed + .number("(-?d{1,3})?,") // coolant temp + .number("(d+.?d*|Inf|NaN)?,") // fuel consumption + .number("(d{1,5})?,") // dtcs cleared distance + .number("(?:d{1,5})?,") + .expression("([01])?,") // obd connect + .number("(d{1,3})?,") // number of dtcs + .number("(x*),") // dtcs + .number("(d{1,3})?,") // throttle + .number("(?:d{1,3})?,") // engine load + .number("(d{1,3})?,") // fuel level + .expression("(?:[0-9A],)?") // obd protocol + .number("(d+),") // odometer + .expression(PATTERN_LOCATION.pattern()) + .number("(d{1,7}.d)?,") // odometer + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeObd(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_OBD, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -637,7 +400,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[index++]); position.setDeviceId(deviceSession.getDeviceId()); - index += 1; // device name + String deviceName = values[index++]; index += 1; // report type index += 1; // canbus state long reportMask = Long.parseLong(values[index++], 16); @@ -679,11 +442,11 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { if (BitUtil.check(reportMask, 11) && !values[index++].isEmpty()) { position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(Double.parseDouble(values[index - 1]))); } - if (BitUtil.check(reportMask, 12)) { - position.set(Position.KEY_DRIVING_TIME, Double.parseDouble(values[index++])); + if (BitUtil.check(reportMask, 12) && !values[index++].isEmpty()) { + position.set(Position.KEY_DRIVING_TIME, Double.parseDouble(values[index - 1])); } - if (BitUtil.check(reportMask, 13)) { - position.set("idleHours", Double.parseDouble(values[index++])); + if (BitUtil.check(reportMask, 13) && !values[index++].isEmpty()) { + position.set("idleHours", Double.parseDouble(values[index - 1])); } if (BitUtil.check(reportMask, 14) && !values[index++].isEmpty()) { position.set("idleFuelConsumption", Double.parseDouble(values[index - 1])); @@ -709,8 +472,19 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { if (BitUtil.check(reportMask, 21) && !values[index++].isEmpty()) { position.set("engineOverspeed", Double.parseDouble(values[index - 1])); } - if (BitUtil.check(reportMask, 29)) { - reportMaskExt = Long.parseLong(values[index++], 16); + if ("GV350M".equals(deviceName)) { + if (BitUtil.check(reportMask, 22)) { + index += 1; // impulse distance + } + if (BitUtil.check(reportMask, 23)) { + index += 1; // gross vehicle weight + } + if (BitUtil.check(reportMask, 24)) { + index += 1; // catalyst liquid level + } + } + if (BitUtil.check(reportMask, 29) && !values[index++].isEmpty()) { + reportMaskExt = Long.parseLong(values[index - 1], 16); } if (BitUtil.check(reportMaskExt, 0) && !values[index++].isEmpty()) { position.set("adBlueLevel", Integer.parseInt(values[index - 1])); @@ -841,6 +615,51 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { } } + private static final Pattern PATTERN_FRI = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GT...,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:([0-9A-Z]{17}),)?") // vin + .expression("[^,]*,") // device name + .number("(d+)?,") // power + .number("(d{1,2}),").optional() // report type + .number("d{1,2},").optional() // count + .number("d*,").optional() // reserved + .number("(d+),").optional() // battery + .expression("((?:") + .expression(PATTERN_LOCATION.pattern()) + .expression(")+)") + .groupBegin() + .number("d{1,2},,") + .number("(d{1,3}),") // battery + .number("[01],") // mode + .number("(?:[01])?,") // motion + .number("(?:-?d{1,2}.d)?,") // temperature + .or() + .number("(d{1,7}.d)?,") // odometer + .number("(d{5}:dd:dd)?,") // hour meter + .number("(x+)?,") // adc 1 + .number("(x+)?,") // adc 2 + .number("(d{1,3})?,") // battery + .number("(?:(xx)(xx)(xx))?,") // device status + .number("(d+)?,") // rpm + .number("(?:d+.?d*|Inf|NaN)?,") // fuel consumption + .number("(d+)?,") // fuel level + .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) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeFri(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_FRI, sentence); if (!parser.matches()) { @@ -921,6 +740,44 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return positions; } + private static final Pattern PATTERN_ERI = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GTERI,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("(x{8}),") // mask + .number("(d+)?,") // power + .number("d{1,2},") // report type + .number("d{1,2},") // count + .expression("((?:") + .expression(PATTERN_LOCATION.pattern()) + .expression(")+)") + .groupBegin() + .number("(d{1,7}.d)?,") // odometer + .number("(d{5}:dd:dd)?,") // hour meter + .number("(x+)?,") // adc 1 + .number("(x+)?,").optional() // adc 2 + .groupBegin() + .number("(x+)?,") // adc 3 + .number("(xx),") // inputs + .number("(xx),") // outputs + .or() + .number("(d{1,3})?,") // battery + .number("(?:(xx)(xx)(xx))?,") // device status + .groupEnd() + .expression("(.*)") // additional data + .or() + .number("d*,,") + .number("(d+),") // battery + .any() + .groupEnd() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeEri(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_ERI, sentence); if (!parser.matches()) { @@ -1024,6 +881,22 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return positions; } + private static final Pattern PATTERN_IGN = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GTIG[NF],") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("d+,") // ignition off duration + .expression(PATTERN_LOCATION.pattern()) + .number("(d{5}:dd:dd)?,") // hour meter + .number("(d{1,7}.d)?,") // odometer + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeIgn(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_IGN, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1042,6 +915,21 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_LSW = new PatternBuilder() + .text("+RESP:").expression("GT[LT]SW,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("[01],") // type + .number("([01]),") // state + .expression(PATTERN_LOCATION.pattern()) + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeLsw(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_LSW, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1058,6 +946,24 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_IDA = new PatternBuilder() + .text("+RESP:GTIDA,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,,") // device name + .number("([^,]+),") // rfid + .expression("[01],") // report type + .number("1,") // count + .expression(PATTERN_LOCATION.pattern()) + .number("(d+.d),") // odometer + .text(",,,,") + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeIda(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_IDA, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1076,6 +982,21 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_WIF = new PatternBuilder() + .text("+RESP:GTWIF,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("(d+),") // count + .number("((?:x{12},-?d+,,,,)+),,,,") // wifi + .number("(d{1,3}),") // battery + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeWif(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_WIF, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1102,6 +1023,19 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_GSM = new PatternBuilder() + .text("+RESP:GTGSM,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:STR|CTN|NMR|RTL),") // fix type + .expression("(.*)") // cells + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeGsm(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_GSM, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1128,6 +1062,18 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_PNA = new PatternBuilder() + .text("+RESP:GT").expression("P[NF]A,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodePna(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_PNA, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1142,6 +1088,22 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_DAR = new PatternBuilder() + .text("+RESP:GTDAR,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("(d),") // warning type + .number("(d{1,2}),,,") // fatigue degree + .expression(PATTERN_LOCATION.pattern()) + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeDar(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_DAR, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1165,6 +1127,165 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_DTT = new PatternBuilder() + .text("+RESP:GTDTT,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,,,") // device name + .number("d,") // data type + .number("d+,") // data length + .number("(x+),") // data + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private Object decodeDtt(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_DTT, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + getLastLocation(position, null); + + String data = Unpooled.wrappedBuffer(DataConverter.parseHex(parser.next())) + .toString(StandardCharsets.US_ASCII); + if (data.contains("COMB")) { + String[] values = data.split(","); + position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(values[2])); + } else { + position.set(Position.KEY_RESULT, data); + } + + decodeDeviceTime(position, parser); + + return position; + } + + private static final Pattern PATTERN_BAA = new PatternBuilder() + .text("+RESP:GTBAA,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("x+,") // index + .number("d,") // accessory type + .number("d,") // accessory model + .number("x+,") // alarm type + .number("(x{4}),") // append mask + .expression("((?:[^,]+,){0,6})") // accessory optionals + .expression(PATTERN_LOCATION.pattern()) + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private Object decodeBaa(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_BAA, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + int mask = parser.nextHexInt(); + String[] values = parser.next().split(","); + int index = 0; + if (BitUtil.check(mask, 0)) { + position.set("accessoryName", values[index++]); + } + if (BitUtil.check(mask, 1)) { + position.set("accessoryMac", values[index++]); + } + if (BitUtil.check(mask, 2)) { + position.set("accessoryStatus", Integer.parseInt(values[index++])); + } + if (BitUtil.check(mask, 3)) { + position.set("accessoryVoltage", Integer.parseInt(values[index++]) * 0.001); + } + if (BitUtil.check(mask, 4)) { + position.set("accessoryTemp", Integer.parseInt(values[index++])); + } + if (BitUtil.check(mask, 5)) { + position.set("accessoryHumidity", Integer.parseInt(values[index])); + } + + decodeLocation(position, parser); + + decodeDeviceTime(position, parser); + + return position; + } + + private static final Pattern PATTERN_BID = new PatternBuilder() + .text("+RESP:GTBID,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("d,") // count + .number("d,") // accessory model + .number("(x{4}),") // append mask + .expression("((?:[^,]+,){0,2})") // accessory optionals + .expression(PATTERN_LOCATION.pattern()) + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private Object decodeBid(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_BID, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + int mask = parser.nextHexInt(); + String[] values = parser.next().split(","); + int index = 0; + if (BitUtil.check(mask, 1)) { + position.set("accessoryMac", values[index++]); + } + if (BitUtil.check(mask, 3)) { + position.set("accessoryVoltage", Integer.parseInt(values[index]) * 0.001); + } + + decodeLocation(position, parser); + + decodeDeviceTime(position, parser); + + return position; + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GT...,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("d*,") + .number("(x{1,2}),") // report type + .number("d{1,2},") // count + .number("d*,").optional() // reserved + .expression(PATTERN_LOCATION.pattern()) + .groupBegin() + .number("(?:(d{1,7}.d)|0)?,").optional() // odometer + .number("(d{1,3})?,") // battery + .or() + .number("(d{1,7}.d)?,") // odometer + .groupEnd() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)") // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeOther(Channel channel, SocketAddress remoteAddress, String sentence, String type) { Parser parser = new Parser(PATTERN, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1215,6 +1336,34 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_BASIC = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF)").text(":") + .expression("GT...,") + .number("(?:[0-9A-Z]{2}xxxx)?,").optional() // protocol version + .number("(d{15}|x{14}),") // imei + .any() + .text(",") + .number("(d{1,2})?,") // hdop + .number("(d{1,3}.d)?,") // speed + .number("(d{1,3})?,") // course + .number("(-?d{1,5}.d)?,") // altitude + .number("(-?d{1,3}.d{6})?,") // longitude + .number("(-?d{1,2}.d{6})?,") // latitude + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(x+),") // lac + .number("(x+),").optional(4) // cell + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeBasic(Channel channel, SocketAddress remoteAddress, String sentence, String type) { Parser parser = new Parser(PATTERN_BASIC, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1359,6 +1508,15 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { case "DAR": result = decodeDar(channel, remoteAddress, sentence); break; + case "DTT": + result = decodeDtt(channel, remoteAddress, sentence); + break; + case "BAA": + result = decodeBaa(channel, remoteAddress, sentence); + break; + case "BID": + result = decodeBid(channel, remoteAddress, sentence); + break; default: result = decodeOther(channel, remoteAddress, sentence, type); break; diff --git a/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java b/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java index 77649a041..f17ea0e08 100644 --- a/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java @@ -93,14 +93,14 @@ public class GoSafeProtocolDecoder extends BaseProtocolDecoder { position.setSpeed(UnitsConverter.knotsFromKph(Integer.parseInt(values[index - 1]))); } position.setCourse(Integer.parseInt(values[index++])); - if (index < values.length) { - position.setAltitude(Integer.parseInt(values[index++])); + if (index < values.length && !values[index++].isEmpty()) { + position.setAltitude(Integer.parseInt(values[index - 1])); } - if (index < values.length) { - position.set(Position.KEY_HDOP, Double.parseDouble(values[index++])); + if (index < values.length && !values[index++].isEmpty()) { + position.set(Position.KEY_HDOP, Double.parseDouble(values[index - 1])); } - if (index < values.length) { - position.set(Position.KEY_VDOP, Double.parseDouble(values[index++])); + if (index < values.length && !values[index++].isEmpty()) { + position.set(Position.KEY_VDOP, Double.parseDouble(values[index - 1])); } break; case "GSM": diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java index 7013533bc..5db06fc41 100644 --- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java @@ -806,7 +806,11 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { } if (hasLbs(type) && buf.readableBytes() > 6) { - decodeLbs(position, buf, type, hasStatus(type) && type != MSG_LBS_ALARM && type != MSG_LBS_STATUS); + boolean hasLength = hasStatus(type) + && type != MSG_LBS_STATUS + && type != MSG_LBS_ALARM + && (type != MSG_GPS_LBS_STATUS_1 || variant != Variant.VXT01); + decodeLbs(position, buf, type, hasLength); } if (hasStatus(type)) { @@ -828,6 +832,11 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { } } + if (type == MSG_STATUS && variant == Variant.VXT01) { + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + } + if (type == MSG_GPS_LBS_1) { if (variant == Variant.GT06E_CARD) { position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); @@ -1408,6 +1417,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { variant = Variant.VXT01; } else if (header == 0x7878 && type == MSG_GPS_LBS_STATUS_1 && length == 0x24) { variant = Variant.VXT01; + } else if (header == 0x7878 && type == MSG_STATUS && length == 0x0a) { + variant = Variant.VXT01; } else if (header == 0x7878 && type == MSG_LBS_MULTIPLE_3 && length == 0x31) { variant = Variant.WANWAY_S20; } else if (header == 0x7878 && type == MSG_LBS_MULTIPLE_3 && length == 0x2e) { diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java index ee5ab19d6..beb1ec41a 100644 --- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2023 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. @@ -484,6 +484,10 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY, Integer.parseInt(lockStatus.substring(2, 5)) * 0.01); } break; + case 0x56: + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte() * 10); + buf.readUnsignedByte(); // reserved + break; case 0x60: position.set(Position.KEY_EVENT, buf.readUnsignedShort()); buf.skipBytes(length - 2); @@ -692,6 +696,8 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { case 0xFE: if (length == 1) { position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + } else if (length == 2) { + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1); } else { int mark = buf.readUnsignedByte(); if (mark == 0x7C) { @@ -880,9 +886,15 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { case 0x0539: position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort() * 0.01); break; + case 0x052B: + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte()); + break; case 0x052D: position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedByte() - 40); break; + case 0x052E: + position.set("airTemp", buf.readUnsignedByte() - 40); + break; case 0x0530: position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.001); break; @@ -892,6 +904,12 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { case 0x0536: position.set(Position.KEY_RPM, buf.readUnsignedShort()); break; + case 0x053D: + position.set("intakePressure", buf.readUnsignedShort() * 0.1); + break; + case 0x0544: + position.set("liquidLevel", buf.readUnsignedByte()); + break; case 0x0547: case 0x0548: position.set(Position.KEY_THROTTLE, buf.readUnsignedByte()); diff --git a/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java b/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java index f2e5eb905..1fdb1ece0 100644 --- a/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java @@ -143,7 +143,7 @@ public class MiniFinderProtocolDecoder extends BaseProtocolDecoder { } DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); - if (deviceSession == null || !sentence.matches("![35A-D],.*")) { + if (deviceSession == null || !sentence.matches("![345A-D],.*")) { return null; } @@ -161,6 +161,20 @@ public class MiniFinderProtocolDecoder extends BaseProtocolDecoder { return position; + } else if (type.equals("4")) { + + String[] values = sentence.split(","); + + getLastLocation(position, null); + + for (int i = 1; i <= 3; i++) { + if (!values[i + 1].isEmpty()) { + position.set("phone" + i, values[i + 1]); + } + } + + return position; + } else if (type.equals("5")) { String[] values = sentence.split(","); diff --git a/src/main/java/org/traccar/protocol/Minifinder2Protocol.java b/src/main/java/org/traccar/protocol/Minifinder2Protocol.java index 61a43d8db..842194235 100644 --- a/src/main/java/org/traccar/protocol/Minifinder2Protocol.java +++ b/src/main/java/org/traccar/protocol/Minifinder2Protocol.java @@ -35,7 +35,7 @@ public class Minifinder2Protocol extends BaseProtocol { addServer(new TrackerServer(config, getName(), false) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { - pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1200, 2, 2, 4, 0, true)); + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 2048, 2, 2, 4, 0, true)); pipeline.addLast(new Minifinder2ProtocolEncoder(Minifinder2Protocol.this)); pipeline.addLast(new Minifinder2ProtocolDecoder(Minifinder2Protocol.this)); } diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java index f660f2e92..cd8d8e0cf 100644 --- a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java @@ -151,7 +151,7 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { sendResponse(channel, remoteAddress, index, type, buf); } - if (type == MSG_DATA) { + if (type == MSG_DATA || type == MSG_SERVICES) { List<Position> positions = new LinkedList<>(); Set<Integer> keys = new HashSet<>(); @@ -239,6 +239,14 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { case 0x24: position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); long status = buf.readUnsignedIntLE(); + if (BitUtil.check(status, 4)) { + position.set(Position.KEY_CHARGE, true); + } + if (BitUtil.check(status, 7)) { + position.set(Position.KEY_ARCHIVE, true); + } + position.set(Position.KEY_MOTION, BitUtil.check(status, 9)); + position.set(Position.KEY_RSSI, BitUtil.between(status, 19, 24)); position.set(Position.KEY_BATTERY_LEVEL, BitUtil.from(status, 24)); position.set(Position.KEY_STATUS, status); break; diff --git a/src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java b/src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java deleted file mode 100644 index 5b610013a..000000000 --- a/src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.ByteBuf; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import org.traccar.BaseFrameDecoder; - -public class NdtpV6FrameDecoder extends BaseFrameDecoder { - - @Override - protected Object decode( - ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { - - return buf; - } - -} diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java index 77df0deb7..2122d5081 100644 --- a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java @@ -22,6 +22,7 @@ import org.traccar.BaseProtocolDecoder; import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; +import org.traccar.helper.BitUtil; import org.traccar.helper.DataConverter; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; @@ -92,22 +93,48 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { } } + private void decodeDriver(Position position, String part1, String part2) { + Long driverIdPart1 = (Long) position.getAttributes().remove(part1); + Long driverIdPart2 = (Long) position.getAttributes().remove(part2); + if (driverIdPart1 != null && driverIdPart2 != null) { + ByteBuf driverId = Unpooled.copyLong(driverIdPart1, driverIdPart2); + position.set(Position.KEY_DRIVER_UNIQUE_ID, driverId.toString(StandardCharsets.US_ASCII)); + driverId.release(); + } + } + private void decodeParameter(Position position, int id, ByteBuf buf, int length) { switch (id) { case 2: case 3: case 4: - position.set("di" + (id - 1), readValue(buf, length, false)); - break; case 5: - position.set(Position.KEY_IGNITION, readValue(buf, length, false) == 1); + position.set(Position.PREFIX_IN + (id - 1), readValue(buf, length, false)); + break; + case 20: + position.set(Position.PREFIX_ADC + 3, readValue(buf, length, false)); + break; + case 21: + position.set(Position.PREFIX_ADC + 4, readValue(buf, length, false)); + break; + case 22: + position.set(Position.PREFIX_ADC + 1, readValue(buf, length, false)); + break; + case 23: + position.set(Position.PREFIX_ADC + 2, readValue(buf, length, false)); break; case 29: - position.set(Position.KEY_POWER, readValue(buf, length, false)); + position.set(Position.KEY_POWER, readValue(buf, length, false) * 0.001); break; case 30: position.set(Position.KEY_BATTERY, readValue(buf, length, false) * 0.001); break; + case 32: + position.set(Position.KEY_DEVICE_TEMP, readValue(buf, length, true)); + break; + case 65: + position.set(Position.KEY_ODOMETER, readValue(buf, length, true)); + break; case 74: position.set(Position.PREFIX_TEMP + 3, readValue(buf, length, true) * 0.1); break; @@ -116,6 +143,14 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { case 80: position.set(Position.PREFIX_TEMP + (id - 78), readValue(buf, length, true) * 0.1); break; + case 88: + if (readValue(buf, length, false) > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_JAMMING); + } + break; + case 95: + position.set(Position.KEY_OBD_SPEED, UnitsConverter.knotsFromKph(readValue(buf, length, true))); + break; case 134: if (readValue(buf, length, false) > 0) { position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); @@ -126,9 +161,45 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); } break; + case 150: + position.set(Position.KEY_OPERATOR, readValue(buf, length, false)); + break; + case 170: + position.set(Position.KEY_CHARGE, readValue(buf, length, false) > 0); + break; + case 173: + position.set(Position.KEY_MOTION, readValue(buf, length, false) > 0); + break; case 197: position.set(Position.KEY_RPM, readValue(buf, length, false) * 0.125); break; + case 251: + case 409: + position.set(Position.KEY_IGNITION, readValue(buf, length, false) > 0); + break; + case 410: + if (readValue(buf, length, false) > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_TOW); + } + break; + case 411: + if (readValue(buf, length, false) > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT); + } + break; + case 415: + if (readValue(buf, length, false) == 0) { + position.set(Position.KEY_ALARM, Position.ALARM_GPS_ANTENNA_CUT); + } + break; + case 645: + position.set(Position.KEY_OBD_ODOMETER, readValue(buf, length, true) * 1000); + break; + case 758: + if (readValue(buf, length, false) == 1) { + position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING); + } + break; default: position.set(Position.PREFIX_IO + id, readValue(buf, length, false)); break; @@ -166,7 +237,13 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedByte(); // timestamp extension if (type == MSG_EXTENDED_RECORDS) { - buf.readUnsignedByte(); // record extension + int recordExtension = buf.readUnsignedByte(); + int mergeRecordCount = BitUtil.from(recordExtension, 4); + int currentRecord = BitUtil.to(recordExtension, 4); + + if (currentRecord > 0 && currentRecord <= mergeRecordCount) { + position = positions.remove(positions.size() - 1); + } } buf.readUnsignedByte(); // priority (reserved) @@ -217,12 +294,13 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { decodeParameter(position, id, buf, 8); } - Long driverIdPart1 = (Long) position.getAttributes().remove(Position.PREFIX_IO + 126); - Long driverIdPart2 = (Long) position.getAttributes().remove(Position.PREFIX_IO + 127); - if (driverIdPart1 != null && driverIdPart2 != null) { - ByteBuf driverId = Unpooled.copyLong(driverIdPart1, driverIdPart2); - position.set(Position.KEY_DRIVER_UNIQUE_ID, driverId.toString(StandardCharsets.US_ASCII)); - driverId.release(); + decodeDriver(position, Position.PREFIX_IO + 126, Position.PREFIX_IO + 127); // can driver + decodeDriver(position, Position.PREFIX_IO + 155, Position.PREFIX_IO + 156); // tco driver + + Long tagIdPart1 = (Long) position.getAttributes().remove(Position.PREFIX_IO + 760); + Long tagIdPart2 = (Long) position.getAttributes().remove(Position.PREFIX_IO + 761); + if (tagIdPart1 != null && tagIdPart2 != null) { + position.set("tagId", Long.toHexString(tagIdPart1) + Long.toHexString(tagIdPart2)); } positions.add(position); diff --git a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java index aa23bfac5..193005e28 100644 --- a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java @@ -61,10 +61,10 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { @Override protected void init() { setFormat(getConfig().getString( - getProtocolName() + ".format", "#EDT#,#EID#,#PDT#,#LAT#,#LONG#,#SPD#,#HEAD#,#ODO#," + Keys.PROTOCOL_FORMAT.withPrefix(getProtocolName()), "#EDT#,#EID#,#PDT#,#LAT#,#LONG#,#SPD#,#HEAD#,#ODO#," + "#IN1#,#IN2#,#IN3#,#IN4#,#OUT1#,#OUT2#,#OUT3#,#OUT4#,#LAC#,#CID#,#VIN#,#VBAT#,#DEST#,#IGN#,#ENG#")); - setDateFormat(getConfig().getString(getProtocolName() + ".dateFormat", "yyMMddHHmmss")); + setDateFormat(getConfig().getString(Keys.PROTOCOL_DATE_FORMAT.withPrefix(getProtocolName()), "yyMMddHHmmss")); } public String[] getFormat(long deviceId) { diff --git a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java index d08bb92a8..9c749c8d9 100644 --- a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java @@ -72,12 +72,10 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder { .number("(x+)") // battery .expression("([^,]+)?") // adc .groupBegin() - .text(",") - .number("d,") // extended - .expression("([^,]+)?") // fuel + .number(",d+") // extended + .expression(",([^,]+)?") // fuel .groupBegin() - .text(",") - .expression("([^,]+)?") // temperature + .expression(",([^,]+)?") // temperature .groupBegin() .text(",") .groupBegin() @@ -91,9 +89,11 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder { .number("(d+)?|") // instant fuel .number("(d+)[%L]").optional() // fuel level .groupEnd("?") + .number(",(d+)").optional() // hours .groupEnd("?") .groupEnd("?") .groupEnd("?") + .any() .compile(); private String decodeAlarm(int value) { @@ -224,7 +224,7 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder { } } - if (parser.hasNextAny(6)) { + if (parser.hasNextAny(9)) { position.set(Position.KEY_RPM, parser.nextInt()); position.set(Position.KEY_ENGINE_LOAD, parser.nextInt()); position.set("airFlow", parser.nextInt()); @@ -242,6 +242,10 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); } + if (parser.hasNext()) { + position.set(Position.KEY_HOURS, parser.nextInt() * 1000L); + } + return position; } diff --git a/src/main/java/org/traccar/protocol/T622IridiumProtocol.java b/src/main/java/org/traccar/protocol/T622IridiumProtocol.java new file mode 100644 index 000000000..1289fe8e7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/T622IridiumProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 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.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class T622IridiumProtocol extends BaseProtocol { + + @Inject + public T622IridiumProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 1, 2)); + pipeline.addLast(new T622IridiumProtocolDecoder(T622IridiumProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/T622IridiumProtocolDecoder.java b/src/main/java/org/traccar/protocol/T622IridiumProtocolDecoder.java new file mode 100644 index 000000000..27b7baf54 --- /dev/null +++ b/src/main/java/org/traccar/protocol/T622IridiumProtocolDecoder.java @@ -0,0 +1,149 @@ +/* + * Copyright 2023 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.ByteBuf; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Protocol; +import org.traccar.config.Keys; +import org.traccar.helper.UnitsConverter; +import org.traccar.helper.model.AttributeUtil; +import org.traccar.model.Position; +import org.traccar.session.DeviceSession; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +public class T622IridiumProtocolDecoder extends BaseProtocolDecoder { + + private String format; + + public T622IridiumProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public List<Integer> getParameters(long deviceId) { + String value = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_FORMAT.withPrefix(getProtocolName()), deviceId); + return Arrays.stream((value != null ? value : format).split(",")) + .map(s -> Integer.parseInt(s, 16)) + .collect(Collectors.toList()); + } + + public void setFormat(String format) { + this.format = format; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // protocol revision + buf.readUnsignedShort(); // length + buf.readUnsignedByte(); // header indicator + buf.readUnsignedShort(); // header length + buf.readUnsignedInt(); // reference + + String imei = buf.readCharSequence(15, StandardCharsets.US_ASCII).toString(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + buf.readUnsignedByte(); // session status + buf.readUnsignedShort(); // originator index + buf.readUnsignedShort(); // transfer index + buf.readUnsignedInt(); // session time + buf.readUnsignedByte(); // payload indicator + buf.readUnsignedShort(); // payload length + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + List<Integer> parameters = getParameters(deviceSession.getDeviceId()); + + for (int parameter : parameters) { + switch (parameter) { + case 0x01: + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + break; + case 0x02: + position.setLatitude(buf.readIntLE() / 1000000.0); + break; + case 0x03: + position.setLongitude(buf.readIntLE() / 1000000.0); + break; + case 0x04: + position.setTime(new Date((buf.readUnsignedIntLE() + 946713600) * 1000)); + break; + case 0x05: + position.setValid(buf.readUnsignedByte() > 0); + break; + case 0x06: + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + break; + case 0x07: + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + break; + case 0x08: + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE())); + break; + case 0x09: + position.setCourse(buf.readUnsignedShortLE()); + break; + case 0x0A: + position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1); + break; + case 0x0B: + position.setAltitude(buf.readShortLE()); + break; + case 0x0C: + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + break; + case 0x0D: + position.set(Position.KEY_HOURS, buf.readUnsignedIntLE() * 1000); + break; + case 0x14: + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + break; + case 0x15: + position.set(Position.KEY_INPUT, buf.readUnsignedByte()); + break; + case 0x19: + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.01); + break; + case 0x1A: + position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.01); + break; + case 0x1B: + buf.readUnsignedByte(); // geofence + break; + default: + break; + } + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java index 70d633e19..c8e0005f9 100644 --- a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java @@ -252,10 +252,11 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { register(83, fmbXXX, (p, b) -> p.set(Position.KEY_FUEL_USED, b.readUnsignedInt() * 0.1)); register(84, fmbXXX, (p, b) -> p.set(Position.KEY_FUEL_LEVEL, b.readUnsignedShort() * 0.1)); register(85, fmbXXX, (p, b) -> p.set(Position.KEY_RPM, b.readUnsignedShort())); - register(87, fmbXXX, (p, b) -> p.set(Position.KEY_OBD_ODOMETER, b.readUnsignedInt() * 0.001)); + register(87, fmbXXX, (p, b) -> p.set(Position.KEY_OBD_ODOMETER, b.readUnsignedInt())); register(89, fmbXXX, (p, b) -> p.set("fuelLevelPercentage", b.readUnsignedByte())); register(90, null, (p, b) -> p.set(Position.KEY_DOOR, b.readUnsignedShort())); register(110, fmbXXX, (p, b) -> p.set(Position.KEY_FUEL_CONSUMPTION, b.readUnsignedShort() * 0.1)); + register(113, fmbXXX, (p, b) -> p.set(Position.KEY_BATTERY_LEVEL, b.readUnsignedByte())); register(179, null, (p, b) -> p.set(Position.PREFIX_OUT + 1, b.readUnsignedByte() > 0)); register(180, null, (p, b) -> p.set(Position.PREFIX_OUT + 2, b.readUnsignedByte() > 0)); register(181, null, (p, b) -> p.set(Position.KEY_PDOP, b.readUnsignedShort() * 0.1)); diff --git a/src/main/java/org/traccar/protocol/WatchFrameDecoder.java b/src/main/java/org/traccar/protocol/WatchFrameDecoder.java index f99bd52e2..ec67aa34d 100644 --- a/src/main/java/org/traccar/protocol/WatchFrameDecoder.java +++ b/src/main/java/org/traccar/protocol/WatchFrameDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,14 @@ public class WatchFrameDecoder extends BaseFrameDecoder { protected Object decode( ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { - int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ']') + 1; + int endIndex = -1; + for (int i = buf.writerIndex() - 1; i >= buf.readerIndex(); i--) { + if (buf.getByte(i) == ']') { + endIndex = i + 1; + break; + } + } + if (endIndex > 0) { ByteBuf frame = Unpooled.buffer(); while (buf.readerIndex() < endIndex) { diff --git a/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java index 913dfaf28..0e3c44e12 100644 --- a/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java @@ -156,7 +156,7 @@ public class Xexun2ProtocolDecoder extends BaseProtocolDecoder { for (int j = 0; j < wifiCount; j++) { String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:"); network.addWifiAccessPoint(WifiAccessPoint.from( - mac.substring(0, mac.length() - 1), buf.readUnsignedByte())); + mac.substring(0, mac.length() - 1), buf.readByte())); } } if (BitUtil.check(positionMask, 2)) { @@ -164,7 +164,7 @@ public class Xexun2ProtocolDecoder extends BaseProtocolDecoder { for (int j = 0; j < cellCount; j++) { network.addCellTower(CellTower.from( buf.readUnsignedShort(), buf.readUnsignedShort(), - buf.readInt(), buf.readUnsignedInt(), buf.readUnsignedByte())); + buf.readInt(), buf.readUnsignedInt(), buf.readByte())); } } if (network.getWifiAccessPoints() != null || network.getCellTowers() != null) { diff --git a/src/main/java/org/traccar/reports/RouteReportProvider.java b/src/main/java/org/traccar/reports/RouteReportProvider.java index 6d44259e6..5343652b7 100644 --- a/src/main/java/org/traccar/reports/RouteReportProvider.java +++ b/src/main/java/org/traccar/reports/RouteReportProvider.java @@ -42,6 +42,8 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Date; +import java.util.Map; +import java.util.HashMap; public class RouteReportProvider { @@ -49,6 +51,8 @@ public class RouteReportProvider { private final ReportUtils reportUtils; private final Storage storage; + private final Map<String, Integer> namesCount = new HashMap<>(); + @Inject public RouteReportProvider(Config config, ReportUtils reportUtils, Storage storage) { this.config = config; @@ -67,6 +71,12 @@ public class RouteReportProvider { return result; } + + private String getUniqueSheetName(String key) { + namesCount.compute(key, (k, value) -> value == null ? 1 : (value + 1)); + return namesCount.get(key) > 1 ? key + '-' + namesCount.get(key) : key; + } + public void getExcel(OutputStream outputStream, long userId, Collection<Long> deviceIds, Collection<Long> groupIds, Date from, Date to) throws StorageException, IOException { @@ -78,7 +88,7 @@ public class RouteReportProvider { var positions = PositionUtil.getPositions(storage, device.getId(), from, to); DeviceReportSection deviceRoutes = new DeviceReportSection(); deviceRoutes.setDeviceName(device.getName()); - sheetNames.add(WorkbookUtil.createSafeSheetName(deviceRoutes.getDeviceName())); + sheetNames.add(WorkbookUtil.createSafeSheetName(getUniqueSheetName(deviceRoutes.getDeviceName()))); if (device.getGroupId() > 0) { Group group = storage.getObject(Group.class, new Request( new Columns.All(), new Condition.Equals("id", device.getGroupId()))); diff --git a/src/main/java/org/traccar/reports/common/ReportUtils.java b/src/main/java/org/traccar/reports/common/ReportUtils.java index 28e3d2ae5..995e92676 100644 --- a/src/main/java/org/traccar/reports/common/ReportUtils.java +++ b/src/main/java/org/traccar/reports/common/ReportUtils.java @@ -392,7 +392,10 @@ public class ReportUtils { new Columns.All(), new Condition.Equals("id", startEvent.getPositionId()))); Position endPosition = storage.getObject(Position.class, new Request( new Columns.All(), new Condition.Equals("id", event.getPositionId()))); - result.add(calculateTripOrStop(device, startPosition, endPosition, 0, ignoreOdometer, reportClass)); + if (startPosition != null && endPosition != null) { + result.add(calculateTripOrStop( + device, startPosition, endPosition, 0, ignoreOdometer, reportClass)); + } startEvent = null; } } diff --git a/src/main/java/org/traccar/session/cache/CacheManager.java b/src/main/java/org/traccar/session/cache/CacheManager.java index 9d2350012..24abd7347 100644 --- a/src/main/java/org/traccar/session/cache/CacheManager.java +++ b/src/main/java/org/traccar/session/cache/CacheManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2022 - 2023 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. @@ -31,7 +31,7 @@ import org.traccar.model.GroupedModel; import org.traccar.model.Maintenance; import org.traccar.model.Notification; import org.traccar.model.Position; -import org.traccar.model.ScheduledModel; +import org.traccar.model.Schedulable; import org.traccar.model.Server; import org.traccar.model.User; import org.traccar.storage.Storage; @@ -244,8 +244,8 @@ public class CacheManager implements BroadcastInterface { if (((GroupedModel) before).getGroupId() != ((GroupedModel) object).getGroupId()) { invalidate = true; } - } else if (object instanceof ScheduledModel) { - if (((ScheduledModel) before).getCalendarId() != ((ScheduledModel) object).getCalendarId()) { + } else if (object instanceof Schedulable) { + if (((Schedulable) before).getCalendarId() != ((Schedulable) object).getCalendarId()) { invalidate = true; } } @@ -308,6 +308,12 @@ public class CacheManager implements BroadcastInterface { new Columns.All(), new Condition.Equals("id", deviceId))); if (device != null) { addObject(deviceId, device); + if (device.getCalendarId() > 0) { + var calendar = storage.getObject(Calendar.class, new Request( + new Columns.All(), new Condition.Equals("id", device.getCalendarId()))); + links.computeIfAbsent(Calendar.class, k -> new LinkedHashSet<>()).add(calendar.getId()); + addObject(deviceId, calendar); + } int groupDepth = 0; long groupId = device.getGroupId(); @@ -326,13 +332,12 @@ public class CacheManager implements BroadcastInterface { links.put(clazz, objects.stream().map(BaseModel::getId).collect(Collectors.toSet())); for (var object : objects) { addObject(deviceId, object); - if (object instanceof ScheduledModel) { - var scheduled = (ScheduledModel) object; + if (object instanceof Schedulable) { + var scheduled = (Schedulable) object; if (scheduled.getCalendarId() > 0) { var calendar = storage.getObject(Calendar.class, new Request( new Columns.All(), new Condition.Equals("id", scheduled.getCalendarId()))); - links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>()) - .add(calendar.getId()); + links.computeIfAbsent(Calendar.class, k -> new LinkedHashSet<>()).add(calendar.getId()); addObject(deviceId, calendar); } } @@ -350,14 +355,12 @@ public class CacheManager implements BroadcastInterface { .filter(Notification::getAlways) .collect(Collectors.toList()); for (var notification : notifications) { - links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>()) - .add(notification.getId()); + links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>()).add(notification.getId()); addObject(deviceId, notification); if (notification.getCalendarId() > 0) { var calendar = storage.getObject(Calendar.class, new Request( new Columns.All(), new Condition.Equals("id", notification.getCalendarId()))); - links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>()) - .add(calendar.getId()); + links.computeIfAbsent(Calendar.class, k -> new LinkedHashSet<>()).add(calendar.getId()); addObject(deviceId, calendar); } } diff --git a/src/main/java/org/traccar/sms/HttpSmsClient.java b/src/main/java/org/traccar/sms/HttpSmsClient.java index 683022967..b4271a6f2 100644 --- a/src/main/java/org/traccar/sms/HttpSmsClient.java +++ b/src/main/java/org/traccar/sms/HttpSmsClient.java @@ -57,7 +57,10 @@ public class HttpSmsClient implements SmsManager { } } template = config.getString(Keys.SMS_HTTP_TEMPLATE).trim(); - if (template.charAt(0) == '{' || template.charAt(0) == '[') { + if (template.charAt(0) == '<') { + encode = false; + mediaType = MediaType.APPLICATION_XML_TYPE; + } else if (template.charAt(0) == '{' || template.charAt(0) == '[') { encode = false; mediaType = MediaType.APPLICATION_JSON_TYPE; } else { diff --git a/src/main/java/org/traccar/web/OverrideFilter.java b/src/main/java/org/traccar/web/OverrideFilter.java index 70eb80a67..6d20789f2 100644 --- a/src/main/java/org/traccar/web/OverrideFilter.java +++ b/src/main/java/org/traccar/web/OverrideFilter.java @@ -45,6 +45,11 @@ public class OverrideFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (((HttpServletRequest) request).getServletPath().startsWith("/api")) { + chain.doFilter(request, response); + return; + } + ResponseWrapper wrappedResponse = new ResponseWrapper((HttpServletResponse) response); chain.doFilter(request, wrappedResponse); diff --git a/src/main/java/org/traccar/web/ThrottlingFilter.java b/src/main/java/org/traccar/web/ThrottlingFilter.java index 054af652f..6d2328562 100644 --- a/src/main/java/org/traccar/web/ThrottlingFilter.java +++ b/src/main/java/org/traccar/web/ThrottlingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2022 - 2023 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. @@ -39,6 +39,7 @@ public class ThrottlingFilter extends DoSFilter { if (config.hasKey(Keys.WEB_MAX_REQUESTS_PER_SECOND)) { setMaxRequestsPerSec(config.getInteger(Keys.WEB_MAX_REQUESTS_PER_SECOND)); } + setMaxRequestMs(config.getInteger(Keys.WEB_MAX_REQUEST_SECONDS) * 1000L); } @Override diff --git a/src/test/java/org/traccar/handler/FilterHandlerTest.java b/src/test/java/org/traccar/handler/FilterHandlerTest.java index 26281e351..36bb84f19 100644 --- a/src/test/java/org/traccar/handler/FilterHandlerTest.java +++ b/src/test/java/org/traccar/handler/FilterHandlerTest.java @@ -29,7 +29,8 @@ public class FilterHandlerTest extends BaseTest { when(config.getBoolean(Keys.FILTER_ENABLE)).thenReturn(true); var cacheManager = mock(CacheManager.class); when(cacheManager.getConfig()).thenReturn(config); - passingHandler = new FilterHandler(config, cacheManager, null); + when(cacheManager.getObject(any(), anyLong())).thenReturn(mock(Device.class)); + passingHandler = new FilterHandler(config, cacheManager, null, null); } @BeforeEach @@ -50,7 +51,7 @@ public class FilterHandlerTest extends BaseTest { var cacheManager = mock(CacheManager.class); when(cacheManager.getConfig()).thenReturn(config); when(cacheManager.getObject(any(), anyLong())).thenReturn(mock(Device.class)); - filteringHandler = new FilterHandler(config, cacheManager, null); + filteringHandler = new FilterHandler(config, cacheManager, null, null); } private Position createPosition(Date time, boolean valid, double speed) { diff --git a/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java index 32000c4a7..be6fb5c45 100644 --- a/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java @@ -44,6 +44,9 @@ public class AtrackProtocolDecoderTest extends ProtocolTest { decoder.setCustom(true); verifyPositions(decoder, binary( + "4050d78502e01d29000312fa45441d6d647d8e67647d8e67647eef190205437c021846e6001a020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010c0000000000000000000000000000000000000000000000000e647d8e85647d8e86647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010c0000000000000000000000000000000000000000000000000e647d8ea3647d8ea4647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010b0000000000000000000000000000000000000000000000000e647d8ec1647d8ec2647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010a0000000000000000000000000000000000000000000000000e647d8edf647d8ee0647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010a0000000000000000000000000000000000000000000000000e647d8efd647d8efe647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010a0000000000000000000000000000000000000000000000000e")); + + verifyPositions(decoder, binary( "405099280272000300014399e3f93d136438abdf644083f56440842afb2711c701b9eaee0067020003e0bb03de0000000000000007d007d00025434925454c25455425464325464c255250254d4c25534d25545225494125454f25564e254d56254256254548255a4c33255a4f3134255a4f3131255a4f3130255a4f32255a4c3400000000000000000000000000000000000000000000930025000000000000000000000000000000006438abdf644083f76440842afb2711c701b9eaee0067710003e0bb03de0100000000000007d007d00025434925454c25455425464325464c255250254d4c25534d25545225494125454f25564e254d56254256254548255a4c33255a4f3134255a4f3131255a4f3130255a4f32255a4c3400000000000000000000000000000000000000000000950025000000000000000000000000000000006438abdf644083f76440842afb2711c701b9eaee0067840003e0bb03de0100000000000007d007d00025434925454c25455425464325464c255250254d4c25534d25545225494125454f25564e254d56254256254548255a4c33255a4f3134255a4f3131255a4f3130255a4f32255a4c3400000000000000000000000000000000000000000000950025000000000000000000000000000000006438abdf644083f86440842afb2711c701b9eaee0067760003e0bb03de0100000000000007d007d00025434925454c25455425464325464c255250254d4c25534d25545225494125454f25564e254d56254256254548255a4c33255a4f3134255a4f3131255a4f3130255a4f32255a4c340000000000000000000000000000000000000000000095002500000000000000000000000000000000")); verifyPositions(decoder, buffer( diff --git a/src/test/java/org/traccar/protocol/FifotrackProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/FifotrackProtocolDecoderTest.java index 518eada52..cbfc46703 100644 --- a/src/test/java/org/traccar/protocol/FifotrackProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/FifotrackProtocolDecoderTest.java @@ -11,6 +11,10 @@ public class FifotrackProtocolDecoderTest extends ProtocolTest { var decoder = inject(new FifotrackProtocolDecoder(null)); + verifyAttribute(decoder, buffer( + "$$99,865413050150407,7F,A03,,230626072722,460|0|25FC|AC2AB0B,3.74,52,0019,0,A,0,13,22.643466,114.018211*74"), + Position.KEY_SATELLITES, 13); + verifyPosition(decoder, buffer( "$$95,866104023192332,1,A03,,210414055249,460|0|25FC|104C,4.18,100,000F,0,A,2,9,22.643175,114.018150*75")); diff --git a/src/test/java/org/traccar/protocol/FreematicsProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/FreematicsProtocolDecoderTest.java index dae3815ac..53ed07da7 100644 --- a/src/test/java/org/traccar/protocol/FreematicsProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/FreematicsProtocolDecoderTest.java @@ -11,6 +11,9 @@ public class FreematicsProtocolDecoderTest extends ProtocolTest { var decoder = inject(new FreematicsProtocolDecoder(null)); verifyPositions(decoder, text( + "UCFLFAYM#0:33770,24:300,82:53.000000,*F9")); + + verifyPositions(decoder, text( "M0ZR4X0#0:204391,11:140221,10:8445000,A:49.215920,B:18.737755,C:410,D:0,E:208,24:1252,20:0;0;0,82:47*B5")); verifyNull(decoder, text( diff --git a/src/test/java/org/traccar/protocol/GalileoFrameDecoderTest.java b/src/test/java/org/traccar/protocol/GalileoFrameDecoderTest.java index 256517460..530f0930e 100644 --- a/src/test/java/org/traccar/protocol/GalileoFrameDecoderTest.java +++ b/src/test/java/org/traccar/protocol/GalileoFrameDecoderTest.java @@ -13,6 +13,10 @@ public class GalileoFrameDecoderTest extends ProtocolTest { var decoder = inject(new GalileoFrameDecoder()); assertEquals( + binary("01003f01001c475b166133303035333430363431383437393000001d000064897bb003000b0221c20512a60a0000000802000f209d7b8964300f2536fbfd103c1d01"), + decoder.decode(null, null, binary("01003f01001c475b166133303035333430363431383437393000001d000064897bb003000b0221c20512a60a0000000802000f209d7b8964300f2536fbfd103c1d01"))); + + assertEquals( binary("011780011102e603383633353931303238393630323437043200801c"), decoder.decode(null, null, binary("011780011102e603383633353931303238393630323437043200801c"))); diff --git a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java index 4b0edfd81..f0dd10772 100644 --- a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java @@ -11,6 +11,24 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest { var decoder = inject(new Gl200TextProtocolDecoder(null)); + verifyAttributes(decoder, buffer( + "+RESP:GTCAN,F1040C,862599050497393,GV350M,0,0,FFFFFFFF,,1,,,,,,,,,,,,,,,,,,,,,,,0,,,1,0.0,70,2961.6,-78.691750,-0.951135,20230703191659,,,,,,20230703191659,5A4A$")); + + verifyAttribute(decoder, buffer( + "+RESP:GTBAA,F1040C,862599050497393,GV350M,FF,3,0,04,000A,780541256AE9,3065,0,0.0,213,2908.3,-78.691944,-0.951426,20230511173150,,,,,,20230511175001,0159$"), + "accessoryVoltage", 3.065); + + verifyAttribute(decoder, buffer( + "+RESP:GTBID,C20105,866833040163013,GV350M,1,0,000A,B80EA11FF800,2934,0,0.0,0,1506.5,-99.192686,18.932709,20221026025339,0334,0020,0232,029D4E02,,20221026181026,9F1D$"), + "accessoryVoltage", 2.934); + + verifyAttribute(decoder, buffer( + "+RESP:GTDTT,410502,864802030541621,,,,1,35,45637561747261636b0d0a434f4d422c302c39342e302c2d312e302c2c2c4844430d0a,20230421034626,EA2E$"), + Position.KEY_FUEL_LEVEL, 94.0); + + verifyNull(decoder, buffer( + "+RESP:GTFRI,5E0100,862061048023666,,,12940,10,1,1,0.0,97,179.8,-90.366478,38.735379,20230616183231,0310,0410,6709,03ADF710,00,6223.7,,,,,110000,,,,202306161834$")); + verifyAttribute(decoder, buffer( "+BUFF:GTERI,410502,864802030794634,,00000001,,10,1,1,0.0,0,3027.8,-78.706612,-0.955699,20230418170736,0740,0002,A08C,2AB72D,00,0.0,,,,100,110000,1,0099,20230418171004,8B98$"), Position.KEY_FUEL_LEVEL, 153); diff --git a/src/test/java/org/traccar/protocol/GoSafeProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/GoSafeProtocolDecoderTest.java index ad944d73a..3e1349a80 100644 --- a/src/test/java/org/traccar/protocol/GoSafeProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/GoSafeProtocolDecoderTest.java @@ -14,6 +14,9 @@ public class GoSafeProtocolDecoderTest extends ProtocolTest { verifyPositions(decoder, false, text( "*GS06,357330050846344,RST#")); + verifyPositions(decoder, text( + "*GS06,353218073585128,181255300523,,SYS:Smart Track;V9.31;V1.1.5,GPS:A;5;N31.551856;E74.366920;0;0;;2.15;2.64,COT:0,ADC:10.78;0.02,DTT:4002;E1;0;0;0;1$181325300523,,SYS:Smart Track;V9.31;V1.1.5,GPS:A;6;N31.551856;E74.366920;0;0;;2.05;2.13,COT:0,ADC:10.79;0.02,DTT:4002;E1;0;0;0;1#")); + verifyAttribute(decoder, text( "*GS06,356449068350122,013519070819,,SYS:G6S;V3.37;V1.1.8,GPS:A;12;N23.169866;E113.450728;0;255;54;0.79,COT:18779;,ADC:12.66;0.58,DTT:4084;E1;0;0;0;1,IWD:0;1;ad031652643fff28;23.2;1;1;86031652504fff28;24.3;2;1;e603165252a5ff28;24.2;3;1;bb0416557da6ff28;24.0#"), Position.PREFIX_TEMP + 3, 24.0); diff --git a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java index 8f2c97f86..04f41a5ee 100644 --- a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java @@ -17,6 +17,14 @@ public class Gt06ProtocolDecoderTest extends ProtocolTest { verifyNull(decoder, binary( "78780D01086471700328358100093F040D0A")); + verifyAttribute(decoder, binary( + "78782416160501000221c0027c5e180c2b9c8d00150301cc002503002bde20050f01000004bd320d0a"), + Position.KEY_ALARM, Position.ALARM_SOS); + + verifyAttribute(decoder, binary( + "78780a130604ea04000006bc8a0d0a"), + Position.KEY_POWER, 0.0); + verifyAttributes(decoder, binary( "797900849404414c4d313d43353b414c4d323d43433b414c4d333d35433b535441313d43303b4459443d30313b534f533d303133323838333730302c2c3b43454e5445523d303133323838333730303b46454e43453d46656e63652c4f46462c302c302e3030303030302c302e3030303030302c3330302c494e206f72204f55542c313b00b79d120d0a")); diff --git a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java index 5fd9ed894..9c3fc164a 100644 --- a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java @@ -15,6 +15,10 @@ public class HuabaoProtocolDecoderTest extends ProtocolTest { "7e010200204f07788ef67601824f4459344f544d314d4459774d4441314d444977626d5633553235536457786cba7e")); verifyAttribute(decoder, binary( + "7e0200003f014501643822000300020000000c000c0000000000000000000000000000230615143903300111310100530901027f0300456f073e56020900fe02001e57080002000200000000df7e"), + Position.KEY_BATTERY_LEVEL, 90); + + verifyAttribute(decoder, binary( "7e090000344f07788ef87d0138f02305151230460102020001ffffffff000100001457000000020006503134353700000c000a029dc63004b99a98230515132726787e"), Position.KEY_DTCS, "P1457"); diff --git a/src/test/java/org/traccar/protocol/MiniFinderProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/MiniFinderProtocolDecoderTest.java index d955d8e50..712d59dc9 100644 --- a/src/test/java/org/traccar/protocol/MiniFinderProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/MiniFinderProtocolDecoderTest.java @@ -21,6 +21,10 @@ public class MiniFinderProtocolDecoderTest extends ProtocolTest { "!1,123456789012345")); verifyAttribute(decoder, text( + "!4,10,040123,,,1.0,110,0,0S,33"), + "phone1", "040123"); + + verifyAttribute(decoder, text( "!5,17,V,50"), Position.KEY_BATTERY_LEVEL, 50); diff --git a/src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java index 693a11fc5..587a520d1 100644 --- a/src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java @@ -24,10 +24,10 @@ public class Minifinder2ProtocolDecoderTest extends ProtocolTest { verifyPositions(decoder, binary( "ab10350015ae59010110013836333932313033333836353231360924723a12610042535a182ac0f6b4f2923100c900af02215c2b9bfb5461736b4c4d53")); - verifyNull(decoder, binary( + verifyPositions(decoder, false, binary( "ab10150076f1320003100133353534363530373130323933303602105a")); - verifyNull(decoder, binary( + verifyPositions(decoder, false, binary( "AB101400594A01000310013836333932323033343437333734350112")); verifyPositions(decoder, binary( diff --git a/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java index 361e6e6f2..9b1362f5d 100644 --- a/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java @@ -12,6 +12,10 @@ public class StartekProtocolDecoderTest extends ProtocolTest { var decoder = inject(new StartekProtocolDecoder(null)); verifyAttribute(decoder, text( + "&&s148,868703050178631,000,37,,230704040211,A,22.678565,114.046011,31,0.5,0,339,77,8,460|0|249F|0AC2620D,27,0000001D,02,00,04F2|01A1|0000|0000,129,,,,949037"), + Position.KEY_HOURS, 9490000L); + + verifyAttribute(decoder, text( "&&x164,869926040743375,000,0,,220705205955,A,33.326001,44.445318,10,1.2,0,57,8,925,418|40|038C|000083CD,31,00000015,00,00,0016|016A|0000|0000,1,,,686|33||44|99|14|124|11|8D"), Position.KEY_FUEL_CONSUMPTION, 1.1); diff --git a/src/test/java/org/traccar/protocol/T622IridiumProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/T622IridiumProtocolDecoderTest.java new file mode 100644 index 000000000..17b252618 --- /dev/null +++ b/src/test/java/org/traccar/protocol/T622IridiumProtocolDecoderTest.java @@ -0,0 +1,22 @@ +package org.traccar.protocol; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.traccar.ProtocolTest; + +public class T622IridiumProtocolDecoderTest extends ProtocolTest { + + @Disabled + @Test + public void testDecode() throws Exception { + + var decoder = inject(new T622IridiumProtocolDecoder(null)); + + decoder.setFormat("01,02,03,04,05,08"); + + verifyPosition(decoder, binary( + "01003301001c2a8cef8333303034333430363735343836353000001700006461d512020011232f03a0fff1c85d0612b3f02b00000048")); + + } + +} diff --git a/src/test/java/org/traccar/protocol/WatchFrameDecoderTest.java b/src/test/java/org/traccar/protocol/WatchFrameDecoderTest.java index 8b0486322..bf212544c 100644 --- a/src/test/java/org/traccar/protocol/WatchFrameDecoderTest.java +++ b/src/test/java/org/traccar/protocol/WatchFrameDecoderTest.java @@ -11,6 +11,10 @@ public class WatchFrameDecoderTest extends ProtocolTest { var decoder = inject(new WatchFrameDecoder()); verifyFrame( + binary("5b33472a3838303930303234322a303133442a55442c3132303632332c3134303032302c412c34382e3934393237332c4e2c20342e333738333036302c452c31382e35362c34332e382c302e302c31322c3130302c37362c3232363132302c302c30303030303030302c322c3235352c3230342c382c333131302c35353032352c3134362c333133302c34393239372c3132342c352c42616e67696e67576966692c33343a61313a65643a65313a39313a34662c2d37312c42415220576946692c33363a61323a65313a65643a61313a64652c2d37322c4e6574776f726b576966692c32363a64653a61313a65643a65313a61302c2d37332c46696265722c33363a61313a65643a65313a39313a34662c2d37352c5b4c475f57616c6c2d4d6f756e7420412f435d653732352c36363a61313a65643a65313a65373a32352c2d38322c31352e305d"), + decoder.decode(null, null, binary("5b33472a3838303930303234322a303133442a55442c3132303632332c3134303032302c412c34382e3934393237332c4e2c20342e333738333036302c452c31382e35362c34332e382c302e302c31322c3130302c37362c3232363132302c302c30303030303030302c322c3235352c3230342c382c333131302c35353032352c3134362c333133302c34393239372c3132342c352c42616e67696e67576966692c33343a61313a65643a65313a39313a34662c2d37312c42415220576946692c33363a61323a65313a65643a61313a64652c2d37322c4e6574776f726b576966692c32363a64653a61313a65643a65313a61302c2d37332c46696265722c33363a61313a65643a65313a39313a34662c2d37352c5b4c475f57616c6c2d4d6f756e7420412f435d653732352c36363a61313a65643a65313a65373a32352c2d38322c31352e305d"))); + + verifyFrame( binary("5b33472a3335323636313039303134333135302a303030412a4c4b2c302c302c3130305d"), decoder.decode(null, null, binary("5b33472a3335323636313039303134333135302a303030412a4c4b2c302c302c3130305d"))); diff --git a/swagger.json b/swagger.json index 889728d33..eec852ac7 100644 --- a/swagger.json +++ b/swagger.json @@ -2,7 +2,7 @@ "openapi": "3.0.1", "info": { "title": "Traccar", - "version": "5.7", + "version": "5.8", "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 84d2fa2e3..204fecb70 100755 --- a/tools/test-integration.py +++ b/tools/test-integration.py @@ -126,6 +126,7 @@ messages = { 'armoli': '[M123456789012345210122125205N38.735641E035.4727751E003340000000C00000E9E07FF:106AG505283H60E];', 'teratrack': '{"MDeviceID":"022043756090","DiviceType":"1","DataType":"1","DataLength":"69","DateTime":"2022-03-09 10:56:01","Latitude":"-6.846451","Longitude":"39.316324","LongitudeState":"1","LatitudeState":"0","Speed":"90","Mileage":"0","FenceAlarm":"0","AreaAlarmID":"0","LockCutOff":"0","SealTampered":"0","MessageAck":"1","LockRope":"1","LockStatus":"1","LockOpen":"0","PasswordError":"0","CardNo":"60000644","IllegalCard":"0","LowPower":"0","UnCoverBack":"0","CoverStatus":"1","LockStuck":"0","Power":"79","GSM":"16","IMEI":"123456789012345","Index":"20","Slave":[]}', 'envotech': '$80SLM,02,F,123456,130410155921,431750216,000040,0000,,00000000,\'13041015592110476673N10111459E001281*2A#', + 'bstpl': 'BSTPL$1,123456789012345,V,200722,045113,00.000000,0,00.00000,0,0,0,000,00,0,17,1,1,0,0,00.01,0,04.19,15B_190821,8991000907387031196F,12.27#', } baseUrl = 'http://localhost:8082' diff --git a/traccar-web b/traccar-web -Subproject 74dc5065ffd3866d660fd7b908a26c5089173d0 +Subproject f8048106dacb36466ae221a8258da85f35db6e9 |