aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/gradle.yml2
-rw-r--r--build.gradle50
-rw-r--r--debug.xml6
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin59821 -> 43453 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties2
-rwxr-xr-xgradlew41
-rw-r--r--gradlew.bat35
-rw-r--r--schema/changelog-6.2.xml22
-rw-r--r--schema/changelog-master.xml2
-rw-r--r--setup/cloud-init.yaml4
-rw-r--r--setup/default.xml302
-rwxr-xr-xsetup/package.sh1
-rw-r--r--setup/traccar.iss2
-rw-r--r--setup/traccar.xml16
-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
-rw-r--r--src/test/java/org/traccar/calendar/CalendarTest.java38
-rw-r--r--src/test/java/org/traccar/protocol/EasyTrackProtocolDecoderTest.java3
-rw-r--r--src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java4
-rw-r--r--src/test/java/org/traccar/protocol/GlobalstarProtocolDecoderTest.java4
-rw-r--r--src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java6
-rw-r--r--src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java8
-rw-r--r--src/test/java/org/traccar/protocol/SnapperFrameDecoderTest.java23
-rw-r--r--src/test/java/org/traccar/protocol/SnapperProtocolDecoderTest.java30
-rw-r--r--src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java4
-rw-r--r--src/test/java/org/traccar/protocol/TrvProtocolDecoderTest.java27
-rw-r--r--src/test/java/org/traccar/protocol/WatchProtocolDecoderTest.java2
-rw-r--r--swagger.json2
-rw-r--r--templates/export/stops.xlsxbin15643 -> 13212 bytes
-rw-r--r--templates/export/summary.xlsxbin16889 -> 13233 bytes
-rw-r--r--templates/export/trips.xlsxbin15944 -> 13511 bytes
-rwxr-xr-xtools/config-doc.py54
-rwxr-xr-xtools/test-integration.py11
m---------traccar-web0
80 files changed, 1410 insertions, 809 deletions
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index e50354cbd..8bc638a65 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -18,4 +18,4 @@ jobs:
distribution: temurin
java-version: 11
cache: gradle
- - run: ./gradlew build --no-daemon --warning-mode=fail
+ - run: ./gradlew build --no-daemon
diff --git a/build.gradle b/build.gradle
index 2fe2bfaab..029c4ae6d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,13 +9,17 @@ repositories {
mavenCentral()
}
-sourceCompatibility = "11"
+java {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+}
+
compileJava.options.encoding = "UTF-8"
jar.destinationDirectory = file("$projectDir/target")
checkstyle {
- toolVersion = "10.15.0"
- configFile = "gradle/checkstyle.xml" as File
+ toolVersion = "10.17.0"
+ configFile = file("gradle/checkstyle.xml")
checkstyleTest.enabled = false
}
@@ -27,10 +31,10 @@ enforce {
ext {
guiceVersion = "7.0.0"
- jettyVersion = "11.0.20"
- jerseyVersion = "3.1.5"
- jacksonVersion = "2.15.3" // same version as jersey-media-json-jackson dependency
- protobufVersion = "4.26.1"
+ jettyVersion = "11.0.21"
+ jerseyVersion = "3.1.7"
+ jacksonVersion = "2.17.1" // same version as jersey-media-json-jackson dependency
+ protobufVersion = "4.27.0"
jxlsVersion = "2.14.0" // version 3 requires java 17
junitVersion = "5.10.2"
}
@@ -42,18 +46,17 @@ protobuf {
}
dependencies {
- implementation "commons-codec:commons-codec:1.16.1"
+ implementation "commons-codec:commons-codec:1.17.0"
implementation "com.h2database:h2:2.2.224"
- implementation "com.mysql:mysql-connector-j:8.3.0"
- implementation "org.mariadb.jdbc:mariadb-java-client:3.3.3"
+ implementation "com.mysql:mysql-connector-j:8.4.0"
+ implementation "org.mariadb.jdbc:mariadb-java-client:3.4.0"
implementation "org.postgresql:postgresql:42.7.3"
- implementation "com.microsoft.sqlserver:mssql-jdbc:12.6.1.jre11"
+ implementation "com.microsoft.sqlserver:mssql-jdbc:12.6.2.jre11"
implementation "com.zaxxer:HikariCP:5.1.0"
- implementation "io.netty:netty-all:4.1.108.Final"
- implementation "org.slf4j:slf4j-jdk14:2.0.12"
+ implementation "io.netty:netty-all:4.1.110.Final"
+ implementation "org.slf4j:slf4j-jdk14:2.0.13"
implementation "com.google.inject:guice:$guiceVersion"
implementation "com.google.inject.extensions:guice-servlet:$guiceVersion"
- implementation "org.owasp.encoder:encoder:1.2.3"
implementation "org.glassfish:jakarta.json:2.0.1"
implementation "com.sun.mail:jakarta.mail:2.0.1"
implementation "org.eclipse.jetty:jetty-server:$jettyVersion"
@@ -66,7 +69,7 @@ dependencies {
implementation "org.glassfish.jersey.containers:jersey-container-servlet:$jerseyVersion"
implementation "org.glassfish.jersey.media:jersey-media-json-jackson:$jerseyVersion"
implementation "org.glassfish.jersey.inject:jersey-hk2:$jerseyVersion"
- implementation "org.glassfish.hk2:guice-bridge:3.0.5" // same version as jersey-hk2
+ implementation "org.glassfish.hk2:guice-bridge:3.1.0" // same version as jersey-hk2
implementation "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jacksonVersion"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jakarta-jsonp:$jacksonVersion"
implementation "org.liquibase:liquibase-core:4.23.2" // upgrade has issues
@@ -76,23 +79,24 @@ dependencies {
implementation "org.apache.velocity:velocity-engine-core:2.3"
implementation "org.apache.velocity.tools:velocity-tools-generic:3.1"
implementation "org.apache.commons:commons-collections4:4.4"
- implementation "org.mnode.ical4j:ical4j:3.2.17"
+ implementation "org.mnode.ical4j:ical4j:3.2.18"
implementation "org.locationtech.spatial4j:spatial4j:0.8"
implementation "org.locationtech.jts:jts-core:1.19.0"
implementation "net.java.dev.jna:jna-platform:5.14.0"
implementation "com.github.jnr:jnr-posix:3.1.19"
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
- implementation "com.amazonaws:aws-java-sdk-sns:1.12.694"
+ implementation "com.amazonaws:aws-java-sdk-sns:1.12.733"
implementation "org.apache.kafka:kafka-clients:3.7.0"
implementation "com.hivemq:hivemq-mqtt-client:1.3.3"
- implementation "redis.clients:jedis:5.1.2"
- implementation "com.google.firebase:firebase-admin:9.2.0"
- implementation "com.nimbusds:oauth2-oidc-sdk:11.10.1"
- implementation "com.rabbitmq:amqp-client:5.20.0"
+ implementation "redis.clients:jedis:5.1.3"
+ implementation "com.google.firebase:firebase-admin:9.3.0"
+ implementation "com.nimbusds:oauth2-oidc-sdk:11.12"
+ implementation "com.rabbitmq:amqp-client:5.21.0"
implementation "com.warrenstrange:googleauth:1.5.0"
+ implementation "com.google.openlocationcode:openlocationcode:1.0.4"
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- testImplementation "org.mockito:mockito-core:5.11.0"
+ testImplementation "org.mockito:mockito-core:5.12.0"
}
test {
@@ -109,7 +113,7 @@ jar {
manifest {
attributes(
"Main-Class": "org.traccar.Main",
- "Implementation-Version": "6.0",
+ "Implementation-Version": "6.2",
"Class-Path": configurations.runtimeClasspath.files.collect { "lib/$it.name" }.join(" "))
}
}
diff --git a/debug.xml b/debug.xml
index a0c179cff..15fa57324 100644
--- a/debug.xml
+++ b/debug.xml
@@ -1,16 +1,12 @@
<?xml version='1.0' encoding='UTF-8'?>
-
<!DOCTYPE properties SYSTEM 'http://java.sun.com/dtd/properties.dtd'>
-
<properties>
- <entry key='config.default'>./setup/default.xml</entry>
-
<entry key='web.path'>./traccar-web/simple</entry>
<entry key='web.debug'>true</entry>
<entry key='web.console'>true</entry>
- <entry key='geocoder.type'>test</entry>
+ <entry key='geocoder.type'>pluscodes</entry>
<entry key='media.path'>./target/media</entry>
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 41d9927a4..e6441136f 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ae04661ee..0d1842103 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 1b6c78733..b740cf133 100755
--- a/gradlew
+++ b/gradlew
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +80,11 @@ do
esac
done
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-APP_NAME="Gradle"
+# This is normally unused
+# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +131,29 @@ location of your Java installation."
fi
else
JAVACMD=java
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done
fi
-# Collect all arguments for the java command;
-# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-# shell script including quotes and variable substitutions, so put them in
-# double quotes to make sure that they get re-expanded; and
-# * put everything else in single quotes, so that it's not re-expanded.
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -205,6 +214,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
diff --git a/gradlew.bat b/gradlew.bat
index 107acd32c..25da30dbd 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
-@if "%DEBUG%" == "" @echo off
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
+if %ERRORLEVEL% equ 0 goto execute
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/schema/changelog-6.2.xml b/schema/changelog-6.2.xml
new file mode 100644
index 000000000..568d9a3a0
--- /dev/null
+++ b/schema/changelog-6.2.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<databaseChangeLog
+ xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
+ http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"
+ logicalFilePath="changelog-6.2">
+
+ <changeSet author="author" id="changelog-6.2">
+
+ <dropColumn tableName="tc_servers" columnName="twelvehourformat" />
+ <dropColumn tableName="tc_users" columnName="twelvehourformat" />
+
+ <addColumn tableName="tc_attributes">
+ <column name="priority" type="INT" defaultValueNumeric="0">
+ <constraints nullable="false" />
+ </column>
+ </addColumn>
+
+ </changeSet>
+
+</databaseChangeLog>
diff --git a/schema/changelog-master.xml b/schema/changelog-master.xml
index 559d90923..e9ae52569 100644
--- a/schema/changelog-master.xml
+++ b/schema/changelog-master.xml
@@ -43,4 +43,6 @@
<include file="changelog-5.10.xml" relativeToChangelogFile="true" />
<include file="changelog-5.11.xml" relativeToChangelogFile="true" />
+ <include file="changelog-6.2.xml" relativeToChangelogFile="true" />
+
</databaseChangeLog>
diff --git a/setup/cloud-init.yaml b/setup/cloud-init.yaml
index 1246fb1b7..3d55f5f6f 100644
--- a/setup/cloud-init.yaml
+++ b/setup/cloud-init.yaml
@@ -5,14 +5,10 @@ write_files:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE properties SYSTEM 'http://java.sun.com/dtd/properties.dtd'>
<properties>
-
- <entry key="config.default">./conf/default.xml</entry>
-
<entry key='database.driver'>com.mysql.jdbc.Driver</entry>
<entry key='database.url'>jdbc:mysql://localhost/traccar?zeroDateTimeBehavior=round&amp;serverTimezone=UTC&amp;allowPublicKeyRetrieval=true&amp;useSSL=false&amp;allowMultiQueries=true&amp;autoReconnect=true&amp;useUnicode=yes&amp;characterEncoding=UTF-8&amp;sessionVariables=sql_mode=''</entry>
<entry key='database.user'>root</entry>
<entry key='database.password'>root</entry>
-
</properties>
path: /root/traccar.xml
diff --git a/setup/default.xml b/setup/default.xml
deleted file mode 100644
index 092b4f494..000000000
--- a/setup/default.xml
+++ /dev/null
@@ -1,302 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-
-<!DOCTYPE properties SYSTEM 'http://java.sun.com/dtd/properties.dtd'>
-
-<properties>
-
- <!--
-
- DO NOT MODIFY THIS FILE. Use traccar.xml instead.
-
- -->
-
- <entry key='web.port'>8082</entry>
- <entry key='web.path'>./web</entry>
- <entry key='web.sanitize'>false</entry>
- <entry key='web.persistSession'>false</entry>
- <entry key='web.showUnknownDevices'>true</entry>
-
- <entry key='geocoder.enable'>true</entry>
- <entry key='geocoder.type'>locationiq</entry>
- <entry key='geocoder.key'>pk.689d849289c8c63708068b2ff1f63b2d</entry>
- <entry key='geocoder.onRequest'>true</entry>
- <entry key='geocoder.ignorePositions'>true</entry>
-
- <entry key='logger.level'>info</entry>
- <entry key='logger.file'>./logs/tracker-server.log</entry>
- <entry key='logger.rotate'>true</entry>
-
- <entry key='filter.enable'>true</entry>
- <entry key='filter.future'>86400</entry>
-
- <entry key='event.ignoreDuplicateAlerts'>true</entry>
-
- <entry key='media.path'>./media</entry>
-
- <entry key='notificator.types'>web,mail,command</entry>
-
- <entry key='server.statistics'>https://www.traccar.org/analytics/</entry>
-
- <entry key='commands.queueing'>true</entry>
-
- <entry key='database.ignoreUnknown'>true</entry>
- <entry key='database.changelog'>./schema/changelog-master.xml</entry>
-
- <entry key='gps103.port'>5001</entry>
- <entry key='tk103.port'>5002</entry>
- <entry key='gl100.port'>5003</entry>
- <entry key='gl200.port'>5004</entry>
- <entry key='t55.port'>5005</entry>
- <entry key='xexun.port'>5006</entry>
- <entry key='xexun.extended'>false</entry>
- <entry key='totem.port'>5007</entry>
- <entry key='enfora.port'>5008</entry>
- <entry key='meiligao.port'>5009</entry>
- <entry key='trv.port'>5010</entry>
- <entry key='suntech.port'>5011</entry>
- <entry key='progress.port'>5012</entry>
- <entry key='h02.port'>5013</entry>
- <entry key='jt600.port'>5014</entry>
- <entry key='huabao.port'>5015</entry>
- <entry key='v680.port'>5016</entry>
- <entry key='pt502.port'>5017</entry>
- <entry key='tr20.port'>5018</entry>
- <entry key='navis.port'>5019</entry>
- <entry key='meitrack.port'>5020</entry>
- <entry key='skypatrol.port'>5021</entry>
- <entry key='gt02.port'>5022</entry>
- <entry key='gt06.port'>5023</entry>
- <entry key='megastek.port'>5024</entry>
- <entry key='navigil.port'>5025</entry>
- <entry key='gpsgate.port'>5026</entry>
- <entry key='teltonika.port'>5027</entry>
- <entry key='mta6.port'>5028</entry>
- <entry key='tzone.port'>5029</entry>
- <entry key='tlt2h.port'>5030</entry>
- <entry key='taip.port'>5031</entry>
- <entry key='wondex.port'>5032</entry>
- <entry key='cellocator.port'>5033</entry>
- <entry key='galileo.port'>5034</entry>
- <entry key='ywt.port'>5035</entry>
- <entry key='tk102.port'>5036</entry>
- <entry key='intellitrac.port'>5037</entry>
- <entry key='gpsmta.port'>5038</entry>
- <entry key='wialon.port'>5039</entry>
- <entry key='carscop.port'>5040</entry>
- <entry key='apel.port'>5041</entry>
- <entry key='manpower.port'>5042</entry>
- <entry key='globalsat.port'>5043</entry>
- <entry key='atrack.port'>5044</entry>
- <entry key='pt3000.port'>5045</entry>
- <entry key='ruptela.port'>5046</entry>
- <entry key='topflytech.port'>5047</entry>
- <entry key='laipac.port'>5048</entry>
- <entry key='aplicom.port'>5049</entry>
- <entry key='gotop.port'>5050</entry>
- <entry key='sanav.port'>5051</entry>
- <entry key='gator.port'>5052</entry>
- <entry key='noran.port'>5053</entry>
- <entry key='m2m.port'>5054</entry>
- <entry key='osmand.port'>5055</entry>
- <entry key='easytrack.port'>5056</entry>
- <entry key='gpsmarker.port'>5057</entry>
- <entry key='khd.port'>5058</entry>
- <entry key='piligrim.port'>5059</entry>
- <entry key='stl060.port'>5060</entry>
- <entry key='cartrack.port'>5061</entry>
- <entry key='minifinder.port'>5062</entry>
- <entry key='haicom.port'>5063</entry>
- <entry key='eelink.port'>5064</entry>
- <entry key='box.port'>5065</entry>
- <entry key='freedom.port'>5066</entry>
- <entry key='telic.port'>5067</entry>
- <entry key='trackbox.port'>5068</entry>
- <entry key='visiontek.port'>5069</entry>
- <entry key='orion.port'>5070</entry>
- <entry key='riti.port'>5071</entry>
- <entry key='ulbotech.port'>5072</entry>
- <entry key='tramigo.port'>5073</entry>
- <entry key='tr900.port'>5074</entry>
- <entry key='ardi01.port'>5075</entry>
- <entry key='xt013.port'>5076</entry>
- <entry key='autofon.port'>5077</entry>
- <entry key='gosafe.port'>5078</entry>
- <entry key='tt8850.port'>5079</entry>
- <entry key='bce.port'>5080</entry>
- <entry key='xirgo.port'>5081</entry>
- <entry key='calamp.port'>5082</entry>
- <entry key='mtx.port'>5083</entry>
- <entry key='tytan.port'>5084</entry>
- <entry key='avl301.port'>5085</entry>
- <entry key='castel.port'>5086</entry>
- <entry key='mxt.port'>5087</entry>
- <entry key='cityeasy.port'>5088</entry>
- <entry key='aquila.port'>5089</entry>
- <entry key='flextrack.port'>5090</entry>
- <entry key='blackkite.port'>5091</entry>
- <entry key='adm.port'>5092</entry>
- <entry key='watch.port'>5093</entry>
- <entry key='t800x.port'>5094</entry>
- <entry key='upro.port'>5095</entry>
- <entry key='auro.port'>5096</entry>
- <entry key='disha.port'>5097</entry>
- <entry key='thinkrace.port'>5098</entry>
- <entry key='pathaway.port'>5099</entry>
- <entry key='arnavi.port'>5100</entry>
- <entry key='nvs.port'>5101</entry>
- <entry key='kenji.port'>5102</entry>
- <entry key='astra.port'>5103</entry>
- <entry key='homtecs.port'>5104</entry>
- <entry key='fox.port'>5105</entry>
- <entry key='gnx.port'>5106</entry>
- <entry key='arknav.port'>5107</entry>
- <entry key='supermate.port'>5108</entry>
- <entry key='appello.port'>5109</entry>
- <entry key='idpl.port'>5110</entry>
- <entry key='huasheng.port'>5111</entry>
- <entry key='l100.port'>5112</entry>
- <entry key='granit.port'>5113</entry>
- <entry key='carcell.port'>5114</entry>
- <entry key='obddongle.port'>5115</entry>
- <entry key='hunterpro.port'>5116</entry>
- <entry key='raveon.port'>5117</entry>
- <entry key='cradlepoint.port'>5118</entry>
- <entry key='arknavx8.port'>5119</entry>
- <entry key='autograde.port'>5120</entry>
- <entry key='oigo.port'>5121</entry>
- <entry key='jpkorjar.port'>5122</entry>
- <entry key='cguard.port'>5123</entry>
- <entry key='fifotrack.port'>5124</entry>
- <entry key='smokey.port'>5125</entry>
- <entry key='extremtrac.port'>5126</entry>
- <entry key='trakmate.port'>5127</entry>
- <entry key='at2000.port'>5128</entry>
- <entry key='maestro.port'>5129</entry>
- <entry key='ais.port'>5130</entry>
- <entry key='gt30.port'>5131</entry>
- <entry key='tmg.port'>5132</entry>
- <entry key='pretrace.port'>5133</entry>
- <entry key='pricol.port'>5134</entry>
- <entry key='siwi.port'>5135</entry>
- <entry key='starlink.port'>5136</entry>
- <entry key='dmt.port'>5137</entry>
- <entry key='xt2400.port'>5138</entry>
- <entry key='dmthttp.port'>5139</entry>
- <entry key='alematics.port'>5140</entry>
- <entry key='gps056.port'>5141</entry>
- <entry key='flexcomm.port'>5142</entry>
- <entry key='vt200.port'>5143</entry>
- <entry key='owntracks.port'>5144</entry>
- <entry key='vtfms.port'>5145</entry>
- <entry key='tlv.port'>5146</entry>
- <entry key='esky.port'>5147</entry>
- <entry key='genx.port'>5148</entry>
- <entry key='flespi.port'>5149</entry>
- <entry key='dway.port'>5150</entry>
- <entry key='recoda.port'>5151</entry>
- <entry key='oko.port'>5152</entry>
- <entry key='ivt401.port'>5153</entry>
- <entry key='sigfox.port'>5154</entry>
- <entry key='t57.port'>5155</entry>
- <entry key='spot.port'>5156</entry>
- <entry key='m2c.port'>5157</entry>
- <entry key='austinnb.port'>5158</entry>
- <entry key='opengts.port'>5159</entry>
- <entry key='cautela.port'>5160</entry>
- <entry key='continental.port'>5161</entry>
- <entry key='egts.port'>5162</entry>
- <entry key='robotrack.port'>5163</entry>
- <entry key='pt60.port'>5164</entry>
- <entry key='telemax.port'>5165</entry>
- <entry key='sabertek.port'>5166</entry>
- <entry key='retranslator.port'>5167</entry>
- <entry key='svias.port'>5168</entry>
- <entry key='eseal.port'>5169</entry>
- <entry key='freematics.port'>5170</entry>
- <entry key='avema.port'>5171</entry>
- <entry key='autotrack.port'>5172</entry>
- <entry key='tek.port'>5173</entry>
- <entry key='wristband.port'>5174</entry>
- <entry key='lacak.port'>5175</entry>
- <entry key='milesmate.port'>5176</entry>
- <entry key='anytrek.port'>5177</entry>
- <entry key='smartsole.port'>5178</entry>
- <entry key='its.port'>5179</entry>
- <entry key='xrb28.port'>5180</entry>
- <entry key='c2stek.port'>5181</entry>
- <entry key='nyitech.port'>5182</entry>
- <entry key='neos.port'>5183</entry>
- <entry key='satsol.port'>5184</entry>
- <entry key='globalstar.port'>5185</entry>
- <entry key='sanul.port'>5186</entry>
- <entry key='minifinder2.port'>5187</entry>
- <entry key='radar.port'>5188</entry>
- <entry key='techtlt.port'>5189</entry>
- <entry key='starcom.port'>5190</entry>
- <entry key='mictrack.port'>5191</entry>
- <entry key='plugin.port'>5192</entry>
- <entry key='leafspy.port'>5193</entry>
- <entry key='naviset.port'>5194</entry>
- <entry key='racedynamics.port'>5195</entry>
- <entry key='rst.port'>5196</entry>
- <entry key='pt215.port'>5197</entry>
- <entry key='pacifictrack.port'>5198</entry>
- <entry key='topin.port'>5199</entry>
- <entry key='outsafe.port'>5200</entry>
- <entry key='solarpowered.port'>5201</entry>
- <entry key='motor.port'>5202</entry>
- <entry key='omnicomm.port'>5203</entry>
- <entry key='s168.port'>5204</entry>
- <entry key='vnet.port'>5205</entry>
- <entry key='blue.port'>5206</entry>
- <entry key='pst.port'>5207</entry>
- <entry key='dingtek.port'>5208</entry>
- <entry key='wli.port'>5209</entry>
- <entry key='niot.port'>5210</entry>
- <entry key='portman.port'>5211</entry>
- <entry key='moovbox.port'>5212</entry>
- <entry key='futureway.port'>5213</entry>
- <entry key='polte.port'>5214</entry>
- <entry key='net.port'>5215</entry>
- <entry key='mobilogix.port'>5216</entry>
- <entry key='swiftech.port'>5217</entry>
- <entry key='iotm.port'>5218</entry>
- <entry key='dolphin.port'>5219</entry>
- <entry key='ennfu.port'>5220</entry>
- <entry key='navtelecom.port'>5221</entry>
- <entry key='startek.port'>5222</entry>
- <entry key='gs100.port'>5223</entry>
- <entry key='mavlink2.port'>5224</entry>
- <entry key='uux.port'>5225</entry>
- <entry key='r12w.port'>5226</entry>
- <entry key='flexiblereport.port'>5227</entry>
- <entry key='thinkpower.port'>5228</entry>
- <entry key='stb.port'>5229</entry>
- <entry key='b2316.port'>5230</entry>
- <entry key='hoopo.port'>5231</entry>
- <entry key='dualcam.port'>5232</entry>
- <entry key='xexun2.port'>5233</entry>
- <entry key='techtocruz.port'>5234</entry>
- <entry key='flexapi.port'>5235</entry>
- <entry key='dsf22.port'>5236</entry>
- <entry key='jido.port'>5237</entry>
- <entry key='armoli.port'>5238</entry>
- <entry key='teratrack.port'>5239</entry>
- <entry key='envotech.port'>5240</entry>
- <entry key='bstpl.port'>5241</entry>
- <entry key='thuraya.port'>5242</entry>
- <entry key='ndtpv6.port'>5243</entry>
- <entry key='g1rus.port'>5244</entry>
- <entry key='rftrack.port'>5245</entry>
- <entry key='vlt.port'>5246</entry>
- <entry key='transync.port'>5247</entry>
- <entry key='t622iridium.port'>5248</entry>
- <entry key='pui.port'>5249</entry>
- <entry key='nto.port'>5250</entry>
- <entry key='ramac.port'>5251</entry>
- <entry key='positrex.port'>5252</entry>
- <entry key='dragino.port'>5253</entry>
- <entry key='fleetguide.port'>5254</entry>
- <entry key='valtrack.port'>5255</entry>
-
-</properties>
diff --git a/setup/package.sh b/setup/package.sh
index 5b3c9bcde..1cac75713 100755
--- a/setup/package.sh
+++ b/setup/package.sh
@@ -88,7 +88,6 @@ prepare () {
cp ../schema/* out/schema
cp -r ../templates/* out/templates
cp -r ../traccar-web/build/* out/web
- cp default.xml out/conf
cp traccar.xml out/conf
if [ $PLATFORM = "all" -o $PLATFORM = "windows-64" ]; then
diff --git a/setup/traccar.iss b/setup/traccar.iss
index 466a40c30..ad8e2552b 100644
--- a/setup/traccar.iss
+++ b/setup/traccar.iss
@@ -1,6 +1,6 @@
[Setup]
AppName=Traccar
-AppVersion=6.0
+AppVersion=6.2
DefaultDirName={pf}\Traccar
OutputBaseFilename=traccar-setup
ArchitecturesInstallIn64BitMode=x64
diff --git a/setup/traccar.xml b/setup/traccar.xml
index aab9ba311..d5bd87e9c 100644
--- a/setup/traccar.xml
+++ b/setup/traccar.xml
@@ -1,22 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
-
<!DOCTYPE properties SYSTEM 'http://java.sun.com/dtd/properties.dtd'>
-
<properties>
- <entry key='config.default'>./conf/default.xml</entry>
-
- <!--
-
- This is the main configuration file. All your configuration parameters should be placed in this file.
-
- Default configuration parameters are located in the "default.xml" file. You should not modify it to avoid issues
- with upgrading to a new version. Parameters in the main config file override values in the default file. Do not
- remove "config.default" parameter from this file unless you know what you are doing.
-
- For list of available parameters see following page: https://www.traccar.org/configuration-file/
-
- -->
+ <!-- Documentation: https://www.traccar.org/configuration-file/ -->
<entry key='database.driver'>org.h2.Driver</entry>
<entry key='database.url'>jdbc:h2:./data/database</entry>
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)) {
diff --git a/src/test/java/org/traccar/calendar/CalendarTest.java b/src/test/java/org/traccar/calendar/CalendarTest.java
index 4106f89a9..d2c2b251c 100644
--- a/src/test/java/org/traccar/calendar/CalendarTest.java
+++ b/src/test/java/org/traccar/calendar/CalendarTest.java
@@ -5,19 +5,19 @@ import org.junit.jupiter.api.Test;
import org.traccar.model.Calendar;
import java.io.IOException;
-import java.sql.SQLException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
-import java.util.Date;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class CalendarTest {
@Test
- public void testCalendar() throws IOException, ParserException, ParseException, SQLException {
+ public void testCalendar() throws IOException, ParserException, ParseException {
String calendarString = "BEGIN:VCALENDAR\n" +
"PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\n" +
"VERSION:2.0\n" +
@@ -54,4 +54,36 @@ public class CalendarTest {
var periods = calendar.findPeriods(format.parse("2016-12-13 06:59:59+05"));
assertFalse(periods.isEmpty());
}
+
+ @Test
+ public void testCalendarOverlap() throws IOException, ParserException, ParseException {
+ String calendarString = "BEGIN:VCALENDAR\n" +
+ "VERSION:2.0\n" +
+ "PRODID:-//Traccar//NONSGML Traccar//EN\n" +
+ "BEGIN:VEVENT\n" +
+ "UID:00000000-0000-0000-0000-000000000000\n" +
+ "DTSTART;TZID=America/Los_Angeles:20240420T060000\n" +
+ "DTEND;TZID=America/Los_Angeles:20240421T060000\n" +
+ "RRULE:FREQ=DAILY\n" +
+ "SUMMARY:Event\n" +
+ "END:VEVENT\n" +
+ "END:VCALENDAR";
+ Calendar calendar = new Calendar();
+ calendar.setData(calendarString.getBytes());
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssX");
+
+ var periods0 = calendar.findPeriods(format.parse("2014-05-13 07:00:00-07"));
+ var periods1 = calendar.findPeriods(format.parse("2024-05-13 05:00:00-07"));
+ var periods2 = calendar.findPeriods(format.parse("2024-05-13 07:00:00-07"));
+ var periods3 = calendar.findPeriods(format.parse("2024-05-13 08:00:00-07"));
+
+ assertEquals(periods0.size(), 0);
+ assertEquals(periods1.size(), 1);
+ assertEquals(periods2.size(), 1);
+ assertEquals(periods3.size(), 1);
+
+ assertNotEquals(periods0, periods1);
+ assertNotEquals(periods1, periods2);
+ assertEquals(periods2, periods3);
+ }
}
diff --git a/src/test/java/org/traccar/protocol/EasyTrackProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/EasyTrackProtocolDecoderTest.java
index d40463c1e..fee0252b7 100644
--- a/src/test/java/org/traccar/protocol/EasyTrackProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/EasyTrackProtocolDecoderTest.java
@@ -11,6 +11,9 @@ public class EasyTrackProtocolDecoderTest extends ProtocolTest {
var decoder = inject(new EasyTrackProtocolDecoder(null));
verifyAttributes(decoder, text(
+ "*ET,358162092884226,OB,BD$V13.6;R01510;S023;P034.9;O035.2;C085;L000.0;XM035.170;M4.25;F001.197;T0000730;A09;B00;D00;GX0;GY0;GZ0;"));
+
+ verifyAttributes(decoder, text(
"*ET,358999999999916,OB,BD$V14.2;R08258;S166;P058.4;O079.2;C025;L081.5;XM091.393;M722379;F352.956;T0037184;A01;B00;D00;GX3;GY-6;GZ-268;@4#"));
verifyNotNull(decoder, text(
diff --git a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java
index 4cccf8fa2..925a0da1c 100644
--- a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java
@@ -12,6 +12,10 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest {
var decoder = inject(new Gl200TextProtocolDecoder(null));
verifyAttribute(decoder, buffer(
+ "+RESP:GTERI,8020050704,867488060246195,,00000004,28823,10,1,1,0.0,33,10.1,10.606120,43.656780,20240408084402,0222,0010,7D53,00DD120D,02,0,0.0,,,,,100,210100,0,1,FFFFF,YS2R4X20009288827,2,H1910197,58234.30,500,1,90,H1.5,P84.00,,0,4616.20,2.28,2.16,5.64,5358,1038,0010,00,00,20240408084403,1809$"),
+ Position.KEY_BATTERY_LEVEL, 100);
+
+ verifyAttribute(decoder, buffer(
"+RESP:GTVGN,C2010D,869653060009939,GV600M,453966,0,0.0,0,163.9,10.239379,45.931389,20231210233723,0222,0010,2F31,006D7621,,,0.0,20240107182623,143F$"),
Position.KEY_IGNITION, true);
diff --git a/src/test/java/org/traccar/protocol/GlobalstarProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/GlobalstarProtocolDecoderTest.java
index 995fffad0..e78c6c7e7 100644
--- a/src/test/java/org/traccar/protocol/GlobalstarProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/GlobalstarProtocolDecoderTest.java
@@ -11,7 +11,7 @@ public class GlobalstarProtocolDecoderTest extends ProtocolTest {
var decoder = inject(new GlobalstarProtocolDecoder(null));
- decoder.setAlternative(true);
+ decoder.setModelOverride("AtlasTrax");
verifyNull(decoder, request(HttpMethod.POST, "/", buffer(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
@@ -24,7 +24,7 @@ public class GlobalstarProtocolDecoderTest extends ProtocolTest {
"</stuMessage>\n",
"</stuMessages>")));
- decoder.setAlternative(false);
+ decoder.setModelOverride(null);
verifyPositions(decoder, request(HttpMethod.POST, "/", buffer(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
diff --git a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java
index 04d7fafb3..05a33cc72 100644
--- a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java
@@ -494,6 +494,12 @@ public class Gt06ProtocolDecoderTest extends ProtocolTest {
"78785195140a020c2914055D4A800209D9C014009300004556454e545f3335333337363131303032333139365f30303030303030305f323032305f31305f30325f31345f34315f32305f30352e6d70340004e3a60d0a"),
Position.KEY_ALARM, Position.ALARM_ACCIDENT);
+ decoder.setModelOverride("LW4G-4B");
+
+ verifyAttribute(decoder, binary(
+ "78782516180516150812c804b50ee80880e40805dcf909012e000000986633460604190106c393490d0a"),
+ Position.KEY_ALARM, Position.ALARM_ACCELERATION);
+
}
}
diff --git a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
index 7fd7cc599..2ba37ab09 100644
--- a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java
@@ -12,6 +12,14 @@ public class HuabaoProtocolDecoderTest extends ProtocolTest {
var decoder = inject(new HuabaoProtocolDecoder(null));
verifyAttribute(decoder, binary(
+ "7e020000520198080908740af300000000000c000f016602a302c662f802fc000000cc24051618132401040000020f30011e310109f30100610204b056020acd5d0b0102d40379b8011423033c51108017ffffffffffffffffffffffffffffc87e"),
+ Position.PREFIX_TEMP + 1, -32745);
+
+ verifyAttribute(decoder, binary(
+ "7e020000460100503769640002000001000000001a01b9eaf804d8ee86001800000000240507035152010400000000300100310107eb1c00060089fffffffe000400ce0000000c00b28942310221007544309f5f7e"),
+ Position.KEY_ICCID, "8942310221007544309");
+
+ verifyAttribute(decoder, binary(
"7E0200005300959000194400080000000000000003015DA64806CCB4A8001100000000230816014137010400005B3F30011F310112EB29000C00B28986049910219020787400060089FFFFFFFF000600C5FFFFFFEF0004002D0F4E000300A84CE07E"),
Position.KEY_BATTERY_LEVEL, 76);
diff --git a/src/test/java/org/traccar/protocol/SnapperFrameDecoderTest.java b/src/test/java/org/traccar/protocol/SnapperFrameDecoderTest.java
new file mode 100644
index 000000000..260a18032
--- /dev/null
+++ b/src/test/java/org/traccar/protocol/SnapperFrameDecoderTest.java
@@ -0,0 +1,23 @@
+package org.traccar.protocol;
+
+import org.junit.jupiter.api.Test;
+import org.traccar.ProtocolTest;
+
+public class SnapperFrameDecoderTest extends ProtocolTest {
+
+ @Test
+ public void testDecode() throws Exception {
+
+ var decoder = inject(new SnapperFrameDecoder());
+
+ verifyFrame(
+ binary("4b0341a6b0c608000040000005000000000000007d5e14010068656c6c6f"),
+ decoder.decode(null, null, binary("4b0341a6b0c608000040000005000000000000007d5e14010068656c6c6f")));
+
+ verifyFrame(
+ binary("5012"),
+ decoder.decode(null, null, binary("5012")));
+
+ }
+
+}
diff --git a/src/test/java/org/traccar/protocol/SnapperProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/SnapperProtocolDecoderTest.java
new file mode 100644
index 000000000..43df24316
--- /dev/null
+++ b/src/test/java/org/traccar/protocol/SnapperProtocolDecoderTest.java
@@ -0,0 +1,30 @@
+package org.traccar.protocol;
+
+import org.junit.jupiter.api.Test;
+import org.traccar.ProtocolTest;
+
+public class SnapperProtocolDecoderTest extends ProtocolTest {
+
+ @Test
+ public void testDecode() throws Exception {
+
+ var decoder = inject(new SnapperProtocolDecoder(null));
+
+ verifyNull(decoder, binary(
+ "4b0341a6b0c608000040000005000000000000007d5e14010068656c6c6f"));
+
+ verifyAttributes(decoder, binary(
+ "4b044daff87aff5b8aad0000d4000000000000000b001337000500210008000d0000ffffffff7f2300190000000000000001000000000224ff000003404000000400000034008c007b2273223a22303034313438222c226332223a2230303030303030303030303030303030222c226132223a2230303030303030303030303830304530222c226f223a2230303030222c2274223a2230303030222c227a223a223030222c2277223a223030222c2272223a222d222c226d223a223030303030303030222c2262223a223030303030303030227d32000a007b2266223a223234227d330007007b2262223a5d7d"));
+
+ verifyPosition(decoder, binary(
+ "4b044daff87aff5b8aad00007b010000000013ea0c006837000500210008003e000058c48fa94823001900000080000200018080000002deff0f0003404000000400000034008c007b2273223a22303034303438222c226332223a2230303030303030303030303030303030222c226132223a2230303030303030303030303030303030222c226f223a2230303030222c2274223a2230303030222c227a223a223030222c2277223a223030222c2272223a222d222c226d223a223030303030303030222c2262223a223030303030303030227d320079007b2266223a224445222c2274223a22303932383336222c2264223a22313530343234222c226c61223a22353334312e34333732222c226c6f223a2230303935342e30373036222c2261223a22382e34222c2273223a22302e3030222c2263223a2232362e3036222c227376223a223135222c2270223a22227d33003f007b2263223a22323632222c226e223a223033222c2262223a5b7b226c223a2232423334222c2263223a223030303041313231222c2273223a223132227d5d7d"));
+
+ verifyPosition(decoder, binary(
+ "4b044daff87aff5b8aad0000430100000000bb870c005b37000500210008003e0100ffffffff7f2300190000007f000000018080000002deff080003404000000400000034008c007b2273223a22303034303438222c226332223a2230303030303030303030303030303030222c226132223a2230303030303030303030303030303030222c226f223a2230303030222c2274223a2230303030222c227a223a223030222c2277223a223030222c2272223a222d222c226d223a223030303030303030222c2262223a223030303030303030227d320079007b2266223a224445222c2274223a22303832303335222c2264223a22313730343234222c226c61223a22353334312e34323635222c226c6f223a2230303935342e30373935222c2261223a22362e32222c2273223a22322e3237222c2263223a2233302e3135222c227376223a223038222c2270223a22227d330007007b2262223a5d7d"));
+
+ verifyNull(decoder, binary(
+ "5003"));
+
+ }
+
+}
diff --git a/src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java
index d656bba13..67fa43dec 100644
--- a/src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java
@@ -12,6 +12,10 @@ public class SuntechProtocolDecoderTest extends ProtocolTest {
var decoder = inject(new SuntechProtocolDecoder(null));
+ verifyAttribute(decoder, buffer(
+ "UEX;1610020241;03FFFFFF;161;3.0.9;0;20240506;15:52:55;00006697;724;11;4EDA;33;-5.129240;-42.797868;0.00;0.00;11;1;00000001;00000000;24;GTSL|6|1|0|22574684|1|\r\n;A7;;164;0;11.82"),
+ Position.KEY_DRIVER_UNIQUE_ID, "22574684");
+
verifyAttributes(decoder, buffer(
"ST410STT;109815653;445;03;16531;724;10;-77;6011;7;16273;724;10;7511;0;0;13903;724;10;6011;0;0;7671;724;10;7111;0;0;16533;724;10;6011;0;0;0;0;0;0;0;0;0;0;0;0;0;0;3.86;0;0098;1;003;;;;;;;;;"));
diff --git a/src/test/java/org/traccar/protocol/TrvProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/TrvProtocolDecoderTest.java
index 370775735..a17fc341f 100644
--- a/src/test/java/org/traccar/protocol/TrvProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/TrvProtocolDecoderTest.java
@@ -2,6 +2,7 @@ package org.traccar.protocol;
import org.junit.jupiter.api.Test;
import org.traccar.ProtocolTest;
+import org.traccar.model.Position;
public class TrvProtocolDecoderTest extends ProtocolTest {
@@ -13,6 +14,30 @@ public class TrvProtocolDecoderTest extends ProtocolTest {
verifyNull(decoder, text(
"TRVAP00352121088015548"));
+ verifyAttribute(decoder, text(
+ "IWAP10080524A2232.9806N11404.9355E000.1061830323.8706000908000502,460,0,9520,3671,01,zhcn,00,HOME|74-DE-2B-44-88-8C|97&HOME1|74-DE-2B-44-88-8C|97&HOME2|74-DE-2B-44-88-8C|97&HOME3|74-DE-2B-44-88-8C|97"),
+ Position.KEY_ALARM, Position.ALARM_SOS);
+
+ verifyAttribute(decoder, text(
+ "IWAP49,68"),
+ Position.KEY_HEART_RATE, 68);
+
+ verifyAttribute(decoder, text(
+ "IWAPHT,60,130,85"),
+ "pressureDiastolic", 85);
+
+ verifyAttribute(decoder, text(
+ "IWAPHP,60,130,85,95,90,36.5,,,,,,,"),
+ "temperature", 36.5);
+
+ verifyAttribute(decoder, text(
+ "IWAP50,36.7,90"),
+ Position.KEY_BATTERY_LEVEL, 90);
+
+ verifyAttribute(decoder, text(
+ "IWAP03,06000908000102,5555,30"),
+ Position.KEY_STEPS, 5555);
+
verifyPosition(decoder, text(
"TRVYP14080524A2232.9806N11404.9355E000.1061830323.870600090800010200011,460,0,9520,3671,Home|74-DE-2B-44-88-8C|97&Home1|74-DE-2B-44-88-8C|97&Home2|74-DE-2B-44-88-8C|97& Home3|74-DE-2B-44-88-8C|97"));
@@ -40,7 +65,7 @@ public class TrvProtocolDecoderTest extends ProtocolTest {
verifyPosition(decoder, text(
"IWAP10080524A2232.9806N11404.9355E000.1061830323.8706000908000502,460,0,9520,3671,00,zh-cn,00,HOME|74-DE-2B-44-88-8C|97&HOME1|74-DE-2B-44-88-8C|97&HOME2|74-DE-2B-44-88-8C|97&HOME3|74-DE-2B-44-88-8C|97"));
- verifyNull(decoder, text(
+ verifyAttributes(decoder, text(
"IWAP03,06000908000102,5555,30"));
verifyNull(decoder, text(
diff --git a/src/test/java/org/traccar/protocol/WatchProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/WatchProtocolDecoderTest.java
index 5fd0ede44..5c61d5426 100644
--- a/src/test/java/org/traccar/protocol/WatchProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/WatchProtocolDecoderTest.java
@@ -18,7 +18,7 @@ public class WatchProtocolDecoderTest extends ProtocolTest {
var decoder = inject(new WatchProtocolDecoder(null));
verifyAttribute(decoder, buffer(
- "[3G*9705141740*000B*oxygen,0,98]"),
+ "[3G*9705141740*000B*oxygen,0,98]"),
"bloodOxygen", 98);
verifyPosition(decoder, buffer(
diff --git a/swagger.json b/swagger.json
index 933652cb0..b6d1ff241 100644
--- a/swagger.json
+++ b/swagger.json
@@ -2,7 +2,7 @@
"openapi": "3.0.1",
"info": {
"title": "Traccar",
- "version": "6.0",
+ "version": "6.2",
"description": "Traccar GPS tracking server API documentation. To use the API you need to have a server instance. For testing purposes you can use one of free [demo servers](https://www.traccar.org/demo-server/). For production use you can install your own server or get a [subscription service](https://www.traccar.org/product/tracking-server/).",
"contact": {
"name": "Traccar Support",
diff --git a/templates/export/stops.xlsx b/templates/export/stops.xlsx
index 967d26309..4e7aaf422 100644
--- a/templates/export/stops.xlsx
+++ b/templates/export/stops.xlsx
Binary files differ
diff --git a/templates/export/summary.xlsx b/templates/export/summary.xlsx
index 47a6241b1..528b99f55 100644
--- a/templates/export/summary.xlsx
+++ b/templates/export/summary.xlsx
Binary files differ
diff --git a/templates/export/trips.xlsx b/templates/export/trips.xlsx
index 3201c66bb..3549a372e 100644
--- a/templates/export/trips.xlsx
+++ b/templates/export/trips.xlsx
Binary files differ
diff --git a/tools/config-doc.py b/tools/config-doc.py
index c55b2b5ef..9d9177070 100755
--- a/tools/config-doc.py
+++ b/tools/config-doc.py
@@ -19,40 +19,32 @@ def get_config_keys():
'key': A dot separated name of the config key
'description': A list of str
"""
- desc_re = re.compile(r"(/\*\*\n|\s+\*/|\s+\*)")
- key_match_re = re.compile(r"\(\n(.+)\);", re.DOTALL)
- key_split_re = re.compile(r",\s+", re.DOTALL)
types_match_re = re.compile(r"List\.of\(([^)]+)\)", re.DOTALL)
keys = []
with open(_KEYS_FILE, "r") as f:
- config = re.findall(
- r"(/\*\*.*?\*/)\n\s+(public static final Config.*?;)", f.read(), re.DOTALL
- )
+ config = re.findall(r"/\*\*\s.*?\);", f.read(), re.DOTALL)
for i in config:
- try:
- key_match = key_match_re.search(i[1])
- if key_match:
- terms = [x.strip() for x in key_split_re.split(key_match.group(1))]
- key = terms[0].replace('"', "")
- key = "[protocol]" + key if key.startswith('.') else key
- description = [
- x.strip().replace("\n", "")
- for x in desc_re.sub("\n", i[0]).strip().split("\n\n")
- ]
- types_match = types_match_re.search(i[1])
- types = map(lambda x: x[8:].lower(), types_match[1].split(", "))
- keys.append(
- {
- "key": key,
- "description": description,
- "types": types,
- }
- )
- except IndexError:
- # will continue if key_match.group(1) or terms[0] does not exist
- # for some reason
- pass
+ lines = i.splitlines()
+ index = -1
+ default = None
+ if "List.of" not in lines[index]:
+ default = lines[index].strip()[:-2]
+ index -= 1
+ types_match = types_match_re.search(lines[index])
+ types = map(lambda x: x[8:].lower(), types_match[1].split(", "))
+ index -= 1
+ key = lines[index].strip()[1:-2]
+ key = "[protocol]" + key if key.startswith('.') else key
+ description = " ".join([l.strip()[2:] for l in lines if l.startswith(" * ")])
+ keys.append(
+ {
+ "key": key,
+ "description": description,
+ "types": types,
+ "default": default,
+ }
+ )
return keys
@@ -66,7 +58,7 @@ def get_html():
{x["key"]}
</h5>
<p class="card-text">
- {"<br /> ".join(x["description"])}
+ {x["description"]}
</p>
</div>
</div>"""
@@ -81,7 +73,7 @@ def get_pug():
f""" div(class='card mt-3')
div(class='card-body')
h5(class='card-title') {x["key"]} {" ".join(map("#[span(class='badge badge-dark') {:}]".format, x["types"]))}
- p(class='card-text') {"#[br] ".join(x["description"])}"""
+ p(class='card-text') {x["description"]}{f"\n p(class='card-text') Default value: {x["default"]}" if x["default"] is not None else ""}"""
for x in get_config_keys()
]
)
diff --git a/tools/test-integration.py b/tools/test-integration.py
index f31ad7a82..29c741390 100755
--- a/tools/test-integration.py
+++ b/tools/test-integration.py
@@ -9,6 +9,7 @@ import json
import socket
import time
import threading
+import re
messages = {
'gps103' : 'imei:123456789012345,help me,1201011201,,F,120100.000,A,6000.0000,N,13000.0000,E,0.00,;',
@@ -137,11 +138,11 @@ debug = '-v' in sys.argv
def load_ports():
ports = {}
dir = os.path.dirname(os.path.abspath(__file__))
- root = xml.etree.ElementTree.parse(dir + '/../setup/default.xml').getroot()
- for entry in root.findall('entry'):
- key = entry.attrib['key']
- if key.endswith('.port'):
- ports[key[:-5]] = int(entry.text)
+ with open(dir + '/../src/main/java/org/traccar/config/PortConfigSuffix.java', 'r') as file:
+ content = file.read()
+ pattern = re.compile(r'PORTS\.put\("([^"]+)",\s*(\d+)\);')
+ matches = pattern.findall(content)
+ ports = {protocol: int(port) for protocol, port in matches}
if debug:
print('\nports: {ports!r}\n')
return ports
diff --git a/traccar-web b/traccar-web
-Subproject 1a7f95baad1939589e6a23bd31b42b8e094a736
+Subproject e9cdc61de90ef44dc2edd1c82d10f2f29803715