aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIván Ávalos <avalos@disroot.org>2024-04-10 15:29:47 -0600
committerIván Ávalos <avalos@disroot.org>2024-04-10 15:29:47 -0600
commite8456ae8ec5960b2119b7cdbcffac6d9ff004c95 (patch)
treeed5dfe5ff3b9e3491adf15b2abe83e13e2ccb6c0
parent4dec2ef603f078ff3bd18d63d1fbd992537cbb88 (diff)
parent9a285e59e580994dc9c3f80935f766f3dafdcd46 (diff)
downloadtrackermap-server-e8456ae8ec5960b2119b7cdbcffac6d9ff004c95.tar.gz
trackermap-server-e8456ae8ec5960b2119b7cdbcffac6d9ff004c95.tar.bz2
trackermap-server-e8456ae8ec5960b2119b7cdbcffac6d9ff004c95.zip
Merge tag 'v6.0'
-rw-r--r--.github/workflows/gradle.yml6
-rw-r--r--.github/workflows/release.yml29
-rw-r--r--build.gradle42
-rw-r--r--debug.xml6
-rw-r--r--schema/changelog-4.0-clean.xml4
-rw-r--r--setup/default.xml2
-rwxr-xr-xsetup/package.sh26
-rw-r--r--setup/traccar.iss2
-rw-r--r--src/main/java/org/traccar/BaseDataHandler.java38
-rw-r--r--src/main/java/org/traccar/BasePipelineFactory.java97
-rw-r--r--src/main/java/org/traccar/BaseProtocolDecoder.java19
-rw-r--r--src/main/java/org/traccar/ExtendedObjectDecoder.java2
-rw-r--r--src/main/java/org/traccar/MainEventHandler.java194
-rw-r--r--src/main/java/org/traccar/MainModule.java31
-rw-r--r--src/main/java/org/traccar/ProcessingHandler.java203
-rw-r--r--src/main/java/org/traccar/ServerManager.java8
-rw-r--r--src/main/java/org/traccar/api/resource/DeviceResource.java34
-rw-r--r--src/main/java/org/traccar/api/resource/UserResource.java10
-rw-r--r--src/main/java/org/traccar/config/Keys.java8
-rw-r--r--src/main/java/org/traccar/database/BufferingManager.java115
-rw-r--r--src/main/java/org/traccar/database/GroupTree.java151
-rw-r--r--src/main/java/org/traccar/handler/BasePositionHandler.java (renamed from src/main/java/org/traccar/GlobalTimer.java)29
-rw-r--r--src/main/java/org/traccar/handler/ComputedAttributesHandler.java40
-rw-r--r--src/main/java/org/traccar/handler/CopyAttributesHandler.java34
-rw-r--r--src/main/java/org/traccar/handler/DatabaseHandler.java (renamed from src/main/java/org/traccar/handler/DefaultDataHandler.java)24
-rw-r--r--src/main/java/org/traccar/handler/DistanceHandler.java13
-rw-r--r--src/main/java/org/traccar/handler/EngineHoursHandler.java16
-rw-r--r--src/main/java/org/traccar/handler/FilterHandler.java27
-rw-r--r--src/main/java/org/traccar/handler/GeocoderHandler.java65
-rw-r--r--src/main/java/org/traccar/handler/GeofenceHandler.java15
-rw-r--r--src/main/java/org/traccar/handler/GeolocationHandler.java73
-rw-r--r--src/main/java/org/traccar/handler/HemisphereHandler.java16
-rw-r--r--src/main/java/org/traccar/handler/MotionHandler.java16
-rw-r--r--src/main/java/org/traccar/handler/OutdatedHandler.java56
-rw-r--r--src/main/java/org/traccar/handler/PositionForwardingHandler.java (renamed from src/main/java/org/traccar/PositionForwardingHandler.java)18
-rw-r--r--src/main/java/org/traccar/handler/PostProcessHandler.java67
-rw-r--r--src/main/java/org/traccar/handler/SpeedLimitHandler.java49
-rw-r--r--src/main/java/org/traccar/handler/TimeHandler.java31
-rw-r--r--src/main/java/org/traccar/handler/events/AlertEventHandler.java17
-rw-r--r--src/main/java/org/traccar/handler/events/BaseEventHandler.java32
-rw-r--r--src/main/java/org/traccar/handler/events/BehaviorEventHandler.java18
-rw-r--r--src/main/java/org/traccar/handler/events/CommandResultEventHandler.java17
-rw-r--r--src/main/java/org/traccar/handler/events/DriverEventHandler.java18
-rw-r--r--src/main/java/org/traccar/handler/events/FuelEventHandler.java22
-rw-r--r--src/main/java/org/traccar/handler/events/GeofenceEventHandler.java21
-rw-r--r--src/main/java/org/traccar/handler/events/IgnitionEventHandler.java25
-rw-r--r--src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java19
-rw-r--r--src/main/java/org/traccar/handler/events/MediaEventHandler.java16
-rw-r--r--src/main/java/org/traccar/handler/events/MotionEventHandler.java22
-rw-r--r--src/main/java/org/traccar/handler/events/OverspeedEventHandler.java27
-rw-r--r--src/main/java/org/traccar/handler/network/AcknowledgementHandler.java (renamed from src/main/java/org/traccar/handler/AcknowledgementHandler.java)2
-rw-r--r--src/main/java/org/traccar/handler/network/MainEventHandler.java98
-rw-r--r--src/main/java/org/traccar/handler/network/NetworkForwarderHandler.java (renamed from src/main/java/org/traccar/handler/NetworkForwarderHandler.java)2
-rw-r--r--src/main/java/org/traccar/handler/network/NetworkMessageHandler.java (renamed from src/main/java/org/traccar/handler/NetworkMessageHandler.java)2
-rw-r--r--src/main/java/org/traccar/handler/network/OpenChannelHandler.java (renamed from src/main/java/org/traccar/handler/OpenChannelHandler.java)2
-rw-r--r--src/main/java/org/traccar/handler/network/RemoteAddressHandler.java (renamed from src/main/java/org/traccar/handler/RemoteAddressHandler.java)8
-rw-r--r--src/main/java/org/traccar/handler/network/StandardLoggingHandler.java (renamed from src/main/java/org/traccar/handler/StandardLoggingHandler.java)2
-rw-r--r--src/main/java/org/traccar/helper/PositionLogger.java94
-rw-r--r--src/main/java/org/traccar/model/Device.java2
-rw-r--r--src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java5
-rw-r--r--src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java101
-rw-r--r--src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java30
-rw-r--r--src/test/java/org/traccar/database/GroupTreeTest.java56
-rw-r--r--src/test/java/org/traccar/handler/DistanceHandlerTest.java5
-rw-r--r--src/test/java/org/traccar/handler/MotionHandlerTest.java3
-rw-r--r--src/test/java/org/traccar/handler/events/AlertEventHandlerTest.java12
-rw-r--r--src/test/java/org/traccar/handler/events/CommandResultEventHandlerTest.java10
-rw-r--r--src/test/java/org/traccar/handler/events/IgnitionEventHandlerTest.java8
-rw-r--r--src/test/java/org/traccar/handler/events/MaintenanceEventHandlerTest.java25
-rw-r--r--src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java3
-rw-r--r--src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java33
-rw-r--r--swagger.json2
m---------traccar-web0
73 files changed, 1222 insertions, 1153 deletions
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index cbe2721bb..e50354cbd 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -12,10 +12,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
- distribution: zulu
+ distribution: temurin
java-version: 11
cache: gradle
- run: ./gradlew build --no-daemon --warning-mode=fail
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a8e4c5369..26695dbac 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,31 +14,25 @@ jobs:
runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
- run: git checkout ${{ github.ref_name }}
working-directory: ./traccar-web
- - uses: actions/setup-java@v3
+ - uses: actions/setup-java@v4
with:
distribution: temurin
- java-version: 17
+ java-version: 21
cache: gradle
- run: ./gradlew build
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
- node-version: 18
+ node-version: 21
cache: npm
- cache-dependency-path: |
- traccar-web/package-lock.json
- traccar-web/modern/package-lock.json
- - run: |
- wget -q https://trials.sencha.com/cmd/7.6.0/SenchaCmd-7.6.0.87-linux-amd64.sh.zip
- unzip SenchaCmd-*.zip
- ./SenchaCmd-*.sh -q
- echo "$HOME/bin/Sencha/Cmd/" >> $GITHUB_PATH
- - run: ./traccar-web/tools/package.sh
+ cache-dependency-path: traccar-web/package-lock.json
+ - run: npm ci && npm run build
+ working-directory: ./traccar-web
- run: |
sudo dpkg --add-architecture i386
sudo apt-get update
@@ -48,10 +42,9 @@ jobs:
working-directory: ./setup
run: |
wget -q http://files.jrsoftware.org/is/5/isetup-5.5.6.exe
- wget -q https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6+10/OpenJDK17U-jdk_x64_windows_hotspot_17.0.6_10.zip
- wget -q https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6+10/OpenJDK17U-jdk_x64_linux_hotspot_17.0.6_10.tar.gz
- wget -q https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6+10/OpenJDK17U-jdk_arm_linux_hotspot_17.0.6_10.tar.gz
- wget -q https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6+10/OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.6_10.tar.gz
+ wget -q https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2+13/OpenJDK21U-jdk_x64_windows_hotspot_21.0.2_13.zip
+ wget -q https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2+13/OpenJDK21U-jdk_x64_linux_hotspot_21.0.2_13.tar.gz
+ wget -q https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2+13/OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.2_13.tar.gz
./package.sh ${{ github.event.inputs.version }}
- name: Upload installers
working-directory: ./setup
diff --git a/build.gradle b/build.gradle
index 3677f8f94..2fe2bfaab 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@ compileJava.options.encoding = "UTF-8"
jar.destinationDirectory = file("$projectDir/target")
checkstyle {
- toolVersion = "10.12.5"
+ toolVersion = "10.15.0"
configFile = "gradle/checkstyle.xml" as File
checkstyleTest.enabled = false
}
@@ -27,12 +27,12 @@ enforce {
ext {
guiceVersion = "7.0.0"
- jettyVersion = "11.0.19"
+ jettyVersion = "11.0.20"
jerseyVersion = "3.1.5"
jacksonVersion = "2.15.3" // same version as jersey-media-json-jackson dependency
- protobufVersion = "3.25.2"
- jxlsVersion = "2.14.0"
- junitVersion = "5.10.1"
+ protobufVersion = "4.26.1"
+ jxlsVersion = "2.14.0" // version 3 requires java 17
+ junitVersion = "5.10.2"
}
protobuf {
@@ -42,15 +42,15 @@ protobuf {
}
dependencies {
- implementation "commons-codec:commons-codec:1.16.0"
+ implementation "commons-codec:commons-codec:1.16.1"
implementation "com.h2database:h2:2.2.224"
- implementation "com.mysql:mysql-connector-j:8.2.0"
- implementation "org.mariadb.jdbc:mariadb-java-client:3.3.2"
- implementation "org.postgresql:postgresql:42.7.1"
- implementation "com.microsoft.sqlserver:mssql-jdbc:12.4.2.jre11"
+ implementation "com.mysql:mysql-connector-j:8.3.0"
+ implementation "org.mariadb.jdbc:mariadb-java-client:3.3.3"
+ implementation "org.postgresql:postgresql:42.7.3"
+ implementation "com.microsoft.sqlserver:mssql-jdbc:12.6.1.jre11"
implementation "com.zaxxer:HikariCP:5.1.0"
- implementation "io.netty:netty-all:4.1.104.Final"
- implementation "org.slf4j:slf4j-jdk14:2.0.11"
+ implementation "io.netty:netty-all:4.1.108.Final"
+ implementation "org.slf4j:slf4j-jdk14:2.0.12"
implementation "com.google.inject:guice:$guiceVersion"
implementation "com.google.inject.extensions:guice-servlet:$guiceVersion"
implementation "org.owasp.encoder:encoder:1.2.3"
@@ -76,30 +76,30 @@ 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.14"
+ implementation "org.mnode.ical4j:ical4j:3.2.17"
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.18"
+ 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.636"
- implementation "org.apache.kafka:kafka-clients:3.6.1"
+ implementation "com.amazonaws:aws-java-sdk-sns:1.12.694"
+ implementation "org.apache.kafka:kafka-clients:3.7.0"
implementation "com.hivemq:hivemq-mqtt-client:1.3.3"
- implementation "redis.clients:jedis:5.1.0"
+ implementation "redis.clients:jedis:5.1.2"
implementation "com.google.firebase:firebase-admin:9.2.0"
- implementation "com.nimbusds:oauth2-oidc-sdk:11.9.1"
+ implementation "com.nimbusds:oauth2-oidc-sdk:11.10.1"
implementation "com.rabbitmq:amqp-client:5.20.0"
implementation "com.warrenstrange:googleauth:1.5.0"
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
- testImplementation "org.mockito:mockito-core:5.8.0"
+ testImplementation "org.mockito:mockito-core:5.11.0"
}
test {
useJUnitPlatform()
}
-task copyDependencies(type: Copy) {
+tasks.register('copyDependencies', Copy) {
into "$projectDir/target/lib"
from configurations.runtimeClasspath
}
@@ -109,7 +109,7 @@ jar {
manifest {
attributes(
"Main-Class": "org.traccar.Main",
- "Implementation-Version": "5.12",
+ "Implementation-Version": "6.0",
"Class-Path": configurations.runtimeClasspath.files.collect { "lib/$it.name" }.join(" "))
}
}
diff --git a/debug.xml b/debug.xml
index 2569bb8cd..a0c179cff 100644
--- a/debug.xml
+++ b/debug.xml
@@ -6,7 +6,7 @@
<entry key='config.default'>./setup/default.xml</entry>
- <entry key='web.path'>./traccar-web/web</entry>
+ <entry key='web.path'>./traccar-web/simple</entry>
<entry key='web.debug'>true</entry>
<entry key='web.console'>true</entry>
@@ -16,6 +16,7 @@
<entry key='logger.console'>true</entry>
<entry key='logger.queries'>false</entry>
+ <entry key='logger.fullStackTraces'>true</entry>
<entry key='mail.debug'>true</entry>
@@ -24,6 +25,9 @@
<entry key='database.user'>sa</entry>
<entry key='database.password'></entry>
+ <!--<entry key='server.instantAcknowledgement'>true</entry>
+ <entry key='server.buffering.threshold'>5000</entry>-->
+
<entry key='atrack.custom'>true</entry>
<entry key='intellitrac.port'>6037</entry>
diff --git a/schema/changelog-4.0-clean.xml b/schema/changelog-4.0-clean.xml
index b4d8ac0ba..4236e6e95 100644
--- a/schema/changelog-4.0-clean.xml
+++ b/schema/changelog-4.0-clean.xml
@@ -335,7 +335,7 @@
<column name="id" type="INT" autoIncrement="true">
<constraints primaryKey="true" />
</column>
- <column name="registration" type="BOOLEAN" defaultValueBoolean="true">
+ <column name="registration" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false" />
</column>
<column name="latitude" type="DOUBLE" defaultValueNumeric="0">
@@ -614,7 +614,7 @@
<addForeignKeyConstraint baseTableName="tc_user_user" baseColumnNames="userid" constraintName="fk_user_user_userid" onDelete="CASCADE" referencedColumnNames="id" referencedTableName="tc_users" />
<insert tableName="tc_servers">
- <column name="registration" valueBoolean="true" />
+ <column name="registration" valueBoolean="false" />
<column name="latitude" valueNumeric="0" />
<column name="longitude" valueNumeric="0" />
<column name="zoom" valueNumeric="0" />
diff --git a/setup/default.xml b/setup/default.xml
index 95bb1f04b..092b4f494 100644
--- a/setup/default.xml
+++ b/setup/default.xml
@@ -11,7 +11,7 @@
-->
<entry key='web.port'>8082</entry>
- <entry key='web.path'>./modern</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>
diff --git a/setup/package.sh b/setup/package.sh
index f8ec927eb..5b3c9bcde 100755
--- a/setup/package.sh
+++ b/setup/package.sh
@@ -15,7 +15,6 @@ usage () {
echo "Available platforms:"
echo " * linux-64"
echo " * linux-arm"
- echo " * linux-arm64"
echo " * windows-64"
echo " * other"
exit 1
@@ -65,17 +64,14 @@ if [ $PLATFORM = "all" -o $PLATFORM = "windows-64" ]; then
check_requirement "Windows 64 Java" "ls OpenJDK*64_windows*.zip" "Missing Windows 64 JDK (https://adoptium.net/)"
check_requirement "Wine" "which wine" "Missing wine binary"
fi
-if [ $PLATFORM = "all" -o $PLATFORM = "linux-64" -o $PLATFORM = "linux-arm" -o $PLATFORM = "linux-arm64" ]; then
+if [ $PLATFORM = "all" -o $PLATFORM = "linux-64" -o $PLATFORM = "linux-arm" ]; then
check_requirement "Makeself" "which makeself" "Missing makeself binary"
fi
if [ $PLATFORM = "all" -o $PLATFORM = "linux-64" ]; then
check_requirement "Linux 64 Java" "ls OpenJDK*x64_linux*.tar.gz" "Missing Linux 64 JDK (https://adoptium.net/)"
fi
if [ $PLATFORM = "all" -o $PLATFORM = "linux-arm" ]; then
- check_requirement "Linux ARM Java" "ls OpenJDK*arm_linux*.tar.gz" "Missing Linux ARM JDK (https://adoptium.net/)"
-fi
-if [ $PLATFORM = "all" -o $PLATFORM = "linux-arm64" ]; then
- check_requirement "Linux ARM 64 Java" "ls OpenJDK*aarch64_linux*.tar.gz" "Missing Linux ARM 64 JDK (https://adoptium.net/)"
+ check_requirement "Linux ARM Java" "ls OpenJDK*aarch64_linux*.tar.gz" "Missing Linux ARM JDK (https://adoptium.net/)"
fi
if [ $PREREQ = false ]; then
info "Missing build requirements, aborting..."
@@ -85,14 +81,13 @@ else
fi
prepare () {
- mkdir -p out/{conf,data,lib,logs,legacy,modern,schema,templates}
+ mkdir -p out/{conf,data,lib,logs,web,schema,templates}
cp ../target/tracker-server.jar out
cp ../target/lib/* out/lib
cp ../schema/* out/schema
cp -r ../templates/* out/templates
- cp -r ../traccar-web/web/* out/legacy
- cp -r ../traccar-web/modern/build/* out/modern
+ cp -r ../traccar-web/build/* out/web
cp default.xml out/conf
cp traccar.xml out/conf
@@ -158,23 +153,16 @@ package_linux_64 () {
package_linux_arm () {
info "Building Linux ARM installer"
- package_linux arm arm
+ package_linux arm aarch64
ok "Created Linux ARM installer"
}
-package_linux_arm64 () {
- info "Building Linux ARM 64 installer"
- package_linux arm64 aarch64
- ok "Created Linux ARM 64 installer"
-}
-
prepare
case $PLATFORM in
all)
package_linux_64
package_linux_arm
- package_linux_arm64
package_windows
package_other
;;
@@ -187,10 +175,6 @@ case $PLATFORM in
package_linux_arm
;;
- linux-arm64)
- package_linux_arm64
- ;;
-
windows-64)
package_windows
;;
diff --git a/setup/traccar.iss b/setup/traccar.iss
index 2ccee1c3e..466a40c30 100644
--- a/setup/traccar.iss
+++ b/setup/traccar.iss
@@ -1,6 +1,6 @@
[Setup]
AppName=Traccar
-AppVersion=5.12
+AppVersion=6.0
DefaultDirName={pf}\Traccar
OutputBaseFilename=traccar-setup
ArchitecturesInstallIn64BitMode=x64
diff --git a/src/main/java/org/traccar/BaseDataHandler.java b/src/main/java/org/traccar/BaseDataHandler.java
deleted file mode 100644
index 48794b0d7..000000000
--- a/src/main/java/org/traccar/BaseDataHandler.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2015 - 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;
-
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInboundHandlerAdapter;
-import org.traccar.model.Position;
-
-public abstract class BaseDataHandler extends ChannelInboundHandlerAdapter {
-
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- if (msg instanceof Position) {
- Position position = handlePosition((Position) msg);
- if (position != null) {
- ctx.fireChannelRead(position);
- }
- } else {
- super.channelRead(ctx, msg);
- }
- }
-
- protected abstract Position handlePosition(Position position);
-
-}
diff --git a/src/main/java/org/traccar/BasePipelineFactory.java b/src/main/java/org/traccar/BasePipelineFactory.java
index ca4a4ae63..40360cca7 100644
--- a/src/main/java/org/traccar/BasePipelineFactory.java
+++ b/src/main/java/org/traccar/BasePipelineFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2023 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.
@@ -25,36 +25,13 @@ import io.netty.channel.ChannelPipeline;
import io.netty.handler.timeout.IdleStateHandler;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import org.traccar.handler.AcknowledgementHandler;
-import org.traccar.handler.ComputedAttributesHandler;
-import org.traccar.handler.CopyAttributesHandler;
-import org.traccar.handler.DefaultDataHandler;
-import org.traccar.handler.DistanceHandler;
-import org.traccar.handler.EngineHoursHandler;
-import org.traccar.handler.FilterHandler;
-import org.traccar.handler.GeocoderHandler;
-import org.traccar.handler.GeofenceHandler;
-import org.traccar.handler.GeolocationHandler;
-import org.traccar.handler.HemisphereHandler;
-import org.traccar.handler.MotionHandler;
-import org.traccar.handler.NetworkForwarderHandler;
-import org.traccar.handler.NetworkMessageHandler;
-import org.traccar.handler.OpenChannelHandler;
-import org.traccar.handler.RemoteAddressHandler;
-import org.traccar.handler.SpeedLimitHandler;
-import org.traccar.handler.StandardLoggingHandler;
-import org.traccar.handler.TimeHandler;
-import org.traccar.handler.events.AlertEventHandler;
-import org.traccar.handler.events.BehaviorEventHandler;
-import org.traccar.handler.events.CommandResultEventHandler;
-import org.traccar.handler.events.DriverEventHandler;
-import org.traccar.handler.events.FuelEventHandler;
-import org.traccar.handler.events.GeofenceEventHandler;
-import org.traccar.handler.events.IgnitionEventHandler;
-import org.traccar.handler.events.MaintenanceEventHandler;
-import org.traccar.handler.events.MediaEventHandler;
-import org.traccar.handler.events.MotionEventHandler;
-import org.traccar.handler.events.OverspeedEventHandler;
+import org.traccar.handler.network.AcknowledgementHandler;
+import org.traccar.handler.network.MainEventHandler;
+import org.traccar.handler.network.NetworkForwarderHandler;
+import org.traccar.handler.network.NetworkMessageHandler;
+import org.traccar.handler.network.OpenChannelHandler;
+import org.traccar.handler.network.RemoteAddressHandler;
+import org.traccar.handler.network.StandardLoggingHandler;
import java.util.Map;
@@ -83,15 +60,7 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> {
protected abstract void addProtocolHandlers(PipelineBuilder pipeline);
- @SafeVarargs
- private void addHandlers(ChannelPipeline pipeline, Class<? extends ChannelHandler>... handlerClasses) {
- for (Class<? extends ChannelHandler> handlerClass : handlerClasses) {
- if (handlerClass != null) {
- pipeline.addLast(injector.getInstance(handlerClass));
- }
- }
- }
-
+ @SuppressWarnings("unchecked")
public static <T extends ChannelHandler> T getHandler(ChannelPipeline pipeline, Class<T> clazz) {
for (Map.Entry<String, ChannelHandler> handlerEntry : pipeline) {
ChannelHandler handler = handlerEntry.getValue();
@@ -107,6 +76,11 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> {
return null;
}
+ private <T> T injectMembers(T object) {
+ injector.injectMembers(object);
+ return object;
+ }
+
@Override
protected void initChannel(Channel channel) {
final ChannelPipeline pipeline = channel.pipeline();
@@ -119,15 +93,10 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> {
pipeline.addLast(new OpenChannelHandler(connector));
if (config.hasKey(Keys.SERVER_FORWARD)) {
int port = config.getInteger(Keys.PROTOCOL_PORT.withPrefix(protocol));
- var handler = new NetworkForwarderHandler(port);
- injector.injectMembers(handler);
- pipeline.addLast(handler);
+ pipeline.addLast(injectMembers(new NetworkForwarderHandler(port)));
}
pipeline.addLast(new NetworkMessageHandler());
-
- var loggingHandler = new StandardLoggingHandler(protocol);
- injector.injectMembers(loggingHandler);
- pipeline.addLast(loggingHandler);
+ pipeline.addLast(injectMembers(new StandardLoggingHandler(protocol)));
if (!connector.isDatagram() && !config.getBoolean(Keys.SERVER_INSTANT_ACKNOWLEDGEMENT)) {
pipeline.addLast(new AcknowledgementHandler());
@@ -135,7 +104,7 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> {
addProtocolHandlers(handler -> {
if (handler instanceof BaseProtocolDecoder || handler instanceof BaseProtocolEncoder) {
- injector.injectMembers(handler);
+ injectMembers(handler);
} else {
if (handler instanceof ChannelInboundHandler) {
handler = new WrapperInboundHandler((ChannelInboundHandler) handler);
@@ -146,35 +115,9 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> {
pipeline.addLast(handler);
});
- addHandlers(
- pipeline,
- TimeHandler.class,
- GeolocationHandler.class,
- HemisphereHandler.class,
- DistanceHandler.class,
- RemoteAddressHandler.class,
- FilterHandler.class,
- GeofenceHandler.class,
- GeocoderHandler.class,
- SpeedLimitHandler.class,
- MotionHandler.class,
- CopyAttributesHandler.class,
- EngineHoursHandler.class,
- ComputedAttributesHandler.class,
- PositionForwardingHandler.class,
- DefaultDataHandler.class,
- MediaEventHandler.class,
- CommandResultEventHandler.class,
- OverspeedEventHandler.class,
- BehaviorEventHandler.class,
- FuelEventHandler.class,
- MotionEventHandler.class,
- GeofenceEventHandler.class,
- AlertEventHandler.class,
- IgnitionEventHandler.class,
- MaintenanceEventHandler.class,
- DriverEventHandler.class,
- MainEventHandler.class);
+ pipeline.addLast(injector.getInstance(RemoteAddressHandler.class));
+ pipeline.addLast(injector.getInstance(ProcessingHandler.class));
+ pipeline.addLast(injector.getInstance(MainEventHandler.class));
}
}
diff --git a/src/main/java/org/traccar/BaseProtocolDecoder.java b/src/main/java/org/traccar/BaseProtocolDecoder.java
index 495a866c0..b764e5cdf 100644
--- a/src/main/java/org/traccar/BaseProtocolDecoder.java
+++ b/src/main/java/org/traccar/BaseProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2022 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.
@@ -154,25 +154,8 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder {
public void getLastLocation(Position position, Date deviceTime) {
if (position.getDeviceId() != 0) {
position.setOutdated(true);
-
- Position last = cacheManager.getPosition(position.getDeviceId());
- if (last != null) {
- position.setFixTime(last.getFixTime());
- position.setValid(last.getValid());
- position.setLatitude(last.getLatitude());
- position.setLongitude(last.getLongitude());
- position.setAltitude(last.getAltitude());
- position.setSpeed(last.getSpeed());
- position.setCourse(last.getCourse());
- position.setAccuracy(last.getAccuracy());
- } else {
- position.setFixTime(new Date(0));
- }
-
if (deviceTime != null) {
position.setDeviceTime(deviceTime);
- } else {
- position.setDeviceTime(new Date());
}
}
}
diff --git a/src/main/java/org/traccar/ExtendedObjectDecoder.java b/src/main/java/org/traccar/ExtendedObjectDecoder.java
index cddddcd80..9468e2fff 100644
--- a/src/main/java/org/traccar/ExtendedObjectDecoder.java
+++ b/src/main/java/org/traccar/ExtendedObjectDecoder.java
@@ -23,7 +23,7 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import org.traccar.handler.AcknowledgementHandler;
+import org.traccar.handler.network.AcknowledgementHandler;
import org.traccar.helper.DataConverter;
import org.traccar.model.Position;
diff --git a/src/main/java/org/traccar/MainEventHandler.java b/src/main/java/org/traccar/MainEventHandler.java
deleted file mode 100644
index fb0171d63..000000000
--- a/src/main/java/org/traccar/MainEventHandler.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.traccar;
-
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelHandler;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInboundHandlerAdapter;
-import io.netty.channel.socket.DatagramChannel;
-import io.netty.handler.codec.http.HttpRequestDecoder;
-import io.netty.handler.timeout.IdleStateEvent;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.traccar.config.Config;
-import org.traccar.config.Keys;
-import org.traccar.database.StatisticsManager;
-import org.traccar.handler.AcknowledgementHandler;
-import org.traccar.helper.DateUtil;
-import org.traccar.helper.NetworkUtil;
-import org.traccar.helper.model.PositionUtil;
-import org.traccar.model.Device;
-import org.traccar.model.Position;
-import org.traccar.session.ConnectionManager;
-import org.traccar.session.cache.CacheManager;
-import org.traccar.storage.Storage;
-import org.traccar.storage.StorageException;
-import org.traccar.storage.query.Columns;
-import org.traccar.storage.query.Condition;
-import org.traccar.storage.query.Request;
-
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-@Singleton
-@ChannelHandler.Sharable
-public class MainEventHandler extends ChannelInboundHandlerAdapter {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(MainEventHandler.class);
-
- private final Set<String> connectionlessProtocols = new HashSet<>();
- private final Set<String> logAttributes = new LinkedHashSet<>();
-
- private final CacheManager cacheManager;
- private final Storage storage;
- private final ConnectionManager connectionManager;
- private final StatisticsManager statisticsManager;
-
- @Inject
- public MainEventHandler(
- Config config, CacheManager cacheManager, Storage storage, ConnectionManager connectionManager,
- StatisticsManager statisticsManager) {
- this.cacheManager = cacheManager;
- this.storage = storage;
- this.connectionManager = connectionManager;
- this.statisticsManager = statisticsManager;
- String connectionlessProtocolList = config.getString(Keys.STATUS_IGNORE_OFFLINE);
- if (connectionlessProtocolList != null) {
- connectionlessProtocols.addAll(Arrays.asList(connectionlessProtocolList.split("[, ]")));
- }
- logAttributes.addAll(Arrays.asList(config.getString(Keys.LOGGER_ATTRIBUTES).split("[, ]")));
- }
-
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) {
- if (msg instanceof Position) {
-
- Position position = (Position) msg;
- Device device = cacheManager.getObject(Device.class, position.getDeviceId());
-
- try {
- if (PositionUtil.isLatest(cacheManager, position)) {
- Device updatedDevice = new Device();
- updatedDevice.setId(position.getDeviceId());
- updatedDevice.setPositionId(position.getId());
- storage.updateObject(updatedDevice, new Request(
- new Columns.Include("positionId"),
- new Condition.Equals("id", updatedDevice.getId())));
-
- cacheManager.updatePosition(position);
- connectionManager.updatePosition(true, position);
- }
- } catch (StorageException error) {
- LOGGER.warn("Failed to update device", error);
- }
-
- StringBuilder builder = new StringBuilder();
- builder.append("[").append(NetworkUtil.session(ctx.channel())).append("] ");
- builder.append("id: ").append(device.getUniqueId());
- for (String attribute : logAttributes) {
- switch (attribute) {
- case "time":
- builder.append(", time: ").append(DateUtil.formatDate(position.getFixTime(), false));
- break;
- case "position":
- builder.append(", lat: ").append(String.format("%.5f", position.getLatitude()));
- builder.append(", lon: ").append(String.format("%.5f", position.getLongitude()));
- break;
- case "speed":
- if (position.getSpeed() > 0) {
- builder.append(", speed: ").append(String.format("%.1f", position.getSpeed()));
- }
- break;
- case "course":
- builder.append(", course: ").append(String.format("%.1f", position.getCourse()));
- break;
- case "accuracy":
- if (position.getAccuracy() > 0) {
- builder.append(", accuracy: ").append(String.format("%.1f", position.getAccuracy()));
- }
- break;
- case "outdated":
- if (position.getOutdated()) {
- builder.append(", outdated");
- }
- break;
- case "invalid":
- if (!position.getValid()) {
- builder.append(", invalid");
- }
- break;
- default:
- Object value = position.getAttributes().get(attribute);
- if (value != null) {
- builder.append(", ").append(attribute).append(": ").append(value);
- }
- break;
- }
- }
- LOGGER.info(builder.toString());
-
- statisticsManager.registerMessageStored(position.getDeviceId(), position.getProtocol());
-
- ctx.writeAndFlush(new AcknowledgementHandler.EventHandled(position));
- }
- }
-
- @Override
- public void channelActive(ChannelHandlerContext ctx) {
- if (!(ctx.channel() instanceof DatagramChannel)) {
- LOGGER.info("[{}] connected", NetworkUtil.session(ctx.channel()));
- }
- }
-
- @Override
- public void channelInactive(ChannelHandlerContext ctx) {
- LOGGER.info("[{}] disconnected", NetworkUtil.session(ctx.channel()));
- closeChannel(ctx.channel());
-
- boolean supportsOffline = BasePipelineFactory.getHandler(ctx.pipeline(), HttpRequestDecoder.class) == null
- && !connectionlessProtocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName());
- connectionManager.deviceDisconnected(ctx.channel(), supportsOffline);
- }
-
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
- while (cause.getCause() != null && cause.getCause() != cause) {
- cause = cause.getCause();
- }
- LOGGER.info("[{}] error", NetworkUtil.session(ctx.channel()), cause);
- closeChannel(ctx.channel());
- }
-
- @Override
- public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
- if (evt instanceof IdleStateEvent) {
- LOGGER.info("[{}] timed out", NetworkUtil.session(ctx.channel()));
- closeChannel(ctx.channel());
- }
- }
-
- private void closeChannel(Channel channel) {
- if (!(channel instanceof DatagramChannel)) {
- channel.close();
- }
- }
-
-}
diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java
index 26654947e..791d61c61 100644
--- a/src/main/java/org/traccar/MainModule.java
+++ b/src/main/java/org/traccar/MainModule.java
@@ -72,9 +72,12 @@ import org.traccar.geolocation.GeolocationProvider;
import org.traccar.geolocation.GoogleGeolocationProvider;
import org.traccar.geolocation.OpenCellIdGeolocationProvider;
import org.traccar.geolocation.UnwiredGeolocationProvider;
+import org.traccar.handler.CopyAttributesHandler;
+import org.traccar.handler.FilterHandler;
import org.traccar.handler.GeocoderHandler;
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;
@@ -338,6 +341,34 @@ public class MainModule extends AbstractModule {
@Singleton
@Provides
+ public static CopyAttributesHandler provideCopyAttributesHandler(Config config, CacheManager cacheManager) {
+ if (config.getBoolean(Keys.PROCESSING_COPY_ATTRIBUTES_ENABLE)) {
+ return new CopyAttributesHandler(config, cacheManager);
+ }
+ return null;
+ }
+
+ @Singleton
+ @Provides
+ public static FilterHandler provideFilterHandler(
+ Config config, CacheManager cacheManager, Storage storage, StatisticsManager statisticsManager) {
+ if (config.getBoolean(Keys.FILTER_ENABLE)) {
+ return new FilterHandler(config, cacheManager, storage, statisticsManager);
+ }
+ return null;
+ }
+
+ @Singleton
+ @Provides
+ public static TimeHandler provideTimeHandler(Config config) {
+ if (config.hasKey(Keys.TIME_OVERRIDE)) {
+ return new TimeHandler(config);
+ }
+ return null;
+ }
+
+ @Singleton
+ @Provides
public static BroadcastService provideBroadcastService(
Config config, ObjectMapper objectMapper) throws IOException {
if (config.hasKey(Keys.BROADCAST_TYPE)) {
diff --git a/src/main/java/org/traccar/ProcessingHandler.java b/src/main/java/org/traccar/ProcessingHandler.java
new file mode 100644
index 000000000..fd048d127
--- /dev/null
+++ b/src/main/java/org/traccar/ProcessingHandler.java
@@ -0,0 +1,203 @@
+/*
+ * 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;
+
+import com.google.inject.Injector;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.traccar.config.Config;
+import org.traccar.database.BufferingManager;
+import org.traccar.database.NotificationManager;
+import org.traccar.handler.BasePositionHandler;
+import org.traccar.handler.ComputedAttributesHandler;
+import org.traccar.handler.CopyAttributesHandler;
+import org.traccar.handler.DatabaseHandler;
+import org.traccar.handler.DistanceHandler;
+import org.traccar.handler.EngineHoursHandler;
+import org.traccar.handler.FilterHandler;
+import org.traccar.handler.GeocoderHandler;
+import org.traccar.handler.GeofenceHandler;
+import org.traccar.handler.GeolocationHandler;
+import org.traccar.handler.HemisphereHandler;
+import org.traccar.handler.MotionHandler;
+import org.traccar.handler.OutdatedHandler;
+import org.traccar.handler.PositionForwardingHandler;
+import org.traccar.handler.PostProcessHandler;
+import org.traccar.handler.SpeedLimitHandler;
+import org.traccar.handler.TimeHandler;
+import org.traccar.handler.events.AlertEventHandler;
+import org.traccar.handler.events.BaseEventHandler;
+import org.traccar.handler.events.BehaviorEventHandler;
+import org.traccar.handler.events.CommandResultEventHandler;
+import org.traccar.handler.events.DriverEventHandler;
+import org.traccar.handler.events.FuelEventHandler;
+import org.traccar.handler.events.GeofenceEventHandler;
+import org.traccar.handler.events.IgnitionEventHandler;
+import org.traccar.handler.events.MaintenanceEventHandler;
+import org.traccar.handler.events.MediaEventHandler;
+import org.traccar.handler.events.MotionEventHandler;
+import org.traccar.handler.events.OverspeedEventHandler;
+import org.traccar.handler.network.AcknowledgementHandler;
+import org.traccar.helper.PositionLogger;
+import org.traccar.model.Position;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@Singleton
+@ChannelHandler.Sharable
+public class ProcessingHandler extends ChannelInboundHandlerAdapter implements BufferingManager.Callback {
+
+ private final NotificationManager notificationManager;
+ private final PositionLogger positionLogger;
+ private final BufferingManager bufferingManager;
+ private final List<BasePositionHandler> positionHandlers;
+ private final List<BaseEventHandler> eventHandlers;
+ private final PostProcessHandler postProcessHandler;
+
+ private final Map<Long, Queue<Position>> queues = new HashMap<>();
+
+ private synchronized Queue<Position> getQueue(long deviceId) {
+ return queues.computeIfAbsent(deviceId, k -> new LinkedList<>());
+ }
+
+ @Inject
+ public ProcessingHandler(
+ Injector injector, Config config, NotificationManager notificationManager, PositionLogger positionLogger) {
+ this.notificationManager = notificationManager;
+ this.positionLogger = positionLogger;
+ bufferingManager = new BufferingManager(config, this);
+
+ positionHandlers = Stream.of(
+ OutdatedHandler.class,
+ TimeHandler.class,
+ GeolocationHandler.class,
+ HemisphereHandler.class,
+ DistanceHandler.class,
+ FilterHandler.class,
+ GeofenceHandler.class,
+ GeocoderHandler.class,
+ SpeedLimitHandler.class,
+ MotionHandler.class,
+ EngineHoursHandler.class,
+ ComputedAttributesHandler.class,
+ CopyAttributesHandler.class,
+ PositionForwardingHandler.class,
+ DatabaseHandler.class)
+ .map((clazz) -> (BasePositionHandler) injector.getInstance(clazz))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toUnmodifiableList());
+
+ eventHandlers = Stream.of(
+ MediaEventHandler.class,
+ CommandResultEventHandler.class,
+ OverspeedEventHandler.class,
+ BehaviorEventHandler.class,
+ FuelEventHandler.class,
+ MotionEventHandler.class,
+ GeofenceEventHandler.class,
+ AlertEventHandler.class,
+ IgnitionEventHandler.class,
+ MaintenanceEventHandler.class,
+ DriverEventHandler.class)
+ .map((clazz) -> (BaseEventHandler) injector.getInstance(clazz))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toUnmodifiableList());
+
+ postProcessHandler = injector.getInstance(PostProcessHandler.class);
+ }
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ if (msg instanceof Position) {
+ bufferingManager.accept(ctx, (Position) msg);
+ } else {
+ super.channelRead(ctx, msg);
+ }
+ }
+
+ @Override
+ public void onReleased(ChannelHandlerContext context, Position position) {
+ Queue<Position> queue = getQueue(position.getDeviceId());
+ boolean queued;
+ synchronized (queue) {
+ queued = !queue.isEmpty();
+ queue.offer(position);
+ }
+ if (!queued) {
+ processPositionHandlers(context, position);
+ }
+ }
+
+ private void processPositionHandlers(ChannelHandlerContext ctx, Position position) {
+ var iterator = positionHandlers.iterator();
+ iterator.next().handlePosition(position, new BasePositionHandler.Callback() {
+ @Override
+ public void processed(boolean filtered) {
+ if (!filtered) {
+ if (iterator.hasNext()) {
+ iterator.next().handlePosition(position, this);
+ } else {
+ processEventHandlers(ctx, position);
+ }
+ } else {
+ finishedProcessing(ctx, position, true);
+ }
+ }
+ });
+ }
+
+ private void processEventHandlers(ChannelHandlerContext ctx, Position position) {
+ eventHandlers.forEach(handler -> handler.analyzePosition(
+ position, (event) -> notificationManager.updateEvents(Map.of(event, position))));
+ finishedProcessing(ctx, position, false);
+ }
+
+ private void finishedProcessing(ChannelHandlerContext ctx, Position position, boolean filtered) {
+ if (!filtered) {
+ postProcessHandler.handlePosition(position, ignore -> {
+ positionLogger.log(ctx, position);
+ ctx.writeAndFlush(new AcknowledgementHandler.EventHandled(position));
+ processNextPosition(ctx, position.getDeviceId());
+ });
+ } else {
+ ctx.writeAndFlush(new AcknowledgementHandler.EventHandled(position));
+ processNextPosition(ctx, position.getDeviceId());
+ }
+ }
+
+ private void processNextPosition(ChannelHandlerContext ctx, long deviceId) {
+ Queue<Position> queue = getQueue(deviceId);
+ Position nextPosition;
+ synchronized (queue) {
+ queue.poll(); // remove current position
+ nextPosition = queue.peek();
+ }
+ if (nextPosition != null) {
+ processPositionHandlers(ctx, nextPosition);
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/ServerManager.java b/src/main/java/org/traccar/ServerManager.java
index e91be50a2..22af66b41 100644
--- a/src/main/java/org/traccar/ServerManager.java
+++ b/src/main/java/org/traccar/ServerManager.java
@@ -82,12 +82,8 @@ public class ServerManager implements LifecycleObject {
@Override
public void stop() throws Exception {
- try {
- for (TrackerConnector connector : connectorList) {
- connector.stop();
- }
- } finally {
- GlobalTimer.release();
+ for (TrackerConnector connector : connectorList) {
+ connector.stop();
}
}
diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java
index 89bba7237..56253152f 100644
--- a/src/main/java/org/traccar/api/resource/DeviceResource.java
+++ b/src/main/java/org/traccar/api/resource/DeviceResource.java
@@ -62,6 +62,9 @@ import java.util.List;
@Consumes(MediaType.APPLICATION_JSON)
public class DeviceResource extends BaseObjectResource<Device> {
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+ private static final int IMAGE_SIZE_LIMIT = 500000;
+
@Inject
private Config config;
@@ -172,6 +175,23 @@ public class DeviceResource extends BaseObjectResource<Device> {
return Response.noContent().build();
}
+ private String imageExtension(String type) {
+ switch (type) {
+ case "image/jpeg":
+ return "jpg";
+ case "image/png":
+ return "png";
+ case "image/gif":
+ return "gif";
+ case "image/webp":
+ return "webp";
+ case "image/svg+xml":
+ return "svg";
+ default:
+ throw new IllegalArgumentException("Unsupported image type");
+ }
+ }
+
@Path("{id}/image")
@POST
@Consumes("image/*")
@@ -186,10 +206,20 @@ public class DeviceResource extends BaseObjectResource<Device> {
new Condition.Permission(User.class, getUserId(), Device.class))));
if (device != null) {
String name = "device";
- String extension = type.substring("image/".length());
+ String extension = imageExtension(type);
try (var input = new FileInputStream(file);
var output = mediaManager.createFileStream(device.getUniqueId(), name, extension)) {
- input.transferTo(output);
+
+ long transferred = 0;
+ byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+ int read;
+ while ((read = input.read(buffer, 0, buffer.length)) >= 0) {
+ output.write(buffer, 0, read);
+ transferred += read;
+ if (transferred > IMAGE_SIZE_LIMIT) {
+ throw new IllegalArgumentException("Image size limit exceeded");
+ }
+ }
}
return Response.ok(name + "." + extension).build();
}
diff --git a/src/main/java/org/traccar/api/resource/UserResource.java b/src/main/java/org/traccar/api/resource/UserResource.java
index 47ea9b07c..fbc31e46a 100644
--- a/src/main/java/org/traccar/api/resource/UserResource.java
+++ b/src/main/java/org/traccar/api/resource/UserResource.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.
@@ -95,7 +95,9 @@ public class UserResource extends BaseObjectResource<User> {
}
}
} else {
- if (!permissionsService.getServer().getRegistration()) {
+ if (UserUtil.isEmpty(storage)) {
+ entity.setAdministrator(true);
+ } else if (!permissionsService.getServer().getRegistration()) {
throw new SecurityException("Registration disabled");
}
if (permissionsService.getServer().getBoolean(Keys.WEB_TOTP_FORCE.getKey())
@@ -106,10 +108,6 @@ public class UserResource extends BaseObjectResource<User> {
}
}
- if (UserUtil.isEmpty(storage)) {
- entity.setAdministrator(true);
- }
-
entity.setId(storage.addObject(entity, new Request(new Columns.Exclude("id"))));
storage.updateObject(entity, new Request(
new Columns.Include("hashedPassword", "salt"),
diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java
index 4aacb2cd8..d346084bd 100644
--- a/src/main/java/org/traccar/config/Keys.java
+++ b/src/main/java/org/traccar/config/Keys.java
@@ -293,6 +293,14 @@ public final class Keys {
false);
/**
+ * If not zero, enable buffering of incoming data to handle ordering locations. The value is threshold for
+ * buffering in milliseconds.
+ */
+ public static final ConfigKey<Long> SERVER_BUFFERING_THRESHOLD = new LongConfigKey(
+ "server.buffering.threshold",
+ List.of(KeyType.CONFIG));
+
+ /**
* Server wide connection timeout value in seconds. See protocol timeout for more information.
*/
public static final ConfigKey<Integer> SERVER_TIMEOUT = new IntegerConfigKey(
diff --git a/src/main/java/org/traccar/database/BufferingManager.java b/src/main/java/org/traccar/database/BufferingManager.java
new file mode 100644
index 000000000..4d288c8d0
--- /dev/null
+++ b/src/main/java/org/traccar/database/BufferingManager.java
@@ -0,0 +1,115 @@
+/*
+ * 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.database;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.util.HashedWheelTimer;
+import io.netty.util.Timeout;
+import io.netty.util.Timer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+import org.traccar.model.Position;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
+
+public class BufferingManager {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(BufferingManager.class);
+
+ public interface Callback {
+ void onReleased(ChannelHandlerContext context, Position position);
+ }
+
+ private static final class Holder implements Comparable<Holder> {
+
+ private final ChannelHandlerContext context;
+ private final Position position;
+ private Timeout timeout;
+
+ private Holder(ChannelHandlerContext context, Position position) {
+ this.context = context;
+ this.position = position;
+ }
+
+ private int compareTime(Date left, Date right) {
+ if (left != null && right != null) {
+ return left.compareTo(right);
+ }
+ return 0;
+ }
+
+ @Override
+ public int compareTo(Holder other) {
+ int fixTimeResult = compareTime(position.getFixTime(), other.position.getFixTime());
+ if (fixTimeResult != 0) {
+ return fixTimeResult;
+ }
+
+ int deviceTimeResult = compareTime(position.getDeviceTime(), other.position.getDeviceTime());
+ if (deviceTimeResult != 0) {
+ return deviceTimeResult;
+ }
+
+ return position.getServerTime().compareTo(other.position.getServerTime());
+ }
+ }
+
+ private final Timer timer = new HashedWheelTimer();
+ private final Callback callback;
+ private final long threshold;
+
+ private final Map<Long, TreeSet<Holder>> buffer = new HashMap<>();
+
+ public BufferingManager(Config config, Callback callback) {
+ this.callback = callback;
+ threshold = config.getLong(Keys.SERVER_BUFFERING_THRESHOLD);
+ }
+
+ private Timeout scheduleTimeout(Holder holder) {
+ return timer.newTimeout(
+ timeout -> {
+ LOGGER.info("released {}", holder.position.getFixTime());
+ buffer.get(holder.position.getDeviceId()).remove(holder);
+ callback.onReleased(holder.context, holder.position);
+ },
+ threshold, TimeUnit.MILLISECONDS);
+ }
+
+ public void accept(ChannelHandlerContext context, Position position) {
+ if (threshold > 0) {
+ synchronized (buffer) {
+ LOGGER.info("queued {}", position.getFixTime());
+ var queue = buffer.computeIfAbsent(position.getDeviceId(), k -> new TreeSet<>());
+ Holder holder = new Holder(context, position);
+ holder.timeout = scheduleTimeout(holder);
+ queue.add(holder);
+ queue.tailSet(holder).forEach(h -> {
+ h.timeout.cancel();
+ h.timeout = scheduleTimeout(h);
+ });
+ }
+ } else {
+ callback.onReleased(context, position);
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/database/GroupTree.java b/src/main/java/org/traccar/database/GroupTree.java
deleted file mode 100644
index 8798f55bc..000000000
--- a/src/main/java/org/traccar/database/GroupTree.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright 2016 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.database;
-
-import org.traccar.model.Device;
-import org.traccar.model.Group;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-public class GroupTree {
-
- private static class TreeNode {
-
- private Group group;
- private Device device;
- private Collection<TreeNode> children = new HashSet<>();
-
- TreeNode(Group group) {
- this.group = group;
- }
-
- TreeNode(Device device) {
- this.device = device;
- }
-
- @Override
- public int hashCode() {
- if (group != null) {
- return (int) group.getId();
- } else {
- return (int) device.getId();
- }
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof TreeNode)) {
- return false;
- }
- TreeNode other = (TreeNode) obj;
- if (other == this) {
- return true;
- }
- if (group != null && other.group != null) {
- return group.getId() == other.group.getId();
- } else if (device != null && other.device != null) {
- return device.getId() == other.device.getId();
- }
- return false;
- }
-
- public Group getGroup() {
- return group;
- }
-
- public Device getDevice() {
- return device;
- }
-
- public void setParent(TreeNode parent) {
- if (parent != null) {
- parent.children.add(this);
- }
- }
-
- public Collection<TreeNode> getChildren() {
- return children;
- }
-
- }
-
- private final Map<Long, TreeNode> groupMap = new HashMap<>();
-
- public GroupTree(Collection<Group> groups, Collection<Device> devices) {
-
- for (Group group : groups) {
- groupMap.put(group.getId(), new TreeNode(group));
- }
-
- for (TreeNode node : groupMap.values()) {
- if (node.getGroup().getGroupId() != 0) {
- node.setParent(groupMap.get(node.getGroup().getGroupId()));
- }
- }
-
- Map<Long, TreeNode> deviceMap = new HashMap<>();
-
- for (Device device : devices) {
- deviceMap.put(device.getId(), new TreeNode(device));
- }
-
- for (TreeNode node : deviceMap.values()) {
- if (node.getDevice().getGroupId() != 0) {
- node.setParent(groupMap.get(node.getDevice().getGroupId()));
- }
- }
-
- }
-
- public Collection<Group> getGroups(long groupId) {
- Set<TreeNode> results = new HashSet<>();
- getNodes(results, groupMap.get(groupId));
- Collection<Group> groups = new ArrayList<>();
- for (TreeNode node : results) {
- if (node.getGroup() != null) {
- groups.add(node.getGroup());
- }
- }
- return groups;
- }
-
- public Collection<Device> getDevices(long groupId) {
- Set<TreeNode> results = new HashSet<>();
- getNodes(results, groupMap.get(groupId));
- Collection<Device> devices = new ArrayList<>();
- for (TreeNode node : results) {
- if (node.getDevice() != null) {
- devices.add(node.getDevice());
- }
- }
- return devices;
- }
-
- private void getNodes(Set<TreeNode> results, TreeNode node) {
- if (node != null) {
- for (TreeNode child : node.getChildren()) {
- results.add(child);
- getNodes(results, child);
- }
- }
- }
-
-}
diff --git a/src/main/java/org/traccar/GlobalTimer.java b/src/main/java/org/traccar/handler/BasePositionHandler.java
index a97321ba2..0a82e96b7 100644
--- a/src/main/java/org/traccar/GlobalTimer.java
+++ b/src/main/java/org/traccar/handler/BasePositionHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 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.
@@ -13,30 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar;
+package org.traccar.handler;
-import io.netty.util.HashedWheelTimer;
-import io.netty.util.Timer;
+import org.traccar.model.Position;
-public final class GlobalTimer {
+public abstract class BasePositionHandler {
- private static Timer instance = null;
-
- private GlobalTimer() {
- }
-
- public static void release() {
- if (instance != null) {
- instance.stop();
- }
- instance = null;
- }
-
- public static Timer getTimer() {
- if (instance == null) {
- instance = new HashedWheelTimer();
- }
- return instance;
+ public interface Callback {
+ void processed(boolean filtered);
}
+ public abstract void handlePosition(Position position, Callback callback);
}
diff --git a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java
index 8b010ceae..4293bd1fc 100644
--- a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java
+++ b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,26 +16,15 @@
*/
package org.traccar.handler;
-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.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.List;
-
-import io.netty.channel.ChannelHandler;
-import org.apache.commons.jexl3.JexlFeatures;
-import org.apache.commons.jexl3.JexlEngine;
+import jakarta.inject.Inject;
import org.apache.commons.jexl3.JexlBuilder;
-import org.apache.commons.jexl3.introspection.JexlSandbox;
+import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.MapContext;
+import org.apache.commons.jexl3.introspection.JexlSandbox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.traccar.BaseDataHandler;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Attribute;
@@ -43,12 +32,17 @@ import org.traccar.model.Device;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
+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.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
-@Singleton
-@ChannelHandler.Sharable
-public class ComputedAttributesHandler extends BaseDataHandler {
+public class ComputedAttributesHandler extends BasePositionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ComputedAttributesHandler.class);
@@ -144,7 +138,7 @@ public class ComputedAttributesHandler extends BaseDataHandler {
}
@Override
- protected Position handlePosition(Position position) {
+ public void handlePosition(Position position, Callback callback) {
Collection<Attribute> attributes = cacheManager.getDeviceObjects(position.getDeviceId(), Attribute.class);
for (Attribute attribute : attributes) {
if (attribute.getAttribute() != null) {
@@ -202,7 +196,7 @@ public class ComputedAttributesHandler extends BaseDataHandler {
}
}
}
- return position;
+ callback.processed(false);
}
}
diff --git a/src/main/java/org/traccar/handler/CopyAttributesHandler.java b/src/main/java/org/traccar/handler/CopyAttributesHandler.java
index 42b438e41..c7452e58c 100644
--- a/src/main/java/org/traccar/handler/CopyAttributesHandler.java
+++ b/src/main/java/org/traccar/handler/CopyAttributesHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 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");
@@ -16,45 +16,35 @@
*/
package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
-import org.traccar.BaseDataHandler;
+import jakarta.inject.Inject;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.helper.model.AttributeUtil;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-
-@Singleton
-@ChannelHandler.Sharable
-public class CopyAttributesHandler extends BaseDataHandler {
+public class CopyAttributesHandler extends BasePositionHandler {
- private final boolean enabled;
private final CacheManager cacheManager;
@Inject
public CopyAttributesHandler(Config config, CacheManager cacheManager) {
- enabled = config.getBoolean(Keys.PROCESSING_COPY_ATTRIBUTES_ENABLE);
this.cacheManager = cacheManager;
}
@Override
- protected Position handlePosition(Position position) {
- if (enabled) {
- String attributesString = AttributeUtil.lookup(
- cacheManager, Keys.PROCESSING_COPY_ATTRIBUTES, position.getDeviceId());
- Position last = cacheManager.getPosition(position.getDeviceId());
- if (last != null && attributesString != null) {
- for (String attribute : attributesString.split("[ ,]")) {
- if (last.hasAttribute(attribute) && !position.hasAttribute(attribute)) {
- position.getAttributes().put(attribute, last.getAttributes().get(attribute));
- }
+ public void handlePosition(Position position, Callback callback) {
+ String attributesString = AttributeUtil.lookup(
+ cacheManager, Keys.PROCESSING_COPY_ATTRIBUTES, position.getDeviceId());
+ Position last = cacheManager.getPosition(position.getDeviceId());
+ if (last != null && attributesString != null) {
+ for (String attribute : attributesString.split("[ ,]")) {
+ if (last.hasAttribute(attribute) && !position.hasAttribute(attribute)) {
+ position.getAttributes().put(attribute, last.getAttributes().get(attribute));
}
}
}
- return position;
+ callback.processed(false);
}
}
diff --git a/src/main/java/org/traccar/handler/DefaultDataHandler.java b/src/main/java/org/traccar/handler/DatabaseHandler.java
index cca6dcd0a..5d96ebb34 100644
--- a/src/main/java/org/traccar/handler/DefaultDataHandler.java
+++ b/src/main/java/org/traccar/handler/DatabaseHandler.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.
@@ -15,41 +15,39 @@
*/
package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.traccar.BaseDataHandler;
+import org.traccar.database.StatisticsManager;
import org.traccar.model.Position;
import org.traccar.storage.Storage;
import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Request;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-
-@Singleton
-@ChannelHandler.Sharable
-public class DefaultDataHandler extends BaseDataHandler {
+public class DatabaseHandler extends BasePositionHandler {
- private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataHandler.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseHandler.class);
private final Storage storage;
+ private final StatisticsManager statisticsManager;
@Inject
- public DefaultDataHandler(Storage storage) {
+ public DatabaseHandler(Storage storage, StatisticsManager statisticsManager) {
this.storage = storage;
+ this.statisticsManager = statisticsManager;
}
@Override
- protected Position handlePosition(Position position) {
+ public void handlePosition(Position position, Callback callback) {
try {
position.setId(storage.addObject(position, new Request(new Columns.Exclude("id"))));
+ statisticsManager.messageStoredCount(position.getDeviceId());
} catch (Exception error) {
LOGGER.warn("Failed to store position", error);
}
- return position;
+ callback.processed(false);
}
}
diff --git a/src/main/java/org/traccar/handler/DistanceHandler.java b/src/main/java/org/traccar/handler/DistanceHandler.java
index db8c73779..e8ae7753a 100644
--- a/src/main/java/org/traccar/handler/DistanceHandler.java
+++ b/src/main/java/org/traccar/handler/DistanceHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2015 Amila Silva
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,19 +16,14 @@
*/
package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import org.traccar.BaseDataHandler;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.helper.DistanceCalculator;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-@Singleton
-@ChannelHandler.Sharable
-public class DistanceHandler extends BaseDataHandler {
+public class DistanceHandler extends BasePositionHandler {
private final CacheManager cacheManager;
@@ -45,7 +40,7 @@ public class DistanceHandler extends BaseDataHandler {
}
@Override
- protected Position handlePosition(Position position) {
+ public void handlePosition(Position position, Callback callback) {
double distance = 0.0;
if (position.hasAttribute(Position.KEY_DISTANCE)) {
@@ -76,7 +71,7 @@ public class DistanceHandler extends BaseDataHandler {
position.set(Position.KEY_DISTANCE, distance);
position.set(Position.KEY_TOTAL_DISTANCE, totalDistance + distance);
- return position;
+ callback.processed(false);
}
}
diff --git a/src/main/java/org/traccar/handler/EngineHoursHandler.java b/src/main/java/org/traccar/handler/EngineHoursHandler.java
index 621205b34..5aae6f673 100644
--- a/src/main/java/org/traccar/handler/EngineHoursHandler.java
+++ b/src/main/java/org/traccar/handler/EngineHoursHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,17 +16,11 @@
*/
package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
-import org.traccar.BaseDataHandler;
+import jakarta.inject.Inject;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-
-@Singleton
-@ChannelHandler.Sharable
-public class EngineHoursHandler extends BaseDataHandler {
+public class EngineHoursHandler extends BasePositionHandler {
private final CacheManager cacheManager;
@@ -36,7 +30,7 @@ public class EngineHoursHandler extends BaseDataHandler {
}
@Override
- protected Position handlePosition(Position position) {
+ public void handlePosition(Position position, Callback callback) {
if (!position.hasAttribute(Position.KEY_HOURS)) {
Position last = cacheManager.getPosition(position.getDeviceId());
if (last != null) {
@@ -49,7 +43,7 @@ public class EngineHoursHandler extends BaseDataHandler {
}
}
}
- return position;
+ callback.processed(false);
}
}
diff --git a/src/main/java/org/traccar/handler/FilterHandler.java b/src/main/java/org/traccar/handler/FilterHandler.java
index a15d3ffad..796c302fb 100644
--- a/src/main/java/org/traccar/handler/FilterHandler.java
+++ b/src/main/java/org/traccar/handler/FilterHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2014 - 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,7 @@
*/
package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInboundHandlerAdapter;
+import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
@@ -36,17 +34,12 @@ import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Order;
import org.traccar.storage.query.Request;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
import java.util.Date;
-@Singleton
-@ChannelHandler.Sharable
-public class FilterHandler extends ChannelInboundHandlerAdapter {
+public class FilterHandler extends BasePositionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(FilterHandler.class);
- private final boolean enabled;
private final boolean filterInvalid;
private final boolean filterZero;
private final boolean filterDuplicate;
@@ -71,7 +64,6 @@ public class FilterHandler extends ChannelInboundHandlerAdapter {
@Inject
public FilterHandler(
Config config, CacheManager cacheManager, Storage storage, StatisticsManager statisticsManager) {
- enabled = config.getBoolean(Keys.FILTER_ENABLE);
filterInvalid = config.getBoolean(Keys.FILTER_INVALID);
filterZero = config.getBoolean(Keys.FILTER_ZERO);
filterDuplicate = config.getBoolean(Keys.FILTER_DUPLICATE);
@@ -277,17 +269,8 @@ public class FilterHandler extends ChannelInboundHandlerAdapter {
}
@Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- if (msg instanceof Position) {
- Position position = (Position) msg;
- if (enabled && filter(position)) {
- ctx.writeAndFlush(new AcknowledgementHandler.EventHandled(position));
- } else {
- ctx.fireChannelRead(position);
- }
- } else {
- super.channelRead(ctx, msg);
- }
+ public void handlePosition(Position position, Callback callback) {
+ callback.processed(filter(position));
}
}
diff --git a/src/main/java/org/traccar/handler/GeocoderHandler.java b/src/main/java/org/traccar/handler/GeocoderHandler.java
index e4f240a90..b84237856 100644
--- a/src/main/java/org/traccar/handler/GeocoderHandler.java
+++ b/src/main/java/org/traccar/handler/GeocoderHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2021 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.
@@ -15,9 +15,6 @@
*/
package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
@@ -26,8 +23,7 @@ import org.traccar.geocoder.Geocoder;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-@ChannelHandler.Sharable
-public class GeocoderHandler extends ChannelInboundHandlerAdapter {
+public class GeocoderHandler extends BasePositionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GeocoderHandler.class);
@@ -46,39 +42,38 @@ public class GeocoderHandler extends ChannelInboundHandlerAdapter {
}
@Override
- public void channelRead(final ChannelHandlerContext ctx, Object message) {
- if (message instanceof Position && !ignorePositions) {
- final Position position = (Position) message;
- if (processInvalidPositions || position.getValid()) {
- if (reuseDistance != 0) {
- Position lastPosition = cacheManager.getPosition(position.getDeviceId());
- if (lastPosition != null && lastPosition.getAddress() != null
- && position.getDouble(Position.KEY_DISTANCE) <= reuseDistance) {
- position.setAddress(lastPosition.getAddress());
- ctx.fireChannelRead(position);
- return;
- }
+ public void handlePosition(Position position, Callback callback) {
+ if (!ignorePositions) {
+ callback.processed(false);
+ }
+
+ if (processInvalidPositions || position.getValid()) {
+ if (reuseDistance != 0) {
+ Position lastPosition = cacheManager.getPosition(position.getDeviceId());
+ if (lastPosition != null && lastPosition.getAddress() != null
+ && position.getDouble(Position.KEY_DISTANCE) <= reuseDistance) {
+ position.setAddress(lastPosition.getAddress());
+ callback.processed(false);
+ return;
}
+ }
- geocoder.getAddress(position.getLatitude(), position.getLongitude(),
- new Geocoder.ReverseGeocoderCallback() {
- @Override
- public void onSuccess(String address) {
- position.setAddress(address);
- ctx.fireChannelRead(position);
- }
+ geocoder.getAddress(position.getLatitude(), position.getLongitude(),
+ new Geocoder.ReverseGeocoderCallback() {
+ @Override
+ public void onSuccess(String address) {
+ position.setAddress(address);
+ callback.processed(false);
+ }
- @Override
- public void onFailure(Throwable e) {
- LOGGER.warn("Geocoding failed", e);
- ctx.fireChannelRead(position);
- }
- });
- } else {
- ctx.fireChannelRead(position);
- }
+ @Override
+ public void onFailure(Throwable e) {
+ LOGGER.warn("Geocoding failed", e);
+ callback.processed(false);
+ }
+ });
} else {
- ctx.fireChannelRead(message);
+ callback.processed(false);
}
}
diff --git a/src/main/java/org/traccar/handler/GeofenceHandler.java b/src/main/java/org/traccar/handler/GeofenceHandler.java
index 68bc6dbf0..8b363057a 100644
--- a/src/main/java/org/traccar/handler/GeofenceHandler.java
+++ b/src/main/java/org/traccar/handler/GeofenceHandler.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.
@@ -15,20 +15,15 @@
*/
package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
-import org.traccar.BaseDataHandler;
+import jakarta.inject.Inject;
import org.traccar.config.Config;
import org.traccar.helper.model.GeofenceUtil;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
import java.util.List;
-@Singleton
-@ChannelHandler.Sharable
-public class GeofenceHandler extends BaseDataHandler {
+public class GeofenceHandler extends BasePositionHandler {
private final Config config;
private final CacheManager cacheManager;
@@ -40,13 +35,13 @@ public class GeofenceHandler extends BaseDataHandler {
}
@Override
- protected Position handlePosition(Position position) {
+ public void handlePosition(Position position, Callback callback) {
List<Long> geofenceIds = GeofenceUtil.getCurrentGeofences(config, cacheManager, position);
if (!geofenceIds.isEmpty()) {
position.setGeofenceIds(geofenceIds);
}
- return position;
+ callback.processed(false);
}
}
diff --git a/src/main/java/org/traccar/handler/GeolocationHandler.java b/src/main/java/org/traccar/handler/GeolocationHandler.java
index a54ea03e3..c46bd3250 100644
--- a/src/main/java/org/traccar/handler/GeolocationHandler.java
+++ b/src/main/java/org/traccar/handler/GeolocationHandler.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.
@@ -15,9 +15,6 @@
*/
package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
@@ -27,8 +24,7 @@ import org.traccar.geolocation.GeolocationProvider;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-@ChannelHandler.Sharable
-public class GeolocationHandler extends ChannelInboundHandlerAdapter {
+public class GeolocationHandler extends BasePositionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GeolocationHandler.class);
@@ -51,46 +47,41 @@ public class GeolocationHandler extends ChannelInboundHandlerAdapter {
}
@Override
- public void channelRead(final ChannelHandlerContext ctx, Object message) {
- if (message instanceof Position) {
- final Position position = (Position) message;
- if ((position.getOutdated() || processInvalidPositions && !position.getValid())
- && position.getNetwork() != null
- && (!requireWifi || position.getNetwork().getWifiAccessPoints() != null)) {
- if (reuse) {
- Position lastPosition = cacheManager.getPosition(position.getDeviceId());
- if (lastPosition != null && position.getNetwork().equals(lastPosition.getNetwork())) {
- updatePosition(
- position, lastPosition.getLatitude(), lastPosition.getLongitude(),
- lastPosition.getAccuracy());
- ctx.fireChannelRead(position);
- return;
- }
+ public void handlePosition(Position position, Callback callback) {
+ if ((position.getOutdated() || processInvalidPositions && !position.getValid())
+ && position.getNetwork() != null
+ && (!requireWifi || position.getNetwork().getWifiAccessPoints() != null)) {
+ if (reuse) {
+ Position lastPosition = cacheManager.getPosition(position.getDeviceId());
+ if (lastPosition != null && position.getNetwork().equals(lastPosition.getNetwork())) {
+ updatePosition(
+ position, lastPosition.getLatitude(), lastPosition.getLongitude(),
+ lastPosition.getAccuracy());
+ callback.processed(false);
+ return;
}
+ }
- if (statisticsManager != null) {
- statisticsManager.registerGeolocationRequest();
- }
+ if (statisticsManager != null) {
+ statisticsManager.registerGeolocationRequest();
+ }
- geolocationProvider.getLocation(position.getNetwork(),
- new GeolocationProvider.LocationProviderCallback() {
- @Override
- public void onSuccess(double latitude, double longitude, double accuracy) {
- updatePosition(position, latitude, longitude, accuracy);
- ctx.fireChannelRead(position);
- }
+ geolocationProvider.getLocation(position.getNetwork(),
+ new GeolocationProvider.LocationProviderCallback() {
+ @Override
+ public void onSuccess(double latitude, double longitude, double accuracy) {
+ updatePosition(position, latitude, longitude, accuracy);
+ callback.processed(false);
+ }
- @Override
- public void onFailure(Throwable e) {
- LOGGER.warn("Geolocation network error", e);
- ctx.fireChannelRead(position);
- }
- });
- } else {
- ctx.fireChannelRead(position);
- }
+ @Override
+ public void onFailure(Throwable e) {
+ LOGGER.warn("Geolocation network error", e);
+ callback.processed(false);
+ }
+ });
} else {
- ctx.fireChannelRead(message);
+ callback.processed(false);
}
}
diff --git a/src/main/java/org/traccar/handler/HemisphereHandler.java b/src/main/java/org/traccar/handler/HemisphereHandler.java
index 294e449db..48929538f 100644
--- a/src/main/java/org/traccar/handler/HemisphereHandler.java
+++ b/src/main/java/org/traccar/handler/HemisphereHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 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.
@@ -15,18 +15,12 @@
*/
package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
-import org.traccar.BaseDataHandler;
+import jakarta.inject.Inject;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Position;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-
-@Singleton
-@ChannelHandler.Sharable
-public class HemisphereHandler extends BaseDataHandler {
+public class HemisphereHandler extends BasePositionHandler {
private int latitudeFactor;
private int longitudeFactor;
@@ -52,14 +46,14 @@ public class HemisphereHandler extends BaseDataHandler {
}
@Override
- protected Position handlePosition(Position position) {
+ public void handlePosition(Position position, Callback callback) {
if (latitudeFactor != 0) {
position.setLatitude(Math.abs(position.getLatitude()) * latitudeFactor);
}
if (longitudeFactor != 0) {
position.setLongitude(Math.abs(position.getLongitude()) * longitudeFactor);
}
- return position;
+ callback.processed(false);
}
}
diff --git a/src/main/java/org/traccar/handler/MotionHandler.java b/src/main/java/org/traccar/handler/MotionHandler.java
index 68a31a16a..804ffdc25 100644
--- a/src/main/java/org/traccar/handler/MotionHandler.java
+++ b/src/main/java/org/traccar/handler/MotionHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,19 +16,13 @@
*/
package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
-import org.traccar.BaseDataHandler;
+import jakarta.inject.Inject;
import org.traccar.config.Keys;
import org.traccar.helper.model.AttributeUtil;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-
-@Singleton
-@ChannelHandler.Sharable
-public class MotionHandler extends BaseDataHandler {
+public class MotionHandler extends BasePositionHandler {
private final CacheManager cacheManager;
@@ -38,13 +32,13 @@ public class MotionHandler extends BaseDataHandler {
}
@Override
- protected Position handlePosition(Position position) {
+ public void handlePosition(Position position, Callback callback) {
if (!position.hasAttribute(Position.KEY_MOTION)) {
double threshold = AttributeUtil.lookup(
cacheManager, Keys.EVENT_MOTION_SPEED_THRESHOLD, position.getDeviceId());
position.set(Position.KEY_MOTION, position.getSpeed() > threshold);
}
- return position;
+ callback.processed(false);
}
}
diff --git a/src/main/java/org/traccar/handler/OutdatedHandler.java b/src/main/java/org/traccar/handler/OutdatedHandler.java
new file mode 100644
index 000000000..536440f66
--- /dev/null
+++ b/src/main/java/org/traccar/handler/OutdatedHandler.java
@@ -0,0 +1,56 @@
+/*
+ * 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.handler;
+
+import jakarta.inject.Inject;
+import org.traccar.model.Position;
+import org.traccar.session.cache.CacheManager;
+
+import java.util.Date;
+
+public class OutdatedHandler extends BasePositionHandler {
+
+ private final CacheManager cacheManager;
+
+ @Inject
+ public OutdatedHandler(CacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+ }
+
+ @Override
+ public void handlePosition(Position position, Callback callback) {
+ if (position.getOutdated()) {
+ Position last = cacheManager.getPosition(position.getDeviceId());
+ if (last != null) {
+ position.setFixTime(last.getFixTime());
+ position.setValid(last.getValid());
+ position.setLatitude(last.getLatitude());
+ position.setLongitude(last.getLongitude());
+ position.setAltitude(last.getAltitude());
+ position.setSpeed(last.getSpeed());
+ position.setCourse(last.getCourse());
+ position.setAccuracy(last.getAccuracy());
+ } else {
+ position.setFixTime(new Date(315964819000L)); // gps epoch 1980-01-06
+ }
+ if (position.getDeviceTime() == null) {
+ position.setDeviceTime(position.getServerTime());
+ }
+ }
+ callback.processed(false);
+ }
+
+}
diff --git a/src/main/java/org/traccar/PositionForwardingHandler.java b/src/main/java/org/traccar/handler/PositionForwardingHandler.java
index a79b01367..8512d4552 100644
--- a/src/main/java/org/traccar/PositionForwardingHandler.java
+++ b/src/main/java/org/traccar/handler/PositionForwardingHandler.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.
@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar;
+package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.TimerTask;
+import jakarta.annotation.Nullable;
+import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
@@ -30,15 +31,10 @@ import org.traccar.model.Device;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.annotation.Nullable;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-@Singleton
-@ChannelHandler.Sharable
-public class PositionForwardingHandler extends BaseDataHandler {
+public class PositionForwardingHandler extends BasePositionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(PositionForwardingHandler.class);
@@ -128,14 +124,14 @@ public class PositionForwardingHandler extends BaseDataHandler {
}
@Override
- protected Position handlePosition(Position position) {
+ public void handlePosition(Position position, Callback callback) {
if (positionForwarder != null) {
PositionData positionData = new PositionData();
positionData.setPosition(position);
positionData.setDevice(cacheManager.getObject(Device.class, position.getDeviceId()));
new AsyncRequestAndCallback(positionData).send();
}
- return position;
+ callback.processed(false);
}
}
diff --git a/src/main/java/org/traccar/handler/PostProcessHandler.java b/src/main/java/org/traccar/handler/PostProcessHandler.java
new file mode 100644
index 000000000..5b1b2ef86
--- /dev/null
+++ b/src/main/java/org/traccar/handler/PostProcessHandler.java
@@ -0,0 +1,67 @@
+/*
+ * 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.handler;
+
+import jakarta.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.helper.model.PositionUtil;
+import org.traccar.model.Device;
+import org.traccar.model.Position;
+import org.traccar.session.ConnectionManager;
+import org.traccar.session.cache.CacheManager;
+import org.traccar.storage.Storage;
+import org.traccar.storage.StorageException;
+import org.traccar.storage.query.Columns;
+import org.traccar.storage.query.Condition;
+import org.traccar.storage.query.Request;
+
+public class PostProcessHandler extends BasePositionHandler {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(PostProcessHandler.class);
+
+ private final CacheManager cacheManager;
+ private final Storage storage;
+ private final ConnectionManager connectionManager;
+
+ @Inject
+ public PostProcessHandler(CacheManager cacheManager, Storage storage, ConnectionManager connectionManager) {
+ this.cacheManager = cacheManager;
+ this.storage = storage;
+ this.connectionManager = connectionManager;
+ }
+
+ @Override
+ public void handlePosition(Position position, Callback callback) {
+ try {
+ if (PositionUtil.isLatest(cacheManager, position)) {
+ Device updatedDevice = new Device();
+ updatedDevice.setId(position.getDeviceId());
+ updatedDevice.setPositionId(position.getId());
+ storage.updateObject(updatedDevice, new Request(
+ new Columns.Include("positionId"),
+ new Condition.Equals("id", updatedDevice.getId())));
+
+ cacheManager.updatePosition(position);
+ connectionManager.updatePosition(true, position);
+ }
+ } catch (StorageException error) {
+ LOGGER.warn("Failed to update device", error);
+ }
+ callback.processed(false);
+ }
+
+}
diff --git a/src/main/java/org/traccar/handler/SpeedLimitHandler.java b/src/main/java/org/traccar/handler/SpeedLimitHandler.java
index 6edb6e912..4c0922d01 100644
--- a/src/main/java/org/traccar/handler/SpeedLimitHandler.java
+++ b/src/main/java/org/traccar/handler/SpeedLimitHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2020 - 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,20 +15,13 @@
*/
package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInboundHandlerAdapter;
+import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.model.Position;
import org.traccar.speedlimit.SpeedLimitProvider;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-
-@Singleton
-@ChannelHandler.Sharable
-public class SpeedLimitHandler extends ChannelInboundHandlerAdapter {
+public class SpeedLimitHandler extends BasePositionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(SpeedLimitHandler.class);
@@ -40,26 +33,22 @@ public class SpeedLimitHandler extends ChannelInboundHandlerAdapter {
}
@Override
- public void channelRead(final ChannelHandlerContext ctx, Object message) {
- if (message instanceof Position) {
- final Position position = (Position) message;
- speedLimitProvider.getSpeedLimit(position.getLatitude(), position.getLongitude(),
- new SpeedLimitProvider.SpeedLimitProviderCallback() {
- @Override
- public void onSuccess(double speedLimit) {
- position.set(Position.KEY_SPEED_LIMIT, speedLimit);
- ctx.fireChannelRead(position);
- }
-
- @Override
- public void onFailure(Throwable e) {
- LOGGER.warn("Speed limit provider failed", e);
- ctx.fireChannelRead(position);
- }
- });
- } else {
- ctx.fireChannelRead(message);
- }
+ public void handlePosition(Position position, Callback callback) {
+
+ speedLimitProvider.getSpeedLimit(position.getLatitude(), position.getLongitude(),
+ new SpeedLimitProvider.SpeedLimitProviderCallback() {
+ @Override
+ public void onSuccess(double speedLimit) {
+ position.set(Position.KEY_SPEED_LIMIT, speedLimit);
+ callback.processed(false);
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ LOGGER.warn("Speed limit provider failed", e);
+ callback.processed(false);
+ }
+ });
}
}
diff --git a/src/main/java/org/traccar/handler/TimeHandler.java b/src/main/java/org/traccar/handler/TimeHandler.java
index 3c3e17450..f6c67bb76 100644
--- a/src/main/java/org/traccar/handler/TimeHandler.java
+++ b/src/main/java/org/traccar/handler/TimeHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 - 2022 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.
@@ -15,36 +15,23 @@
*/
package org.traccar.handler;
-import io.netty.channel.ChannelHandler;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInboundHandlerAdapter;
-import org.traccar.BaseProtocolDecoder;
+import jakarta.inject.Inject;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Position;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
-@Singleton
-@ChannelHandler.Sharable
-public class TimeHandler extends ChannelInboundHandlerAdapter {
+public class TimeHandler extends BasePositionHandler {
- private final boolean enabled;
private final boolean useServerTime;
private final Set<String> protocols;
@Inject
public TimeHandler(Config config) {
- enabled = config.hasKey(Keys.TIME_OVERRIDE);
- if (enabled) {
- useServerTime = config.getString(Keys.TIME_OVERRIDE).equalsIgnoreCase("serverTime");
- } else {
- useServerTime = false;
- }
+ useServerTime = config.getString(Keys.TIME_OVERRIDE).equalsIgnoreCase("serverTime");
String protocolList = config.getString(Keys.TIME_PROTOCOLS);
if (protocolList != null) {
protocols = new HashSet<>(Arrays.asList(protocolList.split("[, ]")));
@@ -54,21 +41,17 @@ public class TimeHandler extends ChannelInboundHandlerAdapter {
}
@Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) {
-
- if (enabled && msg instanceof Position && (protocols == null
- || protocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName()))) {
+ public void handlePosition(Position position, Callback callback) {
- Position position = (Position) msg;
+ if (protocols == null || protocols.contains(position.getProtocol())) {
if (useServerTime) {
position.setDeviceTime(position.getServerTime());
position.setFixTime(position.getServerTime());
} else {
position.setFixTime(position.getDeviceTime());
}
-
}
- ctx.fireChannelRead(msg);
+ callback.processed(false);
}
}
diff --git a/src/main/java/org/traccar/handler/events/AlertEventHandler.java b/src/main/java/org/traccar/handler/events/AlertEventHandler.java
index 531a0f957..ca580b60d 100644
--- a/src/main/java/org/traccar/handler/events/AlertEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/AlertEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 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.
@@ -15,21 +15,13 @@
*/
package org.traccar.handler.events;
-import java.util.Collections;
-import java.util.Map;
-
-import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-
-@Singleton
-@ChannelHandler.Sharable
public class AlertEventHandler extends BaseEventHandler {
private final CacheManager cacheManager;
@@ -42,7 +34,7 @@ public class AlertEventHandler extends BaseEventHandler {
}
@Override
- protected Map<Event, Position> analyzePosition(Position position) {
+ public void analyzePosition(Position position, Callback callback) {
Object alarm = position.getAttributes().get(Position.KEY_ALARM);
if (alarm != null) {
boolean ignoreAlert = false;
@@ -55,10 +47,9 @@ public class AlertEventHandler extends BaseEventHandler {
if (!ignoreAlert) {
Event event = new Event(Event.TYPE_ALARM, position);
event.set(Position.KEY_ALARM, (String) alarm);
- return Collections.singletonMap(event, position);
+ callback.eventDetected(event);
}
}
- return null;
}
}
diff --git a/src/main/java/org/traccar/handler/events/BaseEventHandler.java b/src/main/java/org/traccar/handler/events/BaseEventHandler.java
index 4a4fb40ff..009c83145 100644
--- a/src/main/java/org/traccar/handler/events/BaseEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/BaseEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 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.
@@ -15,33 +15,17 @@
*/
package org.traccar.handler.events;
-import java.util.Map;
-
-import org.traccar.BaseDataHandler;
-import org.traccar.database.NotificationManager;
import org.traccar.model.Event;
import org.traccar.model.Position;
-import jakarta.inject.Inject;
-
-public abstract class BaseEventHandler extends BaseDataHandler {
-
- private NotificationManager notificationManager;
+public abstract class BaseEventHandler {
- @Inject
- public void setNotificationManager(NotificationManager notificationManager) {
- this.notificationManager = notificationManager;
+ public interface Callback {
+ void eventDetected(Event event);
}
- @Override
- protected Position handlePosition(Position position) {
- Map<Event, Position> events = analyzePosition(position);
- if (events != null && !events.isEmpty()) {
- notificationManager.updateEvents(events);
- }
- return position;
- }
-
- protected abstract Map<Event, Position> analyzePosition(Position position);
-
+ /**
+ * Event handlers should be processed synchronously.
+ */
+ public abstract void analyzePosition(Position position, Callback callback);
}
diff --git a/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java b/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java
index 08ae35fcd..d654e18ce 100644
--- a/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2021 - 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,7 +15,7 @@
*/
package org.traccar.handler.events;
-import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.helper.UnitsConverter;
@@ -23,13 +23,6 @@ import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import java.util.Collections;
-import java.util.Map;
-
-@Singleton
-@ChannelHandler.Sharable
public class BehaviorEventHandler extends BaseEventHandler {
private final double accelerationThreshold;
@@ -45,7 +38,7 @@ public class BehaviorEventHandler extends BaseEventHandler {
}
@Override
- protected Map<Event, Position> analyzePosition(Position position) {
+ public void analyzePosition(Position position, Callback callback) {
Position lastPosition = cacheManager.getPosition(position.getDeviceId());
if (lastPosition != null && position.getFixTime().equals(lastPosition.getFixTime())) {
@@ -54,14 +47,13 @@ public class BehaviorEventHandler extends BaseEventHandler {
if (accelerationThreshold != 0 && acceleration >= accelerationThreshold) {
Event event = new Event(Event.TYPE_ALARM, position);
event.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION);
- return Collections.singletonMap(event, position);
+ callback.eventDetected(event);
} else if (brakingThreshold != 0 && acceleration <= -brakingThreshold) {
Event event = new Event(Event.TYPE_ALARM, position);
event.set(Position.KEY_ALARM, Position.ALARM_BRAKING);
- return Collections.singletonMap(event, position);
+ callback.eventDetected(event);
}
}
- return null;
}
}
diff --git a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java
index b70f8f33b..b98807b23 100644
--- a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 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.
@@ -15,18 +15,10 @@
*/
package org.traccar.handler.events;
-import java.util.Collections;
-import java.util.Map;
-
-import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
import org.traccar.model.Event;
import org.traccar.model.Position;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-
-@Singleton
-@ChannelHandler.Sharable
public class CommandResultEventHandler extends BaseEventHandler {
@Inject
@@ -34,14 +26,13 @@ public class CommandResultEventHandler extends BaseEventHandler {
}
@Override
- protected Map<Event, Position> analyzePosition(Position position) {
+ public void analyzePosition(Position position, Callback callback) {
Object commandResult = position.getAttributes().get(Position.KEY_RESULT);
if (commandResult != null) {
Event event = new Event(Event.TYPE_COMMAND_RESULT, position);
event.set(Position.KEY_RESULT, (String) commandResult);
- return Collections.singletonMap(event, position);
+ callback.eventDetected(event);
}
- return null;
}
}
diff --git a/src/main/java/org/traccar/handler/events/DriverEventHandler.java b/src/main/java/org/traccar/handler/events/DriverEventHandler.java
index b68327983..31f8d2b4b 100644
--- a/src/main/java/org/traccar/handler/events/DriverEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/DriverEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,19 +16,12 @@
*/
package org.traccar.handler.events;
-import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
import org.traccar.helper.model.PositionUtil;
import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import java.util.Collections;
-import java.util.Map;
-
-@Singleton
-@ChannelHandler.Sharable
public class DriverEventHandler extends BaseEventHandler {
private final CacheManager cacheManager;
@@ -39,9 +32,9 @@ public class DriverEventHandler extends BaseEventHandler {
}
@Override
- protected Map<Event, Position> analyzePosition(Position position) {
+ public void analyzePosition(Position position, Callback callback) {
if (!PositionUtil.isLatest(cacheManager, position)) {
- return null;
+ return;
}
String driverUniqueId = position.getString(Position.KEY_DRIVER_UNIQUE_ID);
if (driverUniqueId != null) {
@@ -53,10 +46,9 @@ public class DriverEventHandler extends BaseEventHandler {
if (!driverUniqueId.equals(oldDriverUniqueId)) {
Event event = new Event(Event.TYPE_DRIVER_CHANGED, position);
event.set(Position.KEY_DRIVER_UNIQUE_ID, driverUniqueId);
- return Collections.singletonMap(event, position);
+ callback.eventDetected(event);
}
}
- return null;
}
}
diff --git a/src/main/java/org/traccar/handler/events/FuelEventHandler.java b/src/main/java/org/traccar/handler/events/FuelEventHandler.java
index e5085ecc2..c5675f51d 100644
--- a/src/main/java/org/traccar/handler/events/FuelEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/FuelEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 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,7 +15,7 @@
*/
package org.traccar.handler.events;
-import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
import org.traccar.config.Keys;
import org.traccar.helper.model.AttributeUtil;
import org.traccar.helper.model.PositionUtil;
@@ -24,12 +24,6 @@ import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import java.util.Map;
-
-@Singleton
-@ChannelHandler.Sharable
public class FuelEventHandler extends BaseEventHandler {
private final CacheManager cacheManager;
@@ -40,14 +34,14 @@ public class FuelEventHandler extends BaseEventHandler {
}
@Override
- protected Map<Event, Position> analyzePosition(Position position) {
+ public void analyzePosition(Position position, Callback callback) {
Device device = cacheManager.getObject(Device.class, position.getDeviceId());
if (device == null) {
- return null;
+ return;
}
if (!PositionUtil.isLatest(cacheManager, position)) {
- return null;
+ return;
}
if (position.hasAttribute(Position.KEY_FUEL_LEVEL)) {
@@ -61,19 +55,17 @@ public class FuelEventHandler extends BaseEventHandler {
double threshold = AttributeUtil.lookup(
cacheManager, Keys.EVENT_FUEL_INCREASE_THRESHOLD, position.getDeviceId());
if (threshold > 0 && change >= threshold) {
- return Map.of(new Event(Event.TYPE_DEVICE_FUEL_INCREASE, position), position);
+ callback.eventDetected(new Event(Event.TYPE_DEVICE_FUEL_INCREASE, position));
}
} else if (change < 0) {
double threshold = AttributeUtil.lookup(
cacheManager, Keys.EVENT_FUEL_DROP_THRESHOLD, position.getDeviceId());
if (threshold > 0 && Math.abs(change) >= threshold) {
- return Map.of(new Event(Event.TYPE_DEVICE_FUEL_DROP, position), position);
+ callback.eventDetected(new Event(Event.TYPE_DEVICE_FUEL_DROP, position));
}
}
}
}
-
- return null;
}
}
diff --git a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java
index dbe2b8118..c8ecfb1ed 100644
--- a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.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.
@@ -15,7 +15,7 @@
*/
package org.traccar.handler.events;
-import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
import org.traccar.helper.model.PositionUtil;
import org.traccar.model.Calendar;
import org.traccar.model.Event;
@@ -23,15 +23,9 @@ import org.traccar.model.Geofence;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-@Singleton
-@ChannelHandler.Sharable
public class GeofenceEventHandler extends BaseEventHandler {
private final CacheManager cacheManager;
@@ -42,9 +36,9 @@ public class GeofenceEventHandler extends BaseEventHandler {
}
@Override
- protected Map<Event, Position> analyzePosition(Position position) {
+ public void analyzePosition(Position position, Callback callback) {
if (!PositionUtil.isLatest(cacheManager, position)) {
- return null;
+ return;
}
List<Long> oldGeofences = new ArrayList<>();
@@ -60,7 +54,6 @@ public class GeofenceEventHandler extends BaseEventHandler {
oldGeofences.removeAll(position.getGeofenceIds());
}
- Map<Event, Position> events = new HashMap<>();
for (long geofenceId : oldGeofences) {
Geofence geofence = cacheManager.getObject(Geofence.class, geofenceId);
if (geofence != null) {
@@ -69,7 +62,7 @@ public class GeofenceEventHandler extends BaseEventHandler {
if (calendar == null || calendar.checkMoment(position.getFixTime())) {
Event event = new Event(Event.TYPE_GEOFENCE_EXIT, position);
event.setGeofenceId(geofenceId);
- events.put(event, position);
+ callback.eventDetected(event);
}
}
}
@@ -79,10 +72,8 @@ public class GeofenceEventHandler extends BaseEventHandler {
if (calendar == null || calendar.checkMoment(position.getFixTime())) {
Event event = new Event(Event.TYPE_GEOFENCE_ENTER, position);
event.setGeofenceId(geofenceId);
- events.put(event, position);
+ callback.eventDetected(event);
}
}
- return events;
}
-
}
diff --git a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java
index ba4159a42..bbf9fadd1 100644
--- a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.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");
@@ -16,21 +16,13 @@
*/
package org.traccar.handler.events;
-import java.util.Collections;
-import java.util.Map;
-
-import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
import org.traccar.helper.model.PositionUtil;
import org.traccar.model.Device;
import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-
-@Singleton
-@ChannelHandler.Sharable
public class IgnitionEventHandler extends BaseEventHandler {
private final CacheManager cacheManager;
@@ -41,14 +33,12 @@ public class IgnitionEventHandler extends BaseEventHandler {
}
@Override
- protected Map<Event, Position> analyzePosition(Position position) {
+ public void analyzePosition(Position position, Callback callback) {
Device device = cacheManager.getObject(Device.class, position.getDeviceId());
if (device == null || !PositionUtil.isLatest(cacheManager, position)) {
- return null;
+ return;
}
- Map<Event, Position> result = null;
-
if (position.hasAttribute(Position.KEY_IGNITION)) {
boolean ignition = position.getBoolean(Position.KEY_IGNITION);
@@ -57,15 +47,12 @@ public class IgnitionEventHandler extends BaseEventHandler {
boolean oldIgnition = lastPosition.getBoolean(Position.KEY_IGNITION);
if (ignition && !oldIgnition) {
- result = Collections.singletonMap(
- new Event(Event.TYPE_IGNITION_ON, position), position);
+ callback.eventDetected(new Event(Event.TYPE_IGNITION_ON, position));
} else if (!ignition && oldIgnition) {
- result = Collections.singletonMap(
- new Event(Event.TYPE_IGNITION_OFF, position), position);
+ callback.eventDetected(new Event(Event.TYPE_IGNITION_OFF, position));
}
}
}
- return result;
}
}
diff --git a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java
index 2fa2e8869..573ad4ad6 100644
--- a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java
@@ -16,20 +16,12 @@
*/
package org.traccar.handler.events;
-import java.util.HashMap;
-import java.util.Map;
-
-import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
import org.traccar.model.Event;
import org.traccar.model.Maintenance;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-
-@Singleton
-@ChannelHandler.Sharable
public class MaintenanceEventHandler extends BaseEventHandler {
private final CacheManager cacheManager;
@@ -40,13 +32,12 @@ public class MaintenanceEventHandler extends BaseEventHandler {
}
@Override
- protected Map<Event, Position> analyzePosition(Position position) {
+ public void analyzePosition(Position position, Callback callback) {
Position lastPosition = cacheManager.getPosition(position.getDeviceId());
if (lastPosition == null || position.getFixTime().compareTo(lastPosition.getFixTime()) < 0) {
- return null;
+ return;
}
- Map<Event, Position> events = new HashMap<>();
for (Maintenance maintenance : cacheManager.getDeviceObjects(position.getDeviceId(), Maintenance.class)) {
if (maintenance.getPeriod() != 0) {
double oldValue = getValue(lastPosition, maintenance.getType());
@@ -58,13 +49,11 @@ public class MaintenanceEventHandler extends BaseEventHandler {
Event event = new Event(Event.TYPE_MAINTENANCE, position);
event.setMaintenanceId(maintenance.getId());
event.set(maintenance.getType(), newValue);
- events.put(event, position);
+ callback.eventDetected(event);
}
}
}
}
-
- return events;
}
private double getValue(Position position, String type) {
diff --git a/src/main/java/org/traccar/handler/events/MediaEventHandler.java b/src/main/java/org/traccar/handler/events/MediaEventHandler.java
index 52d8e6961..2745296c4 100644
--- a/src/main/java/org/traccar/handler/events/MediaEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/MediaEventHandler.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,18 +15,12 @@
*/
package org.traccar.handler.events;
-import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
import org.traccar.model.Event;
import org.traccar.model.Position;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import java.util.Map;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
-@Singleton
-@ChannelHandler.Sharable
public class MediaEventHandler extends BaseEventHandler {
@Inject
@@ -34,8 +28,8 @@ public class MediaEventHandler extends BaseEventHandler {
}
@Override
- protected Map<Event, Position> analyzePosition(Position position) {
- return Stream.of(Position.KEY_IMAGE, Position.KEY_VIDEO, Position.KEY_AUDIO)
+ public void analyzePosition(Position position, Callback callback) {
+ Stream.of(Position.KEY_IMAGE, Position.KEY_VIDEO, Position.KEY_AUDIO)
.filter(position::hasAttribute)
.map(type -> {
Event event = new Event(Event.TYPE_MEDIA, position);
@@ -43,7 +37,7 @@ public class MediaEventHandler extends BaseEventHandler {
event.set("file", position.getString(type));
return event;
})
- .collect(Collectors.toMap(event -> event, event -> position));
+ .forEach(callback::eventDetected);
}
}
diff --git a/src/main/java/org/traccar/handler/events/MotionEventHandler.java b/src/main/java/org/traccar/handler/events/MotionEventHandler.java
index 15902d6d4..41d68985b 100644
--- a/src/main/java/org/traccar/handler/events/MotionEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/MotionEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,14 +16,13 @@
*/
package org.traccar.handler.events;
-import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.config.Keys;
import org.traccar.helper.model.AttributeUtil;
import org.traccar.helper.model.PositionUtil;
import org.traccar.model.Device;
-import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.reports.common.TripsConfig;
import org.traccar.session.cache.CacheManager;
@@ -35,13 +34,6 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import java.util.Collections;
-import java.util.Map;
-
-@Singleton
-@ChannelHandler.Sharable
public class MotionEventHandler extends BaseEventHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(MotionEventHandler.class);
@@ -56,17 +48,17 @@ public class MotionEventHandler extends BaseEventHandler {
}
@Override
- protected Map<Event, Position> analyzePosition(Position position) {
+ public void analyzePosition(Position position, Callback callback) {
long deviceId = position.getDeviceId();
Device device = cacheManager.getObject(Device.class, deviceId);
if (device == null || !PositionUtil.isLatest(cacheManager, position)) {
- return null;
+ return;
}
boolean processInvalid = AttributeUtil.lookup(
cacheManager, Keys.EVENT_MOTION_PROCESS_INVALID_POSITIONS, deviceId);
if (!processInvalid && !position.getValid()) {
- return null;
+ return;
}
TripsConfig tripsConfig = new TripsConfig(new AttributeUtil.CacheProvider(cacheManager, deviceId));
@@ -82,7 +74,9 @@ public class MotionEventHandler extends BaseEventHandler {
LOGGER.warn("Update device motion error", e);
}
}
- return state.getEvent() != null ? Collections.singletonMap(state.getEvent(), position) : null;
+ if (state.getEvent() != null) {
+ callback.eventDetected(state.getEvent());
+ }
}
}
diff --git a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java
index 3bb5f713c..9598581e6 100644
--- a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,7 +16,7 @@
*/
package org.traccar.handler.events;
-import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
@@ -24,7 +24,6 @@ import org.traccar.config.Keys;
import org.traccar.helper.model.AttributeUtil;
import org.traccar.helper.model.PositionUtil;
import org.traccar.model.Device;
-import org.traccar.model.Event;
import org.traccar.model.Geofence;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
@@ -36,13 +35,6 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import java.util.Collections;
-import java.util.Map;
-
-@Singleton
-@ChannelHandler.Sharable
public class OverspeedEventHandler extends BaseEventHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(OverspeedEventHandler.class);
@@ -55,8 +47,7 @@ public class OverspeedEventHandler extends BaseEventHandler {
private final double multiplier;
@Inject
- public OverspeedEventHandler(
- Config config, CacheManager cacheManager, Storage storage) {
+ public OverspeedEventHandler(Config config, CacheManager cacheManager, Storage storage) {
this.cacheManager = cacheManager;
this.storage = storage;
minimalDuration = config.getLong(Keys.EVENT_OVERSPEED_MINIMAL_DURATION) * 1000;
@@ -65,15 +56,15 @@ public class OverspeedEventHandler extends BaseEventHandler {
}
@Override
- protected Map<Event, Position> analyzePosition(Position position) {
+ public void analyzePosition(Position position, Callback callback) {
long deviceId = position.getDeviceId();
Device device = cacheManager.getObject(Device.class, position.getDeviceId());
if (device == null) {
- return null;
+ return;
}
if (!PositionUtil.isLatest(cacheManager, position) || !position.getValid()) {
- return null;
+ return;
}
double speedLimit = AttributeUtil.lookup(cacheManager, Keys.EVENT_OVERSPEED_LIMIT, deviceId);
@@ -105,7 +96,7 @@ public class OverspeedEventHandler extends BaseEventHandler {
}
if (speedLimit == 0) {
- return null;
+ return;
}
OverspeedState state = OverspeedState.fromDevice(device);
@@ -120,7 +111,9 @@ public class OverspeedEventHandler extends BaseEventHandler {
LOGGER.warn("Update device overspeed error", e);
}
}
- return state.getEvent() != null ? Collections.singletonMap(state.getEvent(), position) : null;
+ if (state.getEvent() != null) {
+ callback.eventDetected(state.getEvent());
+ }
}
}
diff --git a/src/main/java/org/traccar/handler/AcknowledgementHandler.java b/src/main/java/org/traccar/handler/network/AcknowledgementHandler.java
index 4c1085998..e87f5d34c 100644
--- a/src/main/java/org/traccar/handler/AcknowledgementHandler.java
+++ b/src/main/java/org/traccar/handler/network/AcknowledgementHandler.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.handler;
+package org.traccar.handler.network;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
diff --git a/src/main/java/org/traccar/handler/network/MainEventHandler.java b/src/main/java/org/traccar/handler/network/MainEventHandler.java
new file mode 100644
index 000000000..f60004126
--- /dev/null
+++ b/src/main/java/org/traccar/handler/network/MainEventHandler.java
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ * 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.handler.network;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.socket.DatagramChannel;
+import io.netty.handler.codec.http.HttpRequestDecoder;
+import io.netty.handler.timeout.IdleStateEvent;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.BasePipelineFactory;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+import org.traccar.helper.NetworkUtil;
+import org.traccar.session.ConnectionManager;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+@Singleton
+@ChannelHandler.Sharable
+public class MainEventHandler extends ChannelInboundHandlerAdapter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MainEventHandler.class);
+
+ private final ConnectionManager connectionManager;
+ private final Set<String> connectionlessProtocols = new HashSet<>();
+
+ @Inject
+ public MainEventHandler(Config config, ConnectionManager connectionManager) {
+ this.connectionManager = connectionManager;
+ String connectionlessProtocolList = config.getString(Keys.STATUS_IGNORE_OFFLINE);
+ if (connectionlessProtocolList != null) {
+ connectionlessProtocols.addAll(Arrays.asList(connectionlessProtocolList.split("[, ]")));
+ }
+ }
+
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) {
+ if (!(ctx.channel() instanceof DatagramChannel)) {
+ LOGGER.info("[{}] connected", NetworkUtil.session(ctx.channel()));
+ }
+ }
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) {
+ LOGGER.info("[{}] disconnected", NetworkUtil.session(ctx.channel()));
+ closeChannel(ctx.channel());
+
+ boolean supportsOffline = BasePipelineFactory.getHandler(ctx.pipeline(), HttpRequestDecoder.class) == null
+ && !connectionlessProtocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName());
+ connectionManager.deviceDisconnected(ctx.channel(), supportsOffline);
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+ while (cause.getCause() != null && cause.getCause() != cause) {
+ cause = cause.getCause();
+ }
+ LOGGER.info("[{}] error", NetworkUtil.session(ctx.channel()), cause);
+ closeChannel(ctx.channel());
+ }
+
+ @Override
+ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
+ if (evt instanceof IdleStateEvent) {
+ LOGGER.info("[{}] timed out", NetworkUtil.session(ctx.channel()));
+ closeChannel(ctx.channel());
+ }
+ }
+
+ private void closeChannel(Channel channel) {
+ if (!(channel instanceof DatagramChannel)) {
+ channel.close();
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/handler/NetworkForwarderHandler.java b/src/main/java/org/traccar/handler/network/NetworkForwarderHandler.java
index 470e175ca..5f07ce355 100644
--- a/src/main/java/org/traccar/handler/NetworkForwarderHandler.java
+++ b/src/main/java/org/traccar/handler/network/NetworkForwarderHandler.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.handler;
+package org.traccar.handler.network;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
diff --git a/src/main/java/org/traccar/handler/NetworkMessageHandler.java b/src/main/java/org/traccar/handler/network/NetworkMessageHandler.java
index b1d926bfa..c2fb9016a 100644
--- a/src/main/java/org/traccar/handler/NetworkMessageHandler.java
+++ b/src/main/java/org/traccar/handler/network/NetworkMessageHandler.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.handler;
+package org.traccar.handler.network;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
diff --git a/src/main/java/org/traccar/handler/OpenChannelHandler.java b/src/main/java/org/traccar/handler/network/OpenChannelHandler.java
index e416f35ae..21aaae676 100644
--- a/src/main/java/org/traccar/handler/OpenChannelHandler.java
+++ b/src/main/java/org/traccar/handler/network/OpenChannelHandler.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.handler;
+package org.traccar.handler.network;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
diff --git a/src/main/java/org/traccar/handler/RemoteAddressHandler.java b/src/main/java/org/traccar/handler/network/RemoteAddressHandler.java
index 61ada5b91..c52bb2be1 100644
--- a/src/main/java/org/traccar/handler/RemoteAddressHandler.java
+++ b/src/main/java/org/traccar/handler/network/RemoteAddressHandler.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.
@@ -13,17 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.handler;
+package org.traccar.handler.network;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Position;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
import java.net.InetSocketAddress;
@Singleton
diff --git a/src/main/java/org/traccar/handler/StandardLoggingHandler.java b/src/main/java/org/traccar/handler/network/StandardLoggingHandler.java
index 513602dd8..dae93655d 100644
--- a/src/main/java/org/traccar/handler/StandardLoggingHandler.java
+++ b/src/main/java/org/traccar/handler/network/StandardLoggingHandler.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.handler;
+package org.traccar.handler.network;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
diff --git a/src/main/java/org/traccar/helper/PositionLogger.java b/src/main/java/org/traccar/helper/PositionLogger.java
new file mode 100644
index 000000000..9f149edf4
--- /dev/null
+++ b/src/main/java/org/traccar/helper/PositionLogger.java
@@ -0,0 +1,94 @@
+/*
+ * 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.helper;
+
+import io.netty.channel.ChannelHandlerContext;
+import jakarta.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+import org.traccar.model.Device;
+import org.traccar.model.Position;
+import org.traccar.session.cache.CacheManager;
+
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class PositionLogger {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(PositionLogger.class);
+
+ private final CacheManager cacheManager;
+ private final Set<String> logAttributes = new LinkedHashSet<>();
+
+ @Inject
+ public PositionLogger(Config config, CacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+ logAttributes.addAll(Arrays.asList(config.getString(Keys.LOGGER_ATTRIBUTES).split("[, ]")));
+ }
+
+ public void log(ChannelHandlerContext context, Position position) {
+ Device device = cacheManager.getObject(Device.class, position.getDeviceId());
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("[").append(NetworkUtil.session(context.channel())).append("] ");
+ builder.append("id: ").append(device.getUniqueId());
+ for (String attribute : logAttributes) {
+ switch (attribute) {
+ case "time":
+ builder.append(", time: ").append(DateUtil.formatDate(position.getFixTime(), false));
+ break;
+ case "position":
+ builder.append(", lat: ").append(String.format("%.5f", position.getLatitude()));
+ builder.append(", lon: ").append(String.format("%.5f", position.getLongitude()));
+ break;
+ case "speed":
+ if (position.getSpeed() > 0) {
+ builder.append(", speed: ").append(String.format("%.1f", position.getSpeed()));
+ }
+ break;
+ case "course":
+ builder.append(", course: ").append(String.format("%.1f", position.getCourse()));
+ break;
+ case "accuracy":
+ if (position.getAccuracy() > 0) {
+ builder.append(", accuracy: ").append(String.format("%.1f", position.getAccuracy()));
+ }
+ break;
+ case "outdated":
+ if (position.getOutdated()) {
+ builder.append(", outdated");
+ }
+ break;
+ case "invalid":
+ if (!position.getValid()) {
+ builder.append(", invalid");
+ }
+ break;
+ default:
+ Object value = position.getAttributes().get(attribute);
+ if (value != null) {
+ builder.append(", ").append(attribute).append(": ").append(value);
+ }
+ break;
+ }
+ }
+ LOGGER.info(builder.toString());
+ }
+
+}
diff --git a/src/main/java/org/traccar/model/Device.java b/src/main/java/org/traccar/model/Device.java
index a3088a613..c2da9be79 100644
--- a/src/main/java/org/traccar/model/Device.java
+++ b/src/main/java/org/traccar/model/Device.java
@@ -53,7 +53,7 @@ public class Device extends GroupedModel implements Disableable, Schedulable {
}
public void setUniqueId(String uniqueId) {
- if (uniqueId.contains("../") || uniqueId.contains("..\\")) {
+ if (uniqueId.contains("..")) {
throw new IllegalArgumentException("Invalid unique id");
}
this.uniqueId = uniqueId.trim();
diff --git a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java
index 8896dcfb0..834f4f16c 100644
--- a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 - 2021 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.
@@ -325,6 +325,9 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder {
case "SA":
position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
break;
+ case "MT":
+ position.set(Position.KEY_MOTION, buf.readUnsignedByte() > 0);
+ break;
case "MV":
position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1);
break;
diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
index 7a8d67cf9..775e98401 100644
--- a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
@@ -41,12 +41,54 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
+ private static final Map<String, String> PROTOCOL_MODELS = Map.ofEntries(
+ Map.entry("02", "GL200"),
+ Map.entry("04", "GV200"),
+ Map.entry("06", "GV300"),
+ Map.entry("08", "GMT100"),
+ Map.entry("09", "GV50P"),
+ Map.entry("0F", "GV55"),
+ Map.entry("10", "GV55 LITE"),
+ Map.entry("11", "GL500"),
+ Map.entry("1A", "GL300"),
+ Map.entry("1F", "GV500"),
+ Map.entry("25", "GV300"),
+ Map.entry("27", "GV300W"),
+ Map.entry("28", "GL300VC"),
+ Map.entry("2C", "GL300W"),
+ Map.entry("2F", "GV55"),
+ Map.entry("30", "GL300"),
+ Map.entry("31", "GV65"),
+ Map.entry("35", "GV200"),
+ Map.entry("36", "GV500"),
+ Map.entry("3F", "GMT100"),
+ Map.entry("40", "GL500"),
+ Map.entry("41", "GV75W"),
+ Map.entry("42", "GT501"),
+ Map.entry("44", "GL530"),
+ Map.entry("45", "GB100"),
+ Map.entry("50", "GV55W"),
+ Map.entry("52", "GL50"),
+ Map.entry("55", "GL50B"),
+ Map.entry("5E", "GV500MAP"),
+ Map.entry("6E", "GV310LAU"),
+ Map.entry("BD", "CV200"),
+ Map.entry("C2", "GV600M"),
+ Map.entry("DC", "GV600MG"),
+ Map.entry("DE", "GL500M"),
+ Map.entry("F1", "GV350M"),
+ Map.entry("F8", "GV800W"),
+ Map.entry("FC", "GV600W"),
+ Map.entry("802004", "GV58LAU"),
+ Map.entry("802005", "GV355CEU"));
+
private boolean ignoreFixTime;
private final DateFormat dateFormat;
@@ -62,9 +104,19 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
ignoreFixTime = getConfig().getBoolean(Keys.PROTOCOL_IGNORE_FIX_TIME.withPrefix(getProtocolName()));
}
- private String getDeviceModel(DeviceSession deviceSession, String value) {
- String model = value.isEmpty() ? getDeviceModel(deviceSession) : value;
- return model != null ? model.toUpperCase() : "";
+ private String getDeviceModel(DeviceSession deviceSession, String protocolVersion) {
+ String declaredModel = getDeviceModel(deviceSession);
+ if (declaredModel != null) {
+ return declaredModel.toUpperCase();
+ }
+ String versionPrefix;
+ if (protocolVersion.length() > 6) {
+ versionPrefix = protocolVersion.substring(0, 6);
+ } else {
+ versionPrefix = protocolVersion.substring(0, 2);
+ }
+ String model = PROTOCOL_MODELS.get(versionPrefix);
+ return model != null ? model : "";
}
private Position initPosition(Parser parser, Channel channel, SocketAddress remoteAddress) {
@@ -122,12 +174,12 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
private static final Pattern PATTERN_INF = new PatternBuilder()
.text("+").expression("(?:RESP|BUFF):GTINF,")
- .expression("(?:.{6}|.{10})?,") // protocol version
+ .expression("(.{6}|.{10})?,") // protocol version
.number("(d{15}|x{14}),") // imei
.expression("(?:[0-9A-Z]{17},)?") // vin
.expression("(?:[^,]+)?,") // device name
.number("(xx),") // state
- .expression("(?:[0-9Ff]{20})?,") // iccid
+ .expression("([0-9Ff]{20})?,") // iccid
.number("(d{1,2}),") // rssi
.number("d{1,2},")
.expression("[01]{1,2},") // external power
@@ -148,6 +200,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
.expression("(?:[01])?,").optional() // pin15 mode
.number("(d+)?,") // adc1
.number("(d+)?,").optional() // adc2
+ .number("(d+)?,").optional() // adc3
.number("(xx)?,") // digital input
.number("(xx)?,") // digital output
.number("[-+]dddd,") // timezone
@@ -163,10 +216,17 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
private Object decodeInf(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_INF, sentence);
- Position position = initPosition(parser, channel, remoteAddress);
- if (position == null) {
+ if (!parser.matches()) {
return null;
}
+ String protocolVersion = parser.next();
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+ Position position = new Position();
+ position.setProtocol(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
switch (parser.nextHexInt()) {
case 0x16:
@@ -197,9 +257,15 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
break;
}
+ position.set(Position.KEY_ICCID, parser.next());
position.set(Position.KEY_RSSI, parser.nextInt());
- parser.next(); // odometer or external power
+ String model = getDeviceModel(deviceSession, protocolVersion);
+ if (model.equals("GV310LAU")) {
+ position.set(Position.KEY_POWER, parser.nextDouble() / 1000);
+ } else {
+ parser.next(); // odometer or external power
+ }
position.set(Position.KEY_BATTERY, parser.nextDouble());
position.set(Position.KEY_CHARGE, parser.nextInt() == 1);
@@ -210,6 +276,9 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.PREFIX_ADC + 1, parser.next());
position.set(Position.PREFIX_ADC + 2, parser.next());
+ if (model.equals("GV310LAU")) {
+ position.set(Position.PREFIX_ADC + 3, parser.next());
+ }
position.set(Position.KEY_INPUT, parser.next());
position.set(Position.KEY_OUTPUT, parser.next());
@@ -455,7 +524,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
private Object decodeCan(Channel channel, SocketAddress remoteAddress, String[] v) throws ParseException {
int index = 0;
index += 1; // header
- index += 1; // protocol version
+ String protocolVersion = v[index++]; // protocol version
DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, v[index++]);
if (deviceSession == null) {
return null;
@@ -464,7 +533,8 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
- String model = getDeviceModel(deviceSession, v[index++]);
+ String model = getDeviceModel(deviceSession, protocolVersion);
+ index += 1; // device name
index += 1; // report type
index += 1; // can bus state
long reportMask = Long.parseLong(v[index++], 16);
@@ -872,13 +942,14 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
private Object decodeEri(Channel channel, SocketAddress remoteAddress, String[] v) throws ParseException {
int index = 0;
index += 1; // header
- index += 1; // protocol version
+ String protocolVersion = v[index++]; // protocol version
DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, v[index++]);
if (deviceSession == null) {
return null;
}
- String model = getDeviceModel(deviceSession, v[index++]);
+ String model = getDeviceModel(deviceSession, protocolVersion);
+ index += 1; // device name
long mask = Long.parseLong(v[index++], 16);
Double power = v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]) * 0.001;
index += 1; // report type
@@ -960,7 +1031,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
}
private static final Pattern PATTERN_IGN = new PatternBuilder()
- .text("+").expression("(?:RESP|BUFF):GTIG[NF],")
+ .text("+").expression("(?:RESP|BUFF):GT[IV]G[NF],")
.expression("(?:.{6}|.{10})?,") // protocol version
.number("(d{15}|x{14}),") // imei
.expression("[^,]*,") // device name
@@ -984,7 +1055,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
decodeLocation(position, parser);
- position.set(Position.KEY_IGNITION, sentence.contains("IGN"));
+ position.set(Position.KEY_IGNITION, sentence.contains("GN"));
position.set(Position.KEY_HOURS, parseHours(parser.next()));
position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000);
@@ -1606,6 +1677,8 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
break;
case "IGN":
case "IGF":
+ case "VGN":
+ case "VGF":
result = decodeIgn(channel, remoteAddress, sentence);
break;
case "LSW":
diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
index 2186fb91f..648c5fb42 100644
--- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
@@ -979,6 +979,12 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
case 0x0103:
position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedInt() * 0.01);
break;
+ case 0x0111:
+ position.set("fuelTemp", buf.readUnsignedByte() - 40);
+ break;
+ case 0x012E:
+ position.set("oilLevel", buf.readUnsignedShort() * 0.1);
+ break;
case 0x052A:
position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShort() * 0.01);
break;
@@ -1113,6 +1119,30 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
}
getLastLocation(position, time);
break;
+ case 0x15:
+ int event = buf.readInt();
+ switch (event) {
+ case 51:
+ position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION);
+ break;
+ case 52:
+ position.set(Position.KEY_ALARM, Position.ALARM_BRAKING);
+ break;
+ case 53:
+ position.set(Position.KEY_ALARM, Position.ALARM_CORNERING);
+ break;
+ case 54:
+ position.set(Position.KEY_ALARM, Position.ALARM_LANE_CHANGE);
+ break;
+ case 56:
+ position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT);
+ break;
+ default:
+ position.set(Position.KEY_EVENT, event);
+ break;
+ }
+ getLastLocation(position, time);
+ break;
default:
return null;
}
diff --git a/src/test/java/org/traccar/database/GroupTreeTest.java b/src/test/java/org/traccar/database/GroupTreeTest.java
deleted file mode 100644
index 7c0d478f8..000000000
--- a/src/test/java/org/traccar/database/GroupTreeTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.traccar.database;
-
-import org.junit.jupiter.api.Test;
-import org.traccar.model.Device;
-import org.traccar.model.Group;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-public class GroupTreeTest {
-
- private static Group createGroup(long id, String name, long parent) {
- Group group = new Group();
- group.setId(id);
- group.setName(name);
- group.setGroupId(parent);
- return group;
- }
-
- private static Device createDevice(long id, String name, long parent) {
- Device device = new Device();
- device.setId(id);
- device.setName(name);
- device.setGroupId(parent);
- return device;
- }
-
- @Test
- public void testGetDescendants() {
- Collection<Group> groups = new ArrayList<>();
- groups.add(createGroup(1, "First", 0));
- groups.add(createGroup(2, "Second", 1));
- groups.add(createGroup(3, "Third", 2));
- groups.add(createGroup(4, "Fourth", 2));
- groups.add(createGroup(5, "Fifth", 4));
-
- Collection<Device> devices = new ArrayList<>();
- devices.add(createDevice(1, "One", 3));
- devices.add(createDevice(2, "Two", 5));
- devices.add(createDevice(3, "One", 5));
-
- GroupTree groupTree = new GroupTree(groups, devices);
-
- assertEquals(4, groupTree.getGroups(1).size());
- assertEquals(3, groupTree.getGroups(2).size());
- assertEquals(0, groupTree.getGroups(3).size());
- assertEquals(1, groupTree.getGroups(4).size());
-
- assertEquals(3, groupTree.getDevices(1).size());
- assertEquals(1, groupTree.getDevices(3).size());
- assertEquals(2, groupTree.getDevices(4).size());
- }
-
-}
diff --git a/src/test/java/org/traccar/handler/DistanceHandlerTest.java b/src/test/java/org/traccar/handler/DistanceHandlerTest.java
index 7d2f1e2e3..04a27ad79 100644
--- a/src/test/java/org/traccar/handler/DistanceHandlerTest.java
+++ b/src/test/java/org/traccar/handler/DistanceHandlerTest.java
@@ -15,14 +15,15 @@ public class DistanceHandlerTest {
DistanceHandler distanceHandler = new DistanceHandler(new Config(), mock(CacheManager.class));
- Position position = distanceHandler.handlePosition(new Position());
+ Position position = new Position();
+ distanceHandler.handlePosition(position, p -> {});
assertEquals(0.0, position.getAttributes().get(Position.KEY_DISTANCE));
assertEquals(0.0, position.getAttributes().get(Position.KEY_TOTAL_DISTANCE));
position.set(Position.KEY_DISTANCE, 100);
- position = distanceHandler.handlePosition(position);
+ distanceHandler.handlePosition(position, p -> {});
assertEquals(100.0, position.getAttributes().get(Position.KEY_DISTANCE));
assertEquals(100.0, position.getAttributes().get(Position.KEY_TOTAL_DISTANCE));
diff --git a/src/test/java/org/traccar/handler/MotionHandlerTest.java b/src/test/java/org/traccar/handler/MotionHandlerTest.java
index 10cdf6a90..927c803d9 100644
--- a/src/test/java/org/traccar/handler/MotionHandlerTest.java
+++ b/src/test/java/org/traccar/handler/MotionHandlerTest.java
@@ -26,7 +26,8 @@ public class MotionHandlerTest {
MotionHandler motionHandler = new MotionHandler(cacheManager);
- Position position = motionHandler.handlePosition(new Position());
+ Position position = new Position();
+ motionHandler.handlePosition(position, p -> {});
assertEquals(false, position.getAttributes().get(Position.KEY_MOTION));
diff --git a/src/test/java/org/traccar/handler/events/AlertEventHandlerTest.java b/src/test/java/org/traccar/handler/events/AlertEventHandlerTest.java
index 66dc55c85..7b1991553 100644
--- a/src/test/java/org/traccar/handler/events/AlertEventHandlerTest.java
+++ b/src/test/java/org/traccar/handler/events/AlertEventHandlerTest.java
@@ -7,10 +7,11 @@ import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.Mockito.mock;
public class AlertEventHandlerTest extends BaseTest {
@@ -22,9 +23,10 @@ public class AlertEventHandlerTest extends BaseTest {
Position position = new Position();
position.set(Position.KEY_ALARM, Position.ALARM_GENERAL);
- Map<Event, Position> events = alertEventHandler.analyzePosition(position);
- assertNotNull(events);
- Event event = events.keySet().iterator().next();
+ List<Event> events = new ArrayList<>();
+ alertEventHandler.analyzePosition(position, events::add);
+ assertFalse(events.isEmpty());
+ Event event = events.iterator().next();
assertEquals(Event.TYPE_ALARM, event.getType());
}
diff --git a/src/test/java/org/traccar/handler/events/CommandResultEventHandlerTest.java b/src/test/java/org/traccar/handler/events/CommandResultEventHandlerTest.java
index bc24e42f5..58e198b7f 100644
--- a/src/test/java/org/traccar/handler/events/CommandResultEventHandlerTest.java
+++ b/src/test/java/org/traccar/handler/events/CommandResultEventHandlerTest.java
@@ -5,9 +5,12 @@ import org.traccar.BaseTest;
import org.traccar.model.Event;
import org.traccar.model.Position;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class CommandResultEventHandlerTest extends BaseTest {
@@ -19,9 +22,10 @@ public class CommandResultEventHandlerTest extends BaseTest {
Position position = new Position();
position.set(Position.KEY_RESULT, "Test Result");
- Map<Event, Position> events = commandResultEventHandler.analyzePosition(position);
- assertNotNull(events);
- Event event = events.keySet().iterator().next();
+ List<Event> events = new ArrayList<>();
+ commandResultEventHandler.analyzePosition(position, events::add);
+ assertFalse(events.isEmpty());
+ Event event = events.iterator().next();
assertEquals(Event.TYPE_COMMAND_RESULT, event.getType());
}
diff --git a/src/test/java/org/traccar/handler/events/IgnitionEventHandlerTest.java b/src/test/java/org/traccar/handler/events/IgnitionEventHandlerTest.java
index 972932df4..61ff50efa 100644
--- a/src/test/java/org/traccar/handler/events/IgnitionEventHandlerTest.java
+++ b/src/test/java/org/traccar/handler/events/IgnitionEventHandlerTest.java
@@ -1,14 +1,11 @@
package org.traccar.handler.events;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.traccar.BaseTest;
-import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import java.util.Map;
-
-import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock;
public class IgnitionEventHandlerTest extends BaseTest {
@@ -21,8 +18,7 @@ public class IgnitionEventHandlerTest extends BaseTest {
Position position = new Position();
position.set(Position.KEY_IGNITION, true);
position.setValid(true);
- Map<Event, Position> events = ignitionEventHandler.analyzePosition(position);
- assertNull(events);
+ ignitionEventHandler.analyzePosition(position, Assertions::assertNull);
}
}
diff --git a/src/test/java/org/traccar/handler/events/MaintenanceEventHandlerTest.java b/src/test/java/org/traccar/handler/events/MaintenanceEventHandlerTest.java
index 661336d76..f0bd4a3f9 100644
--- a/src/test/java/org/traccar/handler/events/MaintenanceEventHandlerTest.java
+++ b/src/test/java/org/traccar/handler/events/MaintenanceEventHandlerTest.java
@@ -2,11 +2,14 @@ package org.traccar.handler.events;
import org.junit.jupiter.api.Test;
import org.traccar.BaseTest;
+import org.traccar.model.Event;
import org.traccar.model.Maintenance;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -38,24 +41,30 @@ public class MaintenanceEventHandlerTest extends BaseTest {
MaintenanceEventHandler eventHandler = new MaintenanceEventHandler(cacheManager);
when(maintenance.getStart()).thenReturn(10000.0);
- when(maintenance.getPeriod()).thenReturn(2000.0);
+ when(maintenance.getPeriod()).thenReturn(2000.0);
+
+ List<Event> events = new ArrayList<>();
lastPosition.set(Position.KEY_TOTAL_DISTANCE, 1999);
- position.set(Position.KEY_TOTAL_DISTANCE, 2001);
- assertTrue(eventHandler.analyzePosition(position).isEmpty());
+ position.set(Position.KEY_TOTAL_DISTANCE, 2001);
+ eventHandler.analyzePosition(position, events::add);
+ assertTrue(events.isEmpty());
lastPosition.set(Position.KEY_TOTAL_DISTANCE, 3999);
- position.set(Position.KEY_TOTAL_DISTANCE, 4001);
- assertTrue(eventHandler.analyzePosition(position).isEmpty());
+ position.set(Position.KEY_TOTAL_DISTANCE, 4001);
+ eventHandler.analyzePosition(position, events::add);
+ assertTrue(events.isEmpty());
lastPosition.set(Position.KEY_TOTAL_DISTANCE, 9999);
position.set(Position.KEY_TOTAL_DISTANCE, 10001);
- assertEquals(1, eventHandler.analyzePosition(position).size());
+ eventHandler.analyzePosition(position, events::add);
+ assertEquals(1, events.size());
lastPosition.set(Position.KEY_TOTAL_DISTANCE, 11999);
position.set(Position.KEY_TOTAL_DISTANCE, 12001);
- assertEquals(1, eventHandler.analyzePosition(position).size());
-
+ eventHandler.analyzePosition(position, events::add);
+ assertEquals(2, events.size());
+
}
}
diff --git a/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java
index be6fb5c45..7115e0ea6 100644
--- a/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java
@@ -44,6 +44,9 @@ public class AtrackProtocolDecoderTest extends ProtocolTest {
decoder.setCustom(true);
verifyPositions(decoder, binary(
+ "405064e503092d8600000000000f54ab660786b0660786b0660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f002800660786ec660786ec660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f0028006607872866078728660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f0028006607876466078764660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f002800660787a0660787a0660a12c4fa2e202d01cfd32a005a02000022b700060000000000000007d007d000254349254d56254256254d5400007f002800660787dc660787dc660a12c4fa2e202d01cfd32a005a02000022b700060000000000000007d007d000254349254d56254256254d5400007f0028006607881866078818660a12c4fa2e202d01cfd32a005a02000022b700060000000000000007d007d000254349254d56254256254d5400007f0028006607885466078854660a12c4fa2e202d01cfd32a005a02000022b700060000000000000007d007d000254349254d56254256254d5400007f0028006607889066078890660a12c4fa2e202d01cfd32a005a02000022b700060000000000000007d007d000254349254d56254256254d5400007f002800660788cc660788cc660a12c4fa2e202d01cfd32a005a02000022b700060000000000000007d007d000254349254d56254256254d5400007f0028006607890866078908660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f0028006607894466078944660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f0028006607898066078980660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f002800"));
+
+ verifyPositions(decoder, binary(
"4050d78502e01d29000312fa45441d6d647d8e67647d8e67647eef190205437c021846e6001a020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010c0000000000000000000000000000000000000000000000000e647d8e85647d8e86647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010c0000000000000000000000000000000000000000000000000e647d8ea3647d8ea4647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010b0000000000000000000000000000000000000000000000000e647d8ec1647d8ec2647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010a0000000000000000000000000000000000000000000000000e647d8edf647d8ee0647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010a0000000000000000000000000000000000000000000000000e647d8efd647d8efe647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010a0000000000000000000000000000000000000000000000000e"));
verifyPositions(decoder, binary(
diff --git a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java
index b6e50e07d..4cccf8fa2 100644
--- a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java
@@ -11,6 +11,14 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest {
var decoder = inject(new Gl200TextProtocolDecoder(null));
+ 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);
+
+ verifyAttribute(decoder, buffer(
+ "+RESP:GTIGF,8020050502,867488060278727,,509,1,0.0,184,32.6,14.003446,42.654554,20240219124629,0222,0001,0FAD,0497F715,00,,0.0,20240219124630,FD2F$"),
+ Position.KEY_IGNITION, false);
+
verifyPositions(decoder, buffer(
"+RESP:GTERI,C2010D,869653060009939,GV600M,00000100,,10,1,0,0.0,0,163.9,10.239379,45.931389,20231210233723,0222,0010,2F31,006D7621,,0.0,,,,3,410000,,1,0,6,4,0,,01BF,Sondaporte,EA7D0A3882F6,1,3484,21.81,43,00,00,0,0,0,20240114221802,1875$"));
@@ -52,14 +60,14 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest {
"+RESP:GTFRI,5E0100,862061048023666,,,12940,10,1,1,0.0,97,179.8,-90.366478,38.735379,20230616183231,0310,0410,6709,03ADF710,00,6223.7,,,,,110000,,,,202306161834$"));
verifyAttribute(decoder, buffer(
- "+BUFF:GTERI,410502,864802030794634,GV75W,00000001,,10,1,1,0.0,0,3027.8,-78.706612,-0.955699,20230418170736,0740,0002,A08C,2AB72D,00,0.0,,,,100,110000,1,0099,20230418171004,8B98$"),
+ "+BUFF:GTERI,410502,864802030794634,,00000001,,10,1,1,0.0,0,3027.8,-78.706612,-0.955699,20230418170736,0740,0002,A08C,2AB72D,00,0.0,,,,100,110000,1,0099,20230418171004,8B98$"),
Position.KEY_FUEL_LEVEL, 153);
verifyPositions(decoder, false, buffer(
"+BUFF:GTFRI,2E0503,861106050005423,,,0,1,,,,,,,,,,,,0,0,,98,1,0,,,20200101000001,0083$"));
verifyAttribute(decoder, buffer(
- "+RESP:GTERI,271002,863457051562823,GV300,00000002,,10,1,1,0.0,15,28.2,-58.695253,-34.625413,20230119193305,0722,0007,1168,16B3BB,00,0.0,,,,99,210100,2,1,28F8A149F69A3C25,1,0190,20230119193314,07C7$"),
+ "+RESP:GTERI,271002,863457051562823,,00000002,,10,1,1,0.0,15,28.2,-58.695253,-34.625413,20230119193305,0722,0007,1168,16B3BB,00,0.0,,,,99,210100,2,1,28F8A149F69A3C25,1,0190,20230119193314,07C7$"),
Position.PREFIX_TEMP + 1, 25.0);
verifyAttribute(decoder, buffer(
@@ -176,7 +184,7 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest {
"+RESP:GTERI,310603,863286023345490,,00000002,,10,1,2,0.3,0,155.7,8.000000,52.000000,20171215213040,0262,0002,1450,9F13,00,1130.3,00539:27:19,,,110000,2,1,28FFD5239115034E,1,,20171215213041,27C7$"));
verifyPositions(decoder, buffer(
- "+RESP:GTERI,250C02,868789023691057,GV300,00000019,,10,1,1,0.0,196,2258.0,-99.201807,19.559242,20180214002957,0334,0003,235B,7F8D,00,6786.7,,,,100,110000,1,0394,1,4,100.0,100.0,20180214003006,C72B$"));
+ "+RESP:GTERI,250C02,868789023691057,,00000019,,10,1,1,0.0,196,2258.0,-99.201807,19.559242,20180214002957,0334,0003,235B,7F8D,00,6786.7,,,,100,110000,1,0394,1,4,100.0,100.0,20180214003006,C72B$"));
verifyAttributes(decoder, buffer(
"+RESP:GTCAN,310603,863286023335723,gv65,00,1,C03FFFFF,,0,,719601.00,,,,,,,,274.99,179.02,95.98,84761.00,,,0,,0,,,0,0.0,216,29.8,-2.155296,51.899400,20180209172714,0234,0010,53F3,8D38,00,20180211002128,E94E$"));
@@ -203,7 +211,7 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest {
"+RESP:GTOBD,360701,864251020253807,LSGTC58UX7Y067312,GV500,0,70FFFF,LSGTC58UX7Y067312,1,12309,983A8140,0,0,33,nan,,0,0,0,,10,0,,0,4.4,0,83.7,36.235142,49.967324,20170829112348,0255,0001,2760,9017,00,690.1,20170829112400,3456$"));
verifyPositions(decoder, buffer(
- "+RESP:GTERI,060502,861074023620928,GV300,00000002,27822,10,1,1,0.0,84,2870.9,-78.531796,-0.277329,20170825045344,,,,,,0.0,01138:30:24,,,83,220104,2,1,28FF2776A2150308,1,FFAD,0,20170825045348,A88C$"));
+ "+RESP:GTERI,060502,861074023620928,,00000002,27822,10,1,1,0.0,84,2870.9,-78.531796,-0.277329,20170825045344,,,,,,0.0,01138:30:24,,,83,220104,2,1,28FF2776A2150308,1,FFAD,0,20170825045348,A88C$"));
verifyAttributes(decoder, buffer(
"+RESP:GTINF,280500,A1000043D20139,GL300VC,41,,31,0,0,,,3.87,0,1,1,,,20170802150751,70,,48.0,,,20170802112145,03AC$"));
@@ -218,7 +226,7 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest {
"+RESP:GTTRI,862370030005908,1,0,99,1,0.0,354,18.5,18.821100,-34.084002,20170607152024,0655,0001,00DD,1CAE,00,0103010100,20170607172115,3E7D$"));
verifyPositions(decoder, buffer(
- "+RESP:GTERI,060800,861074023677175,GV300,00000002,12351,10,1,1,0.0,0,2862.4,-78.467273,-0.164998,20170529181717,,,,,,0.0,00259:11:50,,,0,210104,2,1,28E17436060000E2,1,015F,0,20170529181723,2824$"));
+ "+RESP:GTERI,060800,861074023677175,,00000002,12351,10,1,1,0.0,0,2862.4,-78.467273,-0.164998,20170529181717,,,,,,0.0,00259:11:50,,,0,210104,2,1,28E17436060000E2,1,015F,0,20170529181723,2824$"));
verifyPosition(decoder, buffer(
"+RESP:GTSWG,110100,358688000000158,,1,0,2.1,0,27.1,121.390717,31.164424,20110901073917,0460,0000,1878,0873,,20110901154653,0015$"));
@@ -239,7 +247,7 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest {
"+RESP:GTFRI,060228,862894020180553,,14827,10,1,1,3.4,199,409.6,-63.174466,-17.739317,20170407121823,0000,0000,0000,0000,00,15989.5,01070:43:13,13,180,0,220101,,,,20170407081824,9607$"));
verifyPositions(decoder, buffer(
- "+RESP:GTERI,060502,861074023376992,GV300,00000002,27239,10,1,1,0.2,312,183.3,-79.320820,-2.499110,20170401212005,0740,0000,EE4E,C98F,00,0.0,02114:36:35,,,90,220504,2,0,0,20170401212007,9E3D$"));
+ "+RESP:GTERI,060502,861074023376992,,00000002,27239,10,1,1,0.2,312,183.3,-79.320820,-2.499110,20170401212005,0740,0000,EE4E,C98F,00,0.0,02114:36:35,,,90,220504,2,0,0,20170401212007,9E3D$"));
verifyPositions(decoder, buffer(
"+RESP:GTFRI,060502,861074023689626,,25202,10,1,1,0.0,0,2744.1,-78.261047,0.023452,20170401211940,,,,,,0.0,00079:19:15,,,51,110000,,,,20170401212003,4DA7$"));
@@ -251,7 +259,7 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest {
"+RESP:GTERI,06020B,862170010196747,,00000000,,10,1,2,1.8,0,-2.5,117.198440,31.845219,20120802061037,0460,0000,5663,0358,00,0.0,,,,0,410000,20120802061040,0012$"));
verifyPositions(decoder, buffer(
- "+RESP:GTERI,060502,861074023692562,GV300,00000002,14197,10,1,1,0.2,220,491.8,-79.064212,-2.159754,20170401212007,0740,0000,EE49,CE25,00,0.0,01509:10:58,,,87,220104,2,0,0,20170401212010,D14D$"));
+ "+RESP:GTERI,060502,861074023692562,,00000002,14197,10,1,1,0.2,220,491.8,-79.064212,-2.159754,20170401212007,0740,0000,EE49,CE25,00,0.0,01509:10:58,,,87,220104,2,0,0,20170401212010,D14D$"));
verifyPositions(decoder, buffer(
"+RESP:GTFRI,210102,354524044925825,,1,1,1,29,2.8,0,133.7,-90.203063,32.265473,20170318005208,,,,,10800,4,20170318005208,0002$"));
@@ -487,8 +495,17 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest {
verifyAttributes(decoder, buffer(
"+ACK:GTGEO,1A0102,135790246811220,,0,0008,20100310172830,11F0"));
- decoder.setModelOverride("GV355CEU");
+ verifyAttributes(decoder, buffer(
+ "+RESP:GTINF,6E0202,868589060187625,RA82,11,89883030000091225018,41,0,1,12349,,4.15,0,1,0,0,20240328231013,0,0,0,0,00,00,+0000,0,20240328231015,7D4F"));
+
+ verifyAttribute(decoder, buffer(
+ "+RESP:GTINF,6E0202,868589060187625,RA82,11,89883030000091225018,41,0,1,12349,,4.15,0,1,0,0,20240328231013,0,0,0,0,00,00,+0000,0,20240328231015,7D4F"),
+ Position.KEY_POWER, 12.349);
+ verifyAttribute(decoder, buffer(
+ "+RESP:GTINF,6E0202,868589060187625,RA82,11,89883030000091225018,41,0,1,12349,,4.15,0,1,0,0,20240328231013,0,0,0,0,00,00,+0000,0,20240328231015,7D4F"),
+ Position.PREFIX_ADC + 3, "0");
+
verifyAttributes(decoder, buffer(
"+RESP:GTCAN,8020050605,867488060270575,,00,1,FFFFFFFF,8LBETF3W4N0001613,,,22.54,0,,,,,,,7.84,4.61,3.24,3.33,,8080,,,00,0.00,0.00,1,14,14,2371,0,001FFFFF,,,,,,,,,7158,9998,0,7.84,0.00,0.00,558,,,,,,,C0,,,,,0,0.0,346,2848.5,-78.592371,-0.968132,20240202083437,0740,0002,526C,00AE7907,00,20240202083440,3F6D$"));
diff --git a/swagger.json b/swagger.json
index b2209a20e..933652cb0 100644
--- a/swagger.json
+++ b/swagger.json
@@ -2,7 +2,7 @@
"openapi": "3.0.1",
"info": {
"title": "Traccar",
- "version": "5.12",
+ "version": "6.0",
"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/traccar-web b/traccar-web
-Subproject c9da10062998a231c038cd3a519f72128fcea2b
+Subproject a3e79c6572759f6c8f4544166ea34d0e6d789b4