aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2024-06-14 21:08:21 -0600
committerIván Ávalos <avalos@disroot.org>2024-06-14 21:08:21 -0600
commit471dc4ca7b6cfd656cc2c04c526fe56ee538991c (patch)
tree4766fa7209e2eaab65269db456cf0436e6a64a49 /src/main
parent447c7e15fcec8fc72d0457bb7dbf166cbea84acd (diff)
parent64528b96da4a742070d5845a876b07ca66ad0be3 (diff)
downloadtrackermap-server-471dc4ca7b6cfd656cc2c04c526fe56ee538991c.tar.gz
trackermap-server-471dc4ca7b6cfd656cc2c04c526fe56ee538991c.tar.bz2
trackermap-server-471dc4ca7b6cfd656cc2c04c526fe56ee538991c.zip
Merge tag 'v6.2'
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/org/traccar/EventLoopGroupFactory.java10
-rw-r--r--src/main/java/org/traccar/Main.java36
-rw-r--r--src/main/java/org/traccar/MainModule.java16
-rw-r--r--src/main/java/org/traccar/ProcessingHandler.java2
-rw-r--r--src/main/java/org/traccar/ServerManager.java2
-rw-r--r--src/main/java/org/traccar/api/AsyncSocketServlet.java30
-rw-r--r--src/main/java/org/traccar/api/resource/CommandResource.java3
-rw-r--r--src/main/java/org/traccar/api/resource/DeviceResource.java8
-rw-r--r--src/main/java/org/traccar/api/resource/NotificationResource.java15
-rw-r--r--src/main/java/org/traccar/api/resource/ReportResource.java24
-rw-r--r--src/main/java/org/traccar/api/resource/ServerResource.java7
-rw-r--r--src/main/java/org/traccar/api/security/LoginService.java18
-rw-r--r--src/main/java/org/traccar/api/security/SecurityRequestFilter.java22
-rw-r--r--src/main/java/org/traccar/config/Config.java22
-rw-r--r--src/main/java/org/traccar/config/Keys.java91
-rw-r--r--src/main/java/org/traccar/config/PortConfigSuffix.java278
-rw-r--r--src/main/java/org/traccar/database/NotificationManager.java13
-rw-r--r--src/main/java/org/traccar/database/StatisticsManager.java2
-rw-r--r--src/main/java/org/traccar/geocoder/PlusCodesGeocoder.java (renamed from src/main/java/org/traccar/geocoder/TestGeocoder.java)7
-rw-r--r--src/main/java/org/traccar/handler/ComputedAttributesHandler.java10
-rw-r--r--src/main/java/org/traccar/handler/DatabaseHandler.java2
-rw-r--r--src/main/java/org/traccar/handler/FilterHandler.java26
-rw-r--r--src/main/java/org/traccar/handler/GeocoderHandler.java6
-rw-r--r--src/main/java/org/traccar/helper/LogAction.java40
-rw-r--r--src/main/java/org/traccar/helper/SanitizerModule.java45
-rw-r--r--src/main/java/org/traccar/model/Attribute.java10
-rw-r--r--src/main/java/org/traccar/model/Calendar.java8
-rw-r--r--src/main/java/org/traccar/model/Server.java12
-rw-r--r--src/main/java/org/traccar/model/User.java12
-rw-r--r--src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java3
-rw-r--r--src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java3
-rw-r--r--src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java19
-rw-r--r--src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java26
-rw-r--r--src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java11
-rw-r--r--src/main/java/org/traccar/protocol/KhdProtocolDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/SnapperFrameDecoder.java44
-rw-r--r--src/main/java/org/traccar/protocol/SnapperProtocol.java37
-rw-r--r--src/main/java/org/traccar/protocol/SnapperProtocolDecoder.java229
-rw-r--r--src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java158
-rw-r--r--src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java8
-rw-r--r--src/main/java/org/traccar/protocol/TrvProtocolDecoder.java93
-rw-r--r--src/main/java/org/traccar/reports/SummaryReportProvider.java21
-rw-r--r--src/main/java/org/traccar/reports/model/SummaryReportItem.java27
-rw-r--r--src/main/java/org/traccar/schedule/TaskExpirations.java2
-rw-r--r--src/main/java/org/traccar/schedule/TaskReports.java11
-rw-r--r--src/main/java/org/traccar/storage/DatabaseModule.java39
-rw-r--r--src/main/java/org/traccar/web/DefaultOverrideServlet.java (renamed from src/main/java/org/traccar/web/ModernDefaultServlet.java)6
-rw-r--r--src/main/java/org/traccar/web/WebServer.java2
48 files changed, 1135 insertions, 383 deletions
diff --git a/src/main/java/org/traccar/EventLoopGroupFactory.java b/src/main/java/org/traccar/EventLoopGroupFactory.java
index 482559253..7f596cd83 100644
--- a/src/main/java/org/traccar/EventLoopGroupFactory.java
+++ b/src/main/java/org/traccar/EventLoopGroupFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2024 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.
@@ -20,18 +20,18 @@ import io.netty.channel.nio.NioEventLoopGroup;
public final class EventLoopGroupFactory {
- private static EventLoopGroup bossGroup = new NioEventLoopGroup();
- private static EventLoopGroup workerGroup = new NioEventLoopGroup();
+ private static final EventLoopGroup BOSS_GROUP = new NioEventLoopGroup();
+ private static final EventLoopGroup WORKER_GROUP = new NioEventLoopGroup();
private EventLoopGroupFactory() {
}
public static EventLoopGroup getBossGroup() {
- return bossGroup;
+ return BOSS_GROUP;
}
public static EventLoopGroup getWorkerGroup() {
- return workerGroup;
+ return WORKER_GROUP;
}
}
diff --git a/src/main/java/org/traccar/Main.java b/src/main/java/org/traccar/Main.java
index 33fcf654f..a22194d9e 100644
--- a/src/main/java/org/traccar/Main.java
+++ b/src/main/java/org/traccar/Main.java
@@ -17,6 +17,7 @@ package org.traccar;
import com.google.inject.Guice;
import com.google.inject.Injector;
+import com.google.inject.ProvisionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.broadcast.BroadcastService;
@@ -51,23 +52,22 @@ public final class Main {
public static void logSystemInfo() {
try {
OperatingSystemMXBean operatingSystemBean = ManagementFactory.getOperatingSystemMXBean();
- LOGGER.info("Operating system"
- + " name: " + operatingSystemBean.getName()
- + " version: " + operatingSystemBean.getVersion()
- + " architecture: " + operatingSystemBean.getArch());
+ LOGGER.info(
+ "Operating system name: {} version: {} architecture: {}",
+ operatingSystemBean.getName(), operatingSystemBean.getVersion(), operatingSystemBean.getArch());
RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
- LOGGER.info("Java runtime"
- + " name: " + runtimeBean.getVmName()
- + " vendor: " + runtimeBean.getVmVendor()
- + " version: " + runtimeBean.getVmVersion());
+ LOGGER.info(
+ "Java runtime name: {} vendor: {} version: {}",
+ runtimeBean.getVmName(), runtimeBean.getVmVendor(), runtimeBean.getVmVersion());
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
- LOGGER.info("Memory limit"
- + " heap: " + memoryBean.getHeapMemoryUsage().getMax() / (1024 * 1024) + "mb"
- + " non-heap: " + memoryBean.getNonHeapMemoryUsage().getMax() / (1024 * 1024) + "mb");
+ LOGGER.info(
+ "Memory limit heap: {}mb non-heap: {}mb",
+ memoryBean.getHeapMemoryUsage().getMax() / (1024 * 1024),
+ memoryBean.getNonHeapMemoryUsage().getMax() / (1024 * 1024));
- LOGGER.info("Character encoding: " + Charset.defaultCharset().displayName());
+ LOGGER.info("Character encoding: {}", Charset.defaultCharset().displayName());
} catch (Exception error) {
LOGGER.warn("Failed to get system info");
@@ -115,7 +115,7 @@ public final class Main {
try {
injector = Guice.createInjector(new MainModule(configFile), new DatabaseModule(), new WebModule());
logSystemInfo();
- LOGGER.info("Version: " + Main.class.getPackage().getImplementationVersion());
+ LOGGER.info("Version: {}", Main.class.getPackage().getImplementationVersion());
LOGGER.info("Starting server...");
var services = new ArrayList<LifecycleObject>();
@@ -142,8 +142,14 @@ public final class Main {
}
}));
} catch (Exception e) {
- LOGGER.error("Main method error", e);
- throw new RuntimeException(e);
+ Throwable unwrapped;
+ if (e instanceof ProvisionException) {
+ unwrapped = e.getCause();
+ } else {
+ unwrapped = e;
+ }
+ LOGGER.error("Main method error", unwrapped);
+ System.exit(1);
}
}
diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java
index 791d61c61..66238ab44 100644
--- a/src/main/java/org/traccar/MainModule.java
+++ b/src/main/java/org/traccar/MainModule.java
@@ -66,7 +66,7 @@ import org.traccar.geocoder.MapmyIndiaGeocoder;
import org.traccar.geocoder.NominatimGeocoder;
import org.traccar.geocoder.OpenCageGeocoder;
import org.traccar.geocoder.PositionStackGeocoder;
-import org.traccar.geocoder.TestGeocoder;
+import org.traccar.geocoder.PlusCodesGeocoder;
import org.traccar.geocoder.TomTomGeocoder;
import org.traccar.geolocation.GeolocationProvider;
import org.traccar.geolocation.GoogleGeolocationProvider;
@@ -79,7 +79,6 @@ import org.traccar.handler.GeolocationHandler;
import org.traccar.handler.SpeedLimitHandler;
import org.traccar.handler.TimeHandler;
import org.traccar.helper.ObjectMapperContextResolver;
-import org.traccar.helper.SanitizerModule;
import org.traccar.helper.WebHelper;
import org.traccar.mail.LogMailManager;
import org.traccar.mail.MailManager;
@@ -132,11 +131,8 @@ public class MainModule extends AbstractModule {
@Singleton
@Provides
- public static ObjectMapper provideObjectMapper(Config config) {
+ public static ObjectMapper provideObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
- if (config.getBoolean(Keys.WEB_SANITIZE)) {
- objectMapper.registerModule(new SanitizerModule());
- }
objectMapper.registerModule(new JSONPModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return objectMapper;
@@ -191,7 +187,7 @@ public class MainModule extends AbstractModule {
@Provides
public static WebServer provideWebServer(Injector injector, Config config) {
- if (config.hasKey(Keys.WEB_PORT)) {
+ if (config.getInteger(Keys.WEB_PORT) > 0) {
return new WebServer(injector, config);
}
return null;
@@ -201,7 +197,7 @@ public class MainModule extends AbstractModule {
@Provides
public static Geocoder provideGeocoder(Config config, Client client, StatisticsManager statisticsManager) {
if (config.getBoolean(Keys.GEOCODER_ENABLE)) {
- String type = config.getString(Keys.GEOCODER_TYPE, "google");
+ String type = config.getString(Keys.GEOCODER_TYPE);
String url = config.getString(Keys.GEOCODER_URL);
String key = config.getString(Keys.GEOCODER_KEY);
String language = config.getString(Keys.GEOCODER_LANGUAGE);
@@ -211,8 +207,8 @@ public class MainModule extends AbstractModule {
int cacheSize = config.getInteger(Keys.GEOCODER_CACHE_SIZE);
Geocoder geocoder;
switch (type) {
- case "test":
- geocoder = new TestGeocoder();
+ case "pluscodes":
+ geocoder = new PlusCodesGeocoder();
break;
case "nominatim":
geocoder = new NominatimGeocoder(client, url, key, language, cacheSize, addressFormat);
diff --git a/src/main/java/org/traccar/ProcessingHandler.java b/src/main/java/org/traccar/ProcessingHandler.java
index fd048d127..bb040bfff 100644
--- a/src/main/java/org/traccar/ProcessingHandler.java
+++ b/src/main/java/org/traccar/ProcessingHandler.java
@@ -101,8 +101,8 @@ public class ProcessingHandler extends ChannelInboundHandlerAdapter implements B
GeocoderHandler.class,
SpeedLimitHandler.class,
MotionHandler.class,
- EngineHoursHandler.class,
ComputedAttributesHandler.class,
+ EngineHoursHandler.class,
CopyAttributesHandler.class,
PositionForwardingHandler.class,
DatabaseHandler.class)
diff --git a/src/main/java/org/traccar/ServerManager.java b/src/main/java/org/traccar/ServerManager.java
index 22af66b41..1b0c441cf 100644
--- a/src/main/java/org/traccar/ServerManager.java
+++ b/src/main/java/org/traccar/ServerManager.java
@@ -54,7 +54,7 @@ public class ServerManager implements LifecycleObject {
for (Class<?> protocolClass : ClassScanner.findSubclasses(BaseProtocol.class, "org.traccar.protocol")) {
String protocolName = BaseProtocol.nameFromClass(protocolClass);
if (enabledProtocols == null || enabledProtocols.contains(protocolName)) {
- if (config.hasKey(Keys.PROTOCOL_PORT.withPrefix(protocolName))) {
+ if (config.getInteger(Keys.PROTOCOL_PORT.withPrefix(protocolName)) > 0) {
BaseProtocol protocol = (BaseProtocol) injector.getInstance(protocolClass);
connectorList.addAll(protocol.getConnectorList());
protocolList.put(protocol.getName(), protocol);
diff --git a/src/main/java/org/traccar/api/AsyncSocketServlet.java b/src/main/java/org/traccar/api/AsyncSocketServlet.java
index cd2c1639e..e1e7ee977 100644
--- a/src/main/java/org/traccar/api/AsyncSocketServlet.java
+++ b/src/main/java/org/traccar/api/AsyncSocketServlet.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2024 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
import org.traccar.api.resource.SessionResource;
+import org.traccar.api.security.LoginService;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.session.ConnectionManager;
@@ -27,7 +28,12 @@ import org.traccar.storage.Storage;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import jakarta.servlet.http.HttpSession;
+import org.traccar.storage.StorageException;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
import java.time.Duration;
+import java.util.List;
@Singleton
public class AsyncSocketServlet extends JettyWebSocketServlet {
@@ -36,25 +42,37 @@ public class AsyncSocketServlet extends JettyWebSocketServlet {
private final ObjectMapper objectMapper;
private final ConnectionManager connectionManager;
private final Storage storage;
+ private final LoginService loginService;
@Inject
public AsyncSocketServlet(
- Config config, ObjectMapper objectMapper, ConnectionManager connectionManager, Storage storage) {
+ Config config, ObjectMapper objectMapper, ConnectionManager connectionManager, Storage storage,
+ LoginService loginService) {
this.config = config;
this.objectMapper = objectMapper;
this.connectionManager = connectionManager;
this.storage = storage;
+ this.loginService = loginService;
}
@Override
public void configure(JettyWebSocketServletFactory factory) {
factory.setIdleTimeout(Duration.ofMillis(config.getLong(Keys.WEB_TIMEOUT)));
factory.setCreator((req, resp) -> {
- if (req.getSession() != null) {
- Long userId = (Long) ((HttpSession) req.getSession()).getAttribute(SessionResource.USER_ID_KEY);
- if (userId != null) {
- return new AsyncSocket(objectMapper, connectionManager, storage, userId);
+ Long userId = null;
+ List<String> tokens = req.getParameterMap().get("token");
+ if (tokens != null && !tokens.isEmpty()) {
+ String token = tokens.iterator().next();
+ try {
+ userId = loginService.login(token).getUser().getId();
+ } catch (StorageException | GeneralSecurityException | IOException e) {
+ throw new RuntimeException(e);
}
+ } else if (req.getSession() != null) {
+ userId = (Long) ((HttpSession) req.getSession()).getAttribute(SessionResource.USER_ID_KEY);
+ }
+ if (userId != null) {
+ return new AsyncSocket(objectMapper, connectionManager, storage, userId);
}
return null;
});
diff --git a/src/main/java/org/traccar/api/resource/CommandResource.java b/src/main/java/org/traccar/api/resource/CommandResource.java
index c23d91e77..66ec0f8a3 100644
--- a/src/main/java/org/traccar/api/resource/CommandResource.java
+++ b/src/main/java/org/traccar/api/resource/CommandResource.java
@@ -23,6 +23,7 @@ import org.traccar.BaseProtocol;
import org.traccar.ServerManager;
import org.traccar.api.ExtendedObjectResource;
import org.traccar.database.CommandsManager;
+import org.traccar.helper.LogAction;
import org.traccar.helper.model.DeviceUtil;
import org.traccar.model.Command;
import org.traccar.model.Device;
@@ -140,6 +141,8 @@ public class CommandResource extends ExtendedObjectResource<Command> {
return Response.accepted(queuedCommand).build();
}
}
+
+ LogAction.command(getUserId(), groupId, entity.getDeviceId(), entity.getType());
return Response.ok(entity).build();
}
diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java
index 56253152f..971c29c60 100644
--- a/src/main/java/org/traccar/api/resource/DeviceResource.java
+++ b/src/main/java/org/traccar/api/resource/DeviceResource.java
@@ -137,10 +137,8 @@ public class DeviceResource extends BaseObjectResource<Device> {
@Path("{id}/accumulators")
@PUT
public Response updateAccumulators(DeviceAccumulators entity) throws Exception {
- if (permissionsService.notAdmin(getUserId())) {
- permissionsService.checkManager(getUserId());
- permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId());
- }
+ permissionsService.checkEdit(getUserId(), Device.class, false);
+ permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId());
Position position = storage.getObject(Position.class, new Request(
new Columns.All(), new Condition.LatestPositions(entity.getDeviceId())));
@@ -171,7 +169,7 @@ public class DeviceResource extends BaseObjectResource<Device> {
throw new IllegalArgumentException();
}
- LogAction.resetDeviceAccumulators(getUserId(), entity.getDeviceId());
+ LogAction.resetAccumulators(getUserId(), entity.getDeviceId());
return Response.noContent().build();
}
diff --git a/src/main/java/org/traccar/api/resource/NotificationResource.java b/src/main/java/org/traccar/api/resource/NotificationResource.java
index 43dc1fa98..a41d00cf3 100644
--- a/src/main/java/org/traccar/api/resource/NotificationResource.java
+++ b/src/main/java/org/traccar/api/resource/NotificationResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,6 +46,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
@Path("notifications")
@Produces(MediaType.APPLICATION_JSON)
@@ -80,8 +82,11 @@ public class NotificationResource extends ExtendedObjectResource<Notification> {
@GET
@Path("notificators")
- public Collection<Typed> getNotificators() {
- return notificatorManager.getAllNotificatorTypes();
+ public Collection<Typed> getNotificators(@QueryParam("announcement") boolean announcement) {
+ Set<String> announcementsUnsupported = Set.of("command", "web");
+ return notificatorManager.getAllNotificatorTypes().stream()
+ .filter(typed -> !announcement || !announcementsUnsupported.contains(typed.getType()))
+ .collect(Collectors.toUnmodifiableSet());
}
@POST
@@ -120,7 +125,9 @@ public class NotificationResource extends ExtendedObjectResource<Notification> {
}
}
for (User user : users) {
- notificatorManager.getNotificator(notificator).send(user, message, null, null);
+ if (!user.getTemporary()) {
+ notificatorManager.getNotificator(notificator).send(user, message, null, null);
+ }
}
return Response.noContent().build();
}
diff --git a/src/main/java/org/traccar/api/resource/ReportResource.java b/src/main/java/org/traccar/api/resource/ReportResource.java
index 55a96fa90..81f409c0f 100644
--- a/src/main/java/org/traccar/api/resource/ReportResource.java
+++ b/src/main/java/org/traccar/api/resource/ReportResource.java
@@ -113,7 +113,7 @@ public class ReportResource extends SimpleObjectResource<Report> {
@QueryParam("from") Date from,
@QueryParam("to") Date to) throws StorageException {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
- LogAction.logReport(getUserId(), "combined", from, to, deviceIds, groupIds);
+ LogAction.report(getUserId(), false, "combined", from, to, deviceIds, groupIds);
return combinedReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to);
}
@@ -125,7 +125,7 @@ public class ReportResource extends SimpleObjectResource<Report> {
@QueryParam("from") Date from,
@QueryParam("to") Date to) throws StorageException {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
- LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds);
+ LogAction.report(getUserId(), false, "route", from, to, deviceIds, groupIds);
return routeReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to);
}
@@ -140,7 +140,7 @@ public class ReportResource extends SimpleObjectResource<Report> {
@QueryParam("mail") boolean mail) throws StorageException {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
return executeReport(getUserId(), mail, stream -> {
- LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds);
+ LogAction.report(getUserId(), false, "route", from, to, deviceIds, groupIds);
routeReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to);
});
}
@@ -166,7 +166,7 @@ public class ReportResource extends SimpleObjectResource<Report> {
@QueryParam("from") Date from,
@QueryParam("to") Date to) throws StorageException {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
- LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds);
+ LogAction.report(getUserId(), false, "events", from, to, deviceIds, groupIds);
return eventsReportProvider.getObjects(getUserId(), deviceIds, groupIds, types, from, to);
}
@@ -182,7 +182,7 @@ public class ReportResource extends SimpleObjectResource<Report> {
@QueryParam("mail") boolean mail) throws StorageException {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
return executeReport(getUserId(), mail, stream -> {
- LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds);
+ LogAction.report(getUserId(), false, "events", from, to, deviceIds, groupIds);
eventsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, types, from, to);
});
}
@@ -209,7 +209,7 @@ public class ReportResource extends SimpleObjectResource<Report> {
@QueryParam("to") Date to,
@QueryParam("daily") boolean daily) throws StorageException {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
- LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds);
+ LogAction.report(getUserId(), false, "summary", from, to, deviceIds, groupIds);
return summaryReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to, daily);
}
@@ -225,7 +225,7 @@ public class ReportResource extends SimpleObjectResource<Report> {
@QueryParam("mail") boolean mail) throws StorageException {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
return executeReport(getUserId(), mail, stream -> {
- LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds);
+ LogAction.report(getUserId(), false, "summary", from, to, deviceIds, groupIds);
summaryReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to, daily);
});
}
@@ -251,7 +251,7 @@ public class ReportResource extends SimpleObjectResource<Report> {
@QueryParam("from") Date from,
@QueryParam("to") Date to) throws StorageException {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
- LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds);
+ LogAction.report(getUserId(), false, "trips", from, to, deviceIds, groupIds);
return tripsReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to);
}
@@ -266,7 +266,7 @@ public class ReportResource extends SimpleObjectResource<Report> {
@QueryParam("mail") boolean mail) throws StorageException {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
return executeReport(getUserId(), mail, stream -> {
- LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds);
+ LogAction.report(getUserId(), false, "trips", from, to, deviceIds, groupIds);
tripsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to);
});
}
@@ -291,7 +291,7 @@ public class ReportResource extends SimpleObjectResource<Report> {
@QueryParam("from") Date from,
@QueryParam("to") Date to) throws StorageException {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
- LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds);
+ LogAction.report(getUserId(), false, "stops", from, to, deviceIds, groupIds);
return stopsReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to);
}
@@ -306,7 +306,7 @@ public class ReportResource extends SimpleObjectResource<Report> {
@QueryParam("mail") boolean mail) throws StorageException {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
return executeReport(getUserId(), mail, stream -> {
- LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds);
+ LogAction.report(getUserId(), false, "stops", from, to, deviceIds, groupIds);
stopsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to);
});
}
@@ -326,7 +326,7 @@ public class ReportResource extends SimpleObjectResource<Report> {
@Path("devices/{type:xlsx|mail}")
@GET
@Produces(EXCEL)
- public Response geDevicesExcel(
+ public Response getDevicesExcel(
@PathParam("type") String type) throws StorageException {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
return executeReport(getUserId(), type.equals("mail"), stream -> {
diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java
index 2a72d2773..eb10ac763 100644
--- a/src/main/java/org/traccar/api/resource/ServerResource.java
+++ b/src/main/java/org/traccar/api/resource/ServerResource.java
@@ -161,4 +161,11 @@ public class ServerResource extends BaseResource {
return cacheManager.toString();
}
+ @Path("reboot")
+ @POST
+ public void reboot() throws StorageException {
+ permissionsService.checkAdmin(getUserId());
+ System.exit(130);
+ }
+
}
diff --git a/src/main/java/org/traccar/api/security/LoginService.java b/src/main/java/org/traccar/api/security/LoginService.java
index 930c4fa46..507288c31 100644
--- a/src/main/java/org/traccar/api/security/LoginService.java
+++ b/src/main/java/org/traccar/api/security/LoginService.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2024 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.
@@ -20,6 +20,7 @@ import org.traccar.api.signature.TokenManager;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.database.LdapProvider;
+import org.traccar.helper.DataConverter;
import org.traccar.helper.model.UserUtil;
import org.traccar.model.User;
import org.traccar.storage.Storage;
@@ -32,6 +33,7 @@ import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
@Singleton
@@ -58,6 +60,20 @@ public class LoginService {
forceOpenId = config.getBoolean(Keys.OPENID_FORCE);
}
+ public LoginResult login(
+ String scheme, String credentials) throws StorageException, GeneralSecurityException, IOException {
+ switch (scheme.toLowerCase()) {
+ case "bearer":
+ return login(credentials);
+ case "basic":
+ byte[] decodedBytes = DataConverter.parseBase64(credentials);
+ String[] auth = new String(decodedBytes, StandardCharsets.US_ASCII).split(":", 2);
+ return login(auth[0], auth[1], null);
+ default:
+ throw new SecurityException("Unsupported authorization scheme");
+ }
+ }
+
public LoginResult login(String token) throws StorageException, GeneralSecurityException, IOException {
if (serviceAccountToken != null && serviceAccountToken.equals(token)) {
return new LoginResult(new ServiceAccountUser());
diff --git a/src/main/java/org/traccar/api/security/SecurityRequestFilter.java b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
index 12a5dbecf..07083e7a8 100644
--- a/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
+++ b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2024 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.
@@ -20,7 +20,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.api.resource.SessionResource;
import org.traccar.database.StatisticsManager;
-import org.traccar.helper.DataConverter;
import org.traccar.model.User;
import org.traccar.storage.StorageException;
@@ -36,7 +35,6 @@ import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;
import java.io.IOException;
import java.lang.reflect.Method;
-import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Date;
@@ -44,15 +42,6 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityRequestFilter.class);
- public static String[] decodeBasicAuth(String auth) {
- auth = auth.replaceFirst("[B|b]asic ", "");
- byte[] decodedBytes = DataConverter.parseBase64(auth);
- if (decodedBytes != null && decodedBytes.length > 0) {
- return new String(decodedBytes, StandardCharsets.US_ASCII).split(":", 2);
- }
- return null;
- }
-
@Context
private HttpServletRequest request;
@@ -83,13 +72,8 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
if (authHeader != null) {
try {
- LoginResult loginResult;
- if (authHeader.startsWith("Bearer ")) {
- loginResult = loginService.login(authHeader.substring(7));
- } else {
- String[] auth = decodeBasicAuth(authHeader);
- loginResult = loginService.login(auth[0], auth[1], null);
- }
+ String[] auth = authHeader.split(" ");
+ LoginResult loginResult = loginService.login(auth[0], auth[1]);
if (loginResult != null) {
User user = loginResult.getUser();
statisticsManager.registerRequest(user.getId());
diff --git a/src/main/java/org/traccar/config/Config.java b/src/main/java/org/traccar/config/Config.java
index 47e1f0707..2d76a5c70 100644
--- a/src/main/java/org/traccar/config/Config.java
+++ b/src/main/java/org/traccar/config/Config.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2024 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,20 +41,10 @@ public class Config {
@Inject
public Config(@Named("configFile") String file) throws IOException {
try {
- Properties mainProperties = new Properties();
try (InputStream inputStream = new FileInputStream(file)) {
- mainProperties.loadFromXML(inputStream);
+ properties.loadFromXML(inputStream);
}
- String defaultConfigFile = mainProperties.getProperty("config.default");
- if (defaultConfigFile != null) {
- try (InputStream inputStream = new FileInputStream(defaultConfigFile)) {
- properties.loadFromXML(inputStream);
- }
- }
-
- properties.putAll(mainProperties); // override defaults
-
useEnvironmentVariables = Boolean.parseBoolean(System.getenv("CONFIG_USE_ENVIRONMENT_VARIABLES"))
|| Boolean.parseBoolean(properties.getProperty("config.useEnvironmentVariables"));
@@ -102,7 +92,13 @@ public class Config {
}
public boolean getBoolean(ConfigKey<Boolean> key) {
- return Boolean.parseBoolean(getString(key.getKey()));
+ String value = getString(key.getKey());
+ if (value != null) {
+ return Boolean.parseBoolean(value);
+ } else {
+ Boolean defaultValue = key.getDefaultValue();
+ return Objects.requireNonNullElse(defaultValue, false);
+ }
}
public int getInteger(ConfigKey<Integer> key) {
diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java
index d346084bd..91d5dac5d 100644
--- a/src/main/java/org/traccar/config/Keys.java
+++ b/src/main/java/org/traccar/config/Keys.java
@@ -23,7 +23,7 @@ public final class Keys {
}
/**
- * Network interface for a the protocol. If not specified, server will bind all interfaces.
+ * Network interface for the protocol. If not specified, server will bind all interfaces.
*/
public static final ConfigSuffix<String> PROTOCOL_ADDRESS = new StringConfigSuffix(
".address",
@@ -33,7 +33,7 @@ public final class Keys {
* Port number for the protocol. Most protocols use TCP on the transport layer. Some protocols use UDP. Some
* support both TCP and UDP.
*/
- public static final ConfigSuffix<Integer> PROTOCOL_PORT = new IntegerConfigSuffix(
+ public static final ConfigSuffix<Integer> PROTOCOL_PORT = new PortConfigSuffix(
".port",
List.of(KeyType.CONFIG));
@@ -320,7 +320,8 @@ public final class Keys {
*/
public static final ConfigKey<String> SERVER_STATISTICS = new StringConfigKey(
"server.statistics",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ "https://www.traccar.org/analytics/");
/**
* Fuel drop threshold value. When fuel level drops from one position to another for more the value, an event is
@@ -397,7 +398,8 @@ public final class Keys {
*/
public static final ConfigKey<Boolean> EVENT_IGNORE_DUPLICATE_ALERTS = new BooleanConfigKey(
"event.ignoreDuplicateAlerts",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ true);
/**
* If set to true, invalid positions will be considered for motion logic.
@@ -472,7 +474,8 @@ public final class Keys {
*/
public static final ConfigKey<String> DATABASE_CHANGELOG = new StringConfigKey(
"database.changelog",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ "./schema/changelog-master.xml");
/**
* Database connection pool size. Default value is defined by the HikariCP library.
@@ -598,7 +601,7 @@ public final class Keys {
"uid");
/**
- * LDAP attribute used as user name. Default value is 'cn'.
+ * LDAP attribute used as username. Default value is 'cn'.
*/
public static final ConfigKey<String> LDAP_NAME_ATTRIBUTE = new StringConfigKey(
"ldap.nameAttribute",
@@ -671,7 +674,7 @@ public final class Keys {
/**
* OpenID Connect Authorization URL.
* This can usually be found in the documentation of your identity provider or by using the well-known
- * configuration endpoint, e.g. https://auth.example.com//.well-known/openid-configuration
+ * configuration endpoint, e.g. https://auth.example.com/.well-known/openid-configuration
* Required to enable SSO if openid.issuerUrl is not set.
*/
public static final ConfigKey<String> OPENID_AUTH_URL = new StringConfigKey(
@@ -736,7 +739,8 @@ public final class Keys {
*/
public static final ConfigKey<String> MEDIA_PATH = new StringConfigKey(
"media.path",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ "./media");
/**
* Optional parameter to specify network interface for web interface to bind to. By default server will bind to all
@@ -747,12 +751,13 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
- * Web interface TCP port number. By default Traccar uses port 8082. To avoid specifying port in the browser you
+ * Web interface TCP port number. By default, Traccar uses port 8082. To avoid specifying port in the browser you
* can set it to 80 (default HTTP port).
*/
public static final ConfigKey<Integer> WEB_PORT = new IntegerConfigKey(
"web.port",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ 8082);
/**
* Maximum API requests per second. Above this limit requests and delayed and throttled.
@@ -770,26 +775,20 @@ public final class Keys {
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.
- */
- public static final ConfigKey<Boolean> WEB_SANITIZE = new BooleanConfigKey(
- "web.sanitize",
- List.of(KeyType.CONFIG));
-
- /**
* Path to the web app folder.
*/
public static final ConfigKey<String> WEB_PATH = new StringConfigKey(
"web.path",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ "./web");
/**
* Path to a folder with overrides. It can be used for branding to keep custom logos in a separate place.
*/
public static final ConfigKey<String> WEB_OVERRIDE = new StringConfigKey(
"web.override",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ "./override");
/**
* WebSocket connection timeout in milliseconds. Default timeout is 5 minutes.
@@ -1172,7 +1171,8 @@ public final class Keys {
*/
public static final ConfigKey<String> NOTIFICATOR_TYPES = new StringConfigKey(
"notificator.types",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ "web,mail,command");
/**
* If the event time is too old, we should not send notifications. This parameter is the threshold value in
@@ -1261,6 +1261,13 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * Block notifications for specific users. The value should be a comma-separated list of internal user ids.
+ */
+ public static final ConfigKey<String> NOTIFICATION_BLOCK_USERS = new StringConfigKey(
+ "notification.block.users",
+ List.of(KeyType.CONFIG));
+
+ /**
* Maximum time period for reports in seconds. Can be useful to prevent users to request unreasonably long reports.
* By default, there is no limit.
*/
@@ -1331,7 +1338,8 @@ public final class Keys {
*/
public static final ConfigKey<Boolean> FILTER_ENABLE = new BooleanConfigKey(
"filter.enable",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ true);
/**
* Filter invalid (valid field is set to false) positions.
@@ -1369,7 +1377,8 @@ public final class Keys {
*/
public static final ConfigKey<Long> FILTER_FUTURE = new LongConfigKey(
"filter.future",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ 86400L);
/**
* Filter records with fix time in the past. The value is specified in seconds. Records that have fix time more
@@ -1426,13 +1435,20 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
- * Filter position if the daily limit is exceeded for the device.
+ * Throttle positions 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));
/**
+ * Throttling interval if the limit exceeded. The value is in seconds.
+ */
+ public static final ConfigKey<Integer> FILTER_DAILY_LIMIT_INTERVAL = new IntegerConfigKey(
+ "filter.dailyLimitInterval",
+ 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).
@@ -1580,15 +1596,16 @@ public final class Keys {
*/
public static final ConfigKey<Boolean> GEOCODER_ENABLE = new BooleanConfigKey(
"geocoder.enable",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ true);
/**
- * Reverse geocoder type. Check reverse geocoding documentation for more info. By default (if the value is not
- * specified) server uses Google API.
+ * Reverse geocoder type. Check reverse geocoding documentation for more info.
*/
public static final ConfigKey<String> GEOCODER_TYPE = new StringConfigKey(
"geocoder.type",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ "locationiq");
/**
* Geocoder server URL. Applicable only to Nominatim and Gisgraphy providers.
@@ -1602,7 +1619,8 @@ public final class Keys {
*/
public static final ConfigKey<String> GEOCODER_KEY = new StringConfigKey(
"geocoder.key",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ "pk.689d849289c8c63708068b2ff1f63b2d");
/**
* Language parameter for providers that support localization (e.g. Google and Nominatim).
@@ -1630,7 +1648,8 @@ public final class Keys {
*/
public static final ConfigKey<Boolean> GEOCODER_IGNORE_POSITIONS = new BooleanConfigKey(
"geocoder.ignorePositions",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ true);
/**
* Boolean flag to apply reverse geocoding to invalid positions.
@@ -1652,7 +1671,8 @@ public final class Keys {
*/
public static final ConfigKey<Boolean> GEOCODER_ON_REQUEST = new BooleanConfigKey(
"geocoder.onRequest",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ true);
/**
* Boolean flag to enable LBS location resolution. Some devices send cell towers information and WiFi point when GPS
@@ -1853,7 +1873,8 @@ public final class Keys {
*/
public static final ConfigKey<String> LOGGER_FILE = new StringConfigKey(
"logger.file",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ "./logs/tracker-server.log");
/**
* Logging level. Default value is 'info'.
@@ -1861,7 +1882,8 @@ public final class Keys {
*/
public static final ConfigKey<String> LOGGER_LEVEL = new StringConfigKey(
"logger.level",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ "info");
/**
* Print full exception traces. Useful for debugging. By default shortened traces are logged.
@@ -1876,7 +1898,8 @@ public final class Keys {
*/
public static final ConfigKey<Boolean> LOGGER_ROTATE = new BooleanConfigKey(
"logger.rotate",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ true);
/**
* Log file rotation interval, the default rotation interval is once a day.
diff --git a/src/main/java/org/traccar/config/PortConfigSuffix.java b/src/main/java/org/traccar/config/PortConfigSuffix.java
new file mode 100644
index 000000000..11d6f2dbb
--- /dev/null
+++ b/src/main/java/org/traccar/config/PortConfigSuffix.java
@@ -0,0 +1,278 @@
+package org.traccar.config;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PortConfigSuffix extends ConfigSuffix<Integer> {
+
+ private static final Map<String, Integer> PORTS = new HashMap<>();
+
+ static {
+ PORTS.put("gps103", 5001);
+ PORTS.put("tk103", 5002);
+ PORTS.put("gl100", 5003);
+ PORTS.put("gl200", 5004);
+ PORTS.put("t55", 5005);
+ PORTS.put("xexun", 5006);
+ PORTS.put("totem", 5007);
+ PORTS.put("enfora", 5008);
+ PORTS.put("meiligao", 5009);
+ PORTS.put("trv", 5010);
+ PORTS.put("suntech", 5011);
+ PORTS.put("progress", 5012);
+ PORTS.put("h02", 5013);
+ PORTS.put("jt600", 5014);
+ PORTS.put("huabao", 5015);
+ PORTS.put("v680", 5016);
+ PORTS.put("pt502", 5017);
+ PORTS.put("tr20", 5018);
+ PORTS.put("navis", 5019);
+ PORTS.put("meitrack", 5020);
+ PORTS.put("skypatrol", 5021);
+ PORTS.put("gt02", 5022);
+ PORTS.put("gt06", 5023);
+ PORTS.put("megastek", 5024);
+ PORTS.put("navigil", 5025);
+ PORTS.put("gpsgate", 5026);
+ PORTS.put("teltonika", 5027);
+ PORTS.put("mta6", 5028);
+ PORTS.put("tzone", 5029);
+ PORTS.put("tlt2h", 5030);
+ PORTS.put("taip", 5031);
+ PORTS.put("wondex", 5032);
+ PORTS.put("cellocator", 5033);
+ PORTS.put("galileo", 5034);
+ PORTS.put("ywt", 5035);
+ PORTS.put("tk102", 5036);
+ PORTS.put("intellitrac", 5037);
+ PORTS.put("gpsmta", 5038);
+ PORTS.put("wialon", 5039);
+ PORTS.put("carscop", 5040);
+ PORTS.put("apel", 5041);
+ PORTS.put("manpower", 5042);
+ PORTS.put("globalsat", 5043);
+ PORTS.put("atrack", 5044);
+ PORTS.put("pt3000", 5045);
+ PORTS.put("ruptela", 5046);
+ PORTS.put("topflytech", 5047);
+ PORTS.put("laipac", 5048);
+ PORTS.put("aplicom", 5049);
+ PORTS.put("gotop", 5050);
+ PORTS.put("sanav", 5051);
+ PORTS.put("gator", 5052);
+ PORTS.put("noran", 5053);
+ PORTS.put("m2m", 5054);
+ PORTS.put("osmand", 5055);
+ PORTS.put("easytrack", 5056);
+ PORTS.put("gpsmarker", 5057);
+ PORTS.put("khd", 5058);
+ PORTS.put("piligrim", 5059);
+ PORTS.put("stl060", 5060);
+ PORTS.put("cartrack", 5061);
+ PORTS.put("minifinder", 5062);
+ PORTS.put("haicom", 5063);
+ PORTS.put("eelink", 5064);
+ PORTS.put("box", 5065);
+ PORTS.put("freedom", 5066);
+ PORTS.put("telic", 5067);
+ PORTS.put("trackbox", 5068);
+ PORTS.put("visiontek", 5069);
+ PORTS.put("orion", 5070);
+ PORTS.put("riti", 5071);
+ PORTS.put("ulbotech", 5072);
+ PORTS.put("tramigo", 5073);
+ PORTS.put("tr900", 5074);
+ PORTS.put("ardi01", 5075);
+ PORTS.put("xt013", 5076);
+ PORTS.put("autofon", 5077);
+ PORTS.put("gosafe", 5078);
+ PORTS.put("tt8850", 5079);
+ PORTS.put("bce", 5080);
+ PORTS.put("xirgo", 5081);
+ PORTS.put("calamp", 5082);
+ PORTS.put("mtx", 5083);
+ PORTS.put("tytan", 5084);
+ PORTS.put("avl301", 5085);
+ PORTS.put("castel", 5086);
+ PORTS.put("mxt", 5087);
+ PORTS.put("cityeasy", 5088);
+ PORTS.put("aquila", 5089);
+ PORTS.put("flextrack", 5090);
+ PORTS.put("blackkite", 5091);
+ PORTS.put("adm", 5092);
+ PORTS.put("watch", 5093);
+ PORTS.put("t800x", 5094);
+ PORTS.put("upro", 5095);
+ PORTS.put("auro", 5096);
+ PORTS.put("disha", 5097);
+ PORTS.put("thinkrace", 5098);
+ PORTS.put("pathaway", 5099);
+ PORTS.put("arnavi", 5100);
+ PORTS.put("nvs", 5101);
+ PORTS.put("kenji", 5102);
+ PORTS.put("astra", 5103);
+ PORTS.put("homtecs", 5104);
+ PORTS.put("fox", 5105);
+ PORTS.put("gnx", 5106);
+ PORTS.put("arknav", 5107);
+ PORTS.put("supermate", 5108);
+ PORTS.put("appello", 5109);
+ PORTS.put("idpl", 5110);
+ PORTS.put("huasheng", 5111);
+ PORTS.put("l100", 5112);
+ PORTS.put("granit", 5113);
+ PORTS.put("carcell", 5114);
+ PORTS.put("obddongle", 5115);
+ PORTS.put("hunterpro", 5116);
+ PORTS.put("raveon", 5117);
+ PORTS.put("cradlepoint", 5118);
+ PORTS.put("arknavx8", 5119);
+ PORTS.put("autograde", 5120);
+ PORTS.put("oigo", 5121);
+ PORTS.put("jpkorjar", 5122);
+ PORTS.put("cguard", 5123);
+ PORTS.put("fifotrack", 5124);
+ PORTS.put("smokey", 5125);
+ PORTS.put("extremtrac", 5126);
+ PORTS.put("trakmate", 5127);
+ PORTS.put("at2000", 5128);
+ PORTS.put("maestro", 5129);
+ PORTS.put("ais", 5130);
+ PORTS.put("gt30", 5131);
+ PORTS.put("tmg", 5132);
+ PORTS.put("pretrace", 5133);
+ PORTS.put("pricol", 5134);
+ PORTS.put("siwi", 5135);
+ PORTS.put("starlink", 5136);
+ PORTS.put("dmt", 5137);
+ PORTS.put("xt2400", 5138);
+ PORTS.put("dmthttp", 5139);
+ PORTS.put("alematics", 5140);
+ PORTS.put("gps056", 5141);
+ PORTS.put("flexcomm", 5142);
+ PORTS.put("vt200", 5143);
+ PORTS.put("owntracks", 5144);
+ PORTS.put("vtfms", 5145);
+ PORTS.put("tlv", 5146);
+ PORTS.put("esky", 5147);
+ PORTS.put("genx", 5148);
+ PORTS.put("flespi", 5149);
+ PORTS.put("dway", 5150);
+ PORTS.put("recoda", 5151);
+ PORTS.put("oko", 5152);
+ PORTS.put("ivt401", 5153);
+ PORTS.put("sigfox", 5154);
+ PORTS.put("t57", 5155);
+ PORTS.put("spot", 5156);
+ PORTS.put("m2c", 5157);
+ PORTS.put("austinnb", 5158);
+ PORTS.put("opengts", 5159);
+ PORTS.put("cautela", 5160);
+ PORTS.put("continental", 5161);
+ PORTS.put("egts", 5162);
+ PORTS.put("robotrack", 5163);
+ PORTS.put("pt60", 5164);
+ PORTS.put("telemax", 5165);
+ PORTS.put("sabertek", 5166);
+ PORTS.put("retranslator", 5167);
+ PORTS.put("svias", 5168);
+ PORTS.put("eseal", 5169);
+ PORTS.put("freematics", 5170);
+ PORTS.put("avema", 5171);
+ PORTS.put("autotrack", 5172);
+ PORTS.put("tek", 5173);
+ PORTS.put("wristband", 5174);
+ PORTS.put("lacak", 5175);
+ PORTS.put("milesmate", 5176);
+ PORTS.put("anytrek", 5177);
+ PORTS.put("smartsole", 5178);
+ PORTS.put("its", 5179);
+ PORTS.put("xrb28", 5180);
+ PORTS.put("c2stek", 5181);
+ PORTS.put("nyitech", 5182);
+ PORTS.put("neos", 5183);
+ PORTS.put("satsol", 5184);
+ PORTS.put("globalstar", 5185);
+ PORTS.put("sanul", 5186);
+ PORTS.put("minifinder2", 5187);
+ PORTS.put("radar", 5188);
+ PORTS.put("techtlt", 5189);
+ PORTS.put("starcom", 5190);
+ PORTS.put("mictrack", 5191);
+ PORTS.put("plugin", 5192);
+ PORTS.put("leafspy", 5193);
+ PORTS.put("naviset", 5194);
+ PORTS.put("racedynamics", 5195);
+ PORTS.put("rst", 5196);
+ PORTS.put("pt215", 5197);
+ PORTS.put("pacifictrack", 5198);
+ PORTS.put("topin", 5199);
+ PORTS.put("outsafe", 5200);
+ PORTS.put("solarpowered", 5201);
+ PORTS.put("motor", 5202);
+ PORTS.put("omnicomm", 5203);
+ PORTS.put("s168", 5204);
+ PORTS.put("vnet", 5205);
+ PORTS.put("blue", 5206);
+ PORTS.put("pst", 5207);
+ PORTS.put("dingtek", 5208);
+ PORTS.put("wli", 5209);
+ PORTS.put("niot", 5210);
+ PORTS.put("portman", 5211);
+ PORTS.put("moovbox", 5212);
+ PORTS.put("futureway", 5213);
+ PORTS.put("polte", 5214);
+ PORTS.put("net", 5215);
+ PORTS.put("mobilogix", 5216);
+ PORTS.put("swiftech", 5217);
+ PORTS.put("iotm", 5218);
+ PORTS.put("dolphin", 5219);
+ PORTS.put("ennfu", 5220);
+ PORTS.put("navtelecom", 5221);
+ PORTS.put("startek", 5222);
+ PORTS.put("gs100", 5223);
+ PORTS.put("mavlink2", 5224);
+ PORTS.put("uux", 5225);
+ PORTS.put("r12w", 5226);
+ PORTS.put("flexiblereport", 5227);
+ PORTS.put("thinkpower", 5228);
+ PORTS.put("stb", 5229);
+ PORTS.put("b2316", 5230);
+ PORTS.put("hoopo", 5231);
+ PORTS.put("dualcam", 5232);
+ PORTS.put("xexun2", 5233);
+ PORTS.put("techtocruz", 5234);
+ PORTS.put("flexapi", 5235);
+ PORTS.put("dsf22", 5236);
+ PORTS.put("jido", 5237);
+ PORTS.put("armoli", 5238);
+ PORTS.put("teratrack", 5239);
+ PORTS.put("envotech", 5240);
+ PORTS.put("bstpl", 5241);
+ PORTS.put("thuraya", 5242);
+ PORTS.put("ndtpv6", 5243);
+ PORTS.put("g1rus", 5244);
+ PORTS.put("rftrack", 5245);
+ PORTS.put("vlt", 5246);
+ PORTS.put("transync", 5247);
+ PORTS.put("t622iridium", 5248);
+ PORTS.put("pui", 5249);
+ PORTS.put("nto", 5250);
+ PORTS.put("ramac", 5251);
+ PORTS.put("positrex", 5252);
+ PORTS.put("dragino", 5253);
+ PORTS.put("fleetguide", 5254);
+ PORTS.put("valtrack", 5255);
+ PORTS.put("snapper", 5256);
+ }
+
+ PortConfigSuffix(String key, List<KeyType> types) {
+ super(key, types, null);
+ }
+
+ @Override
+ public ConfigKey<Integer> withPrefix(String protocol) {
+ return new IntegerConfigKey(protocol + keySuffix, types, PORTS.get(protocol));
+ }
+}
diff --git a/src/main/java/org/traccar/database/NotificationManager.java b/src/main/java/org/traccar/database/NotificationManager.java
index 65437f0a1..60b4f246b 100644
--- a/src/main/java/org/traccar/database/NotificationManager.java
+++ b/src/main/java/org/traccar/database/NotificationManager.java
@@ -42,8 +42,10 @@ import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
import java.util.stream.Collectors;
@Singleton
@@ -59,6 +61,7 @@ public class NotificationManager {
private final boolean geocodeOnRequest;
private final long timeThreshold;
+ private final Set<Long> blockedUsers = new HashSet<>();
@Inject
public NotificationManager(
@@ -71,6 +74,12 @@ public class NotificationManager {
this.geocoder = geocoder;
geocodeOnRequest = config.getBoolean(Keys.GEOCODER_ON_REQUEST);
timeThreshold = config.getLong(Keys.NOTIFICATOR_TIME_THRESHOLD);
+ String blockedUsersString = config.getString(Keys.NOTIFICATION_BLOCK_USERS);
+ if (blockedUsersString != null) {
+ for (String userIdString : blockedUsersString.split(",")) {
+ blockedUsers.add(Long.parseLong(userIdString));
+ }
+ }
}
private void updateEvent(Event event, Position position) {
@@ -122,6 +131,10 @@ public class NotificationManager {
notifications.forEach(notification -> {
cacheManager.getNotificationUsers(notification.getId(), event.getDeviceId()).forEach(user -> {
+ if (blockedUsers.contains(user.getId())) {
+ LOGGER.info("User {} notification blocked", user.getId());
+ return;
+ }
for (String notificator : notification.getNotificatorsTypes()) {
try {
notificatorManager.getNotificator(notificator).send(notification, user, event, position);
diff --git a/src/main/java/org/traccar/database/StatisticsManager.java b/src/main/java/org/traccar/database/StatisticsManager.java
index 445e53e7c..8711289a0 100644
--- a/src/main/java/org/traccar/database/StatisticsManager.java
+++ b/src/main/java/org/traccar/database/StatisticsManager.java
@@ -121,7 +121,7 @@ public class StatisticsManager {
}
String url = config.getString(Keys.SERVER_STATISTICS);
- if (url != null) {
+ if (url != null && !url.isEmpty()) {
String time = DateUtil.formatDate(statistics.getCaptureTime());
Form form = new Form();
diff --git a/src/main/java/org/traccar/geocoder/TestGeocoder.java b/src/main/java/org/traccar/geocoder/PlusCodesGeocoder.java
index 259f13c6c..5d003192f 100644
--- a/src/main/java/org/traccar/geocoder/TestGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/PlusCodesGeocoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2024 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,9 +15,10 @@
*/
package org.traccar.geocoder;
+import com.google.openlocationcode.OpenLocationCode;
import org.traccar.database.StatisticsManager;
-public class TestGeocoder implements Geocoder {
+public class PlusCodesGeocoder implements Geocoder {
@Override
public void setStatisticsManager(StatisticsManager statisticsManager) {
@@ -25,7 +26,7 @@ public class TestGeocoder implements Geocoder {
@Override
public String getAddress(double latitude, double longitude, ReverseGeocoderCallback callback) {
- String address = String.format("Location %f, %f", latitude, longitude);
+ String address = new OpenLocationCode(latitude, longitude).getCode();
if (callback != null) {
callback.onSuccess(address);
return null;
diff --git a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java
index 4293bd1fc..d286866a5 100644
--- a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java
+++ b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java
@@ -35,12 +35,14 @@ import org.traccar.session.cache.CacheManager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.Date;
+import java.util.stream.Collectors;
public class ComputedAttributesHandler extends BasePositionHandler {
@@ -63,7 +65,7 @@ public class ComputedAttributesHandler extends BasePositionHandler {
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)
+ Character.class, Boolean.class, String.class, Byte.class, Date.class)
.forEach((type) -> sandbox.allow(type.getName()));
features = new JexlFeatures()
.localVar(config.getBoolean(Keys.PROCESSING_COMPUTED_ATTRIBUTES_LOCAL_VARIABLES))
@@ -139,7 +141,9 @@ public class ComputedAttributesHandler extends BasePositionHandler {
@Override
public void handlePosition(Position position, Callback callback) {
- Collection<Attribute> attributes = cacheManager.getDeviceObjects(position.getDeviceId(), Attribute.class);
+ var attributes = cacheManager.getDeviceObjects(position.getDeviceId(), Attribute.class).stream()
+ .sorted(Comparator.comparing(Attribute::getPriority).reversed())
+ .collect(Collectors.toUnmodifiableList());
for (Attribute attribute : attributes) {
if (attribute.getAttribute() != null) {
Object result = null;
diff --git a/src/main/java/org/traccar/handler/DatabaseHandler.java b/src/main/java/org/traccar/handler/DatabaseHandler.java
index 5d96ebb34..4619e9d34 100644
--- a/src/main/java/org/traccar/handler/DatabaseHandler.java
+++ b/src/main/java/org/traccar/handler/DatabaseHandler.java
@@ -42,7 +42,7 @@ public class DatabaseHandler extends BasePositionHandler {
try {
position.setId(storage.addObject(position, new Request(new Columns.Exclude("id"))));
- statisticsManager.messageStoredCount(position.getDeviceId());
+ statisticsManager.registerMessageStored(position.getDeviceId(), position.getProtocol());
} catch (Exception error) {
LOGGER.warn("Failed to store position", error);
}
diff --git a/src/main/java/org/traccar/handler/FilterHandler.java b/src/main/java/org/traccar/handler/FilterHandler.java
index 796c302fb..700fdbc13 100644
--- a/src/main/java/org/traccar/handler/FilterHandler.java
+++ b/src/main/java/org/traccar/handler/FilterHandler.java
@@ -53,6 +53,7 @@ public class FilterHandler extends BasePositionHandler {
private final int filterMaxSpeed;
private final long filterMinPeriod;
private final int filterDailyLimit;
+ private final long filterDailyLimitInterval;
private final boolean filterRelative;
private final long skipLimit;
private final boolean skipAttributes;
@@ -77,6 +78,7 @@ public class FilterHandler extends BasePositionHandler {
filterMaxSpeed = config.getInteger(Keys.FILTER_MAX_SPEED);
filterMinPeriod = config.getInteger(Keys.FILTER_MIN_PERIOD) * 1000L;
filterDailyLimit = config.getInteger(Keys.FILTER_DAILY_LIMIT);
+ filterDailyLimitInterval = config.getInteger(Keys.FILTER_DAILY_LIMIT_INTERVAL) * 1000L;
filterRelative = config.getBoolean(Keys.FILTER_RELATIVE);
skipLimit = config.getLong(Keys.FILTER_SKIP_LIMIT) * 1000;
skipAttributes = config.getBoolean(Keys.FILTER_SKIP_ATTRIBUTES_ENABLE);
@@ -151,7 +153,7 @@ public class FilterHandler extends BasePositionHandler {
if (filterMaxSpeed != 0 && last != null) {
double distance = position.getDouble(Position.KEY_DISTANCE);
double time = position.getFixTime().getTime() - last.getFixTime().getTime();
- return UnitsConverter.knotsFromMps(distance / (time / 1000)) > filterMaxSpeed;
+ return time > 0 && UnitsConverter.knotsFromMps(distance / (time / 1000)) > filterMaxSpeed;
}
return false;
}
@@ -164,9 +166,12 @@ public class FilterHandler extends BasePositionHandler {
return false;
}
- private boolean filterDailyLimit(Position position) {
- if (filterDailyLimit != 0) {
- return statisticsManager.messageStoredCount(position.getDeviceId()) >= filterDailyLimit;
+ private boolean filterDailyLimit(Position position, Position last) {
+ if (filterDailyLimit != 0
+ && statisticsManager.messageStoredCount(position.getDeviceId()) >= filterDailyLimit) {
+ long lastTime = last != null ? last.getFixTime().getTime() : 0;
+ long interval = position.getFixTime().getTime() - lastTime;
+ return filterDailyLimitInterval <= 0 || interval < filterDailyLimitInterval;
}
return false;
}
@@ -216,20 +221,18 @@ public class FilterHandler extends BasePositionHandler {
if (filterApproximate(position)) {
filterType.append("Approximate ");
}
- if (filterDailyLimit(position)) {
- filterType.append("DailyLimit ");
- }
// filter out excessive data
long deviceId = position.getDeviceId();
- if (filterDuplicate || filterStatic || filterDistance > 0 || filterMaxSpeed > 0 || filterMinPeriod > 0) {
- Position preceding = null;
+ if (filterDuplicate || filterStatic
+ || filterDistance > 0 || filterMaxSpeed > 0 || filterMinPeriod > 0 || filterDailyLimit > 0) {
+ Position preceding;
if (filterRelative) {
try {
Date newFixTime = position.getFixTime();
preceding = getPrecedingPosition(deviceId, newFixTime);
} catch (StorageException e) {
- LOGGER.warn("Error retrieving preceding position; fallbacking to last received position.", e);
+ LOGGER.warn("Error retrieving preceding position; fall backing to last received position.", e);
preceding = cacheManager.getPosition(deviceId);
}
} else {
@@ -250,6 +253,9 @@ public class FilterHandler extends BasePositionHandler {
if (filterMinPeriod(position, preceding)) {
filterType.append("MinPeriod ");
}
+ if (filterDailyLimit(position, preceding)) {
+ filterType.append("DailyLimit ");
+ }
}
Device device = cacheManager.getObject(Device.class, deviceId);
diff --git a/src/main/java/org/traccar/handler/GeocoderHandler.java b/src/main/java/org/traccar/handler/GeocoderHandler.java
index b84237856..3744700da 100644
--- a/src/main/java/org/traccar/handler/GeocoderHandler.java
+++ b/src/main/java/org/traccar/handler/GeocoderHandler.java
@@ -43,11 +43,7 @@ public class GeocoderHandler extends BasePositionHandler {
@Override
public void handlePosition(Position position, Callback callback) {
- if (!ignorePositions) {
- callback.processed(false);
- }
-
- if (processInvalidPositions || position.getValid()) {
+ if (!ignorePositions && (processInvalidPositions || position.getValid())) {
if (reuseDistance != 0) {
Position lastPosition = cacheManager.getPosition(position.getDeviceId());
if (lastPosition != null && lastPosition.getAddress() != null
diff --git a/src/main/java/org/traccar/helper/LogAction.java b/src/main/java/org/traccar/helper/LogAction.java
index b255b9206..3c4b69a2e 100644
--- a/src/main/java/org/traccar/helper/LogAction.java
+++ b/src/main/java/org/traccar/helper/LogAction.java
@@ -43,14 +43,17 @@ public final class LogAction {
private static final String ACTION_LOGIN = "login";
private static final String ACTION_LOGOUT = "logout";
- private static final String ACTION_DEVICE_ACCUMULATORS = "resetDeviceAccumulators";
+ private static final String ACTION_ACCUMULATORS = "accumulators";
+ private static final String ACTION_COMMAND = "command";
private static final String PATTERN_OBJECT = "user: %d, action: %s, object: %s, id: %d";
private static final String PATTERN_LINK = "user: %d, action: %s, owner: %s, id: %d, property: %s, id: %d";
private static final String PATTERN_LOGIN = "user: %d, action: %s, from: %s";
private static final String PATTERN_LOGIN_FAILED = "login failed from: %s";
- private static final String PATTERN_DEVICE_ACCUMULATORS = "user: %d, action: %s, deviceId: %d";
- private static final String PATTERN_REPORT = "user: %d, report: %s, from: %s, to: %s, devices: %s, groups: %s";
+ private static final String PATTERN_ACCUMULATORS = "user: %d, action: %s, deviceId: %d";
+ private static final String PATTERN_COMMAND_DEVICE = "user: %d, action: %s, deviceId: %d, type: %s";
+ private static final String PATTERN_COMMAND_GROUP = "user: %d, action: %s, groupId: %d, type: %s";
+ private static final String PATTERN_REPORT = "user: %d, %s: %s, from: %s, to: %s, devices: %s, groups: %s";
public static void create(long userId, BaseModel object) {
logObjectAction(ACTION_CREATE, userId, object.getClass(), object.getId());
@@ -87,9 +90,27 @@ public final class LogAction {
LOGGER.info(String.format(PATTERN_LOGIN_FAILED, remoteAddress));
}
- public static void resetDeviceAccumulators(long userId, long deviceId) {
+ public static void resetAccumulators(long userId, long deviceId) {
LOGGER.info(String.format(
- PATTERN_DEVICE_ACCUMULATORS, userId, ACTION_DEVICE_ACCUMULATORS, deviceId));
+ PATTERN_ACCUMULATORS, userId, ACTION_ACCUMULATORS, deviceId));
+ }
+
+ public static void command(long userId, long groupId, long deviceId, String type) {
+ if (groupId > 0) {
+ LOGGER.info(String.format(PATTERN_COMMAND_GROUP, userId, ACTION_COMMAND, groupId, type));
+ } else {
+ LOGGER.info(String.format(PATTERN_COMMAND_DEVICE, userId, ACTION_COMMAND, deviceId, type));
+ }
+ }
+
+ public static void report(
+ long userId, boolean scheduled, String report,
+ Date from, Date to, List<Long> deviceIds, List<Long> groupIds) {
+ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+ LOGGER.info(String.format(
+ PATTERN_REPORT, userId, scheduled ? "scheduled" : "report", report,
+ dateFormat.format(from), dateFormat.format(to),
+ deviceIds.toString(), groupIds.toString()));
}
private static void logObjectAction(String action, long userId, Class<?> clazz, long objectId) {
@@ -112,13 +133,4 @@ public final class LogAction {
LOGGER.info(String.format(PATTERN_LOGIN, userId, action, remoteAddress));
}
- public static void logReport(
- long userId, String report, Date from, Date to, List<Long> deviceIds, List<Long> groupIds) {
- DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
- LOGGER.info(String.format(
- PATTERN_REPORT, userId, report,
- dateFormat.format(from), dateFormat.format(to),
- deviceIds.toString(), groupIds.toString()));
- }
-
}
diff --git a/src/main/java/org/traccar/helper/SanitizerModule.java b/src/main/java/org/traccar/helper/SanitizerModule.java
deleted file mode 100644
index af9ac5c2b..000000000
--- a/src/main/java/org/traccar/helper/SanitizerModule.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2018 Anton Tananaev (anton@traccar.org)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.traccar.helper;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.module.SimpleModule;
-import com.fasterxml.jackson.databind.ser.std.StdSerializer;
-import org.owasp.encoder.Encode;
-
-import java.io.IOException;
-
-public class SanitizerModule extends SimpleModule {
-
- public static class SanitizerSerializer extends StdSerializer<String> {
-
- protected SanitizerSerializer() {
- super(String.class);
- }
-
- @Override
- public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
- gen.writeString(Encode.forHtml(value));
- }
-
- }
-
- public SanitizerModule() {
- addSerializer(new SanitizerSerializer());
- }
-
-}
diff --git a/src/main/java/org/traccar/model/Attribute.java b/src/main/java/org/traccar/model/Attribute.java
index 65f2e3881..573207170 100644
--- a/src/main/java/org/traccar/model/Attribute.java
+++ b/src/main/java/org/traccar/model/Attribute.java
@@ -61,4 +61,14 @@ public class Attribute extends BaseModel {
this.type = type;
}
+ private int priority;
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+
}
diff --git a/src/main/java/org/traccar/model/Calendar.java b/src/main/java/org/traccar/model/Calendar.java
index 03f1995ba..76c9a2cc1 100644
--- a/src/main/java/org/traccar/model/Calendar.java
+++ b/src/main/java/org/traccar/model/Calendar.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -34,6 +34,7 @@ import java.time.Duration;
import java.util.Collection;
import java.util.Date;
import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
@StorageName("tc_calendars")
@@ -78,10 +79,9 @@ public class Calendar extends ExtendedModel {
}
}
- public Collection<Period> findPeriods(Date date) {
- var calendarDate = new net.fortuna.ical4j.model.Date(date);
+ public Set<Period> findPeriods(Date date) {
return findEvents(date).stream()
- .flatMap((event) -> event.getConsumedTime(calendarDate, calendarDate).stream())
+ .flatMap((e) -> e.calculateRecurrenceSet(new Period(new DateTime(date), Duration.ZERO)).stream())
.collect(Collectors.toSet());
}
diff --git a/src/main/java/org/traccar/model/Server.java b/src/main/java/org/traccar/model/Server.java
index 6442186b6..b29fe27cc 100644
--- a/src/main/java/org/traccar/model/Server.java
+++ b/src/main/java/org/traccar/model/Server.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2024 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.
@@ -126,16 +126,6 @@ public class Server extends ExtendedModel implements UserRestrictions {
this.zoom = zoom;
}
- private boolean twelveHourFormat;
-
- public boolean getTwelveHourFormat() {
- return twelveHourFormat;
- }
-
- public void setTwelveHourFormat(boolean twelveHourFormat) {
- this.twelveHourFormat = twelveHourFormat;
- }
-
private boolean forceSettings;
public boolean getForceSettings() {
diff --git a/src/main/java/org/traccar/model/User.java b/src/main/java/org/traccar/model/User.java
index 8cfee0f48..9b8ee3e53 100644
--- a/src/main/java/org/traccar/model/User.java
+++ b/src/main/java/org/traccar/model/User.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2013 - 2024 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.
@@ -133,16 +133,6 @@ public class User extends ExtendedModel implements UserRestrictions, Disableable
this.zoom = zoom;
}
- private boolean twelveHourFormat;
-
- public boolean getTwelveHourFormat() {
- return twelveHourFormat;
- }
-
- public void setTwelveHourFormat(boolean twelveHourFormat) {
- this.twelveHourFormat = twelveHourFormat;
- }
-
private String coordinateFormat;
public String getCoordinateFormat() {
diff --git a/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java
index b10ff4c64..ff192807b 100644
--- a/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java
@@ -92,7 +92,7 @@ public class EasyTrackProtocolDecoder extends BaseProtocolDecoder {
.number("C(d+);") // coolant temperature
.number("L(d+.d);") // fuel level
.number("[XY][MH]d+.d+;")
- .number("M(d+);") // mileage
+ .number("Md+.?d*;") // mileage
.number("F(d+.d+);") // fuel consumption
.number("T(d+);") // engine time
.any()
@@ -264,7 +264,6 @@ public class EasyTrackProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_ENGINE_LOAD, parser.nextDouble());
position.set(Position.KEY_COOLANT_TEMP, parser.nextInt());
position.set(Position.KEY_FUEL_LEVEL, parser.nextDouble());
- position.set(Position.KEY_ODOMETER, parser.nextInt());
position.set(Position.KEY_FUEL_CONSUMPTION, parser.nextDouble());
position.set(Position.KEY_HOURS, parser.nextInt());
diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
index 775e98401..8edce9346 100644
--- a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
@@ -974,6 +974,9 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
if (model.startsWith("GV") && !model.startsWith("GV6")) {
position.set(Position.PREFIX_ADC + 2, v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]) * 0.001);
}
+ if (model.startsWith("GV355CEU")) {
+ index += 1; // reserved
+ }
position.set(Position.KEY_BATTERY_LEVEL, v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]));
if (model.startsWith("GL5")) {
diff --git a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java
index b75e612b8..654071d22 100644
--- a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2024 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,6 @@ import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import org.traccar.BaseHttpProtocolDecoder;
-import org.traccar.config.Keys;
import org.traccar.session.DeviceSession;
import org.traccar.NetworkMessage;
import org.traccar.Protocol;
@@ -65,12 +64,6 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder {
private final XPath xPath;
private final XPathExpression messageExpression;
- private boolean alternative;
-
- public void setAlternative(boolean alternative) {
- this.alternative = alternative;
- }
-
public GlobalstarProtocolDecoder(Protocol protocol) {
super(protocol);
try {
@@ -89,11 +82,6 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder {
}
}
- @Override
- protected void init() {
- this.alternative = getConfig().getBoolean(Keys.PROTOCOL_ALTERNATIVE.withPrefix(getProtocolName()));
- }
-
private void sendResponse(Channel channel, String messageId) throws TransformerException {
Document document = documentBuilder.newDocument();
@@ -144,6 +132,7 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder {
DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, xPath.evaluate("esn", node));
if (deviceSession != null) {
+ boolean atlas = "AtlasTrax".equalsIgnoreCase(getDeviceModel(deviceSession));
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
@@ -154,7 +143,7 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder {
int flags = buf.readUnsignedByte();
int type;
- if (alternative) {
+ if (atlas) {
type = BitUtil.to(flags, 1);
position.setValid(true);
position.set(Position.PREFIX_IN + 1, !BitUtil.check(flags, 1));
@@ -179,7 +168,7 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder {
position.setLongitude(longitude > 180 ? longitude - 360 : longitude);
int speed = 0;
- if (alternative) {
+ if (atlas) {
speed = buf.readUnsignedByte();
position.setSpeed(UnitsConverter.knotsFromKph(speed));
position.set("batteryReplace", BitUtil.check(buf.readUnsignedByte(), 7));
diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
index 6c0380278..a1a9c3773 100644
--- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
@@ -410,7 +410,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
}
}
- private String decodeAlarm(short value) {
+ private String decodeAlarm(short value, String model) {
+ boolean modelLW = model != null && model.toUpperCase().startsWith("LW");
switch (value) {
case 0x01:
return Position.ALARM_SOS;
@@ -427,7 +428,6 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
return Position.ALARM_OVERSPEED;
case 0x0E:
case 0x0F:
- case 0x19:
return Position.ALARM_LOW_BATTERY;
case 0x11:
return Position.ALARM_POWER_OFF;
@@ -438,17 +438,21 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
case 0x14:
return Position.ALARM_DOOR;
case 0x18:
- return Position.ALARM_REMOVING;
- case 0x23:
- return Position.ALARM_FALL_DOWN;
+ return modelLW ? Position.ALARM_ACCIDENT : Position.ALARM_REMOVING;
+ case 0x19:
+ return modelLW ? Position.ALARM_ACCELERATION : Position.ALARM_LOW_BATTERY;
+ case 0x1A:
case 0x28:
return Position.ALARM_BRAKING;
- case 0x29:
- return Position.ALARM_ACCELERATION;
+ case 0x1B:
case 0x2A:
case 0x2B:
case 0x2E:
return Position.ALARM_CORNERING;
+ case 0x23:
+ return Position.ALARM_FALL_DOWN;
+ case 0x29:
+ return Position.ALARM_ACCELERATION;
case 0x2C:
return Position.ALARM_ACCIDENT;
case 0x30:
@@ -831,7 +835,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
int satellites = BitUtil.between(signal, 10, 15) + BitUtil.between(signal, 5, 10);
position.set(Position.KEY_SATELLITES, satellites);
position.set(Position.KEY_RSSI, BitUtil.to(signal, 5));
- position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte()));
+ position.set(Position.KEY_ALARM, decodeAlarm(
+ buf.readUnsignedByte(), getDeviceModel(deviceSession)));
buf.readUnsignedByte(); // language
position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte());
buf.readUnsignedByte(); // working mode
@@ -842,7 +847,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_RSSI, buf.readUnsignedByte());
short alarmExtension = buf.readUnsignedByte();
if (variant != Variant.VXT01) {
- position.set(Position.KEY_ALARM, decodeAlarm(alarmExtension));
+ position.set(Position.KEY_ALARM, decodeAlarm(alarmExtension, getDeviceModel(deviceSession)));
}
}
}
@@ -881,7 +886,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
decodeStatus(position, buf);
position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01);
position.set(Position.KEY_RSSI, buf.readUnsignedByte());
- position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte()));
+ position.set(Position.KEY_ALARM, decodeAlarm(
+ buf.readUnsignedByte(), getDeviceModel(deviceSession)));
position.set("oil", buf.readUnsignedShort());
int temperature = buf.readUnsignedByte();
if (BitUtil.check(temperature, 7)) {
diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
index 648c5fb42..d010a8fe0 100644
--- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
@@ -492,6 +492,13 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_BATTERY, Integer.parseInt(lockStatus.substring(2, 5)) * 0.01);
}
break;
+ case 0x51:
+ if (length == 16) {
+ for (int i = 1; i <= 8; i++) {
+ position.set(Position.PREFIX_TEMP + i, buf.readShort());
+ }
+ }
+ break;
case 0x56:
position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte() * 10);
buf.readUnsignedByte(); // reserved
@@ -668,6 +675,10 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
position.set("fuel2", Double.parseDouble(
buf.readCharSequence(6, StandardCharsets.US_ASCII).toString()));
break;
+ case 0x00B2:
+ position.set(Position.KEY_ICCID, ByteBufUtil.hexDump(
+ buf.readSlice(10)).replaceAll("f", ""));
+ break;
case 0x00CE:
position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01);
break;
diff --git a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java
index e88b34478..2b234ab21 100644
--- a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java
@@ -171,7 +171,7 @@ public class KhdProtocolDecoder extends BaseProtocolDecoder {
}
long status = buf.readUnsignedInt();
- position.set(Position.KEY_IGNITION, BitUtil.check(status, 7 + 3 * 8));
+ position.set(Position.KEY_IGNITION, !BitUtil.check(status, 7 + 3 * 8));
position.set(Position.KEY_STATUS, status);
buf.readUnsignedShort();
diff --git a/src/main/java/org/traccar/protocol/SnapperFrameDecoder.java b/src/main/java/org/traccar/protocol/SnapperFrameDecoder.java
new file mode 100644
index 000000000..be45346a6
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/SnapperFrameDecoder.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 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 SnapperFrameDecoder extends BaseFrameDecoder {
+
+ @Override
+ protected Object decode(
+ ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception {
+
+ byte header = buf.getByte(buf.readerIndex());
+ if (header == 'P') {
+ if (buf.readableBytes() >= 2) {
+ return buf.readRetainedSlice(2);
+ }
+ } else if (buf.readableBytes() >= 16) {
+ int length = buf.getIntLE(buf.readerIndex() + 12) + 12 + 4 + 9;
+ if (buf.readableBytes() >= length) {
+ return buf.readRetainedSlice(length);
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/SnapperProtocol.java b/src/main/java/org/traccar/protocol/SnapperProtocol.java
new file mode 100644
index 000000000..25a11ed3a
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/SnapperProtocol.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 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 jakarta.inject.Inject;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+import org.traccar.config.Config;
+
+public class SnapperProtocol extends BaseProtocol {
+
+ @Inject
+ public SnapperProtocol(Config config) {
+ addServer(new TrackerServer(config, getName(), false) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new SnapperFrameDecoder());
+ pipeline.addLast(new SnapperProtocolDecoder(SnapperProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/SnapperProtocolDecoder.java b/src/main/java/org/traccar/protocol/SnapperProtocolDecoder.java
new file mode 100644
index 000000000..ef1a4426a
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/SnapperProtocolDecoder.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2024 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.buffer.Unpooled;
+import io.netty.channel.Channel;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.NetworkMessage;
+import org.traccar.Protocol;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.Checksum;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.io.StringReader;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.TimeZone;
+
+public class SnapperProtocolDecoder extends BaseProtocolDecoder {
+
+ public SnapperProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ public static final int MSG_HELLO = 0x01;
+ public static final int MSG_SENDING_START = 0x02;
+ public static final int MSG_SENDING_FINISH = 0x03;
+ public static final int MSG_SEND_EVENTS = 0x21;
+ public static final int MSG_SEND_TECH_INFO = 0x23;
+ public static final int MSG_UPDATE_CMS_NUM = 0x24;
+ public static final int MSG_SEND_SYSTEM_INFO = 0x26;
+ public static final int MSG_SEND_USER_PHONE_NUMBERS = 0x31;
+ public static final int MSG_SEND_GPS_DATA = 0x32;
+ public static final int MSG_SEND_LBS_DATA = 0x33;
+ public static final int MSG_SEND_SYSTEM_STATUS = 0x34;
+ public static final int MSG_SEND_TRANSIT_SETTINGS = 0x35;
+ public static final int MSG_GET_SETTINGS = 0x36;
+ public static final int MSG_SEND_CONCATENATED_PACKET = 0x37;
+ public static final int MSG_SEND_DEBUG_INFO = 0x38;
+
+ private void sendResponse(
+ Channel channel, SocketAddress remoteAddress, int index, int type, String answer) {
+
+ if (channel != null) {
+ ByteBuf response = Unpooled.buffer();
+ response.writeByte('K');
+ response.writeByte(3); // protocol version
+ response.writeLongLE(0); // reserved
+ response.writeShortLE(0); // encryption
+ response.writeIntLE(answer.length());
+ response.writeIntLE(0); // reserved
+ response.writeShortLE(index);
+ response.writeByte(Checksum.sum(ByteBuffer.wrap(answer.getBytes(StandardCharsets.US_ASCII))));
+ response.writeShortLE(type);
+ response.writeCharSequence(answer, StandardCharsets.US_ASCII);
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
+ }
+ }
+
+ private void decodeEvents(Position position, ByteBuf buf) {
+
+ position.set(Position.KEY_EVENT, buf.readUnsignedByte());
+ buf.readUnsignedByte(); // info 1
+ buf.readUnsignedByte(); // info 2
+ buf.readUnsignedIntLE(); // timestamp
+ buf.readUnsignedByte(); // timezone
+ }
+
+ private void decodeTechInfo(Position position, ByteBuf buf) {
+
+ for (int i = 0; i < 5; i++) {
+ buf.readUnsignedByte(); // index
+ int type = buf.readUnsignedByte();
+ switch (type) {
+ case 0x00:
+ position.set(Position.KEY_POWER, buf.readUnsignedByte() * 0.1);
+ position.set(Position.KEY_DEVICE_TEMP, buf.readByte());
+ position.set(Position.KEY_RSSI, buf.readUnsignedByte());
+ break;
+ case 0x01:
+ position.set("interiorTemp", buf.readByte());
+ position.set("engineTemp", buf.readByte());
+ buf.readUnsignedByte(); // reserved
+ break;
+ default:
+ buf.skipBytes(3);
+ break;
+ }
+ }
+ }
+
+ private void decodeGpsData(Position position, ByteBuf buf) throws ParseException {
+
+ String content = buf.readCharSequence(buf.readableBytes(), StandardCharsets.US_ASCII).toString();
+ JsonObject json = Json.createReader(new StringReader(content)).readObject();
+
+ int flags = Integer.parseInt(json.getString("f"), 16);
+ if (!BitUtil.check(flags, 3)) {
+ return;
+ }
+
+ position.setValid(BitUtil.check(flags, 1));
+ if (!BitUtil.check(flags, 6)) {
+ position.setLatitude(-position.getLatitude());
+ }
+ if (!BitUtil.check(flags, 7)) {
+ position.setLongitude(-position.getLongitude());
+ }
+
+ DateFormat dateFormat = new SimpleDateFormat("ddMMyyHHmmss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ position.setTime(dateFormat.parse(json.getString("d") + json.getString("t").split("\\.")[0]));
+
+ String lat = json.getString("la");
+ position.setLatitude(Integer.parseInt(lat.substring(0, 2)) + Double.parseDouble(lat.substring(2)) / 60);
+ String lon = json.getString("lo");
+ position.setLongitude(Integer.parseInt(lon.substring(0, 3)) + Double.parseDouble(lon.substring(3)) / 60);
+
+ position.setAltitude(Double.parseDouble(json.getString("a")));
+ position.setSpeed(Double.parseDouble(json.getString("s")));
+ position.setCourse(Double.parseDouble(json.getString("c")));
+
+ position.set(Position.KEY_SATELLITES, Integer.parseInt(json.getString("sv")));
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ ByteBuf buf = (ByteBuf) msg;
+
+ byte header = buf.readByte();
+ if (header == 'P') {
+ if (channel != null) {
+ ByteBuf response = Unpooled.wrappedBuffer(new byte[] {0x50, 0x4f});
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
+ }
+ return null;
+ }
+
+ buf.readUnsignedByte(); // protocol version
+ buf.readUnsignedIntLE(); // system bonus identifier
+
+ String serialNumber = String.valueOf(buf.readUnsignedIntLE());
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, serialNumber);
+ if (deviceSession == null) {
+ return null;
+ }
+
+ buf.readUnsignedShortLE(); // encryption
+ int length = buf.readIntLE();
+ buf.readUnsignedByte(); // flags
+ buf.readUnsignedMediumLE(); // reserved
+ int index = buf.readUnsignedShortLE();
+ buf.readUnsignedByte(); // checksum
+ int type = buf.readUnsignedShortLE();
+
+ if (type == MSG_HELLO) {
+ sendResponse(channel, remoteAddress, index, type, "hello");
+ } else {
+ sendResponse(channel, remoteAddress, index, type, "OK");
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ switch (type) {
+ case MSG_SEND_EVENTS:
+ decodeEvents(position, buf);
+ getLastLocation(position, null); // TODO read timestamp
+ return position;
+ case MSG_SEND_TECH_INFO:
+ decodeTechInfo(position, buf);
+ getLastLocation(position, null);
+ return position;
+ case MSG_SEND_GPS_DATA:
+ decodeGpsData(position, buf.readSlice(length));
+ return position;
+ case MSG_SEND_CONCATENATED_PACKET:
+ int count = buf.readUnsignedShortLE();
+ for (int i = 0; i < count; i++) {
+ int partType = buf.readUnsignedShortLE();
+ int partLength = buf.readUnsignedShortLE();
+ switch (partType) {
+ case MSG_SEND_EVENTS:
+ decodeEvents(position, buf);
+ break;
+ case MSG_SEND_TECH_INFO:
+ decodeTechInfo(position, buf);
+ break;
+ case MSG_SEND_GPS_DATA:
+ decodeGpsData(position, buf.readSlice(partLength));
+ break;
+ default:
+ buf.skipBytes(partLength);
+ break;
+ }
+ }
+ if (position.getFixTime() == null) {
+ getLastLocation(position, null);
+ }
+ return position;
+ default:
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java
index 53c4a5d02..c9d6f16ef 100644
--- a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java
@@ -295,6 +295,60 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private int decodeSerialData(Position position, String[] values, int index) {
+
+ int remaining = Integer.parseInt(values[index++]);
+ double totalFuel = 0;
+ while (remaining > 0) {
+ String attribute = values[index++];
+ if (attribute.startsWith("CabAVL")) {
+ String[] data = attribute.split(",");
+ double fuel1 = Double.parseDouble(data[2]);
+ if (fuel1 > 0) {
+ totalFuel += fuel1;
+ position.set("fuel1", fuel1);
+ }
+ double fuel2 = Double.parseDouble(data[3]);
+ if (fuel2 > 0) {
+ totalFuel += fuel2;
+ position.set("fuel2", fuel2);
+ }
+ } else if (attribute.startsWith("GTSL")) {
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, attribute.split("\\|")[4]);
+ } else if (attribute.contains("=")) {
+ String[] pair = attribute.split("=");
+ if (pair.length >= 2) {
+ String value = pair[1].trim();
+ if (value.contains(".")) {
+ value = value.substring(0, value.indexOf('.'));
+ }
+ switch (pair[0].charAt(0)) {
+ case 't':
+ position.set(Position.PREFIX_TEMP + pair[0].charAt(2), Integer.parseInt(value, 16));
+ break;
+ case 'N':
+ int fuel = Integer.parseInt(value, 16);
+ totalFuel += fuel;
+ position.set("fuel" + pair[0].charAt(2), fuel);
+ break;
+ case 'Q':
+ position.set("drivingQuality", Integer.parseInt(value, 16));
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ position.set("serial", attribute.trim());
+ }
+ remaining -= attribute.length() + 1;
+ }
+ if (totalFuel > 0) {
+ position.set(Position.KEY_FUEL_LEVEL, totalFuel);
+ }
+ return index + 1; // checksum
+ }
+
private Position decode2356(
Channel channel, SocketAddress remoteAddress, String protocol, String[] values) throws ParseException {
int index = 0;
@@ -371,56 +425,7 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_ALARM, decodeAlert(Integer.parseInt(values[index++])));
break;
case "UEX":
- int remaining = Integer.parseInt(values[index++]);
- double totalFuel = 0;
- while (remaining > 0) {
- String attribute = values[index++];
- if (attribute.startsWith("CabAVL")) {
- String[] data = attribute.split(",");
- double fuel1 = Double.parseDouble(data[2]);
- if (fuel1 > 0) {
- totalFuel += fuel1;
- position.set("fuel1", fuel1);
- }
- double fuel2 = Double.parseDouble(data[3]);
- if (fuel2 > 0) {
- totalFuel += fuel2;
- position.set("fuel2", fuel2);
- }
- } else if (attribute.startsWith("GTSL")) {
- position.set(Position.KEY_DRIVER_UNIQUE_ID, attribute.split("\\|")[4]);
- } else if (attribute.contains("=")) {
- String[] pair = attribute.split("=");
- if (pair.length >= 2) {
- String value = pair[1].trim();
- if (value.contains(".")) {
- value = value.substring(0, value.indexOf('.'));
- }
- switch (pair[0].charAt(0)) {
- case 't':
- position.set(Position.PREFIX_TEMP + pair[0].charAt(2), Integer.parseInt(value, 16));
- break;
- case 'N':
- int fuel = Integer.parseInt(value, 16);
- totalFuel += fuel;
- position.set("fuel" + pair[0].charAt(2), fuel);
- break;
- case 'Q':
- position.set("drivingQuality", Integer.parseInt(value, 16));
- break;
- default:
- break;
- }
- }
- } else {
- position.set("serial", attribute.trim());
- }
- remaining -= attribute.length() + 1;
- }
- if (totalFuel > 0) {
- position.set(Position.KEY_FUEL_LEVEL, totalFuel);
- }
- index += 1; // checksum
+ index = decodeSerialData(position, values, index);
break;
default:
break;
@@ -482,7 +487,8 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
String type = values[index++];
- if (!type.equals("STT") && !type.equals("ALT") && !type.equals("BLE") && !type.equals("RES")) {
+ if (!type.equals("STT") && !type.equals("ALT") && !type.equals("BLE") && !type.equals("RES")
+ && !type.equals("UEX")) {
return null;
}
@@ -601,34 +607,40 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_OUTPUT, Integer.parseInt(values[index++]));
}
- if (type.equals("ALT")) {
- if (BitUtil.check(mask, 19)) {
- int alertId = Integer.parseInt(values[index++]);
- position.set(Position.KEY_ALARM, decodeAlert(alertId));
- }
- if (BitUtil.check(mask, 20)) {
- position.set("alertModifier", values[index++]);
- }
- if (BitUtil.check(mask, 21)) {
- position.set("alertData", values[index++]);
- }
- } else {
- if (BitUtil.check(mask, 19)) {
- position.set("mode", Integer.parseInt(values[index++]));
- }
- if (BitUtil.check(mask, 20)) {
- position.set("reason", Integer.parseInt(values[index++]));
- }
- if (BitUtil.check(mask, 21)) {
- position.set(Position.KEY_INDEX, Integer.parseInt(values[index++]));
- }
+ switch (type) {
+ case "ALT":
+ if (BitUtil.check(mask, 19)) {
+ int alertId = Integer.parseInt(values[index++]);
+ position.set(Position.KEY_ALARM, decodeAlert(alertId));
+ }
+ if (BitUtil.check(mask, 20)) {
+ position.set("alertModifier", values[index++]);
+ }
+ if (BitUtil.check(mask, 21)) {
+ position.set("alertData", values[index++]);
+ }
+ break;
+ case "UEX":
+ index = decodeSerialData(position, values, index);
+ break;
+ default:
+ if (BitUtil.check(mask, 19)) {
+ position.set("mode", Integer.parseInt(values[index++]));
+ }
+ if (BitUtil.check(mask, 20)) {
+ position.set("reason", Integer.parseInt(values[index++]));
+ }
+ if (BitUtil.check(mask, 21)) {
+ position.set(Position.KEY_INDEX, Integer.parseInt(values[index++]));
+ }
+ break;
}
if (BitUtil.check(mask, 22)) {
index += 1; // reserved
}
- if (BitUtil.check(mask, 23)) {
+ if (BitUtil.check(mask, 23) && !type.equals("UEX")) {
int assignMask = Integer.parseInt(values[index++], 16);
for (int i = 0; i <= 30; i++) {
if (BitUtil.check(assignMask, i)) {
diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java
index 6197c6c13..de42031d7 100644
--- a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java
@@ -199,7 +199,8 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
var fmbXXX = Set.of(
"FMB001", "FMB010", "FMB002", "FMB020", "FMB003", "FMB110", "FMB120", "FMB122", "FMB125", "FMB130",
"FMB140", "FMU125", "FMB900", "FMB920", "FMB962", "FMB964", "FM3001", "FMB202", "FMB204", "FMB206",
- "FMT100", "MTB100", "FMP100", "MSP500");
+ "FMT100", "MTB100", "FMP100", "MSP500", "FMC125", "FMM125", "FMU130", "FMC130", "FMM130", "FMB150",
+ "FMC150", "FMM150");
register(1, null, (p, b) -> p.set(Position.PREFIX_IN + 1, b.readUnsignedByte() > 0));
register(2, null, (p, b) -> p.set(Position.PREFIX_IN + 2, b.readUnsignedByte() > 0));
@@ -231,7 +232,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
register(75, fmbXXX, (p, b) -> p.set(Position.PREFIX_TEMP + 4, b.readInt() * 0.1));
register(78, null, (p, b) -> {
long driverUniqueId = b.readLongLE();
- if (driverUniqueId > 0) {
+ if (driverUniqueId != 0) {
p.set(Position.KEY_DRIVER_UNIQUE_ID, String.format("%016X", driverUniqueId));
}
});
@@ -243,9 +244,9 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
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()));
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(115, fmbXXX, (p, b) -> p.set("engineTemp", b.readShort() * 0.1));
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));
@@ -292,6 +293,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
}
});
register(636, fmbXXX, (p, b) -> p.set("cid4g", b.readUnsignedInt()));
+ register(662, fmbXXX, (p, b) -> p.set(Position.KEY_DOOR, b.readUnsignedByte() > 0));
}
private void decodeGh3000Parameter(Position position, int id, ByteBuf buf, int length) {
diff --git a/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java b/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java
index 02744f8ab..1187250f8 100644
--- a/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2024 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,6 +64,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
.number("(d+),") // mnc
.number("(d+),") // lac
.number("(d+)") // cell
+ .number(",(dd)").optional() // alarm
.groupBegin()
.text(",")
.expression("(")
@@ -77,7 +78,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
.any()
.compile();
- private static final Pattern PATTERN_HEARTBEAT = new PatternBuilder()
+ private static final Pattern PATTERN_CP01 = new PatternBuilder()
.expression("[A-Z]{2,3}")
.text("CP01,")
.number("(ddd)") // gsm
@@ -99,7 +100,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
.any()
.compile();
- private static final Pattern PATTERN_LBS = new PatternBuilder()
+ private static final Pattern PATTERN_AP02 = new PatternBuilder()
.expression("[A-Z]{2,3}")
.text("AP02,")
.expression("[^,]+,") // language
@@ -118,6 +119,19 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
.expression("(.*)") // wifi
.compile();
+ private static final Pattern PATTERN_AP03 = new PatternBuilder()
+ .expression("[A-Z]{2,3}")
+ .text("AP03,")
+ .number("(ddd)") // rssi
+ .number("(ddd)") // satellites
+ .number("(ddd)") // battery level
+ .number("d") // space
+ .number("xx") // fortification state
+ .number("dd,") // working mode
+ .number("(d+),") // steps
+ .number("d+") // rolls frequency
+ .compile();
+
private Boolean decodeOptionalValue(Parser parser, int activeValue) {
int value = parser.nextInt();
if (value != 0) {
@@ -183,7 +197,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
if (type.equals("CP01")) {
- Parser parser = new Parser(PATTERN_HEARTBEAT, sentence);
+ Parser parser = new Parser(PATTERN_CP01, sentence);
if (!parser.matches()) {
return null;
}
@@ -234,6 +248,20 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextInt()));
if (parser.hasNext()) {
+ switch (parser.nextInt()) {
+ case 1:
+ position.set(Position.KEY_ALARM, Position.ALARM_SOS);
+ break;
+ case 5:
+ case 6:
+ position.set(Position.KEY_ALARM, Position.ALARM_FALL_DOWN);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (parser.hasNext()) {
decodeWifi(network, parser.next());
}
@@ -243,7 +271,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
} else if (type.equals("AP02")) {
- Parser parser = new Parser(PATTERN_LBS, sentence);
+ Parser parser = new Parser(PATTERN_AP02, sentence);
if (!parser.matches()) {
return null;
}
@@ -275,6 +303,61 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
return position;
+ } else if (type.equals("AP03")) {
+
+ Parser parser = new Parser(PATTERN_AP03, sentence);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ getLastLocation(position, null);
+
+ position.set(Position.KEY_RSSI, parser.nextInt());
+ position.set(Position.KEY_SATELLITES, parser.nextInt());
+ position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt());
+ position.set(Position.KEY_STEPS, parser.nextInt());
+
+ return position;
+
+ } else if (type.equals("AP49") || type.equals("APHT") || type.equals("APHP") || type.equals("AP50")) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ getLastLocation(position, null);
+
+ String[] values = sentence.split(",");
+
+ switch (type) {
+ case "AP49":
+ position.set(Position.KEY_HEART_RATE, Integer.parseInt(values[1]));
+ break;
+ case "APHT":
+ position.set(Position.KEY_HEART_RATE, Integer.parseInt(values[1]));
+ position.set("pressureSystolic", Integer.parseInt(values[2]));
+ position.set("pressureDiastolic", Integer.parseInt(values[3]));
+ break;
+ case "APHP":
+ position.set(Position.KEY_HEART_RATE, Integer.parseInt(values[1]));
+ position.set("pressureSystolic", Integer.parseInt(values[2]));
+ position.set("pressureDiastolic", Integer.parseInt(values[3]));
+ position.set("spo2", Integer.parseInt(values[4]));
+ position.set("bloodSugar", Double.parseDouble(values[5]));
+ position.set("temperature", Double.parseDouble(values[6]));
+ break;
+ case "AP50":
+ position.set("temperature", Double.parseDouble(values[1]));
+ position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(values[2]));
+ break;
+ default:
+ break;
+ }
+
+ return position;
+
}
return null;
diff --git a/src/main/java/org/traccar/reports/SummaryReportProvider.java b/src/main/java/org/traccar/reports/SummaryReportProvider.java
index ffde0b067..5bd7e51b3 100644
--- a/src/main/java/org/traccar/reports/SummaryReportProvider.java
+++ b/src/main/java/org/traccar/reports/SummaryReportProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -105,16 +105,11 @@ public class SummaryReportProvider {
result.setDistance(PositionUtil.calculateDistance(first, last, !ignoreOdometer));
result.setSpentFuel(reportUtils.calculateFuel(first, last));
- long durationMilliseconds;
if (first.hasAttribute(Position.KEY_HOURS) && last.hasAttribute(Position.KEY_HOURS)) {
- durationMilliseconds = last.getLong(Position.KEY_HOURS) - first.getLong(Position.KEY_HOURS);
- result.setEngineHours(durationMilliseconds);
- } else {
- durationMilliseconds = last.getFixTime().getTime() - first.getFixTime().getTime();
- }
-
- if (durationMilliseconds > 0) {
- result.setAverageSpeed(UnitsConverter.knotsFromMps(result.getDistance() * 1000 / durationMilliseconds));
+ result.setStartHours(first.getLong(Position.KEY_HOURS));
+ result.setEndHours(last.getLong(Position.KEY_HOURS));
+ result.setAverageSpeed(UnitsConverter.knotsFromMps(
+ result.getDistance() * 1000 / result.getEngineHours()));
}
if (!ignoreOdometer
@@ -142,15 +137,13 @@ public class SummaryReportProvider {
if (daily) {
while (from.truncatedTo(ChronoUnit.DAYS).isBefore(to.truncatedTo(ChronoUnit.DAYS))) {
ZonedDateTime fromDay = from.truncatedTo(ChronoUnit.DAYS);
- ZonedDateTime nextDay = fromDay.plus(1, ChronoUnit.DAYS);
+ ZonedDateTime nextDay = fromDay.plusDays(1);
results.addAll(calculateDeviceResult(
device, Date.from(from.toInstant()), Date.from(nextDay.toInstant()), fast));
from = nextDay;
}
- results.addAll(calculateDeviceResult(device, Date.from(from.toInstant()), Date.from(to.toInstant()), fast));
- } else {
- results.addAll(calculateDeviceResult(device, Date.from(from.toInstant()), Date.from(to.toInstant()), fast));
}
+ results.addAll(calculateDeviceResult(device, Date.from(from.toInstant()), Date.from(to.toInstant()), fast));
return results;
}
diff --git a/src/main/java/org/traccar/reports/model/SummaryReportItem.java b/src/main/java/org/traccar/reports/model/SummaryReportItem.java
index 44a15cf1d..b88cabe49 100644
--- a/src/main/java/org/traccar/reports/model/SummaryReportItem.java
+++ b/src/main/java/org/traccar/reports/model/SummaryReportItem.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,13 +18,28 @@ package org.traccar.reports.model;
public class SummaryReportItem extends BaseReportItem {
- private long engineHours; // milliseconds
-
public long getEngineHours() {
- return engineHours;
+ return endHours - startHours;
+ }
+
+ private long startHours; // milliseconds
+
+ public long getStartHours() {
+ return startHours;
+ }
+
+ public void setStartHours(long startHours) {
+ this.startHours = startHours;
}
- public void setEngineHours(long engineHours) {
- this.engineHours = engineHours;
+ private long endHours; // milliseconds
+
+ public long getEndHours() {
+ return endHours;
}
+
+ public void setEndHours(long endHours) {
+ this.endHours = endHours;
+ }
+
}
diff --git a/src/main/java/org/traccar/schedule/TaskExpirations.java b/src/main/java/org/traccar/schedule/TaskExpirations.java
index 94f855c5f..e16dcd86c 100644
--- a/src/main/java/org/traccar/schedule/TaskExpirations.java
+++ b/src/main/java/org/traccar/schedule/TaskExpirations.java
@@ -111,7 +111,7 @@ public class TaskExpirations implements ScheduleTask {
}
if (config.getBoolean(Keys.NOTIFICATION_EXPIRATION_DEVICE)) {
- long reminder = config.getLong(Keys.NOTIFICATION_EXPIRATION_USER_REMINDER);
+ long reminder = config.getLong(Keys.NOTIFICATION_EXPIRATION_DEVICE_REMINDER);
var devices = storage.getObjects(Device.class, new Request(new Columns.All()));
for (Device device : devices) {
if (checkTimeTrigger(device, currentTime, 0)) {
diff --git a/src/main/java/org/traccar/schedule/TaskReports.java b/src/main/java/org/traccar/schedule/TaskReports.java
index e0fa6f8d6..070fa9d2b 100644
--- a/src/main/java/org/traccar/schedule/TaskReports.java
+++ b/src/main/java/org/traccar/schedule/TaskReports.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2023 - 2024 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.
@@ -21,6 +21,7 @@ import com.google.inject.servlet.ServletScopes;
import net.fortuna.ical4j.model.Period;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.traccar.helper.LogAction;
import org.traccar.model.BaseModel;
import org.traccar.model.Calendar;
import org.traccar.model.Device;
@@ -42,7 +43,9 @@ import org.traccar.storage.query.Request;
import jakarta.inject.Inject;
import java.util.Collections;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -80,8 +83,9 @@ public class TaskReports implements ScheduleTask {
var lastEvents = calendar.findPeriods(lastCheck);
var currentEvents = calendar.findPeriods(currentCheck);
- if (!lastEvents.isEmpty() && currentEvents.isEmpty()) {
- Period period = lastEvents.iterator().next();
+ Set<Period> finishedEvents = new HashSet<>(lastEvents);
+ finishedEvents.removeAll(currentEvents);
+ for (Period period : finishedEvents) {
RequestScoper scope = ServletScopes.scopeRequest(Collections.emptyMap());
try (RequestScoper.CloseableScope ignored = scope.open()) {
executeReport(report, period.getStart(), period.getEnd());
@@ -110,6 +114,7 @@ public class TaskReports implements ScheduleTask {
ReportMailer reportMailer = injector.getInstance(ReportMailer.class);
for (User user : users) {
+ LogAction.report(user.getId(), true, report.getType(), from, to, deviceIds, groupIds);
switch (report.getType()) {
case "events":
var eventsReportProvider = injector.getInstance(EventsReportProvider.class);
diff --git a/src/main/java/org/traccar/storage/DatabaseModule.java b/src/main/java/org/traccar/storage/DatabaseModule.java
index 9d9e5bd5e..cd66d7298 100644
--- a/src/main/java/org/traccar/storage/DatabaseModule.java
+++ b/src/main/java/org/traccar/storage/DatabaseModule.java
@@ -24,6 +24,7 @@ import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.exception.LiquibaseException;
+import liquibase.exception.LockException;
import liquibase.resource.DirectoryResourceAccessor;
import liquibase.resource.ResourceAccessor;
import org.traccar.config.Config;
@@ -78,22 +79,27 @@ public class DatabaseModule extends AbstractModule {
DataSource dataSource = new HikariDataSource(hikariConfig);
- if (config.hasKey(Keys.DATABASE_CHANGELOG)) {
+ String changelog = config.getString(Keys.DATABASE_CHANGELOG);
+ if (changelog != null && !changelog.isEmpty()) {
ResourceAccessor resourceAccessor = new DirectoryResourceAccessor(new File("."));
- Database database = DatabaseFactory.getInstance().openDatabase(
- config.getString(Keys.DATABASE_URL),
- config.getString(Keys.DATABASE_USER),
- config.getString(Keys.DATABASE_PASSWORD),
- config.getString(Keys.DATABASE_DRIVER),
- null, null, null, resourceAccessor);
+ System.setProperty("liquibase.changelogLockWaitTimeInMinutes", "1");
- String changelog = config.getString(Keys.DATABASE_CHANGELOG);
-
- try (Liquibase liquibase = new Liquibase(changelog, resourceAccessor, database)) {
- liquibase.clearCheckSums();
- liquibase.update(new Contexts());
+ try {
+ Database database = DatabaseFactory.getInstance().openDatabase(
+ config.getString(Keys.DATABASE_URL),
+ config.getString(Keys.DATABASE_USER),
+ config.getString(Keys.DATABASE_PASSWORD),
+ config.getString(Keys.DATABASE_DRIVER),
+ null, null, null, resourceAccessor);
+
+ try (Liquibase liquibase = new Liquibase(changelog, resourceAccessor, database)) {
+ liquibase.clearCheckSums();
+ liquibase.update(new Contexts());
+ }
+ } catch (LockException e) {
+ throw new DatabaseLockException();
}
}
@@ -101,3 +107,12 @@ public class DatabaseModule extends AbstractModule {
}
}
+
+class DatabaseLockException extends RuntimeException {
+ DatabaseLockException() {
+ super("Database is in a locked state. "
+ + "It could be due to early service termination on a previous launch. "
+ + "To unlock you can run this query: 'UPDATE DATABASECHANGELOGLOCK SET locked = 0'. "
+ + "Make sure the schema is up to date before unlocking the database.");
+ }
+}
diff --git a/src/main/java/org/traccar/web/ModernDefaultServlet.java b/src/main/java/org/traccar/web/DefaultOverrideServlet.java
index a7c8cdb29..14b441f86 100644
--- a/src/main/java/org/traccar/web/ModernDefaultServlet.java
+++ b/src/main/java/org/traccar/web/DefaultOverrideServlet.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2023 - 2024 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.
@@ -24,12 +24,12 @@ import jakarta.inject.Inject;
import java.io.File;
import java.io.IOException;
-public class ModernDefaultServlet extends DefaultServlet {
+public class DefaultOverrideServlet extends DefaultServlet {
private Resource overrideResource;
@Inject
- public ModernDefaultServlet(Config config) {
+ public DefaultOverrideServlet(Config config) {
String override = config.getString(Keys.WEB_OVERRIDE);
if (override != null) {
overrideResource = Resource.newResource(new File(override));
diff --git a/src/main/java/org/traccar/web/WebServer.java b/src/main/java/org/traccar/web/WebServer.java
index 4759942b1..68fcf52de 100644
--- a/src/main/java/org/traccar/web/WebServer.java
+++ b/src/main/java/org/traccar/web/WebServer.java
@@ -139,7 +139,7 @@ public class WebServer implements LifecycleObject {
}
private void initWebApp(ServletContextHandler servletHandler) {
- ServletHolder servletHolder = new ServletHolder(new ModernDefaultServlet(config));
+ ServletHolder servletHolder = new ServletHolder(new DefaultOverrideServlet(config));
servletHolder.setInitParameter("resourceBase", new File(config.getString(Keys.WEB_PATH)).getAbsolutePath());
servletHolder.setInitParameter("dirAllowed", "false");
if (config.getBoolean(Keys.WEB_DEBUG)) {