From c15525531bbdc953b0c11e5365540f68b0da1e4e Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 6 Jan 2024 11:31:02 -0800 Subject: [PATCH 01/55] Queclink GV58LAU improvements --- .../protocol/Gl200TextProtocolDecoder.java | 36 +++++++++---------- .../Gl200TextProtocolDecoderTest.java | 9 +++-- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java index ffeeab7deeb..0628a06d41e 100644 --- a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java @@ -61,7 +61,7 @@ protected void init() { private static final Pattern PATTERN_ACK = new PatternBuilder() .text("+ACK:GT") .expression("...,") // type - .number("([0-9A-Z]{2}xxxx),") // protocol version + .expression("(.{6}|.{10}),") // protocol version .number("(d{15}|x{14}),") // imei .any().text(",") .number("(dddd)(dd)(dd)") // date (yyyymmdd) @@ -130,7 +130,7 @@ private Long parseHours(String hoursString) { private static final Pattern PATTERN_INF = new PatternBuilder() .text("+").expression("(?:RESP|BUFF):GTINF,") - .number("[0-9A-Z]{2}xxxx,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("(?:[0-9A-Z]{17},)?") // vin .expression("(?:[^,]+)?,") // device name @@ -231,7 +231,7 @@ private Object decodeInf(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_VER = new PatternBuilder() .text("+").expression("(?:RESP|BUFF):GTVER,") - .number("[0-9A-Z]{2}xxxx,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,") // device name .expression("([^,]*),") // device type @@ -340,7 +340,7 @@ private void decodeLocation(Position position, Parser parser) { private static final Pattern PATTERN_OBD = new PatternBuilder() .text("+RESP:GTOBD,") - .number("[0-9A-Z]{2}xxxx,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("(?:[0-9A-Z]{17})?,") // vin .expression("[^,]{0,20},") // device name @@ -636,7 +636,7 @@ private void decodeStatus(Position position, Parser parser) { private static final Pattern PATTERN_FRI = new PatternBuilder() .text("+").expression("(?:RESP|BUFF):GT...,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("(?:([0-9A-Z]{17}),)?") // vin .expression("[^,]*,") // device name @@ -764,7 +764,7 @@ private Object decodeFri(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_ERI = new PatternBuilder() .text("+").expression("(?:RESP|BUFF):GTERI,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,") // device name .number("(x{8}),") // mask @@ -905,7 +905,7 @@ private Object decodeEri(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_IGN = new PatternBuilder() .text("+").expression("(?:RESP|BUFF):GTIG[NF],") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,") // device name .number("d+,") // ignition off duration @@ -939,7 +939,7 @@ private Object decodeIgn(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_LSW = new PatternBuilder() .text("+RESP:").expression("GT[LT]SW,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,") // device name .number("[01],") // type @@ -970,7 +970,7 @@ private Object decodeLsw(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_IDA = new PatternBuilder() .text("+RESP:GTIDA,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,,") // device name .number("([^,]+),") // rfid @@ -1006,7 +1006,7 @@ private Object decodeIda(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_WIF = new PatternBuilder() .text("+RESP:GTWIF,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,") // device name .number("(d+),") // count @@ -1047,7 +1047,7 @@ private Object decodeWif(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_GSM = new PatternBuilder() .text("+RESP:GTGSM,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("(?:STR|CTN|NMR|RTL),") // fix type .expression("(.*)") // cells @@ -1086,7 +1086,7 @@ private Object decodeGsm(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_PNA = new PatternBuilder() .text("+RESP:GT").expression("P[NF]A,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,") // device name .number("(dddd)(dd)(dd)") // date (yyyymmdd) @@ -1112,7 +1112,7 @@ private Object decodePna(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_DAR = new PatternBuilder() .text("+RESP:GTDAR,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,") // device name .number("(d),") // warning type @@ -1151,7 +1151,7 @@ private Object decodeDar(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_DTT = new PatternBuilder() .text("+RESP:GTDTT,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,,,") // device name .number("d,") // data type @@ -1189,7 +1189,7 @@ private Object decodeDtt(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_BAA = new PatternBuilder() .text("+RESP:GTBAA,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,") // device name .number("x+,") // index @@ -1245,7 +1245,7 @@ private Object decodeBaa(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_BID = new PatternBuilder() .text("+RESP:GTBID,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,") // device name .number("d,") // count @@ -1287,7 +1287,7 @@ private Object decodeBid(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN_LSA = new PatternBuilder() .text("+RESP:GTLSA,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,") // device name .number("d,") // event state 1 @@ -1327,7 +1327,7 @@ private Object decodeLsa(Channel channel, SocketAddress remoteAddress, String se private static final Pattern PATTERN = new PatternBuilder() .text("+").expression("(?:RESP|BUFF):GT...,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,") // device name .number("d*,") diff --git a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java index 199012ca02a..2c012eb6fd5 100644 --- a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java @@ -11,18 +11,17 @@ public void testDecode() throws Exception { var decoder = inject(new Gl200TextProtocolDecoder(null)); - verifyAttribute(decoder, buffer( - "+RESP:GTFRI,710303,868487004352084,GL530MG,0,0,1,1,16.6,0,9.4,121.307910,31.127837,20230815050629,0460,0000,1815,B93B,26,0,8964,90,1,0,26.6,20230815130830,0174$"), - Position.PREFIX_TEMP + 1, 26.6); + verifyPositions(decoder, buffer( + "+RESP:GTFRI,8020040305,866314060272661,,,50,1,1,0.0,0,2957.9,-78.691727,-0.951205,20231227162916,,,,,00,0.0,,,,,100,210100,,,,20231227162916,0117$")); - verifyPosition(decoder, buffer( + verifyPositions(decoder, buffer( "+BUFF:GTFRI,8020040200,866314060249032,,12194,10,1,3,0.0,0,20.1,-71.596533,-33.524718,20230926200338,0730,0001,772A,052B253E,02,0,0.0,,,,,0,420000,,,,20230926200340,1549$")); verifyAttribute(decoder, buffer( "+RESP:GTFRI,423037,866884047716519,GT501,0,1,1,5,12,0.1,0,46.8,-95.559173,30.109955,20231110185836,6,0e36c9916485,-50,,,,e831cd5eb79d,-73,,,,ccf4110c4bd5,-79,,,,acdb48973168,-79,,,,80ab4dc323c4,-82,,,,ec8eb5cfa1c6,-89,,,,310,10,711D,81ECF0F,00,,93,20231110185839,0005$"), Position.KEY_BATTERY_LEVEL, 93); - verifyPosition(decoder, buffer( + verifyPositions(decoder, buffer( "+RESP:GTFRI,8020040200,866314060109269,,,10,1,1,0.0,0,9.0,-71.596601,-33.524595,20230722145338,0730,0001,772A,052B253E,00,0.0,,,,,100,210100,,,,20230722145341,0F4C$")); verifyAttributes(decoder, buffer( From 346a860b0aae7097445aa5aa22f37a88d9d7955e Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 7 Jan 2024 12:13:47 -0800 Subject: [PATCH 02/55] User and device expiration emails --- src/main/java/org/traccar/config/Keys.java | 38 +++-- .../org/traccar/schedule/ScheduleManager.java | 3 +- .../org/traccar/schedule/TaskExpirations.java | 130 ++++++++++++++++++ templates/full/deviceExpiration.vm | 7 + templates/full/deviceExpirationReminder.vm | 7 + templates/full/userExpiration.vm | 7 + templates/full/userExpirationReminder.vm | 7 + 7 files changed, 189 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/traccar/schedule/TaskExpirations.java create mode 100644 templates/full/deviceExpiration.vm create mode 100644 templates/full/deviceExpirationReminder.vm create mode 100644 templates/full/userExpiration.vm create mode 100644 templates/full/userExpirationReminder.vm diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 3059c4f4b56..3528dafa087 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -489,14 +489,6 @@ private Keys() { "database.throttleUnknown", List.of(KeyType.CONFIG)); - /** - * By default, server syncs with the database if it encounters and unknown device. This flag allows to disable that - * behavior to improve performance in some cases. - */ - public static final ConfigKey DATABASE_IGNORE_UNKNOWN = new BooleanConfigKey( - "database.ignoreUnknown", - List.of(KeyType.CONFIG)); - /** * Automatically register unknown devices in the database. */ @@ -664,7 +656,7 @@ private Keys() { /** * OpenID Connect Authorization URL. * This can usually be found in the documentation of your identity provider or by using the well-known - * configuration endpoint, eg. https://auth.example.com//.well-known/openid-configuration + * configuration endpoint, e.g. https://auth.example.com//.well-known/openid-configuration * Required to enable SSO if openid.issuerUrl is not set. */ public static final ConfigKey OPENID_AUTH_URL = new StringConfigKey( @@ -1225,6 +1217,34 @@ private Keys() { "notificator.telegram.sendLocation", List.of(KeyType.CONFIG)); + /** + * Enable user expiration email notification. + */ + public static final ConfigKey NOTIFICATION_EXPIRATION_USER = new BooleanConfigKey( + "notification.expiration.user", + List.of(KeyType.CONFIG)); + + /** + * User expiration reminder. Value in milliseconds. + */ + public static final ConfigKey NOTIFICATION_EXPIRATION_USER_REMINDER = new LongConfigKey( + "notification.expiration.user.reminder", + List.of(KeyType.CONFIG)); + + /** + * Enable device expiration email notification. + */ + public static final ConfigKey NOTIFICATION_EXPIRATION_DEVICE = new BooleanConfigKey( + "notification.expiration.device", + List.of(KeyType.CONFIG)); + + /** + * Device expiration reminder. Value in milliseconds. + */ + public static final ConfigKey NOTIFICATION_EXPIRATION_DEVICE_REMINDER = new LongConfigKey( + "notification.expiration.device.reminder", + List.of(KeyType.CONFIG)); + /** * Maximum time period for reports in seconds. Can be useful to prevent users to request unreasonably long reports. * By default there is no limit. diff --git a/src/main/java/org/traccar/schedule/ScheduleManager.java b/src/main/java/org/traccar/schedule/ScheduleManager.java index 38e8f281c41..3756d955b8e 100644 --- a/src/main/java/org/traccar/schedule/ScheduleManager.java +++ b/src/main/java/org/traccar/schedule/ScheduleManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 - 2023 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. @@ -39,6 +39,7 @@ public ScheduleManager(Injector injector) { public void start() { executor = Executors.newSingleThreadScheduledExecutor(); var tasks = List.of( + TaskExpirations.class, TaskDeleteTemporary.class, TaskReports.class, TaskDeviceInactivityCheck.class, diff --git a/src/main/java/org/traccar/schedule/TaskExpirations.java b/src/main/java/org/traccar/schedule/TaskExpirations.java new file mode 100644 index 00000000000..94f855c5fb2 --- /dev/null +++ b/src/main/java/org/traccar/schedule/TaskExpirations.java @@ -0,0 +1,130 @@ +/* + * 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.schedule; + +import jakarta.inject.Inject; +import jakarta.mail.MessagingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.mail.MailManager; +import org.traccar.model.Device; +import org.traccar.model.Disableable; +import org.traccar.model.Server; +import org.traccar.model.User; +import org.traccar.notification.TextTemplateFormatter; +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 java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class TaskExpirations implements ScheduleTask { + + private static final Logger LOGGER = LoggerFactory.getLogger(TaskExpirations.class); + + private static final long CHECK_PERIOD_HOURS = 1; + + private final Config config; + private final Storage storage; + private final TextTemplateFormatter textTemplateFormatter; + private final MailManager mailManager; + + @Inject + public TaskExpirations( + Config config, Storage storage, TextTemplateFormatter textTemplateFormatter, MailManager mailManager) { + this.config = config; + this.storage = storage; + this.textTemplateFormatter = textTemplateFormatter; + this.mailManager = mailManager; + } + + @Override + public void schedule(ScheduledExecutorService executor) { + executor.scheduleAtFixedRate(this, CHECK_PERIOD_HOURS, CHECK_PERIOD_HOURS, TimeUnit.HOURS); + } + + private boolean checkTimeTrigger(Disableable disableable, long currentTime, long offsetTime) { + if (disableable.getExpirationTime() != null) { + long previousTime = currentTime - TimeUnit.HOURS.toMillis(CHECK_PERIOD_HOURS); + long expirationTime = disableable.getExpirationTime().getTime() + offsetTime; + return previousTime < expirationTime && currentTime >= expirationTime; + } + return false; + } + + private void sendUserExpiration( + Server server, User user, String template) throws MessagingException { + var velocityContext = textTemplateFormatter.prepareContext(server, user); + velocityContext.put("expiration", user.getExpirationTime()); + var fullMessage = textTemplateFormatter.formatMessage(velocityContext, template, "full"); + mailManager.sendMessage(user, true, fullMessage.getSubject(), fullMessage.getBody()); + } + + private void sendDeviceExpiration( + Server server, Device device, String template) throws MessagingException, StorageException { + var users = storage.getObjects(User.class, new Request( + new Columns.All(), new Condition.Permission(User.class, Device.class, device.getId()))); + for (User user : users) { + var velocityContext = textTemplateFormatter.prepareContext(server, user); + velocityContext.put("expiration", device.getExpirationTime()); + velocityContext.put("device", device); + var fullMessage = textTemplateFormatter.formatMessage(velocityContext, template, "full"); + mailManager.sendMessage(user, true, fullMessage.getSubject(), fullMessage.getBody()); + } + } + + @Override + public void run() { + try { + + long currentTime = System.currentTimeMillis(); + Server server = storage.getObject(Server.class, new Request(new Columns.All())); + + if (config.getBoolean(Keys.NOTIFICATION_EXPIRATION_USER)) { + long reminder = config.getLong(Keys.NOTIFICATION_EXPIRATION_USER_REMINDER); + var users = storage.getObjects(User.class, new Request(new Columns.All())); + for (User user : users) { + if (checkTimeTrigger(user, currentTime, 0)) { + sendUserExpiration(server, user, "userExpiration"); + } else if (reminder > 0 && checkTimeTrigger(user, currentTime, -reminder)) { + sendUserExpiration(server, user, "userExpirationReminder"); + } + } + } + + if (config.getBoolean(Keys.NOTIFICATION_EXPIRATION_DEVICE)) { + long reminder = config.getLong(Keys.NOTIFICATION_EXPIRATION_USER_REMINDER); + var devices = storage.getObjects(Device.class, new Request(new Columns.All())); + for (Device device : devices) { + if (checkTimeTrigger(device, currentTime, 0)) { + sendDeviceExpiration(server, device, "deviceExpiration"); + } else if (reminder > 0 && checkTimeTrigger(device, currentTime, -reminder)) { + sendDeviceExpiration(server, device, "deviceExpirationReminder"); + } + } + } + + } catch (StorageException | MessagingException e) { + LOGGER.warn("Failed to check expirations", e); + } + } + +} diff --git a/templates/full/deviceExpiration.vm b/templates/full/deviceExpiration.vm new file mode 100644 index 00000000000..879b31778ba --- /dev/null +++ b/templates/full/deviceExpiration.vm @@ -0,0 +1,7 @@ +#set($subject = "Device expiration") + + + +Your device $device.name has expired. + + diff --git a/templates/full/deviceExpirationReminder.vm b/templates/full/deviceExpirationReminder.vm new file mode 100644 index 00000000000..aa47ac0edce --- /dev/null +++ b/templates/full/deviceExpirationReminder.vm @@ -0,0 +1,7 @@ +#set($subject = "Device expiration reminder") + + + +Your device $device.name will expire on $dateTool.format("YYYY-MM-dd", $expiration, $locale, $timezone). + + diff --git a/templates/full/userExpiration.vm b/templates/full/userExpiration.vm new file mode 100644 index 00000000000..43fe2563e9e --- /dev/null +++ b/templates/full/userExpiration.vm @@ -0,0 +1,7 @@ +#set($subject = "Account expiration") + + + +Your user account has expired. + + diff --git a/templates/full/userExpirationReminder.vm b/templates/full/userExpirationReminder.vm new file mode 100644 index 00000000000..ecdee05888c --- /dev/null +++ b/templates/full/userExpirationReminder.vm @@ -0,0 +1,7 @@ +#set($subject = "Account expiration reminder") + + + +Your user account will expire on $dateTool.format("YYYY-MM-dd", $expiration, $locale, $timezone). + + From 64b2d09ecc58ba68eea39fdb538f22accf386e72 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 13 Jan 2024 08:14:29 -0800 Subject: [PATCH 03/55] Add Minifinder config command --- .../java/org/traccar/protocol/Minifinder2Protocol.java | 3 ++- .../org/traccar/protocol/Minifinder2ProtocolEncoder.java | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/traccar/protocol/Minifinder2Protocol.java b/src/main/java/org/traccar/protocol/Minifinder2Protocol.java index c12933b8166..082b9146d34 100644 --- a/src/main/java/org/traccar/protocol/Minifinder2Protocol.java +++ b/src/main/java/org/traccar/protocol/Minifinder2Protocol.java @@ -31,7 +31,8 @@ public class Minifinder2Protocol extends BaseProtocol { @Inject public Minifinder2Protocol(Config config) { setSupportedDataCommands( - Command.TYPE_FIRMWARE_UPDATE); + Command.TYPE_FIRMWARE_UPDATE, + Command.TYPE_CONFIGURATION); addServer(new TrackerServer(config, getName(), false) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolEncoder.java index fab3c3a6d4a..72ac9db4ecb 100644 --- a/src/main/java/org/traccar/protocol/Minifinder2ProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/Minifinder2ProtocolEncoder.java @@ -48,6 +48,13 @@ private ByteBuf encodeContent(ByteBuf content) { @Override protected Object encodeCommand(Command command) { + if (command.getType().equals(Command.TYPE_CONFIGURATION)) { + ByteBuf content = Unpooled.buffer(); + content.writeByte(Minifinder2ProtocolDecoder.MSG_CONFIGURATION); + content.writeByte(1); // length + content.writeByte(0xF0); // type + } + Device device = getCacheManager().getObject(Device.class, command.getDeviceId()); if ("Nano".equalsIgnoreCase(device.getModel())) { ByteBuf content = Unpooled.buffer(); From 093cc360cd1ac64b50b59780b621a58a043e1623 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 14 Jan 2024 08:20:49 -0800 Subject: [PATCH 04/55] Update project dependencies --- build.gradle | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/build.gradle b/build.gradle index a51ebaff011..9239360bcdc 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.0" + toolVersion = "10.12.5" configFile = "gradle/checkstyle.xml" as File checkstyleTest.enabled = false } @@ -27,11 +27,11 @@ enforce { ext { guiceVersion = "7.0.0" - jettyVersion = "11.0.18" - jerseyVersion = "3.1.3" - jacksonVersion = "2.15.2" // same version as jersey-media-json-jackson dependency - protobufVersion = "3.24.0" - jxlsVersion = "2.13.0" + jettyVersion = "11.0.19" + 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" } @@ -45,12 +45,12 @@ dependencies { implementation "commons-codec:commons-codec:1.16.0" 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.0" - implementation "org.postgresql:postgresql:42.6.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.zaxxer:HikariCP:5.1.0" - implementation "io.netty:netty-all:4.1.101.Final" - implementation "org.slf4j:slf4j-jdk14:2.0.9" + implementation "io.netty:netty-all:4.1.104.Final" + implementation "org.slf4j:slf4j-jdk14:2.0.11" implementation "com.google.inject:guice:$guiceVersion" implementation "com.google.inject.extensions:guice-servlet:$guiceVersion" implementation "org.owasp.encoder:encoder:1.2.3" @@ -66,7 +66,7 @@ dependencies { implementation "org.glassfish.jersey.containers:jersey-container-servlet:$jerseyVersion" implementation "org.glassfish.jersey.media:jersey-media-json-jackson:$jerseyVersion" implementation "org.glassfish.jersey.inject:jersey-hk2:$jerseyVersion" - implementation "org.glassfish.hk2:guice-bridge:3.0.4" // same version as jersey-hk2 + implementation "org.glassfish.hk2:guice-bridge:3.0.5" // same version as jersey-hk2 implementation "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jacksonVersion" implementation "com.fasterxml.jackson.datatype:jackson-datatype-jakarta-jsonp:$jacksonVersion" implementation "org.liquibase:liquibase-core:4.23.2" // upgrade has issues @@ -79,20 +79,20 @@ dependencies { implementation "org.mnode.ical4j:ical4j:3.2.14" implementation "org.locationtech.spatial4j:spatial4j:0.8" implementation "org.locationtech.jts:jts-core:1.19.0" - implementation "net.java.dev.jna:jna-platform:5.13.0" + implementation "net.java.dev.jna:jna-platform:5.14.0" implementation "com.github.jnr:jnr-posix:3.1.18" implementation "com.google.protobuf:protobuf-java:$protobufVersion" - implementation "com.amazonaws:aws-java-sdk-sns:1.12.592" - implementation "org.apache.kafka:kafka-clients:3.6.0" + implementation "com.amazonaws:aws-java-sdk-sns:1.12.636" + implementation "org.apache.kafka:kafka-clients:3.6.1" implementation "com.hivemq:hivemq-mqtt-client:1.3.3" - implementation "redis.clients:jedis:5.0.2" + implementation "redis.clients:jedis:5.1.0" implementation "com.google.firebase:firebase-admin:9.2.0" - implementation "com.nimbusds:oauth2-oidc-sdk:11.6" + implementation "com.nimbusds:oauth2-oidc-sdk:11.9.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.7.0" + testImplementation "org.mockito:mockito-core:5.8.0" } test { From 172422eb5262a8e0ff560b689504c0365c8e0503 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 14 Jan 2024 08:23:28 -0800 Subject: [PATCH 05/55] Fix style issues --- src/main/java/org/traccar/WindowsService.java | 4 ++-- src/main/java/org/traccar/database/DeviceLookupService.java | 2 +- src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/traccar/WindowsService.java b/src/main/java/org/traccar/WindowsService.java index f233337a755..08eba25a650 100644 --- a/src/main/java/org/traccar/WindowsService.java +++ b/src/main/java/org/traccar/WindowsService.java @@ -170,7 +170,7 @@ private void reportStatus(int status, int win32ExitCode, int waitHint) { public abstract void run(); - private class ServiceMain implements SERVICE_MAIN_FUNCTION { + private final class ServiceMain implements SERVICE_MAIN_FUNCTION { public void callback(int dwArgc, Pointer lpszArgv) { ServiceControl serviceControl = new ServiceControl(); @@ -203,7 +203,7 @@ public void callback(int dwArgc, Pointer lpszArgv) { } - private class ServiceControl implements HandlerEx { + private final class ServiceControl implements HandlerEx { public int callback(int dwControl, int dwEventType, Pointer lpEventData, Pointer lpContext) { switch (dwControl) { diff --git a/src/main/java/org/traccar/database/DeviceLookupService.java b/src/main/java/org/traccar/database/DeviceLookupService.java index 6ec6841a1f3..90d23531e33 100644 --- a/src/main/java/org/traccar/database/DeviceLookupService.java +++ b/src/main/java/org/traccar/database/DeviceLookupService.java @@ -49,7 +49,7 @@ public class DeviceLookupService { private final boolean throttlingEnabled; - private static class IdentifierInfo { + private static final class IdentifierInfo { private long lastQuery; private long delay; private Timeout timeout; diff --git a/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java index 896c7a2d2c8..9704cf09959 100644 --- a/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java @@ -96,7 +96,7 @@ public float readFloat(ByteBuf buf) { } - private static class TimeReader extends FloatReader { + private static final class TimeReader extends FloatReader { private long weekNumber; From c68318800bac25249ae9b958b8672f2e50dd2db9 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 14 Jan 2024 08:35:53 -0800 Subject: [PATCH 06/55] Update version number --- build.gradle | 2 +- setup/traccar.iss | 2 +- swagger.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 9239360bcdc..41baa7f898f 100644 --- a/build.gradle +++ b/build.gradle @@ -109,7 +109,7 @@ jar { manifest { attributes( "Main-Class": "org.traccar.Main", - "Implementation-Version": "5.10", + "Implementation-Version": "5.11", "Class-Path": configurations.runtimeClasspath.files.collect { "lib/$it.name" }.join(" ")) } } diff --git a/setup/traccar.iss b/setup/traccar.iss index 23daf6e1389..68258dcb58e 100644 --- a/setup/traccar.iss +++ b/setup/traccar.iss @@ -1,6 +1,6 @@ [Setup] AppName=Traccar -AppVersion=5.10 +AppVersion=5.11 DefaultDirName={pf}\Traccar OutputBaseFilename=traccar-setup ArchitecturesInstallIn64BitMode=x64 diff --git a/swagger.json b/swagger.json index 982e1bff1e2..9268e1a0dca 100644 --- a/swagger.json +++ b/swagger.json @@ -2,7 +2,7 @@ "openapi": "3.0.1", "info": { "title": "Traccar", - "version": "5.10", + "version": "5.11", "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", From ba1e70e50f6310ec9f331a7e585f004ca4e17298 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 14 Jan 2024 14:35:12 -0800 Subject: [PATCH 07/55] Update web submodule --- traccar-web | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traccar-web b/traccar-web index dc46059f6bf..d1a9c085716 160000 --- a/traccar-web +++ b/traccar-web @@ -1 +1 @@ -Subproject commit dc46059f6bfeedca04333c2839872055db066dc6 +Subproject commit d1a9c08571683184ce10b7db5890e2a75bf6179c From 079eaa6ed49813f4050076ac45849f6e4334e45b Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Mon, 15 Jan 2024 08:20:34 -0800 Subject: [PATCH 08/55] Fix duplicate notifications (fix #5245) --- src/main/java/org/traccar/database/NotificationManager.java | 2 +- src/main/java/org/traccar/session/cache/CacheManager.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/traccar/database/NotificationManager.java b/src/main/java/org/traccar/database/NotificationManager.java index 45263ff3c16..65437f0a1f7 100644 --- a/src/main/java/org/traccar/database/NotificationManager.java +++ b/src/main/java/org/traccar/database/NotificationManager.java @@ -87,7 +87,7 @@ private void updateEvent(Event event, Position position) { return; } - var notifications = cacheManager.getDeviceNotifications(event.getDeviceId()) + var notifications = cacheManager.getDeviceNotifications(event.getDeviceId()).stream() .filter(notification -> notification.getType().equals(event.getType())) .filter(notification -> { if (event.getType().equals(Event.TYPE_ALARM)) { diff --git a/src/main/java/org/traccar/session/cache/CacheManager.java b/src/main/java/org/traccar/session/cache/CacheManager.java index bb9b4c99537..89b25af2f1e 100644 --- a/src/main/java/org/traccar/session/cache/CacheManager.java +++ b/src/main/java/org/traccar/session/cache/CacheManager.java @@ -136,14 +136,15 @@ public Set getNotificationUsers(long notificationId, long deviceId) { } } - public Stream getDeviceNotifications(long deviceId) { + public Set getDeviceNotifications(long deviceId) { try { lock.readLock().lock(); var direct = graph.getObjects(Device.class, deviceId, Notification.class, Set.of(Group.class), true) .map(BaseModel::getId) .collect(Collectors.toUnmodifiableSet()); return graph.getObjects(Device.class, deviceId, Notification.class, Set.of(Group.class, User.class), true) - .filter(notification -> notification.getAlways() || direct.contains(notification.getId())); + .filter(notification -> notification.getAlways() || direct.contains(notification.getId())) + .collect(Collectors.toUnmodifiableSet()); } finally { lock.readLock().unlock(); } From 2b893df60f62ee8b363cd6dd528c155bb5124c5c Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Mon, 15 Jan 2024 08:23:19 -0800 Subject: [PATCH 09/55] Fix lint issue --- src/main/java/org/traccar/session/cache/CacheManager.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/traccar/session/cache/CacheManager.java b/src/main/java/org/traccar/session/cache/CacheManager.java index 89b25af2f1e..064e5672f99 100644 --- a/src/main/java/org/traccar/session/cache/CacheManager.java +++ b/src/main/java/org/traccar/session/cache/CacheManager.java @@ -49,7 +49,6 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; -import java.util.stream.Stream; @Singleton public class CacheManager implements BroadcastInterface { From a531585474ca9ee7437d2a5b1698d1cf5f507a09 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Mon, 15 Jan 2024 15:49:36 -0800 Subject: [PATCH 10/55] Add notification to templates --- .../java/org/traccar/notification/NotificationFormatter.java | 5 ++++- .../java/org/traccar/notificators/NotificatorFirebase.java | 2 +- src/main/java/org/traccar/notificators/NotificatorMail.java | 2 +- .../java/org/traccar/notificators/NotificatorPushover.java | 2 +- src/main/java/org/traccar/notificators/NotificatorSms.java | 2 +- .../java/org/traccar/notificators/NotificatorTelegram.java | 2 +- .../java/org/traccar/notificators/NotificatorTraccar.java | 2 +- src/main/java/org/traccar/notificators/NotificatorWeb.java | 2 +- 8 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/traccar/notification/NotificationFormatter.java b/src/main/java/org/traccar/notification/NotificationFormatter.java index e994729c026..7685eac0dda 100644 --- a/src/main/java/org/traccar/notification/NotificationFormatter.java +++ b/src/main/java/org/traccar/notification/NotificationFormatter.java @@ -23,6 +23,7 @@ import org.traccar.model.Event; import org.traccar.model.Geofence; import org.traccar.model.Maintenance; +import org.traccar.model.Notification; import org.traccar.model.Position; import org.traccar.model.Server; import org.traccar.model.User; @@ -44,13 +45,15 @@ public NotificationFormatter( this.textTemplateFormatter = textTemplateFormatter; } - public NotificationMessage formatMessage(User user, Event event, Position position, String templatePath) { + public NotificationMessage formatMessage( + Notification notification, User user, Event event, Position position, String templatePath) { Server server = cacheManager.getServer(); Device device = cacheManager.getObject(Device.class, event.getDeviceId()); VelocityContext velocityContext = textTemplateFormatter.prepareContext(server, user); + velocityContext.put("notification", notification); velocityContext.put("device", device); velocityContext.put("event", event); if (position != null) { diff --git a/src/main/java/org/traccar/notificators/NotificatorFirebase.java b/src/main/java/org/traccar/notificators/NotificatorFirebase.java index d75eb21a93e..89031ba2682 100644 --- a/src/main/java/org/traccar/notificators/NotificatorFirebase.java +++ b/src/main/java/org/traccar/notificators/NotificatorFirebase.java @@ -85,7 +85,7 @@ public NotificatorFirebase( public void send(Notification notification, User user, Event event, Position position) throws MessageException { if (user.hasAttribute("notificationTokens")) { - var shortMessage = notificationFormatter.formatMessage(user, event, position, "short"); + var shortMessage = notificationFormatter.formatMessage(notification, user, event, position, "short"); List registrationTokens = new ArrayList<>( Arrays.asList(user.getString("notificationTokens").split("[, ]"))); diff --git a/src/main/java/org/traccar/notificators/NotificatorMail.java b/src/main/java/org/traccar/notificators/NotificatorMail.java index 3ab050686b8..11d4c5baea1 100644 --- a/src/main/java/org/traccar/notificators/NotificatorMail.java +++ b/src/main/java/org/traccar/notificators/NotificatorMail.java @@ -43,7 +43,7 @@ public NotificatorMail(MailManager mailManager, NotificationFormatter notificati @Override public void send(Notification notification, User user, Event event, Position position) throws MessageException { try { - var fullMessage = notificationFormatter.formatMessage(user, event, position, "full"); + var fullMessage = notificationFormatter.formatMessage(notification, user, event, position, "full"); mailManager.sendMessage(user, false, fullMessage.getSubject(), fullMessage.getBody()); } catch (MessagingException e) { throw new MessageException(e); diff --git a/src/main/java/org/traccar/notificators/NotificatorPushover.java b/src/main/java/org/traccar/notificators/NotificatorPushover.java index 9f2a8c94de2..cf4c4026b0e 100644 --- a/src/main/java/org/traccar/notificators/NotificatorPushover.java +++ b/src/main/java/org/traccar/notificators/NotificatorPushover.java @@ -63,7 +63,7 @@ public NotificatorPushover(Config config, NotificationFormatter notificationForm @Override public void send(Notification notification, User user, Event event, Position position) { - var shortMessage = notificationFormatter.formatMessage(user, event, position, "short"); + var shortMessage = notificationFormatter.formatMessage(notification, user, event, position, "short"); Message message = new Message(); message.token = token; diff --git a/src/main/java/org/traccar/notificators/NotificatorSms.java b/src/main/java/org/traccar/notificators/NotificatorSms.java index 2b6b20b1b6c..ce362290efe 100644 --- a/src/main/java/org/traccar/notificators/NotificatorSms.java +++ b/src/main/java/org/traccar/notificators/NotificatorSms.java @@ -46,7 +46,7 @@ public NotificatorSms( @Override public void send(Notification notification, User user, Event event, Position position) throws MessageException { if (user.getPhone() != null) { - var shortMessage = notificationFormatter.formatMessage(user, event, position, "short"); + var shortMessage = notificationFormatter.formatMessage(notification, user, event, position, "short"); statisticsManager.registerSms(); smsManager.sendMessage(user.getPhone(), shortMessage.getBody(), false); } diff --git a/src/main/java/org/traccar/notificators/NotificatorTelegram.java b/src/main/java/org/traccar/notificators/NotificatorTelegram.java index c91aaa4ff8d..eaee3281083 100644 --- a/src/main/java/org/traccar/notificators/NotificatorTelegram.java +++ b/src/main/java/org/traccar/notificators/NotificatorTelegram.java @@ -87,7 +87,7 @@ private LocationMessage createLocationMessage(String messageChatId, Position pos @Override public void send(Notification notification, User user, Event event, Position position) { - var shortMessage = notificationFormatter.formatMessage(user, event, position, "short"); + var shortMessage = notificationFormatter.formatMessage(notification, user, event, position, "short"); TextMessage message = new TextMessage(); message.chatId = user.getString("telegramChatId"); diff --git a/src/main/java/org/traccar/notificators/NotificatorTraccar.java b/src/main/java/org/traccar/notificators/NotificatorTraccar.java index 717742a1ee9..c00e3e02950 100644 --- a/src/main/java/org/traccar/notificators/NotificatorTraccar.java +++ b/src/main/java/org/traccar/notificators/NotificatorTraccar.java @@ -87,7 +87,7 @@ public NotificatorTraccar( public void send(org.traccar.model.Notification notification, User user, Event event, Position position) { if (user.hasAttribute("notificationTokens")) { - var shortMessage = notificationFormatter.formatMessage(user, event, position, "short"); + var shortMessage = notificationFormatter.formatMessage(notification, user, event, position, "short"); NotificationObject item = new NotificationObject(); item.title = shortMessage.getSubject(); diff --git a/src/main/java/org/traccar/notificators/NotificatorWeb.java b/src/main/java/org/traccar/notificators/NotificatorWeb.java index 3a125db3c2c..2b9030226e6 100644 --- a/src/main/java/org/traccar/notificators/NotificatorWeb.java +++ b/src/main/java/org/traccar/notificators/NotificatorWeb.java @@ -51,7 +51,7 @@ public void send(Notification notification, User user, Event event, Position pos copy.setMaintenanceId(event.getMaintenanceId()); copy.getAttributes().putAll(event.getAttributes()); - var message = notificationFormatter.formatMessage(user, event, position, "short"); + var message = notificationFormatter.formatMessage(notification, user, event, position, "short"); copy.set("message", message.getBody()); connectionManager.updateEvent(true, user.getId(), copy); From 72cfe80fb23bb5e1d2aec2f2d2b4cfcd3662a82d Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Mon, 15 Jan 2024 16:29:07 -0800 Subject: [PATCH 11/55] Configurable show unknown logs --- setup/default.xml | 1 + src/main/java/org/traccar/config/Keys.java | 11 +++++++++-- .../java/org/traccar/session/ConnectionManager.java | 6 ++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/setup/default.xml b/setup/default.xml index 48fd8c99312..fbe63c87378 100644 --- a/setup/default.xml +++ b/setup/default.xml @@ -14,6 +14,7 @@ ./modern false false + true true locationiq diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 3528dafa087..1da68e1c0ef 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1247,7 +1247,7 @@ private Keys() { /** * Maximum time period for reports in seconds. Can be useful to prevent users to request unreasonably long reports. - * By default there is no limit. + * By default, there is no limit. */ public static final ConfigKey REPORT_PERIOD_LIMIT = new LongConfigKey( "report.periodLimit", @@ -1798,6 +1798,13 @@ private Keys() { "web.url", List.of(KeyType.CONFIG)); + /** + * Show logs from unknown devices. + */ + public static final ConfigKey WEB_SHOW_UNKNOWN_DEVICES = new BooleanConfigKey( + "web.showUnknownDevices", + List.of(KeyType.CONFIG)); + /** * Output logging to the standard terminal output instead of a log file. */ diff --git a/src/main/java/org/traccar/session/ConnectionManager.java b/src/main/java/org/traccar/session/ConnectionManager.java index a598260aab2..1461c66eadd 100644 --- a/src/main/java/org/traccar/session/ConnectionManager.java +++ b/src/main/java/org/traccar/session/ConnectionManager.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. @@ -63,6 +63,7 @@ public class ConnectionManager implements BroadcastInterface { private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class); private final long deviceTimeout; + private final boolean showUnknownDevices; private final Map sessionsByDeviceId = new ConcurrentHashMap<>(); private final Map> sessionsByEndpoint = new ConcurrentHashMap<>(); @@ -95,6 +96,7 @@ public ConnectionManager( this.broadcastService = broadcastService; this.deviceLookupService = deviceLookupService; deviceTimeout = config.getLong(Keys.STATUS_TIMEOUT); + showUnknownDevices = config.getBoolean(Keys.WEB_SHOW_UNKNOWN_DEVICES); broadcastService.registerListener(this); } @@ -344,7 +346,7 @@ public synchronized void updateLog(LogRecord record) { var sessions = sessionsByEndpoint.getOrDefault(record.getAddress(), Map.of()); if (sessions.isEmpty()) { String unknownUniqueId = unknownByEndpoint.get(record.getAddress()); - if (unknownUniqueId != null) { + if (unknownUniqueId != null && showUnknownDevices) { record.setUniqueId(unknownUniqueId); listeners.values().stream() .flatMap(Set::stream) From acebabc690f78921a1bc10191df092c207393415 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Mon, 15 Jan 2024 16:36:17 -0800 Subject: [PATCH 12/55] Make device sharing configurable --- .../org/traccar/api/resource/DeviceResource.java | 7 +++++++ src/main/java/org/traccar/config/Keys.java | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java index 217ccda6530..540450cbb8e 100644 --- a/src/main/java/org/traccar/api/resource/DeviceResource.java +++ b/src/main/java/org/traccar/api/resource/DeviceResource.java @@ -19,6 +19,8 @@ import org.traccar.api.BaseObjectResource; import org.traccar.api.signature.TokenManager; import org.traccar.broadcast.BroadcastService; +import org.traccar.config.Config; +import org.traccar.config.Keys; import org.traccar.database.MediaManager; import org.traccar.helper.LogAction; import org.traccar.model.Device; @@ -60,6 +62,9 @@ @Consumes(MediaType.APPLICATION_JSON) public class DeviceResource extends BaseObjectResource { + @Inject + private Config config; + @Inject private CacheManager cacheManager; @@ -212,6 +217,8 @@ public String shareDevice( share.setExpirationTime(expiration); share.setTemporary(true); share.setReadonly(true); + share.setLimitCommands(!config.getBoolean(Keys.WEB_SHARE_DEVICE_COMMANDS)); + share.setDisableReports(!config.getBoolean(Keys.WEB_SHARE_DEVICE_REPORTS)); share.setId(storage.addObject(share, new Request(new Columns.Exclude("id")))); diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 1da68e1c0ef..e7926490878 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -1805,6 +1805,20 @@ private Keys() { "web.showUnknownDevices", List.of(KeyType.CONFIG)); + /** + * Enable commands for a shared device. + */ + public static final ConfigKey WEB_SHARE_DEVICE_COMMANDS = new BooleanConfigKey( + "web.shareDevice.commands", + List.of(KeyType.CONFIG)); + + /** + * Enable reports for a shared device. + */ + public static final ConfigKey WEB_SHARE_DEVICE_REPORTS = new BooleanConfigKey( + "web.shareDevice.reports", + List.of(KeyType.CONFIG)); + /** * Output logging to the standard terminal output instead of a log file. */ From 2eb1c791693421607847962cf365847b7ba3dfe2 Mon Sep 17 00:00:00 2001 From: Theerayuttu <149565351+Theerayuttu@users.noreply.github.com> Date: Tue, 16 Jan 2024 12:29:03 +0700 Subject: [PATCH 13/55] Update decoder Alarm for StartekProtocol --- .../java/org/traccar/protocol/StartekProtocolDecoder.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java index 5cfbb36ca77..71afb6ad64c 100644 --- a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java @@ -103,6 +103,12 @@ private String decodeAlarm(int value) { case 5: case 6: return Position.ALARM_DOOR; + case 17: + return Position.ALARM_LOW_POWER; + case 18: + return Position.ALARM_POWER_CUT; + case 19: + return Position.ALARM_POWER_RESTORED; case 39: return Position.ALARM_ACCELERATION; case 40: From 7855762df494ed113210d5d202a13de279b13dcc Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Tue, 16 Jan 2024 20:45:27 -0800 Subject: [PATCH 14/55] Disallow device re-sharing --- src/main/java/org/traccar/api/resource/DeviceResource.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java index 540450cbb8e..d7c5a5477d0 100644 --- a/src/main/java/org/traccar/api/resource/DeviceResource.java +++ b/src/main/java/org/traccar/api/resource/DeviceResource.java @@ -204,6 +204,9 @@ public String shareDevice( @FormParam("expiration") Date expiration) throws StorageException, GeneralSecurityException, IOException { User user = permissionsService.getUser(getUserId()); + if (user.getTemporary()) { + throw new SecurityException("Temporary user"); + } Device device = storage.getObject(Device.class, new Request( new Columns.All(), From 79a4cf9355b08e0b306146a94bd65adb5ed963a5 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Tue, 16 Jan 2024 20:48:48 -0800 Subject: [PATCH 15/55] Return existing user on second share --- .../traccar/api/resource/DeviceResource.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java index d7c5a5477d0..00a3fc5b9f5 100644 --- a/src/main/java/org/traccar/api/resource/DeviceResource.java +++ b/src/main/java/org/traccar/api/resource/DeviceResource.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. @@ -214,18 +214,24 @@ public String shareDevice( new Condition.Equals("id", deviceId), new Condition.Permission(User.class, user.getId(), Device.class)))); - User share = new User(); - share.setName(device.getName()); - share.setEmail(user.getEmail() + ":" + device.getUniqueId()); - share.setExpirationTime(expiration); - share.setTemporary(true); - share.setReadonly(true); - share.setLimitCommands(!config.getBoolean(Keys.WEB_SHARE_DEVICE_COMMANDS)); - share.setDisableReports(!config.getBoolean(Keys.WEB_SHARE_DEVICE_REPORTS)); + String shareEmail = user.getEmail() + ":" + device.getUniqueId(); + User share = storage.getObject(User.class, new Request( + new Columns.All(), new Condition.Equals("email", shareEmail))); - share.setId(storage.addObject(share, new Request(new Columns.Exclude("id")))); + if (share == null) { + share = new User(); + share.setName(device.getName()); + share.setEmail(shareEmail); + share.setExpirationTime(expiration); + share.setTemporary(true); + share.setReadonly(true); + share.setLimitCommands(!config.getBoolean(Keys.WEB_SHARE_DEVICE_COMMANDS)); + share.setDisableReports(!config.getBoolean(Keys.WEB_SHARE_DEVICE_REPORTS)); - storage.addPermission(new Permission(User.class, share.getId(), Device.class, deviceId)); + share.setId(storage.addObject(share, new Request(new Columns.Exclude("id")))); + + storage.addPermission(new Permission(User.class, share.getId(), Device.class, deviceId)); + } return tokenManager.generateToken(share.getId(), expiration); } From 2eba0bdf8c02036d6761667f0aa022ead9587c11 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 18 Jan 2024 06:44:11 -0800 Subject: [PATCH 16/55] Add MT700 battery (fix #5197) --- .../org/traccar/protocol/Tlt2hProtocolDecoder.java | 10 ++++++---- .../org/traccar/protocol/Tlt2hProtocolDecoderTest.java | 5 +++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java index 2e7cdde4e70..79488acc02a 100644 --- a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java @@ -55,7 +55,7 @@ public Tlt2hProtocolDecoder(Protocol protocol) { private static final Pattern PATTERN_POSITION = new PatternBuilder() .text("#") - .number("(?:(dd)|x*)") // cell or voltage + .number("(?:(dd|dddd)|x*)") // cell or voltage .groupBegin() .number("#(d+),") // mcc .number("(d+),") // mnc @@ -77,7 +77,7 @@ public Tlt2hProtocolDecoder(Protocol protocol) { private static final Pattern PATTERN_WIFI = new PatternBuilder() .text("#") - .number("(?:(dd)|x+)") // cell or voltage + .number("(?:(dd|dddd)|x+)") // cell or voltage .expression("#?") .groupBegin() .number("(d+),") // mcc @@ -179,7 +179,8 @@ protected Object decode( if (parser.matches()) { if (parser.hasNext()) { - position.set(Position.KEY_BATTERY, parser.nextInt() * 0.1); + int voltage = parser.nextInt(); + position.set(Position.KEY_BATTERY, voltage > 100 ? voltage * 0.001 : voltage * 0.1); } if (parser.hasNext(4)) { @@ -211,7 +212,8 @@ protected Object decode( if (parser.matches()) { if (parser.hasNext()) { - position.set(Position.KEY_BATTERY, parser.nextInt() * 0.1); + int voltage = parser.nextInt(); + position.set(Position.KEY_BATTERY, voltage > 100 ? voltage * 0.001 : voltage * 0.1); } Network network = new Network(); diff --git a/src/test/java/org/traccar/protocol/Tlt2hProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Tlt2hProtocolDecoderTest.java index cdfae465c07..a64a10450a5 100644 --- a/src/test/java/org/traccar/protocol/Tlt2hProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Tlt2hProtocolDecoderTest.java @@ -11,6 +11,11 @@ public void testDecode() throws Exception { var decoder = inject(new Tlt2hProtocolDecoder(null)); + verifyAttribute(decoder, text( + "#867665041689485#MT700N#0000#HT#1\r\n", + "#5065$GPRMC,148996.00,A,2485.2458,N,01258.4535,E,,,151348,,,A*5D\r\n"), + Position.KEY_BATTERY, 5.065); + verifyPositions(decoder, false, text( "#862255061983166#MT700NW#0000#TOWED#1\r\n", "#4502$WIFI,051550.00,A,-50,7683C2CBC0B0,-51,7683C29BC0B0,-51,7683C2BBC0B0,-51,7483C2DBC0B0,-51,7683C2ABC0B0,221123*78\r\n")); From 22f45bd129f275b930ab2166a6b76713ca972abf Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 18 Jan 2024 07:06:46 -0800 Subject: [PATCH 17/55] Option to disable device sharing --- src/main/java/org/traccar/api/resource/DeviceResource.java | 3 +++ src/main/java/org/traccar/config/Keys.java | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java index 00a3fc5b9f5..c3ced9b846f 100644 --- a/src/main/java/org/traccar/api/resource/DeviceResource.java +++ b/src/main/java/org/traccar/api/resource/DeviceResource.java @@ -204,6 +204,9 @@ public String shareDevice( @FormParam("expiration") Date expiration) throws StorageException, GeneralSecurityException, IOException { User user = permissionsService.getUser(getUserId()); + if (permissionsService.getServer().getBoolean(Keys.DEVICE_SHARE_DISABLE.getKey())) { + throw new SecurityException("Sharing is disabled"); + } if (user.getTemporary()) { throw new SecurityException("Temporary user"); } diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index e7926490878..02e6848759a 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -340,6 +340,13 @@ private Keys() { List.of(KeyType.SERVER, KeyType.DEVICE), 0.0); + /** + * Disable device sharing on the server. + */ + public static final ConfigKey DEVICE_SHARE_DISABLE = new BooleanConfigKey( + "disableShare", + List.of(KeyType.SERVER)); + /** * Speed limit threshold multiplier. For example, if the speed limit is 100, but we only want to generate an event * if the speed is higher than 105, this parameter can be set to 1.05. Default multiplier is 1.0. From bb8ff944398cd79f2d576f25758ea9dd6c35d384 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 18 Jan 2024 08:30:20 -0800 Subject: [PATCH 18/55] iStartek serial interface support --- .../traccar/protocol/StartekFrameDecoder.java | 49 ++++++++ .../org/traccar/protocol/StartekProtocol.java | 5 +- .../protocol/StartekProtocolDecoder.java | 112 +++++++++++++++--- .../protocol/StartekFrameDecoderTest.java | 23 ++++ .../protocol/StartekProtocolDecoderTest.java | 37 +++--- 5 files changed, 191 insertions(+), 35 deletions(-) create mode 100644 src/main/java/org/traccar/protocol/StartekFrameDecoder.java create mode 100644 src/test/java/org/traccar/protocol/StartekFrameDecoderTest.java diff --git a/src/main/java/org/traccar/protocol/StartekFrameDecoder.java b/src/main/java/org/traccar/protocol/StartekFrameDecoder.java new file mode 100644 index 00000000000..608b128cdac --- /dev/null +++ b/src/main/java/org/traccar/protocol/StartekFrameDecoder.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +import java.nio.charset.StandardCharsets; + +public class StartekFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 10) { + return null; + } + + int lengthIndex = buf.readerIndex() + 3; + int dividerIndex = buf.indexOf(lengthIndex, buf.writerIndex(), (byte) ','); + if (dividerIndex > 0) { + int lengthOffset = dividerIndex - buf.readerIndex() + 4; + int length = lengthOffset + Integer.parseInt(buf.getCharSequence( + lengthIndex, dividerIndex - lengthIndex, StandardCharsets.US_ASCII).toString()); + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/StartekProtocol.java b/src/main/java/org/traccar/protocol/StartekProtocol.java index 5505453458a..9c01d24e2e1 100644 --- a/src/main/java/org/traccar/protocol/StartekProtocol.java +++ b/src/main/java/org/traccar/protocol/StartekProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 - 2023 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,6 @@ */ package org.traccar.protocol; -import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; @@ -38,7 +37,7 @@ public StartekProtocol(Config config) { addServer(new TrackerServer(config, getName(), false) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { - pipeline.addLast(new LineBasedFrameDecoder(1100)); + pipeline.addLast(new StartekFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StartekProtocolEncoder(StartekProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java index 71afb6ad64c..0eeb5b2aa7b 100644 --- a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 - 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. @@ -41,12 +41,13 @@ public StartekProtocolDecoder(Protocol protocol) { .expression(".") // index .number("d+,") // length .number("(d+),") // imei + .number("(xxx),") // type .expression("(.+)") // content .number("xx") // checksum + .text("\r\n") .compile(); private static final Pattern PATTERN_POSITION = new PatternBuilder() - .number("xxx,") // command .number("(d+),") // event .expression("([^,]+)?,") // event data .number("(dd)(dd)(dd)") // date (yyymmdd) @@ -134,26 +135,24 @@ protected Object decode( return null; } + String type = parser.next(); String content = parser.next(); - if (content.length() < 100) { - - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); - - getLastLocation(position, null); - - position.set(Position.KEY_RESULT, content); - - return position; - - } else { - - return decodePosition(deviceSession, content); - + switch (type) { + case "000": + return decodePosition(deviceSession, content); + case "710": + return decodeSerial(deviceSession, content); + default: + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, null); + position.set(Position.KEY_TYPE, type); + position.set(Position.KEY_RESULT, content); + return position; } } - protected Object decodePosition(DeviceSession deviceSession, String content) throws Exception { + private Object decodePosition(DeviceSession deviceSession, String content) { Parser parser = new Parser(PATTERN_POSITION, content); if (!parser.matches()) { @@ -255,4 +254,81 @@ protected Object decodePosition(DeviceSession deviceSession, String content) thr return position; } + private Object decodeSerial(DeviceSession deviceSession, String content) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + String[] frames = content.split("\r\n"); + + for (String frame : frames) { + String[] values = frame.split(","); + int index = 0; + String type = values[index++]; + switch (type) { + case "T1": + index += 1; // speed + position.set(Position.KEY_RPM, Double.parseDouble(values[index++])); + index += 1; // fuel consumption + position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(values[index++])); + index += 4; // axel weights + index += 1; // turbo pressure + position.set(Position.KEY_COOLANT_TEMP, Integer.parseInt(values[index++])); + index += 1; // accelerator pedal + position.set("torque", Integer.parseInt(values[index++])); + index += 1; // firmware version + position.set(Position.KEY_POWER, Double.parseDouble(values[index++])); + index += 1; // coolant level + position.set("oilTemp", Double.parseDouble(values[index++])); + index += 1; // oil level + position.set(Position.KEY_THROTTLE, Double.parseDouble(values[index++])); + index += 1; // air inlet pressure + index += 1; // fuel tank secondary + index += 1; // current gear + index += 1; // seatbelt + position.set("oilPressure", Integer.parseInt(values[index++])); + index += 1; // wet tank air pressure + index += 1; // pto state + int ignition = Integer.parseInt(values[index++]); + if (ignition < 2) { + position.set(Position.KEY_IGNITION, ignition > 0); + } + index += 1; // brake pedal + position.set("catalystLevel", Double.parseDouble(values[index++])); + index += 1; // fuel type + break; + case "T2": + position.set(Position.KEY_ODOMETER, Double.parseDouble(values[index++]) * 1000); + index += 1; // total fuel + index += 1; // fuel used cruise + index += 1; // fuel used drive + index += 1; + index += 1; + index += 1; // total idle time + index += 1; // total time pto + index += 1; // time cruise + index += 1; + index += 1; + index += 1; + index += 1; + index += 1; + index += 1; // brake apps + index += 1; // clutch apps + position.set(Position.KEY_HOURS, Integer.parseInt(values[index++])); + index += 1; // time torque + position.set(Position.KEY_FUEL_CONSUMPTION, Double.parseDouble(values[index++])); + index += 1; // total cruise control distance + position.set(Position.KEY_FUEL_USED, Double.parseDouble(values[index++])); + index += 1; // total drive time + break; + default: + break; + } + } + + return position; + } + } diff --git a/src/test/java/org/traccar/protocol/StartekFrameDecoderTest.java b/src/test/java/org/traccar/protocol/StartekFrameDecoderTest.java new file mode 100644 index 00000000000..789126471d7 --- /dev/null +++ b/src/test/java/org/traccar/protocol/StartekFrameDecoderTest.java @@ -0,0 +1,23 @@ +package org.traccar.protocol; + +import org.junit.jupiter.api.Test; +import org.traccar.ProtocolTest; + +public class StartekFrameDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + var decoder = inject(new StartekFrameDecoder()); + + verifyFrame( + binary("26265c3134332c3836353439313036393537333134302c3030302c302c2c3234303130343136303031362c412c2d362e3239303633382c3130362e3830393537382c31352c302e382c302c3238322c35372c3338382c3531307c31307c303444457c30303030373442452c33312c30303030303033432c30302c30302c303444347c303141327c303030307c303030302c312c2c2c31350d0a"), + decoder.decode(null, null, binary("26265c3134332c3836353439313036393537333134302c3030302c302c2c3234303130343136303031362c412c2d362e3239303633382c3130362e3830393537382c31352c302e382c302c3238322c35372c3338382c3531307c31307c303444457c30303030373442452c33312c30303030303033432c30302c30302c303444347c303141327c303030307c303030302c312c2c2c31350d0a"))); + + verifyFrame( + binary("26265c3534362c3836353439313036313134353937302c3731302c54312c302e302c302e302c302e302c302e302c302c302c302c302c302e302c302c302e302c302c46302c302e302c302e302c302e302c302e302c302e302c302c302e302c302c302c302c302c302c312c302c302e302c30302c302e302c302e302c302e302c300d0a54322c302e3030302c302e302c393232333337323033363835343737353830382e382c393232333337323033363835343737353830382e382c343239343936373239352c343239343936373239352c302c3432393439363732392c302c302c302c302c302c302c302c302c302c302c302e302c32313437343833362e302c393232333337323033363835343737353830382e382c302c30302c302c302c302e30300d0a54352c302c302c302c302c302c302c302c302c302c302c302c302c302e302c302e302c2a2c2a2c300d0a54362c30302c30332c30302c31462c31462c31462c30452c30332c30302c30302c30302c30302c31462c31460d0a54372c302c302c302c3432393439363732392c32313437343833362e3030302c3432393439363732392c302e3030302c302c302e3030302c302c302e3030302c302c302e3030302c302c302e3030302c302c302e3030302c302c302e3030302c302c302e3030302c302c302e3030302c302c302c302c302e3030300d0a54782c2a2c2a2c2a2c2a2c2a2c302e302c302c302c302c302c2d3132352c302c302c302c302c302c302c300d0a46330d0a"), + decoder.decode(null, null, binary("26265c3534362c3836353439313036313134353937302c3731302c54312c302e302c302e302c302e302c302e302c302c302c302c302c302e302c302c302e302c302c46302c302e302c302e302c302e302c302e302c302e302c302c302e302c302c302c302c302c302c312c302c302e302c30302c302e302c302e302c302e302c300d0a54322c302e3030302c302e302c393232333337323033363835343737353830382e382c393232333337323033363835343737353830382e382c343239343936373239352c343239343936373239352c302c3432393439363732392c302c302c302c302c302c302c302c302c302c302c302e302c32313437343833362e302c393232333337323033363835343737353830382e382c302c30302c302c302c302e30300d0a54352c302c302c302c302c302c302c302c302c302c302c302c302c302e302c302e302c2a2c2a2c300d0a54362c30302c30332c30302c31462c31462c31462c30452c30332c30302c30302c30302c30302c31462c31460d0a54372c302c302c302c3432393439363732392c32313437343833362e3030302c3432393439363732392c302e3030302c302c302e3030302c302c302e3030302c302c302e3030302c302c302e3030302c302c302e3030302c302c302e3030302c302c302e3030302c302c302e3030302c302c302c302c302e3030300d0a54782c2a2c2a2c2a2c2a2c2a2c302e302c302c302c302c302c2d3132352c302c302c302c302c302c302c300d0a46330d0a"))); + + } + +} diff --git a/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java index 94b3fd2568a..0d951625620 100644 --- a/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/StartekProtocolDecoderTest.java @@ -11,49 +11,58 @@ public void testDecode() throws Exception { var decoder = inject(new StartekProtocolDecoder(null)); + verifyAttributes(decoder, text( + "&&\\546,865491061145970,710,T1,0.0,0.0,0.0,0.0,0,0,0,0,0.0,0,0.0,0,F0,0.0,0.0,0.0,0.0,0.0,0,0.0,0,0,0,0,0,1,0,0.0,00,0.0,0.0,0.0,0\r\n", + "T2,0.000,0.0,9223372036854775808.8,9223372036854775808.8,4294967295,4294967295,0,429496729,0,0,0,0,0,0,0,0,0,0,0.0,21474836.0,9223372036854775808.8,0,00,0,0,0.00\r\n", + "T5,0,0,0,0,0,0,0,0,0,0,0,0,0.0,0.0,*,*,0\r\n", + "T6,00,03,00,1F,1F,1F,0E,03,00,00,00,00,1F,1F\r\n", + "T7,0,0,0,429496729,21474836.000,429496729,0.000,0,0.000,0,0.000,0,0.000,0,0.000,0,0.000,0,0.000,0,0.000,0,0.000,0,0,0,0.000\r\n", + "Tx,*,*,*,*,*,0.0,0,0,0,0,-125,0,0,0,0,0,0,0\r\n", + "F3\r\n")); + verifyPosition(decoder, text( - "&&l141,863911061945394,000,0,,230918072531,A,22.678598,114.045970,26,0.6,0,0,74,2286304571,460|0|249F|00001093,20,001C,00,00,04A7|019C|0000|0000,1,C0")); + "&&l141,863911061945394,000,0,,230918072531,A,22.678598,114.045970,26,0.6,0,0,74,2286304571,460|0|249F|00001093,20,001C,00,00,04A7|019C|0000|0000,1,C0\r\n")); verifyAttribute(decoder, text( - "&&s148,868703050178631,000,37,,230704040211,A,22.678565,114.046011,31,0.5,0,339,77,8,460|0|249F|0AC2620D,27,0000001D,02,00,04F2|01A1|0000|0000,129,,,,949037"), + "&&s148,868703050178631,000,37,,230704040211,A,22.678565,114.046011,31,0.5,0,339,77,8,460|0|249F|0AC2620D,27,0000001D,02,00,04F2|01A1|0000|0000,129,,,,949037\r\n"), Position.KEY_HOURS, 9490000L); verifyAttribute(decoder, text( - "&&x164,869926040743375,000,0,,220705205955,A,33.326001,44.445318,10,1.2,0,57,8,925,418|40|038C|000083CD,31,00000015,00,00,0016|016A|0000|0000,1,,,686|33||44|99|14|124|11|8D"), + "&&x164,869926040743375,000,0,,220705205955,A,33.326001,44.445318,10,1.2,0,57,8,925,418|40|038C|000083CD,31,00000015,00,00,0016|016A|0000|0000,1,,,686|33||44|99|14|124|11|8D\r\n"), Position.KEY_FUEL_CONSUMPTION, 1.1); verifyAttribute(decoder, text( - "&&R187,860294046453690,000,0,,220105160656,A,22.994986,72.499711,15,0.9,2,222,55,121135784,404|98|147B|0000376A,24,0000001F,02,00,052E|01A3|0000|0000,1,010000|020000,,853|6|10|105|73|41|125|34|52"), + "&&R187,860294046453690,000,0,,220105160656,A,22.994986,72.499711,15,0.9,2,222,55,121135784,404|98|147B|0000376A,24,0000001F,02,00,052E|01A3|0000|0000,1,010000|020000,,853|6|10|105|73|41|125|34|52\r\n"), Position.KEY_FUEL_LEVEL, null); verifyPosition(decoder, text( - "&&o142,860262050066062,000,27,,211111070826,V,28.653435,-106.077455,0,0.0,0,151,1412,918,0|0|4708|01402D19,6,0000001A,02,00,04C0|016C|0000|0000,1,,,BB")); + "&&o142,860262050066062,000,27,,211111070826,V,28.653435,-106.077455,0,0.0,0,151,1412,918,0|0|4708|01402D19,6,0000001A,02,00,04C0|016C|0000|0000,1,,,BB\r\n")); verifyPosition(decoder, text( - "&&W149,865429043319537,000,0,,211103013512,A,22.679003,114.045085,16,1.1,0,271,76,109075,460|0|249F|000010C5,19,0000003E,00,00,0A57|0168|0000|0000,1,0100000C")); + "&&W149,865429043319537,000,0,,211103013512,A,22.679003,114.045085,16,1.1,0,271,76,109075,460|0|249F|000010C5,19,0000003E,00,00,0A57|0168|0000|0000,1,0100000C\r\n")); verifyAttribute(decoder, text( - "&&:23,860262050015424,129,OKA2"), - Position.KEY_RESULT, "129,OK"); + "&&:23,860262050015424,129,OKA2\r\n"), + Position.KEY_RESULT, "OK"); verifyPosition(decoder, text( - "&&X152,861157040151686,000,18,,210907163833,A,10.232715,-67.880423,11,1.4,0,275,437,34804,734|2|3EE4|00579406,28,00000015,00,00,0000|017D|0000|0000,1,010000,,9A")); + "&&X152,861157040151686,000,18,,210907163833,A,10.232715,-67.880423,11,1.4,0,275,437,34804,734|2|3EE4|00579406,28,00000015,00,00,0000|017D|0000|0000,1,010000,,9A\r\n")); verifyPosition(decoder, text( - "&&o125,861157040554384,000,0,,210702235150,A,27.263505,153.037061,11,1.2,0,0,31,5125,505|1|7032|8C89802,20,0000002D,00,00,01E2|019DF0")); + "&&o125,861157040554384,000,0,,210702235150,A,27.263505,153.037061,11,1.2,0,0,31,5125,505|1|7032|8C89802,20,0000002D,00,00,01E2|019DF0\r\n")); verifyAttribute(decoder, text( - "&&a152,860262050010565,000,53,8F5300,210528015706,A,-38.229746,145.043446,6,1.5,0,285,84,2102994,505|1|306E|082D6101,31,0000003D,02,02,04C0|01A0|0000|0000,1,,DC"), + "&&a152,860262050010565,000,53,8F5300,210528015706,A,-38.229746,145.043446,6,1.5,0,285,84,2102994,505|1|306E|082D6101,31,0000003D,02,02,04C0|01A0|0000|0000,1,,DC\r\n"), Position.KEY_DRIVER_UNIQUE_ID, "8F5300"); verifyPosition(decoder, text( - "&&>141,860262050010565,000,36,,210407094323,V,-38.229711,145.043161,0,0.0,0,0,0,14222,505|1|306E|082D6115,24,00000039,00,00,04C0|0164|0000|0000,1,,41")); + "&&>141,860262050010565,000,36,,210407094323,V,-38.229711,145.043161,0,0.0,0,0,0,14222,505|1|306E|082D6115,24,00000039,00,00,04C0|0164|0000|0000,1,,41\r\n")); verifyPosition(decoder, text( - "&&A147,021104023195429,000,0,,180106093046,A,22.646430,114.065730,8,0.9,54,86,76,326781,460|0|27B3|0EA7,27,0000000F,02,01,04E2|018C|01C8|0000,1,0104B0,01013D|02813546")); + "&&A147,021104023195429,000,0,,180106093046,A,22.646430,114.065730,8,0.9,54,86,76,326781,460|0|27B3|0EA7,27,0000000F,02,01,04E2|018C|01C8|0000,1,0104B0,01013D|02813546\r\n")); verifyPosition(decoder, text( - "&&y139,860262050009146,000,0,,210323131512,A,22.678655,114.046223,14,1.1,0,231,71,5,460|0|249F|0099C257,28,0000003D,00,00,0493|0199|0000|0000,1,,33")); + "&&y139,860262050009146,000,0,,210323131512,A,22.678655,114.046223,14,1.1,0,231,71,5,460|0|249F|0099C257,28,0000003D,00,00,0493|0199|0000|0000,1,,33\r\n")); } From 77a2b4f5e587f648502f3a72e525b7c960e58c80 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Fri, 19 Jan 2024 07:57:49 -0800 Subject: [PATCH 19/55] Support NatureLink parameters --- .../protocol/HuabaoProtocolDecoder.java | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java index a102e9e442b..2243bd982a1 100644 --- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java @@ -471,8 +471,12 @@ private Position decodeLocation(DeviceSession deviceSession, ByteBuf buf) { case 0x02: position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShort() * 0.1); break; + case 0x25: + position.set(Position.KEY_INPUT, buf.readUnsignedInt()); + break; case 0x2b: - position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedInt()); + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShort()); break; case 0x30: position.set(Position.KEY_RSSI, buf.readUnsignedByte()); @@ -517,6 +521,36 @@ private Position decodeLocation(DeviceSession deviceSession, ByteBuf buf) { buf.readUnsignedByte(); // rssi } break; + case 0x64: + buf.readUnsignedInt(); // alarm serial number + buf.readUnsignedByte(); // alarm status + position.set("adasAlarm", buf.readUnsignedByte()); + break; + case 0x65: + buf.readUnsignedInt(); // alarm serial number + buf.readUnsignedByte(); // alarm status + position.set("dmsAlarm", buf.readUnsignedByte()); + break; + case 0x70: + buf.readUnsignedInt(); // alarm serial number + buf.readUnsignedByte(); // alarm status + switch (buf.readUnsignedByte()) { + case 0x01: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 0x02: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 0x03: + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + break; + case 0x16: + position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT); + break; + default: + break; + } + break; case 0x69: position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); break; @@ -571,7 +605,11 @@ private Position decodeLocation(DeviceSession deviceSession, ByteBuf buf) { break; case 0xD4: case 0xE1: - position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + if (length == 1) { + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + } else { + position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(buf.readUnsignedInt())); + } break; case 0xD5: if (length == 2) { @@ -594,6 +632,9 @@ private Position decodeLocation(DeviceSession deviceSession, ByteBuf buf) { position.set(Position.KEY_MOTION, BitUtil.check(deviceStatus, 2)); position.set("cover", BitUtil.check(deviceStatus, 3)); break; + case 0xE2: + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedInt() * 0.1); + break; case 0xE6: while (buf.readerIndex() < endIndex) { int sensorIndex = buf.readUnsignedByte(); From 7374a69f188918cbd247047fcb5fe09dabffa400 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 20 Jan 2024 10:20:51 -0800 Subject: [PATCH 20/55] Ruptela FM Pro4 attributes --- .../protocol/RuptelaProtocolDecoder.java | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java index cde626c5f1a..55b3c900f73 100644 --- a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -112,6 +112,10 @@ private void decodeParameter(Position position, int id, ByteBuf buf, int length) case 5: position.set(Position.PREFIX_IN + (id - 1), readValue(buf, length, false)); break; + case 13: + case 173: + position.set(Position.KEY_MOTION, readValue(buf, length, false) > 0); + break; case 20: position.set(Position.PREFIX_ADC + 3, readValue(buf, length, false)); break; @@ -133,6 +137,9 @@ private void decodeParameter(Position position, int id, ByteBuf buf, int length) case 32: position.set(Position.KEY_DEVICE_TEMP, readValue(buf, length, true)); break; + case 39: + position.set(Position.KEY_ENGINE_LOAD, readValue(buf, length, true)); + break; case 65: position.set(Position.KEY_ODOMETER, readValue(buf, length, true)); break; @@ -149,9 +156,18 @@ private void decodeParameter(Position position, int id, ByteBuf buf, int length) position.set(Position.KEY_ALARM, Position.ALARM_JAMMING); } break; + case 94: + position.set(Position.KEY_RPM, readValue(buf, length, true) * 0.25); + break; case 95: position.set(Position.KEY_OBD_SPEED, readValue(buf, length, true)); break; + case 98: + position.set("fuelRate", readValue(buf, length, true) * 100 / 255.0); + break; + case 100: + position.set(Position.KEY_FUEL_CONSUMPTION, readValue(buf, length, true) / 20.0); + break; case 134: if (readValue(buf, length, false) > 0) { position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); @@ -165,15 +181,31 @@ private void decodeParameter(Position position, int id, ByteBuf buf, int length) case 150: position.set(Position.KEY_OPERATOR, readValue(buf, length, false)); break; - case 170: - position.set(Position.KEY_CHARGE, readValue(buf, length, false) > 0); + case 163: + position.set(Position.KEY_ODOMETER, readValue(buf, length, false) * 5); break; - case 173: - position.set(Position.KEY_MOTION, readValue(buf, length, false) > 0); + case 164: + position.set(Position.KEY_ODOMETER_TRIP, readValue(buf, length, false) * 5); + break; + case 165: + position.set(Position.KEY_OBD_SPEED, readValue(buf, length, false) / 256.0); break; + case 166: case 197: position.set(Position.KEY_RPM, readValue(buf, length, false) * 0.125); break; + case 170: + position.set(Position.KEY_CHARGE, readValue(buf, length, false) > 0); + break; + case 205: + position.set(Position.KEY_FUEL_LEVEL, readValue(buf, length, false)); + break; + case 207: + position.set(Position.KEY_FUEL_LEVEL, readValue(buf, length, false) * 0.4); + break; + case 208: + position.set(Position.KEY_FUEL_USED, readValue(buf, length, false) * 0.5); + break; case 251: case 409: position.set(Position.KEY_IGNITION, readValue(buf, length, false) > 0); From 8d2accad7071321c65055d8fb9e11d2a6a586b02 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 20 Jan 2024 10:35:13 -0800 Subject: [PATCH 21/55] Missing Meitrack T399 parameters --- .../java/org/traccar/protocol/MeitrackProtocolDecoder.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java index c37d1fe4750..965c8ee0de0 100644 --- a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java @@ -465,6 +465,9 @@ private List decodeBinaryE(Channel channel, SocketAddress remoteAddres case 0x16: position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShortLE() * 0.01); break; + case 0x17: + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShortLE() * 0.01); + break; case 0x19: position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.01); break; From d4d66200bf1de4a1932765690ffb9eedd58f0900 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 21 Jan 2024 08:17:47 -0800 Subject: [PATCH 22/55] Improve sharing permissions --- src/main/java/org/traccar/api/resource/DeviceResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java index c3ced9b846f..8e634232acc 100644 --- a/src/main/java/org/traccar/api/resource/DeviceResource.java +++ b/src/main/java/org/traccar/api/resource/DeviceResource.java @@ -228,8 +228,8 @@ public String shareDevice( share.setExpirationTime(expiration); share.setTemporary(true); share.setReadonly(true); - share.setLimitCommands(!config.getBoolean(Keys.WEB_SHARE_DEVICE_COMMANDS)); - share.setDisableReports(!config.getBoolean(Keys.WEB_SHARE_DEVICE_REPORTS)); + share.setLimitCommands(user.getLimitCommands() || !config.getBoolean(Keys.WEB_SHARE_DEVICE_COMMANDS)); + share.setDisableReports(user.getDisableReports() || !config.getBoolean(Keys.WEB_SHARE_DEVICE_REPORTS)); share.setId(storage.addObject(share, new Request(new Columns.Exclude("id")))); From 4d9d78496a0260c6cb43211065a8aafe8cc7e7a9 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 21 Jan 2024 08:21:01 -0800 Subject: [PATCH 23/55] Improve sharing expiration limit --- src/main/java/org/traccar/api/resource/DeviceResource.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java index 8e634232acc..89bba7237bf 100644 --- a/src/main/java/org/traccar/api/resource/DeviceResource.java +++ b/src/main/java/org/traccar/api/resource/DeviceResource.java @@ -210,6 +210,9 @@ public String shareDevice( if (user.getTemporary()) { throw new SecurityException("Temporary user"); } + if (user.getExpirationTime() != null && user.getExpirationTime().before(expiration)) { + expiration = user.getExpirationTime(); + } Device device = storage.getObject(Device.class, new Request( new Columns.All(), From 29b8caa047b3b1468eb8b21e76f5df8b5c2f6ddb Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 25 Jan 2024 08:11:32 -0800 Subject: [PATCH 24/55] Initialization grace period (fix #5258) --- src/main/java/org/traccar/Main.java | 32 ++++++-------- .../org/traccar/schedule/ScheduleManager.java | 11 ++--- .../org/traccar/schedule/TaskClearStatus.java | 43 +++++++++++++++++++ .../org/traccar/schedule/TaskHealthCheck.java | 24 ++++++++--- 4 files changed, 78 insertions(+), 32 deletions(-) create mode 100644 src/main/java/org/traccar/schedule/TaskClearStatus.java diff --git a/src/main/java/org/traccar/Main.java b/src/main/java/org/traccar/Main.java index e34fbb72a14..33fcf654f83 100644 --- a/src/main/java/org/traccar/Main.java +++ b/src/main/java/org/traccar/Main.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. @@ -20,10 +20,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.broadcast.BroadcastService; -import org.traccar.helper.model.DeviceUtil; import org.traccar.schedule.ScheduleManager; import org.traccar.storage.DatabaseModule; -import org.traccar.storage.Storage; import org.traccar.web.WebModule; import org.traccar.web.WebServer; @@ -33,10 +31,9 @@ import java.lang.management.OperatingSystemMXBean; import java.lang.management.RuntimeMXBean; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; public final class Main { @@ -70,8 +67,7 @@ public static void logSystemInfo() { + " heap: " + memoryBean.getHeapMemoryUsage().getMax() / (1024 * 1024) + "mb" + " non-heap: " + memoryBean.getNonHeapMemoryUsage().getMax() / (1024 * 1024) + "mb"); - LOGGER.info("Character encoding: " - + System.getProperty("file.encoding") + " charset: " + Charset.defaultCharset()); + LOGGER.info("Character encoding: " + Charset.defaultCharset().displayName()); } catch (Exception error) { LOGGER.warn("Failed to get system info"); @@ -122,18 +118,14 @@ public static void run(String configFile) { LOGGER.info("Version: " + Main.class.getPackage().getImplementationVersion()); LOGGER.info("Starting server..."); - if (injector.getInstance(BroadcastService.class).singleInstance()) { - DeviceUtil.resetStatus(injector.getInstance(Storage.class)); - } - - var services = Stream.of( - ServerManager.class, WebServer.class, ScheduleManager.class, BroadcastService.class) - .map(injector::getInstance) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - for (var service : services) { - service.start(); + var services = new ArrayList(); + for (var clazz : List.of( + ScheduleManager.class, ServerManager.class, WebServer.class, BroadcastService.class)) { + var service = injector.getInstance(clazz); + if (service != null) { + service.start(); + services.add(service); + } } Thread.setDefaultUncaughtExceptionHandler((t, e) -> LOGGER.error("Thread exception", e)); diff --git a/src/main/java/org/traccar/schedule/ScheduleManager.java b/src/main/java/org/traccar/schedule/ScheduleManager.java index 3756d955b8e..742428fd8fa 100644 --- a/src/main/java/org/traccar/schedule/ScheduleManager.java +++ b/src/main/java/org/traccar/schedule/ScheduleManager.java @@ -20,9 +20,9 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; -import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.stream.Stream; @Singleton public class ScheduleManager implements LifecycleObject { @@ -38,14 +38,15 @@ public ScheduleManager(Injector injector) { @Override public void start() { executor = Executors.newSingleThreadScheduledExecutor(); - var tasks = List.of( + Stream.of( + TaskHealthCheck.class, + TaskClearStatus.class, TaskExpirations.class, TaskDeleteTemporary.class, TaskReports.class, TaskDeviceInactivityCheck.class, - TaskWebSocketKeepalive.class, - TaskHealthCheck.class); - tasks.forEach(task -> injector.getInstance(task).schedule(executor)); + TaskWebSocketKeepalive.class) + .forEachOrdered(task -> injector.getInstance(task).schedule(executor)); } @Override diff --git a/src/main/java/org/traccar/schedule/TaskClearStatus.java b/src/main/java/org/traccar/schedule/TaskClearStatus.java new file mode 100644 index 00000000000..78fecc0ea24 --- /dev/null +++ b/src/main/java/org/traccar/schedule/TaskClearStatus.java @@ -0,0 +1,43 @@ +/* + * 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.schedule; + +import jakarta.inject.Inject; +import org.traccar.broadcast.BroadcastService; +import org.traccar.helper.model.DeviceUtil; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; + +import java.util.concurrent.ScheduledExecutorService; + +public class TaskClearStatus implements ScheduleTask { + + @Inject + public TaskClearStatus(BroadcastService broadcastService, Storage storage) throws StorageException { + if (broadcastService.singleInstance()) { + DeviceUtil.resetStatus(storage); + } + } + + @Override + public void schedule(ScheduledExecutorService executor) { + } + + @Override + public void run() { + } + +} diff --git a/src/main/java/org/traccar/schedule/TaskHealthCheck.java b/src/main/java/org/traccar/schedule/TaskHealthCheck.java index abdc5af48ee..a60935f1804 100644 --- a/src/main/java/org/traccar/schedule/TaskHealthCheck.java +++ b/src/main/java/org/traccar/schedule/TaskHealthCheck.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. @@ -34,6 +34,8 @@ public class TaskHealthCheck implements ScheduleTask { private final Config config; private final Client client; + private final long gracePeriod = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); + private SystemD systemD; private boolean enabled; @@ -77,14 +79,22 @@ public void schedule(ScheduledExecutorService executor) { @Override public void run() { LOGGER.debug("Health check running"); - int status = client.target(getUrl()).request().get().getStatus(); - if (status == 200) { - int result = systemD.sd_notify(0, "WATCHDOG=1"); - if (result < 0) { - LOGGER.warn("Health check notify error {}", result); + if (System.currentTimeMillis() > gracePeriod) { + int status = client.target(getUrl()).request().get().getStatus(); + if (status == 200) { + notifyWatchdog(); + } else { + LOGGER.warn("Health check failed with status {}", status); } } else { - LOGGER.warn("Health check failed with status {}", status); + notifyWatchdog(); + } + } + + private void notifyWatchdog() { + int result = systemD.sd_notify(0, "WATCHDOG=1"); + if (result < 0) { + LOGGER.warn("Health check notify error {}", result); } } From e5c21c80df6310ea29209dc037ba82be5fbf441e Mon Sep 17 00:00:00 2001 From: Sanjeewa Vithanage Date: Fri, 26 Jan 2024 17:03:02 +0200 Subject: [PATCH 25/55] analog input voltage decode --- src/main/java/org/traccar/protocol/UproProtocolDecoder.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java index ed714e46438..debb85365e7 100644 --- a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java @@ -310,6 +310,11 @@ protected Object decode( position.set("serial", data.toString(StandardCharsets.US_ASCII).substring(3)); } break; + case 'd': + if (data.readableBytes() > 3) { + position.set(Position.PREFIX_ADC + 1, Integer.parseInt(data.readSlice(5).toString(StandardCharsets.US_ASCII)) * 0.01); + } + break; default: break; } From 97f0ed1d19bc638ff5866bcdf5163f89801859b0 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Fri, 26 Jan 2024 07:23:13 -0800 Subject: [PATCH 26/55] Add MD500S test case --- .../org/traccar/protocol/MeitrackProtocolDecoderTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/org/traccar/protocol/MeitrackProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/MeitrackProtocolDecoderTest.java index 24dc9d18efa..74a180b1171 100644 --- a/src/test/java/org/traccar/protocol/MeitrackProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/MeitrackProtocolDecoderTest.java @@ -11,6 +11,10 @@ public void testDecode() throws Exception { var decoder = inject(new MeitrackProtocolDecoder(null)); + verifyAttribute(decoder, binary( + "24246D3230312C3836393430393036323730323834332C4343452C000000000100A7002A000D05010626070914001502930194009500960097629D209E63A1640E0824000956000A07000B2A001606001704001901001A0E0B91280092280099EF049C52009F1B004023000C0215B9F2FF035855F506041E0F142D0C01708C010D748AC2001C012000009A000000009BD0623E02A0889FF201A2D61A0000A542020000FEF4A3D50900030E0CFE010A007F466FFC0000000049090401000000000000004B0501010232472A44360D0A"), + Position.KEY_HOURS, 644515 * 60000L); + verifyAttribute(decoder, binary( "2424593136312c3836323039303035303031363139332c4343452c0000000001007f0017000705010607071714001500fe69601b00070800000971000a13000b19001605001acc0440230006029779570103eb5bcc06041ff0e8290c430100000d780400001c01000000030e0ccc010000922781abb90ca4fffe731e0109746e6873656e736f72ac233f6e219064051b753b00000000000000004b060101034c54452a42380d0a"), "tagName", "tnhsensor"); From b82bacce74f38d34cbf16749ca23961eb0f8a919 Mon Sep 17 00:00:00 2001 From: Sanjeewa Vithanage Date: Fri, 26 Jan 2024 17:32:50 +0200 Subject: [PATCH 27/55] Line wrapper --- src/main/java/org/traccar/protocol/UproProtocolDecoder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java index debb85365e7..2f88d803c82 100644 --- a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java @@ -312,7 +312,8 @@ protected Object decode( break; case 'd': if (data.readableBytes() > 3) { - position.set(Position.PREFIX_ADC + 1, Integer.parseInt(data.readSlice(5).toString(StandardCharsets.US_ASCII)) * 0.01); + position.set(Position.PREFIX_ADC + 1, Integer.parseInt(data.readSlice(5). + toString(StandardCharsets.US_ASCII)) * 0.01); } break; default: From 29d50ef59b59026925b580c72c27aed09ab78500 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Fri, 26 Jan 2024 18:03:01 -0800 Subject: [PATCH 28/55] Fix Ruptela fuel level --- src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java index 55b3c900f73..40226234890 100644 --- a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java @@ -163,7 +163,7 @@ private void decodeParameter(Position position, int id, ByteBuf buf, int length) position.set(Position.KEY_OBD_SPEED, readValue(buf, length, true)); break; case 98: - position.set("fuelRate", readValue(buf, length, true) * 100 / 255.0); + position.set(Position.KEY_FUEL_LEVEL, readValue(buf, length, true) * 100 / 255.0); break; case 100: position.set(Position.KEY_FUEL_CONSUMPTION, readValue(buf, length, true) / 20.0); From d1473309d3338c2f247e4d80f5b1076da4882253 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 27 Jan 2024 07:46:33 -0800 Subject: [PATCH 29/55] Update version number --- build.gradle | 2 +- setup/traccar.iss | 2 +- swagger.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 41baa7f898f..3677f8f940a 100644 --- a/build.gradle +++ b/build.gradle @@ -109,7 +109,7 @@ jar { manifest { attributes( "Main-Class": "org.traccar.Main", - "Implementation-Version": "5.11", + "Implementation-Version": "5.12", "Class-Path": configurations.runtimeClasspath.files.collect { "lib/$it.name" }.join(" ")) } } diff --git a/setup/traccar.iss b/setup/traccar.iss index 68258dcb58e..2ccee1c3e8a 100644 --- a/setup/traccar.iss +++ b/setup/traccar.iss @@ -1,6 +1,6 @@ [Setup] AppName=Traccar -AppVersion=5.11 +AppVersion=5.12 DefaultDirName={pf}\Traccar OutputBaseFilename=traccar-setup ArchitecturesInstallIn64BitMode=x64 diff --git a/swagger.json b/swagger.json index 9268e1a0dca..b2209a20e64 100644 --- a/swagger.json +++ b/swagger.json @@ -2,7 +2,7 @@ "openapi": "3.0.1", "info": { "title": "Traccar", - "version": "5.11", + "version": "5.12", "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", From b098be7f6245dcd2c00cc8457174e9fe7733d3b2 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 27 Jan 2024 08:18:22 -0800 Subject: [PATCH 30/55] Update submodule commit --- traccar-web | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/traccar-web b/traccar-web index d1a9c085716..42db1a41cbf 160000 --- a/traccar-web +++ b/traccar-web @@ -1 +1 @@ -Subproject commit d1a9c08571683184ce10b7db5890e2a75bf6179c +Subproject commit 42db1a41cbf733ea4f82c86e5d45a6fbccc8b8f2 From 34b507dbe268c6aec2f20e9ec3c3f9e243187c3e Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 27 Jan 2024 14:41:05 -0800 Subject: [PATCH 31/55] Initial FleetGuide implementation --- setup/default.xml | 1 + .../traccar/protocol/FleetGuideProtocol.java | 36 +++++++ .../protocol/FleetGuideProtocolDecoder.java | 98 +++++++++++++++++++ .../FleetGuideProtocolDecoderTest.java | 20 ++++ 4 files changed, 155 insertions(+) create mode 100644 src/main/java/org/traccar/protocol/FleetGuideProtocol.java create mode 100644 src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java create mode 100644 src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java diff --git a/setup/default.xml b/setup/default.xml index fbe63c87378..b89e3ebef0e 100644 --- a/setup/default.xml +++ b/setup/default.xml @@ -296,5 +296,6 @@ 5251 5252 5253 + 5254 diff --git a/src/main/java/org/traccar/protocol/FleetGuideProtocol.java b/src/main/java/org/traccar/protocol/FleetGuideProtocol.java new file mode 100644 index 00000000000..46611c25c3f --- /dev/null +++ b/src/main/java/org/traccar/protocol/FleetGuideProtocol.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import jakarta.inject.Inject; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.config.Config; + +public class FleetGuideProtocol extends BaseProtocol { + + @Inject + public FleetGuideProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new FleetGuideProtocolDecoder(FleetGuideProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java new file mode 100644 index 00000000000..d6318792147 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java @@ -0,0 +1,98 @@ +/* + * Copyright 2024 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.session.DeviceSession; + +import java.lang.reflect.Method; +import java.net.SocketAddress; + +public class FleetGuideProtocolDecoder extends BaseProtocolDecoder { + + public FleetGuideProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_DATA = 0x10; + public static final int MSG_HEARTBEAT = 0x1A; + public static final int MSG_RESPONSE = 0x1C; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // signature + int options = buf.readUnsignedShort(); + int length = BitUtil.to(options, 11); + + DeviceSession deviceSession; + if (BitUtil.check(options, 11)) { + deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(buf.readUnsignedInt())); + } else { + deviceSession = getDeviceSession(channel, remoteAddress); + } + if (deviceSession == null) { + return null; + } + + int type; + if (BitUtil.check(options, 12)) { + type = BitUtil.to(buf.readUnsignedByte(), 4); + } else { + type = 0; + } + + if (BitUtil.check(options, 13)) { + buf.readUnsignedShort(); // acknowledgement + } + + ByteBuf data; + if (BitUtil.check(options, 14)) { + data = decompress(buf.readSlice(length)); + } else { + data = buf.readRetainedSlice(length); + } + + data.release(); + + return null; + } + + private ByteBuf decompress(ByteBuf input) throws ReflectiveOperationException { + + Class clazz = Class.forName("io.netty.handler.codec.compression.FastLz"); + Method method = clazz.getDeclaredMethod( + "decompress", ByteBuf.class, int.class, int.class, ByteBuf.class, int.class, int.class); + + ByteBuf output = Unpooled.buffer(); + int result = (Integer) method.invoke( + null, input, input.readerIndex(), input.readableBytes(), output, 0, Integer.MAX_VALUE); + if (result <= 0) { + throw new IllegalArgumentException("FastLz decompression failed"); + } + + return output; + } + +} diff --git a/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java new file mode 100644 index 00000000000..010f0d649df --- /dev/null +++ b/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java @@ -0,0 +1,20 @@ +package org.traccar.protocol; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.traccar.ProtocolTest; + +@Disabled +public class FleetGuideProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + var decoder = inject(new FleetGuideProtocolDecoder(null)); + + verifyPosition(decoder, binary( + "539e598f8a020003020700e378351ac39f040c04ffa92e806a28a13b1f00b638030c000067052c06ffffffff033006808001180003200100000700e478351ac40204303dab2e80cb27a13b1c00ac40021b30e778351ac502043082a72e8054020530bf30021b30e978351ac69f0d0c0433a72e80c123a13b2000df3807002579351ac79f06020a170000df28021b476179351ac8020f30021c81279d79351ac9020f30021c8207d979351aca020f30021c8157157a351acb022b8140517a351acc022b608d7a351acd022b60c97a351ace022b30057b351acf022b30417b351ad0022b307d7b351ad1020f3048021b30b97b351ad2020f3070030c004066021630f57b351ad30213841080021730317c351ad4021330c00217306d7c351ad5020f3050021b30a97c351ad602138170021830e57c351ad7020f3058021b30217d351ad8021385500218305d7d351ad9022b8110997d351ada022b8170d57d351adb022b30117e351adc020f3068030ce184680216304d7e351add020f3060030ce34469021630897e351ade021260e4021830c57e351adf021230e5021830017f351ae002293022f2")); + + } + +} From 7577626863a370056169386860a3e8805f45663b Mon Sep 17 00:00:00 2001 From: Sanjeewa Vithanage Date: Sun, 28 Jan 2024 07:36:38 +0200 Subject: [PATCH 32/55] test decode --- .../java/org/traccar/protocol/UproProtocolDecoderTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java index c99166374b7..ca2944c0408 100644 --- a/src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java @@ -83,6 +83,10 @@ public void testDecode() throws Exception { verifyPosition(decoder, buffer( "*AI2000905300036,AD1&A1703054913231101844949860000251115&B0500000000&C0;4?72:9&F0000")); + verifyAttribute(decoder, buffer( + "*HQ200862312328000001,AD1&A1520441548253003503696640017270124&B0000000000&C00000117&F0000&R2118&N01&V0125&X(J01E0)&K00300&Z000&d01286"), + Position.PREFIX_ADC + 1, 12.86); + } } From 2f49889aa496524b298f764e18f8016e74efab87 Mon Sep 17 00:00:00 2001 From: Sanjeewa Vithanage Date: Sun, 28 Jan 2024 09:28:34 +0200 Subject: [PATCH 33/55] fixed with unwanted decimal points ("adc1":12.870000000000001) --- src/main/java/org/traccar/protocol/UproProtocolDecoder.java | 6 ++---- .../java/org/traccar/protocol/UproProtocolDecoderTest.java | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java index 2f88d803c82..1167f568cd6 100644 --- a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java @@ -311,10 +311,8 @@ protected Object decode( } break; case 'd': - if (data.readableBytes() > 3) { - position.set(Position.PREFIX_ADC + 1, Integer.parseInt(data.readSlice(5). - toString(StandardCharsets.US_ASCII)) * 0.01); - } + position.set(Position.PREFIX_ADC + 1, Integer.parseInt(data. + toString(StandardCharsets.US_ASCII))/100.0); break; default: break; diff --git a/src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java index ca2944c0408..f070c620169 100644 --- a/src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/UproProtocolDecoderTest.java @@ -87,6 +87,10 @@ public void testDecode() throws Exception { "*HQ200862312328000001,AD1&A1520441548253003503696640017270124&B0000000000&C00000117&F0000&R2118&N01&V0125&X(J01E0)&K00300&Z000&d01286"), Position.PREFIX_ADC + 1, 12.86); + verifyAttribute(decoder, buffer( + "*HQ200862312328000001,BA&A1520461548253003503696640017270124&B0000000000&C00000117&F0000&R2218&N01&V0125&X(J01E0)&K00300&Z000&d01287"), + Position.PREFIX_ADC + 1, 12.87); + } } From 52dcedfe60e5b9468db16e7d72048595bcdeb98f Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 28 Jan 2024 08:15:46 -0800 Subject: [PATCH 34/55] Support time maintenance --- .../events/MaintenanceEventHandler.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java index 6c4271ce231..2fa2e8869a6 100644 --- a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java +++ b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -49,8 +49,8 @@ protected Map analyzePosition(Position position) { Map events = new HashMap<>(); for (Maintenance maintenance : cacheManager.getDeviceObjects(position.getDeviceId(), Maintenance.class)) { if (maintenance.getPeriod() != 0) { - double oldValue = lastPosition.getDouble(maintenance.getType()); - double newValue = position.getDouble(maintenance.getType()); + double oldValue = getValue(lastPosition, maintenance.getType()); + double newValue = getValue(position, maintenance.getType()); if (oldValue != 0.0 && newValue != 0.0 && newValue >= maintenance.getStart()) { if (oldValue < maintenance.getStart() || (long) ((oldValue - maintenance.getStart()) / maintenance.getPeriod()) @@ -67,4 +67,17 @@ protected Map analyzePosition(Position position) { return events; } + private double getValue(Position position, String type) { + switch (type) { + case "serverTime": + return position.getServerTime().getTime(); + case "deviceTime": + return position.getDeviceTime().getTime(); + case "fixTime": + return position.getFixTime().getTime(); + default: + return position.getDouble(type); + } + } + } From 04a99c14fc2840f78e988d66c150807abae8bf79 Mon Sep 17 00:00:00 2001 From: Sanjeewa Vithanage Date: Mon, 29 Jan 2024 12:11:02 +0200 Subject: [PATCH 35/55] fix styling --- src/main/java/org/traccar/protocol/UproProtocolDecoder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java index 1167f568cd6..915f5adfe68 100644 --- a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java @@ -311,8 +311,8 @@ protected Object decode( } break; case 'd': - position.set(Position.PREFIX_ADC + 1, Integer.parseInt(data. - toString(StandardCharsets.US_ASCII))/100.0); + position.set(Position.PREFIX_ADC + 1, + (Integer.parseInt(data.toString(StandardCharsets.US_ASCII)) / 100.0)); break; default: break; From 7726ea879700329b62700528ad495f4f1c515dd0 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Mon, 29 Jan 2024 06:48:30 -0800 Subject: [PATCH 36/55] Remove unnecessary brackets --- src/main/java/org/traccar/protocol/UproProtocolDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java index 915f5adfe68..8d2e5de0ad4 100644 --- a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java @@ -312,7 +312,7 @@ protected Object decode( break; case 'd': position.set(Position.PREFIX_ADC + 1, - (Integer.parseInt(data.toString(StandardCharsets.US_ASCII)) / 100.0)); + Integer.parseInt(data.toString(StandardCharsets.US_ASCII)) / 100.0); break; default: break; From b7d405e9d5cc64b88dfd09cd43d621338f550d68 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Mon, 29 Jan 2024 20:43:38 -0800 Subject: [PATCH 37/55] Minor FleetGuide updates --- .../org/traccar/protocol/FleetGuideProtocolDecoder.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java index d6318792147..4e5de2b3e48 100644 --- a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java @@ -43,12 +43,12 @@ protected Object decode( ByteBuf buf = (ByteBuf) msg; buf.readUnsignedByte(); // signature - int options = buf.readUnsignedShort(); + int options = buf.readUnsignedShortLE(); int length = BitUtil.to(options, 11); DeviceSession deviceSession; if (BitUtil.check(options, 11)) { - deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(buf.readUnsignedInt())); + deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(buf.readUnsignedIntLE())); } else { deviceSession = getDeviceSession(channel, remoteAddress); } @@ -64,7 +64,7 @@ protected Object decode( } if (BitUtil.check(options, 13)) { - buf.readUnsignedShort(); // acknowledgement + buf.readUnsignedShortLE(); // acknowledgement } ByteBuf data; @@ -84,6 +84,7 @@ private ByteBuf decompress(ByteBuf input) throws ReflectiveOperationException { Class clazz = Class.forName("io.netty.handler.codec.compression.FastLz"); Method method = clazz.getDeclaredMethod( "decompress", ByteBuf.class, int.class, int.class, ByteBuf.class, int.class, int.class); + method.setAccessible(true); ByteBuf output = Unpooled.buffer(); int result = (Integer) method.invoke( From 5c1a520850acced42cf81b3974134067182b6a99 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Tue, 30 Jan 2024 17:35:37 -0800 Subject: [PATCH 38/55] Fix Ruptela unsigned parameters --- .../traccar/protocol/RuptelaProtocolDecoder.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java index 40226234890..e1efb5757ba 100644 --- a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java @@ -138,10 +138,10 @@ private void decodeParameter(Position position, int id, ByteBuf buf, int length) position.set(Position.KEY_DEVICE_TEMP, readValue(buf, length, true)); break; case 39: - position.set(Position.KEY_ENGINE_LOAD, readValue(buf, length, true)); + position.set(Position.KEY_ENGINE_LOAD, readValue(buf, length, false)); break; case 65: - position.set(Position.KEY_ODOMETER, readValue(buf, length, true)); + position.set(Position.KEY_ODOMETER, readValue(buf, length, false)); break; case 74: position.set(Position.PREFIX_TEMP + 3, readValue(buf, length, true) * 0.1); @@ -157,16 +157,16 @@ private void decodeParameter(Position position, int id, ByteBuf buf, int length) } break; case 94: - position.set(Position.KEY_RPM, readValue(buf, length, true) * 0.25); + position.set(Position.KEY_RPM, readValue(buf, length, false) * 0.25); break; case 95: - position.set(Position.KEY_OBD_SPEED, readValue(buf, length, true)); + position.set(Position.KEY_OBD_SPEED, readValue(buf, length, false)); break; case 98: - position.set(Position.KEY_FUEL_LEVEL, readValue(buf, length, true) * 100 / 255.0); + position.set(Position.KEY_FUEL_LEVEL, readValue(buf, length, false) * 100 / 255.0); break; case 100: - position.set(Position.KEY_FUEL_CONSUMPTION, readValue(buf, length, true) / 20.0); + position.set(Position.KEY_FUEL_CONSUMPTION, readValue(buf, length, false) / 20.0); break; case 134: if (readValue(buf, length, false) > 0) { @@ -226,7 +226,7 @@ private void decodeParameter(Position position, int id, ByteBuf buf, int length) } break; case 645: - position.set(Position.KEY_OBD_ODOMETER, readValue(buf, length, true) * 1000); + position.set(Position.KEY_OBD_ODOMETER, readValue(buf, length, false) * 1000); break; case 758: if (readValue(buf, length, false) == 1) { From cff42765e3ab39dccb73ea7d49f4b6270fe9931a Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Wed, 31 Jan 2024 08:24:52 -0800 Subject: [PATCH 39/55] Add LZ77 decompression algorithm --- .../protocol/FleetGuideProtocolDecoder.java | 49 ++++++++++++++----- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java index 4e5de2b3e48..94d8371454c 100644 --- a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java @@ -23,7 +23,6 @@ import org.traccar.helper.BitUtil; import org.traccar.session.DeviceSession; -import java.lang.reflect.Method; import java.net.SocketAddress; public class FleetGuideProtocolDecoder extends BaseProtocolDecoder { @@ -79,21 +78,47 @@ protected Object decode( return null; } - private ByteBuf decompress(ByteBuf input) throws ReflectiveOperationException { + private int readVarSize(ByteBuf buf) { + int b; + int y = 0; + do { + b = buf.readUnsignedByte(); + y = (y << 7) | (b & 0x0000007f); + } while ((b & 0x00000080) > 0); - Class clazz = Class.forName("io.netty.handler.codec.compression.FastLz"); - Method method = clazz.getDeclaredMethod( - "decompress", ByteBuf.class, int.class, int.class, ByteBuf.class, int.class, int.class); - method.setAccessible(true); + return y; + } + + private ByteBuf decompress(ByteBuf in) { + + ByteBuf out = Unpooled.buffer(); - ByteBuf output = Unpooled.buffer(); - int result = (Integer) method.invoke( - null, input, input.readerIndex(), input.readableBytes(), output, 0, Integer.MAX_VALUE); - if (result <= 0) { - throw new IllegalArgumentException("FastLz decompression failed"); + if (in.readableBytes() < 1) { + return out; } - return output; + int marker = in.readUnsignedByte(); + + do { + int symbol = in.readUnsignedByte(); + if (symbol == marker) { + if (in.getUnsignedByte(in.readerIndex()) == 0) { + out.writeByte(marker); + in.skipBytes(1); + } else { + int length = readVarSize(in); + int offset = readVarSize(in); + + for (int i = 0; i < length; i++) { + out.writeByte(out.getUnsignedByte(out.writerIndex() - offset)); + } + } + } else { + out.writeByte(symbol); + } + } while (in.isReadable()); + + return out; } } From 7d82b0f88197f8ebfcef949abec4ddca07d9ed87 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 1 Feb 2024 07:49:28 -0800 Subject: [PATCH 40/55] Decode FleetGuide locations --- .../protocol/FleetGuideProtocolDecoder.java | 77 ++++++++++++++++++- .../FleetGuideProtocolDecoderTest.java | 4 +- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java index 94d8371454c..9fddec922d5 100644 --- a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java @@ -21,9 +21,16 @@ import org.traccar.BaseProtocolDecoder; import org.traccar.Protocol; import org.traccar.helper.BitUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; import org.traccar.session.DeviceSession; import java.net.SocketAddress; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; public class FleetGuideProtocolDecoder extends BaseProtocolDecoder { @@ -73,9 +80,77 @@ protected Object decode( data = buf.readRetainedSlice(length); } + List positions = new LinkedList<>(); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + Set recordTypes = new HashSet<>(); + + while (data.isReadable()) { + + int recordHeader = data.readUnsignedShortLE(); + int recordLength = BitUtil.to(recordHeader, 10); + int recordType = BitUtil.from(recordHeader, 10); + int recordEndIndex = data.readerIndex() + recordLength; + + if (recordTypes.contains(recordType)) { + positions.add(processPosition(position)); + position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + recordTypes.clear(); + } + recordTypes.add(recordType); + + switch (recordType) { + case 0: + position.setTime(new Date((data.readUnsignedIntLE() + 1262304000) * 1000)); // since 2010-01-01 + break; + case 1: + position.setLatitude(data.readUnsignedIntLE() * 90.0 / 0xFFFFFFFFL); + position.setLongitude(data.readUnsignedIntLE() * 180.0 / 0xFFFFFFFFL); + int speed = data.readUnsignedShortLE(); + position.setSpeed(UnitsConverter.knotsFromKph(BitUtil.to(speed, 14) * 0.1)); + if (BitUtil.check(speed, 14)) { + position.setLatitude(-position.getLatitude()); + } + if (BitUtil.check(speed, 15)) { + position.setLongitude(-position.getLongitude()); + } + int course = data.readUnsignedShortLE(); + position.setSpeed(BitUtil.to(course, 9)); + int motion = BitUtil.between(course, 9, 11); + if (motion > 0) { + position.set(Position.KEY_MOTION, motion == 1); + } + position.set(Position.KEY_SATELLITES, BitUtil.from(course, 11)); + int altitude = data.readUnsignedShortLE(); + position.setAltitude(BitUtil.to(altitude, 14)); + if (BitUtil.check(altitude, 14)) { + position.setAltitude(-position.getAltitude()); + } + break; + default: + break; + } + + data.readerIndex(recordEndIndex); + + } + data.release(); - return null; + return positions.isEmpty() ? null : positions; + } + + private Position processPosition(Position position) { + if (position.getFixTime() == null) { + position.setTime(new Date()); + } + if (!position.getAttributes().containsKey(Position.KEY_SATELLITES)) { + getLastLocation(position, null); + } + return position; } private int readVarSize(ByteBuf buf) { diff --git a/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java index 010f0d649df..34b15d14b02 100644 --- a/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java @@ -1,10 +1,8 @@ package org.traccar.protocol; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.traccar.ProtocolTest; -@Disabled public class FleetGuideProtocolDecoderTest extends ProtocolTest { @Test @@ -12,7 +10,7 @@ public void testDecode() throws Exception { var decoder = inject(new FleetGuideProtocolDecoder(null)); - verifyPosition(decoder, binary( + verifyPositions(decoder, binary( "539e598f8a020003020700e378351ac39f040c04ffa92e806a28a13b1f00b638030c000067052c06ffffffff033006808001180003200100000700e478351ac40204303dab2e80cb27a13b1c00ac40021b30e778351ac502043082a72e8054020530bf30021b30e978351ac69f0d0c0433a72e80c123a13b2000df3807002579351ac79f06020a170000df28021b476179351ac8020f30021c81279d79351ac9020f30021c8207d979351aca020f30021c8157157a351acb022b8140517a351acc022b608d7a351acd022b60c97a351ace022b30057b351acf022b30417b351ad0022b307d7b351ad1020f3048021b30b97b351ad2020f3070030c004066021630f57b351ad30213841080021730317c351ad4021330c00217306d7c351ad5020f3050021b30a97c351ad602138170021830e57c351ad7020f3058021b30217d351ad8021385500218305d7d351ad9022b8110997d351ada022b8170d57d351adb022b30117e351adc020f3068030ce184680216304d7e351add020f3060030ce34469021630897e351ade021260e4021830c57e351adf021230e5021830017f351ae002293022f2")); } From 1e0cc806b7386680492cb3b6b266bb4f958b2f1e Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 1 Feb 2024 09:14:28 -0800 Subject: [PATCH 41/55] Decode Eview Pet configuration --- .../java/org/traccar/helper/BufferUtil.java | 4 + .../protocol/Minifinder2ProtocolDecoder.java | 172 +++++++++++++++++- .../Minifinder2ProtocolDecoderTest.java | 3 + 3 files changed, 178 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/traccar/helper/BufferUtil.java b/src/main/java/org/traccar/helper/BufferUtil.java index 12c31ba9df5..b453c437f8f 100644 --- a/src/main/java/org/traccar/helper/BufferUtil.java +++ b/src/main/java/org/traccar/helper/BufferUtil.java @@ -83,4 +83,8 @@ public static boolean isPrintable(ByteBuf buf, int length) { return printable; } + public static String readString(ByteBuf buf, int length) { + return buf.readCharSequence(length, StandardCharsets.US_ASCII).toString(); + } + } diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java index 57ceab4c7e8..64373e3446d 100644 --- a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; +import org.traccar.helper.BufferUtil; import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; @@ -315,6 +316,10 @@ protected Object decode( return positions; + } else if (type == MSG_CONFIGURATION) { + + return decodeConfiguration(channel, remoteAddress, buf); + } else if (type == MSG_RESPONSE) { DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); @@ -337,4 +342,169 @@ protected Object decode( return null; } + private Position decodeConfiguration(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + while (buf.isReadable()) { + int length = buf.readUnsignedByte() - 1; + int endIndex = buf.readerIndex() + length + 1; + int key = buf.readUnsignedByte(); + + switch (key) { + case 0x01: + position.set("moduleNumber", buf.readUnsignedInt()); + break; + case 0x02: + position.set(Position.KEY_VERSION_FW, String.valueOf(buf.readUnsignedInt())); + break; + case 0x03: + position.set("imei", buf.readCharSequence(length, StandardCharsets.US_ASCII).toString()); + break; + case 0x04: + position.set(Position.KEY_ICCID, BufferUtil.readString(buf, length)); + break; + case 0x05: + position.set("bleMac", ByteBufUtil.hexDump(buf.readSlice(length))); + break; + case 0x06: + position.set("settingTime", buf.readUnsignedInt()); + break; + case 0x07: + position.set("runTimes", buf.readUnsignedInt()); + break; + case 0x0A: + position.set("interval", buf.readUnsignedMedium()); + position.set("petMode", buf.readUnsignedByte()); + break; + case 0x0D: + position.set("passwordProtect", buf.readUnsignedInt()); + break; + case 0x0E: + position.set("timeZone", (int) buf.readByte()); + break; + case 0x0F: + position.set("enableControl", buf.readUnsignedInt()); + break; + case 0x13: + position.set("deviceName", BufferUtil.readString(buf, length)); + break; + case 0x14: + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.001); + break; + case 0x15: + position.set("bleLatitude", buf.readIntLE() * 0.0000001); + position.set("bleLongitude", buf.readIntLE() * 0.0000001); + position.set("bleLocation", BufferUtil.readString(buf, length - 8)); + break; + case 0x17: + position.set("gpsUrl", BufferUtil.readString(buf, length)); + break; + case 0x18: + position.set("lbsUrl", BufferUtil.readString(buf, length)); + break; + case 0x1A: + position.set("firmware", BufferUtil.readString(buf, length)); + break; + case 0x1B: + position.set("gsmModule", BufferUtil.readString(buf, length)); + break; + case 0x1D: + position.set("agpsUpdate", buf.readUnsignedByte()); + position.set("agpsLatitude", buf.readIntLE() * 0.0000001); + position.set("agpsLongitude", buf.readIntLE() * 0.0000001); + break; + case 0x30: + position.set("numberFlag", buf.readUnsignedByte()); + position.set("number", BufferUtil.readString(buf, length - 1)); + break; + case 0x31: + position.set("prefixFlag", buf.readUnsignedByte()); + position.set("prefix", BufferUtil.readString(buf, length - 1)); + break; + case 0x33: + position.set("phoneSwitches", buf.readUnsignedByte()); + break; + case 0x40: + position.set("apn", BufferUtil.readString(buf, length)); + break; + case 0x41: + position.set("apnUser", BufferUtil.readString(buf, length)); + break; + case 0x42: + position.set("apnPassword", BufferUtil.readString(buf, length)); + break; + case 0x43: + buf.readUnsignedByte(); // flag + position.set("port", buf.readUnsignedShort()); + position.set("server", BufferUtil.readString(buf, length - 3)); + break; + case 0x44: + position.set("heartbeatInterval", buf.readUnsignedInt()); + position.set("uploadInterval", buf.readUnsignedInt()); + position.set("uploadLazyInterval", buf.readUnsignedInt()); + break; + case 0x47: + position.set("deviceId", BufferUtil.readString(buf, length)); + break; + case 0x4E: + position.set("gsmBand", buf.readUnsignedByte()); + break; + case 0x50: + position.set("powerAlert", buf.readUnsignedInt()); + break; + case 0x51: + position.set("geoAlert", buf.readUnsignedInt()); + break; + case 0x53: + position.set("motionAlert", buf.readUnsignedInt()); + break; + case 0x5C: + position.set("barkLevel", buf.readUnsignedByte()); + position.set("barkInterval", buf.readUnsignedInt()); + break; + case 0x61: + position.set("msisdn", BufferUtil.readString(buf, length)); + break; + case 0x62: + position.set("wifiWhitelist", buf.readUnsignedByte()); + position.set("wifiWhitelistMac", ByteBufUtil.hexDump(buf.readSlice(6))); + break; + case 0x64: + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + position.set("networkBand", buf.readUnsignedInt()); + position.set(Position.KEY_OPERATOR, BufferUtil.readString(buf, length - 5)); + break; + case 0x65: + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + position.set("networkStatus", buf.readUnsignedByte()); + position.set("serverStatus", buf.readUnsignedByte()); + position.set("networkPlmn", ByteBufUtil.hexDump(buf.readSlice(6))); + position.set("homePlmn", ByteBufUtil.hexDump(buf.readSlice(6))); + break; + case 0x66: + position.set("imsi", BufferUtil.readString(buf, length)); + break; + case 0x75: + position.set("extraEnableControl", buf.readUnsignedInt()); + break; + default: + break; + } + + buf.readerIndex(endIndex); + } + + return position; + } + } diff --git a/src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java index 7c6a1de084a..126334767f3 100644 --- a/src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Minifinder2ProtocolDecoderTest.java @@ -14,6 +14,9 @@ public void testDecode() throws Exception { verifyPositions(decoder, false, binary( "ab105a0512e19404011001383632333131303632373037333735093743c3ec640000000009374dc3ec6400000000093750c3ec6400000080092455c3ec640203935e0f22a318d6c7baacd6a2546751467bd009246ac3ec640203b35e0f22a318d6c7baacd6a2546751467bd009246cc3ec640203b35e0f22a318d6c7baacd6a2546751467bd009247ec3ec640203b35e0f22a318d6c7baacd6a2546751467bd0092492c3ec640203b35e0f22a318d6c7baacd6a2546751467bd00924a6c3ec640203b35e0f22a318d6c7baacd6a2546751467bd00924bac3ec640203b35e0f22a318d6c7baacd6a2546751467bd00924d2c3ec640203b35e0f22a7083a2f201a83a3f8084f84ae560924e7c3ec640203b35e0f22a7083a2f201a83a3f8084f84ae560924fbc3ec640203b35e0f22a7083a2f201a83a3f8084f84ae5609240fc4ec640203b35e0f22a7083a2f201a83a3f8084f84ae56092423c4ec640203b35d0f22a7083a2f201a83a3f8084f84ae56092437c4ec640203cb5d0f22a7083a2f201a83a3f8084f84ae5609244fc4ec640003cb5d092464c4ec640003cb5d092478c4ec640003cb5d09248cc4ec640003cb5d0924a0c4ec640003cb5d0924b4c4ec640003cb5d0924ccc4ec640003cb5d0924e5c4ec640003cb5d0924fec4ec6400037b5d092413c5ec6400037b5d092427c5ec6400017b5d0924b785ed640003cb530924d085ed640003ab530924e985ed640003ab530924fe85ed640003ab5309241286ed640003ab5309242686ed640003ab5309243a86ed640003ab5309244e86ed640003ab5309246786ed640003ab5309248086ed640003ab5309249986ed6400037b530924b286ed6400037b530924c686ed6400037b530924da86ed6400037b530924ee86ed6400037b5309240287ed6400037b5309241687ed6400037b5309242f87ed6400037b5309244787ed640003835309246187ed640003835309247a87ed640003835309249287ed64000383530924ab87ed64000383530924c487ed64000383530924d987ed64000383530924ed87ed640003835309240188ed640003835309241588ed640003835309242988ed640003d35309243a88ed640003d3530d02000000803788ed640000000009374188ed640400000009244188ed640003d35309244288ed640003d35309374b88ed640500000009244b88ed640003d35309375588ed640500000009245588ed640003d35309245788ed640003d35309375f88ed640700000009245f88ed640003d35309376988ed640800000009246988ed640003d35309246b88ed640203d3530f22a502184a2cfba0a42c768af4ab5009247188ed640203d3530f22a502184a2cfba0a42c768af4ab5009377388ed640a00000009247688ed640203d3530f22a502184a2cfba0a42c768af4ab5009247b88ed640203d3530f22a502184a2cfba0a42c768af4ab5009377d88ed640300000009247e88ed640203d3530f22a502184a2cfba0a42c768af4ab5009248088ed640203d3530f22a502184a2cfba0a42c768af4ab5009248588ed640203d3530f22a502184a2cfba0a42c768af4ab5009378788ed640000000009248a88ed640203d3530f22a502184a2cfba0a42c768af4ab5009248f88ed640203d3530f22a502184a2cfba0a42c768af4ab5009379188ed640000000009379288ed640000008009249288ed640203d3530f22a502184a2cfba0a42c768af4ab5009249488ed640203d3530f22a502184a2cfba0a42c768af4ab5009249988ed640203d3530f22a502184a2cfba0a42c768af4ab5009249e88ed640203d3530f22a502184a2cfba0a42c768af4ab500924a388ed640203d3530f22a502184a2cfba0a42c768af4ab500924a688ed640203d3530f22a502184a2cfba0a42c768af4ab50")); + verifyAttributes(decoder, binary( + "ab00cc029c9b0000020501040518200502cf290001100338363233313130363534393538393515043839343632303338303735303031383830343034070539eed3f9cec705064f93a7650507010b00002908cf2900010020050000d0000068915b00ae0000004f637420313920323032330031303a35393a3332261b53494d37353030457c4231315630325f3231303330337c50312e30325f3230323231313034050900000000050a00000003070b000000001e01070b010000001e01070b020000001e01070b030000001e01060c0000000000050d6e600580020e04050f0708008002100002110f02126406134d46303758041404a20e08160000000000000008160100000000000008160200000000000008160300000000000008160400000000000008160500000000000008160600000000000008160700000000000008160800000000000008160900000000000020177777772e676f6f676c652e636f6d2f6d6170733f713d252e37662c252e3766211868747470733a2f2f6c6f632e6d696e6966696e6465722e636f6d2f25732f25730519000001fe101a4d4630372e343631302e3233303300021c640a1d802acee6212966cf0803207b3e03217b040230000230010230020230030230040230050230060230070230080230090231000532580214010533820000000e406d326d2e74656c65322e636f6d014101421c4380431468756e7465726465762e6d696e6966696e6465722e636f6d0d440e01008005000000100e0000054505002c01014705500f1400f00d511002e803b84d7f0d0246f6430d511100f40100000000000000000d511200f40100000000000000000d511300f401000000000000000005527800030005532c0100000354500005551e002d400256050b57201c00002c010000ed61025d01055c890a0000057000000000037100001472000000000000000000000000000000000000000568f0000100057500000000074de36000000000")); + verifyAttribute(decoder, binary( "ab101c00d6f61e000110013836333932313033393939363038300937efd201640c000000"), "barkCount", 12L); From b7bd2ce8416a4b7bbc8a5e5171f0891d894e6bc7 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 3 Feb 2024 08:29:43 -0800 Subject: [PATCH 42/55] Implement FleetGuide acknowledgement --- .../protocol/FleetGuideProtocolDecoder.java | 68 +++++++++++++++++-- .../FleetGuideProtocolDecoderTest.java | 3 + 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java index 9fddec922d5..ba4d9cfaebb 100644 --- a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java @@ -19,8 +19,10 @@ import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; +import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; import org.traccar.session.DeviceSession; @@ -38,9 +40,13 @@ public FleetGuideProtocolDecoder(Protocol protocol) { super(protocol); } - public static final int MSG_DATA = 0x10; - public static final int MSG_HEARTBEAT = 0x1A; - public static final int MSG_RESPONSE = 0x1C; + public static final int MSG_EMPTY = 0; + public static final int MSG_SYNC_REQ = 1; + public static final int MSG_SYNC_ACK = 2; + public static final int MSG_DATA_R_ACK = 3; + public static final int MSG_DATA_N_ACK = 4; + public static final int MSG_REP_R_ACK = 5; + public static final int MSG_REP_N_ACK = 6; @Override protected Object decode( @@ -53,9 +59,12 @@ protected Object decode( int length = BitUtil.to(options, 11); DeviceSession deviceSession; + Long deviceId; if (BitUtil.check(options, 11)) { - deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(buf.readUnsignedIntLE())); + deviceId = buf.readUnsignedIntLE(); + deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(deviceId)); } else { + deviceId = null; deviceSession = getDeviceSession(channel, remoteAddress); } if (deviceSession == null) { @@ -63,12 +72,24 @@ protected Object decode( } int type; + Integer index; if (BitUtil.check(options, 12)) { - type = BitUtil.to(buf.readUnsignedByte(), 4); + int value = buf.readUnsignedByte(); + type = BitUtil.to(value, 4); + index = BitUtil.from(value, 4); } else { type = 0; + index = null; } + Integer responseType; + if (type == MSG_SYNC_REQ) { + responseType = MSG_SYNC_ACK; + } else { + responseType = null; + } + sendResponse(channel, remoteAddress, deviceId, responseType, index); + if (BitUtil.check(options, 13)) { buf.readUnsignedShortLE(); // acknowledgement } @@ -153,6 +174,43 @@ private Position processPosition(Position position) { return position; } + + private void sendResponse( + Channel channel, SocketAddress remoteAddress, Long deviceId, Integer type, Integer index) { + if (channel != null) { + + ByteBuf response = Unpooled.buffer(); + response.writeByte(0x53); // signature + + int options = 0; + if (deviceId != null) { + options |= 1 << 11; + } + if (type != null) { + options |= 1 << 12; + } + if (index != null) { + options |= 1 << 13; + } + response.writeShortLE(options); + + if (deviceId != null) { + response.writeIntLE(deviceId.intValue()); + } + if (type != null) { + response.writeByte(type); + } + if (index != null) { + int mask = (1 << (index + 1)) - 1; + response.writeShortLE(mask); + } + response.writeShortLE(Checksum.crc16( + Checksum.CRC16_CCITT_FALSE, response.nioBuffer(1, response.writerIndex() - 1))); + + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + private int readVarSize(ByteBuf buf) { int b; int y = 0; diff --git a/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java index 34b15d14b02..363f33c5b19 100644 --- a/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java @@ -10,6 +10,9 @@ public void testDecode() throws Exception { var decoder = inject(new FleetGuideProtocolDecoder(null)); + verifyNull(decoder, binary( + "5300188f8a020001f36a")); + verifyPositions(decoder, binary( "539e598f8a020003020700e378351ac39f040c04ffa92e806a28a13b1f00b638030c000067052c06ffffffff033006808001180003200100000700e478351ac40204303dab2e80cb27a13b1c00ac40021b30e778351ac502043082a72e8054020530bf30021b30e978351ac69f0d0c0433a72e80c123a13b2000df3807002579351ac79f06020a170000df28021b476179351ac8020f30021c81279d79351ac9020f30021c8207d979351aca020f30021c8157157a351acb022b8140517a351acc022b608d7a351acd022b60c97a351ace022b30057b351acf022b30417b351ad0022b307d7b351ad1020f3048021b30b97b351ad2020f3070030c004066021630f57b351ad30213841080021730317c351ad4021330c00217306d7c351ad5020f3050021b30a97c351ad602138170021830e57c351ad7020f3058021b30217d351ad8021385500218305d7d351ad9022b8110997d351ada022b8170d57d351adb022b30117e351adc020f3068030ce184680216304d7e351add020f3060030ce34469021630897e351ade021260e4021830c57e351adf021230e5021830017f351ae002293022f2")); From 444d54e792418333b98a109490b7eaffc96cdf53 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 4 Feb 2024 09:47:19 -0800 Subject: [PATCH 43/55] Change to if statement --- src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java index 3a096258415..aabc24887cb 100644 --- a/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java +++ b/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java @@ -29,7 +29,7 @@ public class TeltonikaFrameDecoder extends BaseFrameDecoder { protected Object decode( ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { - while (buf.isReadable() && buf.getByte(buf.readerIndex()) == (byte) 0xff) { + if (buf.isReadable() && buf.getByte(buf.readerIndex()) == (byte) 0xff) { return buf.readRetainedSlice(1); } From c506f723c905fed6995cde26168dce9948599fd4 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Mon, 5 Feb 2024 21:42:08 -0800 Subject: [PATCH 44/55] Add unique id validation --- src/main/java/org/traccar/model/Device.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/traccar/model/Device.java b/src/main/java/org/traccar/model/Device.java index e0781597649..a3088a613d3 100644 --- a/src/main/java/org/traccar/model/Device.java +++ b/src/main/java/org/traccar/model/Device.java @@ -53,6 +53,9 @@ public String getUniqueId() { } public void setUniqueId(String uniqueId) { + if (uniqueId.contains("../") || uniqueId.contains("..\\")) { + throw new IllegalArgumentException("Invalid unique id"); + } this.uniqueId = uniqueId.trim(); } From 9ca9c0c146125eaa9a6a3e875e5dd596b6fa228b Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Wed, 7 Feb 2024 20:58:19 -0800 Subject: [PATCH 45/55] Reverse Teltonika driver id --- .../java/org/traccar/protocol/TeltonikaProtocolDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java index 537990265f3..2f0874797b8 100644 --- a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java @@ -231,7 +231,7 @@ private static void register(int id, Set models, BiConsumer p.set(Position.PREFIX_TEMP + 3, b.readInt() * 0.1)); register(75, fmbXXX, (p, b) -> p.set(Position.PREFIX_TEMP + 4, b.readInt() * 0.1)); register(78, null, (p, b) -> { - long driverUniqueId = b.readLong(); + long driverUniqueId = b.readLongLE(); if (driverUniqueId > 0) { p.set(Position.KEY_DRIVER_UNIQUE_ID, String.format("%016X", driverUniqueId)); } From 3f55f474fde34c8110b48cb1ac472671ca5f80fc Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 8 Feb 2024 06:59:14 -0800 Subject: [PATCH 46/55] Handle last FleetGuide position --- .../protocol/FleetGuideProtocolDecoder.java | 34 +++++++++++-------- .../FleetGuideProtocolDecoderTest.java | 3 ++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java index ba4d9cfaebb..b4593d84348 100644 --- a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java @@ -82,13 +82,15 @@ protected Object decode( index = null; } - Integer responseType; - if (type == MSG_SYNC_REQ) { - responseType = MSG_SYNC_ACK; - } else { - responseType = null; + if (type != MSG_DATA_N_ACK && type != MSG_REP_N_ACK) { + Integer responseType; + if (type == MSG_SYNC_REQ) { + responseType = MSG_SYNC_ACK; + } else { + responseType = null; + } + sendResponse(channel, remoteAddress, deviceId, responseType, index); } - sendResponse(channel, remoteAddress, deviceId, responseType, index); if (BitUtil.check(options, 13)) { buf.readUnsignedShortLE(); // acknowledgement @@ -116,7 +118,7 @@ protected Object decode( int recordEndIndex = data.readerIndex() + recordLength; if (recordTypes.contains(recordType)) { - positions.add(processPosition(position)); + processPosition(positions, position); position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); recordTypes.clear(); @@ -159,19 +161,23 @@ protected Object decode( } + processPosition(positions, position); + data.release(); return positions.isEmpty() ? null : positions; } - private Position processPosition(Position position) { - if (position.getFixTime() == null) { - position.setTime(new Date()); - } - if (!position.getAttributes().containsKey(Position.KEY_SATELLITES)) { - getLastLocation(position, null); + private void processPosition(List positions, Position position) { + if (!position.getAttributes().isEmpty()) { + if (position.getFixTime() == null) { + position.setTime(new Date()); + } + if (!position.getAttributes().containsKey(Position.KEY_SATELLITES)) { + getLastLocation(position, null); + } + positions.add(position); } - return position; } diff --git a/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java index 363f33c5b19..8146f6a3e00 100644 --- a/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java @@ -13,6 +13,9 @@ public void testDecode() throws Exception { verifyNull(decoder, binary( "5300188f8a020001f36a")); + verifyNull(decoder, binary( + "5322188f8a0200140700f355831a83ec06030c008065052c06ffffffff033006808001180003200100005ec0")); + verifyPositions(decoder, binary( "539e598f8a020003020700e378351ac39f040c04ffa92e806a28a13b1f00b638030c000067052c06ffffffff033006808001180003200100000700e478351ac40204303dab2e80cb27a13b1c00ac40021b30e778351ac502043082a72e8054020530bf30021b30e978351ac69f0d0c0433a72e80c123a13b2000df3807002579351ac79f06020a170000df28021b476179351ac8020f30021c81279d79351ac9020f30021c8207d979351aca020f30021c8157157a351acb022b8140517a351acc022b608d7a351acd022b60c97a351ace022b30057b351acf022b30417b351ad0022b307d7b351ad1020f3048021b30b97b351ad2020f3070030c004066021630f57b351ad30213841080021730317c351ad4021330c00217306d7c351ad5020f3050021b30a97c351ad602138170021830e57c351ad7020f3058021b30217d351ad8021385500218305d7d351ad9022b8110997d351ada022b8170d57d351adb022b30117e351adc020f3068030ce184680216304d7e351add020f3060030ce34469021630897e351ade021260e4021830c57e351adf021230e5021830017f351ae002293022f2")); From fe771851995da97ffa2fb50e21bd437bca208eac Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 8 Feb 2024 07:26:52 -0800 Subject: [PATCH 47/55] Support RST command responses --- .../org/traccar/protocol/RstProtocolDecoder.java | 13 ++++++++++++- .../traccar/protocol/RstProtocolDecoderTest.java | 4 ++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java index 2493f0d9f88..eafa4d3d77c 100644 --- a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -143,6 +143,17 @@ protected Object decode( return position; + } else if (type == 134) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_RESULT, String.valueOf(type)); + + return position; + } else { return null; diff --git a/src/test/java/org/traccar/protocol/RstProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/RstProtocolDecoderTest.java index fc932fe9e2b..a750d031150 100644 --- a/src/test/java/org/traccar/protocol/RstProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/RstProtocolDecoderTest.java @@ -11,6 +11,10 @@ public void testDecode() throws Exception { var decoder = inject(new RstProtocolDecoder(null)); + verifyAttribute(decoder, text( + "RST;A;RST-MINI-4Gv3;V9.10;009521405;1;134;FIM;"), + Position.KEY_RESULT, "134"); + verifyAttribute(decoder, text( "RST;A;RST-MINIv5;V9.08;009767055;248;55;14-12-2023 19:34:20;14-12-2023 19:34:21;-12.923640;-38.388313;0;14;17;1;4;15;00;B0;00;1A;02;12.18;4.02;65;21;FE;0000;01;C0;001606017031;0002;FIM;"), Position.KEY_DRIVER_UNIQUE_ID, "001606017031"); From 0c8b5c1ab1f83cc0fc6d2f17cdf808388b383e14 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 8 Feb 2024 17:49:28 -0800 Subject: [PATCH 48/55] Handle no remote address --- .../org/traccar/session/ConnectionManager.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/traccar/session/ConnectionManager.java b/src/main/java/org/traccar/session/ConnectionManager.java index 1461c66eadd..121b876c5f8 100644 --- a/src/main/java/org/traccar/session/ConnectionManager.java +++ b/src/main/java/org/traccar/session/ConnectionManager.java @@ -188,17 +188,19 @@ private Device addUnknownDevice(String uniqueId) { public void deviceDisconnected(Channel channel, boolean supportsOffline) { SocketAddress remoteAddress = channel.remoteAddress(); - Map endpointSessions = sessionsByEndpoint.remove(remoteAddress); - if (endpointSessions != null) { - for (DeviceSession deviceSession : endpointSessions.values()) { - if (supportsOffline) { - updateDevice(deviceSession.getDeviceId(), Device.STATUS_OFFLINE, null); + if (remoteAddress != null) { + Map endpointSessions = sessionsByEndpoint.remove(remoteAddress); + if (endpointSessions != null) { + for (DeviceSession deviceSession : endpointSessions.values()) { + if (supportsOffline) { + updateDevice(deviceSession.getDeviceId(), Device.STATUS_OFFLINE, null); + } + sessionsByDeviceId.remove(deviceSession.getDeviceId()); + cacheManager.removeDevice(deviceSession.getDeviceId()); } - sessionsByDeviceId.remove(deviceSession.getDeviceId()); - cacheManager.removeDevice(deviceSession.getDeviceId()); } + unknownByEndpoint.remove(remoteAddress); } - unknownByEndpoint.remove(remoteAddress); } public void deviceUnknown(long deviceId) { From f632b15098776b35910834aabc35bb62737c6d78 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 17 Feb 2024 07:13:09 -0800 Subject: [PATCH 49/55] Handle MT600 no location messages --- .../protocol/Tlt2hProtocolDecoder.java | 31 +++++++++++++------ .../protocol/Tlt2hProtocolDecoderTest.java | 10 +++++- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java index 79488acc02a..4b11cbc74a7 100644 --- a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,8 +63,9 @@ public Tlt2hProtocolDecoder(Protocol protocol) { .number("(x+)") // cell id .groupEnd("?") .text("$GPRMC,") - .number("(dd)(dd)(dd).d+,") // time (hhmmss.sss) + .number("(?:(dd)(dd)(dd).d+)?,") // time (hhmmss.sss) .expression("([AVL]),") // validity + .groupBegin() .number("(d+)(dd.d+),") // latitude .expression("([NS]),") .number("(d+)(dd.d+),") // longitude @@ -72,6 +73,7 @@ public Tlt2hProtocolDecoder(Protocol protocol) { .number("(d+.?d*)?,") // speed .number("(d+.?d*)?,") // course .number("(dd)(dd)(dd)") // date (ddmmyy) + .groupEnd("?") .any() .compile(); @@ -190,17 +192,26 @@ protected Object decode( position.setNetwork(network); } - DateBuilder dateBuilder = new DateBuilder() - .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + DateBuilder dateBuilder = new DateBuilder(); + if (parser.hasNext(3)) { + dateBuilder.setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + } position.setValid(parser.next().equals("A")); - position.setLatitude(parser.nextCoordinate()); - position.setLongitude(parser.nextCoordinate()); - position.setSpeed(parser.nextDouble(0)); - position.setCourse(parser.nextDouble(0)); - dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); - position.setTime(dateBuilder.getDate()); + if (parser.hasNext()) { + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + } else { + getLastLocation(position, null); + } } else { continue; diff --git a/src/test/java/org/traccar/protocol/Tlt2hProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Tlt2hProtocolDecoderTest.java index a64a10450a5..8a7ba84ab7b 100644 --- a/src/test/java/org/traccar/protocol/Tlt2hProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Tlt2hProtocolDecoderTest.java @@ -11,6 +11,14 @@ public void testDecode() throws Exception { var decoder = inject(new Tlt2hProtocolDecoder(null)); + verifyPositions(decoder, false, text( + "#868105044690301#MT600+#0000#0#0#129#40#0#AUTOLOW#1\r\n" + + "#072030fa20c$GPRMC,,V,,,,,,,,,,A*5C\r\n")); + + verifyPositions(decoder, text( + "#868105044690301#MT600+#0000#0#0#143#40#0#AUTO#1\r\n", + "#072030fcf21$GPRMC,155616.00,A,4931.9210,N,09652.5290,W,53.80,90.00,150224,,,A*48\r\n")); + verifyAttribute(decoder, text( "#867665041689485#MT700N#0000#HT#1\r\n", "#5065$GPRMC,148996.00,A,2485.2458,N,01258.4535,E,,,151348,,,A*5D\r\n"), @@ -24,7 +32,7 @@ public void testDecode() throws Exception { "#862255061825896#MT710#0000#TOWED#1\r\n", "#39#$WIFI,015259.00,A,-47,7483C2DBC0B0,-48,7683C2ABC0B0,-48,7683C29BC0B0,-48,7683C2CBC0B0,-48,7683C2BBC0B0,151123*74\r\n")); - verifyNull(decoder, text( + verifyPositions(decoder, false, text( "#860517049471362#MT700#0000#AUTO#1\r\n", "#36$GPRMC,,V,,,,,,,,,,A*5C\r\n")); From a8ab00a53f8015df041ced9ba012294cea1068e5 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 17 Feb 2024 07:46:36 -0800 Subject: [PATCH 50/55] Finish FleetGuide implementation --- .../protocol/FleetGuideProtocolDecoder.java | 79 +++++++++++++++++-- .../FleetGuideProtocolDecoderTest.java | 11 ++- 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java index b4593d84348..8f679525be1 100644 --- a/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java @@ -29,10 +29,8 @@ import java.net.SocketAddress; import java.util.Date; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Set; public class FleetGuideProtocolDecoder extends BaseProtocolDecoder { @@ -108,8 +106,6 @@ protected Object decode( Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - Set recordTypes = new HashSet<>(); - while (data.isReadable()) { int recordHeader = data.readUnsignedShortLE(); @@ -117,13 +113,11 @@ protected Object decode( int recordType = BitUtil.from(recordHeader, 10); int recordEndIndex = data.readerIndex() + recordLength; - if (recordTypes.contains(recordType)) { + if (recordType == 0 && position.getDeviceTime() != null) { processPosition(positions, position); position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - recordTypes.clear(); } - recordTypes.add(recordType); switch (recordType) { case 0: @@ -153,6 +147,77 @@ protected Object decode( position.setAltitude(-position.getAltitude()); } break; + case 3: + int powerLow = data.readUnsignedByte(); + int powerFlags = data.readUnsignedByte(); + int batteryHigh = data.readUnsignedByte(); + position.set(Position.KEY_POWER, (powerLow + (BitUtil.to(powerFlags, 5) << 8)) * 0.01); + position.set(Position.KEY_IGNITION, BitUtil.check(powerFlags, 5)); + position.set(Position.KEY_BATTERY, (BitUtil.from(powerFlags, 6) + (batteryHigh << 2)) * 0.01); + if (recordLength >= 4) { + int extraFlags = data.readUnsignedByte(); + if (BitUtil.check(extraFlags, 0)) { + position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); + } + if (BitUtil.check(extraFlags, 1)) { + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + } + } + break; + case 6: + position.set(Position.KEY_INPUT, data.readUnsignedByte()); + break; + case 7: + position.set(Position.KEY_OUTPUT, data.readUnsignedByte()); + break; + case 8: + int adcMask = data.readUnsignedByte(); + for (int i = 0; i < 8; i++) { + if (BitUtil.check(adcMask, i)) { + position.set(Position.PREFIX_ADC + (i + 1), data.readUnsignedShortLE()); + } + } + break; + case 11: + int fuelMask = data.readUnsignedByte(); + for (int i = 1; i < 8; i++) { + if (BitUtil.check(fuelMask, i)) { + position.set("fuel" + i, data.readUnsignedShortLE()); + } + } + break; + case 12: + int fuelTempMask = data.readUnsignedByte(); + for (int i = 1; i < 8; i++) { + if (BitUtil.check(fuelTempMask, i)) { + position.set("fuelTemp" + i, (int) data.readByte()); + } + } + break; + case 13: + int tempMask = data.readUnsignedByte(); + for (int i = 0; i < 8; i++) { + if (BitUtil.check(tempMask, i)) { + position.set(Position.PREFIX_TEMP + (i + 1), data.readShortLE() * 0.01); + } + } + break; + case 18: + int sensorIndex = data.readUnsignedByte(); + switch (recordLength - 1) { + case 1: + position.set("sensor" + sensorIndex, data.readUnsignedByte()); + break; + case 2: + position.set("sensor" + sensorIndex, data.readUnsignedShortLE()); + break; + case 4: + position.set("sensor" + sensorIndex, data.readUnsignedIntLE()); + break; + default: + break; + } + break; default: break; } diff --git a/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java index 8146f6a3e00..3cd74766d31 100644 --- a/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/FleetGuideProtocolDecoderTest.java @@ -13,12 +13,21 @@ public void testDecode() throws Exception { verifyNull(decoder, binary( "5300188f8a020001f36a")); - verifyNull(decoder, binary( + verifyPositions(decoder, false, binary( "5322188f8a0200140700f355831a83ec06030c008065052c06ffffffff033006808001180003200100005ec0")); verifyPositions(decoder, binary( "539e598f8a020003020700e378351ac39f040c04ffa92e806a28a13b1f00b638030c000067052c06ffffffff033006808001180003200100000700e478351ac40204303dab2e80cb27a13b1c00ac40021b30e778351ac502043082a72e8054020530bf30021b30e978351ac69f0d0c0433a72e80c123a13b2000df3807002579351ac79f06020a170000df28021b476179351ac8020f30021c81279d79351ac9020f30021c8207d979351aca020f30021c8157157a351acb022b8140517a351acc022b608d7a351acd022b60c97a351ace022b30057b351acf022b30417b351ad0022b307d7b351ad1020f3048021b30b97b351ad2020f3070030c004066021630f57b351ad30213841080021730317c351ad4021330c00217306d7c351ad5020f3050021b30a97c351ad602138170021830e57c351ad7020f3058021b30217d351ad8021385500218305d7d351ad9022b8110997d351ada022b8170d57d351adb022b30117e351adc020f3068030ce184680216304d7e351add020f3060030ce34469021630897e351ade021260e4021830c57e351adf021230e5021830017f351ae002293022f2")); + verifyPositions(decoder, binary( + "538958235a02008408070027398f1a7da3040c0485c7437f9db8a635ca016189030c76a567052c06bd05ffff033006138009340f69fb0080008000800118010320018005070029398f1a7e08043b39f0437f14b9a635cb010288030c7108233b2b398f1a7f08043bec18447f7fbca6357c010b8008263b2d398f1a8008043be53d447f61c0a63562011480030c7708213b6f3b")); + + verifyPositions(decoder, binary( + "53361886b60e00540700cf3b8f1a838d060e041dc683a1b672a04b0000ba885d00030c0ea567052c06ffffffff03300680800118000720070000000000006bc3")); + + verifyPositions(decoder, binary( + "53dd581fc10e0094080700373d8f1a5e2f090e04cbb6c2a3d3078f4b5d023090b500030cac6567052c06ffffffff0330068080054810fdfdfdff0548121bf0000005481321d4830005481454cf08040e15eee20100054816770308040e171c0208040718281f0804071984000804071a583a000001180107200781050000000007003b3d8f1a5f2f040e04dcfbc2a3b15a8f4b67023890b4081c7c1f08067c2e08067c61081b7c842008057c8308067c443c08107c3e3d8f1a6008047cf125c3a31ca48f4b7c0242081e7c2208067c3908067c6b08067cef08147cac21080c7ce03e080e7cbb62")); + } } From 5a5a0e43d3b123780775f723c1c35a417a4e8265 Mon Sep 17 00:00:00 2001 From: meysamrt <46737899+MeysamRT76@users.noreply.github.com> Date: Sun, 18 Feb 2024 16:44:28 +0330 Subject: [PATCH 51/55] Refactor: Remove duplicated code in get function (ServerResource.java) Eliminate duplicated code in get function to enhance maintainability and readability. --- .../java/org/traccar/api/resource/ServerResource.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java index 66ecc74e18c..76c8e14c73e 100644 --- a/src/main/java/org/traccar/api/resource/ServerResource.java +++ b/src/main/java/org/traccar/api/resource/ServerResource.java @@ -93,15 +93,10 @@ public Server get() throws StorageException { server.setOpenIdEnabled(openIdProvider != null); server.setOpenIdForce(openIdProvider != null && openIdProvider.getForce()); User user = permissionsService.getUser(getUserId()); - if (user != null) { - if (user.getAdministrator()) { - server.setStorageSpace(Log.getStorageSpace()); - } - } else { - server.setNewServer(UserUtil.isEmpty(storage)); - } if (user != null && user.getAdministrator()) { server.setStorageSpace(Log.getStorageSpace()); + } else { + server.setNewServer(UserUtil.isEmpty(storage)); } return server; } From 51d6275a3cb5ed50c34dd2b0ca1252860793244a Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 18 Feb 2024 15:20:30 -0800 Subject: [PATCH 52/55] Support Amber IoT asset tracker --- .../protocol/SigfoxProtocolDecoder.java | 27 ++++++++++++++----- .../protocol/SigfoxProtocolDecoderTest.java | 9 +++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java index 6f739a1a4d3..9380d23271b 100644 --- a/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2020 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. @@ -23,6 +23,7 @@ import io.netty.handler.codec.http.HttpResponseStatus; import org.traccar.BaseHttpProtocolDecoder; import org.traccar.helper.BufferUtil; +import org.traccar.model.Device; import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -154,20 +155,34 @@ protected Object decode( } else if (jsonContains(json, "data")) { + String model = getCacheManager().getObject(Device.class, deviceSession.getDeviceId()).getModel(); + ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(json.getString("data"))); try { - int event = buf.readUnsignedByte(); - if (event == 0x0f || event == 0x1f) { + int header = buf.readUnsignedByte(); + if ("Amber".equals(model)) { + + int flags = buf.readUnsignedByte(); + position.set(Position.KEY_MOTION, BitUtil.check(flags, 1)); + + position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 0.02); + position.set(Position.PREFIX_TEMP + 1, (int) buf.readByte()); + + position.setValid(true); + position.setLatitude(buf.readInt() / 60000.0); + position.setLongitude(buf.readInt() / 60000.0); + + } else if (header == 0x0f || header == 0x1f) { - position.setValid(event >> 4 > 0); + position.setValid(header >> 4 > 0); position.setLatitude(BufferUtil.readSignedMagnitudeInt(buf) * 0.000001); position.setLongitude(BufferUtil.readSignedMagnitudeInt(buf) * 0.000001); position.set(Position.KEY_BATTERY, (int) buf.readUnsignedByte()); - } else if (event >> 4 <= 3 && buf.writerIndex() == 12) { + } else if (header >> 4 <= 3 && buf.writerIndex() == 12) { - if (BitUtil.to(event, 4) == 0) { + if (BitUtil.to(header, 4) == 0) { position.setValid(true); position.setLatitude(buf.readIntLE() * 0.0000001); position.setLongitude(buf.readIntLE() * 0.0000001); diff --git a/src/test/java/org/traccar/protocol/SigfoxProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/SigfoxProtocolDecoderTest.java index cc6c1701494..8a275ce9a86 100644 --- a/src/test/java/org/traccar/protocol/SigfoxProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/SigfoxProtocolDecoderTest.java @@ -3,8 +3,11 @@ import io.netty.handler.codec.http.HttpMethod; import org.junit.jupiter.api.Test; import org.traccar.ProtocolTest; +import org.traccar.model.Device; import org.traccar.model.Position; +import static org.mockito.Mockito.when; + public class SigfoxProtocolDecoderTest extends ProtocolTest { @Test @@ -43,6 +46,12 @@ public void testDecode() throws Exception { verifyPosition(decoder, request(HttpMethod.POST, "/", buffer("%7B++%22device%22%3A%222BF839%22%2C++%22time%22%3A1510605882%2C++%22duplicate%22%3Afalse%2C++%22snr%22%3A45.61%2C++%22station%22%3A%2235A9%22%2C++%22data%22%3A%2200bd6475e907398e562d01b9%22%2C++%22avgSnr%22%3A45.16%2C++%22lat%22%3A-38.0%2C++%22lng%22%3A145.0%2C++%22rssi%22%3A-98.00%2C++%22seqNumber%22%3A228+%7D="))); + var device = decoder.getCacheManager().getObject(Device.class, 1); + when(device.getModel()).thenReturn("Amber"); + + verifyPosition(decoder, request(HttpMethod.POST, "/", + buffer("{ \"deviceId\":\"284019F\", \"timestamp\":\"1707375610\", \"seqNo\":\"42\", \"data\":\"0100b019ffe8645d0019e513\", \"linkQuality\":\"Excellent\", \"operator\":\"SIGFOX_South_Africa_Sqwidnet\", \"country\":\"710\" }"))); + } } From 15a478a959ea6ff4000c6b14d54edc9d2c37e06d Mon Sep 17 00:00:00 2001 From: meysamrt <46737899+MeysamRT76@users.noreply.github.com> Date: Mon, 19 Feb 2024 08:54:26 +0330 Subject: [PATCH 53/55] Update ServerResource.java --- src/main/java/org/traccar/api/resource/ServerResource.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java index 76c8e14c73e..2a72d27736c 100644 --- a/src/main/java/org/traccar/api/resource/ServerResource.java +++ b/src/main/java/org/traccar/api/resource/ServerResource.java @@ -93,8 +93,10 @@ public Server get() throws StorageException { server.setOpenIdEnabled(openIdProvider != null); server.setOpenIdForce(openIdProvider != null && openIdProvider.getForce()); User user = permissionsService.getUser(getUserId()); - if (user != null && user.getAdministrator()) { - server.setStorageSpace(Log.getStorageSpace()); + if (user != null) { + if (user.getAdministrator()) { + server.setStorageSpace(Log.getStorageSpace()); + } } else { server.setNewServer(UserUtil.isEmpty(storage)); } From efd9e261791942334dbf4a911883a1b2b791f282 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Mon, 19 Feb 2024 11:53:08 -0800 Subject: [PATCH 54/55] Streamline model name access --- src/main/java/org/traccar/BaseProtocolDecoder.java | 10 ++++++++++ src/main/java/org/traccar/BaseProtocolEncoder.java | 11 +++++++++++ .../org/traccar/protocol/DualcamProtocolDecoder.java | 3 +-- .../org/traccar/protocol/Gt06ProtocolEncoder.java | 7 +++---- .../org/traccar/protocol/LaipacProtocolDecoder.java | 3 +-- .../org/traccar/protocol/MeiligaoProtocolEncoder.java | 3 +-- .../org/traccar/protocol/MeitrackProtocolDecoder.java | 8 ++------ .../traccar/protocol/Minifinder2ProtocolEncoder.java | 4 +--- .../org/traccar/protocol/SigfoxProtocolDecoder.java | 5 +---- .../traccar/protocol/TeltonikaProtocolDecoder.java | 4 +--- .../java/org/traccar/session/ConnectionManager.java | 2 +- src/main/java/org/traccar/session/DeviceSession.java | 9 ++++++++- src/test/java/org/traccar/BaseTest.java | 3 ++- .../protocol/Minifinder2ProtocolEncoderTest.java | 3 +-- .../traccar/protocol/SigfoxProtocolDecoderTest.java | 3 +-- 15 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/traccar/BaseProtocolDecoder.java b/src/main/java/org/traccar/BaseProtocolDecoder.java index 4d4086c3c24..495a866c051 100644 --- a/src/main/java/org/traccar/BaseProtocolDecoder.java +++ b/src/main/java/org/traccar/BaseProtocolDecoder.java @@ -51,6 +51,8 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { private MediaManager mediaManager; private CommandsManager commandsManager; + private String modelOverride; + public BaseProtocolDecoder(Protocol protocol) { this.protocol = protocol; } @@ -141,6 +143,14 @@ public DeviceSession getDeviceSession(Channel channel, SocketAddress remoteAddre } } + public void setModelOverride(String modelOverride) { + this.modelOverride = modelOverride; + } + + public String getDeviceModel(DeviceSession deviceSession) { + return modelOverride != null ? modelOverride : deviceSession.getModel(); + } + public void getLastLocation(Position position, Date deviceTime) { if (position.getDeviceId() != 0) { position.setOutdated(true); diff --git a/src/main/java/org/traccar/BaseProtocolEncoder.java b/src/main/java/org/traccar/BaseProtocolEncoder.java index b9ca168385a..e357c27dccf 100644 --- a/src/main/java/org/traccar/BaseProtocolEncoder.java +++ b/src/main/java/org/traccar/BaseProtocolEncoder.java @@ -39,6 +39,8 @@ public abstract class BaseProtocolEncoder extends ChannelOutboundHandlerAdapter private CacheManager cacheManager; + private String modelOverride; + public BaseProtocolEncoder(Protocol protocol) { this.protocol = protocol; } @@ -68,6 +70,15 @@ protected void initDevicePassword(Command command, String defaultPassword) { } } + public void setModelOverride(String modelOverride) { + this.modelOverride = modelOverride; + } + + public String getDeviceModel(long deviceId) { + String model = getCacheManager().getObject(Device.class, deviceId).getModel(); + return modelOverride != null ? modelOverride : model; + } + @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { diff --git a/src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java b/src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java index 75cd52384da..411e2b9d718 100644 --- a/src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java @@ -19,7 +19,6 @@ import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.model.Device; import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; @@ -77,7 +76,7 @@ protected Object decode( deviceSession = getDeviceSession(channel, remoteAddress, uniqueId); long settings = buf.readUnsignedInt(); if (channel != null && deviceSession != null) { - model = getCacheManager().getObject(Device.class, deviceSession.getDeviceId()).getModel(); + model = getDeviceModel(deviceSession); ByteBuf response = Unpooled.buffer(); if (BitUtil.check(settings, 25)) { response.writeShort(MSG_PATH_REQUEST); diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java index dc5dd446f40..fd6bb845178 100644 --- a/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java @@ -23,7 +23,6 @@ import org.traccar.helper.Checksum; import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Command; -import org.traccar.model.Device; import java.nio.charset.StandardCharsets; @@ -74,11 +73,11 @@ protected Object encodeCommand(Command command) { String password = AttributeUtil.getDevicePassword( getCacheManager(), command.getDeviceId(), getProtocolName(), "123456"); - Device device = getCacheManager().getObject(Device.class, command.getDeviceId()); + String model = getDeviceModel(command.getDeviceId()); switch (command.getType()) { case Command.TYPE_ENGINE_STOP: - if ("G109".equals(device.getModel())) { + if ("G109".equals(model)) { return encodeContent(command.getDeviceId(), "DYD#"); } else if (alternative) { return encodeContent(command.getDeviceId(), "DYD," + password + "#"); @@ -86,7 +85,7 @@ protected Object encodeCommand(Command command) { return encodeContent(command.getDeviceId(), "Relay,1#"); } case Command.TYPE_ENGINE_RESUME: - if ("G109".equals(device.getModel())) { + if ("G109".equals(model)) { return encodeContent(command.getDeviceId(), "HFYD#"); } else if (alternative) { return encodeContent(command.getDeviceId(), "HFYD," + password + "#"); diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java index 5745909c710..343d42e09df 100644 --- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java @@ -28,7 +28,6 @@ import org.traccar.model.CellTower; import org.traccar.model.Network; import org.traccar.model.Position; -import org.traccar.model.Device; import org.traccar.helper.BitUtil; import java.net.SocketAddress; @@ -226,7 +225,7 @@ protected Object decodeAvrmc( return null; } - String model = getCacheManager().getObject(Device.class, deviceSession.getDeviceId()).getModel(); + String model = getDeviceModel(deviceSession); Position position = new Position(getProtocolName()); diff --git a/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java b/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java index 5859d91ce22..6d3b4f7e93a 100644 --- a/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java @@ -24,7 +24,6 @@ import org.traccar.helper.DataConverter; import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Command; -import org.traccar.model.Device; import java.nio.charset.StandardCharsets; import java.util.Set; @@ -64,7 +63,7 @@ private ByteBuf encodeOutputCommand(long deviceId, int index, int value) { int outputCount; int outputType; - String model = getCacheManager().getObject(Device.class, deviceId).getModel(); + String model = getDeviceModel(deviceId); if (model != null && Set.of("TK510", "GT08", "TK208", "TK228", "MT05").contains(model)) { outputCount = 5; diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java index 965c8ee0de0..88b6380a506 100644 --- a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java @@ -20,7 +20,6 @@ import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.model.Device; import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; @@ -38,6 +37,7 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.regex.Pattern; public class MeitrackProtocolDecoder extends BaseProtocolDecoder { @@ -206,11 +206,7 @@ private Position decodeRegular(Channel channel, SocketAddress remoteAddress, Byt position.set(Position.PREFIX_ADC + i, parser.nextHexInt()); } - String model = getCacheManager().getObject(Device.class, deviceSession.getDeviceId()).getModel(); - if (model == null) { - model = ""; - } - switch (model.toUpperCase()) { + switch (Objects.requireNonNullElse(getDeviceModel(deviceSession), "").toUpperCase()) { case "MVT340": case "MVT380": position.set(Position.KEY_BATTERY, parser.nextHexInt() * 3.0 * 2.0 / 1024.0); diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolEncoder.java index 72ac9db4ecb..6e330a4ddda 100644 --- a/src/main/java/org/traccar/protocol/Minifinder2ProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/Minifinder2ProtocolEncoder.java @@ -21,7 +21,6 @@ import org.traccar.Protocol; import org.traccar.helper.Checksum; import org.traccar.model.Command; -import org.traccar.model.Device; import java.nio.charset.StandardCharsets; @@ -55,8 +54,7 @@ protected Object encodeCommand(Command command) { content.writeByte(0xF0); // type } - Device device = getCacheManager().getObject(Device.class, command.getDeviceId()); - if ("Nano".equalsIgnoreCase(device.getModel())) { + if ("Nano".equalsIgnoreCase(getDeviceModel(command.getDeviceId()))) { ByteBuf content = Unpooled.buffer(); if (command.getType().equals(Command.TYPE_FIRMWARE_UPDATE)) { String url = command.getString(Command.KEY_DATA); diff --git a/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java index 9380d23271b..e0dfab9b258 100644 --- a/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java @@ -23,7 +23,6 @@ import io.netty.handler.codec.http.HttpResponseStatus; import org.traccar.BaseHttpProtocolDecoder; import org.traccar.helper.BufferUtil; -import org.traccar.model.Device; import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -155,12 +154,10 @@ protected Object decode( } else if (jsonContains(json, "data")) { - String model = getCacheManager().getObject(Device.class, deviceSession.getDeviceId()).getModel(); - ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(json.getString("data"))); try { int header = buf.readUnsignedByte(); - if ("Amber".equals(model)) { + if ("Amber".equals(getDeviceModel(deviceSession))) { int flags = buf.readUnsignedByte(); position.set(Position.KEY_MOTION, BitUtil.check(flags, 1)); diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java index 2f0874797b8..6197c6c1394 100644 --- a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java @@ -21,7 +21,6 @@ import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; import org.traccar.helper.BufferUtil; -import org.traccar.model.Device; import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; @@ -654,7 +653,6 @@ private List parseData( if (deviceSession == null) { return null; } - String model = getCacheManager().getObject(Device.class, deviceSession.getDeviceId()).getModel(); for (int i = 0; i < count; i++) { Position position = new Position(getProtocolName()); @@ -680,7 +678,7 @@ private List parseData( } else if (codec == CODEC_12) { decodeSerial(channel, remoteAddress, deviceSession, position, buf); } else { - decodeLocation(position, buf, codec, model); + decodeLocation(position, buf, codec, getDeviceModel(deviceSession)); } if (!position.getOutdated() || !position.getAttributes().isEmpty()) { diff --git a/src/main/java/org/traccar/session/ConnectionManager.java b/src/main/java/org/traccar/session/ConnectionManager.java index 121b876c5f8..42dcf5ce97d 100644 --- a/src/main/java/org/traccar/session/ConnectionManager.java +++ b/src/main/java/org/traccar/session/ConnectionManager.java @@ -147,7 +147,7 @@ public DeviceSession getDeviceSession( } DeviceSession deviceSession = new DeviceSession( - device.getId(), device.getUniqueId(), protocol, channel, remoteAddress); + device.getId(), device.getUniqueId(), device.getModel(), protocol, channel, remoteAddress); endpointSessions.put(device.getUniqueId(), deviceSession); sessionsByEndpoint.put(remoteAddress, endpointSessions); sessionsByDeviceId.put(device.getId(), deviceSession); diff --git a/src/main/java/org/traccar/session/DeviceSession.java b/src/main/java/org/traccar/session/DeviceSession.java index 009f90f5ae3..f124ca7f9f0 100644 --- a/src/main/java/org/traccar/session/DeviceSession.java +++ b/src/main/java/org/traccar/session/DeviceSession.java @@ -29,14 +29,17 @@ public class DeviceSession { private final long deviceId; private final String uniqueId; + private final String model; private final Protocol protocol; private final Channel channel; private final SocketAddress remoteAddress; public DeviceSession( - long deviceId, String uniqueId, Protocol protocol, Channel channel, SocketAddress remoteAddress) { + long deviceId, String uniqueId, String model, + Protocol protocol, Channel channel, SocketAddress remoteAddress) { this.deviceId = deviceId; this.uniqueId = uniqueId; + this.model = model; this.protocol = protocol; this.channel = channel; this.remoteAddress = remoteAddress; @@ -50,6 +53,10 @@ public String getUniqueId() { return uniqueId; } + public String getModel() { + return model; + } + public Channel getChannel() { return channel; } diff --git a/src/test/java/org/traccar/BaseTest.java b/src/test/java/org/traccar/BaseTest.java index 2ace781f37c..ce19d8ace37 100644 --- a/src/test/java/org/traccar/BaseTest.java +++ b/src/test/java/org/traccar/BaseTest.java @@ -33,7 +33,8 @@ protected T inject(T decoder) throws Exception { var connectionManager = mock(ConnectionManager.class); var uniqueIdsProvided = new HashSet(); when(connectionManager.getDeviceSession(any(), any(), any(), any(String[].class))).thenAnswer(invocation -> { - var mock = new DeviceSession(1L, "", mock(Protocol.class), mock(Channel.class), mock(SocketAddress.class)); + var mock = new DeviceSession( + 1L, "", null, mock(Protocol.class), mock(Channel.class), mock(SocketAddress.class)); if (uniqueIdsProvided.isEmpty()) { if (invocation.getArguments().length > 3) { uniqueIdsProvided.add(true); diff --git a/src/test/java/org/traccar/protocol/Minifinder2ProtocolEncoderTest.java b/src/test/java/org/traccar/protocol/Minifinder2ProtocolEncoderTest.java index ef6ff6dd9f5..32c8a9ce68d 100644 --- a/src/test/java/org/traccar/protocol/Minifinder2ProtocolEncoderTest.java +++ b/src/test/java/org/traccar/protocol/Minifinder2ProtocolEncoderTest.java @@ -14,8 +14,7 @@ public void testEncodeNano() throws Exception { var encoder = inject(new Minifinder2ProtocolEncoder(null)); - var device = encoder.getCacheManager().getObject(Device.class, 1); - when(device.getModel()).thenReturn("Nano"); + encoder.setModelOverride("Nano"); Command command = new Command(); command.setDeviceId(1); diff --git a/src/test/java/org/traccar/protocol/SigfoxProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/SigfoxProtocolDecoderTest.java index 8a275ce9a86..c7d0671c00f 100644 --- a/src/test/java/org/traccar/protocol/SigfoxProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/SigfoxProtocolDecoderTest.java @@ -46,8 +46,7 @@ public void testDecode() throws Exception { verifyPosition(decoder, request(HttpMethod.POST, "/", buffer("%7B++%22device%22%3A%222BF839%22%2C++%22time%22%3A1510605882%2C++%22duplicate%22%3Afalse%2C++%22snr%22%3A45.61%2C++%22station%22%3A%2235A9%22%2C++%22data%22%3A%2200bd6475e907398e562d01b9%22%2C++%22avgSnr%22%3A45.16%2C++%22lat%22%3A-38.0%2C++%22lng%22%3A145.0%2C++%22rssi%22%3A-98.00%2C++%22seqNumber%22%3A228+%7D="))); - var device = decoder.getCacheManager().getObject(Device.class, 1); - when(device.getModel()).thenReturn("Amber"); + decoder.setModelOverride("Amber"); verifyPosition(decoder, request(HttpMethod.POST, "/", buffer("{ \"deviceId\":\"284019F\", \"timestamp\":\"1707375610\", \"seqNo\":\"42\", \"data\":\"0100b019ffe8645d0019e513\", \"linkQuality\":\"Excellent\", \"operator\":\"SIGFOX_South_Africa_Sqwidnet\", \"country\":\"710\" }"))); From 8cf3fcd643faebd9cb0937bc1e8454a22ad8b137 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Mon, 19 Feb 2024 13:27:02 -0800 Subject: [PATCH 55/55] Support GV355CEU CAN data --- .../protocol/Gl200TextProtocolDecoder.java | 36 +++++++++++++++---- .../Gl200TextProtocolDecoderTest.java | 5 +++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java index 0628a06d41e..8fb30e2ad01 100644 --- a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java @@ -16,6 +16,7 @@ package org.traccar.protocol; import io.netty.buffer.Unpooled; +import org.apache.commons.lang3.StringUtils; import org.traccar.BaseProtocolDecoder; import org.traccar.helper.DataConverter; import org.traccar.session.DeviceSession; @@ -419,17 +420,17 @@ private Object decodeCan(Channel channel, SocketAddress remoteAddress, String se DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[index++]); position.setDeviceId(deviceSession.getDeviceId()); - String deviceName = values[index++]; + String model = StringUtils.firstNonEmpty(values[index++], getDeviceModel(deviceSession)); index += 1; // report type - index += 1; // canbus state + index += 1; // can bus state long reportMask = Long.parseLong(values[index++], 16); long reportMaskExt = 0; if (BitUtil.check(reportMask, 0)) { position.set(Position.KEY_VIN, values[index++]); } - if (BitUtil.check(reportMask, 1)) { - position.set(Position.KEY_IGNITION, Integer.parseInt(values[index++]) > 0); + if (BitUtil.check(reportMask, 1) && !values[index++].isEmpty()) { + position.set(Position.KEY_IGNITION, Integer.parseInt(values[index - 1]) > 0); } if (BitUtil.check(reportMask, 2)) { position.set(Position.KEY_OBD_ODOMETER, values[index++]); @@ -491,7 +492,7 @@ private Object decodeCan(Channel channel, SocketAddress remoteAddress, String se if (BitUtil.check(reportMask, 21) && !values[index++].isEmpty()) { position.set("engineOverspeed", Double.parseDouble(values[index - 1])); } - if ("GV350M".equals(deviceName)) { + if ("GV350M".equals(model)) { if (BitUtil.check(reportMask, 22)) { index += 1; // impulse distance } @@ -501,6 +502,28 @@ private Object decodeCan(Channel channel, SocketAddress remoteAddress, String se if (BitUtil.check(reportMask, 24)) { index += 1; // catalyst liquid level } + } else if ("GV355CEU".equals(model)) { + if (BitUtil.check(reportMask, 22)) { + index += 1; // impulse distance + } + if (BitUtil.check(reportMask, 23)) { + index += 1; // engine cold starts + } + if (BitUtil.check(reportMask, 24)) { + index += 1; // engine all starts + } + if (BitUtil.check(reportMask, 25)) { + index += 1; // engine starts by ignition + } + if (BitUtil.check(reportMask, 26)) { + index += 1; // total engine cold running time + } + if (BitUtil.check(reportMask, 27)) { + index += 1; // handbrake applies during ride + } + if (BitUtil.check(reportMask, 28)) { + index += 1; // electric report mask + } } if (BitUtil.check(reportMask, 29) && !values[index++].isEmpty()) { reportMaskExt = Long.parseLong(values[index - 1], 16); @@ -581,7 +604,7 @@ private Object decodeCan(Channel channel, SocketAddress remoteAddress, String se DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - if (BitUtil.check(reportMask, 30)) { + if (!"GV355CEU".equals(model) && BitUtil.check(reportMask, 30)) { while (values[index].isEmpty()) { index += 1; } @@ -606,6 +629,7 @@ private Object decodeCan(Channel channel, SocketAddress remoteAddress, String se index += 1; // reserved } + index = values.length - 2; if (ignoreFixTime) { position.setTime(dateFormat.parse(values[index])); } else { diff --git a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java index 2c012eb6fd5..6327f65792f 100644 --- a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java @@ -478,6 +478,11 @@ public void testDecode() throws Exception { verifyAttributes(decoder, buffer( "+ACK:GTGEO,1A0102,135790246811220,,0,0008,20100310172830,11F0")); + decoder.setModelOverride("GV355CEU"); + + 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$")); + } }