aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/traccar
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/traccar')
-rw-r--r--src/main/java/org/traccar/BaseMqttProtocolDecoder.java96
-rw-r--r--src/main/java/org/traccar/BasePipelineFactory.java31
-rw-r--r--src/main/java/org/traccar/BaseProtocol.java7
-rw-r--r--src/main/java/org/traccar/BaseProtocolDecoder.java22
-rw-r--r--src/main/java/org/traccar/BaseProtocolEncoder.java52
-rw-r--r--src/main/java/org/traccar/ExtendedObjectDecoder.java14
-rw-r--r--src/main/java/org/traccar/Main.java32
-rw-r--r--src/main/java/org/traccar/MainEventHandler.java7
-rw-r--r--src/main/java/org/traccar/MainModule.java91
-rw-r--r--src/main/java/org/traccar/PositionForwardingHandler.java6
-rw-r--r--src/main/java/org/traccar/ServerManager.java4
-rw-r--r--src/main/java/org/traccar/WindowsService.java4
-rw-r--r--src/main/java/org/traccar/api/AsyncSocket.java40
-rw-r--r--src/main/java/org/traccar/api/AsyncSocketServlet.java6
-rw-r--r--src/main/java/org/traccar/api/BaseObjectResource.java39
-rw-r--r--src/main/java/org/traccar/api/BaseResource.java6
-rw-r--r--src/main/java/org/traccar/api/CorsResponseFilter.java10
-rw-r--r--src/main/java/org/traccar/api/DateParameterConverterProvider.java4
-rw-r--r--src/main/java/org/traccar/api/ExtendedObjectResource.java4
-rw-r--r--src/main/java/org/traccar/api/MediaFilter.java31
-rw-r--r--src/main/java/org/traccar/api/ResourceErrorHandler.java6
-rw-r--r--src/main/java/org/traccar/api/SimpleObjectResource.java4
-rw-r--r--src/main/java/org/traccar/api/resource/AttributeResource.java28
-rw-r--r--src/main/java/org/traccar/api/resource/CalendarResource.java8
-rw-r--r--src/main/java/org/traccar/api/resource/CommandResource.java48
-rw-r--r--src/main/java/org/traccar/api/resource/DeviceResource.java89
-rw-r--r--src/main/java/org/traccar/api/resource/DriverResource.java8
-rw-r--r--src/main/java/org/traccar/api/resource/EventResource.java16
-rw-r--r--src/main/java/org/traccar/api/resource/GeofenceResource.java8
-rw-r--r--src/main/java/org/traccar/api/resource/GroupResource.java8
-rw-r--r--src/main/java/org/traccar/api/resource/MaintenanceResource.java8
-rw-r--r--src/main/java/org/traccar/api/resource/NotificationResource.java56
-rw-r--r--src/main/java/org/traccar/api/resource/OrderResource.java8
-rw-r--r--src/main/java/org/traccar/api/resource/PasswordResource.java24
-rw-r--r--src/main/java/org/traccar/api/resource/PermissionsResource.java34
-rw-r--r--src/main/java/org/traccar/api/resource/PositionResource.java39
-rw-r--r--src/main/java/org/traccar/api/resource/ReportResource.java69
-rw-r--r--src/main/java/org/traccar/api/resource/ServerResource.java101
-rw-r--r--src/main/java/org/traccar/api/resource/SessionResource.java138
-rw-r--r--src/main/java/org/traccar/api/resource/StatisticsResource.java12
-rw-r--r--src/main/java/org/traccar/api/resource/UserResource.java57
-rw-r--r--src/main/java/org/traccar/api/security/CodeRequiredException.java (renamed from src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java)20
-rw-r--r--src/main/java/org/traccar/api/security/LoginResult.java29
-rw-r--r--src/main/java/org/traccar/api/security/LoginService.java66
-rw-r--r--src/main/java/org/traccar/api/security/PermissionsService.java30
-rw-r--r--src/main/java/org/traccar/api/security/SecurityRequestFilter.java66
-rw-r--r--src/main/java/org/traccar/api/security/UserPrincipal.java11
-rw-r--r--src/main/java/org/traccar/api/security/UserSecurityContext.java2
-rw-r--r--src/main/java/org/traccar/api/signature/CryptoManager.java4
-rw-r--r--src/main/java/org/traccar/api/signature/TokenManager.java24
-rw-r--r--src/main/java/org/traccar/broadcast/BaseBroadcastService.java128
-rw-r--r--src/main/java/org/traccar/broadcast/BroadcastInterface.java13
-rw-r--r--src/main/java/org/traccar/broadcast/BroadcastMessage.java114
-rw-r--r--src/main/java/org/traccar/broadcast/MulticastBroadcastService.java94
-rw-r--r--src/main/java/org/traccar/broadcast/RedisBroadcastService.java125
-rw-r--r--src/main/java/org/traccar/config/Config.java4
-rw-r--r--src/main/java/org/traccar/config/Keys.java370
-rw-r--r--src/main/java/org/traccar/database/CommandsManager.java29
-rw-r--r--src/main/java/org/traccar/database/DeviceLookupService.java6
-rw-r--r--src/main/java/org/traccar/database/MediaManager.java4
-rw-r--r--src/main/java/org/traccar/database/NotificationManager.java35
-rw-r--r--src/main/java/org/traccar/database/OpenIdProvider.java204
-rw-r--r--src/main/java/org/traccar/database/StatisticsManager.java29
-rw-r--r--src/main/java/org/traccar/forward/AmqpClient.java58
-rw-r--r--src/main/java/org/traccar/forward/EventForwarderAmqp.java48
-rw-r--r--src/main/java/org/traccar/forward/EventForwarderJson.java8
-rw-r--r--src/main/java/org/traccar/forward/EventForwarderMqtt.java7
-rw-r--r--src/main/java/org/traccar/forward/NetworkForwarder.java77
-rw-r--r--src/main/java/org/traccar/forward/PositionForwarderAmqp.java48
-rw-r--r--src/main/java/org/traccar/forward/PositionForwarderJson.java12
-rw-r--r--src/main/java/org/traccar/forward/PositionForwarderRedis.java50
-rw-r--r--src/main/java/org/traccar/forward/PositionForwarderUrl.java6
-rw-r--r--src/main/java/org/traccar/geocoder/BanGeocoder.java6
-rw-r--r--src/main/java/org/traccar/geocoder/BingMapsGeocoder.java6
-rw-r--r--src/main/java/org/traccar/geocoder/FactualGeocoder.java4
-rw-r--r--src/main/java/org/traccar/geocoder/GeoapifyGeocoder.java6
-rw-r--r--src/main/java/org/traccar/geocoder/GeocodeFarmGeocoder.java4
-rw-r--r--src/main/java/org/traccar/geocoder/GeocodeXyzGeocoder.java4
-rw-r--r--src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java4
-rw-r--r--src/main/java/org/traccar/geocoder/GoogleGeocoder.java8
-rw-r--r--src/main/java/org/traccar/geocoder/HereGeocoder.java62
-rw-r--r--src/main/java/org/traccar/geocoder/JsonGeocoder.java8
-rw-r--r--src/main/java/org/traccar/geocoder/LocationIqGeocoder.java2
-rw-r--r--src/main/java/org/traccar/geocoder/MapQuestGeocoder.java6
-rw-r--r--src/main/java/org/traccar/geocoder/MapTilerGeocoder.java6
-rw-r--r--src/main/java/org/traccar/geocoder/MapboxGeocoder.java8
-rw-r--r--src/main/java/org/traccar/geocoder/MapmyIndiaGeocoder.java6
-rw-r--r--src/main/java/org/traccar/geocoder/NominatimGeocoder.java4
-rw-r--r--src/main/java/org/traccar/geocoder/OpenCageGeocoder.java6
-rw-r--r--src/main/java/org/traccar/geocoder/PositionStackGeocoder.java6
-rw-r--r--src/main/java/org/traccar/geocoder/TomTomGeocoder.java6
-rw-r--r--src/main/java/org/traccar/geolocation/GoogleGeolocationProvider.java2
-rw-r--r--src/main/java/org/traccar/geolocation/MozillaGeolocationProvider.java2
-rw-r--r--src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java6
-rw-r--r--src/main/java/org/traccar/geolocation/UniversalGeolocationProvider.java8
-rw-r--r--src/main/java/org/traccar/geolocation/UnwiredGeolocationProvider.java8
-rw-r--r--src/main/java/org/traccar/handler/AcknowledgementHandler.java121
-rw-r--r--src/main/java/org/traccar/handler/ComputedAttributesHandler.java68
-rw-r--r--src/main/java/org/traccar/handler/CopyAttributesHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/DefaultDataHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/DistanceHandler.java30
-rw-r--r--src/main/java/org/traccar/handler/EngineHoursHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/FilterHandler.java66
-rw-r--r--src/main/java/org/traccar/handler/GeofenceHandler.java52
-rw-r--r--src/main/java/org/traccar/handler/GeolocationHandler.java7
-rw-r--r--src/main/java/org/traccar/handler/HemisphereHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/MotionHandler.java20
-rw-r--r--src/main/java/org/traccar/handler/NetworkForwarderHandler.java72
-rw-r--r--src/main/java/org/traccar/handler/RemoteAddressHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/SpeedLimitHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/StandardLoggingHandler.java65
-rw-r--r--src/main/java/org/traccar/handler/TimeHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/events/AlertEventHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/events/BaseEventHandler.java2
-rw-r--r--src/main/java/org/traccar/handler/events/BehaviorEventHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/events/CommandResultEventHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/events/DriverEventHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/events/FuelEventHandler.java10
-rw-r--r--src/main/java/org/traccar/handler/events/GeofenceEventHandler.java72
-rw-r--r--src/main/java/org/traccar/handler/events/IgnitionEventHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java23
-rw-r--r--src/main/java/org/traccar/handler/events/MediaEventHandler.java4
-rw-r--r--src/main/java/org/traccar/handler/events/MotionEventHandler.java21
-rw-r--r--src/main/java/org/traccar/handler/events/OverspeedEventHandler.java14
-rw-r--r--src/main/java/org/traccar/helper/BufferUtil.java16
-rw-r--r--src/main/java/org/traccar/helper/ObdDecoder.java36
-rw-r--r--src/main/java/org/traccar/helper/ObjectMapperContextResolver.java4
-rw-r--r--src/main/java/org/traccar/helper/Parser.java11
-rw-r--r--src/main/java/org/traccar/helper/WebHelper.java (renamed from src/main/java/org/traccar/helper/ServletHelper.java)26
-rw-r--r--src/main/java/org/traccar/helper/model/AttributeUtil.java106
-rw-r--r--src/main/java/org/traccar/helper/model/DeviceUtil.java48
-rw-r--r--src/main/java/org/traccar/helper/model/UserUtil.java10
-rw-r--r--src/main/java/org/traccar/mail/LogMailManager.java18
-rw-r--r--src/main/java/org/traccar/mail/MailManager.java12
-rw-r--r--src/main/java/org/traccar/mail/SmtpMailManager.java30
-rw-r--r--src/main/java/org/traccar/model/Calendar.java13
-rw-r--r--src/main/java/org/traccar/model/CellTower.java2
-rw-r--r--src/main/java/org/traccar/model/Device.java44
-rw-r--r--src/main/java/org/traccar/model/Driver.java2
-rw-r--r--src/main/java/org/traccar/model/Event.java1
-rw-r--r--src/main/java/org/traccar/model/ExtendedModel.java11
-rw-r--r--src/main/java/org/traccar/model/Geofence.java16
-rw-r--r--src/main/java/org/traccar/model/LogRecord.java79
-rw-r--r--src/main/java/org/traccar/model/Notification.java26
-rw-r--r--src/main/java/org/traccar/model/ObjectOperation.java7
-rw-r--r--src/main/java/org/traccar/model/Position.java23
-rw-r--r--src/main/java/org/traccar/model/Report.java16
-rw-r--r--src/main/java/org/traccar/model/Schedulable.java (renamed from src/main/java/org/traccar/model/ScheduledModel.java)16
-rw-r--r--src/main/java/org/traccar/model/Server.java38
-rw-r--r--src/main/java/org/traccar/model/User.java22
-rw-r--r--src/main/java/org/traccar/model/WifiAccessPoint.java2
-rw-r--r--src/main/java/org/traccar/notification/NotificationFormatter.java13
-rw-r--r--src/main/java/org/traccar/notification/NotificationMessage.java12
-rw-r--r--src/main/java/org/traccar/notification/NotificatorManager.java18
-rw-r--r--src/main/java/org/traccar/notification/TextTemplateFormatter.java20
-rw-r--r--src/main/java/org/traccar/notificators/Notificator.java24
-rw-r--r--src/main/java/org/traccar/notificators/NotificatorCommand.java63
-rw-r--r--src/main/java/org/traccar/notificators/NotificatorFirebase.java87
-rw-r--r--src/main/java/org/traccar/notificators/NotificatorMail.java20
-rw-r--r--src/main/java/org/traccar/notificators/NotificatorNull.java34
-rw-r--r--src/main/java/org/traccar/notificators/NotificatorPushover.java20
-rw-r--r--src/main/java/org/traccar/notificators/NotificatorSms.java18
-rw-r--r--src/main/java/org/traccar/notificators/NotificatorTelegram.java20
-rw-r--r--src/main/java/org/traccar/notificators/NotificatorTraccar.java92
-rw-r--r--src/main/java/org/traccar/notificators/NotificatorWeb.java17
-rw-r--r--src/main/java/org/traccar/protocol/AdmProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AisProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AlematicsProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AnytrekProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ApelProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AplicomProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AppelloProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AquilaProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Ardi01Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ArknavProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ArknavX8Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ArmoliProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ArnaviProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/AstraProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/At2000Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AtrackProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java202
-rw-r--r--src/main/java/org/traccar/protocol/AuroProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AustinNbProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AutoFonProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AutoGradeProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AutoTrackProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/AvemaProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Avl301Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/B2316Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/B2316ProtocolDecoder.java6
-rw-r--r--src/main/java/org/traccar/protocol/BceProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/BlackKiteProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/BlueProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/BoxProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/BstplProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/C2stekProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/CalAmpProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/CarTrackProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/CarcellProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/CarscopProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/CastelProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/CastelProtocolDecoder.java33
-rw-r--r--src/main/java/org/traccar/protocol/CautelaProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/CellocatorProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/CguardProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/CityeasyProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ContinentalProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/CradlepointProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/DingtekProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/DishaProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/DmtHttpProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/DmtHttpProtocolDecoder.java6
-rw-r--r--src/main/java/org/traccar/protocol/DmtProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/DolphinProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/DraginoProtocol.java42
-rw-r--r--src/main/java/org/traccar/protocol/DraginoProtocolDecoder.java78
-rw-r--r--src/main/java/org/traccar/protocol/Dsf22Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/DualcamProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java59
-rw-r--r--src/main/java/org/traccar/protocol/DwayProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/EasyTrackProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java70
-rw-r--r--src/main/java/org/traccar/protocol/EelinkProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/EgtsProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/EnforaProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/EnnfuProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/EnvotechProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/EsealProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/EskyProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ExtremTracProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/FifotrackProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java21
-rw-r--r--src/main/java/org/traccar/protocol/FleetGuideProtocol.java36
-rw-r--r--src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java328
-rw-r--r--src/main/java/org/traccar/protocol/FlespiProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java26
-rw-r--r--src/main/java/org/traccar/protocol/FlexApiProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/FlexCommProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/FlexibleReportProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/FlextrackProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/FoxProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/FreedomProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/FreematicsProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/FutureWayProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/G1rusProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/GalileoFrameDecoder.java16
-rw-r--r--src/main/java/org/traccar/protocol/GalileoProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/GatorProtocol.java10
-rw-r--r--src/main/java/org/traccar/protocol/GatorProtocolDecoder.java5
-rw-r--r--src/main/java/org/traccar/protocol/GatorProtocolEncoder.java93
-rw-r--r--src/main/java/org/traccar/protocol/GenxProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Gl100Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Gl200Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Gl200ProtocolDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java1388
-rw-r--r--src/main/java/org/traccar/protocol/GlobalSatProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/GlobalstarProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java56
-rw-r--r--src/main/java/org/traccar/protocol/GnxProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/GoSafeProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java12
-rw-r--r--src/main/java/org/traccar/protocol/GotopProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Gps056Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Gps103Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/GpsGateProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/GpsMarkerProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/GpsmtaProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/GranitProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Gs100Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Gt02Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Gt06Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java227
-rw-r--r--src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java7
-rw-r--r--src/main/java/org/traccar/protocol/Gt30Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/H02Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/HaicomProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/HomtecsProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/HoopoProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/HoopoProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/HuaShengProtocol.java12
-rw-r--r--src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java22
-rw-r--r--src/main/java/org/traccar/protocol/HuaShengProtocolEncoder.java85
-rw-r--r--src/main/java/org/traccar/protocol/HuabaoProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java283
-rw-r--r--src/main/java/org/traccar/protocol/HunterProProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/IdplProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/IntellitracProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/IotmProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/IotmProtocolDecoder.java147
-rw-r--r--src/main/java/org/traccar/protocol/ItsProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Ivt401Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/JidoProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/JpKorjarProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Jt600FrameDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/Jt600Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java24
-rw-r--r--src/main/java/org/traccar/protocol/KenjiProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/KhdProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/KhdProtocolDecoder.java25
-rw-r--r--src/main/java/org/traccar/protocol/L100Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/LacakProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/LacakProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/LaipacProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java33
-rw-r--r--src/main/java/org/traccar/protocol/LeafSpyProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/M2cProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/M2mProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/MaestroProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ManPowerProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Mavlink2Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/MegastekProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/MeiligaoProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java3
-rw-r--r--src/main/java/org/traccar/protocol/MeitrackProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java96
-rw-r--r--src/main/java/org/traccar/protocol/MictrackProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/MilesmateProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/MiniFinderProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java16
-rw-r--r--src/main/java/org/traccar/protocol/Minifinder2Protocol.java11
-rw-r--r--src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java231
-rw-r--r--src/main/java/org/traccar/protocol/Minifinder2ProtocolEncoder.java72
-rw-r--r--src/main/java/org/traccar/protocol/MobilogixProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/MoovboxProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/MotorProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Mta6Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/MtxProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/MxtProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/NavigilProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/NavisProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/NavisetProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/NavtelecomProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java135
-rw-r--r--src/main/java/org/traccar/protocol/NdtpV6Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/NeosProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/NetProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/NiotProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/NoranProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/NtoProtocol.java42
-rw-r--r--src/main/java/org/traccar/protocol/NtoProtocolDecoder.java92
-rw-r--r--src/main/java/org/traccar/protocol/NvsProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/NyitechProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ObdDongleProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/OigoProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/OkoProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/OmnicommProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/OpenGtsProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/OrbcommProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java8
-rw-r--r--src/main/java/org/traccar/protocol/OrionProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/OsmAndProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/OutsafeProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java10
-rw-r--r--src/main/java/org/traccar/protocol/OwnTracksProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/PacificTrackProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/PathAwayProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/PiligrimProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/PluginProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/PolteProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/PolteProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/PortmanProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/PortmanProtocolDecoder.java24
-rw-r--r--src/main/java/org/traccar/protocol/PositrexProtocol.java36
-rw-r--r--src/main/java/org/traccar/protocol/PositrexProtocolDecoder.java116
-rw-r--r--src/main/java/org/traccar/protocol/PretraceProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/PricolProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ProgressProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/PstProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Pt215Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Pt3000Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Pt502Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Pt60Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/PuiProtocol.java40
-rw-r--r--src/main/java/org/traccar/protocol/PuiProtocolDecoder.java73
-rw-r--r--src/main/java/org/traccar/protocol/R12wProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/RadarProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/RamacProtocol.java42
-rw-r--r--src/main/java/org/traccar/protocol/RamacProtocolDecoder.java112
-rw-r--r--src/main/java/org/traccar/protocol/RaveonProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/RecodaProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/RetranslatorProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/RfTrackProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/RfTrackProtocolDecoder.java6
-rw-r--r--src/main/java/org/traccar/protocol/RitiProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/RoboTrackProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/RstProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/RstProtocolDecoder.java36
-rw-r--r--src/main/java/org/traccar/protocol/RuptelaProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java167
-rw-r--r--src/main/java/org/traccar/protocol/S168Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SabertekProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SanavProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SanulProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SatsolProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SigfoxProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java36
-rw-r--r--src/main/java/org/traccar/protocol/SiwiProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SkypatrolProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SmartSoleProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SmokeyProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SolarPoweredProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SpotProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/StarLinkProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/StarcomProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/StartekFrameDecoder.java49
-rw-r--r--src/main/java/org/traccar/protocol/StartekProtocol.java7
-rw-r--r--src/main/java/org/traccar/protocol/StartekProtocolDecoder.java139
-rw-r--r--src/main/java/org/traccar/protocol/StbProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/StbProtocolDecoder.java6
-rw-r--r--src/main/java/org/traccar/protocol/Stl060Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SuntechProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java29
-rw-r--r--src/main/java/org/traccar/protocol/SupermateProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SviasProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/SwiftechProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/T55Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/T55ProtocolDecoder.java51
-rw-r--r--src/main/java/org/traccar/protocol/T57Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/T622IridiumProtocol.java39
-rw-r--r--src/main/java/org/traccar/protocol/T622IridiumProtocolDecoder.java149
-rw-r--r--src/main/java/org/traccar/protocol/T800xProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/T800xProtocolDecoder.java16
-rw-r--r--src/main/java/org/traccar/protocol/TaipPrefixEncoder.java24
-rw-r--r--src/main/java/org/traccar/protocol/TaipProtocol.java13
-rw-r--r--src/main/java/org/traccar/protocol/TaipProtocolDecoder.java28
-rw-r--r--src/main/java/org/traccar/protocol/TechTltProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TechtoCruzProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TekProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TelemaxProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TelicProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/TeltonikaProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java120
-rw-r--r--src/main/java/org/traccar/protocol/TeraTrackProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TeraTrackProtocolDecoder.java6
-rw-r--r--src/main/java/org/traccar/protocol/ThinkPowerProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ThinkRaceProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ThurayaProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Tk102Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Tk103Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java107
-rw-r--r--src/main/java/org/traccar/protocol/Tlt2hProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java53
-rw-r--r--src/main/java/org/traccar/protocol/TlvProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TmgProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TopflytechProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TopinProtocol.java11
-rw-r--r--src/main/java/org/traccar/protocol/TopinProtocolDecoder.java8
-rw-r--r--src/main/java/org/traccar/protocol/TotemProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TotemProtocolDecoder.java175
-rw-r--r--src/main/java/org/traccar/protocol/Tr20Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Tr900Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TrackboxProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TrakMateProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TramigoFrameDecoder.java8
-rw-r--r--src/main/java/org/traccar/protocol/TramigoProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TramigoProtocolDecoder.java264
-rw-r--r--src/main/java/org/traccar/protocol/TranSyncProtocol.java39
-rw-r--r--src/main/java/org/traccar/protocol/TranSyncProtocolDecoder.java166
-rw-r--r--src/main/java/org/traccar/protocol/TrvProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TrvProtocolDecoder.java43
-rw-r--r--src/main/java/org/traccar/protocol/Tt8850Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TytanProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TzoneProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java37
-rw-r--r--src/main/java/org/traccar/protocol/UlbotechProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/UproProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/UproProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/UuxProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/V680Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/ValtrackProtocol.java41
-rw-r--r--src/main/java/org/traccar/protocol/ValtrackProtocolDecoder.java84
-rw-r--r--src/main/java/org/traccar/protocol/VisiontekProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/VltProtocol.java43
-rw-r--r--src/main/java/org/traccar/protocol/VltProtocolDecoder.java139
-rw-r--r--src/main/java/org/traccar/protocol/VnetProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Vt200Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Vt200ProtocolDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/VtfmsProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/WatchFrameDecoder.java23
-rw-r--r--src/main/java/org/traccar/protocol/WatchProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/WatchProtocolDecoder.java7
-rw-r--r--src/main/java/org/traccar/protocol/WialonProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/WialonProtocolDecoder.java21
-rw-r--r--src/main/java/org/traccar/protocol/WliProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/WondexProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/WristbandProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Xexun2Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java4
-rw-r--r--src/main/java/org/traccar/protocol/XexunProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/XirgoProtocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Xrb28Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java51
-rw-r--r--src/main/java/org/traccar/protocol/Xt013Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Xt2400Protocol.java2
-rw-r--r--src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java2
-rw-r--r--src/main/java/org/traccar/protocol/YwtProtocol.java2
-rw-r--r--src/main/java/org/traccar/reports/CombinedReportProvider.java83
-rw-r--r--src/main/java/org/traccar/reports/CsvExportProvider.java2
-rw-r--r--src/main/java/org/traccar/reports/DevicesReportProvider.java78
-rw-r--r--src/main/java/org/traccar/reports/EventsReportProvider.java7
-rw-r--r--src/main/java/org/traccar/reports/GpxExportProvider.java2
-rw-r--r--src/main/java/org/traccar/reports/KmlExportProvider.java2
-rw-r--r--src/main/java/org/traccar/reports/RouteReportProvider.java19
-rw-r--r--src/main/java/org/traccar/reports/StopsReportProvider.java18
-rw-r--r--src/main/java/org/traccar/reports/SummaryReportProvider.java125
-rw-r--r--src/main/java/org/traccar/reports/TripsReportProvider.java18
-rw-r--r--src/main/java/org/traccar/reports/common/ExpressionEvaluatorFactory.java58
-rw-r--r--src/main/java/org/traccar/reports/common/ReportMailer.java12
-rw-r--r--src/main/java/org/traccar/reports/common/ReportUtils.java178
-rw-r--r--src/main/java/org/traccar/reports/common/TripsConfig.java39
-rw-r--r--src/main/java/org/traccar/reports/model/CombinedReportItem.java65
-rw-r--r--src/main/java/org/traccar/reports/model/DeviceReportItem.java48
-rw-r--r--src/main/java/org/traccar/schedule/ScheduleManager.java19
-rw-r--r--src/main/java/org/traccar/schedule/TaskClearStatus.java43
-rw-r--r--src/main/java/org/traccar/schedule/TaskDeleteTemporary.java61
-rw-r--r--src/main/java/org/traccar/schedule/TaskDeviceInactivityCheck.java39
-rw-r--r--src/main/java/org/traccar/schedule/TaskExpirations.java130
-rw-r--r--src/main/java/org/traccar/schedule/TaskHealthCheck.java28
-rw-r--r--src/main/java/org/traccar/schedule/TaskReports.java16
-rw-r--r--src/main/java/org/traccar/schedule/TaskWebSocketKeepalive.java2
-rw-r--r--src/main/java/org/traccar/session/ConnectionManager.java86
-rw-r--r--src/main/java/org/traccar/session/DeviceSession.java9
-rw-r--r--src/main/java/org/traccar/session/Endpoint.java58
-rw-r--r--src/main/java/org/traccar/session/cache/CacheGraph.java139
-rw-r--r--src/main/java/org/traccar/session/cache/CacheKey.java4
-rw-r--r--src/main/java/org/traccar/session/cache/CacheManager.java390
-rw-r--r--src/main/java/org/traccar/session/cache/CacheNode.java40
-rw-r--r--src/main/java/org/traccar/session/cache/CacheValue.java53
-rw-r--r--src/main/java/org/traccar/session/cache/WeakValueMap.java44
-rw-r--r--src/main/java/org/traccar/session/state/OverspeedProcessor.java44
-rw-r--r--src/main/java/org/traccar/sms/HttpSmsClient.java19
-rw-r--r--src/main/java/org/traccar/sms/SmsManager.java3
-rw-r--r--src/main/java/org/traccar/speedlimit/OverpassSpeedLimitProvider.java19
-rw-r--r--src/main/java/org/traccar/storage/DatabaseModule.java2
-rw-r--r--src/main/java/org/traccar/storage/DatabaseStorage.java2
-rw-r--r--src/main/java/org/traccar/storage/MemoryStorage.java126
-rw-r--r--src/main/java/org/traccar/web/ConsoleServlet.java8
-rw-r--r--src/main/java/org/traccar/web/ModernDefaultServlet.java59
-rw-r--r--src/main/java/org/traccar/web/OverrideFilter.java88
-rw-r--r--src/main/java/org/traccar/web/ResponseWrapper.java83
-rw-r--r--src/main/java/org/traccar/web/ThrottlingFilter.java17
-rw-r--r--src/main/java/org/traccar/web/WebInjectionManagerFactory.java2
-rw-r--r--src/main/java/org/traccar/web/WebModule.java3
-rw-r--r--src/main/java/org/traccar/web/WebRequestLog.java57
-rw-r--r--src/main/java/org/traccar/web/WebServer.java35
558 files changed, 11260 insertions, 3486 deletions
diff --git a/src/main/java/org/traccar/BaseMqttProtocolDecoder.java b/src/main/java/org/traccar/BaseMqttProtocolDecoder.java
new file mode 100644
index 000000000..0388563f5
--- /dev/null
+++ b/src/main/java/org/traccar/BaseMqttProtocolDecoder.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar;
+
+import io.netty.channel.Channel;
+import io.netty.handler.codec.mqtt.MqttConnectMessage;
+import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
+import io.netty.handler.codec.mqtt.MqttMessage;
+import io.netty.handler.codec.mqtt.MqttMessageBuilders;
+import io.netty.handler.codec.mqtt.MqttPublishMessage;
+import io.netty.handler.codec.mqtt.MqttSubscribeMessage;
+import org.traccar.session.DeviceSession;
+
+import java.net.SocketAddress;
+
+public abstract class BaseMqttProtocolDecoder extends BaseProtocolDecoder {
+
+ public BaseMqttProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ protected abstract Object decode(DeviceSession deviceSession, MqttPublishMessage message) throws Exception;
+
+ @Override
+ protected final Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ if (msg instanceof MqttConnectMessage) {
+
+ MqttConnectMessage message = (MqttConnectMessage) msg;
+
+ DeviceSession deviceSession = getDeviceSession(
+ channel, remoteAddress, message.payload().clientIdentifier());
+
+ MqttConnectReturnCode returnCode = deviceSession != null
+ ? MqttConnectReturnCode.CONNECTION_ACCEPTED
+ : MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED;
+
+ MqttMessage response = MqttMessageBuilders.connAck().returnCode(returnCode).build();
+
+ if (channel != null) {
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
+ }
+
+ } else if (msg instanceof MqttSubscribeMessage) {
+
+ MqttSubscribeMessage message = (MqttSubscribeMessage) msg;
+
+ MqttMessage response = MqttMessageBuilders.subAck()
+ .packetId(message.variableHeader().messageId())
+ .build();
+
+ if (channel != null) {
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
+ }
+
+ } else if (msg instanceof MqttPublishMessage) {
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
+ if (deviceSession == null) {
+ return null;
+ }
+
+ MqttPublishMessage message = (MqttPublishMessage) msg;
+
+ Object result = decode(deviceSession, message);
+
+ MqttMessage response = MqttMessageBuilders.pubAck()
+ .packetId(message.variableHeader().packetId())
+ .build();
+
+ if (channel != null) {
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
+ }
+
+ return result;
+
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/traccar/BasePipelineFactory.java b/src/main/java/org/traccar/BasePipelineFactory.java
index b184da45c..ca4a4ae63 100644
--- a/src/main/java/org/traccar/BasePipelineFactory.java
+++ b/src/main/java/org/traccar/BasePipelineFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import io.netty.channel.ChannelPipeline;
import io.netty.handler.timeout.IdleStateHandler;
import org.traccar.config.Config;
import org.traccar.config.Keys;
+import org.traccar.handler.AcknowledgementHandler;
import org.traccar.handler.ComputedAttributesHandler;
import org.traccar.handler.CopyAttributesHandler;
import org.traccar.handler.DefaultDataHandler;
@@ -32,9 +33,11 @@ import org.traccar.handler.DistanceHandler;
import org.traccar.handler.EngineHoursHandler;
import org.traccar.handler.FilterHandler;
import org.traccar.handler.GeocoderHandler;
+import org.traccar.handler.GeofenceHandler;
import org.traccar.handler.GeolocationHandler;
import org.traccar.handler.HemisphereHandler;
import org.traccar.handler.MotionHandler;
+import org.traccar.handler.NetworkForwarderHandler;
import org.traccar.handler.NetworkMessageHandler;
import org.traccar.handler.OpenChannelHandler;
import org.traccar.handler.RemoteAddressHandler;
@@ -59,16 +62,20 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> {
private final Injector injector;
private final TrackerConnector connector;
+ private final Config config;
private final String protocol;
- private int timeout;
+ private final int timeout;
public BasePipelineFactory(TrackerConnector connector, Config config, String protocol) {
this.injector = Main.getInjector();
this.connector = connector;
+ this.config = config;
this.protocol = protocol;
- timeout = config.getInteger(Keys.PROTOCOL_TIMEOUT.withPrefix(protocol));
+ int timeout = config.getInteger(Keys.PROTOCOL_TIMEOUT.withPrefix(protocol));
if (timeout == 0) {
- timeout = config.getInteger(Keys.SERVER_TIMEOUT);
+ this.timeout = config.getInteger(Keys.SERVER_TIMEOUT);
+ } else {
+ this.timeout = timeout;
}
}
@@ -110,8 +117,21 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> {
pipeline.addLast(new IdleStateHandler(timeout, 0, 0));
}
pipeline.addLast(new OpenChannelHandler(connector));
+ if (config.hasKey(Keys.SERVER_FORWARD)) {
+ int port = config.getInteger(Keys.PROTOCOL_PORT.withPrefix(protocol));
+ var handler = new NetworkForwarderHandler(port);
+ injector.injectMembers(handler);
+ pipeline.addLast(handler);
+ }
pipeline.addLast(new NetworkMessageHandler());
- pipeline.addLast(new StandardLoggingHandler(protocol));
+
+ var loggingHandler = new StandardLoggingHandler(protocol);
+ injector.injectMembers(loggingHandler);
+ pipeline.addLast(loggingHandler);
+
+ if (!connector.isDatagram() && !config.getBoolean(Keys.SERVER_INSTANT_ACKNOWLEDGEMENT)) {
+ pipeline.addLast(new AcknowledgementHandler());
+ }
addProtocolHandlers(handler -> {
if (handler instanceof BaseProtocolDecoder || handler instanceof BaseProtocolEncoder) {
@@ -134,6 +154,7 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> {
DistanceHandler.class,
RemoteAddressHandler.class,
FilterHandler.class,
+ GeofenceHandler.class,
GeocoderHandler.class,
SpeedLimitHandler.class,
MotionHandler.class,
diff --git a/src/main/java/org/traccar/BaseProtocol.java b/src/main/java/org/traccar/BaseProtocol.java
index d19fc307e..ea302997c 100644
--- a/src/main/java/org/traccar/BaseProtocol.java
+++ b/src/main/java/org/traccar/BaseProtocol.java
@@ -23,8 +23,8 @@ import org.traccar.helper.DataConverter;
import org.traccar.model.Command;
import org.traccar.sms.SmsManager;
-import javax.annotation.Nullable;
-import javax.inject.Inject;
+import jakarta.annotation.Nullable;
+import jakarta.inject.Inject;
import java.net.SocketAddress;
import java.util.Arrays;
import java.util.Collection;
@@ -105,7 +105,8 @@ public abstract class BaseProtocol implements Protocol {
} else if (command.getType().equals(Command.TYPE_CUSTOM)) {
String data = command.getString(Command.KEY_DATA);
if (BasePipelineFactory.getHandler(channel.pipeline(), StringEncoder.class) != null) {
- channel.writeAndFlush(new NetworkMessage(data, remoteAddress));
+ channel.writeAndFlush(new NetworkMessage(
+ data.replace("\\r", "\r").replace("\\n", "\n"), remoteAddress));
} else {
ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(data));
channel.writeAndFlush(new NetworkMessage(buf, remoteAddress));
diff --git a/src/main/java/org/traccar/BaseProtocolDecoder.java b/src/main/java/org/traccar/BaseProtocolDecoder.java
index 382daf92f..495a866c0 100644
--- a/src/main/java/org/traccar/BaseProtocolDecoder.java
+++ b/src/main/java/org/traccar/BaseProtocolDecoder.java
@@ -29,9 +29,8 @@ import org.traccar.model.Position;
import org.traccar.session.ConnectionManager;
import org.traccar.session.DeviceSession;
import org.traccar.session.cache.CacheManager;
-import org.traccar.storage.StorageException;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
@@ -52,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;
}
@@ -125,22 +126,31 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder {
}
protected TimeZone getTimeZone(long deviceId, String defaultTimeZone) {
- TimeZone result = TimeZone.getTimeZone(defaultTimeZone);
String timeZoneName = AttributeUtil.lookup(cacheManager, Keys.DECODER_TIMEZONE, deviceId);
if (timeZoneName != null) {
- result = TimeZone.getTimeZone(timeZoneName);
+ return TimeZone.getTimeZone(timeZoneName);
+ } else if (defaultTimeZone != null) {
+ return TimeZone.getTimeZone(defaultTimeZone);
}
- return result;
+ return null;
}
public DeviceSession getDeviceSession(Channel channel, SocketAddress remoteAddress, String... uniqueIds) {
try {
return connectionManager.getDeviceSession(protocol, channel, remoteAddress, uniqueIds);
- } catch (StorageException e) {
+ } catch (Exception e) {
throw new RuntimeException(e);
}
}
+ 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 9c3934184..e357c27dc 100644
--- a/src/main/java/org/traccar/BaseProtocolEncoder.java
+++ b/src/main/java/org/traccar/BaseProtocolEncoder.java
@@ -27,7 +27,7 @@ import org.traccar.model.Command;
import org.traccar.model.Device;
import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public abstract class BaseProtocolEncoder extends ChannelOutboundHandlerAdapter {
@@ -39,6 +39,8 @@ public abstract class BaseProtocolEncoder extends ChannelOutboundHandlerAdapter
private CacheManager cacheManager;
+ private String modelOverride;
+
public BaseProtocolEncoder(Protocol protocol) {
this.protocol = protocol;
}
@@ -68,34 +70,42 @@ public abstract class BaseProtocolEncoder extends ChannelOutboundHandlerAdapter
}
}
- @Override
- public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
-
- NetworkMessage networkMessage = (NetworkMessage) msg;
+ public void setModelOverride(String modelOverride) {
+ this.modelOverride = modelOverride;
+ }
- if (networkMessage.getMessage() instanceof Command) {
+ public String getDeviceModel(long deviceId) {
+ String model = getCacheManager().getObject(Device.class, deviceId).getModel();
+ return modelOverride != null ? modelOverride : model;
+ }
- Command command = (Command) networkMessage.getMessage();
- Object encodedCommand = encodeCommand(ctx.channel(), command);
+ @Override
+ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
- StringBuilder s = new StringBuilder();
- s.append("[").append(NetworkUtil.session(ctx.channel())).append("] ");
- s.append("id: ").append(getUniqueId(command.getDeviceId())).append(", ");
- s.append("command type: ").append(command.getType()).append(" ");
- if (encodedCommand != null) {
- s.append("sent");
- } else {
- s.append("not sent");
- }
- LOGGER.info(s.toString());
+ if (msg instanceof NetworkMessage) {
+ NetworkMessage networkMessage = (NetworkMessage) msg;
+ if (networkMessage.getMessage() instanceof Command) {
- ctx.write(new NetworkMessage(encodedCommand, networkMessage.getRemoteAddress()), promise);
+ Command command = (Command) networkMessage.getMessage();
+ Object encodedCommand = encodeCommand(ctx.channel(), command);
- } else {
+ StringBuilder s = new StringBuilder();
+ s.append("[").append(NetworkUtil.session(ctx.channel())).append("] ");
+ s.append("id: ").append(getUniqueId(command.getDeviceId())).append(", ");
+ s.append("command type: ").append(command.getType()).append(" ");
+ if (encodedCommand != null) {
+ s.append("sent");
+ } else {
+ s.append("not sent");
+ }
+ LOGGER.info(s.toString());
- super.write(ctx, msg, promise);
+ ctx.write(new NetworkMessage(encodedCommand, networkMessage.getRemoteAddress()), promise);
+ return;
+ }
}
+ super.write(ctx, msg, promise);
}
protected Object encodeCommand(Channel channel, Command command) {
diff --git a/src/main/java/org/traccar/ExtendedObjectDecoder.java b/src/main/java/org/traccar/ExtendedObjectDecoder.java
index f79a36c85..cddddcd80 100644
--- a/src/main/java/org/traccar/ExtendedObjectDecoder.java
+++ b/src/main/java/org/traccar/ExtendedObjectDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,13 +23,15 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
import org.traccar.config.Config;
import org.traccar.config.Keys;
+import org.traccar.handler.AcknowledgementHandler;
import org.traccar.helper.DataConverter;
import org.traccar.model.Position;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
+import java.util.List;
public abstract class ExtendedObjectDecoder extends ChannelInboundHandlerAdapter {
@@ -68,6 +70,7 @@ public abstract class ExtendedObjectDecoder extends ChannelInboundHandlerAdapter
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NetworkMessage networkMessage = (NetworkMessage) msg;
Object originalMessage = networkMessage.getMessage();
+ ctx.writeAndFlush(new AcknowledgementHandler.EventReceived());
try {
Object decodedMessage = decode(ctx.channel(), networkMessage.getRemoteAddress(), originalMessage);
onMessageEvent(ctx.channel(), networkMessage.getRemoteAddress(), originalMessage, decodedMessage);
@@ -76,14 +79,19 @@ public abstract class ExtendedObjectDecoder extends ChannelInboundHandlerAdapter
}
if (decodedMessage != null) {
if (decodedMessage instanceof Collection) {
- for (Object o : (Collection) decodedMessage) {
+ var collection = (Collection) decodedMessage;
+ ctx.writeAndFlush(new AcknowledgementHandler.EventDecoded(collection));
+ for (Object o : collection) {
saveOriginal(o, originalMessage);
ctx.fireChannelRead(o);
}
} else {
+ ctx.writeAndFlush(new AcknowledgementHandler.EventDecoded(List.of(decodedMessage)));
saveOriginal(decodedMessage, originalMessage);
ctx.fireChannelRead(decodedMessage);
}
+ } else {
+ ctx.writeAndFlush(new AcknowledgementHandler.EventDecoded(List.of()));
}
} finally {
ReferenceCountUtil.release(originalMessage);
diff --git a/src/main/java/org/traccar/Main.java b/src/main/java/org/traccar/Main.java
index e34fbb72a..33fcf654f 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 com.google.inject.Injector;
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.MemoryMXBean;
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 final class Main {
+ " 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 final class Main {
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<LifecycleObject>();
+ 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/MainEventHandler.java b/src/main/java/org/traccar/MainEventHandler.java
index 877f03ae7..fb0171d63 100644
--- a/src/main/java/org/traccar/MainEventHandler.java
+++ b/src/main/java/org/traccar/MainEventHandler.java
@@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.database.StatisticsManager;
+import org.traccar.handler.AcknowledgementHandler;
import org.traccar.helper.DateUtil;
import org.traccar.helper.NetworkUtil;
import org.traccar.helper.model.PositionUtil;
@@ -40,8 +41,8 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -145,6 +146,8 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter {
LOGGER.info(builder.toString());
statisticsManager.registerMessageStored(position.getDeviceId(), position.getProtocol());
+
+ ctx.writeAndFlush(new AcknowledgementHandler.EventHandled(position));
}
}
diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java
index 55211d109..3fec4d1e6 100644
--- a/src/main/java/org/traccar/MainModule.java
+++ b/src/main/java/org/traccar/MainModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@ package org.traccar;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
-import com.fasterxml.jackson.datatype.jsr353.JSR353Module;
+import com.fasterxml.jackson.datatype.jsonp.JSONPModule;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.Provides;
@@ -26,22 +26,25 @@ import com.google.inject.name.Names;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timer;
import org.apache.velocity.app.VelocityEngine;
-import org.apache.velocity.runtime.log.NullLogChute;
-import org.eclipse.jetty.util.URIUtil;
import org.traccar.broadcast.BroadcastService;
import org.traccar.broadcast.MulticastBroadcastService;
+import org.traccar.broadcast.RedisBroadcastService;
import org.traccar.broadcast.NullBroadcastService;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.database.LdapProvider;
+import org.traccar.database.OpenIdProvider;
import org.traccar.database.StatisticsManager;
import org.traccar.forward.EventForwarder;
import org.traccar.forward.EventForwarderJson;
+import org.traccar.forward.EventForwarderAmqp;
import org.traccar.forward.EventForwarderKafka;
import org.traccar.forward.EventForwarderMqtt;
import org.traccar.forward.PositionForwarder;
import org.traccar.forward.PositionForwarderJson;
+import org.traccar.forward.PositionForwarderAmqp;
import org.traccar.forward.PositionForwarderKafka;
+import org.traccar.forward.PositionForwarderRedis;
import org.traccar.forward.PositionForwarderUrl;
import org.traccar.geocoder.AddressFormat;
import org.traccar.geocoder.BanGeocoder;
@@ -74,6 +77,7 @@ import org.traccar.handler.GeolocationHandler;
import org.traccar.handler.SpeedLimitHandler;
import org.traccar.helper.ObjectMapperContextResolver;
import org.traccar.helper.SanitizerModule;
+import org.traccar.helper.WebHelper;
import org.traccar.mail.LogMailManager;
import org.traccar.mail.MailManager;
import org.traccar.mail.SmtpMailManager;
@@ -84,16 +88,18 @@ import org.traccar.sms.SnsSmsClient;
import org.traccar.speedlimit.OverpassSpeedLimitProvider;
import org.traccar.speedlimit.SpeedLimitProvider;
import org.traccar.storage.DatabaseStorage;
+import org.traccar.storage.MemoryStorage;
import org.traccar.storage.Storage;
import org.traccar.web.WebServer;
+import org.traccar.api.security.LoginService;
-import javax.annotation.Nullable;
-import javax.inject.Singleton;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientBuilder;
+import jakarta.annotation.Nullable;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
import java.io.IOException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
import java.util.Properties;
public class MainModule extends AbstractModule {
@@ -108,18 +114,27 @@ public class MainModule extends AbstractModule {
protected void configure() {
bindConstant().annotatedWith(Names.named("configFile")).to(configFile);
bind(Config.class).asEagerSingleton();
- bind(Storage.class).to(DatabaseStorage.class).in(Scopes.SINGLETON);
bind(Timer.class).to(HashedWheelTimer.class).in(Scopes.SINGLETON);
}
@Singleton
@Provides
+ public static Storage provideStorage(Injector injector, Config config) {
+ if (config.getBoolean(Keys.DATABASE_MEMORY)) {
+ return injector.getInstance(MemoryStorage.class);
+ } else {
+ return injector.getInstance(DatabaseStorage.class);
+ }
+ }
+
+ @Singleton
+ @Provides
public static ObjectMapper provideObjectMapper(Config config) {
ObjectMapper objectMapper = new ObjectMapper();
if (config.getBoolean(Keys.WEB_SANITIZE)) {
objectMapper.registerModule(new SanitizerModule());
}
- objectMapper.registerModule(new JSR353Module());
+ objectMapper.registerModule(new JSONPModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return objectMapper;
}
@@ -160,6 +175,17 @@ public class MainModule extends AbstractModule {
return null;
}
+ @Singleton
+ @Provides
+ public static OpenIdProvider provideOpenIDProvider(
+ Config config, LoginService loginService, ObjectMapper objectMapper
+ ) throws InterruptedException, IOException, URISyntaxException {
+ if (config.hasKey(Keys.OPENID_CLIENT_ID)) {
+ return new OpenIdProvider(config, loginService, HttpClient.newHttpClient(), objectMapper);
+ }
+ return null;
+ }
+
@Provides
public static WebServer provideWebServer(Injector injector, Config config) {
if (config.hasKey(Keys.WEB_PORT)) {
@@ -174,7 +200,6 @@ public class MainModule extends AbstractModule {
if (config.getBoolean(Keys.GEOCODER_ENABLE)) {
String type = config.getString(Keys.GEOCODER_TYPE, "google");
String url = config.getString(Keys.GEOCODER_URL);
- String id = config.getString(Keys.GEOCODER_ID);
String key = config.getString(Keys.GEOCODER_KEY);
String language = config.getString(Keys.GEOCODER_LANGUAGE);
String formatString = config.getString(Keys.GEOCODER_FORMAT);
@@ -217,7 +242,7 @@ public class MainModule extends AbstractModule {
geocoder = new BanGeocoder(client, cacheSize, addressFormat);
break;
case "here":
- geocoder = new HereGeocoder(client, url, id, key, language, cacheSize, addressFormat);
+ geocoder = new HereGeocoder(client, url, key, language, cacheSize, addressFormat);
break;
case "mapmyindia":
geocoder = new MapmyIndiaGeocoder(client, url, key, cacheSize, addressFormat);
@@ -277,7 +302,7 @@ public class MainModule extends AbstractModule {
switch (type) {
case "overpass":
default:
- return new OverpassSpeedLimitProvider(client, url);
+ return new OverpassSpeedLimitProvider(config, client, url);
}
}
return null;
@@ -317,8 +342,15 @@ public class MainModule extends AbstractModule {
@Provides
public static BroadcastService provideBroadcastService(
Config config, ObjectMapper objectMapper) throws IOException {
- if (config.hasKey(Keys.BROADCAST_ADDRESS)) {
- return new MulticastBroadcastService(config, objectMapper);
+ if (config.hasKey(Keys.BROADCAST_TYPE)) {
+ switch (config.getString(Keys.BROADCAST_TYPE)) {
+ case "multicast":
+ return new MulticastBroadcastService(config, objectMapper);
+ case "redis":
+ return new RedisBroadcastService(config, objectMapper);
+ default:
+ break;
+ }
}
return new NullBroadcastService();
}
@@ -329,10 +361,13 @@ public class MainModule extends AbstractModule {
if (config.hasKey(Keys.EVENT_FORWARD_URL)) {
String forwardType = config.getString(Keys.EVENT_FORWARD_TYPE);
switch (forwardType) {
+ case "amqp":
+ return new EventForwarderAmqp(config, objectMapper);
case "kafka":
return new EventForwarderKafka(config, objectMapper);
case "mqtt":
return new EventForwarderMqtt(config, objectMapper);
+ case "json":
default:
return new EventForwarderJson(config, client);
}
@@ -347,8 +382,13 @@ public class MainModule extends AbstractModule {
switch (config.getString(Keys.FORWARD_TYPE)) {
case "json":
return new PositionForwarderJson(config, client, objectMapper);
+ case "amqp":
+ return new PositionForwarderAmqp(config, objectMapper);
case "kafka":
return new PositionForwarderKafka(config, objectMapper);
+ case "redis":
+ return new PositionForwarderRedis(config, objectMapper);
+ case "url":
default:
return new PositionForwarderUrl(config, client, objectMapper);
}
@@ -360,21 +400,8 @@ public class MainModule extends AbstractModule {
@Provides
public static VelocityEngine provideVelocityEngine(Config config) {
Properties properties = new Properties();
- properties.setProperty("file.resource.loader.path", config.getString(Keys.TEMPLATES_ROOT) + "/");
- properties.setProperty("runtime.log.logsystem.class", NullLogChute.class.getName());
-
- if (config.hasKey(Keys.WEB_URL)) {
- properties.setProperty("web.url", config.getString(Keys.WEB_URL).replaceAll("/$", ""));
- } else {
- String address;
- try {
- address = config.getString(Keys.WEB_ADDRESS, InetAddress.getLocalHost().getHostAddress());
- } catch (UnknownHostException e) {
- address = "localhost";
- }
- String url = URIUtil.newURI("http", address, config.getInteger(Keys.WEB_PORT), "", "");
- properties.setProperty("web.url", url);
- }
+ properties.setProperty("resource.loader.file.path", config.getString(Keys.TEMPLATES_ROOT) + "/");
+ properties.setProperty("web.url", WebHelper.retrieveWebUrl(config));
VelocityEngine velocityEngine = new VelocityEngine();
velocityEngine.init(properties);
diff --git a/src/main/java/org/traccar/PositionForwardingHandler.java b/src/main/java/org/traccar/PositionForwardingHandler.java
index 83f91e937..a79b01367 100644
--- a/src/main/java/org/traccar/PositionForwardingHandler.java
+++ b/src/main/java/org/traccar/PositionForwardingHandler.java
@@ -30,9 +30,9 @@ import org.traccar.model.Device;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.annotation.Nullable;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
diff --git a/src/main/java/org/traccar/ServerManager.java b/src/main/java/org/traccar/ServerManager.java
index 57afb01fd..e91be50a2 100644
--- a/src/main/java/org/traccar/ServerManager.java
+++ b/src/main/java/org/traccar/ServerManager.java
@@ -22,8 +22,8 @@ import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.helper.ClassScanner;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.IOException;
import java.net.BindException;
import java.net.ConnectException;
diff --git a/src/main/java/org/traccar/WindowsService.java b/src/main/java/org/traccar/WindowsService.java
index f233337a7..08eba25a6 100644
--- a/src/main/java/org/traccar/WindowsService.java
+++ b/src/main/java/org/traccar/WindowsService.java
@@ -170,7 +170,7 @@ public abstract class WindowsService {
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 abstract class WindowsService {
}
- 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/api/AsyncSocket.java b/src/main/java/org/traccar/api/AsyncSocket.java
index 5fc4b4412..f5fbcbf62 100644
--- a/src/main/java/org/traccar/api/AsyncSocket.java
+++ b/src/main/java/org/traccar/api/AsyncSocket.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,16 +22,17 @@ import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.helper.model.PositionUtil;
-import org.traccar.session.ConnectionManager;
import org.traccar.model.Device;
import org.traccar.model.Event;
+import org.traccar.model.LogRecord;
import org.traccar.model.Position;
+import org.traccar.session.ConnectionManager;
import org.traccar.storage.Storage;
import org.traccar.storage.StorageException;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.UpdateListener {
@@ -41,12 +42,15 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U
private static final String KEY_DEVICES = "devices";
private static final String KEY_POSITIONS = "positions";
private static final String KEY_EVENTS = "events";
+ private static final String KEY_LOGS = "logs";
private final ObjectMapper objectMapper;
private final ConnectionManager connectionManager;
private final Storage storage;
private final long userId;
+ private boolean includeLogs;
+
public AsyncSocket(ObjectMapper objectMapper, ConnectionManager connectionManager, Storage storage, long userId) {
this.objectMapper = objectMapper;
this.connectionManager = connectionManager;
@@ -76,29 +80,41 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U
}
@Override
+ public void onWebSocketText(String message) {
+ super.onWebSocketText(message);
+
+ try {
+ includeLogs = objectMapper.readTree(message).get("logs").asBoolean();
+ } catch (JsonProcessingException e) {
+ LOGGER.warn("Socket JSON parsing error", e);
+ }
+ }
+
+ @Override
public void onKeepalive() {
sendData(new HashMap<>());
}
@Override
public void onUpdateDevice(Device device) {
- Map<String, Collection<?>> data = new HashMap<>();
- data.put(KEY_DEVICES, Collections.singletonList(device));
- sendData(data);
+ sendData(Map.of(KEY_DEVICES, List.of(device)));
}
@Override
public void onUpdatePosition(Position position) {
- Map<String, Collection<?>> data = new HashMap<>();
- data.put(KEY_POSITIONS, Collections.singletonList(position));
- sendData(data);
+ sendData(Map.of(KEY_POSITIONS, List.of(position)));
}
@Override
public void onUpdateEvent(Event event) {
- Map<String, Collection<?>> data = new HashMap<>();
- data.put(KEY_EVENTS, Collections.singletonList(event));
- sendData(data);
+ sendData(Map.of(KEY_EVENTS, List.of(event)));
+ }
+
+ @Override
+ public void onUpdateLog(LogRecord record) {
+ if (includeLogs) {
+ sendData(Map.of(KEY_LOGS, List.of(record)));
+ }
}
private void sendData(Map<String, Collection<?>> data) {
diff --git a/src/main/java/org/traccar/api/AsyncSocketServlet.java b/src/main/java/org/traccar/api/AsyncSocketServlet.java
index 91a745eeb..cd2c1639e 100644
--- a/src/main/java/org/traccar/api/AsyncSocketServlet.java
+++ b/src/main/java/org/traccar/api/AsyncSocketServlet.java
@@ -24,9 +24,9 @@ import org.traccar.config.Keys;
import org.traccar.session.ConnectionManager;
import org.traccar.storage.Storage;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javax.servlet.http.HttpSession;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.servlet.http.HttpSession;
import java.time.Duration;
@Singleton
diff --git a/src/main/java/org/traccar/api/BaseObjectResource.java b/src/main/java/org/traccar/api/BaseObjectResource.java
index 904781e54..2a801221b 100644
--- a/src/main/java/org/traccar/api/BaseObjectResource.java
+++ b/src/main/java/org/traccar/api/BaseObjectResource.java
@@ -16,6 +16,8 @@
*/
package org.traccar.api;
+import org.traccar.api.security.ServiceAccountUser;
+import org.traccar.model.ObjectOperation;
import org.traccar.helper.LogAction;
import org.traccar.model.BaseModel;
import org.traccar.model.Group;
@@ -28,14 +30,14 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.core.Response;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Response;
public abstract class BaseObjectResource<T extends BaseModel> extends BaseResource {
@@ -65,22 +67,25 @@ public abstract class BaseObjectResource<T extends BaseModel> extends BaseResour
}
@POST
- public Response add(T entity) throws StorageException {
+ public Response add(T entity) throws Exception {
permissionsService.checkEdit(getUserId(), entity, true);
entity.setId(storage.addObject(entity, new Request(new Columns.Exclude("id"))));
LogAction.create(getUserId(), entity);
- storage.addPermission(new Permission(User.class, getUserId(), baseClass, entity.getId()));
- cacheManager.invalidatePermission(true, User.class, getUserId(), baseClass, entity.getId());
- connectionManager.invalidatePermission(true, User.class, getUserId(), baseClass, entity.getId());
- LogAction.link(getUserId(), User.class, getUserId(), baseClass, entity.getId());
+
+ if (getUserId() != ServiceAccountUser.ID) {
+ storage.addPermission(new Permission(User.class, getUserId(), baseClass, entity.getId()));
+ cacheManager.invalidatePermission(true, User.class, getUserId(), baseClass, entity.getId(), true);
+ connectionManager.invalidatePermission(true, User.class, getUserId(), baseClass, entity.getId(), true);
+ LogAction.link(getUserId(), User.class, getUserId(), baseClass, entity.getId());
+ }
return Response.ok(entity).build();
}
@Path("{id}")
@PUT
- public Response update(T entity) throws StorageException {
+ public Response update(T entity) throws Exception {
permissionsService.checkEdit(getUserId(), entity, false);
permissionsService.checkPermission(baseClass, getUserId(), entity.getId());
@@ -106,7 +111,7 @@ public abstract class BaseObjectResource<T extends BaseModel> extends BaseResour
new Condition.Equals("id", entity.getId())));
}
}
- cacheManager.updateOrInvalidate(true, entity);
+ cacheManager.invalidateObject(true, entity.getClass(), entity.getId(), ObjectOperation.UPDATE);
LogAction.edit(getUserId(), entity);
return Response.ok(entity).build();
@@ -114,12 +119,12 @@ public abstract class BaseObjectResource<T extends BaseModel> extends BaseResour
@Path("{id}")
@DELETE
- public Response remove(@PathParam("id") long id) throws StorageException {
+ public Response remove(@PathParam("id") long id) throws Exception {
permissionsService.checkEdit(getUserId(), baseClass, false);
permissionsService.checkPermission(baseClass, getUserId(), id);
storage.removeObject(baseClass, new Request(new Condition.Equals("id", id)));
- cacheManager.invalidate(baseClass, id);
+ cacheManager.invalidateObject(true, baseClass, id, ObjectOperation.DELETE);
LogAction.remove(getUserId(), baseClass, id);
diff --git a/src/main/java/org/traccar/api/BaseResource.java b/src/main/java/org/traccar/api/BaseResource.java
index 33abe73fa..f20b8c4c2 100644
--- a/src/main/java/org/traccar/api/BaseResource.java
+++ b/src/main/java/org/traccar/api/BaseResource.java
@@ -19,9 +19,9 @@ import org.traccar.api.security.PermissionsService;
import org.traccar.api.security.UserPrincipal;
import org.traccar.storage.Storage;
-import javax.inject.Inject;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.SecurityContext;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.SecurityContext;
public class BaseResource {
diff --git a/src/main/java/org/traccar/api/CorsResponseFilter.java b/src/main/java/org/traccar/api/CorsResponseFilter.java
index 67d0341a1..a380eb41d 100644
--- a/src/main/java/org/traccar/api/CorsResponseFilter.java
+++ b/src/main/java/org/traccar/api/CorsResponseFilter.java
@@ -19,11 +19,11 @@ import io.netty.handler.codec.http.HttpHeaderNames;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javax.ws.rs.container.ContainerRequestContext;
-import javax.ws.rs.container.ContainerResponseContext;
-import javax.ws.rs.container.ContainerResponseFilter;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerResponseContext;
+import jakarta.ws.rs.container.ContainerResponseFilter;
import java.io.IOException;
@Singleton
diff --git a/src/main/java/org/traccar/api/DateParameterConverterProvider.java b/src/main/java/org/traccar/api/DateParameterConverterProvider.java
index f07ece984..4858fcbfd 100644
--- a/src/main/java/org/traccar/api/DateParameterConverterProvider.java
+++ b/src/main/java/org/traccar/api/DateParameterConverterProvider.java
@@ -17,8 +17,8 @@ package org.traccar.api;
import org.traccar.helper.DateUtil;
-import javax.ws.rs.ext.ParamConverter;
-import javax.ws.rs.ext.ParamConverterProvider;
+import jakarta.ws.rs.ext.ParamConverter;
+import jakarta.ws.rs.ext.ParamConverterProvider;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Date;
diff --git a/src/main/java/org/traccar/api/ExtendedObjectResource.java b/src/main/java/org/traccar/api/ExtendedObjectResource.java
index 8467b46c6..348ebe38a 100644
--- a/src/main/java/org/traccar/api/ExtendedObjectResource.java
+++ b/src/main/java/org/traccar/api/ExtendedObjectResource.java
@@ -25,8 +25,8 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.ws.rs.GET;
-import javax.ws.rs.QueryParam;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.QueryParam;
import java.util.Collection;
import java.util.LinkedList;
diff --git a/src/main/java/org/traccar/api/MediaFilter.java b/src/main/java/org/traccar/api/MediaFilter.java
index ab75bdc5d..38d13078d 100644
--- a/src/main/java/org/traccar/api/MediaFilter.java
+++ b/src/main/java/org/traccar/api/MediaFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2023 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,17 +28,16 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@Singleton
@@ -58,10 +57,6 @@ public class MediaFilter implements Filter {
}
@Override
- public void init(FilterConfig filterConfig) throws ServletException {
- }
-
- @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
@@ -99,8 +94,4 @@ public class MediaFilter implements Filter {
}
}
- @Override
- public void destroy() {
- }
-
}
diff --git a/src/main/java/org/traccar/api/ResourceErrorHandler.java b/src/main/java/org/traccar/api/ResourceErrorHandler.java
index 108a8e8cc..387f3db6a 100644
--- a/src/main/java/org/traccar/api/ResourceErrorHandler.java
+++ b/src/main/java/org/traccar/api/ResourceErrorHandler.java
@@ -17,9 +17,9 @@ package org.traccar.api;
import org.traccar.helper.Log;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
public class ResourceErrorHandler implements ExceptionMapper<Exception> {
diff --git a/src/main/java/org/traccar/api/SimpleObjectResource.java b/src/main/java/org/traccar/api/SimpleObjectResource.java
index 4a435ca7d..c9d41b063 100644
--- a/src/main/java/org/traccar/api/SimpleObjectResource.java
+++ b/src/main/java/org/traccar/api/SimpleObjectResource.java
@@ -23,8 +23,8 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.ws.rs.GET;
-import javax.ws.rs.QueryParam;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.QueryParam;
import java.util.Collection;
import java.util.LinkedList;
diff --git a/src/main/java/org/traccar/api/resource/AttributeResource.java b/src/main/java/org/traccar/api/resource/AttributeResource.java
index f85e90133..52c4d6324 100644
--- a/src/main/java/org/traccar/api/resource/AttributeResource.java
+++ b/src/main/java/org/traccar/api/resource/AttributeResource.java
@@ -16,17 +16,17 @@
*/
package org.traccar.api.resource;
-import javax.inject.Inject;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import org.traccar.api.ExtendedObjectResource;
import org.traccar.model.Attribute;
@@ -78,21 +78,21 @@ public class AttributeResource extends ExtendedObjectResource<Attribute> {
}
@POST
- public Response add(Attribute entity) throws StorageException {
+ public Response add(Attribute entity) throws Exception {
permissionsService.checkAdmin(getUserId());
return super.add(entity);
}
@Path("{id}")
@PUT
- public Response update(Attribute entity) throws StorageException {
+ public Response update(Attribute entity) throws Exception {
permissionsService.checkAdmin(getUserId());
return super.update(entity);
}
@Path("{id}")
@DELETE
- public Response remove(@PathParam("id") long id) throws StorageException {
+ public Response remove(@PathParam("id") long id) throws Exception {
permissionsService.checkAdmin(getUserId());
return super.remove(id);
}
diff --git a/src/main/java/org/traccar/api/resource/CalendarResource.java b/src/main/java/org/traccar/api/resource/CalendarResource.java
index 9399c34a5..f6c1f3c59 100644
--- a/src/main/java/org/traccar/api/resource/CalendarResource.java
+++ b/src/main/java/org/traccar/api/resource/CalendarResource.java
@@ -16,10 +16,10 @@
*/
package org.traccar.api.resource;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
import org.traccar.api.SimpleObjectResource;
import org.traccar.model.Calendar;
diff --git a/src/main/java/org/traccar/api/resource/CommandResource.java b/src/main/java/org/traccar/api/resource/CommandResource.java
index 3460cf6e0..c23d91e77 100644
--- a/src/main/java/org/traccar/api/resource/CommandResource.java
+++ b/src/main/java/org/traccar/api/resource/CommandResource.java
@@ -23,9 +23,12 @@ import org.traccar.BaseProtocol;
import org.traccar.ServerManager;
import org.traccar.api.ExtendedObjectResource;
import org.traccar.database.CommandsManager;
+import org.traccar.helper.model.DeviceUtil;
import org.traccar.model.Command;
import org.traccar.model.Device;
+import org.traccar.model.Group;
import org.traccar.model.Position;
+import org.traccar.model.QueuedCommand;
import org.traccar.model.Typed;
import org.traccar.model.User;
import org.traccar.model.UserRestrictions;
@@ -34,15 +37,15 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
@@ -104,7 +107,7 @@ public class CommandResource extends ExtendedObjectResource<Command> {
@POST
@Path("send")
- public Response send(Command entity) throws Exception {
+ public Response send(Command entity, @QueryParam("groupId") long groupId) throws Exception {
if (entity.getId() > 0) {
permissionsService.checkPermission(baseClass, getUserId(), entity.getId());
long deviceId = entity.getDeviceId();
@@ -114,9 +117,28 @@ public class CommandResource extends ExtendedObjectResource<Command> {
} else {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getLimitCommands);
}
- permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId());
- if (!commandsManager.sendCommand(entity)) {
- return Response.accepted(entity).build();
+
+ if (groupId > 0) {
+ permissionsService.checkPermission(Group.class, getUserId(), groupId);
+ var devices = DeviceUtil.getAccessibleDevices(storage, getUserId(), List.of(), List.of(groupId));
+ List<QueuedCommand> queuedCommands = new ArrayList<>();
+ for (Device device : devices) {
+ Command command = QueuedCommand.fromCommand(entity).toCommand();
+ command.setDeviceId(device.getId());
+ QueuedCommand queuedCommand = commandsManager.sendCommand(command);
+ if (queuedCommand != null) {
+ queuedCommands.add(queuedCommand);
+ }
+ }
+ if (!queuedCommands.isEmpty()) {
+ return Response.accepted(queuedCommands).build();
+ }
+ } else {
+ permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId());
+ QueuedCommand queuedCommand = commandsManager.sendCommand(entity);
+ if (queuedCommand != null) {
+ return Response.accepted(queuedCommand).build();
+ }
}
return Response.ok(entity).build();
}
diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java
index c0b0cea0d..89bba7237 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.
@@ -15,12 +15,17 @@
*/
package org.traccar.api.resource;
+import jakarta.ws.rs.FormParam;
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;
import org.traccar.model.DeviceAccumulators;
+import org.traccar.model.Permission;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.session.ConnectionManager;
@@ -30,23 +35,25 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.HeaderParam;
-import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.security.GeneralSecurityException;
import java.util.Collection;
+import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@@ -56,6 +63,9 @@ import java.util.List;
public class DeviceResource extends BaseObjectResource<Device> {
@Inject
+ private Config config;
+
+ @Inject
private CacheManager cacheManager;
@Inject
@@ -67,6 +77,9 @@ public class DeviceResource extends BaseObjectResource<Device> {
@Inject
private MediaManager mediaManager;
+ @Inject
+ private TokenManager tokenManager;
+
public DeviceResource() {
super(Device.class);
}
@@ -120,7 +133,7 @@ public class DeviceResource extends BaseObjectResource<Device> {
@Path("{id}/accumulators")
@PUT
- public Response updateAccumulators(DeviceAccumulators entity) throws StorageException {
+ public Response updateAccumulators(DeviceAccumulators entity) throws Exception {
if (permissionsService.notAdmin(getUserId())) {
permissionsService.checkManager(getUserId());
permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId());
@@ -183,4 +196,50 @@ public class DeviceResource extends BaseObjectResource<Device> {
return Response.status(Response.Status.NOT_FOUND).build();
}
+ @Path("share")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ @POST
+ public String shareDevice(
+ @FormParam("deviceId") long deviceId,
+ @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");
+ }
+ if (user.getExpirationTime() != null && user.getExpirationTime().before(expiration)) {
+ expiration = user.getExpirationTime();
+ }
+
+ Device device = storage.getObject(Device.class, new Request(
+ new Columns.All(),
+ new Condition.And(
+ new Condition.Equals("id", deviceId),
+ new Condition.Permission(User.class, user.getId(), Device.class))));
+
+ String shareEmail = user.getEmail() + ":" + device.getUniqueId();
+ User share = storage.getObject(User.class, new Request(
+ new Columns.All(), new Condition.Equals("email", shareEmail)));
+
+ if (share == null) {
+ share = new User();
+ share.setName(device.getName());
+ share.setEmail(shareEmail);
+ share.setExpirationTime(expiration);
+ share.setTemporary(true);
+ share.setReadonly(true);
+ 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"))));
+
+ storage.addPermission(new Permission(User.class, share.getId(), Device.class, deviceId));
+ }
+
+ return tokenManager.generateToken(share.getId(), expiration);
+ }
+
}
diff --git a/src/main/java/org/traccar/api/resource/DriverResource.java b/src/main/java/org/traccar/api/resource/DriverResource.java
index 91aa54c5e..19cf74f39 100644
--- a/src/main/java/org/traccar/api/resource/DriverResource.java
+++ b/src/main/java/org/traccar/api/resource/DriverResource.java
@@ -16,10 +16,10 @@
*/
package org.traccar.api.resource;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
import org.traccar.api.ExtendedObjectResource;
import org.traccar.model.Driver;
diff --git a/src/main/java/org/traccar/api/resource/EventResource.java b/src/main/java/org/traccar/api/resource/EventResource.java
index afdaf52b5..1f20b880d 100644
--- a/src/main/java/org/traccar/api/resource/EventResource.java
+++ b/src/main/java/org/traccar/api/resource/EventResource.java
@@ -23,14 +23,14 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
@Path("events")
@Produces(MediaType.APPLICATION_JSON)
diff --git a/src/main/java/org/traccar/api/resource/GeofenceResource.java b/src/main/java/org/traccar/api/resource/GeofenceResource.java
index 58f2c188c..030690889 100644
--- a/src/main/java/org/traccar/api/resource/GeofenceResource.java
+++ b/src/main/java/org/traccar/api/resource/GeofenceResource.java
@@ -18,10 +18,10 @@ package org.traccar.api.resource;
import org.traccar.api.ExtendedObjectResource;
import org.traccar.model.Geofence;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
@Path("geofences")
@Produces(MediaType.APPLICATION_JSON)
diff --git a/src/main/java/org/traccar/api/resource/GroupResource.java b/src/main/java/org/traccar/api/resource/GroupResource.java
index fcea15d0a..628f8f655 100644
--- a/src/main/java/org/traccar/api/resource/GroupResource.java
+++ b/src/main/java/org/traccar/api/resource/GroupResource.java
@@ -18,10 +18,10 @@ package org.traccar.api.resource;
import org.traccar.api.SimpleObjectResource;
import org.traccar.model.Group;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
@Path("groups")
@Produces(MediaType.APPLICATION_JSON)
diff --git a/src/main/java/org/traccar/api/resource/MaintenanceResource.java b/src/main/java/org/traccar/api/resource/MaintenanceResource.java
index fa1b359ce..12841e497 100644
--- a/src/main/java/org/traccar/api/resource/MaintenanceResource.java
+++ b/src/main/java/org/traccar/api/resource/MaintenanceResource.java
@@ -16,10 +16,10 @@
*/
package org.traccar.api.resource;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
import org.traccar.api.ExtendedObjectResource;
import org.traccar.model.Maintenance;
diff --git a/src/main/java/org/traccar/api/resource/NotificationResource.java b/src/main/java/org/traccar/api/resource/NotificationResource.java
index 2e4ad12f3..43dc1fa98 100644
--- a/src/main/java/org/traccar/api/resource/NotificationResource.java
+++ b/src/main/java/org/traccar/api/resource/NotificationResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,16 @@
*/
package org.traccar.api.resource;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.api.ExtendedObjectResource;
@@ -23,20 +33,16 @@ import org.traccar.model.Notification;
import org.traccar.model.Typed;
import org.traccar.model.User;
import org.traccar.notification.MessageException;
+import org.traccar.notification.NotificationMessage;
import org.traccar.notification.NotificatorManager;
import org.traccar.storage.StorageException;
+import org.traccar.storage.query.Columns;
+import org.traccar.storage.query.Condition;
+import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -80,10 +86,10 @@ public class NotificationResource extends ExtendedObjectResource<Notification> {
@POST
@Path("test")
- public Response testMessage() throws MessageException, InterruptedException, StorageException {
+ public Response testMessage() throws MessageException, StorageException {
User user = permissionsService.getUser(getUserId());
for (Typed method : notificatorManager.getAllNotificatorTypes()) {
- notificatorManager.getNotificator(method.getType()).send(user, new Event("test", 0), null);
+ notificatorManager.getNotificator(method.getType()).send(null, user, new Event("test", 0), null);
}
return Response.noContent().build();
}
@@ -91,9 +97,31 @@ public class NotificationResource extends ExtendedObjectResource<Notification> {
@POST
@Path("test/{notificator}")
public Response testMessage(@PathParam("notificator") String notificator)
- throws MessageException, InterruptedException, StorageException {
+ throws MessageException, StorageException {
User user = permissionsService.getUser(getUserId());
- notificatorManager.getNotificator(notificator).send(user, new Event("test", 0), null);
+ notificatorManager.getNotificator(notificator).send(null, user, new Event("test", 0), null);
+ return Response.noContent().build();
+ }
+
+ @POST
+ @Path("send/{notificator}")
+ public Response sendMessage(
+ @PathParam("notificator") String notificator, @QueryParam("userId") List<Long> userIds,
+ NotificationMessage message) throws MessageException, StorageException {
+ permissionsService.checkAdmin(getUserId());
+ List<User> users;
+ if (userIds.isEmpty()) {
+ users = storage.getObjects(User.class, new Request(new Columns.All()));
+ } else {
+ users = new ArrayList<>();
+ for (long userId : userIds) {
+ users.add(storage.getObject(
+ User.class, new Request(new Columns.All(), new Condition.Equals("id", userId))));
+ }
+ }
+ for (User user : users) {
+ notificatorManager.getNotificator(notificator).send(user, message, null, null);
+ }
return Response.noContent().build();
}
diff --git a/src/main/java/org/traccar/api/resource/OrderResource.java b/src/main/java/org/traccar/api/resource/OrderResource.java
index 77608a508..3852b975f 100644
--- a/src/main/java/org/traccar/api/resource/OrderResource.java
+++ b/src/main/java/org/traccar/api/resource/OrderResource.java
@@ -18,10 +18,10 @@ package org.traccar.api.resource;
import org.traccar.api.SimpleObjectResource;
import org.traccar.model.Order;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
@Path("orders")
@Produces(MediaType.APPLICATION_JSON)
diff --git a/src/main/java/org/traccar/api/resource/PasswordResource.java b/src/main/java/org/traccar/api/resource/PasswordResource.java
index 2d87a8665..22b3f0cd3 100644
--- a/src/main/java/org/traccar/api/resource/PasswordResource.java
+++ b/src/main/java/org/traccar/api/resource/PasswordResource.java
@@ -25,16 +25,16 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.annotation.security.PermitAll;
-import javax.inject.Inject;
-import javax.mail.MessagingException;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.FormParam;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.annotation.security.PermitAll;
+import jakarta.inject.Inject;
+import jakarta.mail.MessagingException;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.FormParam;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import java.io.IOException;
import java.security.GeneralSecurityException;
@@ -63,7 +63,7 @@ public class PasswordResource extends BaseResource {
if (user != null) {
var velocityContext = textTemplateFormatter.prepareContext(permissionsService.getServer(), user);
var fullMessage = textTemplateFormatter.formatMessage(velocityContext, "passwordReset", "full");
- mailManager.sendMessage(user, fullMessage.getSubject(), fullMessage.getBody());
+ mailManager.sendMessage(user, true, fullMessage.getSubject(), fullMessage.getBody());
}
return Response.ok().build();
}
@@ -75,7 +75,7 @@ public class PasswordResource extends BaseResource {
@FormParam("token") String token, @FormParam("password") String password)
throws StorageException, GeneralSecurityException, IOException {
- long userId = tokenManager.verifyToken(token);
+ long userId = tokenManager.verifyToken(token).getUserId();
User user = storage.getObject(User.class, new Request(
new Columns.All(), new Condition.Equals("id", userId)));
if (user != null) {
diff --git a/src/main/java/org/traccar/api/resource/PermissionsResource.java b/src/main/java/org/traccar/api/resource/PermissionsResource.java
index d35cb98bb..9e2d21f2c 100644
--- a/src/main/java/org/traccar/api/resource/PermissionsResource.java
+++ b/src/main/java/org/traccar/api/resource/PermissionsResource.java
@@ -23,15 +23,15 @@ import org.traccar.model.UserRestrictions;
import org.traccar.session.cache.CacheManager;
import org.traccar.storage.StorageException;
-import javax.inject.Inject;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -48,7 +48,7 @@ public class PermissionsResource extends BaseResource {
private void checkPermission(Permission permission) throws StorageException {
if (permissionsService.notAdmin(getUserId())) {
permissionsService.checkPermission(permission.getOwnerClass(), getUserId(), permission.getOwnerId());
- permissionsService.checkPermission(permission.getOwnerClass(), getUserId(), permission.getOwnerId());
+ permissionsService.checkPermission(permission.getPropertyClass(), getUserId(), permission.getPropertyId());
}
}
@@ -64,7 +64,7 @@ public class PermissionsResource extends BaseResource {
@Path("bulk")
@POST
- public Response add(List<LinkedHashMap<String, Long>> entities) throws StorageException, ClassNotFoundException {
+ public Response add(List<LinkedHashMap<String, Long>> entities) throws Exception {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getReadonly);
checkPermissionTypes(entities);
for (LinkedHashMap<String, Long> entity: entities) {
@@ -74,7 +74,8 @@ public class PermissionsResource extends BaseResource {
cacheManager.invalidatePermission(
true,
permission.getOwnerClass(), permission.getOwnerId(),
- permission.getPropertyClass(), permission.getPropertyId());
+ permission.getPropertyClass(), permission.getPropertyId(),
+ true);
LogAction.link(getUserId(),
permission.getOwnerClass(), permission.getOwnerId(),
permission.getPropertyClass(), permission.getPropertyId());
@@ -83,13 +84,13 @@ public class PermissionsResource extends BaseResource {
}
@POST
- public Response add(LinkedHashMap<String, Long> entity) throws StorageException, ClassNotFoundException {
+ public Response add(LinkedHashMap<String, Long> entity) throws Exception {
return add(Collections.singletonList(entity));
}
@DELETE
@Path("bulk")
- public Response remove(List<LinkedHashMap<String, Long>> entities) throws StorageException, ClassNotFoundException {
+ public Response remove(List<LinkedHashMap<String, Long>> entities) throws Exception {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getReadonly);
checkPermissionTypes(entities);
for (LinkedHashMap<String, Long> entity: entities) {
@@ -99,7 +100,8 @@ public class PermissionsResource extends BaseResource {
cacheManager.invalidatePermission(
true,
permission.getOwnerClass(), permission.getOwnerId(),
- permission.getPropertyClass(), permission.getPropertyId());
+ permission.getPropertyClass(), permission.getPropertyId(),
+ false);
LogAction.unlink(getUserId(),
permission.getOwnerClass(), permission.getOwnerId(),
permission.getPropertyClass(), permission.getPropertyId());
@@ -108,7 +110,7 @@ public class PermissionsResource extends BaseResource {
}
@DELETE
- public Response remove(LinkedHashMap<String, Long> entity) throws StorageException, ClassNotFoundException {
+ public Response remove(LinkedHashMap<String, Long> entity) throws Exception {
return remove(Collections.singletonList(entity));
}
diff --git a/src/main/java/org/traccar/api/resource/PositionResource.java b/src/main/java/org/traccar/api/resource/PositionResource.java
index 042dd1e23..0d783a0fe 100644
--- a/src/main/java/org/traccar/api/resource/PositionResource.java
+++ b/src/main/java/org/traccar/api/resource/PositionResource.java
@@ -28,21 +28,23 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.StreamingOutput;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.StreamingOutput;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
+import java.util.LinkedList;
@Path("positions")
@Produces(MediaType.APPLICATION_JSON)
@@ -86,6 +88,21 @@ public class PositionResource extends BaseResource {
}
}
+ @DELETE
+ public Response remove(
+ @QueryParam("deviceId") long deviceId,
+ @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException {
+ permissionsService.checkPermission(Device.class, getUserId(), deviceId);
+ permissionsService.checkRestriction(getUserId(), UserRestrictions::getReadonly);
+
+ var conditions = new LinkedList<Condition>();
+ conditions.add(new Condition.Equals("deviceId", deviceId));
+ conditions.add(new Condition.Between("fixTime", "from", from, "to", to));
+ storage.removeObject(Position.class, new Request(Condition.merge(conditions)));
+
+ return Response.status(Response.Status.NO_CONTENT).build();
+ }
+
@Path("kml")
@GET
@Produces("application/vnd.google-earth.kml+xml")
diff --git a/src/main/java/org/traccar/api/resource/ReportResource.java b/src/main/java/org/traccar/api/resource/ReportResource.java
index 6944de9cb..55a96fa90 100644
--- a/src/main/java/org/traccar/api/resource/ReportResource.java
+++ b/src/main/java/org/traccar/api/resource/ReportResource.java
@@ -16,13 +16,14 @@
*/
package org.traccar.api.resource;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.traccar.api.BaseResource;
+import org.traccar.api.SimpleObjectResource;
import org.traccar.helper.LogAction;
import org.traccar.model.Event;
import org.traccar.model.Position;
+import org.traccar.model.Report;
import org.traccar.model.UserRestrictions;
+import org.traccar.reports.CombinedReportProvider;
+import org.traccar.reports.DevicesReportProvider;
import org.traccar.reports.EventsReportProvider;
import org.traccar.reports.RouteReportProvider;
import org.traccar.reports.StopsReportProvider;
@@ -30,23 +31,24 @@ import org.traccar.reports.SummaryReportProvider;
import org.traccar.reports.TripsReportProvider;
import org.traccar.reports.common.ReportExecutor;
import org.traccar.reports.common.ReportMailer;
+import org.traccar.reports.model.CombinedReportItem;
import org.traccar.reports.model.StopReportItem;
import org.traccar.reports.model.SummaryReportItem;
import org.traccar.reports.model.TripReportItem;
import org.traccar.storage.StorageException;
-import javax.inject.Inject;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.StreamingOutput;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.StreamingOutput;
import java.util.Collection;
import java.util.Date;
import java.util.List;
@@ -54,13 +56,14 @@ import java.util.List;
@Path("reports")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
-public class ReportResource extends BaseResource {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(ReportResource.class);
+public class ReportResource extends SimpleObjectResource<Report> {
private static final String EXCEL = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
@Inject
+ private CombinedReportProvider combinedReportProvider;
+
+ @Inject
private EventsReportProvider eventsReportProvider;
@Inject
@@ -76,8 +79,15 @@ public class ReportResource extends BaseResource {
private TripsReportProvider tripsReportProvider;
@Inject
+ private DevicesReportProvider devicesReportProvider;
+
+ @Inject
private ReportMailer reportMailer;
+ public ReportResource() {
+ super(Report.class);
+ }
+
private Response executeReport(long userId, boolean mail, ReportExecutor executor) {
if (mail) {
reportMailer.sendAsync(userId, executor);
@@ -95,6 +105,18 @@ public class ReportResource extends BaseResource {
}
}
+ @Path("combined")
+ @GET
+ public Collection<CombinedReportItem> getCombined(
+ @QueryParam("deviceId") List<Long> deviceIds,
+ @QueryParam("groupId") List<Long> groupIds,
+ @QueryParam("from") Date from,
+ @QueryParam("to") Date to) throws StorageException {
+ permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
+ LogAction.logReport(getUserId(), "combined", from, to, deviceIds, groupIds);
+ return combinedReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to);
+ }
+
@Path("route")
@GET
public Collection<Position> getRoute(
@@ -301,4 +323,15 @@ public class ReportResource extends BaseResource {
return getStopsExcel(deviceIds, groupIds, from, to, type.equals("mail"));
}
+ @Path("devices/{type:xlsx|mail}")
+ @GET
+ @Produces(EXCEL)
+ public Response geDevicesExcel(
+ @PathParam("type") String type) throws StorageException {
+ permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports);
+ return executeReport(getUserId(), type.equals("mail"), stream -> {
+ devicesReportProvider.getExcel(stream, getUserId());
+ });
+ }
+
}
diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java
index 4b7ee9189..2a72d2773 100644
--- a/src/main/java/org/traccar/api/resource/ServerResource.java
+++ b/src/main/java/org/traccar/api/resource/ServerResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,30 +16,43 @@
package org.traccar.api.resource;
import org.traccar.api.BaseResource;
-import org.traccar.helper.model.UserUtil;
-import org.traccar.mail.MailManager;
+import org.traccar.model.ObjectOperation;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+import org.traccar.database.OpenIdProvider;
import org.traccar.geocoder.Geocoder;
import org.traccar.helper.Log;
import org.traccar.helper.LogAction;
+import org.traccar.helper.model.UserUtil;
+import org.traccar.mail.MailManager;
import org.traccar.model.Server;
import org.traccar.model.User;
import org.traccar.session.cache.CacheManager;
+import org.traccar.sms.SmsManager;
import org.traccar.storage.StorageException;
import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.annotation.Nullable;
-import javax.annotation.security.PermitAll;
-import javax.inject.Inject;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.annotation.Nullable;
+import jakarta.annotation.security.PermitAll;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.TimeZone;
@@ -50,6 +63,9 @@ import java.util.TimeZone;
public class ServerResource extends BaseResource {
@Inject
+ private Config config;
+
+ @Inject
private CacheManager cacheManager;
@Inject
@@ -57,6 +73,14 @@ public class ServerResource extends BaseResource {
@Inject
@Nullable
+ private SmsManager smsManager;
+
+ @Inject
+ @Nullable
+ private OpenIdProvider openIdProvider;
+
+ @Inject
+ @Nullable
private Geocoder geocoder;
@PermitAll
@@ -64,7 +88,10 @@ public class ServerResource extends BaseResource {
public Server get() throws StorageException {
Server server = storage.getObject(Server.class, new Request(new Columns.All()));
server.setEmailEnabled(mailManager.getEmailEnabled());
+ server.setTextEnabled(smsManager != null);
server.setGeocoderEnabled(geocoder != null);
+ server.setOpenIdEnabled(openIdProvider != null);
+ server.setOpenIdForce(openIdProvider != null && openIdProvider.getForce());
User user = permissionsService.getUser(getUserId());
if (user != null) {
if (user.getAdministrator()) {
@@ -73,21 +100,18 @@ public class ServerResource extends BaseResource {
} else {
server.setNewServer(UserUtil.isEmpty(storage));
}
- if (user != null && user.getAdministrator()) {
- server.setStorageSpace(Log.getStorageSpace());
- }
return server;
}
@PUT
- public Response update(Server entity) throws StorageException {
+ public Response update(Server server) throws Exception {
permissionsService.checkAdmin(getUserId());
- storage.updateObject(entity, new Request(
+ storage.updateObject(server, new Request(
new Columns.Exclude("id"),
- new Condition.Equals("id", entity.getId())));
- cacheManager.updateOrInvalidate(true, entity);
- LogAction.edit(getUserId(), entity);
- return Response.ok(entity).build();
+ new Condition.Equals("id", server.getId())));
+ cacheManager.invalidateObject(true, Server.class, server.getId(), ObjectOperation.UPDATE);
+ LogAction.edit(getUserId(), server);
+ return Response.ok(server).build();
}
@Path("geocode")
@@ -106,4 +130,35 @@ public class ServerResource extends BaseResource {
return Arrays.asList(TimeZone.getAvailableIDs());
}
+ @Path("file/{path}")
+ @POST
+ @Consumes("*/*")
+ public Response uploadFile(@PathParam("path") String path, File inputFile) throws IOException, StorageException {
+ permissionsService.checkAdmin(getUserId());
+ String root = config.getString(Keys.WEB_OVERRIDE, config.getString(Keys.WEB_PATH));
+
+ var rootPath = Paths.get(root).normalize();
+ var outputPath = rootPath.resolve(path).normalize();
+ if (!outputPath.startsWith(rootPath)) {
+ return Response.status(Response.Status.BAD_REQUEST).build();
+ }
+
+ var directoryPath = outputPath.getParent();
+ if (directoryPath != null) {
+ Files.createDirectories(directoryPath);
+ }
+
+ try (var input = new FileInputStream(inputFile); var output = new FileOutputStream(outputPath.toFile())) {
+ input.transferTo(output);
+ }
+ return Response.ok().build();
+ }
+
+ @Path("cache")
+ @GET
+ public String cache() throws StorageException {
+ permissionsService.checkAdmin(getUserId());
+ return cacheManager.toString();
+ }
+
}
diff --git a/src/main/java/org/traccar/api/resource/SessionResource.java b/src/main/java/org/traccar/api/resource/SessionResource.java
index 7025d5fa7..979b62589 100644
--- a/src/main/java/org/traccar/api/resource/SessionResource.java
+++ b/src/main/java/org/traccar/api/resource/SessionResource.java
@@ -16,39 +16,41 @@
package org.traccar.api.resource;
import org.traccar.api.BaseResource;
+import org.traccar.api.security.CodeRequiredException;
+import org.traccar.api.security.LoginResult;
import org.traccar.api.security.LoginService;
import org.traccar.api.signature.TokenManager;
-import org.traccar.helper.DataConverter;
+import org.traccar.database.OpenIdProvider;
import org.traccar.helper.LogAction;
-import org.traccar.helper.ServletHelper;
+import org.traccar.helper.WebHelper;
import org.traccar.model.User;
import org.traccar.storage.StorageException;
import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.annotation.security.PermitAll;
-import javax.inject.Inject;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.FormParam;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import com.nimbusds.oauth2.sdk.ParseException;
+import jakarta.annotation.Nullable;
+import jakarta.annotation.security.PermitAll;
+import jakarta.inject.Inject;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.FormParam;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import java.io.IOException;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Date;
+import java.net.URI;
@Path("session")
@Produces(MediaType.APPLICATION_JSON)
@@ -56,13 +58,16 @@ import java.util.Date;
public class SessionResource extends BaseResource {
public static final String USER_ID_KEY = "userId";
- public static final String USER_COOKIE_KEY = "user";
- public static final String PASS_COOKIE_KEY = "password";
+ public static final String EXPIRATION_KEY = "expiration";
@Inject
private LoginService loginService;
@Inject
+ @Nullable
+ private OpenIdProvider openIdProvider;
+
+ @Inject
private TokenManager tokenManager;
@Context
@@ -73,48 +78,22 @@ public class SessionResource extends BaseResource {
public User get(@QueryParam("token") String token) throws StorageException, IOException, GeneralSecurityException {
if (token != null) {
- User user = loginService.login(token);
- if (user != null) {
+ LoginResult loginResult = loginService.login(token);
+ if (loginResult != null) {
+ User user = loginResult.getUser();
request.getSession().setAttribute(USER_ID_KEY, user.getId());
- LogAction.login(user.getId(), ServletHelper.retrieveRemoteAddress(request));
+ request.getSession().setAttribute(EXPIRATION_KEY, loginResult.getExpiration());
+ LogAction.login(user.getId(), WebHelper.retrieveRemoteAddress(request));
return user;
}
}
Long userId = (Long) request.getSession().getAttribute(USER_ID_KEY);
- if (userId == null) {
-
- Cookie[] cookies = request.getCookies();
- String email = null, password = null;
- if (cookies != null) {
- for (Cookie cookie : cookies) {
- if (cookie.getName().equals(USER_COOKIE_KEY)) {
- byte[] emailBytes = DataConverter.parseBase64(
- URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII));
- email = new String(emailBytes, StandardCharsets.UTF_8);
- } else if (cookie.getName().equals(PASS_COOKIE_KEY)) {
- byte[] passwordBytes = DataConverter.parseBase64(
- URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII));
- password = new String(passwordBytes, StandardCharsets.UTF_8);
- }
- }
- }
- if (email != null && password != null) {
- User user = loginService.login(email, password);
- if (user != null) {
- request.getSession().setAttribute(USER_ID_KEY, user.getId());
- LogAction.login(user.getId(), ServletHelper.retrieveRemoteAddress(request));
- return user;
- }
- }
-
- } else {
-
+ if (userId != null) {
User user = permissionsService.getUser(userId);
if (user != null) {
return user;
}
-
}
throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build());
@@ -123,32 +102,44 @@ public class SessionResource extends BaseResource {
@Path("{id}")
@GET
public User get(@PathParam("id") long userId) throws StorageException {
- permissionsService.checkAdmin(getUserId());
+ permissionsService.checkUser(getUserId(), userId);
User user = storage.getObject(User.class, new Request(
new Columns.All(), new Condition.Equals("id", userId)));
request.getSession().setAttribute(USER_ID_KEY, user.getId());
- LogAction.login(user.getId(), ServletHelper.retrieveRemoteAddress(request));
+ LogAction.login(user.getId(), WebHelper.retrieveRemoteAddress(request));
return user;
}
@PermitAll
@POST
public User add(
- @FormParam("email") String email, @FormParam("password") String password) throws StorageException {
- User user = loginService.login(email, password);
- if (user != null) {
+ @FormParam("email") String email,
+ @FormParam("password") String password,
+ @FormParam("code") Integer code) throws StorageException {
+ LoginResult loginResult;
+ try {
+ loginResult = loginService.login(email, password, code);
+ } catch (CodeRequiredException e) {
+ Response response = Response
+ .status(Response.Status.UNAUTHORIZED)
+ .header("WWW-Authenticate", "TOTP")
+ .build();
+ throw new WebApplicationException(response);
+ }
+ if (loginResult != null) {
+ User user = loginResult.getUser();
request.getSession().setAttribute(USER_ID_KEY, user.getId());
- LogAction.login(user.getId(), ServletHelper.retrieveRemoteAddress(request));
+ LogAction.login(user.getId(), WebHelper.retrieveRemoteAddress(request));
return user;
} else {
- LogAction.failedLogin(ServletHelper.retrieveRemoteAddress(request));
+ LogAction.failedLogin(WebHelper.retrieveRemoteAddress(request));
throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
@DELETE
public Response remove() {
- LogAction.logout(getUserId(), ServletHelper.retrieveRemoteAddress(request));
+ LogAction.logout(getUserId(), WebHelper.retrieveRemoteAddress(request));
request.getSession().removeAttribute(USER_ID_KEY);
return Response.noContent().build();
}
@@ -157,7 +148,28 @@ public class SessionResource extends BaseResource {
@POST
public String requestToken(
@FormParam("expiration") Date expiration) throws StorageException, GeneralSecurityException, IOException {
+ Date currentExpiration = (Date) request.getSession().getAttribute(EXPIRATION_KEY);
+ if (currentExpiration != null && currentExpiration.before(expiration)) {
+ expiration = currentExpiration;
+ }
return tokenManager.generateToken(getUserId(), expiration);
}
+ @PermitAll
+ @Path("openid/auth")
+ @GET
+ public Response openIdAuth() {
+ return Response.seeOther(openIdProvider.createAuthUri()).build();
+ }
+
+ @PermitAll
+ @Path("openid/callback")
+ @GET
+ public Response requestToken() throws IOException, StorageException, ParseException, GeneralSecurityException {
+ StringBuilder requestUrl = new StringBuilder(request.getRequestURL().toString());
+ String queryString = request.getQueryString();
+ String requestUri = requestUrl.append('?').append(queryString).toString();
+
+ return Response.seeOther(openIdProvider.handleCallback(URI.create(requestUri), request)).build();
+ }
}
diff --git a/src/main/java/org/traccar/api/resource/StatisticsResource.java b/src/main/java/org/traccar/api/resource/StatisticsResource.java
index 1f2296f28..0c728c77d 100644
--- a/src/main/java/org/traccar/api/resource/StatisticsResource.java
+++ b/src/main/java/org/traccar/api/resource/StatisticsResource.java
@@ -23,12 +23,12 @@ import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Order;
import org.traccar.storage.query.Request;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.MediaType;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
import java.util.Collection;
import java.util.Date;
diff --git a/src/main/java/org/traccar/api/resource/UserResource.java b/src/main/java/org/traccar/api/resource/UserResource.java
index e41ebbe61..47ea9b07c 100644
--- a/src/main/java/org/traccar/api/resource/UserResource.java
+++ b/src/main/java/org/traccar/api/resource/UserResource.java
@@ -15,6 +15,11 @@
*/
package org.traccar.api.resource;
+import com.warrenstrange.googleauth.GoogleAuthenticator;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.Context;
import org.traccar.api.BaseObjectResource;
import org.traccar.config.Config;
import org.traccar.config.Keys;
@@ -28,18 +33,17 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.annotation.security.PermitAll;
-import javax.inject.Inject;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.annotation.security.PermitAll;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import java.util.Collection;
-import java.util.Date;
@Path("users")
@Produces(MediaType.APPLICATION_JSON)
@@ -49,6 +53,9 @@ public class UserResource extends BaseObjectResource<User> {
@Inject
private Config config;
+ @Context
+ private HttpServletRequest request;
+
public UserResource() {
super(User.class);
}
@@ -91,11 +98,11 @@ public class UserResource extends BaseObjectResource<User> {
if (!permissionsService.getServer().getRegistration()) {
throw new SecurityException("Registration disabled");
}
- entity.setDeviceLimit(config.getInteger(Keys.USERS_DEFAULT_DEVICE_LIMIT));
- int expirationDays = config.getInteger(Keys.USERS_DEFAULT_EXPIRATION_DAYS);
- if (expirationDays > 0) {
- entity.setExpirationTime(new Date(System.currentTimeMillis() + expirationDays * 86400000L));
+ if (permissionsService.getServer().getBoolean(Keys.WEB_TOTP_FORCE.getKey())
+ && entity.getTotpKey() == null) {
+ throw new SecurityException("One-time password key is required");
}
+ UserUtil.setUserDefaults(entity, config);
}
}
@@ -117,4 +124,24 @@ public class UserResource extends BaseObjectResource<User> {
return Response.ok(entity).build();
}
+ @Path("{id}")
+ @DELETE
+ public Response remove(@PathParam("id") long id) throws Exception {
+ Response response = super.remove(id);
+ if (getUserId() == id) {
+ request.getSession().removeAttribute(SessionResource.USER_ID_KEY);
+ }
+ return response;
+ }
+
+ @Path("totp")
+ @PermitAll
+ @POST
+ public String generateTotpKey() throws StorageException {
+ if (!permissionsService.getServer().getBoolean(Keys.WEB_TOTP_ENABLE.getKey())) {
+ throw new SecurityException("One-time password is disabled");
+ }
+ return new GoogleAuthenticator().createCredentials().getKey();
+ }
+
}
diff --git a/src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java b/src/main/java/org/traccar/api/security/CodeRequiredException.java
index 5b610013a..d522c6540 100644
--- a/src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java
+++ b/src/main/java/org/traccar/api/security/CodeRequiredException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,20 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.protocol;
+package org.traccar.api.security;
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelHandlerContext;
-import org.traccar.BaseFrameDecoder;
-
-public class NdtpV6FrameDecoder extends BaseFrameDecoder {
-
- @Override
- protected Object decode(
- ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception {
-
- return buf;
+public class CodeRequiredException extends SecurityException {
+ public CodeRequiredException() {
+ super("Code not provided");
}
-
}
diff --git a/src/main/java/org/traccar/api/security/LoginResult.java b/src/main/java/org/traccar/api/security/LoginResult.java
new file mode 100644
index 000000000..1fccc36d1
--- /dev/null
+++ b/src/main/java/org/traccar/api/security/LoginResult.java
@@ -0,0 +1,29 @@
+package org.traccar.api.security;
+
+import org.traccar.model.User;
+
+import java.util.Date;
+
+public class LoginResult {
+
+ private final User user;
+ private final Date expiration;
+
+ public LoginResult(User user) {
+ this(user, null);
+ }
+
+ public LoginResult(User user, Date expiration) {
+ this.user = user;
+ this.expiration = expiration;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ public Date getExpiration() {
+ return expiration;
+ }
+
+}
diff --git a/src/main/java/org/traccar/api/security/LoginService.java b/src/main/java/org/traccar/api/security/LoginService.java
index 88bafcfb5..930c4fa46 100644
--- a/src/main/java/org/traccar/api/security/LoginService.java
+++ b/src/main/java/org/traccar/api/security/LoginService.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,10 +15,12 @@
*/
package org.traccar.api.security;
+import com.warrenstrange.googleauth.GoogleAuthenticator;
import org.traccar.api.signature.TokenManager;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.database.LdapProvider;
+import org.traccar.helper.model.UserUtil;
import org.traccar.model.User;
import org.traccar.storage.Storage;
import org.traccar.storage.StorageException;
@@ -26,46 +28,54 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.annotation.Nullable;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.IOException;
import java.security.GeneralSecurityException;
@Singleton
public class LoginService {
+ private final Config config;
private final Storage storage;
private final TokenManager tokenManager;
private final LdapProvider ldapProvider;
private final String serviceAccountToken;
private final boolean forceLdap;
+ private final boolean forceOpenId;
@Inject
public LoginService(
Config config, Storage storage, TokenManager tokenManager, @Nullable LdapProvider ldapProvider) {
this.storage = storage;
+ this.config = config;
this.tokenManager = tokenManager;
this.ldapProvider = ldapProvider;
serviceAccountToken = config.getString(Keys.WEB_SERVICE_ACCOUNT_TOKEN);
forceLdap = config.getBoolean(Keys.LDAP_FORCE);
+ forceOpenId = config.getBoolean(Keys.OPENID_FORCE);
}
- public User login(String token) throws StorageException, GeneralSecurityException, IOException {
+ public LoginResult login(String token) throws StorageException, GeneralSecurityException, IOException {
if (serviceAccountToken != null && serviceAccountToken.equals(token)) {
- return new ServiceAccountUser();
+ return new LoginResult(new ServiceAccountUser());
}
- long userId = tokenManager.verifyToken(token);
+ TokenManager.TokenData tokenData = tokenManager.verifyToken(token);
User user = storage.getObject(User.class, new Request(
- new Columns.All(), new Condition.Equals("id", userId)));
+ new Columns.All(), new Condition.Equals("id", tokenData.getUserId())));
if (user != null) {
checkUserEnabled(user);
}
- return user;
+ return new LoginResult(user, tokenData.getExpiration());
}
- public User login(String email, String password) throws StorageException {
+ public LoginResult login(String email, String password, Integer code) throws StorageException {
+ if (forceOpenId) {
+ return null;
+ }
+
email = email.trim();
User user = storage.getObject(User.class, new Request(
new Columns.All(),
@@ -75,20 +85,39 @@ public class LoginService {
if (user != null) {
if (ldapProvider != null && user.getLogin() != null && ldapProvider.login(user.getLogin(), password)
|| !forceLdap && user.isPasswordValid(password)) {
+ checkUserCode(user, code);
checkUserEnabled(user);
- return user;
+ return new LoginResult(user);
}
} else {
if (ldapProvider != null && ldapProvider.login(email, password)) {
user = ldapProvider.getUser(email);
user.setId(storage.addObject(user, new Request(new Columns.Exclude("id"))));
checkUserEnabled(user);
- return user;
+ return new LoginResult(user);
}
}
return null;
}
+ public LoginResult login(String email, String name, boolean administrator) throws StorageException {
+ User user = storage.getObject(User.class, new Request(
+ new Columns.All(),
+ new Condition.Equals("email", email)));
+
+ if (user == null) {
+ user = new User();
+ UserUtil.setUserDefaults(user, config);
+ user.setName(name);
+ user.setEmail(email);
+ user.setFixedEmail(true);
+ user.setAdministrator(administrator);
+ user.setId(storage.addObject(user, new Request(new Columns.Exclude("id"))));
+ }
+ checkUserEnabled(user);
+ return new LoginResult(user);
+ }
+
private void checkUserEnabled(User user) throws SecurityException {
if (user == null) {
throw new SecurityException("Unknown account");
@@ -96,4 +125,17 @@ public class LoginService {
user.checkDisabled();
}
+ private void checkUserCode(User user, Integer code) throws SecurityException {
+ String key = user.getTotpKey();
+ if (key != null && !key.isEmpty()) {
+ if (code == null) {
+ throw new CodeRequiredException();
+ }
+ GoogleAuthenticator authenticator = new GoogleAuthenticator();
+ if (!authenticator.authorize(key, code)) {
+ throw new SecurityException("User authorization failed");
+ }
+ }
+ }
+
}
diff --git a/src/main/java/org/traccar/api/security/PermissionsService.java b/src/main/java/org/traccar/api/security/PermissionsService.java
index 4421572d7..d60bbafb8 100644
--- a/src/main/java/org/traccar/api/security/PermissionsService.java
+++ b/src/main/java/org/traccar/api/security/PermissionsService.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,8 @@ import org.traccar.model.Device;
import org.traccar.model.Group;
import org.traccar.model.GroupedModel;
import org.traccar.model.ManagedUser;
-import org.traccar.model.ScheduledModel;
+import org.traccar.model.Notification;
+import org.traccar.model.Schedulable;
import org.traccar.model.Server;
import org.traccar.model.User;
import org.traccar.model.UserRestrictions;
@@ -33,7 +34,7 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.util.Objects;
@RequestScoped
@@ -129,17 +130,17 @@ public class PermissionsService {
GroupedModel before = null;
if (!addition) {
before = storage.getObject(after.getClass(), new Request(
- new Columns.Include("groupId"), new Condition.Equals("id", object.getId())));
+ new Columns.Include("groupId"), new Condition.Equals("id", after.getId())));
}
if (before == null || before.getGroupId() != after.getGroupId()) {
checkPermission(Group.class, userId, after.getGroupId());
}
}
}
- if (object instanceof ScheduledModel) {
- ScheduledModel after = ((ScheduledModel) object);
+ if (object instanceof Schedulable) {
+ Schedulable after = ((Schedulable) object);
if (after.getCalendarId() > 0) {
- ScheduledModel before = null;
+ Schedulable before = null;
if (!addition) {
before = storage.getObject(after.getClass(), new Request(
new Columns.Include("calendarId"), new Condition.Equals("id", object.getId())));
@@ -149,6 +150,19 @@ public class PermissionsService {
}
}
}
+ if (object instanceof Notification) {
+ Notification after = ((Notification) object);
+ if (after.getCommandId() > 0) {
+ Notification before = null;
+ if (!addition) {
+ before = storage.getObject(after.getClass(), new Request(
+ new Columns.Include("commandId"), new Condition.Equals("id", object.getId())));
+ }
+ if (before == null || before.getCommandId() != after.getCommandId()) {
+ checkPermission(Command.class, userId, after.getCommandId());
+ }
+ }
+ }
}
}
@@ -167,7 +181,7 @@ public class PermissionsService {
|| before.getUserLimit() != after.getUserLimit()) {
checkAdmin(userId);
}
- User user = getUser(userId);
+ User user = userId > 0 ? getUser(userId) : null;
if (user != null && user.getExpirationTime() != null
&& !Objects.equals(before.getExpirationTime(), after.getExpirationTime())
&& (after.getExpirationTime() == null
diff --git a/src/main/java/org/traccar/api/security/SecurityRequestFilter.java b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
index 94b6bbf05..12a5dbecf 100644
--- a/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
+++ b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
*/
package org.traccar.api.security;
+import com.google.inject.Injector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.api.resource.SessionResource;
@@ -23,32 +24,26 @@ import org.traccar.helper.DataConverter;
import org.traccar.model.User;
import org.traccar.storage.StorageException;
-import javax.annotation.security.PermitAll;
-import javax.inject.Inject;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.container.ContainerRequestContext;
-import javax.ws.rs.container.ContainerRequestFilter;
-import javax.ws.rs.container.ResourceInfo;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.SecurityContext;
+import jakarta.annotation.security.PermitAll;
+import jakarta.inject.Inject;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.container.ResourceInfo;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.SecurityContext;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
+import java.util.Date;
public class SecurityRequestFilter implements ContainerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityRequestFilter.class);
- public static final String AUTHORIZATION_HEADER = "Authorization";
- public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
- public static final String BASIC_REALM = "Basic realm=\"api\"";
- public static final String BEARER_PREFIX = "Bearer ";
- public static final String X_REQUESTED_WITH = "X-Requested-With";
- public static final String XML_HTTP_REQUEST = "XMLHttpRequest";
-
public static String[] decodeBasicAuth(String auth) {
auth = auth.replaceFirst("[B|b]asic ", "");
byte[] decodedBytes = DataConverter.parseBase64(auth);
@@ -70,6 +65,9 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
@Inject
private StatisticsManager statisticsManager;
+ @Inject
+ private Injector injector;
+
@Override
public void filter(ContainerRequestContext requestContext) {
@@ -81,20 +79,22 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
try {
- String authHeader = requestContext.getHeaderString(AUTHORIZATION_HEADER);
+ String authHeader = requestContext.getHeaderString("Authorization");
if (authHeader != null) {
try {
- User user;
- if (authHeader.startsWith(BEARER_PREFIX)) {
- user = loginService.login(authHeader.substring(BEARER_PREFIX.length()));
+ LoginResult loginResult;
+ if (authHeader.startsWith("Bearer ")) {
+ loginResult = loginService.login(authHeader.substring(7));
} else {
String[] auth = decodeBasicAuth(authHeader);
- user = loginService.login(auth[0], auth[1]);
+ loginResult = loginService.login(auth[0], auth[1], null);
}
- if (user != null) {
+ if (loginResult != null) {
+ User user = loginResult.getUser();
statisticsManager.registerRequest(user.getId());
- securityContext = new UserSecurityContext(new UserPrincipal(user.getId()));
+ securityContext = new UserSecurityContext(
+ new UserPrincipal(user.getId(), loginResult.getExpiration()));
}
} catch (StorageException | GeneralSecurityException | IOException e) {
throw new WebApplicationException(e);
@@ -103,14 +103,19 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
} else if (request.getSession() != null) {
Long userId = (Long) request.getSession().getAttribute(SessionResource.USER_ID_KEY);
+ Date expiration = (Date) request.getSession().getAttribute(SessionResource.EXPIRATION_KEY);
if (userId != null) {
- statisticsManager.registerRequest(userId);
- securityContext = new UserSecurityContext(new UserPrincipal(userId));
+ User user = injector.getInstance(PermissionsService.class).getUser(userId);
+ if (user != null) {
+ user.checkDisabled();
+ statisticsManager.registerRequest(userId);
+ securityContext = new UserSecurityContext(new UserPrincipal(userId, expiration));
+ }
}
}
- } catch (SecurityException e) {
+ } catch (SecurityException | StorageException e) {
LOGGER.warn("Authentication error", e);
}
@@ -120,8 +125,9 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
Method method = resourceInfo.getResourceMethod();
if (!method.isAnnotationPresent(PermitAll.class)) {
Response.ResponseBuilder responseBuilder = Response.status(Response.Status.UNAUTHORIZED);
- if (!XML_HTTP_REQUEST.equals(request.getHeader(X_REQUESTED_WITH))) {
- responseBuilder.header(WWW_AUTHENTICATE, BASIC_REALM);
+ String accept = request.getHeader("Accept");
+ if (accept != null && accept.contains("text/html")) {
+ responseBuilder.header("WWW-Authenticate", "Basic realm=\"api\"");
}
throw new WebApplicationException(responseBuilder.build());
}
diff --git a/src/main/java/org/traccar/api/security/UserPrincipal.java b/src/main/java/org/traccar/api/security/UserPrincipal.java
index 18b84a0e1..83bd06fe9 100644
--- a/src/main/java/org/traccar/api/security/UserPrincipal.java
+++ b/src/main/java/org/traccar/api/security/UserPrincipal.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,19 +16,26 @@
package org.traccar.api.security;
import java.security.Principal;
+import java.util.Date;
public class UserPrincipal implements Principal {
private final long userId;
+ private final Date expiration;
- public UserPrincipal(long userId) {
+ public UserPrincipal(long userId, Date expiration) {
this.userId = userId;
+ this.expiration = expiration;
}
public Long getUserId() {
return userId;
}
+ public Date getExpiration() {
+ return expiration;
+ }
+
@Override
public String getName() {
return null;
diff --git a/src/main/java/org/traccar/api/security/UserSecurityContext.java b/src/main/java/org/traccar/api/security/UserSecurityContext.java
index 97df6b6c7..f7adeac64 100644
--- a/src/main/java/org/traccar/api/security/UserSecurityContext.java
+++ b/src/main/java/org/traccar/api/security/UserSecurityContext.java
@@ -15,7 +15,7 @@
*/
package org.traccar.api.security;
-import javax.ws.rs.core.SecurityContext;
+import jakarta.ws.rs.core.SecurityContext;
import java.security.Principal;
public class UserSecurityContext implements SecurityContext {
diff --git a/src/main/java/org/traccar/api/signature/CryptoManager.java b/src/main/java/org/traccar/api/signature/CryptoManager.java
index 249d5bd97..71f56e0fb 100644
--- a/src/main/java/org/traccar/api/signature/CryptoManager.java
+++ b/src/main/java/org/traccar/api/signature/CryptoManager.java
@@ -20,8 +20,8 @@ import org.traccar.storage.StorageException;
import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
diff --git a/src/main/java/org/traccar/api/signature/TokenManager.java b/src/main/java/org/traccar/api/signature/TokenManager.java
index 6a0d90b40..824433b08 100644
--- a/src/main/java/org/traccar/api/signature/TokenManager.java
+++ b/src/main/java/org/traccar/api/signature/TokenManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Base64;
import org.traccar.storage.StorageException;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Date;
@@ -35,11 +35,19 @@ public class TokenManager {
private final ObjectMapper objectMapper;
private final CryptoManager cryptoManager;
- public static class Data {
+ public static class TokenData {
@JsonProperty("u")
private long userId;
@JsonProperty("e")
private Date expiration;
+
+ public long getUserId() {
+ return userId;
+ }
+
+ public Date getExpiration() {
+ return expiration;
+ }
}
@Inject
@@ -54,7 +62,7 @@ public class TokenManager {
public String generateToken(
long userId, Date expiration) throws IOException, GeneralSecurityException, StorageException {
- Data data = new Data();
+ TokenData data = new TokenData();
data.userId = userId;
if (expiration != null) {
data.expiration = expiration;
@@ -65,13 +73,13 @@ public class TokenManager {
return Base64.encodeBase64URLSafeString(cryptoManager.sign(encoded));
}
- public long verifyToken(String token) throws IOException, GeneralSecurityException, StorageException {
+ public TokenData verifyToken(String token) throws IOException, GeneralSecurityException, StorageException {
byte[] encoded = cryptoManager.verify(Base64.decodeBase64(token));
- Data data = objectMapper.readValue(encoded, Data.class);
+ TokenData data = objectMapper.readValue(encoded, TokenData.class);
if (data.expiration.before(new Date())) {
throw new SecurityException("Token has expired");
}
- return data.userId;
+ return data;
}
}
diff --git a/src/main/java/org/traccar/broadcast/BaseBroadcastService.java b/src/main/java/org/traccar/broadcast/BaseBroadcastService.java
new file mode 100644
index 000000000..01b212c60
--- /dev/null
+++ b/src/main/java/org/traccar/broadcast/BaseBroadcastService.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.broadcast;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.traccar.model.BaseModel;
+import org.traccar.model.Device;
+import org.traccar.model.Event;
+import org.traccar.model.ObjectOperation;
+import org.traccar.model.Permission;
+import org.traccar.model.Position;
+
+public abstract class BaseBroadcastService implements BroadcastService {
+
+ private final Set<BroadcastInterface> listeners = new HashSet<>();
+
+ @Override
+ public boolean singleInstance() {
+ return true;
+ }
+
+ @Override
+ public void registerListener(BroadcastInterface listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void updateDevice(boolean local, Device device) {
+ BroadcastMessage message = new BroadcastMessage();
+ message.setDevice(device);
+ sendMessage(message);
+ }
+
+ @Override
+ public void updatePosition(boolean local, Position position) {
+ BroadcastMessage message = new BroadcastMessage();
+ message.setPosition(position);
+ sendMessage(message);
+ }
+
+ @Override
+ public void updateEvent(boolean local, long userId, Event event) {
+ BroadcastMessage message = new BroadcastMessage();
+ message.setUserId(userId);
+ message.setEvent(event);
+ sendMessage(message);
+ }
+
+ @Override
+ public void updateCommand(boolean local, long deviceId) {
+ BroadcastMessage message = new BroadcastMessage();
+ message.setCommandDeviceId(deviceId);
+ sendMessage(message);
+ }
+
+ @Override
+ public <T extends BaseModel> void invalidateObject(
+ boolean local, Class<T> clazz, long id, ObjectOperation operation) {
+ BroadcastMessage message = new BroadcastMessage();
+ var invalidateObject = new BroadcastMessage.InvalidateObject();
+ invalidateObject.setClazz(Permission.getKey(clazz));
+ invalidateObject.setId(id);
+ invalidateObject.setOperation(operation);
+ message.setInvalidateObject(invalidateObject);
+ sendMessage(message);
+ }
+
+ @Override
+ public synchronized <T1 extends BaseModel, T2 extends BaseModel> void invalidatePermission(
+ boolean local, Class<T1> clazz1, long id1, Class<T2> clazz2, long id2, boolean link) {
+ BroadcastMessage message = new BroadcastMessage();
+ var invalidatePermission = new BroadcastMessage.InvalidatePermission();
+ invalidatePermission.setClazz1(Permission.getKey(clazz1));
+ invalidatePermission.setId1(id1);
+ invalidatePermission.setClazz2(Permission.getKey(clazz2));
+ invalidatePermission.setId2(id2);
+ invalidatePermission.setLink(link);
+ message.setInvalidatePermission(invalidatePermission);
+ sendMessage(message);
+ }
+
+ protected abstract void sendMessage(BroadcastMessage message);
+
+ protected void handleMessage(BroadcastMessage message) throws Exception {
+ if (message.getDevice() != null) {
+ listeners.forEach(listener -> listener.updateDevice(false, message.getDevice()));
+ } else if (message.getPosition() != null) {
+ listeners.forEach(listener -> listener.updatePosition(false, message.getPosition()));
+ } else if (message.getUserId() != null && message.getEvent() != null) {
+ listeners.forEach(listener -> listener.updateEvent(false, message.getUserId(), message.getEvent()));
+ } else if (message.getCommandDeviceId() != null) {
+ listeners.forEach(listener -> listener.updateCommand(false, message.getCommandDeviceId()));
+ } else if (message.getInvalidateObject() != null) {
+ var invalidateObject = message.getInvalidateObject();
+ for (BroadcastInterface listener : listeners) {
+ listener.invalidateObject(
+ false,
+ Permission.getKeyClass(invalidateObject.getClazz()), invalidateObject.getId(),
+ invalidateObject.getOperation());
+ }
+ } else if (message.getInvalidatePermission() != null) {
+ var invalidatePermission = message.getInvalidatePermission();
+ for (BroadcastInterface listener : listeners) {
+ listener.invalidatePermission(
+ false,
+ Permission.getKeyClass(invalidatePermission.getClazz1()), invalidatePermission.getId1(),
+ Permission.getKeyClass(invalidatePermission.getClazz2()), invalidatePermission.getId2(),
+ invalidatePermission.getLink());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/broadcast/BroadcastInterface.java b/src/main/java/org/traccar/broadcast/BroadcastInterface.java
index 673ebd8b8..d0a491cd2 100644
--- a/src/main/java/org/traccar/broadcast/BroadcastInterface.java
+++ b/src/main/java/org/traccar/broadcast/BroadcastInterface.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.traccar.broadcast;
import org.traccar.model.BaseModel;
import org.traccar.model.Device;
import org.traccar.model.Event;
+import org.traccar.model.ObjectOperation;
import org.traccar.model.Position;
public interface BroadcastInterface {
@@ -34,12 +35,12 @@ public interface BroadcastInterface {
default void updateCommand(boolean local, long deviceId) {
}
- default void invalidateObject(boolean local, Class<? extends BaseModel> clazz, long id) {
+ default <T extends BaseModel> void invalidateObject(
+ boolean local, Class<T> clazz, long id, ObjectOperation operation) throws Exception {
}
- default void invalidatePermission(
- boolean local,
- Class<? extends BaseModel> clazz1, long id1,
- Class<? extends BaseModel> clazz2, long id2) {
+ default <T1 extends BaseModel, T2 extends BaseModel> void invalidatePermission(
+ boolean local, Class<T1> clazz1, long id1, Class<T2> clazz2, long id2, boolean link) throws Exception {
}
+
}
diff --git a/src/main/java/org/traccar/broadcast/BroadcastMessage.java b/src/main/java/org/traccar/broadcast/BroadcastMessage.java
index 985848d04..0d15d7495 100644
--- a/src/main/java/org/traccar/broadcast/BroadcastMessage.java
+++ b/src/main/java/org/traccar/broadcast/BroadcastMessage.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,10 +17,9 @@ package org.traccar.broadcast;
import org.traccar.model.Device;
import org.traccar.model.Event;
+import org.traccar.model.ObjectOperation;
import org.traccar.model.Position;
-import java.util.Map;
-
public class BroadcastMessage {
private Device device;
@@ -73,13 +72,112 @@ public class BroadcastMessage {
this.commandDeviceId = commandDeviceId;
}
- private Map<String, Long> changes;
+ public static class InvalidateObject {
+
+ private String clazz;
+
+ public String getClazz() {
+ return clazz;
+ }
+
+ public void setClazz(String clazz) {
+ this.clazz = clazz;
+ }
+
+ private long id;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ private ObjectOperation operation;
+
+ public ObjectOperation getOperation() {
+ return operation;
+ }
+
+ public void setOperation(ObjectOperation operation) {
+ this.operation = operation;
+ }
- public Map<String, Long> getChanges() {
- return changes;
}
- public void setChanges(Map<String, Long> changes) {
- this.changes = changes;
+ private InvalidateObject invalidateObject;
+
+ public InvalidateObject getInvalidateObject() {
+ return invalidateObject;
+ }
+
+ public void setInvalidateObject(InvalidateObject invalidateObject) {
+ this.invalidateObject = invalidateObject;
}
+
+ public static class InvalidatePermission {
+
+ private String clazz1;
+
+ public String getClazz1() {
+ return clazz1;
+ }
+
+ public void setClazz1(String clazz1) {
+ this.clazz1 = clazz1;
+ }
+
+ private long id1;
+
+ public long getId1() {
+ return id1;
+ }
+
+ public void setId1(long id1) {
+ this.id1 = id1;
+ }
+
+ private String clazz2;
+
+ public String getClazz2() {
+ return clazz2;
+ }
+
+ public void setClazz2(String clazz2) {
+ this.clazz2 = clazz2;
+ }
+
+ private long id2;
+
+ public long getId2() {
+ return id2;
+ }
+
+ public void setId2(long id2) {
+ this.id2 = id2;
+ }
+
+ private boolean link;
+
+ public boolean getLink() {
+ return link;
+ }
+
+ public void setLink(boolean link) {
+ this.link = link;
+ }
+
+ }
+
+ private InvalidatePermission invalidatePermission;
+
+ public InvalidatePermission getInvalidatePermission() {
+ return invalidatePermission;
+ }
+
+ public void setInvalidatePermission(InvalidatePermission invalidatePermission) {
+ this.invalidatePermission = invalidatePermission;
+ }
+
}
diff --git a/src/main/java/org/traccar/broadcast/MulticastBroadcastService.java b/src/main/java/org/traccar/broadcast/MulticastBroadcastService.java
index b1b66f1e3..793c6df36 100644
--- a/src/main/java/org/traccar/broadcast/MulticastBroadcastService.java
+++ b/src/main/java/org/traccar/broadcast/MulticastBroadcastService.java
@@ -20,11 +20,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import org.traccar.model.BaseModel;
-import org.traccar.model.Device;
-import org.traccar.model.Event;
-import org.traccar.model.Permission;
-import org.traccar.model.Position;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -34,13 +29,10 @@ import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.nio.charset.StandardCharsets;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-public class MulticastBroadcastService implements BroadcastService {
+public class MulticastBroadcastService extends BaseBroadcastService {
private static final Logger LOGGER = LoggerFactory.getLogger(MulticastBroadcastService.class);
@@ -55,8 +47,6 @@ public class MulticastBroadcastService implements BroadcastService {
private final ExecutorService service = Executors.newSingleThreadExecutor();
private final byte[] receiverBuffer = new byte[4096];
- private final Set<BroadcastInterface> listeners = new HashSet<>();
-
public MulticastBroadcastService(Config config, ObjectMapper objectMapper) throws IOException {
this.objectMapper = objectMapper;
port = config.getInteger(Keys.BROADCAST_PORT);
@@ -76,57 +66,7 @@ public class MulticastBroadcastService implements BroadcastService {
}
@Override
- public void registerListener(BroadcastInterface listener) {
- listeners.add(listener);
- }
-
- @Override
- public void updateDevice(boolean local, Device device) {
- BroadcastMessage message = new BroadcastMessage();
- message.setDevice(device);
- sendMessage(message);
- }
-
- @Override
- public void updatePosition(boolean local, Position position) {
- BroadcastMessage message = new BroadcastMessage();
- message.setPosition(position);
- sendMessage(message);
- }
-
- @Override
- public void updateEvent(boolean local, long userId, Event event) {
- BroadcastMessage message = new BroadcastMessage();
- message.setUserId(userId);
- message.setEvent(event);
- sendMessage(message);
- }
-
- @Override
- public void updateCommand(boolean local, long deviceId) {
- BroadcastMessage message = new BroadcastMessage();
- message.setCommandDeviceId(deviceId);
- sendMessage(message);
- }
-
- @Override
- public void invalidateObject(boolean local, Class<? extends BaseModel> clazz, long id) {
- BroadcastMessage message = new BroadcastMessage();
- message.setChanges(Map.of(Permission.getKey(clazz), id));
- sendMessage(message);
- }
-
- @Override
- public void invalidatePermission(
- boolean local,
- Class<? extends BaseModel> clazz1, long id1,
- Class<? extends BaseModel> clazz2, long id2) {
- BroadcastMessage message = new BroadcastMessage();
- message.setChanges(Map.of(Permission.getKey(clazz1), id1, Permission.getKey(clazz2), id2));
- sendMessage(message);
- }
-
- private void sendMessage(BroadcastMessage message) {
+ protected void sendMessage(BroadcastMessage message) {
try {
byte[] buffer = objectMapper.writeValueAsString(message).getBytes(StandardCharsets.UTF_8);
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, group);
@@ -136,34 +76,6 @@ public class MulticastBroadcastService implements BroadcastService {
}
}
- private void handleMessage(BroadcastMessage message) {
- if (message.getDevice() != null) {
- listeners.forEach(listener -> listener.updateDevice(false, message.getDevice()));
- } else if (message.getPosition() != null) {
- listeners.forEach(listener -> listener.updatePosition(false, message.getPosition()));
- } else if (message.getUserId() != null && message.getEvent() != null) {
- listeners.forEach(listener -> listener.updateEvent(false, message.getUserId(), message.getEvent()));
- } else if (message.getCommandDeviceId() != null) {
- listeners.forEach(listener -> listener.updateCommand(false, message.getCommandDeviceId()));
- } else if (message.getChanges() != null) {
- var iterator = message.getChanges().entrySet().iterator();
- if (iterator.hasNext()) {
- var first = iterator.next();
- if (iterator.hasNext()) {
- var second = iterator.next();
- listeners.forEach(listener -> listener.invalidatePermission(
- false,
- Permission.getKeyClass(first.getKey()), first.getValue(),
- Permission.getKeyClass(second.getKey()), second.getValue()));
- } else {
- listeners.forEach(listener -> listener.invalidateObject(
- false,
- Permission.getKeyClass(first.getKey()), first.getValue()));
- }
- }
- }
- }
-
@Override
public void start() throws IOException {
service.submit(receiver);
@@ -191,7 +103,7 @@ public class MulticastBroadcastService implements BroadcastService {
}
publisherSocket = null;
socket.leaveGroup(group, networkInterface);
- } catch (IOException e) {
+ } catch (Exception e) {
throw new RuntimeException(e);
}
}
diff --git a/src/main/java/org/traccar/broadcast/RedisBroadcastService.java b/src/main/java/org/traccar/broadcast/RedisBroadcastService.java
new file mode 100644
index 000000000..697c45a4a
--- /dev/null
+++ b/src/main/java/org/traccar/broadcast/RedisBroadcastService.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.broadcast;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+
+import java.io.IOException;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPubSub;
+import redis.clients.jedis.exceptions.JedisConnectionException;
+import redis.clients.jedis.exceptions.JedisException;
+
+public class RedisBroadcastService extends BaseBroadcastService {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(RedisBroadcastService.class);
+
+ private final ObjectMapper objectMapper;
+
+ private final ExecutorService service = Executors.newSingleThreadExecutor();
+
+ private final String channel = "traccar";
+
+ private Jedis subscriber;
+ private Jedis publisher;
+
+ private final String id = UUID.randomUUID().toString();
+
+ public RedisBroadcastService(Config config, ObjectMapper objectMapper) throws IOException {
+ this.objectMapper = objectMapper;
+ String url = config.getString(Keys.BROADCAST_ADDRESS);
+
+ try {
+ subscriber = new Jedis(url);
+ publisher = new Jedis(url);
+ subscriber.connect();
+ } catch (JedisConnectionException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public boolean singleInstance() {
+ return false;
+ }
+
+ @Override
+ protected void sendMessage(BroadcastMessage message) {
+ try {
+ String payload = id + ":" + objectMapper.writeValueAsString(message);
+ publisher.publish(channel, payload);
+ } catch (IOException | JedisConnectionException e) {
+ LOGGER.warn("Broadcast failed", e);
+ }
+ }
+
+ @Override
+ public void start() throws IOException {
+ service.submit(receiver);
+ }
+
+ @Override
+ public void stop() {
+ try {
+ if (subscriber != null) {
+ subscriber.close();
+ subscriber = null;
+ }
+ } catch (JedisException e) {
+ LOGGER.warn("Subscriber close failed", e);
+ }
+ try {
+ if (publisher != null) {
+ publisher.close();
+ publisher = null;
+ }
+ } catch (JedisException e) {
+ LOGGER.warn("Publisher close failed", e);
+ }
+ service.shutdown();
+ }
+
+ private final Runnable receiver = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ subscriber.subscribe(new JedisPubSub() {
+ @Override
+ public void onMessage(String messageChannel, String message) {
+ try {
+ String[] parts = message.split(":", 2);
+ if (messageChannel.equals(channel) && parts.length == 2 && !id.equals(parts[0])) {
+ handleMessage(objectMapper.readValue(parts[1], BroadcastMessage.class));
+ }
+ } catch (Exception e) {
+ LOGGER.warn("Broadcast handleMessage failed", e);
+ }
+ }
+ }, channel);
+ } catch (JedisException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+
+}
diff --git a/src/main/java/org/traccar/config/Config.java b/src/main/java/org/traccar/config/Config.java
index c73be6475..47e1f0707 100644
--- a/src/main/java/org/traccar/config/Config.java
+++ b/src/main/java/org/traccar/config/Config.java
@@ -19,8 +19,8 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.inject.name.Named;
import org.traccar.helper.Log;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java
index 093f4298c..02e684875 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 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2024 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -234,11 +234,18 @@ public final class Keys {
List.of(KeyType.CONFIG, KeyType.DEVICE));
/**
+ * Disable commands for the protocol. Not all protocols support this option.
+ */
+ public static final ConfigSuffix<Boolean> PROTOCOL_DISABLE_COMMANDS = new BooleanConfigSuffix(
+ ".disableCommands",
+ List.of(KeyType.CONFIG));
+
+ /**
* Protocol format. Used by protocols that have configurable message format.
*/
public static final ConfigSuffix<String> PROTOCOL_FORMAT = new StringConfigSuffix(
".format",
- List.of(KeyType.DEVICE));
+ List.of(KeyType.CONFIG, KeyType.DEVICE));
/**
* Protocol date format. Used by protocols that have configurable date format.
@@ -293,6 +300,13 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * Send device responses immediately before writing it in the database.
+ */
+ public static final ConfigKey<Boolean> SERVER_INSTANT_ACKNOWLEDGEMENT = new BooleanConfigKey(
+ "server.instantAcknowledgement",
+ List.of(KeyType.CONFIG));
+
+ /**
* Address for uploading aggregated anonymous usage statistics. Uploaded information is the same you can see on the
* statistics screen in the web app. It does not include any sensitive (e.g. locations).
*/
@@ -327,6 +341,22 @@ public final class Keys {
0.0);
/**
+ * Disable device sharing on the server.
+ */
+ public static final ConfigKey<Boolean> 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.
+ */
+ public static final ConfigKey<Double> EVENT_OVERSPEED_THRESHOLD_MULTIPLIER = new DoubleConfigKey(
+ "event.overspeed.thresholdMultiplier",
+ List.of(KeyType.CONFIG),
+ 1.0);
+
+ /**
* Minimal over speed duration to trigger the event. Value in seconds.
*/
public static final ConfigKey<Long> EVENT_OVERSPEED_MINIMAL_DURATION = new LongConfigKey(
@@ -366,14 +396,15 @@ public final class Keys {
*/
public static final ConfigKey<Boolean> EVENT_MOTION_PROCESS_INVALID_POSITIONS = new BooleanConfigKey(
"event.motion.processInvalidPositions",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG, KeyType.DEVICE),
+ false);
/**
* If the speed is above specified value, the object is considered to be in motion. Default value is 0.01 knots.
*/
public static final ConfigKey<Double> EVENT_MOTION_SPEED_THRESHOLD = new DoubleConfigKey(
"event.motion.speedThreshold",
- List.of(KeyType.CONFIG),
+ List.of(KeyType.CONFIG, KeyType.DEVICE),
0.01);
/**
@@ -386,6 +417,13 @@ public final class Keys {
25.0);
/**
+ * Enable in-memory database instead of an SQL database.
+ */
+ public static final ConfigKey<Boolean> DATABASE_MEMORY = new BooleanConfigKey(
+ "database.memory",
+ List.of(KeyType.CONFIG));
+
+ /**
* Path to the database driver JAR file. Traccar includes drivers for MySQL, PostgreSQL and H2 databases. If you use
* one of those, you don't need to specify this parameter.
*/
@@ -459,14 +497,6 @@ public final class Keys {
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<Boolean> DATABASE_IGNORE_UNKNOWN = new BooleanConfigKey(
- "database.ignoreUnknown",
- List.of(KeyType.CONFIG));
-
- /**
* Automatically register unknown devices in the database.
*/
public static final ConfigKey<Boolean> DATABASE_REGISTER_UNKNOWN = new BooleanConfigKey(
@@ -488,6 +518,13 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * Automatically register unknown devices with regex filter.
+ */
+ public static final ConfigKey<String> DATABASE_REGISTER_UNKNOWN_REGEX = new StringConfigKey(
+ "database.registerUnknown.regex",
+ List.of(KeyType.CONFIG), "\\w{3,15}");
+
+ /**
* Store empty messages as positions. For example, heartbeats.
*/
public static final ConfigKey<Boolean> DATABASE_SAVE_EMPTY = new BooleanConfigKey(
@@ -590,6 +627,85 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * Force OpenID Connect authentication. When enabled, the Traccar login page will be skipped
+ * and users are redirected to the OpenID Connect provider.
+ */
+ public static final ConfigKey<Boolean> OPENID_FORCE = new BooleanConfigKey(
+ "openid.force",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * OpenID Connect Client ID.
+ * This is a unique ID assigned to each application you register with your identity provider.
+ * Required to enable SSO.
+ */
+ public static final ConfigKey<String> OPENID_CLIENT_ID = new StringConfigKey(
+ "openid.clientId",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * OpenID Connect Client Secret.
+ * This is a secret assigned to each application you register with your identity provider.
+ * Required to enable SSO.
+ */
+ public static final ConfigKey<String> OPENID_CLIENT_SECRET = new StringConfigKey(
+ "openid.clientSecret",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * OpenID Connect Issuer (Base) URL.
+ * This is used to automatically configure the authorization, token and user info URLs if provided.
+ */
+ public static final ConfigKey<String> OPENID_ISSUER_URL = new StringConfigKey(
+ "openid.issuerUrl",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * OpenID Connect Authorization URL.
+ * This can usually be found in the documentation of your identity provider or by using the well-known
+ * configuration endpoint, e.g. https://auth.example.com//.well-known/openid-configuration
+ * Required to enable SSO if openid.issuerUrl is not set.
+ */
+ public static final ConfigKey<String> OPENID_AUTH_URL = new StringConfigKey(
+ "openid.authUrl",
+ List.of(KeyType.CONFIG));
+ /**
+ * OpenID Connect Token URL.
+ * This can be found in the same ways at openid.authUrl.
+ * Required to enable SSO if openid.issuerUrl is not set.
+ */
+ public static final ConfigKey<String> OPENID_TOKEN_URL = new StringConfigKey(
+ "openid.tokenUrl",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * OpenID Connect User Info URL.
+ * This can be found in the same ways at openid.authUrl.
+ * Required to enable SSO if openid.issuerUrl is not set.
+ */
+ public static final ConfigKey<String> OPENID_USERINFO_URL = new StringConfigKey(
+ "openid.userInfoUrl",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * OpenID Connect group to restrict access to.
+ * If this is not provided, all OpenID users will have access to Traccar.
+ * This option will only work if your OpenID provider supports the groups scope.
+ */
+ public static final ConfigKey<String> OPENID_ALLOW_GROUP = new StringConfigKey(
+ "openid.allowGroup",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * OpenID Connect group to grant admin access.
+ * If this is not provided, no groups will be granted admin access.
+ * This option will only work if your OpenID provider supports the groups scope.
+ */
+ public static final ConfigKey<String> OPENID_ADMIN_GROUP = new StringConfigKey(
+ "openid.adminGroup",
+ List.of(KeyType.CONFIG));
+
+ /**
* If no data is reported by a device for the given amount of time, status changes from online to unknown. Value is
* in seconds. Default timeout is 10 minutes.
*/
@@ -638,6 +754,14 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * Maximum API request duration in seconds.
+ */
+ public static final ConfigKey<Integer> WEB_MAX_REQUEST_SECONDS = new IntegerConfigKey(
+ "web.maxRequestSec",
+ List.of(KeyType.CONFIG),
+ 600);
+
+ /**
* Sanitize all strings returned via API. This is needed to fix XSS issues in the old web interface. New React-based
* interface doesn't require this.
*/
@@ -653,12 +777,19 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
- * WebSocket connection timeout in milliseconds. Default timeout is 10 minutes.
+ * Path to a folder with overrides. It can be used for branding to keep custom logos in a separate place.
+ */
+ public static final ConfigKey<String> WEB_OVERRIDE = new StringConfigKey(
+ "web.override",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * WebSocket connection timeout in milliseconds. Default timeout is 5 minutes.
*/
public static final ConfigKey<Long> WEB_TIMEOUT = new LongConfigKey(
"web.timeout",
List.of(KeyType.CONFIG),
- 60000L);
+ 300000L);
/**
* Authentication sessions timeout in seconds. By default no timeout.
@@ -706,6 +837,27 @@ public final class Keys {
"max-age=3600,public");
/**
+ * Enable TOTP authentication on the server.
+ */
+ public static final ConfigKey<Boolean> WEB_TOTP_ENABLE = new BooleanConfigKey(
+ "totpEnable",
+ List.of(KeyType.SERVER));
+
+ /**
+ * Server attribute that indicates that TOTP authentication is required for new users.
+ */
+ public static final ConfigKey<Boolean> WEB_TOTP_FORCE = new BooleanConfigKey(
+ "totpForce",
+ List.of(KeyType.SERVER));
+
+ /**
+ * Host for raw data forwarding.
+ */
+ public static final ConfigKey<String> SERVER_FORWARD = new StringConfigKey(
+ "server.forward",
+ List.of(KeyType.CONFIG));
+
+ /**
* Position forwarding format. Available options are "url", "json" and "kafka". Default is "url".
*/
public static final ConfigKey<String> FORWARD_TYPE = new StringConfigKey(
@@ -714,7 +866,15 @@ public final class Keys {
"url");
/**
- * Position forwarding Kafka topic.
+ * Position forwarding AMQP exchange.
+ */
+ public static final ConfigKey<String> FORWARD_EXCHANGE = new StringConfigKey(
+ "forward.exchange",
+ List.of(KeyType.CONFIG),
+ "traccar");
+
+ /**
+ * Position forwarding Kafka topic or AQMP Routing Key.
*/
public static final ConfigKey<String> FORWARD_TOPIC = new StringConfigKey(
"forward.topic",
@@ -783,7 +943,15 @@ public final class Keys {
"json");
/**
- * Events forwarding Kafka topic.
+ * Events forwarding AMQP exchange.
+ */
+ public static final ConfigKey<String> EVENT_FORWARD_EXCHANGE = new StringConfigKey(
+ "event.forward.exchange",
+ List.of(KeyType.CONFIG),
+ "traccar");
+
+ /**
+ * Events forwarding Kafka topic or AQMP Routing Key.
*/
public static final ConfigKey<String> EVENT_FORWARD_TOPIC = new StringConfigKey(
"event.forward.topic",
@@ -822,6 +990,13 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * Restrict global SMTP configuration to system messages only (e.g. password reset).
+ */
+ public static final ConfigKey<Boolean> MAIL_SMTP_SYSTEM_ONLY = new BooleanConfigKey(
+ "mail.smtp.systemOnly",
+ List.of(KeyType.CONFIG));
+
+ /**
* Force SMTP settings from the config file and ignore user attributes.
*/
public static final ConfigKey<Boolean> MAIL_SMTP_IGNORE_USER_CONFIG = new BooleanConfigKey(
@@ -992,6 +1167,15 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * If the event time is too old, we should not send notifications. This parameter is the threshold value in
+ * milliseconds. Default value is 15 minutes.
+ */
+ public static final ConfigKey<Long> NOTIFICATOR_TIME_THRESHOLD = new LongConfigKey(
+ "notificator.timeThreshold",
+ List.of(KeyType.CONFIG),
+ 15 * 60 * 1000L);
+
+ /**
* Traccar notification API key.
*/
public static final ConfigKey<String> NOTIFICATOR_TRACCAR_KEY = new StringConfigKey(
@@ -1041,19 +1225,56 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * Enable user expiration email notification.
+ */
+ public static final ConfigKey<Boolean> NOTIFICATION_EXPIRATION_USER = new BooleanConfigKey(
+ "notification.expiration.user",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * User expiration reminder. Value in milliseconds.
+ */
+ public static final ConfigKey<Long> NOTIFICATION_EXPIRATION_USER_REMINDER = new LongConfigKey(
+ "notification.expiration.user.reminder",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * Enable device expiration email notification.
+ */
+ public static final ConfigKey<Boolean> NOTIFICATION_EXPIRATION_DEVICE = new BooleanConfigKey(
+ "notification.expiration.device",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * Device expiration reminder. Value in milliseconds.
+ */
+ public static final ConfigKey<Long> 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.
+ * By default, there is no limit.
*/
public static final ConfigKey<Long> REPORT_PERIOD_LIMIT = new LongConfigKey(
"report.periodLimit",
List.of(KeyType.CONFIG));
/**
+ * Time threshold for fast reports. Fast reports are more efficient, but less accurate and missing some information.
+ * The value is in seconds. One day by default.
+ */
+ public static final ConfigKey<Long> REPORT_FAST_THRESHOLD = new LongConfigKey(
+ "report.fastThreshold",
+ List.of(KeyType.CONFIG),
+ 86400L);
+
+ /**
* Trips less than minimal duration and minimal distance are ignored. 300 seconds and 500 meters are default.
*/
public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_TRIP_DISTANCE = new LongConfigKey(
"report.trip.minimalTripDistance",
- List.of(KeyType.CONFIG),
+ List.of(KeyType.CONFIG, KeyType.DEVICE),
500L);
/**
@@ -1061,7 +1282,7 @@ public final class Keys {
*/
public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_TRIP_DURATION = new LongConfigKey(
"report.trip.minimalTripDuration",
- List.of(KeyType.CONFIG),
+ List.of(KeyType.CONFIG, KeyType.DEVICE),
300L);
/**
@@ -1069,7 +1290,7 @@ public final class Keys {
*/
public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_PARKING_DURATION = new LongConfigKey(
"report.trip.minimalParkingDuration",
- List.of(KeyType.CONFIG),
+ List.of(KeyType.CONFIG, KeyType.DEVICE),
300L);
/**
@@ -1077,7 +1298,7 @@ public final class Keys {
*/
public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_NO_DATA_DURATION = new LongConfigKey(
"report.trip.minimalNoDataDuration",
- List.of(KeyType.CONFIG),
+ List.of(KeyType.CONFIG, KeyType.DEVICE),
3600L);
/**
@@ -1085,7 +1306,8 @@ public final class Keys {
*/
public static final ConfigKey<Boolean> REPORT_TRIP_USE_IGNITION = new BooleanConfigKey(
"report.trip.useIgnition",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG, KeyType.DEVICE),
+ false);
/**
* Ignore odometer value reported by the device and use server-calculated total distance instead. This is useful
@@ -1093,7 +1315,8 @@ public final class Keys {
*/
public static final ConfigKey<Boolean> REPORT_IGNORE_ODOMETER = new BooleanConfigKey(
"report.ignoreOdometer",
- List.of(KeyType.CONFIG));
+ List.of(KeyType.CONFIG),
+ false);
/**
* Boolean flag to enable or disable position filtering.
@@ -1125,6 +1348,14 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * Filter messages that do not have GPS location. If they are not filtered, they will include the last known
+ * location.
+ */
+ public static final ConfigKey<Boolean> FILTER_OUTDATED = new BooleanConfigKey(
+ "filter.outdated",
+ List.of(KeyType.CONFIG));
+
+ /**
* Filter records with fix time in the future. The value is specified in seconds. Records that have fix time more
* than the specified number of seconds later than current server time would be filtered out.
*/
@@ -1187,6 +1418,13 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * Filter position if the daily limit is exceeded for the device.
+ */
+ public static final ConfigKey<Integer> FILTER_DAILY_LIMIT = new IntegerConfigKey(
+ "filter.dailyLimit",
+ List.of(KeyType.CONFIG));
+
+ /**
* If false, the server expects all locations to come sequentially (for each device). Filter checks for duplicates,
* distance, speed, or time period only against the location that was last received by server.
* If true, the server expects locations to come at random order (since tracking device might go offline).
@@ -1294,13 +1532,42 @@ public final class Keys {
List.of(KeyType.CONFIG, KeyType.DEVICE));
/**
- * Enable computed attributes processing.
+ * Include device attributes in the computed attribute context.
*/
public static final ConfigKey<Boolean> PROCESSING_COMPUTED_ATTRIBUTES_DEVICE_ATTRIBUTES = new BooleanConfigKey(
"processing.computedAttributes.deviceAttributes",
List.of(KeyType.CONFIG));
/**
+ * Include last position attributes in the computed attribute context.
+ */
+ public static final ConfigKey<Boolean> PROCESSING_COMPUTED_ATTRIBUTES_LAST_ATTRIBUTES = new BooleanConfigKey(
+ "processing.computedAttributes.lastAttributes",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * Enable local variables declaration.
+ */
+ public static final ConfigKey<Boolean> PROCESSING_COMPUTED_ATTRIBUTES_LOCAL_VARIABLES = new BooleanConfigKey(
+ "processing.computedAttributes.localVariables",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * Enable loops processing.
+ */
+ public static final ConfigKey<Boolean> PROCESSING_COMPUTED_ATTRIBUTES_LOOPS = new BooleanConfigKey(
+ "processing.computedAttributes.loops",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * Enable new instances creation.
+ * When disabled, parsing a script/expression using 'new(...)' will throw a parsing exception;
+ */
+ public static final ConfigKey<Boolean> PROCESSING_COMPUTED_ATTRIBUTES_NEW_INSTANCE_CREATION = new BooleanConfigKey(
+ "processing.computedAttributes.newInstanceCreation",
+ List.of(KeyType.CONFIG));
+
+ /**
* Boolean flag to enable or disable reverse geocoder.
*/
public static final ConfigKey<Boolean> GEOCODER_ENABLE = new BooleanConfigKey(
@@ -1323,13 +1590,6 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
- * App id for use with Here provider.
- */
- public static final ConfigKey<String> GEOCODER_ID = new StringConfigKey(
- "geocoder.id",
- List.of(KeyType.CONFIG));
-
- /**
* Provider API key. Most providers require API keys.
*/
public static final ConfigKey<String> GEOCODER_KEY = new StringConfigKey(
@@ -1433,6 +1693,13 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * Process geolocation only when Wi-Fi information is available. This makes the result more accurate.
+ */
+ public static final ConfigKey<Boolean> GEOLOCATION_REQUIRE_WIFI = new BooleanConfigKey(
+ "geolocation.requireWifi",
+ List.of(KeyType.CONFIG));
+
+ /**
* Default MCC value to use if device doesn't report MCC.
*/
public static final ConfigKey<Integer> GEOLOCATION_MCC = new IntegerConfigKey(
@@ -1468,6 +1735,14 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * Search radius for speed limit. Value is in meters. Default value is 100.
+ */
+ public static final ConfigKey<Integer> SPEED_LIMIT_ACCURACY = new IntegerConfigKey(
+ "speedLimit.accuracy",
+ List.of(KeyType.CONFIG),
+ 100);
+
+ /**
* Override latitude sign / hemisphere. Useful in cases where value is incorrect because of device bug. Value can be
* N for North or S for South.
*/
@@ -1523,7 +1798,7 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
- * Public URL for the web app. Used for notification and report link.
+ * Public URL for the web app. Used for notification, report link and OpenID Connect.
* If not provided, Traccar will attempt to get a URL from the server IP address, but it might be a local address.
*/
public static final ConfigKey<String> WEB_URL = new StringConfigKey(
@@ -1531,6 +1806,27 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
+ * Show logs from unknown devices.
+ */
+ public static final ConfigKey<Boolean> WEB_SHOW_UNKNOWN_DEVICES = new BooleanConfigKey(
+ "web.showUnknownDevices",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * Enable commands for a shared device.
+ */
+ public static final ConfigKey<Boolean> WEB_SHARE_DEVICE_COMMANDS = new BooleanConfigKey(
+ "web.shareDevice.commands",
+ List.of(KeyType.CONFIG));
+
+ /**
+ * Enable reports for a shared device.
+ */
+ public static final ConfigKey<Boolean> 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.
*/
public static final ConfigKey<Boolean> LOGGER_CONSOLE = new BooleanConfigKey(
@@ -1593,6 +1889,14 @@ public final class Keys {
"time,position,speed,course,accuracy,result");
/**
+ * Broadcast method. Available options are "multicast" and "redis". By default (if the value is not
+ * specified or does not matches available options) server disables broadcast.
+ */
+ public static final ConfigKey<String> BROADCAST_TYPE = new StringConfigKey(
+ "broadcast.type",
+ List.of(KeyType.CONFIG));
+
+ /**
* Multicast interface. It can be either an IP address or an interface name.
*/
public static final ConfigKey<String> BROADCAST_INTERFACE = new StringConfigKey(
@@ -1600,7 +1904,7 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
- * Multicast address for broadcasting synchronization events.
+ * Multicast address or Redis URL for broadcasting synchronization events.
*/
public static final ConfigKey<String> BROADCAST_ADDRESS = new StringConfigKey(
"broadcast.address",
diff --git a/src/main/java/org/traccar/database/CommandsManager.java b/src/main/java/org/traccar/database/CommandsManager.java
index df399cd7a..90180b989 100644
--- a/src/main/java/org/traccar/database/CommandsManager.java
+++ b/src/main/java/org/traccar/database/CommandsManager.java
@@ -22,6 +22,7 @@ import org.traccar.broadcast.BroadcastInterface;
import org.traccar.broadcast.BroadcastService;
import org.traccar.model.Command;
import org.traccar.model.Device;
+import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.model.QueuedCommand;
import org.traccar.session.ConnectionManager;
@@ -34,10 +35,12 @@ import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Order;
import org.traccar.storage.query.Request;
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.annotation.Nullable;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
import java.util.stream.Collectors;
@Singleton
@@ -48,20 +51,23 @@ public class CommandsManager implements BroadcastInterface {
private final SmsManager smsManager;
private final ConnectionManager connectionManager;
private final BroadcastService broadcastService;
+ private final NotificationManager notificationManager;
@Inject
public CommandsManager(
Storage storage, ServerManager serverManager, @Nullable SmsManager smsManager,
- ConnectionManager connectionManager, BroadcastService broadcastService) {
+ ConnectionManager connectionManager, BroadcastService broadcastService,
+ NotificationManager notificationManager) {
this.storage = storage;
this.serverManager = serverManager;
this.smsManager = smsManager;
this.connectionManager = connectionManager;
this.broadcastService = broadcastService;
+ this.notificationManager = notificationManager;
broadcastService.registerListener(this);
}
- public boolean sendCommand(Command command) throws Exception {
+ public QueuedCommand sendCommand(Command command) throws Exception {
long deviceId = command.getDeviceId();
if (command.getTextChannel()) {
if (smsManager == null) {
@@ -84,12 +90,13 @@ public class CommandsManager implements BroadcastInterface {
if (deviceSession != null && deviceSession.supportsLiveCommands()) {
deviceSession.sendCommand(command);
} else {
- storage.addObject(QueuedCommand.fromCommand(command), new Request(new Columns.Exclude("id")));
+ QueuedCommand queuedCommand = QueuedCommand.fromCommand(command);
+ queuedCommand.setId(storage.addObject(queuedCommand, new Request(new Columns.Exclude("id"))));
broadcastService.updateCommand(true, deviceId);
- return false;
+ return queuedCommand;
}
}
- return true;
+ return null;
}
public Collection<Command> readQueuedCommands(long deviceId) {
@@ -102,10 +109,16 @@ public class CommandsManager implements BroadcastInterface {
new Columns.All(),
new Condition.Equals("deviceId", deviceId),
new Order("id", false, count)));
+ Map<Event, Position> events = new HashMap<>();
for (var command : commands) {
storage.removeObject(QueuedCommand.class, new Request(
new Condition.Equals("id", command.getId())));
+
+ Event event = new Event(Event.TYPE_QUEUED_COMMAND_SENT, command.getDeviceId());
+ event.set("id", command.getId());
+ events.put(event, null);
}
+ notificationManager.updateEvents(events);
return commands.stream().map(QueuedCommand::toCommand).collect(Collectors.toList());
} catch (StorageException e) {
throw new RuntimeException(e);
diff --git a/src/main/java/org/traccar/database/DeviceLookupService.java b/src/main/java/org/traccar/database/DeviceLookupService.java
index 583b2ae35..90d23531e 100644
--- a/src/main/java/org/traccar/database/DeviceLookupService.java
+++ b/src/main/java/org/traccar/database/DeviceLookupService.java
@@ -29,8 +29,8 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -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/database/MediaManager.java b/src/main/java/org/traccar/database/MediaManager.java
index c1ef810ee..2f2369c96 100644
--- a/src/main/java/org/traccar/database/MediaManager.java
+++ b/src/main/java/org/traccar/database/MediaManager.java
@@ -21,8 +21,8 @@ import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
diff --git a/src/main/java/org/traccar/database/NotificationManager.java b/src/main/java/org/traccar/database/NotificationManager.java
index cb971b082..65437f0a1 100644
--- a/src/main/java/org/traccar/database/NotificationManager.java
+++ b/src/main/java/org/traccar/database/NotificationManager.java
@@ -23,12 +23,12 @@ import org.traccar.config.Keys;
import org.traccar.forward.EventData;
import org.traccar.forward.EventForwarder;
import org.traccar.geocoder.Geocoder;
+import org.traccar.helper.DateUtil;
import org.traccar.model.Calendar;
import org.traccar.model.Device;
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.notification.MessageException;
import org.traccar.notification.NotificatorManager;
@@ -38,9 +38,9 @@ import org.traccar.storage.StorageException;
import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Request;
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.annotation.Nullable;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
@@ -58,6 +58,7 @@ public class NotificationManager {
private final Geocoder geocoder;
private final boolean geocodeOnRequest;
+ private final long timeThreshold;
@Inject
public NotificationManager(
@@ -69,6 +70,7 @@ public class NotificationManager {
this.notificatorManager = notificatorManager;
this.geocoder = geocoder;
geocodeOnRequest = config.getBoolean(Keys.GEOCODER_ON_REQUEST);
+ timeThreshold = config.getLong(Keys.NOTIFICATOR_TIME_THRESHOLD);
}
private void updateEvent(Event event, Position position) {
@@ -78,7 +80,14 @@ public class NotificationManager {
LOGGER.warn("Event save error", error);
}
- var notifications = cacheManager.getDeviceObjects(event.getDeviceId(), Notification.class).stream()
+ forwardEvent(event, position);
+
+ if (System.currentTimeMillis() - event.getEventTime().getTime() > timeThreshold) {
+ LOGGER.info("Skipping notifications for old event");
+ return;
+ }
+
+ var notifications = cacheManager.getDeviceNotifications(event.getDeviceId()).stream()
.filter(notification -> notification.getType().equals(event.getType()))
.filter(notification -> {
if (event.getType().equals(Event.TYPE_ALARM)) {
@@ -98,6 +107,14 @@ public class NotificationManager {
})
.collect(Collectors.toUnmodifiableList());
+ Device device = cacheManager.getObject(Device.class, event.getDeviceId());
+ LOGGER.info(
+ "Event id: {}, time: {}, type: {}, notifications: {}",
+ device.getUniqueId(),
+ DateUtil.formatDate(event.getEventTime(), false),
+ event.getType(),
+ notifications.size());
+
if (!notifications.isEmpty()) {
if (position != null && position.getAddress() == null && geocodeOnRequest && geocoder != null) {
position.setAddress(geocoder.getAddress(position.getLatitude(), position.getLongitude(), null));
@@ -107,16 +124,14 @@ public class NotificationManager {
cacheManager.getNotificationUsers(notification.getId(), event.getDeviceId()).forEach(user -> {
for (String notificator : notification.getNotificatorsTypes()) {
try {
- notificatorManager.getNotificator(notificator).send(user, event, position);
- } catch (MessageException | InterruptedException exception) {
+ notificatorManager.getNotificator(notificator).send(notification, user, event, position);
+ } catch (MessageException exception) {
LOGGER.warn("Notification failed", exception);
}
}
});
});
}
-
- forwardEvent(event, position);
}
private void forwardEvent(Event event, Position position) {
@@ -146,7 +161,7 @@ public class NotificationManager {
try {
cacheManager.addDevice(event.getDeviceId());
updateEvent(event, position);
- } catch (StorageException e) {
+ } catch (Exception e) {
throw new RuntimeException(e);
} finally {
cacheManager.removeDevice(event.getDeviceId());
diff --git a/src/main/java/org/traccar/database/OpenIdProvider.java b/src/main/java/org/traccar/database/OpenIdProvider.java
new file mode 100644
index 000000000..93297f7ab
--- /dev/null
+++ b/src/main/java/org/traccar/database/OpenIdProvider.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2023 Daniel Raper (me@danr.uk)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.database;
+
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+import org.traccar.api.resource.SessionResource;
+import org.traccar.api.security.LoginService;
+import org.traccar.model.User;
+import org.traccar.storage.StorageException;
+import org.traccar.helper.LogAction;
+import org.traccar.helper.WebHelper;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.security.GeneralSecurityException;
+import java.util.List;
+import java.util.Map;
+import java.io.IOException;
+import jakarta.servlet.http.HttpServletRequest;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Inject;
+
+import com.nimbusds.oauth2.sdk.http.HTTPResponse;
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.AuthorizationResponse;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
+import com.nimbusds.openid.connect.sdk.UserInfoResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoRequest;
+import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+
+public class OpenIdProvider {
+ private final Boolean force;
+ private final ClientID clientId;
+ private final ClientAuthentication clientAuth;
+ private final URI callbackUrl;
+ private final URI authUrl;
+ private final URI tokenUrl;
+ private final URI userInfoUrl;
+ private final URI baseUrl;
+ private final String adminGroup;
+ private final String allowGroup;
+
+ private final LoginService loginService;
+
+ @Inject
+ public OpenIdProvider(Config config, LoginService loginService, HttpClient httpClient, ObjectMapper objectMapper)
+ throws InterruptedException, IOException, URISyntaxException {
+
+ this.loginService = loginService;
+
+ force = config.getBoolean(Keys.OPENID_FORCE);
+ clientId = new ClientID(config.getString(Keys.OPENID_CLIENT_ID));
+ clientAuth = new ClientSecretBasic(clientId, new Secret(config.getString(Keys.OPENID_CLIENT_SECRET)));
+
+ baseUrl = new URI(WebHelper.retrieveWebUrl(config));
+ callbackUrl = new URI(WebHelper.retrieveWebUrl(config) + "/api/session/openid/callback");
+
+ if (config.hasKey(Keys.OPENID_ISSUER_URL)) {
+ HttpRequest httpRequest = HttpRequest.newBuilder(
+ URI.create(config.getString(Keys.OPENID_ISSUER_URL) + "/.well-known/openid-configuration"))
+ .header("Accept", "application/json")
+ .build();
+
+ String httpResponse = httpClient.send(httpRequest, BodyHandlers.ofString()).body();
+
+ Map<String, Object> discoveryMap = objectMapper.readValue(httpResponse, new TypeReference<>() {
+ });
+
+ authUrl = new URI((String) discoveryMap.get("authorization_endpoint"));
+ tokenUrl = new URI((String) discoveryMap.get("token_endpoint"));
+ userInfoUrl = new URI((String) discoveryMap.get("userinfo_endpoint"));
+ } else {
+ authUrl = new URI(config.getString(Keys.OPENID_AUTH_URL));
+ tokenUrl = new URI(config.getString(Keys.OPENID_TOKEN_URL));
+ userInfoUrl = new URI(config.getString(Keys.OPENID_USERINFO_URL));
+ }
+
+ adminGroup = config.getString(Keys.OPENID_ADMIN_GROUP);
+ allowGroup = config.getString(Keys.OPENID_ALLOW_GROUP);
+ }
+
+ public URI createAuthUri() {
+ Scope scope = new Scope("openid", "profile", "email");
+
+ if (adminGroup != null) {
+ scope.add("groups");
+ }
+
+ AuthenticationRequest.Builder request = new AuthenticationRequest.Builder(
+ new ResponseType("code"),
+ scope,
+ clientId,
+ callbackUrl);
+
+ return request.endpointURI(authUrl)
+ .state(new State())
+ .build()
+ .toURI();
+ }
+
+ private OIDCTokenResponse getToken(AuthorizationCode code)
+ throws IOException, ParseException, GeneralSecurityException {
+ AuthorizationGrant codeGrant = new AuthorizationCodeGrant(code, callbackUrl);
+ TokenRequest tokenRequest = new TokenRequest(tokenUrl, clientAuth, codeGrant);
+
+ HTTPResponse tokenResponse = tokenRequest.toHTTPRequest().send();
+ TokenResponse token = OIDCTokenResponseParser.parse(tokenResponse);
+ if (!token.indicatesSuccess()) {
+ throw new GeneralSecurityException("Unable to authenticate with the OpenID Connect provider.");
+ }
+
+ return (OIDCTokenResponse) token.toSuccessResponse();
+ }
+
+ private UserInfo getUserInfo(BearerAccessToken token) throws IOException, ParseException, GeneralSecurityException {
+ HTTPResponse httpResponse = new UserInfoRequest(userInfoUrl, token)
+ .toHTTPRequest()
+ .send();
+
+ UserInfoResponse userInfoResponse = UserInfoResponse.parse(httpResponse);
+
+ if (!userInfoResponse.indicatesSuccess()) {
+ throw new GeneralSecurityException(
+ "Failed to access OpenID Connect user info endpoint. Please contact your administrator.");
+ }
+
+ return userInfoResponse.toSuccessResponse().getUserInfo();
+ }
+
+ public URI handleCallback(URI requestUri, HttpServletRequest request)
+ throws StorageException, ParseException, IOException, GeneralSecurityException {
+
+ AuthorizationResponse response = AuthorizationResponse.parse(requestUri);
+
+ if (!response.indicatesSuccess()) {
+ throw new GeneralSecurityException(response.toErrorResponse().getErrorObject().getDescription());
+ }
+
+ AuthorizationCode authCode = response.toSuccessResponse().getAuthorizationCode();
+
+ if (authCode == null) {
+ throw new GeneralSecurityException("Malformed OpenID callback.");
+ }
+
+ OIDCTokenResponse tokens = getToken(authCode);
+
+ BearerAccessToken bearerToken = tokens.getOIDCTokens().getBearerAccessToken();
+
+ UserInfo userInfo = getUserInfo(bearerToken);
+
+ List<String> userGroups = userInfo.getStringListClaim("groups");
+ boolean administrator = adminGroup != null && userGroups.contains(adminGroup);
+
+ if (!(administrator || allowGroup == null || userGroups.contains(allowGroup))) {
+ throw new GeneralSecurityException("Your OpenID Groups do not permit access to Traccar.");
+ }
+
+ User user = loginService.login(
+ userInfo.getEmailAddress(), userInfo.getName(), administrator).getUser();
+
+ request.getSession().setAttribute(SessionResource.USER_ID_KEY, user.getId());
+ LogAction.login(user.getId(), WebHelper.retrieveRemoteAddress(request));
+
+ return baseUrl;
+ }
+
+ public boolean getForce() {
+ return force;
+ }
+}
diff --git a/src/main/java/org/traccar/database/StatisticsManager.java b/src/main/java/org/traccar/database/StatisticsManager.java
index e0995dabc..445e53e7c 100644
--- a/src/main/java/org/traccar/database/StatisticsManager.java
+++ b/src/main/java/org/traccar/database/StatisticsManager.java
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.traccar.api.security.ServiceAccountUser;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.helper.DateUtil;
@@ -28,11 +29,11 @@ import org.traccar.storage.StorageException;
import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.core.Form;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Form;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
@@ -57,6 +58,7 @@ public class StatisticsManager {
private final Set<Long> users = new HashSet<>();
private final Map<Long, String> deviceProtocols = new HashMap<>();
+ private final Map<Long, Integer> deviceMessages = new HashMap<>();
private int requests;
private int messagesReceived;
@@ -98,8 +100,11 @@ public class StatisticsManager {
statistics.setProtocols(protocols);
}
+ statistics.set("modern", config.getString(Keys.WEB_PATH).contains("modern"));
+
users.clear();
deviceProtocols.clear();
+ deviceMessages.clear();
requests = 0;
messagesReceived = 0;
messagesStored = 0;
@@ -138,6 +143,13 @@ public class StatisticsManager {
LOGGER.warn("Failed to serialize protocols", e);
}
}
+ if (!statistics.getAttributes().isEmpty()) {
+ try {
+ form.param("attributes", objectMapper.writeValueAsString(statistics.getAttributes()));
+ } catch (JsonProcessingException e) {
+ LOGGER.warn("Failed to serialize attributes", e);
+ }
+ }
client.target(url).request().async().post(Entity.form(form));
}
@@ -147,7 +159,7 @@ public class StatisticsManager {
public synchronized void registerRequest(long userId) {
checkSplit();
requests += 1;
- if (userId != 0) {
+ if (userId != 0 && userId != ServiceAccountUser.ID) {
users.add(userId);
}
}
@@ -162,9 +174,14 @@ public class StatisticsManager {
messagesStored += 1;
if (deviceId != 0) {
deviceProtocols.put(deviceId, protocol);
+ deviceMessages.merge(deviceId, 1, Integer::sum);
}
}
+ public synchronized int messageStoredCount(long deviceId) {
+ return deviceMessages.getOrDefault(deviceId, 0);
+ }
+
public synchronized void registerMail() {
checkSplit();
mailSent += 1;
diff --git a/src/main/java/org/traccar/forward/AmqpClient.java b/src/main/java/org/traccar/forward/AmqpClient.java
new file mode 100644
index 000000000..361cfffee
--- /dev/null
+++ b/src/main/java/org/traccar/forward/AmqpClient.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.forward;
+
+import com.rabbitmq.client.BuiltinExchangeType;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.MessageProperties;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.TimeoutException;
+
+public class AmqpClient {
+ private final Channel channel;
+ private final String exchange;
+ private final String topic;
+
+ AmqpClient(String connectionUrl, String exchange, String topic) {
+ this.exchange = exchange;
+ this.topic = topic;
+
+ ConnectionFactory factory = new ConnectionFactory();
+ try {
+ factory.setUri(connectionUrl);
+ } catch (NoSuchAlgorithmException | URISyntaxException | KeyManagementException e) {
+ throw new RuntimeException("Error while setting URI for RabbitMQ connection factory", e);
+ }
+
+ try {
+ Connection connection = factory.newConnection();
+ channel = connection.createChannel();
+ channel.exchangeDeclare(exchange, BuiltinExchangeType.TOPIC, true);
+ } catch (IOException | TimeoutException e) {
+ throw new RuntimeException("Error while creating and configuring RabbitMQ channel", e);
+ }
+ }
+
+ public void publishMessage(String message) throws IOException {
+ channel.basicPublish(exchange, topic, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
+ }
+}
diff --git a/src/main/java/org/traccar/forward/EventForwarderAmqp.java b/src/main/java/org/traccar/forward/EventForwarderAmqp.java
new file mode 100644
index 000000000..5c38a4459
--- /dev/null
+++ b/src/main/java/org/traccar/forward/EventForwarderAmqp.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.forward;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+
+import java.io.IOException;
+
+public class EventForwarderAmqp implements EventForwarder {
+
+ private final AmqpClient amqpClient;
+ private final ObjectMapper objectMapper;
+
+ public EventForwarderAmqp(Config config, ObjectMapper objectMapper) {
+ String connectionUrl = config.getString(Keys.EVENT_FORWARD_URL);
+ String exchange = config.getString(Keys.EVENT_FORWARD_EXCHANGE);
+ String topic = config.getString(Keys.EVENT_FORWARD_TOPIC);
+ this.objectMapper = objectMapper;
+ amqpClient = new AmqpClient(connectionUrl, exchange, topic);
+ }
+
+ @Override
+ public void forward(EventData eventData, ResultHandler resultHandler) {
+ try {
+ String value = objectMapper.writeValueAsString(eventData);
+ amqpClient.publishMessage(value);
+ resultHandler.onResult(true, null);
+ } catch (IOException e) {
+ resultHandler.onResult(false, e);
+ }
+ }
+}
diff --git a/src/main/java/org/traccar/forward/EventForwarderJson.java b/src/main/java/org/traccar/forward/EventForwarderJson.java
index 7527d568a..df53d3d46 100644
--- a/src/main/java/org/traccar/forward/EventForwarderJson.java
+++ b/src/main/java/org/traccar/forward/EventForwarderJson.java
@@ -18,10 +18,10 @@ package org.traccar.forward;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.InvocationCallback;
-import javax.ws.rs.core.Response;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.InvocationCallback;
+import jakarta.ws.rs.core.Response;
public class EventForwarderJson implements EventForwarder {
diff --git a/src/main/java/org/traccar/forward/EventForwarderMqtt.java b/src/main/java/org/traccar/forward/EventForwarderMqtt.java
index dc95cb4e2..7f4e29384 100644
--- a/src/main/java/org/traccar/forward/EventForwarderMqtt.java
+++ b/src/main/java/org/traccar/forward/EventForwarderMqtt.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,8 +21,6 @@ import com.hivemq.client.mqtt.datatypes.MqttQos;
import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient;
import com.hivemq.client.mqtt.mqtt5.Mqtt5Client;
import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5SimpleAuth;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
import org.traccar.config.Keys;
@@ -32,7 +30,6 @@ import java.util.UUID;
public class EventForwarderMqtt implements EventForwarder {
- private static final Logger LOGGER = LoggerFactory.getLogger(EventForwarderMqtt.class);
private final Mqtt5AsyncClient client;
private final ObjectMapper objectMapper;
@@ -63,7 +60,7 @@ public class EventForwarderMqtt implements EventForwarder {
String host = url.getHost();
int port = url.getPort();
client = Mqtt5Client.builder()
- .identifier("traccar-" + UUID.randomUUID().toString())
+ .identifier("traccar-" + UUID.randomUUID())
.serverHost(host)
.serverPort(port)
.simpleAuth(simpleAuth)
diff --git a/src/main/java/org/traccar/forward/NetworkForwarder.java b/src/main/java/org/traccar/forward/NetworkForwarder.java
new file mode 100644
index 000000000..86c9a77f3
--- /dev/null
+++ b/src/main/java/org/traccar/forward/NetworkForwarder.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.forward;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.Map;
+
+@Singleton
+public class NetworkForwarder {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(NetworkForwarder.class);
+
+ private final InetAddress destination;
+ private final DatagramSocket connectionUdp;
+ private final Map<InetSocketAddress, Socket> connectionsTcp = new HashMap<>();
+
+ @Inject
+ public NetworkForwarder(Config config) throws IOException {
+ destination = InetAddress.getByName(config.getString(Keys.SERVER_FORWARD));
+ connectionUdp = new DatagramSocket();
+ }
+
+ public void forward(InetSocketAddress source, int port, boolean datagram, byte[] data) {
+ try {
+ if (datagram) {
+ connectionUdp.send(new DatagramPacket(data, data.length, destination, port));
+ } else {
+ Socket connectionTcp = connectionsTcp.get(source);
+ if (connectionTcp == null || connectionTcp.isClosed()) {
+ connectionTcp = new Socket(destination, port);
+ connectionsTcp.put(source, connectionTcp);
+ }
+ connectionTcp.getOutputStream().write(data);
+ }
+ } catch (IOException e) {
+ LOGGER.warn("Network forwarding error", e);
+ }
+ }
+
+ public void disconnect(InetSocketAddress source) {
+ Socket connectionTcp = connectionsTcp.remove(source);
+ if (connectionTcp != null) {
+ try {
+ connectionTcp.close();
+ } catch (IOException e) {
+ LOGGER.warn("Connection close error", e);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/forward/PositionForwarderAmqp.java b/src/main/java/org/traccar/forward/PositionForwarderAmqp.java
new file mode 100644
index 000000000..3996bda15
--- /dev/null
+++ b/src/main/java/org/traccar/forward/PositionForwarderAmqp.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.forward;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+
+import java.io.IOException;
+
+public class PositionForwarderAmqp implements PositionForwarder {
+
+ private final AmqpClient amqpClient;
+ private final ObjectMapper objectMapper;
+
+ public PositionForwarderAmqp(Config config, ObjectMapper objectMapper) {
+ String connectionUrl = config.getString(Keys.FORWARD_URL);
+ String exchange = config.getString(Keys.FORWARD_EXCHANGE);
+ String topic = config.getString(Keys.FORWARD_TOPIC);
+ amqpClient = new AmqpClient(connectionUrl, exchange, topic);
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public void forward(PositionData positionData, ResultHandler resultHandler) {
+ try {
+ String value = objectMapper.writeValueAsString(positionData);
+ amqpClient.publishMessage(value);
+ resultHandler.onResult(true, null);
+ } catch (IOException e) {
+ resultHandler.onResult(false, e);
+ }
+ }
+}
diff --git a/src/main/java/org/traccar/forward/PositionForwarderJson.java b/src/main/java/org/traccar/forward/PositionForwarderJson.java
index 27b96308e..a0ad8ffd0 100644
--- a/src/main/java/org/traccar/forward/PositionForwarderJson.java
+++ b/src/main/java/org/traccar/forward/PositionForwarderJson.java
@@ -20,12 +20,12 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.InvocationCallback;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.InvocationCallback;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
public class PositionForwarderJson implements PositionForwarder {
diff --git a/src/main/java/org/traccar/forward/PositionForwarderRedis.java b/src/main/java/org/traccar/forward/PositionForwarderRedis.java
new file mode 100644
index 000000000..539d247b6
--- /dev/null
+++ b/src/main/java/org/traccar/forward/PositionForwarderRedis.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.forward;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+import redis.clients.jedis.Jedis;
+
+public class PositionForwarderRedis implements PositionForwarder {
+
+ private final String url;
+
+ private final ObjectMapper objectMapper;
+
+ public PositionForwarderRedis(Config config, ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ this.url = config.getString(Keys.FORWARD_URL);
+ }
+
+ @Override
+ public void forward(PositionData positionData, ResultHandler resultHandler) {
+
+ try {
+ String key = "positions." + positionData.getDevice().getUniqueId();
+ String value = objectMapper.writeValueAsString(positionData.getPosition());
+ try (Jedis jedis = new Jedis(url)) {
+ jedis.lpush(key, value);
+ }
+ resultHandler.onResult(true, null);
+ } catch (JsonProcessingException e) {
+ resultHandler.onResult(false, e);
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/forward/PositionForwarderUrl.java b/src/main/java/org/traccar/forward/PositionForwarderUrl.java
index 53cc7ad24..33474d40b 100644
--- a/src/main/java/org/traccar/forward/PositionForwarderUrl.java
+++ b/src/main/java/org/traccar/forward/PositionForwarderUrl.java
@@ -23,9 +23,9 @@ import org.traccar.helper.Checksum;
import org.traccar.model.Device;
import org.traccar.model.Position;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.InvocationCallback;
-import javax.ws.rs.core.Response;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.InvocationCallback;
+import jakarta.ws.rs.core.Response;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
diff --git a/src/main/java/org/traccar/geocoder/BanGeocoder.java b/src/main/java/org/traccar/geocoder/BanGeocoder.java
index f878a8bab..e2ff72311 100644
--- a/src/main/java/org/traccar/geocoder/BanGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/BanGeocoder.java
@@ -20,9 +20,9 @@ package org.traccar.geocoder;
* API documentation: https://adresse.data.gouv.fr/api
*/
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class BanGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/BingMapsGeocoder.java b/src/main/java/org/traccar/geocoder/BingMapsGeocoder.java
index 01e33c2ea..bc3b15ce7 100644
--- a/src/main/java/org/traccar/geocoder/BingMapsGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/BingMapsGeocoder.java
@@ -16,9 +16,9 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class BingMapsGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/FactualGeocoder.java b/src/main/java/org/traccar/geocoder/FactualGeocoder.java
index 384f46b0e..6c8891316 100644
--- a/src/main/java/org/traccar/geocoder/FactualGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/FactualGeocoder.java
@@ -16,8 +16,8 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class FactualGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/GeoapifyGeocoder.java b/src/main/java/org/traccar/geocoder/GeoapifyGeocoder.java
index 4748d6a2c..35a47bb88 100644
--- a/src/main/java/org/traccar/geocoder/GeoapifyGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/GeoapifyGeocoder.java
@@ -15,9 +15,9 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class GeoapifyGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/GeocodeFarmGeocoder.java b/src/main/java/org/traccar/geocoder/GeocodeFarmGeocoder.java
index 2af95910f..80b00b3cc 100644
--- a/src/main/java/org/traccar/geocoder/GeocodeFarmGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/GeocodeFarmGeocoder.java
@@ -15,8 +15,8 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class GeocodeFarmGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/GeocodeXyzGeocoder.java b/src/main/java/org/traccar/geocoder/GeocodeXyzGeocoder.java
index 96491ece3..e88962e1a 100644
--- a/src/main/java/org/traccar/geocoder/GeocodeXyzGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/GeocodeXyzGeocoder.java
@@ -15,8 +15,8 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class GeocodeXyzGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java b/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java
index 0589eb000..062e795eb 100644
--- a/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java
@@ -15,8 +15,8 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class GisgraphyGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/GoogleGeocoder.java b/src/main/java/org/traccar/geocoder/GoogleGeocoder.java
index 4d9ec8f36..93f128b46 100644
--- a/src/main/java/org/traccar/geocoder/GoogleGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/GoogleGeocoder.java
@@ -15,10 +15,10 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.json.JsonString;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonString;
+import jakarta.ws.rs.client.Client;
public class GoogleGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/HereGeocoder.java b/src/main/java/org/traccar/geocoder/HereGeocoder.java
index eb639995e..4767eabad 100644
--- a/src/main/java/org/traccar/geocoder/HereGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/HereGeocoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,70 +15,64 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class HereGeocoder extends JsonGeocoder {
- private static String formatUrl(String url, String id, String key, String language) {
+ private static String formatUrl(String url, String key, String language) {
if (url == null) {
- url = "https://reverse.geocoder.ls.hereapi.com/6.2/reversegeocode.json";
+ url = "https://revgeocode.search.hereapi.com/v1/revgeocode";
}
- url += "?mode=retrieveAddresses&maxresults=1";
- url += "&prox=%f,%f,0";
- url += "&app_id=" + id;
- url += "&app_code=" + key;
+ url += "?types=address&limit=1";
+ url += "&at=%f,%f";
url += "&apiKey=" + key;
if (language != null) {
- url += "&language=" + language;
+ url += "&lang=" + language;
}
return url;
}
public HereGeocoder(
- Client client, String url, String id, String key, String language,
+ Client client, String url, String key, String language,
int cacheSize, AddressFormat addressFormat) {
- super(client, formatUrl(url, id, key, language), cacheSize, addressFormat);
+ super(client, formatUrl(url, key, language), cacheSize, addressFormat);
}
@Override
public Address parseAddress(JsonObject json) {
JsonObject result = json
- .getJsonObject("Response")
- .getJsonArray("View")
+ .getJsonArray("items")
.getJsonObject(0)
- .getJsonArray("Result")
- .getJsonObject(0)
- .getJsonObject("Location")
- .getJsonObject("Address");
+ .getJsonObject("address");
if (result != null) {
Address address = new Address();
- if (result.containsKey("Label")) {
- address.setFormattedAddress(result.getString("Label"));
+ if (result.containsKey("label")) {
+ address.setFormattedAddress(result.getString("label"));
}
- if (result.containsKey("HouseNumber")) {
- address.setHouse(result.getString("HouseNumber"));
+ if (result.containsKey("houseNumber")) {
+ address.setHouse(result.getString("houseNumber"));
}
- if (result.containsKey("Street")) {
- address.setStreet(result.getString("Street"));
+ if (result.containsKey("street")) {
+ address.setStreet(result.getString("street"));
}
- if (result.containsKey("City")) {
- address.setSettlement(result.getString("City"));
+ if (result.containsKey("city")) {
+ address.setSettlement(result.getString("city"));
}
- if (result.containsKey("District")) {
- address.setDistrict(result.getString("District"));
+ if (result.containsKey("district")) {
+ address.setDistrict(result.getString("district"));
}
- if (result.containsKey("State")) {
- address.setState(result.getString("State"));
+ if (result.containsKey("state")) {
+ address.setState(result.getString("state"));
}
- if (result.containsKey("Country")) {
- address.setCountry(result.getString("Country").toUpperCase());
+ if (result.containsKey("countryCode")) {
+ address.setCountry(result.getString("countryCode").toUpperCase());
}
- if (result.containsKey("PostalCode")) {
- address.setPostcode(result.getString("PostalCode"));
+ if (result.containsKey("postalCode")) {
+ address.setPostcode(result.getString("postalCode"));
}
return address;
diff --git a/src/main/java/org/traccar/geocoder/JsonGeocoder.java b/src/main/java/org/traccar/geocoder/JsonGeocoder.java
index 6105e8cfd..f9b039f43 100644
--- a/src/main/java/org/traccar/geocoder/JsonGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/JsonGeocoder.java
@@ -19,10 +19,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.database.StatisticsManager;
-import javax.json.JsonObject;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.InvocationCallback;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.InvocationCallback;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.LinkedHashMap;
diff --git a/src/main/java/org/traccar/geocoder/LocationIqGeocoder.java b/src/main/java/org/traccar/geocoder/LocationIqGeocoder.java
index f2ffe02d6..f304ffeff 100644
--- a/src/main/java/org/traccar/geocoder/LocationIqGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/LocationIqGeocoder.java
@@ -15,7 +15,7 @@
*/
package org.traccar.geocoder;
-import javax.ws.rs.client.Client;
+import jakarta.ws.rs.client.Client;
public class LocationIqGeocoder extends NominatimGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java b/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java
index 3f2554c6e..1b6c8adcc 100644
--- a/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java
@@ -16,9 +16,9 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class MapQuestGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/MapTilerGeocoder.java b/src/main/java/org/traccar/geocoder/MapTilerGeocoder.java
index 203f5f99b..24c9da2ad 100644
--- a/src/main/java/org/traccar/geocoder/MapTilerGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/MapTilerGeocoder.java
@@ -15,9 +15,9 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class MapTilerGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/MapboxGeocoder.java b/src/main/java/org/traccar/geocoder/MapboxGeocoder.java
index 72bfb53f5..9fa6b8d88 100644
--- a/src/main/java/org/traccar/geocoder/MapboxGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/MapboxGeocoder.java
@@ -15,10 +15,10 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.json.JsonString;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonString;
+import jakarta.ws.rs.client.Client;
public class MapboxGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/MapmyIndiaGeocoder.java b/src/main/java/org/traccar/geocoder/MapmyIndiaGeocoder.java
index dea295cca..b68db07bc 100644
--- a/src/main/java/org/traccar/geocoder/MapmyIndiaGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/MapmyIndiaGeocoder.java
@@ -15,9 +15,9 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class MapmyIndiaGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/NominatimGeocoder.java b/src/main/java/org/traccar/geocoder/NominatimGeocoder.java
index b731549f7..1e26d0042 100644
--- a/src/main/java/org/traccar/geocoder/NominatimGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/NominatimGeocoder.java
@@ -15,8 +15,8 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class NominatimGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java b/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java
index fb61440aa..4607fdc87 100644
--- a/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java
@@ -16,9 +16,9 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class OpenCageGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/PositionStackGeocoder.java b/src/main/java/org/traccar/geocoder/PositionStackGeocoder.java
index 9778d9eda..4aed27fc5 100644
--- a/src/main/java/org/traccar/geocoder/PositionStackGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/PositionStackGeocoder.java
@@ -15,9 +15,9 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class PositionStackGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geocoder/TomTomGeocoder.java b/src/main/java/org/traccar/geocoder/TomTomGeocoder.java
index 9bb36efc2..4d452fd43 100644
--- a/src/main/java/org/traccar/geocoder/TomTomGeocoder.java
+++ b/src/main/java/org/traccar/geocoder/TomTomGeocoder.java
@@ -15,9 +15,9 @@
*/
package org.traccar.geocoder;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
public class TomTomGeocoder extends JsonGeocoder {
diff --git a/src/main/java/org/traccar/geolocation/GoogleGeolocationProvider.java b/src/main/java/org/traccar/geolocation/GoogleGeolocationProvider.java
index 8f0f3b704..9425e9111 100644
--- a/src/main/java/org/traccar/geolocation/GoogleGeolocationProvider.java
+++ b/src/main/java/org/traccar/geolocation/GoogleGeolocationProvider.java
@@ -15,7 +15,7 @@
*/
package org.traccar.geolocation;
-import javax.ws.rs.client.Client;
+import jakarta.ws.rs.client.Client;
public class GoogleGeolocationProvider extends UniversalGeolocationProvider {
diff --git a/src/main/java/org/traccar/geolocation/MozillaGeolocationProvider.java b/src/main/java/org/traccar/geolocation/MozillaGeolocationProvider.java
index 3b4ba4e1f..7eb22dcca 100644
--- a/src/main/java/org/traccar/geolocation/MozillaGeolocationProvider.java
+++ b/src/main/java/org/traccar/geolocation/MozillaGeolocationProvider.java
@@ -15,7 +15,7 @@
*/
package org.traccar.geolocation;
-import javax.ws.rs.client.Client;
+import jakarta.ws.rs.client.Client;
public class MozillaGeolocationProvider extends UniversalGeolocationProvider {
diff --git a/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java b/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java
index 82fcf42ab..72a05d10f 100644
--- a/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java
+++ b/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java
@@ -18,9 +18,9 @@ package org.traccar.geolocation;
import org.traccar.model.CellTower;
import org.traccar.model.Network;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.InvocationCallback;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.InvocationCallback;
public class OpenCellIdGeolocationProvider implements GeolocationProvider {
diff --git a/src/main/java/org/traccar/geolocation/UniversalGeolocationProvider.java b/src/main/java/org/traccar/geolocation/UniversalGeolocationProvider.java
index 7a3f71ee1..9086d8ce3 100644
--- a/src/main/java/org/traccar/geolocation/UniversalGeolocationProvider.java
+++ b/src/main/java/org/traccar/geolocation/UniversalGeolocationProvider.java
@@ -17,10 +17,10 @@ package org.traccar.geolocation;
import org.traccar.model.Network;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.InvocationCallback;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.InvocationCallback;
public class UniversalGeolocationProvider implements GeolocationProvider {
diff --git a/src/main/java/org/traccar/geolocation/UnwiredGeolocationProvider.java b/src/main/java/org/traccar/geolocation/UnwiredGeolocationProvider.java
index 14893b6a3..4f1c5617e 100644
--- a/src/main/java/org/traccar/geolocation/UnwiredGeolocationProvider.java
+++ b/src/main/java/org/traccar/geolocation/UnwiredGeolocationProvider.java
@@ -23,10 +23,10 @@ import org.traccar.model.CellTower;
import org.traccar.model.Network;
import org.traccar.model.WifiAccessPoint;
-import javax.json.JsonObject;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.InvocationCallback;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.InvocationCallback;
import java.util.Collection;
public class UnwiredGeolocationProvider implements GeolocationProvider {
diff --git a/src/main/java/org/traccar/handler/AcknowledgementHandler.java b/src/main/java/org/traccar/handler/AcknowledgementHandler.java
new file mode 100644
index 000000000..4c1085998
--- /dev/null
+++ b/src/main/java/org/traccar/handler/AcknowledgementHandler.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.handler;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelOutboundHandlerAdapter;
+import io.netty.channel.ChannelPromise;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+public class AcknowledgementHandler extends ChannelOutboundHandlerAdapter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AcknowledgementHandler.class);
+
+ public interface Event {
+ }
+
+ public static class EventReceived implements Event {
+ }
+
+ public static class EventDecoded implements Event {
+ private final Collection<Object> objects;
+
+ public EventDecoded(Collection<Object> objects) {
+ this.objects = objects;
+ }
+
+ public Collection<Object> getObjects() {
+ return objects;
+ }
+ }
+
+ public static class EventHandled implements Event {
+ private final Object object;
+
+ public EventHandled(Object object) {
+ this.object = object;
+ }
+
+ public Object getObject() {
+ return object;
+ }
+ }
+
+ private static final class Entry {
+ private final Object message;
+ private final ChannelPromise promise;
+
+ private Entry(Object message, ChannelPromise promise) {
+ this.message = message;
+ this.promise = promise;
+ }
+
+ public Object getMessage() {
+ return message;
+ }
+
+ public ChannelPromise getPromise() {
+ return promise;
+ }
+ }
+
+ private List<Entry> queue;
+ private final Set<Object> waiting = new HashSet<>();
+
+ @Override
+ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
+ List<Entry> output = new LinkedList<>();
+ synchronized (this) {
+ if (msg instanceof Event) {
+ if (msg instanceof EventReceived) {
+ LOGGER.debug("Event received");
+ if (queue == null) {
+ queue = new LinkedList<>();
+ }
+ } else if (msg instanceof EventDecoded) {
+ EventDecoded event = (EventDecoded) msg;
+ LOGGER.debug("Event decoded {}", event.getObjects().size());
+ waiting.addAll(event.getObjects());
+ } else if (msg instanceof EventHandled) {
+ EventHandled event = (EventHandled) msg;
+ LOGGER.debug("Event handled");
+ waiting.remove(event.getObject());
+ }
+ if (!(msg instanceof EventReceived) && waiting.isEmpty()) {
+ output.addAll(queue);
+ queue = null;
+ }
+ } else if (queue != null) {
+ LOGGER.debug("Message queued");
+ queue.add(new Entry(msg, promise));
+ } else {
+ LOGGER.debug("Message sent");
+ output.add(new Entry(msg, promise));
+ }
+ }
+ for (Entry entry : output) {
+ ctx.write(entry.getMessage(), entry.getPromise());
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java
index 620852502..8b010ceae 100644
--- a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java
+++ b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java
@@ -24,11 +24,15 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.List;
import io.netty.channel.ChannelHandler;
-import org.apache.commons.jexl2.JexlEngine;
-import org.apache.commons.jexl2.JexlException;
-import org.apache.commons.jexl2.MapContext;
+import org.apache.commons.jexl3.JexlFeatures;
+import org.apache.commons.jexl3.JexlEngine;
+import org.apache.commons.jexl3.JexlBuilder;
+import org.apache.commons.jexl3.introspection.JexlSandbox;
+import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.MapContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.BaseDataHandler;
@@ -39,8 +43,8 @@ import org.traccar.model.Device;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
@ChannelHandler.Sharable
@@ -52,15 +56,33 @@ public class ComputedAttributesHandler extends BaseDataHandler {
private final JexlEngine engine;
+ private final JexlFeatures features;
+
private final boolean includeDeviceAttributes;
+ private final boolean includeLastAttributes;
@Inject
public ComputedAttributesHandler(Config config, CacheManager cacheManager) {
this.cacheManager = cacheManager;
- engine = new JexlEngine();
- engine.setStrict(true);
- engine.setFunctions(Collections.singletonMap("math", Math.class));
+ JexlSandbox sandbox = new JexlSandbox(false);
+ sandbox.allow("com.safe.Functions");
+ sandbox.allow(Math.class.getName());
+ List.of(
+ Double.class, Float.class, Integer.class, Long.class, Short.class,
+ Character.class, Boolean.class, String.class, Byte.class)
+ .forEach((type) -> sandbox.allow(type.getName()));
+ features = new JexlFeatures()
+ .localVar(config.getBoolean(Keys.PROCESSING_COMPUTED_ATTRIBUTES_LOCAL_VARIABLES))
+ .loops(config.getBoolean(Keys.PROCESSING_COMPUTED_ATTRIBUTES_LOOPS))
+ .newInstance(config.getBoolean(Keys.PROCESSING_COMPUTED_ATTRIBUTES_NEW_INSTANCE_CREATION))
+ .structuredLiteral(true);
+ engine = new JexlBuilder()
+ .strict(true)
+ .namespaces(Collections.singletonMap("math", Math.class))
+ .sandbox(sandbox)
+ .create();
includeDeviceAttributes = config.getBoolean(Keys.PROCESSING_COMPUTED_ATTRIBUTES_DEVICE_ATTRIBUTES);
+ includeLastAttributes = config.getBoolean(Keys.PROCESSING_COMPUTED_ATTRIBUTES_LAST_ATTRIBUTES);
}
private MapContext prepareContext(Position position) {
@@ -68,13 +90,17 @@ public class ComputedAttributesHandler extends BaseDataHandler {
if (includeDeviceAttributes) {
Device device = cacheManager.getObject(Device.class, position.getDeviceId());
if (device != null) {
- for (Object key : device.getAttributes().keySet()) {
- result.set((String) key, device.getAttributes().get(key));
+ for (String key : device.getAttributes().keySet()) {
+ result.set(key, device.getAttributes().get(key));
}
}
}
+ Position last = null;
+ if (includeLastAttributes) {
+ last = cacheManager.getPosition(position.getDeviceId());
+ }
Set<Method> methods = new HashSet<>(Arrays.asList(position.getClass().getMethods()));
- methods.removeAll(Arrays.asList(Object.class.getMethods()));
+ Arrays.asList(Object.class.getMethods()).forEach(methods::remove);
for (Method method : methods) {
if (method.getName().startsWith("get") && method.getParameterTypes().length == 0) {
String name = Character.toLowerCase(method.getName().charAt(3)) + method.getName().substring(4);
@@ -82,9 +108,17 @@ public class ComputedAttributesHandler extends BaseDataHandler {
try {
if (!method.getReturnType().equals(Map.class)) {
result.set(name, method.invoke(position));
+ if (last != null) {
+ result.set(prefixAttribute("last", name), method.invoke(last));
+ }
} else {
- for (Object key : ((Map) method.invoke(position)).keySet()) {
- result.set((String) key, ((Map) method.invoke(position)).get(key));
+ for (Map.Entry<?, ?> entry : ((Map<?, ?>) method.invoke(position)).entrySet()) {
+ result.set((String) entry.getKey(), entry.getValue());
+ }
+ if (last != null) {
+ for (Map.Entry<?, ?> entry : ((Map<?, ?>) method.invoke(last)).entrySet()) {
+ result.set(prefixAttribute("last", (String) entry.getKey()), entry.getValue());
+ }
}
}
} catch (IllegalAccessException | InvocationTargetException error) {
@@ -95,12 +129,18 @@ public class ComputedAttributesHandler extends BaseDataHandler {
return result;
}
+ private String prefixAttribute(String prefix, String key) {
+ return prefix + Character.toUpperCase(key.charAt(0)) + key.substring(1);
+ }
+
/**
* @deprecated logic needs to be extracted to be used in API resource
*/
@Deprecated
public Object computeAttribute(Attribute attribute, Position position) throws JexlException {
- return engine.createExpression(attribute.getExpression()).evaluate(prepareContext(position));
+ return engine
+ .createScript(features, engine.createInfo(), attribute.getExpression())
+ .execute(prepareContext(position));
}
@Override
diff --git a/src/main/java/org/traccar/handler/CopyAttributesHandler.java b/src/main/java/org/traccar/handler/CopyAttributesHandler.java
index e5c9bc29a..42b438e41 100644
--- a/src/main/java/org/traccar/handler/CopyAttributesHandler.java
+++ b/src/main/java/org/traccar/handler/CopyAttributesHandler.java
@@ -24,8 +24,8 @@ import org.traccar.helper.model.AttributeUtil;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
@ChannelHandler.Sharable
diff --git a/src/main/java/org/traccar/handler/DefaultDataHandler.java b/src/main/java/org/traccar/handler/DefaultDataHandler.java
index 89255a5fe..cca6dcd0a 100644
--- a/src/main/java/org/traccar/handler/DefaultDataHandler.java
+++ b/src/main/java/org/traccar/handler/DefaultDataHandler.java
@@ -24,8 +24,8 @@ import org.traccar.storage.Storage;
import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
@ChannelHandler.Sharable
diff --git a/src/main/java/org/traccar/handler/DistanceHandler.java b/src/main/java/org/traccar/handler/DistanceHandler.java
index 30dc9ff2b..db8c73779 100644
--- a/src/main/java/org/traccar/handler/DistanceHandler.java
+++ b/src/main/java/org/traccar/handler/DistanceHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
* Copyright 2015 Amila Silva
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,6 +17,8 @@
package org.traccar.handler;
import io.netty.channel.ChannelHandler;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import org.traccar.BaseDataHandler;
import org.traccar.config.Config;
import org.traccar.config.Keys;
@@ -24,11 +26,6 @@ import org.traccar.helper.DistanceCalculator;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-
@Singleton
@ChannelHandler.Sharable
public class DistanceHandler extends BaseDataHandler {
@@ -36,15 +33,15 @@ public class DistanceHandler extends BaseDataHandler {
private final CacheManager cacheManager;
private final boolean filter;
- private final int coordinatesMinError;
- private final int coordinatesMaxError;
+ private final int minError;
+ private final int maxError;
@Inject
public DistanceHandler(Config config, CacheManager cacheManager) {
this.cacheManager = cacheManager;
this.filter = config.getBoolean(Keys.COORDINATES_FILTER);
- this.coordinatesMinError = config.getInteger(Keys.COORDINATES_MIN_ERROR);
- this.coordinatesMaxError = config.getInteger(Keys.COORDINATES_MAX_ERROR);
+ this.minError = config.getInteger(Keys.COORDINATES_MIN_ERROR);
+ this.maxError = config.getInteger(Keys.COORDINATES_MAX_ERROR);
}
@Override
@@ -54,8 +51,7 @@ public class DistanceHandler extends BaseDataHandler {
if (position.hasAttribute(Position.KEY_DISTANCE)) {
distance = position.getDouble(Position.KEY_DISTANCE);
}
- double totalDistance = 0.0;
-
+ double totalDistance;
Position last = cacheManager.getPosition(position.getDeviceId());
if (last != null) {
totalDistance = last.getDouble(Position.KEY_TOTAL_DISTANCE);
@@ -63,11 +59,10 @@ public class DistanceHandler extends BaseDataHandler {
distance = DistanceCalculator.distance(
position.getLatitude(), position.getLongitude(),
last.getLatitude(), last.getLongitude());
- distance = BigDecimal.valueOf(distance).setScale(2, RoundingMode.HALF_EVEN).doubleValue();
}
if (filter && last.getLatitude() != 0 && last.getLongitude() != 0) {
- boolean satisfiesMin = coordinatesMinError == 0 || distance > coordinatesMinError;
- boolean satisfiesMax = coordinatesMaxError == 0 || distance < coordinatesMaxError;
+ boolean satisfiesMin = minError == 0 || distance > minError;
+ boolean satisfiesMax = maxError == 0 || distance < maxError || position.getValid();
if (!satisfiesMin || !satisfiesMax) {
position.setValid(last.getValid());
position.setLatitude(last.getLatitude());
@@ -75,10 +70,11 @@ public class DistanceHandler extends BaseDataHandler {
distance = 0;
}
}
+ } else {
+ totalDistance = 0.0;
}
position.set(Position.KEY_DISTANCE, distance);
- totalDistance = BigDecimal.valueOf(totalDistance + distance).setScale(2, RoundingMode.HALF_EVEN).doubleValue();
- position.set(Position.KEY_TOTAL_DISTANCE, totalDistance);
+ position.set(Position.KEY_TOTAL_DISTANCE, totalDistance + distance);
return position;
}
diff --git a/src/main/java/org/traccar/handler/EngineHoursHandler.java b/src/main/java/org/traccar/handler/EngineHoursHandler.java
index c10fe9064..621205b34 100644
--- a/src/main/java/org/traccar/handler/EngineHoursHandler.java
+++ b/src/main/java/org/traccar/handler/EngineHoursHandler.java
@@ -21,8 +21,8 @@ import org.traccar.BaseDataHandler;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
@ChannelHandler.Sharable
diff --git a/src/main/java/org/traccar/handler/FilterHandler.java b/src/main/java/org/traccar/handler/FilterHandler.java
index 994276bb6..a15d3ffad 100644
--- a/src/main/java/org/traccar/handler/FilterHandler.java
+++ b/src/main/java/org/traccar/handler/FilterHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2014 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,13 +16,16 @@
package org.traccar.handler;
import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.traccar.BaseDataHandler;
import org.traccar.config.Config;
import org.traccar.config.Keys;
+import org.traccar.database.StatisticsManager;
import org.traccar.helper.UnitsConverter;
import org.traccar.helper.model.AttributeUtil;
+import org.traccar.model.Calendar;
import org.traccar.model.Device;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
@@ -33,13 +36,13 @@ import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Order;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Date;
@Singleton
@ChannelHandler.Sharable
-public class FilterHandler extends BaseDataHandler {
+public class FilterHandler extends ChannelInboundHandlerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(FilterHandler.class);
@@ -47,6 +50,7 @@ public class FilterHandler extends BaseDataHandler {
private final boolean filterInvalid;
private final boolean filterZero;
private final boolean filterDuplicate;
+ private final boolean filterOutdated;
private final long filterFuture;
private final long filterPast;
private final boolean filterApproximate;
@@ -55,19 +59,23 @@ public class FilterHandler extends BaseDataHandler {
private final int filterDistance;
private final int filterMaxSpeed;
private final long filterMinPeriod;
+ private final int filterDailyLimit;
private final boolean filterRelative;
private final long skipLimit;
private final boolean skipAttributes;
private final CacheManager cacheManager;
private final Storage storage;
+ private final StatisticsManager statisticsManager;
@Inject
- public FilterHandler(Config config, CacheManager cacheManager, Storage storage) {
+ public FilterHandler(
+ Config config, CacheManager cacheManager, Storage storage, StatisticsManager statisticsManager) {
enabled = config.getBoolean(Keys.FILTER_ENABLE);
filterInvalid = config.getBoolean(Keys.FILTER_INVALID);
filterZero = config.getBoolean(Keys.FILTER_ZERO);
filterDuplicate = config.getBoolean(Keys.FILTER_DUPLICATE);
+ filterOutdated = config.getBoolean(Keys.FILTER_OUTDATED);
filterFuture = config.getLong(Keys.FILTER_FUTURE) * 1000;
filterPast = config.getLong(Keys.FILTER_PAST) * 1000;
filterAccuracy = config.getInteger(Keys.FILTER_ACCURACY);
@@ -76,11 +84,13 @@ public class FilterHandler extends BaseDataHandler {
filterDistance = config.getInteger(Keys.FILTER_DISTANCE);
filterMaxSpeed = config.getInteger(Keys.FILTER_MAX_SPEED);
filterMinPeriod = config.getInteger(Keys.FILTER_MIN_PERIOD) * 1000L;
+ filterDailyLimit = config.getInteger(Keys.FILTER_DAILY_LIMIT);
filterRelative = config.getBoolean(Keys.FILTER_RELATIVE);
skipLimit = config.getLong(Keys.FILTER_SKIP_LIMIT) * 1000;
skipAttributes = config.getBoolean(Keys.FILTER_SKIP_ATTRIBUTES_ENABLE);
this.cacheManager = cacheManager;
this.storage = storage;
+ this.statisticsManager = statisticsManager;
}
private Position getPrecedingPosition(long deviceId, Date date) throws StorageException {
@@ -114,6 +124,10 @@ public class FilterHandler extends BaseDataHandler {
return false;
}
+ private boolean filterOutdated(Position position) {
+ return filterOutdated && position.getOutdated();
+ }
+
private boolean filterFuture(Position position) {
return filterFuture != 0 && position.getFixTime().getTime() > System.currentTimeMillis() + filterFuture;
}
@@ -158,6 +172,13 @@ public class FilterHandler extends BaseDataHandler {
return false;
}
+ private boolean filterDailyLimit(Position position) {
+ if (filterDailyLimit != 0) {
+ return statisticsManager.messageStoredCount(position.getDeviceId()) >= filterDailyLimit;
+ }
+ return false;
+ }
+
private boolean skipLimit(Position position, Position last) {
if (skipLimit != 0 && last != null) {
return (position.getServerTime().getTime() - last.getServerTime().getTime()) > skipLimit;
@@ -177,7 +198,7 @@ public class FilterHandler extends BaseDataHandler {
return false;
}
- private boolean filter(Position position) {
+ protected boolean filter(Position position) {
StringBuilder filterType = new StringBuilder();
@@ -188,6 +209,9 @@ public class FilterHandler extends BaseDataHandler {
if (filterZero(position)) {
filterType.append("Zero ");
}
+ if (filterOutdated(position)) {
+ filterType.append("Outdated ");
+ }
if (filterFuture(position)) {
filterType.append("Future ");
}
@@ -200,6 +224,9 @@ public class FilterHandler extends BaseDataHandler {
if (filterApproximate(position)) {
filterType.append("Approximate ");
}
+ if (filterDailyLimit(position)) {
+ filterType.append("DailyLimit ");
+ }
// filter out excessive data
long deviceId = position.getDeviceId();
@@ -233,9 +260,16 @@ public class FilterHandler extends BaseDataHandler {
}
}
+ Device device = cacheManager.getObject(Device.class, deviceId);
+ if (device.getCalendarId() > 0) {
+ Calendar calendar = cacheManager.getObject(Calendar.class, device.getCalendarId());
+ if (!calendar.checkMoment(position.getFixTime())) {
+ filterType.append("Calendar ");
+ }
+ }
+
if (filterType.length() > 0) {
- String uniqueId = cacheManager.getObject(Device.class, deviceId).getUniqueId();
- LOGGER.info("Position filtered by {}filters from device: {}", filterType, uniqueId);
+ LOGGER.info("Position filtered by {}filters from device: {}", filterType, device.getUniqueId());
return true;
}
@@ -243,11 +277,17 @@ public class FilterHandler extends BaseDataHandler {
}
@Override
- protected Position handlePosition(Position position) {
- if (enabled && filter(position)) {
- return null;
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ if (msg instanceof Position) {
+ Position position = (Position) msg;
+ if (enabled && filter(position)) {
+ ctx.writeAndFlush(new AcknowledgementHandler.EventHandled(position));
+ } else {
+ ctx.fireChannelRead(position);
+ }
+ } else {
+ super.channelRead(ctx, msg);
}
- return position;
}
}
diff --git a/src/main/java/org/traccar/handler/GeofenceHandler.java b/src/main/java/org/traccar/handler/GeofenceHandler.java
new file mode 100644
index 000000000..68bc6dbf0
--- /dev/null
+++ b/src/main/java/org/traccar/handler/GeofenceHandler.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.handler;
+
+import io.netty.channel.ChannelHandler;
+import org.traccar.BaseDataHandler;
+import org.traccar.config.Config;
+import org.traccar.helper.model.GeofenceUtil;
+import org.traccar.model.Position;
+import org.traccar.session.cache.CacheManager;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import java.util.List;
+
+@Singleton
+@ChannelHandler.Sharable
+public class GeofenceHandler extends BaseDataHandler {
+
+ private final Config config;
+ private final CacheManager cacheManager;
+
+ @Inject
+ public GeofenceHandler(Config config, CacheManager cacheManager) {
+ this.config = config;
+ this.cacheManager = cacheManager;
+ }
+
+ @Override
+ protected Position handlePosition(Position position) {
+
+ List<Long> geofenceIds = GeofenceUtil.getCurrentGeofences(config, cacheManager, position);
+ if (!geofenceIds.isEmpty()) {
+ position.setGeofenceIds(geofenceIds);
+ }
+ return position;
+ }
+
+}
diff --git a/src/main/java/org/traccar/handler/GeolocationHandler.java b/src/main/java/org/traccar/handler/GeolocationHandler.java
index e7389f22d..a54ea03e3 100644
--- a/src/main/java/org/traccar/handler/GeolocationHandler.java
+++ b/src/main/java/org/traccar/handler/GeolocationHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@ public class GeolocationHandler extends ChannelInboundHandlerAdapter {
private final StatisticsManager statisticsManager;
private final boolean processInvalidPositions;
private final boolean reuse;
+ private final boolean requireWifi;
public GeolocationHandler(
Config config, GeolocationProvider geolocationProvider, CacheManager cacheManager,
@@ -46,6 +47,7 @@ public class GeolocationHandler extends ChannelInboundHandlerAdapter {
this.statisticsManager = statisticsManager;
processInvalidPositions = config.getBoolean(Keys.GEOLOCATION_PROCESS_INVALID_POSITIONS);
reuse = config.getBoolean(Keys.GEOLOCATION_REUSE);
+ requireWifi = config.getBoolean(Keys.GEOLOCATION_REQUIRE_WIFI);
}
@Override
@@ -53,7 +55,8 @@ public class GeolocationHandler extends ChannelInboundHandlerAdapter {
if (message instanceof Position) {
final Position position = (Position) message;
if ((position.getOutdated() || processInvalidPositions && !position.getValid())
- && position.getNetwork() != null) {
+ && position.getNetwork() != null
+ && (!requireWifi || position.getNetwork().getWifiAccessPoints() != null)) {
if (reuse) {
Position lastPosition = cacheManager.getPosition(position.getDeviceId());
if (lastPosition != null && position.getNetwork().equals(lastPosition.getNetwork())) {
diff --git a/src/main/java/org/traccar/handler/HemisphereHandler.java b/src/main/java/org/traccar/handler/HemisphereHandler.java
index ccbde9fe5..294e449db 100644
--- a/src/main/java/org/traccar/handler/HemisphereHandler.java
+++ b/src/main/java/org/traccar/handler/HemisphereHandler.java
@@ -21,8 +21,8 @@ import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Position;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
@ChannelHandler.Sharable
diff --git a/src/main/java/org/traccar/handler/MotionHandler.java b/src/main/java/org/traccar/handler/MotionHandler.java
index 10312f9b3..68a31a16a 100644
--- a/src/main/java/org/traccar/handler/MotionHandler.java
+++ b/src/main/java/org/traccar/handler/MotionHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org)
* Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,27 +18,31 @@ package org.traccar.handler;
import io.netty.channel.ChannelHandler;
import org.traccar.BaseDataHandler;
+import org.traccar.config.Keys;
+import org.traccar.helper.model.AttributeUtil;
import org.traccar.model.Position;
-import org.traccar.reports.common.TripsConfig;
+import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
@ChannelHandler.Sharable
public class MotionHandler extends BaseDataHandler {
- private final double speedThreshold;
+ private final CacheManager cacheManager;
@Inject
- public MotionHandler(TripsConfig tripsConfig) {
- speedThreshold = tripsConfig.getSpeedThreshold();
+ public MotionHandler(CacheManager cacheManager) {
+ this.cacheManager = cacheManager;
}
@Override
protected Position handlePosition(Position position) {
if (!position.hasAttribute(Position.KEY_MOTION)) {
- position.set(Position.KEY_MOTION, position.getSpeed() > speedThreshold);
+ double threshold = AttributeUtil.lookup(
+ cacheManager, Keys.EVENT_MOTION_SPEED_THRESHOLD, position.getDeviceId());
+ position.set(Position.KEY_MOTION, position.getSpeed() > threshold);
}
return position;
}
diff --git a/src/main/java/org/traccar/handler/NetworkForwarderHandler.java b/src/main/java/org/traccar/handler/NetworkForwarderHandler.java
new file mode 100644
index 000000000..470e175ca
--- /dev/null
+++ b/src/main/java/org/traccar/handler/NetworkForwarderHandler.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.handler;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.socket.DatagramChannel;
+import io.netty.channel.socket.DatagramPacket;
+import org.traccar.forward.NetworkForwarder;
+
+import jakarta.inject.Inject;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+public class NetworkForwarderHandler extends ChannelInboundHandlerAdapter {
+
+ private final int port;
+
+ private NetworkForwarder networkForwarder;
+
+ public NetworkForwarderHandler(int port) {
+ this.port = port;
+ }
+
+ @Inject
+ public void setNetworkForwarder(NetworkForwarder networkForwarder) {
+ this.networkForwarder = networkForwarder;
+ }
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ boolean datagram = ctx.channel() instanceof DatagramChannel;
+ SocketAddress remoteAddress;
+ ByteBuf buffer;
+ if (datagram) {
+ DatagramPacket message = (DatagramPacket) msg;
+ remoteAddress = message.recipient();
+ buffer = message.content();
+ } else {
+ remoteAddress = ctx.channel().remoteAddress();
+ buffer = (ByteBuf) msg;
+ }
+
+ byte[] data = new byte[buffer.readableBytes()];
+ buffer.getBytes(buffer.readerIndex(), data);
+ networkForwarder.forward((InetSocketAddress) remoteAddress, port, datagram, data);
+ super.channelRead(ctx, msg);
+ }
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ if (!(ctx.channel() instanceof DatagramChannel)) {
+ networkForwarder.disconnect((InetSocketAddress) ctx.channel().remoteAddress());
+ }
+ super.channelInactive(ctx);
+ }
+
+}
diff --git a/src/main/java/org/traccar/handler/RemoteAddressHandler.java b/src/main/java/org/traccar/handler/RemoteAddressHandler.java
index e18d34ef2..61ada5b91 100644
--- a/src/main/java/org/traccar/handler/RemoteAddressHandler.java
+++ b/src/main/java/org/traccar/handler/RemoteAddressHandler.java
@@ -22,8 +22,8 @@ import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Position;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.net.InetSocketAddress;
@Singleton
diff --git a/src/main/java/org/traccar/handler/SpeedLimitHandler.java b/src/main/java/org/traccar/handler/SpeedLimitHandler.java
index 0c6025999..6edb6e912 100644
--- a/src/main/java/org/traccar/handler/SpeedLimitHandler.java
+++ b/src/main/java/org/traccar/handler/SpeedLimitHandler.java
@@ -23,8 +23,8 @@ import org.slf4j.LoggerFactory;
import org.traccar.model.Position;
import org.traccar.speedlimit.SpeedLimitProvider;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
@ChannelHandler.Sharable
diff --git a/src/main/java/org/traccar/handler/StandardLoggingHandler.java b/src/main/java/org/traccar/handler/StandardLoggingHandler.java
index 84492e2a5..5978d632e 100644
--- a/src/main/java/org/traccar/handler/StandardLoggingHandler.java
+++ b/src/main/java/org/traccar/handler/StandardLoggingHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,68 +20,73 @@ import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
+import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.NetworkMessage;
import org.traccar.helper.NetworkUtil;
+import org.traccar.model.LogRecord;
+import org.traccar.session.ConnectionManager;
import java.net.InetSocketAddress;
-import java.net.SocketAddress;
public class StandardLoggingHandler extends ChannelDuplexHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(StandardLoggingHandler.class);
private final String protocol;
+ private ConnectionManager connectionManager;
public StandardLoggingHandler(String protocol) {
this.protocol = protocol;
}
+ @Inject
+ public void setConnectionManager(ConnectionManager connectionManager) {
+ this.connectionManager = connectionManager;
+ }
+
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- log(ctx, false, msg);
+ LogRecord record = createLogRecord(msg);
+ log(ctx, false, record);
super.channelRead(ctx, msg);
+ if (record != null) {
+ connectionManager.updateLog(record);
+ }
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
- log(ctx, true, msg);
+ log(ctx, true, createLogRecord(msg));
super.write(ctx, msg, promise);
}
- public void log(ChannelHandlerContext ctx, boolean downstream, Object o) {
- if (o instanceof NetworkMessage) {
- NetworkMessage networkMessage = (NetworkMessage) o;
+ private LogRecord createLogRecord(Object msg) {
+ if (msg instanceof NetworkMessage) {
+ NetworkMessage networkMessage = (NetworkMessage) msg;
if (networkMessage.getMessage() instanceof ByteBuf) {
- log(ctx, downstream, networkMessage.getRemoteAddress(), (ByteBuf) networkMessage.getMessage());
+ LogRecord record = new LogRecord();
+ record.setAddress((InetSocketAddress) networkMessage.getRemoteAddress());
+ record.setProtocol(protocol);
+ record.setData(ByteBufUtil.hexDump((ByteBuf) networkMessage.getMessage()));
+ return record;
}
- } else if (o instanceof ByteBuf) {
- log(ctx, downstream, ctx.channel().remoteAddress(), (ByteBuf) o);
}
+ return null;
}
- public void log(ChannelHandlerContext ctx, boolean downstream, SocketAddress remoteAddress, ByteBuf buf) {
- StringBuilder message = new StringBuilder();
-
- message.append("[").append(NetworkUtil.session(ctx.channel())).append(": ");
- message.append(protocol);
- if (downstream) {
- message.append(" > ");
- } else {
- message.append(" < ");
+ private void log(ChannelHandlerContext ctx, boolean downstream, LogRecord record) {
+ if (record != null) {
+ StringBuilder message = new StringBuilder();
+ message.append("[").append(NetworkUtil.session(ctx.channel())).append(": ");
+ message.append(protocol);
+ message.append(downstream ? " > " : " < ");
+ message.append(record.getAddress().getHostString());
+ message.append("] ");
+ message.append(record.getData());
+ LOGGER.info(message.toString());
}
-
- if (remoteAddress instanceof InetSocketAddress) {
- message.append(((InetSocketAddress) remoteAddress).getHostString());
- } else {
- message.append("unknown");
- }
- message.append("] ");
-
- message.append(ByteBufUtil.hexDump(buf));
-
- LOGGER.info(message.toString());
}
}
diff --git a/src/main/java/org/traccar/handler/TimeHandler.java b/src/main/java/org/traccar/handler/TimeHandler.java
index c98b0bd4c..3c3e17450 100644
--- a/src/main/java/org/traccar/handler/TimeHandler.java
+++ b/src/main/java/org/traccar/handler/TimeHandler.java
@@ -23,8 +23,8 @@ import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Position;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
diff --git a/src/main/java/org/traccar/handler/events/AlertEventHandler.java b/src/main/java/org/traccar/handler/events/AlertEventHandler.java
index 9f77df989..531a0f957 100644
--- a/src/main/java/org/traccar/handler/events/AlertEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/AlertEventHandler.java
@@ -25,8 +25,8 @@ import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
@ChannelHandler.Sharable
diff --git a/src/main/java/org/traccar/handler/events/BaseEventHandler.java b/src/main/java/org/traccar/handler/events/BaseEventHandler.java
index 271aaa35d..4a4fb40ff 100644
--- a/src/main/java/org/traccar/handler/events/BaseEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/BaseEventHandler.java
@@ -22,7 +22,7 @@ import org.traccar.database.NotificationManager;
import org.traccar.model.Event;
import org.traccar.model.Position;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public abstract class BaseEventHandler extends BaseDataHandler {
diff --git a/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java b/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java
index 51bbd82d6..08ae35fcd 100644
--- a/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java
@@ -23,8 +23,8 @@ import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Collections;
import java.util.Map;
diff --git a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java
index 772176e9c..b70f8f33b 100644
--- a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java
@@ -22,8 +22,8 @@ import io.netty.channel.ChannelHandler;
import org.traccar.model.Event;
import org.traccar.model.Position;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
@ChannelHandler.Sharable
diff --git a/src/main/java/org/traccar/handler/events/DriverEventHandler.java b/src/main/java/org/traccar/handler/events/DriverEventHandler.java
index 51fdc0307..b68327983 100644
--- a/src/main/java/org/traccar/handler/events/DriverEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/DriverEventHandler.java
@@ -22,8 +22,8 @@ import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Collections;
import java.util.Map;
diff --git a/src/main/java/org/traccar/handler/events/FuelEventHandler.java b/src/main/java/org/traccar/handler/events/FuelEventHandler.java
index 462cc4223..e5085ecc2 100644
--- a/src/main/java/org/traccar/handler/events/FuelEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/FuelEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,8 @@ import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Map;
@Singleton
@@ -60,13 +60,13 @@ public class FuelEventHandler extends BaseEventHandler {
if (change > 0) {
double threshold = AttributeUtil.lookup(
cacheManager, Keys.EVENT_FUEL_INCREASE_THRESHOLD, position.getDeviceId());
- if (change >= threshold) {
+ if (threshold > 0 && change >= threshold) {
return Map.of(new Event(Event.TYPE_DEVICE_FUEL_INCREASE, position), position);
}
} else if (change < 0) {
double threshold = AttributeUtil.lookup(
cacheManager, Keys.EVENT_FUEL_DROP_THRESHOLD, position.getDeviceId());
- if (Math.abs(change) >= threshold) {
+ if (threshold > 0 && Math.abs(change) >= threshold) {
return Map.of(new Event(Event.TYPE_DEVICE_FUEL_DROP, position), position);
}
}
diff --git a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java
index 9414f4b31..dbe2b8118 100644
--- a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,24 +16,15 @@
package org.traccar.handler.events;
import io.netty.channel.ChannelHandler;
-import org.traccar.config.Config;
-import org.traccar.helper.model.GeofenceUtil;
import org.traccar.helper.model.PositionUtil;
import org.traccar.model.Calendar;
-import org.traccar.model.Device;
import org.traccar.model.Event;
import org.traccar.model.Geofence;
import org.traccar.model.Position;
-import org.traccar.session.ConnectionManager;
import org.traccar.session.cache.CacheManager;
-import org.traccar.storage.Storage;
-import org.traccar.storage.StorageException;
-import org.traccar.storage.query.Columns;
-import org.traccar.storage.query.Condition;
-import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -43,62 +34,43 @@ import java.util.Map;
@ChannelHandler.Sharable
public class GeofenceEventHandler extends BaseEventHandler {
- private final Config config;
private final CacheManager cacheManager;
- private final ConnectionManager connectionManager;
- private final Storage storage;
@Inject
- public GeofenceEventHandler(
- Config config, CacheManager cacheManager, ConnectionManager connectionManager, Storage storage) {
- this.config = config;
+ public GeofenceEventHandler(CacheManager cacheManager) {
this.cacheManager = cacheManager;
- this.connectionManager = connectionManager;
- this.storage = storage;
}
@Override
protected Map<Event, Position> analyzePosition(Position position) {
- Device device = cacheManager.getObject(Device.class, position.getDeviceId());
- if (device == null) {
- return null;
- }
- if (!PositionUtil.isLatest(cacheManager, position) || !position.getValid()) {
+ if (!PositionUtil.isLatest(cacheManager, position)) {
return null;
}
- List<Long> currentGeofences = GeofenceUtil.getCurrentGeofences(config, cacheManager, position);
List<Long> oldGeofences = new ArrayList<>();
- if (device.getGeofenceIds() != null) {
- oldGeofences.addAll(device.getGeofenceIds());
+ Position lastPosition = cacheManager.getPosition(position.getDeviceId());
+ if (lastPosition != null && lastPosition.getGeofenceIds() != null) {
+ oldGeofences.addAll(lastPosition.getGeofenceIds());
}
- List<Long> newGeofences = new ArrayList<>(currentGeofences);
- newGeofences.removeAll(oldGeofences);
- oldGeofences.removeAll(currentGeofences);
-
-
- if (!oldGeofences.isEmpty() || !newGeofences.isEmpty()) {
- device.setGeofenceIds(currentGeofences.isEmpty() ? null : currentGeofences);
-
- try {
- storage.updateObject(device, new Request(
- new Columns.Include("geofenceIds"),
- new Condition.Equals("id", device.getId())));
- } catch (StorageException e) {
- throw new RuntimeException("Update device geofences error", e);
- }
- connectionManager.updateDevice(true, device);
+ List<Long> newGeofences = new ArrayList<>();
+ if (position.getGeofenceIds() != null) {
+ newGeofences.addAll(position.getGeofenceIds());
+ newGeofences.removeAll(oldGeofences);
+ oldGeofences.removeAll(position.getGeofenceIds());
}
Map<Event, Position> events = new HashMap<>();
for (long geofenceId : oldGeofences) {
- long calendarId = cacheManager.getObject(Geofence.class, geofenceId).getCalendarId();
- Calendar calendar = calendarId != 0 ? cacheManager.getObject(Calendar.class, calendarId) : null;
- if (calendar == null || calendar.checkMoment(position.getFixTime())) {
- Event event = new Event(Event.TYPE_GEOFENCE_EXIT, position);
- event.setGeofenceId(geofenceId);
- events.put(event, position);
+ Geofence geofence = cacheManager.getObject(Geofence.class, geofenceId);
+ if (geofence != null) {
+ long calendarId = geofence.getCalendarId();
+ Calendar calendar = calendarId != 0 ? cacheManager.getObject(Calendar.class, calendarId) : null;
+ if (calendar == null || calendar.checkMoment(position.getFixTime())) {
+ Event event = new Event(Event.TYPE_GEOFENCE_EXIT, position);
+ event.setGeofenceId(geofenceId);
+ events.put(event, position);
+ }
}
}
for (long geofenceId : newGeofences) {
diff --git a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java
index b2e9a3325..ba4159a42 100644
--- a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java
@@ -26,8 +26,8 @@ import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
@ChannelHandler.Sharable
diff --git a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java
index 909950acf..2fa2e8869 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");
@@ -25,8 +25,8 @@ import org.traccar.model.Maintenance;
import org.traccar.model.Position;
import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
@ChannelHandler.Sharable
@@ -49,8 +49,8 @@ public class MaintenanceEventHandler extends BaseEventHandler {
Map<Event, Position> 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 @@ public class MaintenanceEventHandler extends BaseEventHandler {
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);
+ }
+ }
+
}
diff --git a/src/main/java/org/traccar/handler/events/MediaEventHandler.java b/src/main/java/org/traccar/handler/events/MediaEventHandler.java
index a49e08e8d..52d8e6961 100644
--- a/src/main/java/org/traccar/handler/events/MediaEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/MediaEventHandler.java
@@ -19,8 +19,8 @@ import io.netty.channel.ChannelHandler;
import org.traccar.model.Event;
import org.traccar.model.Position;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
diff --git a/src/main/java/org/traccar/handler/events/MotionEventHandler.java b/src/main/java/org/traccar/handler/events/MotionEventHandler.java
index c406bd935..15902d6d4 100644
--- a/src/main/java/org/traccar/handler/events/MotionEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/MotionEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
* Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,6 +19,8 @@ package org.traccar.handler.events;
import io.netty.channel.ChannelHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.traccar.config.Keys;
+import org.traccar.helper.model.AttributeUtil;
import org.traccar.helper.model.PositionUtil;
import org.traccar.model.Device;
import org.traccar.model.Event;
@@ -33,8 +35,8 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Collections;
import java.util.Map;
@@ -46,14 +48,11 @@ public class MotionEventHandler extends BaseEventHandler {
private final CacheManager cacheManager;
private final Storage storage;
- private final TripsConfig tripsConfig;
@Inject
- public MotionEventHandler(
- CacheManager cacheManager, Storage storage, TripsConfig tripsConfig) {
+ public MotionEventHandler(CacheManager cacheManager, Storage storage) {
this.cacheManager = cacheManager;
this.storage = storage;
- this.tripsConfig = tripsConfig;
}
@Override
@@ -61,14 +60,16 @@ public class MotionEventHandler extends BaseEventHandler {
long deviceId = position.getDeviceId();
Device device = cacheManager.getObject(Device.class, deviceId);
- if (device == null) {
+ if (device == null || !PositionUtil.isLatest(cacheManager, position)) {
return null;
}
- if (!PositionUtil.isLatest(cacheManager, position)
- || !tripsConfig.getProcessInvalidPositions() && !position.getValid()) {
+ boolean processInvalid = AttributeUtil.lookup(
+ cacheManager, Keys.EVENT_MOTION_PROCESS_INVALID_POSITIONS, deviceId);
+ if (!processInvalid && !position.getValid()) {
return null;
}
+ TripsConfig tripsConfig = new TripsConfig(new AttributeUtil.CacheProvider(cacheManager, deviceId));
MotionState state = MotionState.fromDevice(device);
MotionProcessor.updateState(state, position, position.getBoolean(Position.KEY_MOTION), tripsConfig);
if (state.isChanged()) {
diff --git a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java
index 4d6aa8857..3bb5f713c 100644
--- a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java
+++ b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -36,8 +36,8 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Collections;
import java.util.Map;
@@ -52,6 +52,7 @@ public class OverspeedEventHandler extends BaseEventHandler {
private final long minimalDuration;
private final boolean preferLowest;
+ private final double multiplier;
@Inject
public OverspeedEventHandler(
@@ -60,6 +61,7 @@ public class OverspeedEventHandler extends BaseEventHandler {
this.storage = storage;
minimalDuration = config.getLong(Keys.EVENT_OVERSPEED_MINIMAL_DURATION) * 1000;
preferLowest = config.getBoolean(Keys.EVENT_OVERSPEED_PREFER_LOWEST);
+ multiplier = config.getDouble(Keys.EVENT_OVERSPEED_THRESHOLD_MULTIPLIER);
}
@Override
@@ -84,8 +86,8 @@ public class OverspeedEventHandler extends BaseEventHandler {
double geofenceSpeedLimit = 0;
long overspeedGeofenceId = 0;
- if (device.getGeofenceIds() != null) {
- for (long geofenceId : device.getGeofenceIds()) {
+ if (position.getGeofenceIds() != null) {
+ for (long geofenceId : position.getGeofenceIds()) {
Geofence geofence = cacheManager.getObject(Geofence.class, geofenceId);
if (geofence != null) {
double currentSpeedLimit = geofence.getDouble(Keys.EVENT_OVERSPEED_LIMIT.getKey());
@@ -107,7 +109,7 @@ public class OverspeedEventHandler extends BaseEventHandler {
}
OverspeedState state = OverspeedState.fromDevice(device);
- OverspeedProcessor.updateState(state, position, speedLimit, minimalDuration, overspeedGeofenceId);
+ OverspeedProcessor.updateState(state, position, speedLimit, multiplier, minimalDuration, overspeedGeofenceId);
if (state.isChanged()) {
state.toDevice(device);
try {
diff --git a/src/main/java/org/traccar/helper/BufferUtil.java b/src/main/java/org/traccar/helper/BufferUtil.java
index d1025f548..b453c437f 100644
--- a/src/main/java/org/traccar/helper/BufferUtil.java
+++ b/src/main/java/org/traccar/helper/BufferUtil.java
@@ -71,4 +71,20 @@ public final class BufferUtil {
}
}
+ public static boolean isPrintable(ByteBuf buf, int length) {
+ boolean printable = true;
+ for (int i = 0; i < length; i++) {
+ byte b = buf.getByte(buf.readerIndex() + i);
+ if (b < 32 && b != '\r' && b != '\n') {
+ printable = false;
+ break;
+ }
+ }
+ 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/helper/ObdDecoder.java b/src/main/java/org/traccar/helper/ObdDecoder.java
index b22065f4e..3cbae334a 100644
--- a/src/main/java/org/traccar/helper/ObdDecoder.java
+++ b/src/main/java/org/traccar/helper/ObdDecoder.java
@@ -51,22 +51,7 @@ public final class ObdDecoder {
StringBuilder codes = new StringBuilder();
for (int i = 0; i < value.length() / 4; i++) {
int numValue = Integer.parseInt(value.substring(i * 4, (i + 1) * 4), 16);
- codes.append(' ');
- switch (numValue >> 14) {
- case 1:
- codes.append('C');
- break;
- case 2:
- codes.append('B');
- break;
- case 3:
- codes.append('U');
- break;
- default:
- codes.append('P');
- break;
- }
- codes.append(String.format("%04X", numValue & 0x3FFF));
+ codes.append(' ').append(decodeCode(numValue));
}
if (codes.length() > 0) {
return createEntry(Position.KEY_DTCS, codes.toString().trim());
@@ -75,6 +60,25 @@ public final class ObdDecoder {
}
}
+ public static String decodeCode(int value) {
+ char prefix;
+ switch (value >> 14) {
+ case 1:
+ prefix = 'C';
+ break;
+ case 2:
+ prefix = 'B';
+ break;
+ case 3:
+ prefix = 'U';
+ break;
+ default:
+ prefix = 'P';
+ break;
+ }
+ return String.format("%c%04X", prefix, value & 0x3FFF);
+ }
+
public static Map.Entry<String, Object> decodeData(int pid, long value, boolean convert) {
switch (pid) {
case 0x04:
diff --git a/src/main/java/org/traccar/helper/ObjectMapperContextResolver.java b/src/main/java/org/traccar/helper/ObjectMapperContextResolver.java
index b40e30d76..634950b85 100644
--- a/src/main/java/org/traccar/helper/ObjectMapperContextResolver.java
+++ b/src/main/java/org/traccar/helper/ObjectMapperContextResolver.java
@@ -17,8 +17,8 @@ package org.traccar.helper;
import com.fasterxml.jackson.databind.ObjectMapper;
-import javax.inject.Inject;
-import javax.ws.rs.ext.ContextResolver;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.ext.ContextResolver;
// This does not work as a lambda
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
diff --git a/src/main/java/org/traccar/helper/Parser.java b/src/main/java/org/traccar/helper/Parser.java
index aa39e1ad7..c2aea28fa 100644
--- a/src/main/java/org/traccar/helper/Parser.java
+++ b/src/main/java/org/traccar/helper/Parser.java
@@ -50,6 +50,17 @@ public class Parser {
public boolean hasNext(int number) {
for (int i = position; i < position + number; i++) {
String value = matcher.group(i);
+ if (value == null || value.isEmpty()) {
+ position += number;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean hasNextAny(int number) {
+ for (int i = position; i < position + number; i++) {
+ String value = matcher.group(i);
if (value != null && !value.isEmpty()) {
return true;
}
diff --git a/src/main/java/org/traccar/helper/ServletHelper.java b/src/main/java/org/traccar/helper/WebHelper.java
index b6c587ec3..9533fe84b 100644
--- a/src/main/java/org/traccar/helper/ServletHelper.java
+++ b/src/main/java/org/traccar/helper/WebHelper.java
@@ -15,11 +15,18 @@
*/
package org.traccar.helper;
-import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
-public final class ServletHelper {
+import jakarta.servlet.http.HttpServletRequest;
- private ServletHelper() {
+import org.eclipse.jetty.util.URIUtil;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+
+public final class WebHelper {
+
+ private WebHelper() {
}
public static String retrieveRemoteAddress(HttpServletRequest request) {
@@ -42,4 +49,17 @@ public final class ServletHelper {
}
}
+ public static String retrieveWebUrl(Config config) {
+ if (config.hasKey(Keys.WEB_URL)) {
+ return config.getString(Keys.WEB_URL).replaceAll("/$", "");
+ } else {
+ String address;
+ try {
+ address = config.getString(Keys.WEB_ADDRESS, InetAddress.getLocalHost().getHostAddress());
+ } catch (UnknownHostException e) {
+ address = "localhost";
+ }
+ return URIUtil.newURI("http", address, config.getInteger(Keys.WEB_PORT), "", "");
+ }
+ }
}
diff --git a/src/main/java/org/traccar/helper/model/AttributeUtil.java b/src/main/java/org/traccar/helper/model/AttributeUtil.java
index 43558e8f7..2630f64f0 100644
--- a/src/main/java/org/traccar/helper/model/AttributeUtil.java
+++ b/src/main/java/org/traccar/helper/model/AttributeUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,25 +15,44 @@
*/
package org.traccar.helper.model;
+import org.traccar.api.security.PermissionsService;
+import org.traccar.config.Config;
import org.traccar.config.ConfigKey;
import org.traccar.config.KeyType;
import org.traccar.config.Keys;
import org.traccar.model.Device;
import org.traccar.model.Group;
+import org.traccar.model.Server;
import org.traccar.session.cache.CacheManager;
+import org.traccar.storage.Storage;
+import org.traccar.storage.StorageException;
+import org.traccar.storage.query.Columns;
+import org.traccar.storage.query.Condition;
+import org.traccar.storage.query.Request;
public final class AttributeUtil {
private AttributeUtil() {
}
- @SuppressWarnings({ "deprecation", "unchecked" })
+ public interface Provider {
+ Device getDevice();
+ Group getGroup(long groupId);
+ Server getServer();
+ Config getConfig();
+ }
+
public static <T> T lookup(CacheManager cacheManager, ConfigKey<T> key, long deviceId) {
- Device device = cacheManager.getObject(Device.class, deviceId);
+ return lookup(new CacheProvider(cacheManager, deviceId), key);
+ }
+
+ @SuppressWarnings({ "deprecation", "unchecked" })
+ public static <T> T lookup(Provider provider, ConfigKey<T> key) {
+ Device device = provider.getDevice();
Object result = device.getAttributes().get(key.getKey());
long groupId = device.getGroupId();
while (result == null && groupId > 0) {
- Group group = cacheManager.getObject(Group.class, groupId);
+ Group group = provider.getGroup(groupId);
if (group != null) {
result = group.getAttributes().get(key.getKey());
groupId = group.getGroupId();
@@ -42,10 +61,10 @@ public final class AttributeUtil {
}
}
if (result == null && key.hasType(KeyType.SERVER)) {
- result = cacheManager.getServer().getAttributes().get(key.getKey());
+ result = provider.getServer().getAttributes().get(key.getKey());
}
if (result == null && key.hasType(KeyType.CONFIG)) {
- result = cacheManager.getConfig().getString(key.getKey());
+ result = provider.getConfig().getString(key.getKey());
}
if (result != null) {
@@ -91,4 +110,79 @@ public final class AttributeUtil {
return defaultPassword;
}
+ public static class CacheProvider implements Provider {
+
+ private final CacheManager cacheManager;
+ private final long deviceId;
+
+ public CacheProvider(CacheManager cacheManager, long deviceId) {
+ this.cacheManager = cacheManager;
+ this.deviceId = deviceId;
+ }
+
+ @Override
+ public Device getDevice() {
+ return cacheManager.getObject(Device.class, deviceId);
+ }
+
+ @Override
+ public Group getGroup(long groupId) {
+ return cacheManager.getObject(Group.class, groupId);
+ }
+
+ @Override
+ public Server getServer() {
+ return cacheManager.getServer();
+ }
+
+ @Override
+ public Config getConfig() {
+ return cacheManager.getConfig();
+ }
+ }
+
+ public static class StorageProvider implements Provider {
+
+ private final Config config;
+ private final Storage storage;
+ private final PermissionsService permissionsService;
+ private final Device device;
+
+ public StorageProvider(Config config, Storage storage, PermissionsService permissionsService, Device device) {
+ this.config = config;
+ this.storage = storage;
+ this.permissionsService = permissionsService;
+ this.device = device;
+ }
+
+ @Override
+ public Device getDevice() {
+ return device;
+ }
+
+ @Override
+ public Group getGroup(long groupId) {
+ try {
+ return storage.getObject(
+ Group.class, new Request(new Columns.All(), new Condition.Equals("id", groupId)));
+ } catch (StorageException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Server getServer() {
+ try {
+ return permissionsService.getServer();
+ } catch (StorageException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Config getConfig() {
+ return config;
+ }
+ }
+
}
diff --git a/src/main/java/org/traccar/helper/model/DeviceUtil.java b/src/main/java/org/traccar/helper/model/DeviceUtil.java
index 597078caf..5d8cb5f25 100644
--- a/src/main/java/org/traccar/helper/model/DeviceUtil.java
+++ b/src/main/java/org/traccar/helper/model/DeviceUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,20 @@
package org.traccar.helper.model;
import org.traccar.model.Device;
+import org.traccar.model.Group;
+import org.traccar.model.User;
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.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
public final class DeviceUtil {
private DeviceUtil() {
@@ -30,4 +39,41 @@ public final class DeviceUtil {
storage.updateObject(new Device(), new Request(new Columns.Include("status")));
}
+
+ public static Collection<Device> getAccessibleDevices(
+ Storage storage, long userId,
+ Collection<Long> deviceIds, Collection<Long> groupIds) throws StorageException {
+
+ var devices = storage.getObjects(Device.class, new Request(
+ new Columns.All(),
+ new Condition.Permission(User.class, userId, Device.class)));
+ var deviceById = devices.stream()
+ .collect(Collectors.toUnmodifiableMap(Device::getId, x -> x));
+ var devicesByGroup = devices.stream()
+ .filter(x -> x.getGroupId() > 0)
+ .collect(Collectors.groupingBy(Device::getGroupId));
+
+ var groups = storage.getObjects(Group.class, new Request(
+ new Columns.All(),
+ new Condition.Permission(User.class, userId, Group.class)));
+ var groupsByGroup = groups.stream()
+ .filter(x -> x.getGroupId() > 0)
+ .collect(Collectors.groupingBy(Group::getGroupId));
+
+ var results = deviceIds.stream()
+ .map(deviceById::get)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
+ var groupQueue = new LinkedList<>(groupIds);
+ while (!groupQueue.isEmpty()) {
+ long groupId = groupQueue.pop();
+ results.addAll(devicesByGroup.getOrDefault(groupId, Collections.emptyList()));
+ groupQueue.addAll(groupsByGroup.getOrDefault(groupId, Collections.emptyList())
+ .stream().map(Group::getId).collect(Collectors.toUnmodifiableList()));
+ }
+
+ return results;
+ }
+
}
diff --git a/src/main/java/org/traccar/helper/model/UserUtil.java b/src/main/java/org/traccar/helper/model/UserUtil.java
index 9f93afeae..4b1c404f9 100644
--- a/src/main/java/org/traccar/helper/model/UserUtil.java
+++ b/src/main/java/org/traccar/helper/model/UserUtil.java
@@ -15,6 +15,8 @@
*/
package org.traccar.helper.model;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
import org.traccar.model.Server;
import org.traccar.model.User;
import org.traccar.storage.Storage;
@@ -23,6 +25,7 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Order;
import org.traccar.storage.query.Request;
+import java.util.Date;
import java.util.TimeZone;
public final class UserUtil {
@@ -65,4 +68,11 @@ public final class UserUtil {
return preference != null ? preference : defaultValue;
}
+ public static void setUserDefaults(User user, Config config) {
+ user.setDeviceLimit(config.getInteger(Keys.USERS_DEFAULT_DEVICE_LIMIT));
+ int expirationDays = config.getInteger(Keys.USERS_DEFAULT_EXPIRATION_DAYS);
+ if (expirationDays > 0) {
+ user.setExpirationTime(new Date(System.currentTimeMillis() + expirationDays * 86400000L));
+ }
+ }
}
diff --git a/src/main/java/org/traccar/mail/LogMailManager.java b/src/main/java/org/traccar/mail/LogMailManager.java
index b6b912d6c..90de3bcce 100644
--- a/src/main/java/org/traccar/mail/LogMailManager.java
+++ b/src/main/java/org/traccar/mail/LogMailManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.model.User;
-import javax.mail.MessagingException;
-import javax.mail.internet.MimeBodyPart;
+import jakarta.mail.MessagingException;
+import jakarta.mail.internet.MimeBodyPart;
public class LogMailManager implements MailManager {
@@ -32,13 +32,17 @@ public class LogMailManager implements MailManager {
}
@Override
- public void sendMessage(User user, String subject, String body) throws MessagingException {
- sendMessage(user, subject, body, null);
+ public void sendMessage(
+ User user, boolean system, String subject, String body) throws MessagingException {
+ sendMessage(user, system, subject, body, null);
}
@Override
- public void sendMessage(User user, String subject, String body, MimeBodyPart attachment) throws MessagingException {
- LOGGER.info("\nTo: " + user.getEmail() + "\nSubject: " + subject + "\nBody:\n" + body);
+ public void sendMessage(
+ User user, boolean system, String subject, String body, MimeBodyPart attachment) throws MessagingException {
+ LOGGER.info(
+ "Email sent\nTo: {}\nSubject: {}\nAttachment: {}\nBody:\n{}",
+ user.getEmail(), subject, attachment != null ? attachment.getFileName() : null, body);
}
}
diff --git a/src/main/java/org/traccar/mail/MailManager.java b/src/main/java/org/traccar/mail/MailManager.java
index 69efbed32..d05a07de9 100644
--- a/src/main/java/org/traccar/mail/MailManager.java
+++ b/src/main/java/org/traccar/mail/MailManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,15 +17,17 @@ package org.traccar.mail;
import org.traccar.model.User;
-import javax.mail.MessagingException;
-import javax.mail.internet.MimeBodyPart;
+import jakarta.mail.MessagingException;
+import jakarta.mail.internet.MimeBodyPart;
public interface MailManager {
boolean getEmailEnabled();
- void sendMessage(User user, String subject, String body) throws MessagingException;
+ void sendMessage(
+ User user, boolean system, String subject, String body) throws MessagingException;
- void sendMessage(User user, String subject, String body, MimeBodyPart attachment) throws MessagingException;
+ void sendMessage(
+ User user, boolean system, String subject, String body, MimeBodyPart attachment) throws MessagingException;
}
diff --git a/src/main/java/org/traccar/mail/SmtpMailManager.java b/src/main/java/org/traccar/mail/SmtpMailManager.java
index 4a0b7048f..70099d879 100644
--- a/src/main/java/org/traccar/mail/SmtpMailManager.java
+++ b/src/main/java/org/traccar/mail/SmtpMailManager.java
@@ -23,16 +23,16 @@ import org.traccar.database.StatisticsManager;
import org.traccar.model.User;
import org.traccar.notification.PropertiesProvider;
-import javax.mail.BodyPart;
-import javax.mail.Message;
-import javax.mail.MessagingException;
-import javax.mail.Multipart;
-import javax.mail.Session;
-import javax.mail.Transport;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
+import jakarta.mail.BodyPart;
+import jakarta.mail.Message;
+import jakarta.mail.MessagingException;
+import jakarta.mail.Multipart;
+import jakarta.mail.Session;
+import jakarta.mail.Transport;
+import jakarta.mail.internet.InternetAddress;
+import jakarta.mail.internet.MimeBodyPart;
+import jakarta.mail.internet.MimeMessage;
+import jakarta.mail.internet.MimeMultipart;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;
@@ -93,19 +93,21 @@ public final class SmtpMailManager implements MailManager {
return config.hasKey(Keys.MAIL_SMTP_HOST);
}
+ @Override
public void sendMessage(
- User user, String subject, String body) throws MessagingException {
- sendMessage(user, subject, body, null);
+ User user, boolean system, String subject, String body) throws MessagingException {
+ sendMessage(user, system, subject, body, null);
}
+ @Override
public void sendMessage(
- User user, String subject, String body, MimeBodyPart attachment) throws MessagingException {
+ User user, boolean system, String subject, String body, MimeBodyPart attachment) throws MessagingException {
Properties properties = null;
if (!config.getBoolean(Keys.MAIL_SMTP_IGNORE_USER_CONFIG)) {
properties = getProperties(new PropertiesProvider(user));
}
- if (properties == null) {
+ if (properties == null && (system || !config.getBoolean(Keys.MAIL_SMTP_SYSTEM_ONLY))) {
properties = getProperties(new PropertiesProvider(config));
}
if (properties == null) {
diff --git a/src/main/java/org/traccar/model/Calendar.java b/src/main/java/org/traccar/model/Calendar.java
index 62c51cc4a..03f1995ba 100644
--- a/src/main/java/org/traccar/model/Calendar.java
+++ b/src/main/java/org/traccar/model/Calendar.java
@@ -34,6 +34,7 @@ import java.time.Duration;
import java.util.Collection;
import java.util.Date;
import java.util.List;
+import java.util.stream.Collectors;
@StorageName("tc_calendars")
public class Calendar extends ExtendedModel {
@@ -68,16 +69,22 @@ public class Calendar extends ExtendedModel {
return calendar;
}
- public Collection<VEvent> findEvents(Date date) {
+ private Collection<VEvent> findEvents(Date date) {
if (calendar != null) {
- Period period = new Period(new DateTime(date), Duration.ZERO);
- Filter<VEvent> filter = new Filter<>(new PeriodRule<>(period));
+ var filter = new Filter<VEvent>(new PeriodRule<>(new Period(new DateTime(date), Duration.ZERO)));
return filter.filter(calendar.getComponents(CalendarComponent.VEVENT));
} else {
return List.of();
}
}
+ public Collection<Period> findPeriods(Date date) {
+ var calendarDate = new net.fortuna.ical4j.model.Date(date);
+ return findEvents(date).stream()
+ .flatMap((event) -> event.getConsumedTime(calendarDate, calendarDate).stream())
+ .collect(Collectors.toSet());
+ }
+
public boolean checkMoment(Date date) {
return !findEvents(date).isEmpty();
}
diff --git a/src/main/java/org/traccar/model/CellTower.java b/src/main/java/org/traccar/model/CellTower.java
index 355594c64..4277cc4c4 100644
--- a/src/main/java/org/traccar/model/CellTower.java
+++ b/src/main/java/org/traccar/model/CellTower.java
@@ -104,7 +104,7 @@ public class CellTower {
}
public void setSignalStrength(Integer signalStrength) {
- this.signalStrength = signalStrength;
+ this.signalStrength = signalStrength > 0 ? -signalStrength : signalStrength;
}
public void setOperator(long operator) {
diff --git a/src/main/java/org/traccar/model/Device.java b/src/main/java/org/traccar/model/Device.java
index b8c87921d..a3088a613 100644
--- a/src/main/java/org/traccar/model/Device.java
+++ b/src/main/java/org/traccar/model/Device.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,16 +15,26 @@
*/
package org.traccar.model;
-import java.util.Date;
-import java.util.List;
-import java.util.stream.Collectors;
-
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.traccar.storage.QueryIgnore;
import org.traccar.storage.StorageName;
+import java.util.Date;
+
@StorageName("tc_devices")
-public class Device extends GroupedModel implements Disableable {
+public class Device extends GroupedModel implements Disableable, Schedulable {
+
+ private long calendarId;
+
+ @Override
+ public long getCalendarId() {
+ return calendarId;
+ }
+
+ @Override
+ public void setCalendarId(long calendarId) {
+ this.calendarId = calendarId;
+ }
private String name;
@@ -43,7 +53,10 @@ public class Device extends GroupedModel implements Disableable {
}
public void setUniqueId(String uniqueId) {
- this.uniqueId = uniqueId;
+ if (uniqueId.contains("../") || uniqueId.contains("..\\")) {
+ throw new IllegalArgumentException("Invalid unique id");
+ }
+ this.uniqueId = uniqueId.trim();
}
public static final String STATUS_UNKNOWN = "unknown";
@@ -83,21 +96,6 @@ public class Device extends GroupedModel implements Disableable {
this.positionId = positionId;
}
- private List<Long> geofenceIds;
-
- @QueryIgnore
- public List<Long> getGeofenceIds() {
- return geofenceIds;
- }
-
- public void setGeofenceIds(List<? extends Number> geofenceIds) {
- if (geofenceIds != null) {
- this.geofenceIds = geofenceIds.stream().map(Number::longValue).collect(Collectors.toList());
- } else {
- this.geofenceIds = null;
- }
- }
-
private String phone;
public String getPhone() {
@@ -105,7 +103,7 @@ public class Device extends GroupedModel implements Disableable {
}
public void setPhone(String phone) {
- this.phone = phone;
+ this.phone = phone != null ? phone.trim() : null;
}
private String model;
diff --git a/src/main/java/org/traccar/model/Driver.java b/src/main/java/org/traccar/model/Driver.java
index b9e023088..ca5714e51 100644
--- a/src/main/java/org/traccar/model/Driver.java
+++ b/src/main/java/org/traccar/model/Driver.java
@@ -38,7 +38,7 @@ public class Driver extends ExtendedModel {
}
public void setUniqueId(String uniqueId) {
- this.uniqueId = uniqueId;
+ this.uniqueId = uniqueId.trim();
}
}
diff --git a/src/main/java/org/traccar/model/Event.java b/src/main/java/org/traccar/model/Event.java
index 0e851d748..6f90de9da 100644
--- a/src/main/java/org/traccar/model/Event.java
+++ b/src/main/java/org/traccar/model/Event.java
@@ -46,6 +46,7 @@ public class Event extends Message {
public static final String TYPE_DEVICE_UNKNOWN = "deviceUnknown";
public static final String TYPE_DEVICE_OFFLINE = "deviceOffline";
public static final String TYPE_DEVICE_INACTIVE = "deviceInactive";
+ public static final String TYPE_QUEUED_COMMAND_SENT = "queuedCommandSent";
public static final String TYPE_DEVICE_MOVING = "deviceMoving";
public static final String TYPE_DEVICE_STOPPED = "deviceStopped";
diff --git a/src/main/java/org/traccar/model/ExtendedModel.java b/src/main/java/org/traccar/model/ExtendedModel.java
index 7a61eda8c..d5cd094da 100644
--- a/src/main/java/org/traccar/model/ExtendedModel.java
+++ b/src/main/java/org/traccar/model/ExtendedModel.java
@@ -89,14 +89,19 @@ public class ExtendedModel extends BaseModel {
}
}
- public String getString(String key) {
+ public String getString(String key, String defaultValue) {
if (attributes.containsKey(key)) {
- return attributes.get(key).toString();
+ Object value = attributes.get(key);
+ return value != null ? value.toString() : null;
} else {
- return null;
+ return defaultValue;
}
}
+ public String getString(String key) {
+ return getString(key, null);
+ }
+
public double getDouble(String key) {
if (attributes.containsKey(key)) {
Object value = attributes.get(key);
diff --git a/src/main/java/org/traccar/model/Geofence.java b/src/main/java/org/traccar/model/Geofence.java
index 9259028fb..ca6293651 100644
--- a/src/main/java/org/traccar/model/Geofence.java
+++ b/src/main/java/org/traccar/model/Geofence.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,7 +26,19 @@ import org.traccar.storage.StorageName;
import java.text.ParseException;
@StorageName("tc_geofences")
-public class Geofence extends ScheduledModel {
+public class Geofence extends ExtendedModel implements Schedulable {
+
+ private long calendarId;
+
+ @Override
+ public long getCalendarId() {
+ return calendarId;
+ }
+
+ @Override
+ public void setCalendarId(long calendarId) {
+ this.calendarId = calendarId;
+ }
private String name;
diff --git a/src/main/java/org/traccar/model/LogRecord.java b/src/main/java/org/traccar/model/LogRecord.java
new file mode 100644
index 000000000..c19163af3
--- /dev/null
+++ b/src/main/java/org/traccar/model/LogRecord.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import java.net.InetSocketAddress;
+
+public class LogRecord {
+
+ private InetSocketAddress address;
+
+ public void setAddress(InetSocketAddress address) {
+ this.address = address;
+ }
+
+ @JsonIgnore
+ public InetSocketAddress getAddress() {
+ return address;
+ }
+
+ public String getHost() {
+ return address.getHostString();
+ }
+
+ private String protocol;
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ private String uniqueId;
+
+ public String getUniqueId() {
+ return uniqueId;
+ }
+
+ public void setUniqueId(String uniqueId) {
+ this.uniqueId = uniqueId;
+ }
+
+ private long deviceId;
+
+ public long getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(long deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ private String data;
+
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+
+}
diff --git a/src/main/java/org/traccar/model/Notification.java b/src/main/java/org/traccar/model/Notification.java
index 95e446132..6dcd9c9de 100644
--- a/src/main/java/org/traccar/model/Notification.java
+++ b/src/main/java/org/traccar/model/Notification.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,19 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import org.traccar.storage.StorageName;
@StorageName("tc_notifications")
-public class Notification extends ScheduledModel {
+public class Notification extends ExtendedModel implements Schedulable {
+
+ private long calendarId;
+
+ @Override
+ public long getCalendarId() {
+ return calendarId;
+ }
+
+ @Override
+ public void setCalendarId(long calendarId) {
+ this.calendarId = calendarId;
+ }
private boolean always;
@@ -46,6 +58,16 @@ public class Notification extends ScheduledModel {
this.type = type;
}
+ private long commandId;
+
+ public long getCommandId() {
+ return commandId;
+ }
+
+ public void setCommandId(long commandId) {
+ this.commandId = commandId;
+ }
+
private String notificators;
public String getNotificators() {
diff --git a/src/main/java/org/traccar/model/ObjectOperation.java b/src/main/java/org/traccar/model/ObjectOperation.java
new file mode 100644
index 000000000..b462580bb
--- /dev/null
+++ b/src/main/java/org/traccar/model/ObjectOperation.java
@@ -0,0 +1,7 @@
+package org.traccar.model;
+
+public enum ObjectOperation {
+ ADD,
+ UPDATE,
+ DELETE,
+}
diff --git a/src/main/java/org/traccar/model/Position.java b/src/main/java/org/traccar/model/Position.java
index 1286db5f2..39f63217d 100644
--- a/src/main/java/org/traccar/model/Position.java
+++ b/src/main/java/org/traccar/model/Position.java
@@ -16,6 +16,8 @@
package org.traccar.model;
import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.traccar.storage.QueryIgnore;
@@ -40,7 +42,7 @@ public class Position extends Message {
public static final String KEY_ODOMETER = "odometer"; // meters
public static final String KEY_ODOMETER_SERVICE = "serviceOdometer"; // meters
public static final String KEY_ODOMETER_TRIP = "tripOdometer"; // meters
- public static final String KEY_HOURS = "hours";
+ public static final String KEY_HOURS = "hours"; // milliseconds
public static final String KEY_STEPS = "steps";
public static final String KEY_HEART_RATE = "heartRate";
public static final String KEY_INPUT = "input";
@@ -83,20 +85,23 @@ public class Position extends Message {
public static final String KEY_OPERATOR = "operator";
public static final String KEY_COMMAND = "command";
public static final String KEY_BLOCKED = "blocked";
+ public static final String KEY_LOCK = "lock";
public static final String KEY_DOOR = "door";
public static final String KEY_AXLE_WEIGHT = "axleWeight";
public static final String KEY_G_SENSOR = "gSensor";
public static final String KEY_ICCID = "iccid";
public static final String KEY_PHONE = "phone";
public static final String KEY_SPEED_LIMIT = "speedLimit";
+ public static final String KEY_DRIVING_TIME = "drivingTime";
public static final String KEY_DTCS = "dtcs";
- public static final String KEY_OBD_SPEED = "obdSpeed"; // knots
+ public static final String KEY_OBD_SPEED = "obdSpeed"; // km/h
public static final String KEY_OBD_ODOMETER = "obdOdometer"; // meters
public static final String KEY_RESULT = "result";
public static final String KEY_DRIVER_UNIQUE_ID = "driverUniqueId";
+ public static final String KEY_CARD = "card";
// Start with 1 not 0
public static final String PREFIX_TEMP = "temp";
@@ -306,6 +311,20 @@ public class Position extends Message {
this.network = network;
}
+ private List<Long> geofenceIds;
+
+ public List<Long> getGeofenceIds() {
+ return geofenceIds;
+ }
+
+ public void setGeofenceIds(List<? extends Number> geofenceIds) {
+ if (geofenceIds != null) {
+ this.geofenceIds = geofenceIds.stream().map(Number::longValue).collect(Collectors.toList());
+ } else {
+ this.geofenceIds = null;
+ }
+ }
+
@JsonIgnore
@QueryIgnore
@Override
diff --git a/src/main/java/org/traccar/model/Report.java b/src/main/java/org/traccar/model/Report.java
index 1556ecc9e..2ee7ae288 100644
--- a/src/main/java/org/traccar/model/Report.java
+++ b/src/main/java/org/traccar/model/Report.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,19 @@ package org.traccar.model;
import org.traccar.storage.StorageName;
@StorageName("tc_reports")
-public class Report extends ScheduledModel {
+public class Report extends ExtendedModel implements Schedulable {
+
+ private long calendarId;
+
+ @Override
+ public long getCalendarId() {
+ return calendarId;
+ }
+
+ @Override
+ public void setCalendarId(long calendarId) {
+ this.calendarId = calendarId;
+ }
private String type;
diff --git a/src/main/java/org/traccar/model/ScheduledModel.java b/src/main/java/org/traccar/model/Schedulable.java
index 9e6a4b9a6..331e77583 100644
--- a/src/main/java/org/traccar/model/ScheduledModel.java
+++ b/src/main/java/org/traccar/model/Schedulable.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2023 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +16,7 @@
*/
package org.traccar.model;
-public class ScheduledModel extends ExtendedModel {
-
- private long calendarId;
-
- public long getCalendarId() {
- return calendarId;
- }
-
- public void setCalendarId(long calendarId) {
- this.calendarId = calendarId;
- }
+public interface Schedulable {
+ long getCalendarId();
+ void setCalendarId(long calendarId);
}
diff --git a/src/main/java/org/traccar/model/Server.java b/src/main/java/org/traccar/model/Server.java
index 73645721b..6442186b6 100644
--- a/src/main/java/org/traccar/model/Server.java
+++ b/src/main/java/org/traccar/model/Server.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.traccar.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
import org.traccar.storage.QueryIgnore;
import org.traccar.storage.StorageName;
@@ -227,6 +228,18 @@ public class Server extends ExtendedModel implements UserRestrictions {
private boolean geocoderEnabled;
+ private boolean textEnabled;
+
+ @QueryIgnore
+ public void setTextEnabled(boolean textEnabled) {
+ this.textEnabled = textEnabled;
+ }
+
+ @QueryIgnore
+ public Boolean getTextEnabled() {
+ return textEnabled;
+ }
+
@QueryIgnore
public void setGeocoderEnabled(boolean geocoderEnabled) {
this.geocoderEnabled = geocoderEnabled;
@@ -261,4 +274,27 @@ public class Server extends ExtendedModel implements UserRestrictions {
this.newServer = newServer;
}
+ private boolean openIdEnabled;
+
+ @QueryIgnore
+ public boolean getOpenIdEnabled() {
+ return openIdEnabled;
+ }
+
+ @QueryIgnore
+ public void setOpenIdEnabled(boolean openIdEnabled) {
+ this.openIdEnabled = openIdEnabled;
+ }
+
+ private boolean openIdForce;
+
+ @QueryIgnore
+ public boolean getOpenIdForce() {
+ return openIdForce;
+ }
+
+ @QueryIgnore
+ public void setOpenIdForce(boolean openIdForce) {
+ this.openIdForce = openIdForce;
+ }
}
diff --git a/src/main/java/org/traccar/model/User.java b/src/main/java/org/traccar/model/User.java
index 53594fe07..8cfee0f48 100644
--- a/src/main/java/org/traccar/model/User.java
+++ b/src/main/java/org/traccar/model/User.java
@@ -63,7 +63,7 @@ public class User extends ExtendedModel implements UserRestrictions, Disableable
}
public void setPhone(String phone) {
- this.phone = phone;
+ this.phone = phone != null ? phone.trim() : null;
}
private boolean readonly;
@@ -251,6 +251,26 @@ public class User extends ExtendedModel implements UserRestrictions, Disableable
this.poiLayer = poiLayer;
}
+ private String totpKey;
+
+ public String getTotpKey() {
+ return totpKey;
+ }
+
+ public void setTotpKey(String totpKey) {
+ this.totpKey = totpKey;
+ }
+
+ private boolean temporary;
+
+ public boolean getTemporary() {
+ return temporary;
+ }
+
+ public void setTemporary(boolean temporary) {
+ this.temporary = temporary;
+ }
+
@QueryIgnore
public String getPassword() {
return null;
diff --git a/src/main/java/org/traccar/model/WifiAccessPoint.java b/src/main/java/org/traccar/model/WifiAccessPoint.java
index e28c1b935..64858f4c7 100644
--- a/src/main/java/org/traccar/model/WifiAccessPoint.java
+++ b/src/main/java/org/traccar/model/WifiAccessPoint.java
@@ -52,7 +52,7 @@ public class WifiAccessPoint {
}
public void setSignalStrength(Integer signalStrength) {
- this.signalStrength = signalStrength;
+ this.signalStrength = signalStrength > 0 ? -signalStrength : signalStrength;
}
private Integer channel;
diff --git a/src/main/java/org/traccar/notification/NotificationFormatter.java b/src/main/java/org/traccar/notification/NotificationFormatter.java
index 9ee3b97b6..7685eac0d 100644
--- a/src/main/java/org/traccar/notification/NotificationFormatter.java
+++ b/src/main/java/org/traccar/notification/NotificationFormatter.java
@@ -19,16 +19,18 @@ package org.traccar.notification;
import org.apache.velocity.VelocityContext;
import org.traccar.helper.model.UserUtil;
import org.traccar.model.Device;
+import org.traccar.model.Driver;
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;
import org.traccar.session.cache.CacheManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
public class NotificationFormatter {
@@ -43,13 +45,15 @@ public class 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) {
@@ -66,7 +70,8 @@ public class NotificationFormatter {
}
String driverUniqueId = event.getString(Position.KEY_DRIVER_UNIQUE_ID);
if (driverUniqueId != null) {
- velocityContext.put("driver", cacheManager.findDriverByUniqueId(device.getId(), driverUniqueId));
+ velocityContext.put("driver", cacheManager.getDeviceObjects(device.getId(), Driver.class).stream()
+ .filter(driver -> driver.getUniqueId().equals(driverUniqueId)).findFirst().orElse(null));
}
return textTemplateFormatter.formatMessage(velocityContext, event.getType(), templatePath);
diff --git a/src/main/java/org/traccar/notification/NotificationMessage.java b/src/main/java/org/traccar/notification/NotificationMessage.java
index 0fb8d7654..551a2d823 100644
--- a/src/main/java/org/traccar/notification/NotificationMessage.java
+++ b/src/main/java/org/traccar/notification/NotificationMessage.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,12 +16,16 @@
*/
package org.traccar.notification;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
public class NotificationMessage {
- private String subject;
- private String body;
+ private final String subject;
+ private final String body;
- public NotificationMessage(String subject, String body) {
+ @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
+ public NotificationMessage(@JsonProperty("subject") String subject, @JsonProperty("body") String body) {
this.subject = subject;
this.body = body;
}
diff --git a/src/main/java/org/traccar/notification/NotificatorManager.java b/src/main/java/org/traccar/notification/NotificatorManager.java
index 1d9f4f423..8d41ee354 100644
--- a/src/main/java/org/traccar/notification/NotificatorManager.java
+++ b/src/main/java/org/traccar/notification/NotificatorManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2023 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,23 +17,21 @@
package org.traccar.notification;
import com.google.inject.Injector;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Typed;
import org.traccar.notificators.Notificator;
+import org.traccar.notificators.NotificatorCommand;
import org.traccar.notificators.NotificatorFirebase;
import org.traccar.notificators.NotificatorMail;
-import org.traccar.notificators.NotificatorNull;
import org.traccar.notificators.NotificatorPushover;
import org.traccar.notificators.NotificatorSms;
import org.traccar.notificators.NotificatorTelegram;
import org.traccar.notificators.NotificatorTraccar;
import org.traccar.notificators.NotificatorWeb;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
@@ -43,9 +41,8 @@ import java.util.stream.Collectors;
@Singleton
public class NotificatorManager {
- private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorManager.class);
-
private static final Map<String, Class<? extends Notificator>> NOTIFICATORS_ALL = Map.of(
+ "command", NotificatorCommand.class,
"web", NotificatorWeb.class,
"mail", NotificatorMail.class,
"sms", NotificatorSms.class,
@@ -69,14 +66,13 @@ public class NotificatorManager {
public Notificator getNotificator(String type) {
var clazz = NOTIFICATORS_ALL.get(type);
- if (clazz != null) {
+ if (clazz != null && types.contains(type)) {
var notificator = injector.getInstance(clazz);
if (notificator != null) {
return notificator;
}
}
- LOGGER.warn("Failed to get notificator {}", type);
- return new NotificatorNull();
+ throw new RuntimeException("Failed to get notificator " + type);
}
public Set<Typed> getAllNotificatorTypes() {
diff --git a/src/main/java/org/traccar/notification/TextTemplateFormatter.java b/src/main/java/org/traccar/notification/TextTemplateFormatter.java
index 444f4a7c2..adcfc2ab5 100644
--- a/src/main/java/org/traccar/notification/TextTemplateFormatter.java
+++ b/src/main/java/org/traccar/notification/TextTemplateFormatter.java
@@ -15,10 +15,11 @@
*/
package org.traccar.notification;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
-import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.tools.generic.DateTool;
import org.apache.velocity.tools.generic.NumberTool;
import org.slf4j.Logger;
@@ -29,8 +30,6 @@ import org.traccar.model.Server;
import org.traccar.model.User;
import org.traccar.storage.StorageException;
-import javax.inject.Inject;
-import javax.inject.Singleton;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
@@ -75,19 +74,8 @@ public class TextTemplateFormatter {
}
public Template getTemplate(String name, String path) {
-
- String templateFilePath;
- Template template;
-
- try {
- templateFilePath = Paths.get(path, name + ".vm").toString();
- template = velocityEngine.getTemplate(templateFilePath, StandardCharsets.UTF_8.name());
- } catch (ResourceNotFoundException error) {
- LOGGER.warn("Notification template error", error);
- templateFilePath = Paths.get(path, "unknown.vm").toString();
- template = velocityEngine.getTemplate(templateFilePath, StandardCharsets.UTF_8.name());
- }
- return template;
+ String templateFilePath = Paths.get(path, name + ".vm").toString();
+ return velocityEngine.getTemplate(templateFilePath, StandardCharsets.UTF_8.name());
}
public NotificationMessage formatMessage(VelocityContext velocityContext, String name, String templatePath) {
diff --git a/src/main/java/org/traccar/notificators/Notificator.java b/src/main/java/org/traccar/notificators/Notificator.java
index 052365c7a..59216b1b6 100644
--- a/src/main/java/org/traccar/notificators/Notificator.java
+++ b/src/main/java/org/traccar/notificators/Notificator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,12 +17,30 @@
package org.traccar.notificators;
import org.traccar.model.Event;
+import org.traccar.model.Notification;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.notification.MessageException;
+import org.traccar.notification.NotificationFormatter;
+import org.traccar.notification.NotificationMessage;
-public interface Notificator {
+public abstract class Notificator {
- void send(User user, Event event, Position position) throws MessageException, InterruptedException;
+ private final NotificationFormatter notificationFormatter;
+ private final String templatePath;
+
+ public Notificator(NotificationFormatter notificationFormatter, String templatePath) {
+ this.notificationFormatter = notificationFormatter;
+ this.templatePath = templatePath;
+ }
+
+ public void send(Notification notification, User user, Event event, Position position) throws MessageException {
+ var message = notificationFormatter.formatMessage(notification, user, event, position, templatePath);
+ send(user, message, event, position);
+ }
+
+ public void send(User user, NotificationMessage message, Event event, Position position) throws MessageException {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/src/main/java/org/traccar/notificators/NotificatorCommand.java b/src/main/java/org/traccar/notificators/NotificatorCommand.java
new file mode 100644
index 000000000..712dda274
--- /dev/null
+++ b/src/main/java/org/traccar/notificators/NotificatorCommand.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023 - 2024 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.notificators;
+
+import org.traccar.database.CommandsManager;
+import org.traccar.model.Command;
+import org.traccar.model.Event;
+import org.traccar.model.Notification;
+import org.traccar.model.Position;
+import org.traccar.model.User;
+import org.traccar.notification.MessageException;
+import org.traccar.storage.Storage;
+import org.traccar.storage.query.Columns;
+import org.traccar.storage.query.Condition;
+import org.traccar.storage.query.Request;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class NotificatorCommand extends Notificator {
+
+ private final Storage storage;
+ private final CommandsManager commandsManager;
+
+ @Inject
+ public NotificatorCommand(Storage storage, CommandsManager commandsManager) {
+ super(null, null);
+ this.storage = storage;
+ this.commandsManager = commandsManager;
+ }
+
+ @Override
+ public void send(Notification notification, User user, Event event, Position position) throws MessageException {
+
+ if (notification == null || notification.getCommandId() <= 0) {
+ throw new MessageException("Saved command not provided");
+ }
+
+ try {
+ Command command = storage.getObject(Command.class, new Request(
+ new Columns.All(), new Condition.Equals("id", notification.getCommandId())));
+ command.setDeviceId(event.getDeviceId());
+ commandsManager.sendCommand(command);
+ } catch (Exception e) {
+ throw new MessageException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/notificators/NotificatorFirebase.java b/src/main/java/org/traccar/notificators/NotificatorFirebase.java
index 5ce2cbc0b..7440068f8 100644
--- a/src/main/java/org/traccar/notificators/NotificatorFirebase.java
+++ b/src/main/java/org/traccar/notificators/NotificatorFirebase.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,34 +24,50 @@ import com.google.firebase.messaging.AndroidNotification;
import com.google.firebase.messaging.ApnsConfig;
import com.google.firebase.messaging.Aps;
import com.google.firebase.messaging.FirebaseMessaging;
-import com.google.firebase.messaging.FirebaseMessagingException;
+import com.google.firebase.messaging.MessagingErrorCode;
import com.google.firebase.messaging.MulticastMessage;
-import com.google.firebase.messaging.Notification;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Event;
+import org.traccar.model.ObjectOperation;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.notification.MessageException;
import org.traccar.notification.NotificationFormatter;
+import org.traccar.notification.NotificationMessage;
+import org.traccar.session.cache.CacheManager;
+import org.traccar.storage.Storage;
+import org.traccar.storage.query.Columns;
+import org.traccar.storage.query.Condition;
+import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.LinkedList;
import java.util.List;
@Singleton
-public class NotificatorFirebase implements Notificator {
+public class NotificatorFirebase extends Notificator {
- private final NotificationFormatter notificationFormatter;
+ private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorFirebase.class);
+ private final Storage storage;
+ private final CacheManager cacheManager;
@Inject
- public NotificatorFirebase(Config config, NotificationFormatter notificationFormatter) throws IOException {
+ public NotificatorFirebase(
+ Config config, NotificationFormatter notificationFormatter,
+ Storage storage, CacheManager cacheManager) throws IOException {
- this.notificationFormatter = notificationFormatter;
+ super(notificationFormatter, "short");
+ this.storage = storage;
+ this.cacheManager = cacheManager;
InputStream serviceAccount = new ByteArrayInputStream(
config.getString(Keys.NOTIFICATOR_FIREBASE_SERVICE_ACCOUNT).getBytes());
@@ -64,17 +80,16 @@ public class NotificatorFirebase implements Notificator {
}
@Override
- public void send(User user, Event event, Position position) throws MessageException {
+ public void send(User user, NotificationMessage message, Event event, Position position) throws MessageException {
if (user.hasAttribute("notificationTokens")) {
- var shortMessage = notificationFormatter.formatMessage(user, event, position, "short");
+ List<String> registrationTokens = new ArrayList<>(
+ Arrays.asList(user.getString("notificationTokens").split("[, ]")));
- List<String> registrationTokens = Arrays.asList(user.getString("notificationTokens").split("[, ]"));
-
- MulticastMessage message = MulticastMessage.builder()
- .setNotification(Notification.builder()
- .setTitle(shortMessage.getSubject())
- .setBody(shortMessage.getBody())
+ var messageBuilder = MulticastMessage.builder()
+ .setNotification(com.google.firebase.messaging.Notification.builder()
+ .setTitle(message.getSubject())
+ .setBody(message.getBody())
.build())
.setAndroidConfig(AndroidConfig.builder()
.setNotification(AndroidNotification.builder()
@@ -86,19 +101,41 @@ public class NotificatorFirebase implements Notificator {
.setSound("default")
.build())
.build())
- .addAllTokens(registrationTokens)
- .putData("eventId", String.valueOf(event.getId()))
- .build();
+ .addAllTokens(registrationTokens);
+
+ if (event != null) {
+ messageBuilder.putData("eventId", String.valueOf(event.getId()));
+ }
try {
- var result = FirebaseMessaging.getInstance().sendMulticast(message);
- for (var response : result.getResponses()) {
+ var result = FirebaseMessaging.getInstance().sendEachForMulticast(messageBuilder.build());
+ List<String> failedTokens = new LinkedList<>();
+ var iterator = result.getResponses().listIterator();
+ while (iterator.hasNext()) {
+ int index = iterator.nextIndex();
+ var response = iterator.next();
if (!response.isSuccessful()) {
- throw new MessageException(response.getException());
+ MessagingErrorCode error = response.getException().getMessagingErrorCode();
+ if (error == MessagingErrorCode.INVALID_ARGUMENT || error == MessagingErrorCode.UNREGISTERED) {
+ failedTokens.add(registrationTokens.get(index));
+ }
+ LOGGER.warn("Firebase user {} error", user.getId(), response.getException());
+ }
+ }
+ if (!failedTokens.isEmpty()) {
+ registrationTokens.removeAll(failedTokens);
+ if (registrationTokens.isEmpty()) {
+ user.getAttributes().remove("notificationTokens");
+ } else {
+ user.set("notificationTokens", String.join(",", registrationTokens));
}
+ storage.updateObject(user, new Request(
+ new Columns.Include("attributes"),
+ new Condition.Equals("id", user.getId())));
+ cacheManager.invalidateObject(true, User.class, user.getId(), ObjectOperation.UPDATE);
}
- } catch (FirebaseMessagingException e) {
- throw new MessageException(e);
+ } catch (Exception e) {
+ LOGGER.warn("Firebase error", e);
}
}
}
diff --git a/src/main/java/org/traccar/notificators/NotificatorMail.java b/src/main/java/org/traccar/notificators/NotificatorMail.java
index 19fde6756..e7ca10157 100644
--- a/src/main/java/org/traccar/notificators/NotificatorMail.java
+++ b/src/main/java/org/traccar/notificators/NotificatorMail.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,34 +16,32 @@
*/
package org.traccar.notificators;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.mail.MessagingException;
import org.traccar.mail.MailManager;
import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.notification.MessageException;
import org.traccar.notification.NotificationFormatter;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javax.mail.MessagingException;
+import org.traccar.notification.NotificationMessage;
@Singleton
-public class NotificatorMail implements Notificator {
+public class NotificatorMail extends Notificator {
private final MailManager mailManager;
- private final NotificationFormatter notificationFormatter;
@Inject
public NotificatorMail(MailManager mailManager, NotificationFormatter notificationFormatter) {
+ super(notificationFormatter, "full");
this.mailManager = mailManager;
- this.notificationFormatter = notificationFormatter;
}
@Override
- public void send(User user, Event event, Position position) throws MessageException {
+ public void send(User user, NotificationMessage message, Event event, Position position) throws MessageException {
try {
- var fullMessage = notificationFormatter.formatMessage(user, event, position, "full");
- mailManager.sendMessage(user, fullMessage.getSubject(), fullMessage.getBody());
+ mailManager.sendMessage(user, false, message.getSubject(), message.getBody());
} catch (MessagingException e) {
throw new MessageException(e);
}
diff --git a/src/main/java/org/traccar/notificators/NotificatorNull.java b/src/main/java/org/traccar/notificators/NotificatorNull.java
deleted file mode 100644
index ba9ade9c6..000000000
--- a/src/main/java/org/traccar/notificators/NotificatorNull.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org)
- * Copyright 2018 Andrey Kunitsyn (andrey@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.notificators;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.traccar.model.Event;
-import org.traccar.model.Position;
-import org.traccar.model.User;
-
-public class NotificatorNull implements Notificator {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorNull.class);
-
- @Override
- public void send(User user, Event event, Position position) {
- LOGGER.warn("You are using null notificatior, please check your configuration, notification not sent");
- }
-
-}
diff --git a/src/main/java/org/traccar/notificators/NotificatorPushover.java b/src/main/java/org/traccar/notificators/NotificatorPushover.java
index e00db0579..a9708818b 100644
--- a/src/main/java/org/traccar/notificators/NotificatorPushover.java
+++ b/src/main/java/org/traccar/notificators/NotificatorPushover.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.
@@ -16,22 +16,21 @@
package org.traccar.notificators;
import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.Entity;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.notification.NotificationFormatter;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.Entity;
+import org.traccar.notification.NotificationMessage;
@Singleton
-public class NotificatorPushover implements Notificator {
+public class NotificatorPushover extends Notificator {
- private final NotificationFormatter notificationFormatter;
private final Client client;
private final String url;
@@ -53,7 +52,7 @@ public class NotificatorPushover implements Notificator {
@Inject
public NotificatorPushover(Config config, NotificationFormatter notificationFormatter, Client client) {
- this.notificationFormatter = notificationFormatter;
+ super(notificationFormatter, "short");
this.client = client;
url = "https://api.pushover.net/1/messages.json";
token = config.getString(Keys.NOTIFICATOR_PUSHOVER_TOKEN);
@@ -61,8 +60,7 @@ public class NotificatorPushover implements Notificator {
}
@Override
- public void send(User user, Event event, Position position) {
- var shortMessage = notificationFormatter.formatMessage(user, event, position, "short");
+ public void send(User user, NotificationMessage shortMessage, Event event, Position position) {
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 e37d10888..a8086adcc 100644
--- a/src/main/java/org/traccar/notificators/NotificatorSms.java
+++ b/src/main/java/org/traccar/notificators/NotificatorSms.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,38 +16,36 @@
*/
package org.traccar.notificators;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import org.traccar.database.StatisticsManager;
import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.notification.MessageException;
import org.traccar.notification.NotificationFormatter;
+import org.traccar.notification.NotificationMessage;
import org.traccar.sms.SmsManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
@Singleton
-public class NotificatorSms implements Notificator {
+public class NotificatorSms extends Notificator {
private final SmsManager smsManager;
- private final NotificationFormatter notificationFormatter;
private final StatisticsManager statisticsManager;
@Inject
public NotificatorSms(
SmsManager smsManager, NotificationFormatter notificationFormatter, StatisticsManager statisticsManager) {
+ super(notificationFormatter, "short");
this.smsManager = smsManager;
- this.notificationFormatter = notificationFormatter;
this.statisticsManager = statisticsManager;
}
@Override
- public void send(User user, Event event, Position position) throws MessageException, InterruptedException {
+ public void send(User user, NotificationMessage message, Event event, Position position) throws MessageException {
if (user.getPhone() != null) {
- var shortMessage = notificationFormatter.formatMessage(user, event, position, "short");
statisticsManager.registerSms();
- smsManager.sendMessage(user.getPhone(), shortMessage.getBody(), false);
+ smsManager.sendMessage(user.getPhone(), message.getBody(), false);
}
}
diff --git a/src/main/java/org/traccar/notificators/NotificatorTelegram.java b/src/main/java/org/traccar/notificators/NotificatorTelegram.java
index 38e87c222..4c8d7b8aa 100644
--- a/src/main/java/org/traccar/notificators/NotificatorTelegram.java
+++ b/src/main/java/org/traccar/notificators/NotificatorTelegram.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2021 Rafael Miquelino (rafaelmiquelino@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,22 +17,21 @@
package org.traccar.notificators;
import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.Entity;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.notification.NotificationFormatter;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.Entity;
+import org.traccar.notification.NotificationMessage;
@Singleton
-public class NotificatorTelegram implements Notificator {
+public class NotificatorTelegram extends Notificator {
- private final NotificationFormatter notificationFormatter;
private final Client client;
private final String urlSendText;
@@ -64,7 +63,7 @@ public class NotificatorTelegram implements Notificator {
@Inject
public NotificatorTelegram(Config config, NotificationFormatter notificationFormatter, Client client) {
- this.notificationFormatter = notificationFormatter;
+ super(notificationFormatter, "short");
this.client = client;
urlSendText = String.format(
"https://api.telegram.org/bot%s/sendMessage", config.getString(Keys.NOTIFICATOR_TELEGRAM_KEY));
@@ -85,8 +84,7 @@ public class NotificatorTelegram implements Notificator {
}
@Override
- public void send(User user, Event event, Position position) {
- var shortMessage = notificationFormatter.formatMessage(user, event, position, "short");
+ public void send(User user, NotificationMessage shortMessage, Event event, Position position) {
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 9ae39f975..983c4cda6 100644
--- a/src/main/java/org/traccar/notificators/NotificatorTraccar.java
+++ b/src/main/java/org/traccar/notificators/NotificatorTraccar.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.
@@ -16,28 +16,46 @@
package org.traccar.notificators;
import com.fasterxml.jackson.annotation.JsonProperty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.model.ObjectOperation;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.notification.NotificationFormatter;
+import org.traccar.notification.NotificationMessage;
+import org.traccar.session.cache.CacheManager;
+import org.traccar.storage.Storage;
+import org.traccar.storage.query.Columns;
+import org.traccar.storage.query.Condition;
+import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.Entity;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Response;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
@Singleton
-public class NotificatorTraccar implements Notificator {
+public class NotificatorTraccar extends Notificator {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorTraccar.class);
- private final NotificationFormatter notificationFormatter;
private final Client client;
+ private final Storage storage;
+ private final CacheManager cacheManager;
private final String url;
private final String key;
- public static class Notification {
+ public static class NotificationObject {
@JsonProperty("title")
private String title;
@JsonProperty("body")
@@ -50,33 +68,69 @@ public class NotificatorTraccar implements Notificator {
@JsonProperty("registration_ids")
private String[] tokens;
@JsonProperty("notification")
- private Notification notification;
+ private NotificationObject notification;
}
@Inject
- public NotificatorTraccar(Config config, NotificationFormatter notificationFormatter, Client client) {
- this.notificationFormatter = notificationFormatter;
+ public NotificatorTraccar(
+ Config config, NotificationFormatter notificationFormatter, Client client,
+ Storage storage, CacheManager cacheManager) {
+ super(notificationFormatter, "short");
this.client = client;
+ this.storage = storage;
+ this.cacheManager = cacheManager;
this.url = "https://www.traccar.org/push/";
this.key = config.getString(Keys.NOTIFICATOR_TRACCAR_KEY);
}
@Override
- public void send(User user, Event event, Position position) {
+ public void send(User user, NotificationMessage shortMessage, Event event, Position position) {
if (user.hasAttribute("notificationTokens")) {
- var shortMessage = notificationFormatter.formatMessage(user, event, position, "short");
+ NotificationObject item = new NotificationObject();
+ item.title = shortMessage.getSubject();
+ item.body = shortMessage.getBody();
+ item.sound = "default";
- Notification notification = new Notification();
- notification.title = shortMessage.getSubject();
- notification.body = shortMessage.getBody();
- notification.sound = "default";
+ String[] tokenArray = user.getString("notificationTokens").split("[, ]");
+ List<String> registrationTokens = new ArrayList<>(Arrays.asList(tokenArray));
Message message = new Message();
message.tokens = user.getString("notificationTokens").split("[, ]");
- message.notification = notification;
+ message.notification = item;
- client.target(url).request().header("Authorization", "key=" + key).post(Entity.json(message)).close();
+ var request = client.target(url).request().header("Authorization", "key=" + key);
+ try (Response result = request.post(Entity.json(message))) {
+ var json = result.readEntity(JsonObject.class);
+ List<String> failedTokens = new LinkedList<>();
+ var responses = json.getJsonArray("responses");
+ for (int i = 0; i < responses.size(); i++) {
+ var response = responses.getJsonObject(i);
+ if (!response.getBoolean("success")) {
+ var error = response.getJsonObject("error");
+ String errorCode = error.getString("code");
+ if (errorCode.equals("messaging/invalid-argument")
+ || errorCode.equals("messaging/registration-token-not-registered")) {
+ failedTokens.add(registrationTokens.get(i));
+ }
+ LOGGER.warn("Push user {} error - {}", user.getId(), error.getString("message"));
+ }
+ }
+ if (!failedTokens.isEmpty()) {
+ registrationTokens.removeAll(failedTokens);
+ if (registrationTokens.isEmpty()) {
+ user.getAttributes().remove("notificationTokens");
+ } else {
+ user.set("notificationTokens", String.join(",", registrationTokens));
+ }
+ storage.updateObject(user, new Request(
+ new Columns.Include("attributes"),
+ new Condition.Equals("id", user.getId())));
+ cacheManager.invalidateObject(true, User.class, user.getId(), ObjectOperation.UPDATE);
+ }
+ } catch (Exception e) {
+ LOGGER.warn("Push error", e);
+ }
}
}
diff --git a/src/main/java/org/traccar/notificators/NotificatorWeb.java b/src/main/java/org/traccar/notificators/NotificatorWeb.java
index deabeade1..b7bbdac1b 100644
--- a/src/main/java/org/traccar/notificators/NotificatorWeb.java
+++ b/src/main/java/org/traccar/notificators/NotificatorWeb.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2024 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,29 +17,30 @@
package org.traccar.notificators;
import org.traccar.model.Event;
+import org.traccar.model.Notification;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.notification.NotificationFormatter;
import org.traccar.session.ConnectionManager;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
@Singleton
-public final class NotificatorWeb implements Notificator {
+public final class NotificatorWeb extends Notificator {
private final ConnectionManager connectionManager;
private final NotificationFormatter notificationFormatter;
@Inject
- public NotificatorWeb(
- ConnectionManager connectionManager, NotificationFormatter notificationFormatter) {
+ public NotificatorWeb(ConnectionManager connectionManager, NotificationFormatter notificationFormatter) {
+ super(null, null);
this.connectionManager = connectionManager;
this.notificationFormatter = notificationFormatter;
}
@Override
- public void send(User user, Event event, Position position) {
+ public void send(Notification notification, User user, Event event, Position position) {
Event copy = new Event();
copy.setId(event.getId());
@@ -51,7 +52,7 @@ public final class NotificatorWeb implements Notificator {
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);
diff --git a/src/main/java/org/traccar/protocol/AdmProtocol.java b/src/main/java/org/traccar/protocol/AdmProtocol.java
index bab1d2339..3856dc906 100644
--- a/src/main/java/org/traccar/protocol/AdmProtocol.java
+++ b/src/main/java/org/traccar/protocol/AdmProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AdmProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AisProtocol.java b/src/main/java/org/traccar/protocol/AisProtocol.java
index bc975c277..e792a1d3f 100644
--- a/src/main/java/org/traccar/protocol/AisProtocol.java
+++ b/src/main/java/org/traccar/protocol/AisProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AisProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AlematicsProtocol.java b/src/main/java/org/traccar/protocol/AlematicsProtocol.java
index b85b44382..5219607e7 100644
--- a/src/main/java/org/traccar/protocol/AlematicsProtocol.java
+++ b/src/main/java/org/traccar/protocol/AlematicsProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AlematicsProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AnytrekProtocol.java b/src/main/java/org/traccar/protocol/AnytrekProtocol.java
index b0e974c69..5dce15ce1 100644
--- a/src/main/java/org/traccar/protocol/AnytrekProtocol.java
+++ b/src/main/java/org/traccar/protocol/AnytrekProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.config.Config;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AnytrekProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ApelProtocol.java b/src/main/java/org/traccar/protocol/ApelProtocol.java
index f1d6e659c..550581b85 100644
--- a/src/main/java/org/traccar/protocol/ApelProtocol.java
+++ b/src/main/java/org/traccar/protocol/ApelProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ApelProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AplicomProtocol.java b/src/main/java/org/traccar/protocol/AplicomProtocol.java
index 47bb780cb..48c628d22 100644
--- a/src/main/java/org/traccar/protocol/AplicomProtocol.java
+++ b/src/main/java/org/traccar/protocol/AplicomProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AplicomProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AppelloProtocol.java b/src/main/java/org/traccar/protocol/AppelloProtocol.java
index 25b2bf3b8..34055d7e4 100644
--- a/src/main/java/org/traccar/protocol/AppelloProtocol.java
+++ b/src/main/java/org/traccar/protocol/AppelloProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AppelloProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AquilaProtocol.java b/src/main/java/org/traccar/protocol/AquilaProtocol.java
index 6080df33d..bd9c34d08 100644
--- a/src/main/java/org/traccar/protocol/AquilaProtocol.java
+++ b/src/main/java/org/traccar/protocol/AquilaProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AquilaProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Ardi01Protocol.java b/src/main/java/org/traccar/protocol/Ardi01Protocol.java
index b33c2817f..5ddbe9d62 100644
--- a/src/main/java/org/traccar/protocol/Ardi01Protocol.java
+++ b/src/main/java/org/traccar/protocol/Ardi01Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Ardi01Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ArknavProtocol.java b/src/main/java/org/traccar/protocol/ArknavProtocol.java
index 4f443aa3a..20fec296c 100644
--- a/src/main/java/org/traccar/protocol/ArknavProtocol.java
+++ b/src/main/java/org/traccar/protocol/ArknavProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ArknavProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ArknavX8Protocol.java b/src/main/java/org/traccar/protocol/ArknavX8Protocol.java
index 39c6e8009..a8be6f40c 100644
--- a/src/main/java/org/traccar/protocol/ArknavX8Protocol.java
+++ b/src/main/java/org/traccar/protocol/ArknavX8Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ArknavX8Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ArmoliProtocol.java b/src/main/java/org/traccar/protocol/ArmoliProtocol.java
index 32fba3b52..e9c947ecd 100644
--- a/src/main/java/org/traccar/protocol/ArmoliProtocol.java
+++ b/src/main/java/org/traccar/protocol/ArmoliProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ArmoliProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ArnaviProtocol.java b/src/main/java/org/traccar/protocol/ArnaviProtocol.java
index 091d5c06f..962bcce52 100644
--- a/src/main/java/org/traccar/protocol/ArnaviProtocol.java
+++ b/src/main/java/org/traccar/protocol/ArnaviProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ArnaviProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java
index 361eeeef2..50ceea898 100644
--- a/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java
@@ -21,7 +21,7 @@ import io.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
import org.traccar.Protocol;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.net.SocketAddress;
public class ArnaviProtocolDecoder extends BaseProtocolDecoder {
diff --git a/src/main/java/org/traccar/protocol/AstraProtocol.java b/src/main/java/org/traccar/protocol/AstraProtocol.java
index 021a81e07..dcc02d10d 100644
--- a/src/main/java/org/traccar/protocol/AstraProtocol.java
+++ b/src/main/java/org/traccar/protocol/AstraProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AstraProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/At2000Protocol.java b/src/main/java/org/traccar/protocol/At2000Protocol.java
index 25e9be86f..c7e22f142 100644
--- a/src/main/java/org/traccar/protocol/At2000Protocol.java
+++ b/src/main/java/org/traccar/protocol/At2000Protocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class At2000Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AtrackProtocol.java b/src/main/java/org/traccar/protocol/AtrackProtocol.java
index 21eb09696..8b86955f4 100644
--- a/src/main/java/org/traccar/protocol/AtrackProtocol.java
+++ b/src/main/java/org/traccar/protocol/AtrackProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AtrackProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java
index 340641729..8896dcfb0 100644
--- a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java
@@ -429,6 +429,208 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder {
case "MP":
buf.readUnsignedByte(); // manifold absolute pressure
break;
+ case "EO":
+ position.set(Position.KEY_ODOMETER, UnitsConverter.metersFromMiles(buf.readUnsignedInt()));
+ break;
+ case "EH":
+ position.set(Position.KEY_HOURS, buf.readUnsignedInt() * 360000);
+ break;
+ case "ZO1":
+ buf.readUnsignedByte(); // brake stroke status
+ break;
+ case "ZO2":
+ buf.readUnsignedByte(); // warning indicator status
+ break;
+ case "ZO3":
+ buf.readUnsignedByte(); // abs control status
+ break;
+ case "ZO4":
+ position.set(Position.KEY_THROTTLE, buf.readUnsignedByte() * 0.4);
+ break;
+ case "ZO5":
+ buf.readUnsignedByte(); // parking brake status
+ break;
+ case "ZO6":
+ position.set(Position.KEY_OBD_SPEED, buf.readUnsignedByte() * 0.805);
+ break;
+ case "ZO7":
+ buf.readUnsignedByte(); // cruise control status
+ break;
+ case "ZO8":
+ buf.readUnsignedByte(); // accelector pedal position
+ break;
+ case "ZO9":
+ position.set(Position.KEY_ENGINE_LOAD, buf.readUnsignedByte() * 0.5);
+ break;
+ case "ZO10":
+ position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte() * 0.5);
+ break;
+ case "ZO11":
+ buf.readUnsignedByte(); // engine oil pressure
+ break;
+ case "ZO12":
+ buf.readUnsignedByte(); // boost pressure
+ break;
+ case "ZO13":
+ buf.readUnsignedByte(); // intake temperature
+ break;
+ case "ZO14":
+ position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedByte());
+ break;
+ case "ZO15":
+ buf.readUnsignedByte(); // brake application pressure
+ break;
+ case "ZO16":
+ buf.readUnsignedByte(); // brake primary pressure
+ break;
+ case "ZO17":
+ buf.readUnsignedByte(); // brake secondary pressure
+ break;
+ case "ZH1":
+ buf.readUnsignedShort(); // cargo weight
+ break;
+ case "ZH2":
+ position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort() * 16.428 / 3600);
+ break;
+ case "ZH3":
+ position.set(Position.KEY_RPM, buf.readUnsignedShort() * 0.25);
+ break;
+ case "ZL1":
+ buf.readUnsignedInt(); // fuel used (natural gas)
+ break;
+ case "ZL2":
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 161);
+ break;
+ case "ZL3":
+ buf.readUnsignedInt(); // vehicle hours
+ break;
+ case "ZL4":
+ position.set(Position.KEY_HOURS, buf.readUnsignedInt() * 5 * 36000);
+ break;
+ case "ZS1":
+ position.set(Position.KEY_VIN, readString(buf));
+ break;
+ case "JO1":
+ buf.readUnsignedByte(); // pedals
+ break;
+ case "JO2":
+ buf.readUnsignedByte(); // power takeoff device
+ break;
+ case "JO3":
+ buf.readUnsignedByte(); // accelector pedal position
+ break;
+ case "JO4":
+ position.set(Position.KEY_ENGINE_LOAD, buf.readUnsignedByte());
+ break;
+ case "JO5":
+ position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte() * 0.4);
+ break;
+ case "JO6":
+ buf.readUnsignedByte(); // fms vehicle interface
+ break;
+ case "JO7":
+ buf.readUnsignedByte(); // driver 2
+ break;
+ case "JO8":
+ buf.readUnsignedByte(); // driver 1
+ break;
+ case "JO9":
+ buf.readUnsignedByte(); // drivers
+ break;
+ case "JO10":
+ buf.readUnsignedByte(); // system information
+ break;
+ case "JO11":
+ position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedByte() - 40);
+ break;
+ case "JO12":
+ buf.readUnsignedByte(); // pto engaged
+ break;
+ case "JH1":
+ position.set(Position.KEY_OBD_SPEED, buf.readUnsignedShort() / 256.0);
+ break;
+ case "JH2":
+ position.set(Position.KEY_RPM, buf.readUnsignedShort() * 0.125);
+ break;
+ case "JH3":
+ case "JH4":
+ case "JH5":
+ case "JH6":
+ case "JH7":
+ int index = Integer.parseInt(key.substring(2)) - 2;
+ position.set("axleWeight" + index, buf.readUnsignedShort() * 0.5);
+ break;
+ case "JH8":
+ position.set(Position.KEY_ODOMETER_SERVICE, buf.readUnsignedShort() * 5);
+ break;
+ case "JH9":
+ buf.readUnsignedShort(); // tachograph speed
+ break;
+ case "JH10":
+ buf.readUnsignedShort(); // ambient air temperature
+ break;
+ case "JH11":
+ position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort() * 0.05);
+ break;
+ case "JH12":
+ buf.readUnsignedShort(); // fuel economy
+ break;
+ case "JL1":
+ position.set(Position.KEY_FUEL_USED, buf.readUnsignedInt() * 0.5);
+ break;
+ case "JL2":
+ position.set(Position.KEY_HOURS, buf.readUnsignedInt() * 5 * 36000);
+ break;
+ case "JL3":
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 1000);
+ break;
+ case "JL4":
+ position.set(Position.KEY_FUEL_USED, buf.readUnsignedInt() * 0.001);
+ break;
+ case "JS1":
+ position.set(Position.KEY_VIN, readString(buf));
+ break;
+ case "JS2":
+ readString(buf); // fms version supported
+ break;
+ case "JS3":
+ position.set("driver1", readString(buf));
+ break;
+ case "JS4":
+ position.set("driver2", readString(buf));
+ break;
+ case "JN1":
+ buf.readUnsignedInt(); // cruise control distance
+ break;
+ case "JN2":
+ buf.readUnsignedInt(); // excessive idling time
+ break;
+ case "JN3":
+ buf.readUnsignedInt(); // excessive idling fuel
+ break;
+ case "JN4":
+ buf.readUnsignedInt(); // pto time
+ break;
+ case "JN5":
+ buf.readUnsignedInt(); // pto fuel
+ break;
+ case "IN0":
+ position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0);
+ break;
+ case "IN1":
+ case "IN2":
+ case "IN3":
+ position.set(Position.PREFIX_IN + key.charAt(2), buf.readUnsignedByte() > 0);
+ break;
+ case "HA":
+ position.set(Position.KEY_ALARM, buf.readUnsignedByte() > 0 ? Position.ALARM_ACCELERATION : null);
+ break;
+ case "HB":
+ position.set(Position.KEY_ALARM, buf.readUnsignedByte() > 0 ? Position.ALARM_BRAKING : null);
+ break;
+ case "HC":
+ position.set(Position.KEY_ALARM, buf.readUnsignedByte() > 0 ? Position.ALARM_CORNERING : null);
+ break;
default:
break;
}
diff --git a/src/main/java/org/traccar/protocol/AuroProtocol.java b/src/main/java/org/traccar/protocol/AuroProtocol.java
index d37884c8b..728c8e23c 100644
--- a/src/main/java/org/traccar/protocol/AuroProtocol.java
+++ b/src/main/java/org/traccar/protocol/AuroProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AuroProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AustinNbProtocol.java b/src/main/java/org/traccar/protocol/AustinNbProtocol.java
index 6a68467e2..467deff53 100644
--- a/src/main/java/org/traccar/protocol/AustinNbProtocol.java
+++ b/src/main/java/org/traccar/protocol/AustinNbProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AustinNbProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AutoFonProtocol.java b/src/main/java/org/traccar/protocol/AutoFonProtocol.java
index 0566b1da6..75bd28d5e 100644
--- a/src/main/java/org/traccar/protocol/AutoFonProtocol.java
+++ b/src/main/java/org/traccar/protocol/AutoFonProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AutoFonProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AutoGradeProtocol.java b/src/main/java/org/traccar/protocol/AutoGradeProtocol.java
index bc80e473a..69d9fb4e6 100644
--- a/src/main/java/org/traccar/protocol/AutoGradeProtocol.java
+++ b/src/main/java/org/traccar/protocol/AutoGradeProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AutoGradeProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AutoTrackProtocol.java b/src/main/java/org/traccar/protocol/AutoTrackProtocol.java
index 80255d3e9..df489de3c 100644
--- a/src/main/java/org/traccar/protocol/AutoTrackProtocol.java
+++ b/src/main/java/org/traccar/protocol/AutoTrackProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AutoTrackProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/AvemaProtocol.java b/src/main/java/org/traccar/protocol/AvemaProtocol.java
index b35a447ff..0eeee41b8 100644
--- a/src/main/java/org/traccar/protocol/AvemaProtocol.java
+++ b/src/main/java/org/traccar/protocol/AvemaProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class AvemaProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Avl301Protocol.java b/src/main/java/org/traccar/protocol/Avl301Protocol.java
index c4a0affdc..452bc1501 100644
--- a/src/main/java/org/traccar/protocol/Avl301Protocol.java
+++ b/src/main/java/org/traccar/protocol/Avl301Protocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Avl301Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/B2316Protocol.java b/src/main/java/org/traccar/protocol/B2316Protocol.java
index 582be0b56..e0a6821d8 100644
--- a/src/main/java/org/traccar/protocol/B2316Protocol.java
+++ b/src/main/java/org/traccar/protocol/B2316Protocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class B2316Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/B2316ProtocolDecoder.java b/src/main/java/org/traccar/protocol/B2316ProtocolDecoder.java
index 635806b2d..b0a5411f7 100644
--- a/src/main/java/org/traccar/protocol/B2316ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/B2316ProtocolDecoder.java
@@ -24,9 +24,9 @@ import org.traccar.model.Network;
import org.traccar.model.Position;
import org.traccar.model.WifiAccessPoint;
-import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
import java.io.StringReader;
import java.net.SocketAddress;
import java.util.Date;
diff --git a/src/main/java/org/traccar/protocol/BceProtocol.java b/src/main/java/org/traccar/protocol/BceProtocol.java
index 31fb1bd83..5e1c10abc 100644
--- a/src/main/java/org/traccar/protocol/BceProtocol.java
+++ b/src/main/java/org/traccar/protocol/BceProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class BceProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/BlackKiteProtocol.java b/src/main/java/org/traccar/protocol/BlackKiteProtocol.java
index 3859a9273..d584af5a1 100644
--- a/src/main/java/org/traccar/protocol/BlackKiteProtocol.java
+++ b/src/main/java/org/traccar/protocol/BlackKiteProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class BlackKiteProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/BlueProtocol.java b/src/main/java/org/traccar/protocol/BlueProtocol.java
index da195f438..821111ad7 100644
--- a/src/main/java/org/traccar/protocol/BlueProtocol.java
+++ b/src/main/java/org/traccar/protocol/BlueProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class BlueProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/BoxProtocol.java b/src/main/java/org/traccar/protocol/BoxProtocol.java
index dc6852d50..ac1ba7cae 100644
--- a/src/main/java/org/traccar/protocol/BoxProtocol.java
+++ b/src/main/java/org/traccar/protocol/BoxProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class BoxProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/BstplProtocol.java b/src/main/java/org/traccar/protocol/BstplProtocol.java
index dde14a2ca..ccedcf3d9 100644
--- a/src/main/java/org/traccar/protocol/BstplProtocol.java
+++ b/src/main/java/org/traccar/protocol/BstplProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class BstplProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/C2stekProtocol.java b/src/main/java/org/traccar/protocol/C2stekProtocol.java
index 5cd8ef4fd..5370ea762 100644
--- a/src/main/java/org/traccar/protocol/C2stekProtocol.java
+++ b/src/main/java/org/traccar/protocol/C2stekProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class C2stekProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/CalAmpProtocol.java b/src/main/java/org/traccar/protocol/CalAmpProtocol.java
index d67308cf2..06df6e196 100644
--- a/src/main/java/org/traccar/protocol/CalAmpProtocol.java
+++ b/src/main/java/org/traccar/protocol/CalAmpProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class CalAmpProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/CarTrackProtocol.java b/src/main/java/org/traccar/protocol/CarTrackProtocol.java
index 0538aad72..366f32034 100644
--- a/src/main/java/org/traccar/protocol/CarTrackProtocol.java
+++ b/src/main/java/org/traccar/protocol/CarTrackProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class CarTrackProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/CarcellProtocol.java b/src/main/java/org/traccar/protocol/CarcellProtocol.java
index 832d9bb2d..7ae8159d5 100644
--- a/src/main/java/org/traccar/protocol/CarcellProtocol.java
+++ b/src/main/java/org/traccar/protocol/CarcellProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class CarcellProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/CarscopProtocol.java b/src/main/java/org/traccar/protocol/CarscopProtocol.java
index a4413af28..e904c01c5 100644
--- a/src/main/java/org/traccar/protocol/CarscopProtocol.java
+++ b/src/main/java/org/traccar/protocol/CarscopProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class CarscopProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/CastelProtocol.java b/src/main/java/org/traccar/protocol/CastelProtocol.java
index 9323b1503..74a9e9ca1 100644
--- a/src/main/java/org/traccar/protocol/CastelProtocol.java
+++ b/src/main/java/org/traccar/protocol/CastelProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.config.Config;
import org.traccar.model.Command;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class CastelProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java b/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java
index 4aa65245b..b076b9f66 100644
--- a/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java
@@ -16,7 +16,6 @@
package org.traccar.protocol;
import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
@@ -284,15 +283,27 @@ public class CastelProtocolDecoder extends BaseProtocolDecoder {
case 0x0C:
position.set(Position.KEY_ALARM, Position.ALARM_CORNERING);
break;
+ case 0x0D:
+ position.set(Position.KEY_ALARM, Position.ALARM_FATIGUE_DRIVING);
+ break;
case 0x0E:
position.set(Position.KEY_ALARM, Position.ALARM_POWER_OFF);
break;
+ case 0x11:
+ position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT);
+ break;
+ case 0x12:
+ position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING);
+ break;
case 0x16:
position.set(Position.KEY_IGNITION, true);
break;
case 0x17:
position.set(Position.KEY_IGNITION, false);
break;
+ case 0x1C:
+ position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION);
+ break;
default:
break;
}
@@ -359,9 +370,9 @@ public class CastelProtocolDecoder extends BaseProtocolDecoder {
int alarmCount = buf.readUnsignedByte();
for (int i = 0; i < alarmCount; i++) {
if (buf.readUnsignedByte() != 0) {
- int alarm = buf.readUnsignedByte();
+ int event = buf.readUnsignedByte();
for (Position p : positions) {
- decodeAlarm(p, alarm);
+ decodeAlarm(p, event);
}
buf.readUnsignedShortLE(); // description
buf.readUnsignedShortLE(); // threshold
@@ -421,12 +432,26 @@ public class CastelProtocolDecoder extends BaseProtocolDecoder {
return position;
case MSG_SC_DTCS_PASSENGER:
+ case MSG_SC_DTCS_COMMERCIAL:
position = createPosition(deviceSession);
decodeStat(position, buf);
buf.readUnsignedByte(); // flag
- position.add(ObdDecoder.decodeCodes(ByteBufUtil.hexDump(buf.readSlice(buf.readUnsignedByte()))));
+
+ count = buf.readUnsignedByte();
+ StringBuilder codes = new StringBuilder();
+ for (int i = 0; i < count; i++) {
+ if (type == MSG_SC_DTCS_COMMERCIAL) {
+ codes.append(ObdDecoder.decodeCode(buf.readUnsignedShortLE()));
+ buf.readUnsignedByte(); // attribute
+ buf.readUnsignedByte(); // occurrence
+ } else {
+ codes.append(ObdDecoder.decodeCode(buf.readUnsignedShortLE()));
+ }
+ codes.append(' ');
+ }
+ position.set(Position.KEY_DTCS, codes.toString().trim());
return position;
diff --git a/src/main/java/org/traccar/protocol/CautelaProtocol.java b/src/main/java/org/traccar/protocol/CautelaProtocol.java
index d0ca35ef1..067345f49 100644
--- a/src/main/java/org/traccar/protocol/CautelaProtocol.java
+++ b/src/main/java/org/traccar/protocol/CautelaProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class CautelaProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/CellocatorProtocol.java b/src/main/java/org/traccar/protocol/CellocatorProtocol.java
index 3287928c7..e3325c8b7 100644
--- a/src/main/java/org/traccar/protocol/CellocatorProtocol.java
+++ b/src/main/java/org/traccar/protocol/CellocatorProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class CellocatorProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/CguardProtocol.java b/src/main/java/org/traccar/protocol/CguardProtocol.java
index caf0aad42..c0fc9582a 100644
--- a/src/main/java/org/traccar/protocol/CguardProtocol.java
+++ b/src/main/java/org/traccar/protocol/CguardProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class CguardProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/CityeasyProtocol.java b/src/main/java/org/traccar/protocol/CityeasyProtocol.java
index 9656b284b..60ed5d135 100644
--- a/src/main/java/org/traccar/protocol/CityeasyProtocol.java
+++ b/src/main/java/org/traccar/protocol/CityeasyProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class CityeasyProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ContinentalProtocol.java b/src/main/java/org/traccar/protocol/ContinentalProtocol.java
index 06e93d79d..9567374fd 100644
--- a/src/main/java/org/traccar/protocol/ContinentalProtocol.java
+++ b/src/main/java/org/traccar/protocol/ContinentalProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ContinentalProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/CradlepointProtocol.java b/src/main/java/org/traccar/protocol/CradlepointProtocol.java
index 7f201a31d..220db0747 100644
--- a/src/main/java/org/traccar/protocol/CradlepointProtocol.java
+++ b/src/main/java/org/traccar/protocol/CradlepointProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class CradlepointProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/DingtekProtocol.java b/src/main/java/org/traccar/protocol/DingtekProtocol.java
index e9466b7e8..ab3e32fdb 100644
--- a/src/main/java/org/traccar/protocol/DingtekProtocol.java
+++ b/src/main/java/org/traccar/protocol/DingtekProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class DingtekProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/DishaProtocol.java b/src/main/java/org/traccar/protocol/DishaProtocol.java
index f83b8349a..0a582731d 100644
--- a/src/main/java/org/traccar/protocol/DishaProtocol.java
+++ b/src/main/java/org/traccar/protocol/DishaProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class DishaProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/DmtHttpProtocol.java b/src/main/java/org/traccar/protocol/DmtHttpProtocol.java
index 0dab26cda..d15bfa1ca 100644
--- a/src/main/java/org/traccar/protocol/DmtHttpProtocol.java
+++ b/src/main/java/org/traccar/protocol/DmtHttpProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class DmtHttpProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/DmtHttpProtocolDecoder.java b/src/main/java/org/traccar/protocol/DmtHttpProtocolDecoder.java
index 807850778..c2e617a2a 100644
--- a/src/main/java/org/traccar/protocol/DmtHttpProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/DmtHttpProtocolDecoder.java
@@ -25,9 +25,9 @@ import org.traccar.helper.BitUtil;
import org.traccar.helper.UnitsConverter;
import org.traccar.model.Position;
-import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
import java.io.StringReader;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
diff --git a/src/main/java/org/traccar/protocol/DmtProtocol.java b/src/main/java/org/traccar/protocol/DmtProtocol.java
index de56c9372..e89920cd3 100644
--- a/src/main/java/org/traccar/protocol/DmtProtocol.java
+++ b/src/main/java/org/traccar/protocol/DmtProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class DmtProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/DolphinProtocol.java b/src/main/java/org/traccar/protocol/DolphinProtocol.java
index ed627be78..e2acce7dd 100644
--- a/src/main/java/org/traccar/protocol/DolphinProtocol.java
+++ b/src/main/java/org/traccar/protocol/DolphinProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.config.Config;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class DolphinProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/DraginoProtocol.java b/src/main/java/org/traccar/protocol/DraginoProtocol.java
new file mode 100644
index 000000000..d33efe2ad
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/DraginoProtocol.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpRequestDecoder;
+import io.netty.handler.codec.http.HttpResponseEncoder;
+import jakarta.inject.Inject;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+import org.traccar.config.Config;
+
+public class DraginoProtocol extends BaseProtocol {
+
+ @Inject
+ public DraginoProtocol(Config config) {
+ addServer(new TrackerServer(config, getName(), false) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new HttpResponseEncoder());
+ pipeline.addLast(new HttpRequestDecoder());
+ pipeline.addLast(new HttpObjectAggregator(65535));
+ pipeline.addLast(new DraginoProtocolDecoder(DraginoProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/DraginoProtocolDecoder.java b/src/main/java/org/traccar/protocol/DraginoProtocolDecoder.java
new file mode 100644
index 000000000..5f576d723
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/DraginoProtocolDecoder.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
+import org.traccar.BaseHttpProtocolDecoder;
+import org.traccar.Protocol;
+import org.traccar.helper.DateUtil;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.io.StringReader;
+import java.net.SocketAddress;
+import java.nio.charset.StandardCharsets;
+
+public class DraginoProtocolDecoder extends BaseHttpProtocolDecoder {
+
+ public DraginoProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ FullHttpRequest request = (FullHttpRequest) msg;
+ String content = request.content().toString(StandardCharsets.UTF_8);
+ JsonObject json = Json.createReader(new StringReader(content)).readObject();
+
+ String deviceId = json.getJsonObject("end_device_ids").getString("device_id");
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, deviceId);
+ if (deviceSession == null) {
+ sendResponse(channel, HttpResponseStatus.BAD_REQUEST);
+ return null;
+ }
+
+ JsonObject message = json.getJsonObject("uplink_message");
+ JsonObject decoded = message.getJsonObject("decoded_payload");
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.setTime(DateUtil.parseDate(message.getString("received_at")));
+
+ position.setValid(true);
+ position.setLatitude(decoded.getJsonNumber("Latitude").doubleValue());
+ position.setLongitude(decoded.getJsonNumber("Longitude").doubleValue());
+
+ position.set("humidity", decoded.getJsonNumber("Hum").doubleValue());
+ position.set(Position.KEY_BATTERY, decoded.getJsonNumber("BatV").doubleValue());
+ position.set(Position.PREFIX_TEMP + 1, decoded.getJsonNumber("Tem").doubleValue());
+
+ if (Boolean.parseBoolean(decoded.getString("ALARM_status"))) {
+ position.set(Position.KEY_ALARM, Position.ALARM_SOS);
+ }
+
+ sendResponse(channel, HttpResponseStatus.OK);
+ return position;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/Dsf22Protocol.java b/src/main/java/org/traccar/protocol/Dsf22Protocol.java
index 06c99b0f9..ad349a7ff 100644
--- a/src/main/java/org/traccar/protocol/Dsf22Protocol.java
+++ b/src/main/java/org/traccar/protocol/Dsf22Protocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Dsf22Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/DualcamProtocol.java b/src/main/java/org/traccar/protocol/DualcamProtocol.java
index 363a2c5d9..4725cb180 100644
--- a/src/main/java/org/traccar/protocol/DualcamProtocol.java
+++ b/src/main/java/org/traccar/protocol/DualcamProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class DualcamProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java b/src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java
index d03f7648d..411e2b9d7 100644
--- a/src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2021 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,13 +42,24 @@ public class DualcamProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_COMPLETE = 5;
public static final int MSG_FILE_REQUEST = 8;
public static final int MSG_INIT_REQUEST = 9;
+ public static final int MSG_PATH_REQUEST = 0x000C;
+ public static final int MSG_PATH_RESPONSE = 0x000D;
+ private String model;
private String uniqueId;
- private int packetCount;
- private int currentPacket;
+ private int dataSize;
+ private int dataCurrent;
private boolean video;
private ByteBuf media;
+ private boolean isPacketData() {
+ if (model == null) {
+ return dataSize < 8192;
+ } else {
+ return !"DSM".equals(model);
+ }
+ }
+
@Override
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
@@ -57,15 +68,21 @@ public class DualcamProtocolDecoder extends BaseProtocolDecoder {
int type = buf.readUnsignedShort();
+ DeviceSession deviceSession;
switch (type) {
case MSG_INIT:
buf.readUnsignedShort(); // protocol id
uniqueId = String.valueOf(buf.readLong());
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, uniqueId);
+ deviceSession = getDeviceSession(channel, remoteAddress, uniqueId);
long settings = buf.readUnsignedInt();
if (channel != null && deviceSession != null) {
+ model = getDeviceModel(deviceSession);
ByteBuf response = Unpooled.buffer();
- if (BitUtil.between(settings, 26, 30) > 0) {
+ if (BitUtil.check(settings, 25)) {
+ response.writeShort(MSG_PATH_REQUEST);
+ response.writeShort(2);
+ response.writeShort(0);
+ } else if (BitUtil.between(settings, 26, 30) > 0) {
response.writeShort(MSG_FILE_REQUEST);
String file;
if (BitUtil.check(settings, 26)) {
@@ -91,21 +108,29 @@ public class DualcamProtocolDecoder extends BaseProtocolDecoder {
break;
case MSG_START:
buf.readUnsignedShort(); // length
- packetCount = buf.readInt();
- currentPacket = 1;
+ dataSize = buf.readInt();
+ dataCurrent = isPacketData() ? 1 : 0;
media = Unpooled.buffer();
if (channel != null) {
ByteBuf response = Unpooled.buffer();
response.writeShort(MSG_RESUME);
response.writeShort(4);
- response.writeInt(currentPacket);
+ response.writeInt(dataCurrent);
channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
}
break;
case MSG_DATA:
- buf.readUnsignedShort(); // length
- media.writeBytes(buf, buf.readableBytes() - 2);
- if (currentPacket == packetCount) {
+ int length = buf.readUnsignedShort() - 2;
+ media.writeBytes(buf, length);
+ boolean finished;
+ if (isPacketData()) {
+ finished = dataCurrent == dataSize;
+ dataCurrent += 1;
+ } else {
+ finished = dataCurrent + length == dataSize;
+ dataCurrent += length;
+ }
+ if (finished) {
deviceSession = getDeviceSession(channel, remoteAddress);
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
@@ -126,8 +151,16 @@ public class DualcamProtocolDecoder extends BaseProtocolDecoder {
channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
}
return position;
- } else {
- currentPacket += 1;
+ }
+ break;
+ case MSG_PATH_RESPONSE:
+ String file = buf.readCharSequence(buf.readUnsignedShort(), StandardCharsets.US_ASCII).toString();
+ if (channel != null) {
+ ByteBuf response = Unpooled.buffer();
+ response.writeShort(MSG_FILE_REQUEST);
+ response.writeShort(file.length());
+ response.writeCharSequence(file, StandardCharsets.US_ASCII);
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
}
break;
default:
diff --git a/src/main/java/org/traccar/protocol/DwayProtocol.java b/src/main/java/org/traccar/protocol/DwayProtocol.java
index 1096c945c..2ba1cf5f1 100644
--- a/src/main/java/org/traccar/protocol/DwayProtocol.java
+++ b/src/main/java/org/traccar/protocol/DwayProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class DwayProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/EasyTrackProtocol.java b/src/main/java/org/traccar/protocol/EasyTrackProtocol.java
index 39aa61580..25d4ef9a0 100644
--- a/src/main/java/org/traccar/protocol/EasyTrackProtocol.java
+++ b/src/main/java/org/traccar/protocol/EasyTrackProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class EasyTrackProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java
index 805cf1197..b10ff4c64 100644
--- a/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2013 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -79,26 +79,45 @@ public class EasyTrackProtocolDecoder extends BaseProtocolDecoder {
.any()
.compile();
+ private static final Pattern PATTERN_OBD = new PatternBuilder()
+ .text("*").expression("..,") // manufacturer
+ .number("(d+),") // imei
+ .text("OB,") // command
+ .text("BD$")
+ .number("V(d+.d);") // battery
+ .number("R(d+);") // rpm
+ .number("S(d+);") // speed
+ .number("P(d+.d);") // throttle
+ .number("O(d+.d);") // engine load
+ .number("C(d+);") // coolant temperature
+ .number("L(d+.d);") // fuel level
+ .number("[XY][MH]d+.d+;")
+ .number("M(d+);") // mileage
+ .number("F(d+.d+);") // fuel consumption
+ .number("T(d+);") // engine time
+ .any()
+ .compile();
+
private String decodeAlarm(long status) {
- if ((status & 0x02000000) != 0) {
+ if ((status & 0x02000000L) != 0) {
return Position.ALARM_GEOFENCE_ENTER;
}
- if ((status & 0x04000000) != 0) {
+ if ((status & 0x04000000L) != 0) {
return Position.ALARM_GEOFENCE_EXIT;
}
- if ((status & 0x08000000) != 0) {
+ if ((status & 0x08000000L) != 0) {
return Position.ALARM_LOW_BATTERY;
}
- if ((status & 0x20000000) != 0) {
+ if ((status & 0x20000000L) != 0) {
return Position.ALARM_VIBRATION;
}
- if ((status & 0x80000000) != 0) {
+ if ((status & 0x80000000L) != 0) {
return Position.ALARM_OVERSPEED;
}
- if ((status & 0x00010000) != 0) {
+ if ((status & 0x00010000L) != 0) {
return Position.ALARM_SOS;
}
- if ((status & 0x00040000) != 0) {
+ if ((status & 0x00040000L) != 0) {
return Position.ALARM_POWER_CUT;
}
return null;
@@ -115,7 +134,9 @@ public class EasyTrackProtocolDecoder extends BaseProtocolDecoder {
channel.writeAndFlush(new NetworkMessage(sentence + "#", remoteAddress));
}
- if (type.equals("JZ")) {
+ if (type.equals("OB")) {
+ return decodeObd(channel, remoteAddress, sentence);
+ } else if (type.equals("JZ")) {
return decodeCell(channel, remoteAddress, sentence);
} else {
return decodeLocation(channel, remoteAddress, sentence);
@@ -219,4 +240,35 @@ public class EasyTrackProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private Position decodeObd(Channel channel, SocketAddress remoteAddress, String sentence) {
+
+ Parser parser = new Parser(PATTERN_OBD, sentence);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ getLastLocation(position, null);
+
+ position.set(Position.KEY_BATTERY, parser.nextDouble());
+ position.set(Position.KEY_RPM, parser.nextInt());
+ position.set(Position.KEY_OBD_SPEED, parser.nextInt());
+ position.set(Position.KEY_THROTTLE, parser.nextDouble());
+ position.set(Position.KEY_ENGINE_LOAD, parser.nextDouble());
+ position.set(Position.KEY_COOLANT_TEMP, parser.nextInt());
+ position.set(Position.KEY_FUEL_LEVEL, parser.nextDouble());
+ position.set(Position.KEY_ODOMETER, parser.nextInt());
+ position.set(Position.KEY_FUEL_CONSUMPTION, parser.nextDouble());
+ position.set(Position.KEY_HOURS, parser.nextInt());
+
+ return position;
+ }
+
}
diff --git a/src/main/java/org/traccar/protocol/EelinkProtocol.java b/src/main/java/org/traccar/protocol/EelinkProtocol.java
index 35fd4fe65..2a3c0bd15 100644
--- a/src/main/java/org/traccar/protocol/EelinkProtocol.java
+++ b/src/main/java/org/traccar/protocol/EelinkProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class EelinkProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java
index cb0e10042..db1b365c3 100644
--- a/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java
@@ -314,7 +314,7 @@ public class EelinkProtocolDecoder extends BaseProtocolDecoder {
}
if (buf.readableBytes() >= 12) {
- position.set(Position.PREFIX_TEMP + 1, buf.readUnsignedShort() / 256.0);
+ position.set(Position.PREFIX_TEMP + 1, buf.readShort() / 256.0);
position.set("humidity", buf.readUnsignedShort() * 0.1);
position.set("illuminance", buf.readUnsignedInt() / 256.0);
position.set("co2", buf.readUnsignedInt());
diff --git a/src/main/java/org/traccar/protocol/EgtsProtocol.java b/src/main/java/org/traccar/protocol/EgtsProtocol.java
index f257271d4..5450d9f01 100644
--- a/src/main/java/org/traccar/protocol/EgtsProtocol.java
+++ b/src/main/java/org/traccar/protocol/EgtsProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class EgtsProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/EnforaProtocol.java b/src/main/java/org/traccar/protocol/EnforaProtocol.java
index ebde56f70..3b796db25 100644
--- a/src/main/java/org/traccar/protocol/EnforaProtocol.java
+++ b/src/main/java/org/traccar/protocol/EnforaProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class EnforaProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/EnnfuProtocol.java b/src/main/java/org/traccar/protocol/EnnfuProtocol.java
index e326481fa..a3ff943fa 100644
--- a/src/main/java/org/traccar/protocol/EnnfuProtocol.java
+++ b/src/main/java/org/traccar/protocol/EnnfuProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class EnnfuProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/EnvotechProtocol.java b/src/main/java/org/traccar/protocol/EnvotechProtocol.java
index dffa1c991..e432ac07c 100644
--- a/src/main/java/org/traccar/protocol/EnvotechProtocol.java
+++ b/src/main/java/org/traccar/protocol/EnvotechProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class EnvotechProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/EsealProtocol.java b/src/main/java/org/traccar/protocol/EsealProtocol.java
index 0ed80dc6f..eae4cf2aa 100644
--- a/src/main/java/org/traccar/protocol/EsealProtocol.java
+++ b/src/main/java/org/traccar/protocol/EsealProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class EsealProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/EskyProtocol.java b/src/main/java/org/traccar/protocol/EskyProtocol.java
index cb2f59dc8..002e268ba 100644
--- a/src/main/java/org/traccar/protocol/EskyProtocol.java
+++ b/src/main/java/org/traccar/protocol/EskyProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class EskyProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ExtremTracProtocol.java b/src/main/java/org/traccar/protocol/ExtremTracProtocol.java
index ffc941b69..23a993fe4 100644
--- a/src/main/java/org/traccar/protocol/ExtremTracProtocol.java
+++ b/src/main/java/org/traccar/protocol/ExtremTracProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ExtremTracProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocol.java b/src/main/java/org/traccar/protocol/FifotrackProtocol.java
index fd2beaabb..e98ad84cc 100644
--- a/src/main/java/org/traccar/protocol/FifotrackProtocol.java
+++ b/src/main/java/org/traccar/protocol/FifotrackProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class FifotrackProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java
index a9d77b46e..59019830f 100644
--- a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java
@@ -64,7 +64,7 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
.number("(d+),") // course
.number("(-?d+),") // altitude
.number("(d+),") // odometer
- .number("d+,") // runtime
+ .number("(d+),") // engine hours
.number("(x+),") // status
.number("(x+)?,") // input
.number("(x+)?,") // output
@@ -183,6 +183,8 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
case 30:
case 32:
return Position.ALARM_JAMMING;
+ case 31:
+ return Position.ALARM_FALL_DOWN;
case 33:
return Position.ALARM_GEOFENCE_EXIT;
case 34:
@@ -192,6 +194,10 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
case 40:
case 41:
return Position.ALARM_TEMPERATURE;
+ case 53:
+ return Position.ALARM_POWER_ON;
+ case 54:
+ return Position.ALARM_POWER_OFF;
default:
return null;
}
@@ -235,13 +241,15 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
position.setValid(parser.next().equals("A"));
position.setFixTime(position.getDeviceTime());
- position.set(Position.KEY_SATELLITES, parser.nextInt());
position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt()));
+ position.set(Position.KEY_SATELLITES, parser.nextInt());
position.setLatitude(parser.nextDouble());
position.setLongitude(parser.nextDouble());
} else {
+ getLastLocation(position, position.getDeviceTime());
+
String[] points = parser.next().split("\\|");
for (String point : points) {
String[] wifi = point.split(":");
@@ -290,6 +298,7 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
position.setAltitude(parser.nextInt());
position.set(Position.KEY_ODOMETER, parser.nextLong());
+ position.set(Position.KEY_HOURS, parser.nextLong() * 1000);
long status = parser.nextHexLong();
position.set(Position.KEY_RSSI, BitUtil.between(status, 3, 8));
@@ -308,11 +317,11 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder {
}
if (parser.hasNext()) {
- String rfid = parser.next();
- if (rfid.matches("\\p{XDigit}+")) {
- position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(Integer.parseInt(rfid, 16)));
+ String value = parser.next();
+ if (value.matches("\\p{XDigit}+")) {
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(Integer.parseInt(value, 16)));
} else {
- position.set("driverLicense", rfid);
+ position.set(Position.KEY_CARD, value);
}
}
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 000000000..46611c25c
--- /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 000000000..8f679525b
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/FleetGuideProtocolDecoder.java
@@ -0,0 +1,328 @@
+/*
+ * 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.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;
+
+import java.net.SocketAddress;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+public class FleetGuideProtocolDecoder extends BaseProtocolDecoder {
+
+ public FleetGuideProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ 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(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ ByteBuf buf = (ByteBuf) msg;
+
+ buf.readUnsignedByte(); // signature
+ int options = buf.readUnsignedShortLE();
+ int length = BitUtil.to(options, 11);
+
+ DeviceSession deviceSession;
+ Long deviceId;
+ if (BitUtil.check(options, 11)) {
+ deviceId = buf.readUnsignedIntLE();
+ deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(deviceId));
+ } else {
+ deviceId = null;
+ deviceSession = getDeviceSession(channel, remoteAddress);
+ }
+ if (deviceSession == null) {
+ return null;
+ }
+
+ int type;
+ Integer index;
+ if (BitUtil.check(options, 12)) {
+ int value = buf.readUnsignedByte();
+ type = BitUtil.to(value, 4);
+ index = BitUtil.from(value, 4);
+ } else {
+ type = 0;
+ index = 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);
+ }
+
+ if (BitUtil.check(options, 13)) {
+ buf.readUnsignedShortLE(); // acknowledgement
+ }
+
+ ByteBuf data;
+ if (BitUtil.check(options, 14)) {
+ data = decompress(buf.readSlice(length));
+ } else {
+ data = buf.readRetainedSlice(length);
+ }
+
+ List<Position> positions = new LinkedList<>();
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ 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 (recordType == 0 && position.getDeviceTime() != null) {
+ processPosition(positions, position);
+ position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+ }
+
+ 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;
+ 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;
+ }
+
+ data.readerIndex(recordEndIndex);
+
+ }
+
+ processPosition(positions, position);
+
+ data.release();
+
+ return positions.isEmpty() ? null : positions;
+ }
+
+ private void processPosition(List<Position> 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);
+ }
+ }
+
+
+ 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;
+ do {
+ b = buf.readUnsignedByte();
+ y = (y << 7) | (b & 0x0000007f);
+ } while ((b & 0x00000080) > 0);
+
+ return y;
+ }
+
+ private ByteBuf decompress(ByteBuf in) {
+
+ ByteBuf out = Unpooled.buffer();
+
+ if (in.readableBytes() < 1) {
+ return out;
+ }
+
+ 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;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/FlespiProtocol.java b/src/main/java/org/traccar/protocol/FlespiProtocol.java
index 374cf77e2..0d8448845 100644
--- a/src/main/java/org/traccar/protocol/FlespiProtocol.java
+++ b/src/main/java/org/traccar/protocol/FlespiProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class FlespiProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java
index 6e6f9c700..ea076afd8 100644
--- a/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java
@@ -24,12 +24,12 @@ import org.traccar.Protocol;
import org.traccar.helper.UnitsConverter;
import org.traccar.model.Position;
-import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonNumber;
-import javax.json.JsonObject;
-import javax.json.JsonString;
-import javax.json.JsonValue;
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonNumber;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonString;
+import jakarta.json.JsonValue;
import java.io.StringReader;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
@@ -125,12 +125,13 @@ public class FlespiProtocolDecoder extends BaseHttpProtocolDecoder {
position.set(Position.KEY_PDOP, ((JsonNumber) value).doubleValue());
return true;
case "din":
+ position.set(Position.KEY_INPUT, ((JsonNumber) value).intValue());
+ return true;
case "dout":
- if (name.equals("din")) {
- position.set(Position.KEY_INPUT, ((JsonNumber) value).intValue());
- } else {
- position.set(Position.KEY_OUTPUT, ((JsonNumber) value).intValue());
- }
+ position.set(Position.KEY_OUTPUT, ((JsonNumber) value).intValue());
+ return true;
+ case "report.reason":
+ position.set(Position.KEY_EVENT, ((JsonNumber) value).intValue());
return true;
case "gps.vehicle.mileage":
position.set(Position.KEY_ODOMETER, ((JsonNumber) value).doubleValue());
@@ -141,6 +142,9 @@ public class FlespiProtocolDecoder extends BaseHttpProtocolDecoder {
case "battery.voltage":
position.set(Position.KEY_BATTERY, ((JsonNumber) value).doubleValue());
return true;
+ case "battery.level":
+ position.set(Position.KEY_BATTERY_LEVEL, ((JsonNumber) value).intValue());
+ return true;
case "fuel.level":
case "can.fuel.level":
position.set(Position.KEY_FUEL_LEVEL, ((JsonNumber) value).doubleValue());
diff --git a/src/main/java/org/traccar/protocol/FlexApiProtocol.java b/src/main/java/org/traccar/protocol/FlexApiProtocol.java
index 088072d2d..b2e3f5d00 100644
--- a/src/main/java/org/traccar/protocol/FlexApiProtocol.java
+++ b/src/main/java/org/traccar/protocol/FlexApiProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.config.Config;
import java.nio.charset.StandardCharsets;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class FlexApiProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java
index 2dec44e64..fb76673ca 100644
--- a/src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java
@@ -23,8 +23,8 @@ import org.traccar.model.CellTower;
import org.traccar.model.Network;
import org.traccar.model.Position;
-import javax.json.Json;
-import javax.json.JsonObject;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
import java.io.StringReader;
import java.net.SocketAddress;
import java.util.Date;
diff --git a/src/main/java/org/traccar/protocol/FlexCommProtocol.java b/src/main/java/org/traccar/protocol/FlexCommProtocol.java
index 5397156cb..293b9b12b 100644
--- a/src/main/java/org/traccar/protocol/FlexCommProtocol.java
+++ b/src/main/java/org/traccar/protocol/FlexCommProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class FlexCommProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/FlexibleReportProtocol.java b/src/main/java/org/traccar/protocol/FlexibleReportProtocol.java
index 61e315af9..a16e61458 100644
--- a/src/main/java/org/traccar/protocol/FlexibleReportProtocol.java
+++ b/src/main/java/org/traccar/protocol/FlexibleReportProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class FlexibleReportProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/FlextrackProtocol.java b/src/main/java/org/traccar/protocol/FlextrackProtocol.java
index ebac8b4de..b6353de9d 100644
--- a/src/main/java/org/traccar/protocol/FlextrackProtocol.java
+++ b/src/main/java/org/traccar/protocol/FlextrackProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class FlextrackProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/FoxProtocol.java b/src/main/java/org/traccar/protocol/FoxProtocol.java
index fa45b3817..edb496f11 100644
--- a/src/main/java/org/traccar/protocol/FoxProtocol.java
+++ b/src/main/java/org/traccar/protocol/FoxProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class FoxProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/FreedomProtocol.java b/src/main/java/org/traccar/protocol/FreedomProtocol.java
index dac117c04..87404094a 100644
--- a/src/main/java/org/traccar/protocol/FreedomProtocol.java
+++ b/src/main/java/org/traccar/protocol/FreedomProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class FreedomProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/FreematicsProtocol.java b/src/main/java/org/traccar/protocol/FreematicsProtocol.java
index dce4994ab..c4076f330 100644
--- a/src/main/java/org/traccar/protocol/FreematicsProtocol.java
+++ b/src/main/java/org/traccar/protocol/FreematicsProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class FreematicsProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java b/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java
index 4e5200f37..d0402cc94 100644
--- a/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java
@@ -153,7 +153,7 @@ public class FreematicsProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_RSSI, Integer.parseInt(value));
break;
case 0x82:
- position.set(Position.KEY_DEVICE_TEMP, Integer.parseInt(value) * 0.1);
+ position.set(Position.KEY_DEVICE_TEMP, Double.parseDouble(value) * 0.1);
break;
case 0x104:
position.set(Position.KEY_ENGINE_LOAD, Integer.parseInt(value));
@@ -165,7 +165,7 @@ public class FreematicsProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_RPM, Integer.parseInt(value));
break;
case 0x10d:
- position.set(Position.KEY_OBD_SPEED, UnitsConverter.knotsFromKph(Integer.parseInt(value)));
+ position.set(Position.KEY_OBD_SPEED, Integer.parseInt(value));
break;
case 0x111:
position.set(Position.KEY_THROTTLE, Integer.parseInt(value));
diff --git a/src/main/java/org/traccar/protocol/FutureWayProtocol.java b/src/main/java/org/traccar/protocol/FutureWayProtocol.java
index 715dd3c9c..20586bede 100644
--- a/src/main/java/org/traccar/protocol/FutureWayProtocol.java
+++ b/src/main/java/org/traccar/protocol/FutureWayProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class FutureWayProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/G1rusProtocol.java b/src/main/java/org/traccar/protocol/G1rusProtocol.java
index f1823762d..b3904b357 100644
--- a/src/main/java/org/traccar/protocol/G1rusProtocol.java
+++ b/src/main/java/org/traccar/protocol/G1rusProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class G1rusProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/GalileoFrameDecoder.java b/src/main/java/org/traccar/protocol/GalileoFrameDecoder.java
index c23d26c83..d90e48287 100644
--- a/src/main/java/org/traccar/protocol/GalileoFrameDecoder.java
+++ b/src/main/java/org/traccar/protocol/GalileoFrameDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2013 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ import org.traccar.BaseFrameDecoder;
public class GalileoFrameDecoder extends BaseFrameDecoder {
- private static final int MESSAGE_MINIMUM_LENGTH = 5;
+ private static final int MESSAGE_MINIMUM_LENGTH = 6;
@Override
protected Object decode(
@@ -32,9 +32,15 @@ public class GalileoFrameDecoder extends BaseFrameDecoder {
return null;
}
- int length = buf.getUnsignedShortLE(buf.readerIndex() + 1) & 0x7fff;
- if (buf.readableBytes() >= (length + MESSAGE_MINIMUM_LENGTH)) {
- return buf.readRetainedSlice(length + MESSAGE_MINIMUM_LENGTH);
+ int length;
+ if (buf.getByte(buf.readerIndex()) == 0x01 && buf.getUnsignedMedium(buf.readerIndex() + 3) == 0x01001c) {
+ length = 3 + buf.getUnsignedShort(buf.readerIndex() + 1);
+ } else {
+ length = 5 + (buf.getUnsignedShortLE(buf.readerIndex() + 1) & 0x7fff);
+ }
+
+ if (buf.readableBytes() >= length) {
+ return buf.readRetainedSlice(length);
}
return null;
diff --git a/src/main/java/org/traccar/protocol/GalileoProtocol.java b/src/main/java/org/traccar/protocol/GalileoProtocol.java
index 90e95574a..95ce4edde 100644
--- a/src/main/java/org/traccar/protocol/GalileoProtocol.java
+++ b/src/main/java/org/traccar/protocol/GalileoProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class GalileoProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/GatorProtocol.java b/src/main/java/org/traccar/protocol/GatorProtocol.java
index 7341b69a3..d29ed9ce7 100644
--- a/src/main/java/org/traccar/protocol/GatorProtocol.java
+++ b/src/main/java/org/traccar/protocol/GatorProtocol.java
@@ -20,17 +20,25 @@ import org.traccar.BaseProtocol;
import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
+import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class GatorProtocol extends BaseProtocol {
@Inject
public GatorProtocol(Config config) {
+ setSupportedDataCommands(
+ Command.TYPE_POSITION_SINGLE,
+ Command.TYPE_ENGINE_RESUME,
+ Command.TYPE_ENGINE_STOP,
+ Command.TYPE_SET_SPEED_LIMIT,
+ Command.TYPE_SET_ODOMETER);
addServer(new TrackerServer(config, getName(), false) {
@Override
protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 2));
+ pipeline.addLast(new GatorProtocolEncoder(GatorProtocol.this));
pipeline.addLast(new GatorProtocolDecoder(GatorProtocol.this));
}
});
diff --git a/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java b/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java
index 644caee81..a9c620090 100644
--- a/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java
@@ -37,6 +37,11 @@ public class GatorProtocolDecoder extends BaseProtocolDecoder {
}
public static final int MSG_HEARTBEAT = 0x21;
+ public static final int MSG_POSITION_REQUEST = 0x30;
+ public static final int MSG_OVERSPEED_ALARM = 0x3F;
+ public static final int MSG_RESET_MILEAGE = 0x6B;
+ public static final int MSG_RESTORE_OIL_DUCT = 0x38;
+ public static final int MSG_CLOSE_OIL_DUCT = 0x39;
public static final int MSG_POSITION_DATA = 0x80;
public static final int MSG_ROLLCALL_RESPONSE = 0x81;
public static final int MSG_ALARM_DATA = 0x82;
diff --git a/src/main/java/org/traccar/protocol/GatorProtocolEncoder.java b/src/main/java/org/traccar/protocol/GatorProtocolEncoder.java
new file mode 100644
index 000000000..6c6b9a54a
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/GatorProtocolEncoder.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2023 Hossain Mohammad Seym (seym45@gmail.com)
+ *
+ * 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 org.traccar.BaseProtocolEncoder;
+import org.traccar.Protocol;
+import org.traccar.helper.Checksum;
+import org.traccar.model.Command;
+
+public class GatorProtocolEncoder extends BaseProtocolEncoder {
+
+ public GatorProtocolEncoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ public ByteBuf encodeId(long deviceId) {
+ ByteBuf buf = Unpooled.buffer();
+
+ String id = getUniqueId(deviceId);
+
+ int firstDigit = Integer.parseInt(id.substring(1, 3)) - 30;
+
+ buf.writeByte(Integer.parseInt(id.substring(3, 5)) | (((firstDigit >> 3) & 1) << 7));
+ buf.writeByte(Integer.parseInt(id.substring(5, 7)) | (((firstDigit >> 2) & 1) << 7));
+ buf.writeByte(Integer.parseInt(id.substring(7, 9)) | (((firstDigit >> 1) & 1) << 7));
+ buf.writeByte(Integer.parseInt(id.substring(9)) | ((firstDigit & 1) << 7));
+
+ return buf;
+ }
+
+ private ByteBuf encodeContent(long deviceId, int type, ByteBuf content) {
+ ByteBuf buf = Unpooled.buffer();
+
+ buf.writeByte(0x24);
+ buf.writeByte(0x24);
+ buf.writeByte(type);
+ buf.writeByte(0x00);
+
+ buf.writeByte(4 + 1 + (content != null ? content.readableBytes() : 0) + 1); // length
+
+ ByteBuf pseudoIPAddress = encodeId(deviceId);
+ buf.writeBytes(pseudoIPAddress);
+
+ if (content != null) {
+ buf.writeBytes(content);
+ }
+
+ int checksum = Checksum.xor(buf.nioBuffer());
+ buf.writeByte(checksum);
+
+ buf.writeByte(0x0D);
+
+ return buf;
+ }
+
+ @Override
+ protected Object encodeCommand(Command command) {
+
+ ByteBuf content = Unpooled.buffer();
+
+ switch (command.getType()) {
+ case Command.TYPE_POSITION_SINGLE:
+ return encodeContent(command.getDeviceId(), GatorProtocolDecoder.MSG_POSITION_REQUEST, null);
+ case Command.TYPE_ENGINE_STOP:
+ return encodeContent(command.getDeviceId(), GatorProtocolDecoder.MSG_CLOSE_OIL_DUCT, null);
+ case Command.TYPE_ENGINE_RESUME:
+ return encodeContent(command.getDeviceId(), GatorProtocolDecoder.MSG_RESTORE_OIL_DUCT, null);
+ case Command.TYPE_SET_SPEED_LIMIT:
+ content.writeByte(command.getInteger(Command.KEY_DATA));
+ return encodeContent(command.getDeviceId(), GatorProtocolDecoder.MSG_RESET_MILEAGE, content);
+ case Command.TYPE_SET_ODOMETER:
+ content.writeShort(command.getInteger(Command.KEY_DATA));
+ return encodeContent(command.getDeviceId(), GatorProtocolDecoder.MSG_OVERSPEED_ALARM, content);
+ default:
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/traccar/protocol/GenxProtocol.java b/src/main/java/org/traccar/protocol/GenxProtocol.java
index 97d8633a0..7e5a6a69e 100644
--- a/src/main/java/org/traccar/protocol/GenxProtocol.java
+++ b/src/main/java/org/traccar/protocol/GenxProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class GenxProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Gl100Protocol.java b/src/main/java/org/traccar/protocol/Gl100Protocol.java
index e1748c9a0..fdd79648f 100644
--- a/src/main/java/org/traccar/protocol/Gl100Protocol.java
+++ b/src/main/java/org/traccar/protocol/Gl100Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Gl100Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Gl200Protocol.java b/src/main/java/org/traccar/protocol/Gl200Protocol.java
index c7b6a8e7c..e3ddbb46e 100644
--- a/src/main/java/org/traccar/protocol/Gl200Protocol.java
+++ b/src/main/java/org/traccar/protocol/Gl200Protocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Gl200Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Gl200ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200ProtocolDecoder.java
index a9736c9e7..9b4e05c35 100644
--- a/src/main/java/org/traccar/protocol/Gl200ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gl200ProtocolDecoder.java
@@ -22,7 +22,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import org.traccar.Protocol;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.net.SocketAddress;
public class Gl200ProtocolDecoder extends BaseProtocolDecoder {
diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
index 517499f02..2e5ffa8d6 100644
--- a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 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,12 +15,15 @@
*/
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.session.DeviceSession;
import org.traccar.NetworkMessage;
import org.traccar.Protocol;
import org.traccar.config.Keys;
import org.traccar.helper.BitUtil;
+import org.traccar.helper.DataConverter;
import org.traccar.helper.Parser;
import org.traccar.helper.PatternBuilder;
import org.traccar.helper.UnitsConverter;
@@ -28,15 +31,14 @@ import org.traccar.model.CellTower;
import org.traccar.model.Network;
import org.traccar.model.Position;
import org.traccar.model.WifiAccessPoint;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.Channel;
+import org.traccar.session.DeviceSession;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;
@@ -47,8 +49,12 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
private boolean ignoreFixTime;
+ private final DateFormat dateFormat;
+
public Gl200TextProtocolDecoder(Protocol protocol) {
super(protocol);
+ dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@Override
@@ -56,21 +62,67 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
ignoreFixTime = getConfig().getBoolean(Keys.PROTOCOL_IGNORE_FIX_TIME.withPrefix(getProtocolName()));
}
- private static final Pattern PATTERN_ACK = new PatternBuilder()
- .text("+ACK:GT")
- .expression("...,") // type
- .number("([0-9A-Z]{2}xxxx),") // protocol version
- .number("(d{15}|x{14}),") // imei
- .any().text(",")
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd),") // time (hhmmss)
- .number("(xxxx)") // counter
- .text("$").optional()
- .compile();
+ private String getDeviceModel(DeviceSession deviceSession, String value) {
+ String model = value.isEmpty() ? getDeviceModel(deviceSession) : value;
+ return model != null ? model.toUpperCase() : "";
+ }
+
+ private Position initPosition(Parser parser, Channel channel, SocketAddress remoteAddress) {
+ if (parser.matches()) {
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession != null) {
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+ return position;
+ }
+ }
+ return null;
+ }
+
+ private void decodeDeviceTime(Position position, Parser parser) {
+ if (parser.hasNext(6)) {
+ if (ignoreFixTime) {
+ position.setTime(parser.nextDateTime());
+ } else {
+ position.setDeviceTime(parser.nextDateTime());
+ }
+ }
+ }
+
+ private Long parseHours(String hoursString) {
+ if (hoursString != null && !hoursString.isEmpty()) {
+ String[] hours = hoursString.split(":");
+ return (Integer.parseInt(hours[0]) * 3600L
+ + (hours.length > 1 ? Integer.parseInt(hours[1]) * 60L : 0)
+ + (hours.length > 2 ? Integer.parseInt(hours[2]) : 0)) * 1000;
+ }
+ return null;
+ }
+
+ private Position decodeAck(Channel channel, SocketAddress remoteAddress, String[] values) throws ParseException {
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[2]);
+ if (deviceSession == null) {
+ return null;
+ }
+ if (values[0].equals("+ACK:GTHBD")) {
+ if (channel != null) {
+ channel.writeAndFlush(new NetworkMessage(
+ "+SACK:GTHBD," + values[1] + "," + values[values.length - 1] + "$", remoteAddress));
+ }
+ } else {
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+ getLastLocation(position, dateFormat.parse(values[values.length - 2]));
+ position.setValid(false);
+ position.set(Position.KEY_RESULT, values[0]);
+ return position;
+ }
+ return null;
+ }
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
@@ -109,374 +161,6 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
.text("$").optional()
.compile();
- private static final Pattern PATTERN_VER = new PatternBuilder()
- .text("+").expression("(?:RESP|BUFF):GTVER,")
- .number("[0-9A-Z]{2}xxxx,") // protocol version
- .number("(d{15}|x{14}),") // imei
- .expression("[^,]*,") // device name
- .expression("([^,]*),") // device type
- .number("(xxxx),") // firmware version
- .number("(xxxx),") // hardware version
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd),") // time (hhmmss)
- .number("(xxxx)") // counter
- .text("$").optional()
- .compile();
-
- private static final Pattern PATTERN_LOCATION = new PatternBuilder()
- .number("(d{1,2}.?d?)?,") // hdop
- .number("(d{1,3}.d)?,") // speed
- .number("(d{1,3}.?d?)?,") // course
- .number("(-?d{1,5}.d)?,") // altitude
- .number("(-?d{1,3}.d{6})?,") // longitude
- .number("(-?d{1,2}.d{6})?,") // latitude
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(d+)?,") // mcc
- .number("(d+)?,") // mnc
- .groupBegin()
- .number("(d+),") // lac
- .number("(d+),") // cid
- .or()
- .number("(x+)?,") // lac
- .number("(x+)?,") // cid
- .groupEnd()
- .number("(?:d+|(d+.d))?,") // rssi / odometer
- .compile();
-
- private static final Pattern PATTERN_OBD = new PatternBuilder()
- .text("+RESP:GTOBD,")
- .number("[0-9A-Z]{2}xxxx,") // protocol version
- .number("(d{15}|x{14}),") // imei
- .expression("(?:[0-9A-Z]{17})?,") // vin
- .expression("[^,]{0,20},") // device name
- .expression("[01],") // report type
- .number("x{1,8},") // report mask
- .expression("(?:[0-9A-Z]{17})?,") // vin
- .number("[01],") // obd connect
- .number("(?:d{1,5})?,") // obd voltage
- .number("(?:x{8})?,") // support pids
- .number("(d{1,5})?,") // engine rpm
- .number("(d{1,3})?,") // speed
- .number("(-?d{1,3})?,") // coolant temp
- .number("(d+.?d*|Inf|NaN)?,") // fuel consumption
- .number("(d{1,5})?,") // dtcs cleared distance
- .number("(?:d{1,5})?,")
- .expression("([01])?,") // obd connect
- .number("(d{1,3})?,") // number of dtcs
- .number("(x*),") // dtcs
- .number("(d{1,3})?,") // throttle
- .number("(?:d{1,3})?,") // engine load
- .number("(d{1,3})?,") // fuel level
- .expression("(?:[0-9A],)?") // obd protocol
- .number("(d+),") // odometer
- .expression(PATTERN_LOCATION.pattern())
- .number("(d{1,7}.d)?,") // odometer
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(xxxx)") // count number
- .text("$").optional()
- .compile();
-
- private static final Pattern PATTERN_FRI = new PatternBuilder()
- .text("+").expression("(?:RESP|BUFF):GT...,")
- .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version
- .number("(d{15}|x{14}),") // imei
- .expression("(?:([0-9A-Z]{17}),)?") // vin
- .expression("[^,]*,") // device name
- .number("(d+)?,") // power
- .number("(d{1,2}),").optional() // report type
- .number("d{1,2},").optional() // count
- .number("d*,").optional() // reserved
- .number("(d+),").optional() // battery
- .expression("((?:")
- .expression(PATTERN_LOCATION.pattern())
- .expression(")+)")
- .groupBegin()
- .number("d{1,2},,")
- .number("(d{1,3}),") // battery
- .number("[01],") // mode
- .number("(?:[01])?,") // motion
- .number("(?:-?d{1,2}.d)?,") // temperature
- .or()
- .number("(d{1,7}.d)?,") // odometer
- .number("(d{5}:dd:dd)?,") // hour meter
- .number("(x+)?,") // adc 1
- .number("(x+)?,") // adc 2
- .number("(d{1,3})?,") // battery
- .number("(?:(xx)(xx)(xx))?,") // device status
- .number("(d+)?,") // rpm
- .number("(?:d+.?d*|Inf|NaN)?,") // fuel consumption
- .number("(d+)?,") // fuel level
- .or()
- .number("(-?d),") // rssi
- .number("(d{1,3}),") // battery
- .or()
- .number("(d{1,7}.d)?,").optional() // odometer
- .number("(d{1,3})?,") // battery
- .groupEnd()
- .any()
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(xxxx)") // count number
- .text("$").optional()
- .compile();
-
- private static final Pattern PATTERN_ERI = new PatternBuilder()
- .text("+").expression("(?:RESP|BUFF):GTERI,")
- .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version
- .number("(d{15}|x{14}),") // imei
- .expression("[^,]*,") // device name
- .number("(x{8}),") // mask
- .number("(d+)?,") // power
- .number("d{1,2},") // report type
- .number("d{1,2},") // count
- .expression("((?:")
- .expression(PATTERN_LOCATION.pattern())
- .expression(")+)")
- .groupBegin()
- .number("(d{1,7}.d)?,") // odometer
- .number("(d{5}:dd:dd)?,") // hour meter
- .number("(x+)?,") // adc 1
- .number("(x+)?,").optional() // adc 2
- .groupBegin()
- .number("(x+)?,") // adc 3
- .number("(xx),") // inputs
- .number("(xx),") // outputs
- .or()
- .number("(d{1,3})?,") // battery
- .number("(?:(xx)(xx)(xx))?,") // device status
- .groupEnd()
- .expression("(.*)") // additional data
- .or()
- .number("d*,,")
- .number("(d+),") // battery
- .any()
- .groupEnd()
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(xxxx)") // count number
- .text("$").optional()
- .compile();
-
- private static final Pattern PATTERN_IGN = new PatternBuilder()
- .text("+").expression("(?:RESP|BUFF):GTIG[NF],")
- .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version
- .number("(d{15}|x{14}),") // imei
- .expression("[^,]*,") // device name
- .number("d+,") // ignition off duration
- .expression(PATTERN_LOCATION.pattern())
- .number("(d{5}:dd:dd)?,") // hour meter
- .number("(d{1,7}.d)?,") // odometer
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(xxxx)") // count number
- .text("$").optional()
- .compile();
-
- private static final Pattern PATTERN_LSW = new PatternBuilder()
- .text("+RESP:").expression("GT[LT]SW,")
- .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version
- .number("(d{15}|x{14}),") // imei
- .expression("[^,]*,") // device name
- .number("[01],") // type
- .number("([01]),") // state
- .expression(PATTERN_LOCATION.pattern())
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(xxxx)") // count number
- .text("$").optional()
- .compile();
-
- private static final Pattern PATTERN_IDA = new PatternBuilder()
- .text("+RESP:GTIDA,")
- .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version
- .number("(d{15}|x{14}),") // imei
- .expression("[^,]*,,") // device name
- .number("([^,]+),") // rfid
- .expression("[01],") // report type
- .number("1,") // count
- .expression(PATTERN_LOCATION.pattern())
- .number("(d+.d),") // odometer
- .text(",,,,")
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(xxxx)") // count number
- .text("$").optional()
- .compile();
-
- private static final Pattern PATTERN_WIF = new PatternBuilder()
- .text("+RESP:GTWIF,")
- .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version
- .number("(d{15}|x{14}),") // imei
- .expression("[^,]*,") // device name
- .number("(d+),") // count
- .number("((?:x{12},-?d+,,,,)+),,,,") // wifi
- .number("(d{1,3}),") // battery
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(xxxx)") // count number
- .text("$").optional()
- .compile();
-
- private static final Pattern PATTERN_GSM = new PatternBuilder()
- .text("+RESP:GTGSM,")
- .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version
- .number("(d{15}|x{14}),") // imei
- .expression("(?:STR|CTN|NMR|RTL),") // fix type
- .expression("(.*)") // cells
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(xxxx)") // count number
- .text("$").optional()
- .compile();
-
- private static final Pattern PATTERN_PNA = new PatternBuilder()
- .text("+RESP:GT").expression("P[NF]A,")
- .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version
- .number("(d{15}|x{14}),") // imei
- .expression("[^,]*,") // device name
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(xxxx)") // count number
- .text("$").optional()
- .compile();
-
- private static final Pattern PATTERN_DAR = new PatternBuilder()
- .text("+RESP:GTDAR,")
- .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version
- .number("(d{15}|x{14}),") // imei
- .expression("[^,]*,") // device name
- .number("(d),") // warning type
- .number("(d{1,2}),,,") // fatigue degree
- .expression(PATTERN_LOCATION.pattern())
- .any()
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(xxxx)") // count number
- .text("$").optional()
- .compile();
-
- private static final Pattern PATTERN = new PatternBuilder()
- .text("+").expression("(?:RESP|BUFF):GT...,")
- .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version
- .number("(d{15}|x{14}),") // imei
- .expression("[^,]*,") // device name
- .number("d*,")
- .number("(x{1,2}),") // report type
- .number("d{1,2},") // count
- .number("d*,").optional() // reserved
- .expression(PATTERN_LOCATION.pattern())
- .groupBegin()
- .number("(?:(d{1,7}.d)|0)?,").optional() // odometer
- .number("(d{1,3})?,") // battery
- .or()
- .number("(d{1,7}.d)?,") // odometer
- .groupEnd()
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)") // time (hhmmss)
- .text(",")
- .number("(xxxx)") // count number
- .text("$").optional()
- .compile();
-
- private static final Pattern PATTERN_BASIC = new PatternBuilder()
- .text("+").expression("(?:RESP|BUFF)").text(":")
- .expression("GT...,")
- .number("(?:[0-9A-Z]{2}xxxx)?,").optional() // protocol version
- .number("(d{15}|x{14}),") // imei
- .any()
- .text(",")
- .number("(d{1,2})?,") // hdop
- .number("(d{1,3}.d)?,") // speed
- .number("(d{1,3})?,") // course
- .number("(-?d{1,5}.d)?,") // altitude
- .number("(-?d{1,3}.d{6})?,") // longitude
- .number("(-?d{1,2}.d{6})?,") // latitude
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(d+),") // mcc
- .number("(d+),") // mnc
- .number("(x+),") // lac
- .number("(x+),").optional(4) // cell
- .any()
- .number("(dddd)(dd)(dd)") // date (yyyymmdd)
- .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
- .text(",")
- .number("(xxxx)") // count number
- .text("$").optional()
- .compile();
-
- private Object decodeAck(Channel channel, SocketAddress remoteAddress, String sentence, String type) {
- Parser parser = new Parser(PATTERN_ACK, sentence);
- if (parser.matches()) {
- String protocolVersion = parser.next();
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
- if (deviceSession == null) {
- return null;
- }
- if (type.equals("HBD")) {
- if (channel != null) {
- parser.skip(6);
- channel.writeAndFlush(new NetworkMessage(
- "+SACK:GTHBD," + protocolVersion + "," + parser.next() + "$", remoteAddress));
- }
- } else {
- Position position = new Position(getProtocolName());
- position.setDeviceId(deviceSession.getDeviceId());
- getLastLocation(position, parser.nextDateTime());
- position.setValid(false);
- position.set(Position.KEY_RESULT, "Command " + type + " accepted");
- return position;
- }
- }
- return null;
- }
-
- private Position initPosition(Parser parser, Channel channel, SocketAddress remoteAddress) {
- if (parser.matches()) {
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
- if (deviceSession != null) {
- Position position = new Position(getProtocolName());
- position.setDeviceId(deviceSession.getDeviceId());
- return position;
- }
- }
- return null;
- }
-
- private void decodeDeviceTime(Position position, Parser parser) {
- if (parser.hasNext(6)) {
- if (ignoreFixTime) {
- position.setTime(parser.nextDateTime());
- } else {
- position.setDeviceTime(parser.nextDateTime());
- }
- }
- }
-
- private Long parseHours(String hoursString) {
- if (hoursString != null) {
- String[] hours = hoursString.split(":");
- return (long) (Integer.parseInt(hours[0]) * 3600
- + (hours.length > 1 ? Integer.parseInt(hours[1]) * 60 : 0)
- + (hours.length > 2 ? Integer.parseInt(hours[2]) : 0)) * 1000;
- }
- return null;
- }
-
private Object decodeInf(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_INF, sentence);
Position position = initPosition(parser, channel, remoteAddress);
@@ -537,6 +221,20 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private static final Pattern PATTERN_VER = new PatternBuilder()
+ .text("+").expression("(?:RESP|BUFF):GTVER,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("[^,]*,") // device name
+ .expression("([^,]*),") // device type
+ .number("(xxxx),") // firmware version
+ .number("(xxxx),") // hardware version
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd),") // time (hhmmss)
+ .number("(xxxx)") // counter
+ .text("$").optional()
+ .compile();
+
private Object decodeVer(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_VER, sentence);
Position position = initPosition(parser, channel, remoteAddress);
@@ -554,9 +252,35 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
}
private void skipLocation(Parser parser) {
- parser.skip(19);
+ parser.skip(20);
}
+ private static final Pattern PATTERN_LOCATION = new PatternBuilder()
+ .number("(d{1,2}.?d?)?,") // hdop
+ .number("(d{1,3}.d)?,") // speed
+ .number("(d{1,3}.?d?)?,") // course
+ .number("(-?d{1,5}.d)?,") // altitude
+ .number("(-?d{1,3}.d{6})?,") // longitude
+ .number("(-?d{1,2}.d{6})?,") // latitude
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .groupBegin()
+ .number(",d+") // wifi count
+ .number("((?:,x{12},-d+,,,)+)") // wifi
+ .groupEnd("?")
+ .text(",")
+ .number("(d+)?,") // mcc
+ .number("(d+)?,") // mnc
+ .groupBegin()
+ .number("(d+),") // lac
+ .number("(d+),") // cid
+ .or()
+ .number("(x+)?,") // lac
+ .number("(x+)?,") // cid
+ .groupEnd()
+ .number("(?:d+|(d+.d))?,") // rssi / odometer
+ .compile();
+
private void decodeLocation(Position position, Parser parser) {
Double hdop = parser.nextDouble();
position.setValid(hdop == null || hdop > 0);
@@ -575,22 +299,125 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
getLastLocation(position, null);
}
+ Network network = new Network();
+
+ if (parser.hasNext()) {
+ String[] values = parser.next().split(",");
+ for (int i = 0; i < values.length; i += 5) {
+ String mac = values[i + 1].replaceAll("(..)", "$1:");
+ network.addWifiAccessPoint(WifiAccessPoint.from(
+ mac.substring(0, mac.length() - 1), Integer.parseInt(values[i + 2])));
+ }
+ }
+
if (parser.hasNext(6)) {
int mcc = parser.nextInt();
int mnc = parser.nextInt();
if (parser.hasNext(2)) {
- position.setNetwork(new Network(CellTower.from(mcc, mnc, parser.nextInt(), parser.nextInt())));
+ network.addCellTower(CellTower.from(mcc, mnc, parser.nextInt(), parser.nextInt()));
}
if (parser.hasNext(2)) {
- position.setNetwork(new Network(CellTower.from(mcc, mnc, parser.nextHexInt(), parser.nextHexInt())));
+ network.addCellTower(CellTower.from(mcc, mnc, parser.nextHexInt(), parser.nextHexInt()));
}
}
+ if (network.getWifiAccessPoints() != null || network.getCellTowers() != null) {
+ position.setNetwork(network);
+ }
+
if (parser.hasNext()) {
position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000);
}
}
+ private int decodeLocation(Position position, String model, String[] values, int index) throws ParseException {
+ double hdop = values[index++].isEmpty() ? 0 : Double.parseDouble(values[index - 1]);
+ position.set(Position.KEY_HDOP, hdop);
+
+ position.setSpeed(UnitsConverter.knotsFromKph(
+ values[index++].isEmpty() ? 0 : Double.parseDouble(values[index - 1])));
+ position.setCourse(values[index++].isEmpty() ? 0 : Integer.parseInt(values[index - 1]));
+ position.setAltitude(values[index++].isEmpty() ? 0 : Double.parseDouble(values[index - 1]));
+
+ if (!values[index].isEmpty()) {
+ position.setValid(true);
+ position.setLongitude(values[index++].isEmpty() ? 0 : Double.parseDouble(values[index - 1]));
+ position.setLatitude(values[index++].isEmpty() ? 0 : Double.parseDouble(values[index - 1]));
+ position.setTime(dateFormat.parse(values[index++]));
+ } else {
+ index += 3;
+ getLastLocation(position, null);
+ }
+
+ Network network = new Network();
+
+ if (!values[index].isEmpty()) {
+ network.addCellTower(CellTower.from(
+ Integer.parseInt(values[index++]),
+ Integer.parseInt(values[index++]),
+ Integer.parseInt(values[index++], 16),
+ Long.parseLong(values[index++], 16)));
+ } else {
+ index += 4;
+ }
+
+ if (network.getWifiAccessPoints() != null || network.getCellTowers() != null) {
+ position.setNetwork(network);
+ }
+
+ if (model.startsWith("GL5")) {
+ index += 1; // csq rssi
+ index += 1; // csq ber
+ }
+
+ if (!values[index++].isEmpty()) {
+ int appendMask = Integer.parseInt(values[index - 1]);
+ if (BitUtil.check(appendMask, 0)) {
+ position.set(Position.KEY_SATELLITES, Integer.parseInt(values[index++]));
+ }
+ if (BitUtil.check(appendMask, 1)) {
+ index += 1; // trigger type
+ }
+ }
+
+ return index;
+ }
+
+ private static final Pattern PATTERN_OBD = new PatternBuilder()
+ .text("+RESP:GTOBD,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("(?:[0-9A-Z]{17})?,") // vin
+ .expression("[^,]{0,20},") // device name
+ .expression("[01],") // report type
+ .number("x{1,8},") // report mask
+ .expression("(?:[0-9A-Z]{17})?,") // vin
+ .number("[01],") // obd connect
+ .number("(?:d{1,5})?,") // obd voltage
+ .number("(?:x{8})?,") // support pids
+ .number("(d{1,5})?,") // engine rpm
+ .number("(d{1,3})?,") // speed
+ .number("(-?d{1,3})?,") // coolant temp
+ .number("(d+.?d*|Inf|NaN)?,") // fuel consumption
+ .number("(d{1,5})?,") // dtcs cleared distance
+ .number("(?:d{1,5})?,")
+ .expression("([01])?,") // obd connect
+ .number("(d{1,3})?,") // number of dtcs
+ .number("(x*),") // dtcs
+ .number("(d{1,3})?,") // throttle
+ .number("(?:d{1,3})?,") // engine load
+ .number("(d{1,3})?,") // fuel level
+ .expression("(?:[0-9A],)?") // obd protocol
+ .number("(d+),") // odometer
+ .expression(PATTERN_LOCATION.pattern())
+ .number("(d{1,7}.d)?,") // odometer
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
private Object decodeObd(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_OBD, sentence);
Position position = initPosition(parser, channel, remoteAddress);
@@ -625,104 +452,140 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
return position;
}
- private Object decodeCan(Channel channel, SocketAddress remoteAddress, String sentence) throws ParseException {
- Position position = new Position(getProtocolName());
-
+ private Object decodeCan(Channel channel, SocketAddress remoteAddress, String[] v) throws ParseException {
int index = 0;
- String[] values = sentence.split(",");
-
index += 1; // header
index += 1; // protocol version
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, v[index++]);
+ if (deviceSession == null) {
+ return null;
+ }
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[index++]);
+ Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
- index += 1; // device name
+ String model = getDeviceModel(deviceSession, v[index++]);
index += 1; // report type
- index += 1; // canbus state
- long reportMask = Long.parseLong(values[index++], 16);
+ index += 1; // can bus state
+ long reportMask = Long.parseLong(v[index++], 16);
long reportMaskExt = 0;
if (BitUtil.check(reportMask, 0)) {
- position.set(Position.KEY_VIN, values[index++]);
+ position.set(Position.KEY_VIN, v[index++]);
}
- if (BitUtil.check(reportMask, 1)) {
- position.set(Position.KEY_IGNITION, Integer.parseInt(values[index++]) > 0);
+ if (BitUtil.check(reportMask, 1) && !v[index++].isEmpty()) {
+ position.set(Position.KEY_IGNITION, Integer.parseInt(v[index - 1]) > 0);
}
- if (BitUtil.check(reportMask, 2)) {
- position.set(Position.KEY_OBD_ODOMETER, values[index++]);
+ if (BitUtil.check(reportMask, 2) && !v[index++].isEmpty()) {
+ position.set(Position.KEY_OBD_ODOMETER, Integer.parseInt(v[index - 1].substring(1)));
}
- if (BitUtil.check(reportMask, 3) && !values[index++].isEmpty()) {
- position.set(Position.KEY_FUEL_USED, Double.parseDouble(values[index - 1]));
+ if (BitUtil.check(reportMask, 3) && !v[index++].isEmpty()) {
+ position.set(Position.KEY_FUEL_USED, Double.parseDouble(v[index - 1]));
}
- if (BitUtil.check(reportMask, 5) && !values[index++].isEmpty()) {
- position.set(Position.KEY_RPM, Integer.parseInt(values[index - 1]));
+ if (BitUtil.check(reportMask, 5) && !v[index++].isEmpty()) {
+ position.set(Position.KEY_RPM, Integer.parseInt(v[index - 1]));
}
- if (BitUtil.check(reportMask, 4) && !values[index++].isEmpty()) {
- position.set(Position.KEY_OBD_SPEED, UnitsConverter.knotsFromKph(Integer.parseInt(values[index - 1])));
+ if (BitUtil.check(reportMask, 4) && !v[index++].isEmpty()) {
+ position.set(Position.KEY_OBD_SPEED, Integer.parseInt(v[index - 1]));
}
- if (BitUtil.check(reportMask, 6) && !values[index++].isEmpty()) {
- position.set(Position.KEY_COOLANT_TEMP, Integer.parseInt(values[index - 1]));
+ if (BitUtil.check(reportMask, 6) && !v[index++].isEmpty()) {
+ position.set(Position.KEY_COOLANT_TEMP, Integer.parseInt(v[index - 1]));
}
- if (BitUtil.check(reportMask, 7) && !values[index++].isEmpty()) {
- position.set(Position.KEY_FUEL_CONSUMPTION, Double.parseDouble(values[index - 1].substring(1)));
+ if (BitUtil.check(reportMask, 7) && !v[index++].isEmpty()) {
+ String value = v[index - 1];
+ if (value.startsWith("L/H")) {
+ position.set(Position.KEY_FUEL_CONSUMPTION, Double.parseDouble(value.substring(3)));
+ }
}
- if (BitUtil.check(reportMask, 8) && !values[index++].isEmpty()) {
- position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(values[index - 1].substring(1)));
+ if (BitUtil.check(reportMask, 8) && !v[index++].isEmpty()) {
+ position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(v[index - 1].substring(1)));
}
- if (BitUtil.check(reportMask, 9) && !values[index++].isEmpty()) {
- position.set("range", Long.parseLong(values[index - 1]) * 100);
+ if (BitUtil.check(reportMask, 9) && !v[index++].isEmpty()) {
+ position.set("range", Long.parseLong(v[index - 1]) * 100);
}
- if (BitUtil.check(reportMask, 10) && !values[index++].isEmpty()) {
- position.set(Position.KEY_THROTTLE, Integer.parseInt(values[index - 1]));
+ if (BitUtil.check(reportMask, 10) && !v[index++].isEmpty()) {
+ position.set(Position.KEY_THROTTLE, Integer.parseInt(v[index - 1]));
}
- if (BitUtil.check(reportMask, 11) && !values[index++].isEmpty()) {
- position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(Double.parseDouble(values[index - 1])));
+ if (BitUtil.check(reportMask, 11) && !v[index++].isEmpty()) {
+ position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(Double.parseDouble(v[index - 1])));
}
- if (BitUtil.check(reportMask, 12)) {
- position.set("drivingHours", Double.parseDouble(values[index++]));
+ if (BitUtil.check(reportMask, 12) && !v[index++].isEmpty()) {
+ position.set(Position.KEY_DRIVING_TIME, Double.parseDouble(v[index - 1]));
}
- if (BitUtil.check(reportMask, 13)) {
- position.set("idleHours", Double.parseDouble(values[index++]));
+ if (BitUtil.check(reportMask, 13) && !v[index++].isEmpty()) {
+ position.set("idleHours", Double.parseDouble(v[index - 1]));
}
- if (BitUtil.check(reportMask, 14) && !values[index++].isEmpty()) {
- position.set("idleFuelConsumption", Double.parseDouble(values[index - 1]));
+ if (BitUtil.check(reportMask, 14) && !v[index++].isEmpty()) {
+ position.set("idleFuelConsumption", Double.parseDouble(v[index - 1]));
}
- if (BitUtil.check(reportMask, 15) && !values[index++].isEmpty()) {
- position.set(Position.KEY_AXLE_WEIGHT, Integer.parseInt(values[index - 1]));
+ if (BitUtil.check(reportMask, 15) && !v[index++].isEmpty()) {
+ position.set(Position.KEY_AXLE_WEIGHT, Integer.parseInt(v[index - 1]));
}
- if (BitUtil.check(reportMask, 16) && !values[index++].isEmpty()) {
- position.set("tachographInfo", Integer.parseInt(values[index - 1], 16));
+ if (BitUtil.check(reportMask, 16) && !v[index++].isEmpty()) {
+ position.set("tachographInfo", Integer.parseInt(v[index - 1], 16));
}
- if (BitUtil.check(reportMask, 17) && !values[index++].isEmpty()) {
- position.set("indicators", Integer.parseInt(values[index - 1], 16));
+ if (BitUtil.check(reportMask, 17) && !v[index++].isEmpty()) {
+ position.set("indicators", Integer.parseInt(v[index - 1], 16));
}
- if (BitUtil.check(reportMask, 18) && !values[index++].isEmpty()) {
- position.set("lights", Integer.parseInt(values[index - 1], 16));
+ if (BitUtil.check(reportMask, 18) && !v[index++].isEmpty()) {
+ position.set("lights", Integer.parseInt(v[index - 1], 16));
}
- if (BitUtil.check(reportMask, 19) && !values[index++].isEmpty()) {
- position.set("doors", Integer.parseInt(values[index - 1], 16));
+ if (BitUtil.check(reportMask, 19) && !v[index++].isEmpty()) {
+ position.set("doors", Integer.parseInt(v[index - 1], 16));
}
- if (BitUtil.check(reportMask, 20) && !values[index++].isEmpty()) {
- position.set("vehicleOverspeed", Double.parseDouble(values[index - 1]));
+ if (BitUtil.check(reportMask, 20) && !v[index++].isEmpty()) {
+ position.set("vehicleOverspeed", Double.parseDouble(v[index - 1]));
}
- if (BitUtil.check(reportMask, 21) && !values[index++].isEmpty()) {
- position.set("engineOverspeed", Double.parseDouble(values[index - 1]));
+ if (BitUtil.check(reportMask, 21) && !v[index++].isEmpty()) {
+ position.set("engineOverspeed", Double.parseDouble(v[index - 1]));
}
- if (BitUtil.check(reportMask, 29)) {
- reportMaskExt = Long.parseLong(values[index++], 16);
+ if ("GV350M".equals(model)) {
+ if (BitUtil.check(reportMask, 22)) {
+ index += 1; // impulse distance
+ }
+ if (BitUtil.check(reportMask, 23)) {
+ index += 1; // gross vehicle weight
+ }
+ if (BitUtil.check(reportMask, 24)) {
+ index += 1; // catalyst liquid level
+ }
+ } 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) && !v[index++].isEmpty()) {
+ reportMaskExt = Long.parseLong(v[index - 1], 16);
}
- if (BitUtil.check(reportMaskExt, 0) && !values[index++].isEmpty()) {
- position.set("adBlueLevel", Integer.parseInt(values[index - 1]));
+ if (BitUtil.check(reportMaskExt, 0) && !v[index++].isEmpty()) {
+ position.set("adBlueLevel", Integer.parseInt(v[index - 1]));
}
- if (BitUtil.check(reportMaskExt, 1) && !values[index++].isEmpty()) {
- position.set("axleWeight1", Integer.parseInt(values[index - 1]));
+ if (BitUtil.check(reportMaskExt, 1) && !v[index++].isEmpty()) {
+ position.set("axleWeight1", Integer.parseInt(v[index - 1]));
}
- if (BitUtil.check(reportMaskExt, 2) && !values[index++].isEmpty()) {
- position.set("axleWeight3", Integer.parseInt(values[index - 1]));
+ if (BitUtil.check(reportMaskExt, 2) && !v[index++].isEmpty()) {
+ position.set("axleWeight3", Integer.parseInt(v[index - 1]));
}
- if (BitUtil.check(reportMaskExt, 3) && !values[index++].isEmpty()) {
- position.set("axleWeight4", Integer.parseInt(values[index - 1]));
+ if (BitUtil.check(reportMaskExt, 3) && !v[index++].isEmpty()) {
+ position.set("axleWeight4", Integer.parseInt(v[index - 1]));
}
if (BitUtil.check(reportMaskExt, 4)) {
index += 1; // tachograph overspeed
@@ -733,8 +596,8 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
if (BitUtil.check(reportMaskExt, 6)) {
index += 1; // tachograph direction
}
- if (BitUtil.check(reportMaskExt, 7) && !values[index++].isEmpty()) {
- position.set(Position.PREFIX_ADC + 1, Integer.parseInt(values[index - 1]) * 0.001);
+ if (BitUtil.check(reportMaskExt, 7) && !v[index++].isEmpty()) {
+ position.set(Position.PREFIX_ADC + 1, Integer.parseInt(v[index - 1]) * 0.001);
}
if (BitUtil.check(reportMaskExt, 8)) {
index += 1; // pedal breaking factor
@@ -757,20 +620,20 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
if (BitUtil.check(reportMaskExt, 14)) {
index += 1; // total brake application
}
- if (BitUtil.check(reportMaskExt, 15) && !values[index++].isEmpty()) {
- position.set("driver1Card", values[index - 1]);
+ if (BitUtil.check(reportMaskExt, 15) && !v[index++].isEmpty()) {
+ position.set("driver1Card", v[index - 1]);
}
- if (BitUtil.check(reportMaskExt, 16) && !values[index++].isEmpty()) {
- position.set("driver2Card", values[index - 1]);
+ if (BitUtil.check(reportMaskExt, 16) && !v[index++].isEmpty()) {
+ position.set("driver2Card", v[index - 1]);
}
- if (BitUtil.check(reportMaskExt, 17) && !values[index++].isEmpty()) {
- position.set("driver1Name", values[index - 1]);
+ if (BitUtil.check(reportMaskExt, 17) && !v[index++].isEmpty()) {
+ position.set("driver1Name", v[index - 1]);
}
- if (BitUtil.check(reportMaskExt, 18) && !values[index++].isEmpty()) {
- position.set("driver2Name", values[index - 1]);
+ if (BitUtil.check(reportMaskExt, 18) && !v[index++].isEmpty()) {
+ position.set("driver2Name", v[index - 1]);
}
- if (BitUtil.check(reportMaskExt, 19) && !values[index++].isEmpty()) {
- position.set("registration", values[index - 1]);
+ if (BitUtil.check(reportMaskExt, 19) && !v[index++].isEmpty()) {
+ position.set("registration", v[index - 1]);
}
if (BitUtil.check(reportMaskExt, 20)) {
index += 1; // expansion information
@@ -788,18 +651,18 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- if (BitUtil.check(reportMask, 30)) {
- while (values[index].isEmpty()) {
+ if (!"GV355CEU".equals(model) && BitUtil.check(reportMask, 30)) {
+ while (v[index].isEmpty()) {
index += 1;
}
- position.setValid(Integer.parseInt(values[index++]) > 0);
- if (!values[index].isEmpty()) {
- position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[index++])));
- position.setCourse(Integer.parseInt(values[index++]));
- position.setAltitude(Double.parseDouble(values[index++]));
- position.setLongitude(Double.parseDouble(values[index++]));
- position.setLatitude(Double.parseDouble(values[index++]));
- position.setTime(dateFormat.parse(values[index++]));
+ position.setValid(Integer.parseInt(v[index++]) > 0);
+ if (!v[index].isEmpty()) {
+ position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(v[index++])));
+ position.setCourse(Integer.parseInt(v[index++]));
+ position.setAltitude(Double.parseDouble(v[index++]));
+ position.setLongitude(Double.parseDouble(v[index++]));
+ position.setLatitude(Double.parseDouble(v[index++]));
+ position.setTime(dateFormat.parse(v[index++]));
} else {
index += 6; // no location
getLastLocation(position, null);
@@ -813,34 +676,79 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
index += 1; // reserved
}
+ index = v.length - 2;
if (ignoreFixTime) {
- position.setTime(dateFormat.parse(values[index]));
+ position.setTime(dateFormat.parse(v[index]));
} else {
- position.setDeviceTime(dateFormat.parse(values[index]));
+ position.setDeviceTime(dateFormat.parse(v[index]));
}
return position;
}
- private void decodeStatus(Position position, Parser parser) {
- if (parser.hasNext(3)) {
- int ignition = parser.nextHexInt();
- if (BitUtil.check(ignition, 4)) {
- position.set(Position.KEY_IGNITION, false);
- } else if (BitUtil.check(ignition, 5)) {
- position.set(Position.KEY_IGNITION, true);
- }
- int input = parser.nextHexInt();
- int output = parser.nextHexInt();
- position.set(Position.KEY_INPUT, input);
- position.set(Position.PREFIX_IN + 1, BitUtil.check(input, 1));
- position.set(Position.PREFIX_IN + 2, BitUtil.check(input, 2));
- position.set(Position.KEY_OUTPUT, output);
- position.set(Position.PREFIX_OUT + 1, BitUtil.check(output, 0));
- position.set(Position.PREFIX_OUT + 2, BitUtil.check(output, 1));
- }
+ private void decodeStatus(Position position, long value) {
+ long ignition = BitUtil.between(value, 2 * 8, 3 * 8);
+ if (BitUtil.check(ignition, 4)) {
+ position.set(Position.KEY_IGNITION, false);
+ } else if (BitUtil.check(ignition, 5)) {
+ position.set(Position.KEY_IGNITION, true);
+ }
+ long input = BitUtil.between(value, 8, 2 * 8);
+ long output = BitUtil.to(value, 8);
+ position.set(Position.KEY_INPUT, input);
+ position.set(Position.PREFIX_IN + 1, BitUtil.check(input, 1));
+ position.set(Position.PREFIX_IN + 2, BitUtil.check(input, 2));
+ position.set(Position.KEY_OUTPUT, output);
+ position.set(Position.PREFIX_OUT + 1, BitUtil.check(output, 0));
+ position.set(Position.PREFIX_OUT + 2, BitUtil.check(output, 1));
}
+ private static final Pattern PATTERN_FRI = new PatternBuilder()
+ .text("+").expression("(?:RESP|BUFF):GT...,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("(?:([0-9A-Z]{17}),)?") // vin
+ .expression("[^,]*,") // device name
+ .number("(d+)?,") // power
+ .number("(d{1,2}),").optional() // report type
+ .number("d{1,2},").optional() // count
+ .number("d*,").optional() // reserved
+ .number("(d+),").optional() // battery
+ .expression("((?:")
+ .expression(PATTERN_LOCATION.pattern())
+ .expression(")+)")
+ .groupBegin()
+ .number("d{1,2},")
+ .number("(d{1,5})?,") // battery
+ .number("(d{1,3}),") // battery level
+ .number("[01],") // mode
+ .number("(?:[01])?,") // motion
+ .number("(-?d{1,2}.d)?,") // temperature
+ .or()
+ .number("(d{1,7}.d)?,") // odometer
+ .number("(d{5}:dd:dd)?,") // hour meter
+ .number("(x+)?,") // adc 1
+ .number("(x+)?,") // adc 2
+ .number("(d{1,3})?,") // battery
+ .number("(x{6})?,") // device status
+ .number("(d+)?,") // rpm
+ .number("(?:d+.?d*|Inf|NaN)?,") // fuel consumption
+ .number("(d+)?,") // fuel level
+ .or()
+ .number("(-?d),") // rssi
+ .number("(d{1,3}),") // battery
+ .or()
+ .number("(d{1,7}.d)?,").optional() // odometer
+ .number("(d{1,3})?,") // battery
+ .groupEnd()
+ .any()
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
private Object decodeFri(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_FRI, sentence);
if (!parser.matches()) {
@@ -883,8 +791,10 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
}
if (parser.hasNext()) {
- position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt());
+ position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001);
}
+ position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt());
+ position.set(Position.PREFIX_TEMP + 1, parser.nextDouble());
if (parser.hasNext()) {
position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000);
@@ -894,7 +804,9 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.PREFIX_ADC + 2, parser.next());
position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt());
- decodeStatus(position, parser);
+ if (parser.hasNext()) {
+ decodeStatus(position, parser.nextHexLong());
+ }
position.set(Position.KEY_RPM, parser.nextInt());
position.set(Position.KEY_FUEL_LEVEL, parser.nextInt());
@@ -921,109 +833,112 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
return positions;
}
- private Object decodeEri(Channel channel, SocketAddress remoteAddress, String sentence) {
- Parser parser = new Parser(PATTERN_ERI, sentence);
- if (!parser.matches()) {
- return null;
- }
-
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ private Object decodeEri(Channel channel, SocketAddress remoteAddress, String[] v) throws ParseException {
+ int index = 0;
+ index += 1; // header
+ index += 1; // protocol version
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, v[index++]);
if (deviceSession == null) {
return null;
}
- long mask = parser.nextHexLong();
+ String model = getDeviceModel(deviceSession, v[index++]);
+ long mask = Long.parseLong(v[index++], 16);
+ Double power = v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]) * 0.001;
+ index += 1; // report type
+ int count = Integer.parseInt(v[index++]);
LinkedList<Position> positions = new LinkedList<>();
-
- Integer power = parser.nextInt();
-
- Parser itemParser = new Parser(PATTERN_LOCATION, parser.next());
- while (itemParser.find()) {
+ for (int i = 0; i < count; i++) {
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
-
- decodeLocation(position, itemParser);
-
+ index = decodeLocation(position, model, v, index);
positions.add(position);
}
Position position = positions.getLast();
+ position.set(Position.KEY_POWER, power);
- skipLocation(parser);
-
- if (power != null) {
- position.set(Position.KEY_POWER, power * 0.001);
+ if (!model.startsWith("GL5")) {
+ position.set(Position.KEY_ODOMETER, v[index++].isEmpty() ? null : Double.parseDouble(v[index - 1]) * 1000);
+ position.set(Position.KEY_HOURS, parseHours(v[index++]));
+ position.set(Position.PREFIX_ADC + 1, v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]) * 0.001);
+ }
+ if (model.startsWith("GV") && !model.startsWith("GV6")) {
+ position.set(Position.PREFIX_ADC + 2, v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]) * 0.001);
}
- if (parser.hasNext(12)) {
-
- position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000);
- position.set(Position.KEY_HOURS, parseHours(parser.next()));
- position.set(Position.PREFIX_ADC + 1, parser.next());
- position.set(Position.PREFIX_ADC + 2, parser.next());
- position.set(Position.PREFIX_ADC + 3, parser.next());
- if (parser.hasNext(2)) {
- position.set(Position.KEY_INPUT, parser.nextHexInt());
- position.set(Position.KEY_OUTPUT, parser.nextHexInt());
- }
- if (parser.hasNext(4)) {
- position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt());
- decodeStatus(position, parser);
+ position.set(Position.KEY_BATTERY_LEVEL, v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]));
+ if (model.startsWith("GL5")) {
+ index += 1; // mode selection
+ position.set(Position.KEY_MOTION, v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]) > 0);
+ } else {
+ if (!v[index++].isEmpty()) {
+ decodeStatus(position, Long.parseLong(v[index - 1]));
}
+ index += 1; // reserved / uart device type
+ }
- int index = 0;
- String[] data = parser.next().split(",");
-
- index += 1; // device type
-
- if (BitUtil.check(mask, 0)) {
- index += 1; // digital fuel sensor data
- }
+ if (BitUtil.check(mask, 0)) {
+ position.set(Position.KEY_FUEL_LEVEL, Integer.parseInt(v[index++], 16));
+ }
- if (BitUtil.check(mask, 1)) {
- int deviceCount = Integer.parseInt(data[index++]);
- for (int i = 1; i <= deviceCount; i++) {
- index += 1; // id
- index += 1; // type
- if (!data[index++].isEmpty()) {
- position.set(Position.PREFIX_TEMP + i, (short) Integer.parseInt(data[index - 1], 16) * 0.0625);
- }
+ if (BitUtil.check(mask, 1)) {
+ int deviceCount = Integer.parseInt(v[index++]);
+ for (int i = 1; i <= deviceCount; i++) {
+ index += 1; // id
+ index += 1; // type
+ if (!v[index++].isEmpty()) {
+ position.set(Position.PREFIX_TEMP + i, (short) Integer.parseInt(v[index - 1], 16) * 0.0625);
}
}
+ }
- if (BitUtil.check(mask, 2)) {
- index += 1; // can data
- }
+ if (BitUtil.check(mask, 2)) {
+ index += 1; // can data
+ }
- if (BitUtil.check(mask, 3) || BitUtil.check(mask, 4)) {
- int deviceCount = Integer.parseInt(data[index++]);
- for (int i = 1; i <= deviceCount; i++) {
- index += 1; // type
- if (BitUtil.check(mask, 3)) {
- position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(data[index++]));
- }
- if (BitUtil.check(mask, 4)) {
- index += 1; // volume
- }
+ if (BitUtil.check(mask, 3) || BitUtil.check(mask, 4)) {
+ int deviceCount = Integer.parseInt(v[index++]);
+ for (int i = 1; i <= deviceCount; i++) {
+ index += 1; // type
+ if (BitUtil.check(mask, 3)) {
+ position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(v[index++]));
+ }
+ if (BitUtil.check(mask, 4)) {
+ index += 1; // volume
}
}
-
}
- if (parser.hasNext()) {
- position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt());
- }
-
- decodeDeviceTime(position, parser);
+ Date time = dateFormat.parse(v[v.length - 2]);
if (ignoreFixTime) {
+ position.setTime(time);
positions.clear();
positions.add(position);
+ } else {
+ position.setDeviceTime(time);
}
return positions;
}
+ private static final Pattern PATTERN_IGN = new PatternBuilder()
+ .text("+").expression("(?:RESP|BUFF):GTIG[NF],")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("[^,]*,") // device name
+ .number("d+,") // ignition off duration
+ .expression(PATTERN_LOCATION.pattern())
+ .number("(d{5}:dd:dd)?,") // hour meter
+ .number("(d{1,7}.d)?,") // odometer
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
private Object decodeIgn(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_IGN, sentence);
Position position = initPosition(parser, channel, remoteAddress);
@@ -1042,6 +957,21 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private static final Pattern PATTERN_LSW = new PatternBuilder()
+ .text("+RESP:").expression("GT[LT]SW,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("[^,]*,") // device name
+ .number("[01],") // type
+ .number("([01]),") // state
+ .expression(PATTERN_LOCATION.pattern())
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
private Object decodeLsw(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_LSW, sentence);
Position position = initPosition(parser, channel, remoteAddress);
@@ -1058,6 +988,24 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private static final Pattern PATTERN_IDA = new PatternBuilder()
+ .text("+RESP:GTIDA,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("[^,]*,,") // device name
+ .number("([^,]+),") // rfid
+ .expression("[01],") // report type
+ .number("1,") // count
+ .expression(PATTERN_LOCATION.pattern())
+ .number("(d+.d),") // odometer
+ .text(",,,,")
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
private Object decodeIda(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_IDA, sentence);
Position position = initPosition(parser, channel, remoteAddress);
@@ -1076,6 +1024,21 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private static final Pattern PATTERN_WIF = new PatternBuilder()
+ .text("+RESP:GTWIF,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("[^,]*,") // device name
+ .number("(d+),") // count
+ .number("((?:x{12},-?d+,,,,)+),,,,") // wifi
+ .number("(d{1,3}),") // battery
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
private Object decodeWif(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_WIF, sentence);
Position position = initPosition(parser, channel, remoteAddress);
@@ -1102,6 +1065,19 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private static final Pattern PATTERN_GSM = new PatternBuilder()
+ .text("+RESP:GTGSM,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("(?:STR|CTN|NMR|RTL),") // fix type
+ .expression("(.*)") // cells
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
private Object decodeGsm(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_GSM, sentence);
Position position = initPosition(parser, channel, remoteAddress);
@@ -1128,6 +1104,18 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private static final Pattern PATTERN_PNA = new PatternBuilder()
+ .text("+RESP:GT").expression("P[NF]A,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("[^,]*,") // device name
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
private Object decodePna(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_PNA, sentence);
Position position = initPosition(parser, channel, remoteAddress);
@@ -1142,6 +1130,22 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private static final Pattern PATTERN_DAR = new PatternBuilder()
+ .text("+RESP:GTDAR,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("[^,]*,") // device name
+ .number("(d),") // warning type
+ .number("(d{1,2}),,,") // fatigue degree
+ .expression(PATTERN_LOCATION.pattern())
+ .any()
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
private Object decodeDar(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_DAR, sentence);
Position position = initPosition(parser, channel, remoteAddress);
@@ -1165,6 +1169,204 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private static final Pattern PATTERN_DTT = new PatternBuilder()
+ .text("+RESP:GTDTT,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("[^,]*,,,") // device name
+ .number("d,") // data type
+ .number("d+,") // data length
+ .number("(x+),") // data
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
+ private Object decodeDtt(Channel channel, SocketAddress remoteAddress, String sentence) {
+ Parser parser = new Parser(PATTERN_DTT, sentence);
+ Position position = initPosition(parser, channel, remoteAddress);
+ if (position == null) {
+ return null;
+ }
+
+ getLastLocation(position, null);
+
+ String data = Unpooled.wrappedBuffer(DataConverter.parseHex(parser.next()))
+ .toString(StandardCharsets.US_ASCII);
+ if (data.contains("COMB")) {
+ position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(data.split(",")[2]));
+ } else {
+ position.set(Position.KEY_RESULT, data);
+ }
+
+ decodeDeviceTime(position, parser);
+
+ return position;
+ }
+
+ private static final Pattern PATTERN_BAA = new PatternBuilder()
+ .text("+RESP:GTBAA,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("[^,]*,") // device name
+ .number("x+,") // index
+ .number("d,") // accessory type
+ .number("d,") // accessory model
+ .number("x+,") // alarm type
+ .number("(x{4}),") // append mask
+ .expression("((?:[^,]+,){0,6})") // accessory optionals
+ .expression(PATTERN_LOCATION.pattern())
+ .any()
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
+ private Object decodeBaa(Channel channel, SocketAddress remoteAddress, String sentence) {
+ Parser parser = new Parser(PATTERN_BAA, sentence);
+ Position position = initPosition(parser, channel, remoteAddress);
+ if (position == null) {
+ return null;
+ }
+
+ int mask = parser.nextHexInt();
+ String[] values = parser.next().split(",");
+ int index = 0;
+ if (BitUtil.check(mask, 0)) {
+ position.set("accessoryName", values[index++]);
+ }
+ if (BitUtil.check(mask, 1)) {
+ position.set("accessoryMac", values[index++]);
+ }
+ if (BitUtil.check(mask, 2)) {
+ position.set("accessoryStatus", Integer.parseInt(values[index++]));
+ }
+ if (BitUtil.check(mask, 3)) {
+ position.set("accessoryVoltage", Integer.parseInt(values[index++]) * 0.001);
+ }
+ if (BitUtil.check(mask, 4)) {
+ position.set("accessoryTemp", Integer.parseInt(values[index++]));
+ }
+ if (BitUtil.check(mask, 5)) {
+ position.set("accessoryHumidity", Integer.parseInt(values[index]));
+ }
+
+ decodeLocation(position, parser);
+
+ decodeDeviceTime(position, parser);
+
+ return position;
+ }
+
+ private static final Pattern PATTERN_BID = new PatternBuilder()
+ .text("+RESP:GTBID,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("[^,]*,") // device name
+ .number("d,") // count
+ .number("d,") // accessory model
+ .number("(x{4}),") // append mask
+ .expression("((?:[^,]+,){0,2})") // accessory optionals
+ .expression(PATTERN_LOCATION.pattern())
+ .any()
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
+ private Object decodeBid(Channel channel, SocketAddress remoteAddress, String sentence) {
+ Parser parser = new Parser(PATTERN_BID, sentence);
+ Position position = initPosition(parser, channel, remoteAddress);
+ if (position == null) {
+ return null;
+ }
+
+ int mask = parser.nextHexInt();
+ String[] values = parser.next().split(",");
+ int index = 0;
+ if (BitUtil.check(mask, 1)) {
+ position.set("accessoryMac", values[index++]);
+ }
+ if (BitUtil.check(mask, 3)) {
+ position.set("accessoryVoltage", Integer.parseInt(values[index]) * 0.001);
+ }
+
+ decodeLocation(position, parser);
+
+ decodeDeviceTime(position, parser);
+
+ return position;
+ }
+
+ private static final Pattern PATTERN_LSA = new PatternBuilder()
+ .text("+RESP:GTLSA,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("[^,]*,") // device name
+ .number("d,") // event state 1
+ .number("d,") // event state 2
+ .number("d+,") // number
+ .expression(PATTERN_LOCATION.pattern())
+ .number("d+,") // bit error rate
+ .number("(d),") // light level
+ .number("(d+),") // battery level
+ .number("[01],") // mode selection
+ .number("[01]?,") // movement status
+ .number("(-?d+.d)?,") // temperature
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
+ private Object decodeLsa(Channel channel, SocketAddress remoteAddress, String sentence) {
+ Parser parser = new Parser(PATTERN_LSA, sentence);
+ Position position = initPosition(parser, channel, remoteAddress);
+ if (position == null) {
+ return null;
+ }
+
+ decodeLocation(position, parser);
+
+ position.set("lightLevel", parser.nextInt());
+ position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt());
+ position.set(Position.PREFIX_TEMP + 1, parser.nextDouble());
+
+ decodeDeviceTime(position, parser);
+
+ return position;
+ }
+
+ private static final Pattern PATTERN = new PatternBuilder()
+ .text("+").expression("(?:RESP|BUFF):GT...,")
+ .expression("(?:.{6}|.{10})?,") // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .expression("[^,]*,") // device name
+ .number("d*,")
+ .number("(x{1,2}),") // report type
+ .number("d{1,2},") // count
+ .number("d*,").optional() // reserved
+ .expression(PATTERN_LOCATION.pattern())
+ .groupBegin()
+ .number("(?:(d{1,7}.d)|0)?,").optional() // odometer
+ .number("(d{1,3})?,") // battery
+ .or()
+ .number("(d{1,7}.d)?,") // odometer
+ .groupEnd()
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)") // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
private Object decodeOther(Channel channel, SocketAddress remoteAddress, String sentence, String type) {
Parser parser = new Parser(PATTERN, sentence);
Position position = initPosition(parser, channel, remoteAddress);
@@ -1215,6 +1417,38 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private static final Pattern PATTERN_BASIC = new PatternBuilder()
+ .text("+").expression("(?:RESP|BUFF)").text(":")
+ .expression("GT...,")
+ .expression("[^,]+,").optional() // protocol version
+ .number("(d{15}|x{14}),") // imei
+ .any()
+ .text(",")
+ .number("(d{1,2}),") // hdop
+ .groupBegin()
+ .number("(d{1,3}.d),") // speed
+ .number("(d{1,3}),") // course
+ .number("(-?d{1,5}.d),") // altitude
+ .number("(-?d{1,3}.d{6}),") // longitude
+ .number("(-?d{1,2}.d{6}),") // latitude
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)") // time (hhmmss)
+ .text(",")
+ .or()
+ .text(",,,,,,")
+ .groupEnd()
+ .number("(d+),") // mcc
+ .number("(d+),") // mnc
+ .number("(x+),") // lac
+ .number("(x+),").optional(4) // cell
+ .any()
+ .number("(dddd)(dd)(dd)") // date (yyyymmdd)
+ .number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
+ .text(",")
+ .number("(xxxx)") // count number
+ .text("$").optional()
+ .compile();
+
private Object decodeBasic(Channel channel, SocketAddress remoteAddress, String sentence, String type) {
Parser parser = new Parser(PATTERN_BASIC, sentence);
Position position = initPosition(parser, channel, remoteAddress);
@@ -1299,17 +1533,19 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
- String sentence = ((ByteBuf) msg).toString(StandardCharsets.US_ASCII);
+ String sentence = ((ByteBuf) msg).toString(StandardCharsets.US_ASCII).replaceAll("\\$$", "");
int typeIndex = sentence.indexOf(":GT");
if (typeIndex < 0) {
return null;
}
+ String[] values = sentence.split(",");
+
Object result;
String type = sentence.substring(typeIndex + 3, typeIndex + 6);
if (sentence.startsWith("+ACK")) {
- result = decodeAck(channel, remoteAddress, sentence, type);
+ result = decodeAck(channel, remoteAddress, values);
} else {
switch (type) {
case "INF":
@@ -1319,7 +1555,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
result = decodeObd(channel, remoteAddress, sentence);
break;
case "CAN":
- result = decodeCan(channel, remoteAddress, sentence);
+ result = decodeCan(channel, remoteAddress, values);
break;
case "CTN":
case "FRI":
@@ -1330,7 +1566,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
result = decodeFri(channel, remoteAddress, sentence);
break;
case "ERI":
- result = decodeEri(channel, remoteAddress, sentence);
+ result = decodeEri(channel, remoteAddress, values);
break;
case "IGN":
case "IGF":
@@ -1359,6 +1595,18 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
case "DAR":
result = decodeDar(channel, remoteAddress, sentence);
break;
+ case "DTT":
+ result = decodeDtt(channel, remoteAddress, sentence);
+ break;
+ case "BAA":
+ result = decodeBaa(channel, remoteAddress, sentence);
+ break;
+ case "BID":
+ result = decodeBid(channel, remoteAddress, sentence);
+ break;
+ case "LSA":
+ result = decodeLsa(channel, remoteAddress, sentence);
+ break;
default:
result = decodeOther(channel, remoteAddress, sentence, type);
break;
@@ -1380,13 +1628,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
}
if (channel != null && getConfig().getBoolean(Keys.PROTOCOL_ACK.withPrefix(getProtocolName()))) {
- String checksum;
- if (sentence.endsWith("$")) {
- checksum = sentence.substring(sentence.length() - 1 - 4, sentence.length() - 1);
- } else {
- checksum = sentence.substring(sentence.length() - 4);
- }
- channel.writeAndFlush(new NetworkMessage("+SACK:" + checksum + "$", remoteAddress));
+ channel.writeAndFlush(new NetworkMessage("+SACK:" + values[values.length - 1] + "$", remoteAddress));
}
return result;
diff --git a/src/main/java/org/traccar/protocol/GlobalSatProtocol.java b/src/main/java/org/traccar/protocol/GlobalSatProtocol.java
index 16b99f426..13f4f2646 100644
--- a/src/main/java/org/traccar/protocol/GlobalSatProtocol.java
+++ b/src/main/java/org/traccar/protocol/GlobalSatProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class GlobalSatProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/GlobalstarProtocol.java b/src/main/java/org/traccar/protocol/GlobalstarProtocol.java
index 293f5fda5..1d9b6b6dd 100644
--- a/src/main/java/org/traccar/protocol/GlobalstarProtocol.java
+++ b/src/main/java/org/traccar/protocol/GlobalstarProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class GlobalstarProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java
index 0ddb95c14..b75e612b8 100644
--- a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import org.traccar.BaseHttpProtocolDecoder;
+import org.traccar.config.Keys;
import org.traccar.session.DeviceSession;
import org.traccar.NetworkMessage;
import org.traccar.Protocol;
@@ -64,6 +65,12 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder {
private final XPath xPath;
private final XPathExpression messageExpression;
+ private boolean alternative;
+
+ public void setAlternative(boolean alternative) {
+ this.alternative = alternative;
+ }
+
public GlobalstarProtocolDecoder(Protocol protocol) {
super(protocol);
try {
@@ -82,6 +89,11 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder {
}
}
+ @Override
+ protected void init() {
+ this.alternative = getConfig().getBoolean(Keys.PROTOCOL_ALTERNATIVE.withPrefix(getProtocolName()));
+ }
+
private void sendResponse(Channel channel, String messageId) throws TransformerException {
Document document = documentBuilder.newDocument();
@@ -135,32 +147,50 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder {
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
- position.setValid(true);
position.setTime(new Date(Long.parseLong(xPath.evaluate("unixTime", node)) * 1000));
ByteBuf buf = Unpooled.wrappedBuffer(
DataConverter.parseHex(xPath.evaluate("payload", node).substring(2)));
int flags = buf.readUnsignedByte();
- position.set(Position.PREFIX_IN + 1, !BitUtil.check(flags, 1));
- position.set(Position.PREFIX_IN + 2, !BitUtil.check(flags, 2));
- position.set(Position.KEY_CHARGE, !BitUtil.check(flags, 3));
- if (BitUtil.check(flags, 4)) {
- position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION);
+ int type;
+ if (alternative) {
+ type = BitUtil.to(flags, 1);
+ position.setValid(true);
+ position.set(Position.PREFIX_IN + 1, !BitUtil.check(flags, 1));
+ position.set(Position.PREFIX_IN + 2, !BitUtil.check(flags, 2));
+ position.set(Position.KEY_CHARGE, !BitUtil.check(flags, 3));
+ if (BitUtil.check(flags, 4)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION);
+ }
+ position.setCourse(BitUtil.from(flags, 5) * 45);
+ } else {
+ type = BitUtil.to(flags, 2);
+ if (BitUtil.check(flags, 2)) {
+ position.set("batteryReplace", true);
+ }
+ position.setValid(!BitUtil.check(flags, 3));
}
- position.setCourse(BitUtil.from(flags, 5) * 45);
-
double latitude = buf.readUnsignedMedium() * 90.0 / (1 << 23);
position.setLatitude(latitude > 90 ? latitude - 180 : latitude);
double longitude = buf.readUnsignedMedium() * 180.0 / (1 << 23);
position.setLongitude(longitude > 180 ? longitude - 360 : longitude);
- int speed = buf.readUnsignedByte();
- position.setSpeed(UnitsConverter.knotsFromKph(speed));
-
- position.set("batteryReplace", BitUtil.check(buf.readUnsignedByte(), 7));
+ int speed = 0;
+ if (alternative) {
+ speed = buf.readUnsignedByte();
+ position.setSpeed(UnitsConverter.knotsFromKph(speed));
+ position.set("batteryReplace", BitUtil.check(buf.readUnsignedByte(), 7));
+ } else if (type == 0) {
+ position.set(Position.KEY_INPUT, BitUtil.to(buf.readUnsignedByte(), 4));
+ int other = buf.readUnsignedByte();
+ if (BitUtil.check(other, 4)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION);
+ }
+ position.set(Position.KEY_MOTION, BitUtil.check(other, 6));
+ }
if (speed != 0xff) {
positions.add(position);
diff --git a/src/main/java/org/traccar/protocol/GnxProtocol.java b/src/main/java/org/traccar/protocol/GnxProtocol.java
index 32d642688..cfa496009 100644
--- a/src/main/java/org/traccar/protocol/GnxProtocol.java
+++ b/src/main/java/org/traccar/protocol/GnxProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class GnxProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/GoSafeProtocol.java b/src/main/java/org/traccar/protocol/GoSafeProtocol.java
index 607931500..c9c0456a0 100644
--- a/src/main/java/org/traccar/protocol/GoSafeProtocol.java
+++ b/src/main/java/org/traccar/protocol/GoSafeProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class GoSafeProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java b/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java
index 77649a041..f17ea0e08 100644
--- a/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java
@@ -93,14 +93,14 @@ public class GoSafeProtocolDecoder extends BaseProtocolDecoder {
position.setSpeed(UnitsConverter.knotsFromKph(Integer.parseInt(values[index - 1])));
}
position.setCourse(Integer.parseInt(values[index++]));
- if (index < values.length) {
- position.setAltitude(Integer.parseInt(values[index++]));
+ if (index < values.length && !values[index++].isEmpty()) {
+ position.setAltitude(Integer.parseInt(values[index - 1]));
}
- if (index < values.length) {
- position.set(Position.KEY_HDOP, Double.parseDouble(values[index++]));
+ if (index < values.length && !values[index++].isEmpty()) {
+ position.set(Position.KEY_HDOP, Double.parseDouble(values[index - 1]));
}
- if (index < values.length) {
- position.set(Position.KEY_VDOP, Double.parseDouble(values[index++]));
+ if (index < values.length && !values[index++].isEmpty()) {
+ position.set(Position.KEY_VDOP, Double.parseDouble(values[index - 1]));
}
break;
case "GSM":
diff --git a/src/main/java/org/traccar/protocol/GotopProtocol.java b/src/main/java/org/traccar/protocol/GotopProtocol.java
index 53fcea0d0..21fbbae99 100644
--- a/src/main/java/org/traccar/protocol/GotopProtocol.java
+++ b/src/main/java/org/traccar/protocol/GotopProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class GotopProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Gps056Protocol.java b/src/main/java/org/traccar/protocol/Gps056Protocol.java
index dbffbfdbb..44fc392be 100644
--- a/src/main/java/org/traccar/protocol/Gps056Protocol.java
+++ b/src/main/java/org/traccar/protocol/Gps056Protocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Gps056Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Gps103Protocol.java b/src/main/java/org/traccar/protocol/Gps103Protocol.java
index 2725494c5..8424abfe5 100644
--- a/src/main/java/org/traccar/protocol/Gps103Protocol.java
+++ b/src/main/java/org/traccar/protocol/Gps103Protocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Gps103Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java
index 28efa3c30..d1c35b478 100644
--- a/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java
@@ -225,7 +225,7 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder {
getConfig(), parser.nextHexInt(0), parser.nextHexInt(0))));
}
- if (parser.hasNext(20)) {
+ if (parser.hasNextAny(20)) {
String utcHours = parser.next();
String utcMinutes = parser.next();
diff --git a/src/main/java/org/traccar/protocol/GpsGateProtocol.java b/src/main/java/org/traccar/protocol/GpsGateProtocol.java
index a6a73ae6b..db1e8554a 100644
--- a/src/main/java/org/traccar/protocol/GpsGateProtocol.java
+++ b/src/main/java/org/traccar/protocol/GpsGateProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class GpsGateProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/GpsMarkerProtocol.java b/src/main/java/org/traccar/protocol/GpsMarkerProtocol.java
index 12b53342c..f50088b2b 100644
--- a/src/main/java/org/traccar/protocol/GpsMarkerProtocol.java
+++ b/src/main/java/org/traccar/protocol/GpsMarkerProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class GpsMarkerProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/GpsmtaProtocol.java b/src/main/java/org/traccar/protocol/GpsmtaProtocol.java
index a474b1e53..e146a816d 100644
--- a/src/main/java/org/traccar/protocol/GpsmtaProtocol.java
+++ b/src/main/java/org/traccar/protocol/GpsmtaProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class GpsmtaProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/GranitProtocol.java b/src/main/java/org/traccar/protocol/GranitProtocol.java
index bb66501e2..9ca0fe25e 100644
--- a/src/main/java/org/traccar/protocol/GranitProtocol.java
+++ b/src/main/java/org/traccar/protocol/GranitProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class GranitProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Gs100Protocol.java b/src/main/java/org/traccar/protocol/Gs100Protocol.java
index 425ca9330..715d48fc4 100644
--- a/src/main/java/org/traccar/protocol/Gs100Protocol.java
+++ b/src/main/java/org/traccar/protocol/Gs100Protocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Gs100Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Gt02Protocol.java b/src/main/java/org/traccar/protocol/Gt02Protocol.java
index fa05761f6..f448feacc 100644
--- a/src/main/java/org/traccar/protocol/Gt02Protocol.java
+++ b/src/main/java/org/traccar/protocol/Gt02Protocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Gt02Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Gt06Protocol.java b/src/main/java/org/traccar/protocol/Gt06Protocol.java
index 38278121c..945ec3831 100644
--- a/src/main/java/org/traccar/protocol/Gt06Protocol.java
+++ b/src/main/java/org/traccar/protocol/Gt06Protocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Gt06Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
index 5b639ddfc..6c0380278 100644
--- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.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,6 +20,7 @@ import io.netty.buffer.ByteBufUtil;
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;
@@ -77,11 +78,12 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_HEARTBEAT = 0x23; // GK310
public static final int MSG_ADDRESS_REQUEST = 0x2A; // GK310
public static final int MSG_ADDRESS_RESPONSE = 0x97; // GK310
- public static final int MSG_GPS_LBS_5 = 0x31; // AZ735
- public static final int MSG_GPS_LBS_STATUS_4 = 0x32; // AZ735
- public static final int MSG_WIFI_5 = 0x33; // AZ735
- public static final int MSG_AZ735_GPS = 0x32; // AZ735 / only extended
- public static final int MSG_AZ735_ALARM = 0x33; // AZ735 / only extended
+ public static final int MSG_GPS_LBS_5 = 0x31; // AZ735 & SL4X
+ public static final int MSG_GPS_LBS_STATUS_4 = 0x32; // AZ735 & SL4X
+ public static final int MSG_WIFI_5 = 0x33; // AZ735 & SL4X
+ public static final int MSG_LBS_3 = 0x34; // SL4X
+ public static final int MSG_AZ735_GPS = 0x32; // AZ735 (extended)
+ public static final int MSG_AZ735_ALARM = 0x33; // AZ735 (only extended)
public static final int MSG_X1_GPS = 0x34;
public static final int MSG_X1_PHOTO_INFO = 0x35;
public static final int MSG_X1_PHOTO_DATA = 0x36;
@@ -95,7 +97,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_INFO = 0x94;
public static final int MSG_SERIAL = 0x9B;
public static final int MSG_STRING_INFO = 0x21;
- public static final int MSG_GPS_2 = 0xA0; // GK310
+ public static final int MSG_GPS_LBS_7 = 0xA0; // GK310 & JM-VL03
public static final int MSG_LBS_2 = 0xA1; // GK310
public static final int MSG_WIFI_3 = 0xA2; // GK310
public static final int MSG_FENCE_SINGLE = 0xA3; // GK310
@@ -119,6 +121,10 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
SPACE10X,
STANDARD,
OBD6,
+ WETRUST,
+ JC400,
+ SL4X,
+ SEEWORLD,
}
private Variant variant;
@@ -168,7 +174,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
case MSG_GPS_LBS_STATUS_4:
case MSG_GPS_PHONE:
case MSG_GPS_LBS_EXTEND:
- case MSG_GPS_2:
+ case MSG_GPS_LBS_7:
case MSG_FENCE_SINGLE:
case MSG_FENCE_MULTI:
return true;
@@ -190,7 +196,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
case MSG_GPS_LBS_STATUS_2:
case MSG_GPS_LBS_STATUS_3:
case MSG_GPS_LBS_STATUS_4:
- case MSG_GPS_2:
+ case MSG_GPS_LBS_7:
case MSG_FENCE_SINGLE:
case MSG_FENCE_MULTI:
case MSG_LBS_ALARM:
@@ -267,12 +273,12 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
}
public static boolean decodeGps(Position position, ByteBuf buf, boolean hasLength, TimeZone timezone) {
- return decodeGps(position, buf, hasLength, true, true, timezone);
+ return decodeGps(position, buf, hasLength, true, true, false, timezone);
}
public static boolean decodeGps(
Position position, ByteBuf buf, boolean hasLength, boolean hasSatellites,
- boolean hasSpeed, TimeZone timezone) {
+ boolean hasSpeed, boolean longSpeed, TimeZone timezone) {
DateBuilder dateBuilder = new DateBuilder(timezone)
.setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte())
@@ -291,7 +297,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
double longitude = buf.readUnsignedInt() / 60.0 / 30000.0;
if (hasSpeed) {
- position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte()));
+ position.setSpeed(UnitsConverter.knotsFromKph(
+ longSpeed ? buf.readUnsignedShort() : buf.readUnsignedByte()));
}
int flags = buf.readUnsignedShort();
@@ -337,21 +344,21 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
int mcc = buf.readUnsignedShort();
int mnc;
- if (BitUtil.check(mcc, 15) || type == MSG_GPS_LBS_6) {
+ if (BitUtil.check(mcc, 15) || type == MSG_GPS_LBS_6 || variant == Variant.SL4X) {
mnc = buf.readUnsignedShort();
} else {
mnc = buf.readUnsignedByte();
}
int lac;
- if (type == MSG_LBS_ALARM) {
+ if (type == MSG_LBS_ALARM || type == MSG_GPS_LBS_7) {
lac = buf.readInt();
} else {
lac = buf.readUnsignedShort();
}
long cid;
- if (type == MSG_LBS_ALARM) {
+ if (type == MSG_LBS_ALARM || type == MSG_GPS_LBS_7) {
cid = buf.readLong();
- } else if (type == MSG_GPS_LBS_6) {
+ } else if (type == MSG_GPS_LBS_6 || variant == Variant.SEEWORLD) {
cid = buf.readUnsignedInt();
} else {
cid = buf.readUnsignedMedium();
@@ -434,15 +441,18 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
return Position.ALARM_REMOVING;
case 0x23:
return Position.ALARM_FALL_DOWN;
+ case 0x28:
+ return Position.ALARM_BRAKING;
case 0x29:
return Position.ALARM_ACCELERATION;
- case 0x30:
- return Position.ALARM_BRAKING;
case 0x2A:
case 0x2B:
+ case 0x2E:
return Position.ALARM_CORNERING;
case 0x2C:
return Position.ALARM_ACCIDENT;
+ case 0x30:
+ return Position.ALARM_JAMMING;
default:
return null;
}
@@ -473,28 +483,21 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
buf.readUnsignedShort(); // type
deviceSession = getDeviceSession(channel, remoteAddress, imei);
- if (deviceSession != null && !deviceSession.contains(DeviceSession.KEY_TIMEZONE)) {
- deviceSession.set(DeviceSession.KEY_TIMEZONE, getTimeZone(deviceSession.getDeviceId()));
- }
-
- if (dataLength > 10) {
- int extensionBits = buf.readUnsignedShort();
- int hours = (extensionBits >> 4) / 100;
- int minutes = (extensionBits >> 4) % 100;
- int offset = (hours * 60 + minutes) * 60;
- if ((extensionBits & 0x8) != 0) {
- offset = -offset;
- }
- if (deviceSession != null) {
- TimeZone timeZone = deviceSession.get(DeviceSession.KEY_TIMEZONE);
- if (timeZone.getRawOffset() == 0) {
- timeZone.setRawOffset(offset * 1000);
- deviceSession.set(DeviceSession.KEY_TIMEZONE, timeZone);
+ if (deviceSession != null) {
+ TimeZone timeZone = getTimeZone(deviceSession.getDeviceId(), null);
+ if (timeZone == null && dataLength > 10) {
+ int extensionBits = buf.readUnsignedShort();
+ int hours = (extensionBits >> 4) / 100;
+ int minutes = (extensionBits >> 4) % 100;
+ int offset = (hours * 60 + minutes) * 60;
+ if ((extensionBits & 0x8) != 0) {
+ offset = -offset;
}
+ timeZone = TimeZone.getTimeZone("UTC");
+ timeZone.setRawOffset(offset * 1000);
}
- }
+ deviceSession.set(DeviceSession.KEY_TIMEZONE, timeZone);
- if (deviceSession != null) {
sendResponse(channel, false, type, buf.getShort(buf.writerIndex() - 6), null);
}
@@ -545,7 +548,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
return null;
- } else if (type == MSG_X1_GPS) {
+ } else if (type == MSG_X1_GPS && variant != Variant.SL4X) {
buf.readUnsignedInt(); // data and alarm
@@ -678,41 +681,50 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
return position;
} else if (type == MSG_LBS_MULTIPLE_1 || type == MSG_LBS_MULTIPLE_2 || type == MSG_LBS_MULTIPLE_3
- || type == MSG_LBS_EXTEND || type == MSG_LBS_WIFI || type == MSG_LBS_2
+ || type == MSG_LBS_EXTEND || type == MSG_LBS_WIFI || type == MSG_LBS_2 || type == MSG_LBS_3
|| type == MSG_WIFI_3 || type == MSG_WIFI_5) {
- boolean longFormat = type == MSG_LBS_2 || type == MSG_WIFI_3 || type == MSG_WIFI_5;
-
DateBuilder dateBuilder = new DateBuilder((TimeZone) deviceSession.get(DeviceSession.KEY_TIMEZONE))
.setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte())
.setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte());
getLastLocation(position, dateBuilder.getDate());
- if (variant == Variant.WANWAY_S20) {
+ if (variant == Variant.WANWAY_S20 || variant == Variant.SL4X) {
buf.readUnsignedByte(); // ta
}
int mcc = buf.readUnsignedShort();
- int mnc = BitUtil.check(mcc, 15) ? buf.readUnsignedShort() : buf.readUnsignedByte();
+ int mnc = BitUtil.check(mcc, 15) || variant == Variant.SL4X
+ ? buf.readUnsignedShort() : buf.readUnsignedByte();
Network network = new Network();
int cellCount = variant == Variant.WANWAY_S20 ? buf.readUnsignedByte() : type == MSG_WIFI_5 ? 6 : 7;
for (int i = 0; i < cellCount; i++) {
- int lac = longFormat ? buf.readInt() : buf.readUnsignedShort();
- int cid = longFormat ? (int) buf.readLong() : buf.readUnsignedMedium();
+ int lac;
+ int cid;
+ if (type == MSG_LBS_2 || type == MSG_WIFI_3) {
+ lac = buf.readInt();
+ cid = (int) buf.readLong();
+ } else if (type == MSG_WIFI_5 || type == MSG_LBS_3) {
+ lac = buf.readUnsignedShort();
+ cid = (int) buf.readUnsignedInt();
+ } else {
+ lac = buf.readUnsignedShort();
+ cid = buf.readUnsignedMedium();
+ }
int rssi = -buf.readUnsignedByte();
if (lac > 0) {
network.addCellTower(CellTower.from(BitUtil.to(mcc, 15), mnc, lac, cid, rssi));
}
}
- if (variant != Variant.WANWAY_S20) {
+ if (variant != Variant.WANWAY_S20 && variant != Variant.SL4X) {
buf.readUnsignedByte(); // ta
}
if (type != MSG_LBS_MULTIPLE_1 && type != MSG_LBS_MULTIPLE_2 && type != MSG_LBS_MULTIPLE_3
- && type != MSG_LBS_2) {
+ && type != MSG_LBS_2 && type != MSG_LBS_3) {
int wifiCount = buf.readUnsignedByte();
for (int i = 0; i < wifiCount; i++) {
String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:");
@@ -805,7 +817,11 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
}
if (hasLbs(type) && buf.readableBytes() > 6) {
- decodeLbs(position, buf, type, hasStatus(type) && type != MSG_LBS_ALARM && type != MSG_LBS_STATUS);
+ boolean hasLength = hasStatus(type)
+ && type != MSG_LBS_STATUS
+ && type != MSG_LBS_ALARM
+ && (type != MSG_GPS_LBS_STATUS_1 || variant != Variant.VXT01);
+ decodeLbs(position, buf, type, hasLength);
}
if (hasStatus(type)) {
@@ -821,9 +837,13 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
buf.readUnsignedByte(); // working mode
position.set(Position.KEY_POWER, buf.readUnsignedShort() / 100.0);
} else {
- position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte() * 100 / 6);
+ int battery = buf.readUnsignedByte();
+ position.set(Position.KEY_BATTERY_LEVEL, battery <= 6 ? battery * 100 / 6 : battery);
position.set(Position.KEY_RSSI, buf.readUnsignedByte());
- position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte()));
+ short alarmExtension = buf.readUnsignedByte();
+ if (variant != Variant.VXT01) {
+ position.set(Position.KEY_ALARM, decodeAlarm(alarmExtension));
+ }
}
}
@@ -833,7 +853,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
String data = buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString();
buf.readUnsignedByte(); // alarm
buf.readUnsignedByte(); // swiped
- position.set("driverLicense", data.trim());
+ position.set(Position.KEY_CARD, data.trim());
} else if (variant == Variant.BENWAY) {
int mask = buf.readUnsignedShort();
position.set(Position.KEY_IGNITION, BitUtil.check(mask, 8 + 7));
@@ -869,9 +889,30 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
}
position.set(Position.PREFIX_TEMP + 1, temperature);
position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 10);
+ } else if (variant == Variant.WETRUST) {
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());
+ position.set(Position.KEY_CARD, buf.readCharSequence(
+ buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString());
+ position.set(Position.KEY_ALARM, buf.readUnsignedByte() > 0 ? Position.ALARM_GENERAL : null);
+ position.set("cardStatus", buf.readUnsignedByte());
+ position.set(Position.KEY_DRIVING_TIME, buf.readUnsignedShort());
}
}
+ if (type == MSG_GPS_LBS_2 && variant == Variant.SEEWORLD) {
+ position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0);
+ buf.readUnsignedByte(); // reporting mode
+ buf.readUnsignedByte(); // supplementary transmission
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());
+ buf.readUnsignedInt(); // travel time
+ int temperature = buf.readUnsignedShort();
+ if (BitUtil.check(temperature, 15)) {
+ temperature = -BitUtil.to(temperature, 15);
+ }
+ position.set(Position.PREFIX_TEMP + 1, temperature * 0.01);
+ position.set("humidity", buf.readUnsignedShort() * 0.01);
+ }
+
if ((type == MSG_GPS_LBS_2 || type == MSG_GPS_LBS_3 || type == MSG_GPS_LBS_4)
&& buf.readableBytes() >= 3 + 6) {
position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0);
@@ -898,6 +939,12 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
}
}
+ if (buf.readableBytes() == 3 + 6 || buf.readableBytes() == 3 + 4 + 6) {
+ position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0);
+ buf.readUnsignedByte(); // upload mode
+ position.set(Position.KEY_ARCHIVE, buf.readUnsignedByte() > 0 ? true : null);
+ }
+
if (buf.readableBytes() == 4 + 6) {
position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());
}
@@ -906,24 +953,44 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
boolean extendedAlarm = dataLength > 7;
if (extendedAlarm) {
- decodeGps(position, buf, false, false, false, deviceSession.get(DeviceSession.KEY_TIMEZONE));
+ if (variant == Variant.JC400) {
+ buf.readUnsignedShort(); // marker
+ buf.readUnsignedByte(); // version
+ }
+ decodeGps(
+ position, buf, false,
+ variant == Variant.JC400, variant == Variant.JC400, variant == Variant.JC400,
+ deviceSession.get(DeviceSession.KEY_TIMEZONE));
} else {
DateBuilder dateBuilder = new DateBuilder((TimeZone) deviceSession.get(DeviceSession.KEY_TIMEZONE))
.setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte())
.setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte());
getLastLocation(position, dateBuilder.getDate());
}
- short alarmType = buf.readUnsignedByte();
- switch (alarmType) {
+ if (variant == Variant.JC400) {
+ position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1);
+ }
+ short event = buf.readUnsignedByte();
+ position.set(Position.KEY_EVENT, event);
+ switch (event) {
case 0x01:
position.set(Position.KEY_ALARM, extendedAlarm ? Position.ALARM_SOS : Position.ALARM_GENERAL);
break;
+ case 0x0E:
+ position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER);
+ break;
+ case 0x76:
+ position.set(Position.KEY_ALARM, Position.ALARM_TEMPERATURE);
+ break;
case 0x80:
position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION);
break;
case 0x87:
position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED);
break;
+ case 0x88:
+ position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT);
+ break;
case 0x90:
position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION);
break;
@@ -937,7 +1004,6 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT);
break;
default:
- position.set(Position.KEY_ALARM, Position.ALARM_GENERAL);
break;
}
@@ -1020,6 +1086,29 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort() * 0.01);
return position;
+ } else if (subType == 0x04) {
+
+ CharSequence content = buf.readCharSequence(buf.readableBytes() - 4 - 2, StandardCharsets.US_ASCII);
+ String[] values = content.toString().split(";");
+ for (String value : values) {
+ String[] pair = value.split("=");
+ switch (pair[0]) {
+ case "ALM1":
+ case "ALM2":
+ case "ALM3":
+ position.set("alarm" + pair[0].charAt(3) + "Status", Integer.parseInt(pair[1], 16));
+ case "STA1":
+ position.set("otherStatus", Integer.parseInt(pair[1], 16));
+ break;
+ case "DYD":
+ position.set("engineStatus", Integer.parseInt(pair[1], 16));
+ break;
+ default:
+ break;
+ }
+ }
+ return position;
+
} else if (subType == 0x05) {
if (buf.readableBytes() >= 6 + 1 + 6) {
@@ -1339,19 +1428,13 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
getLastLocation(position, null);
buf.readUnsignedByte(); // external device type code
- int length = buf.readableBytes() - 9; // line break + checksum + index + checksum + footer
- if (length <= 0) {
- return null;
- } else if (length < 8) {
- position.set(
- Position.PREFIX_TEMP + 1,
- Double.parseDouble(buf.readCharSequence(length - 1, StandardCharsets.US_ASCII).toString()));
+ ByteBuf data = buf.readSlice(buf.readableBytes() - 6); // index + checksum + footer
+ if (BufferUtil.isPrintable(data, data.readableBytes())) {
+ String value = data.readCharSequence(data.readableBytes(), StandardCharsets.US_ASCII).toString();
+ position.set(Position.KEY_RESULT, value.trim());
} else {
- buf.readUnsignedByte(); // card type
- position.set(
- Position.KEY_DRIVER_UNIQUE_ID,
- buf.readCharSequence(length - 1, StandardCharsets.US_ASCII).toString());
+ position.set(Position.KEY_RESULT, ByteBufUtil.hexDump(data));
}
return position;
@@ -1391,6 +1474,18 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
variant = Variant.SPACE10X;
} else if (header == 0x7878 && type == MSG_STATUS && length == 0x13) {
variant = Variant.OBD6;
+ } else if (header == 0x7878 && type == MSG_GPS_LBS_1 && length == 0x29) {
+ variant = Variant.WETRUST;
+ } else if (header == 0x7878 && type == MSG_ALARM && buf.getUnsignedShort(buf.readerIndex() + 4) == 0xffff) {
+ variant = Variant.JC400;
+ } else if (header == 0x7878 && type == MSG_LBS_3 && length == 0x37) {
+ variant = Variant.SL4X;
+ } else if (header == 0x7878 && type == MSG_GPS_LBS_STATUS_4 && length == 0x27) {
+ variant = Variant.SL4X;
+ } else if (header == 0x7878 && type == MSG_GPS_LBS_2 && length == 0x2f) {
+ variant = Variant.SEEWORLD;
+ } else if (header == 0x7878 && type == MSG_GPS_LBS_STATUS_1 && length == 0x26) {
+ variant = Variant.SEEWORLD;
} else {
variant = Variant.STANDARD;
}
diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java
index dc5dd446f..fd6bb8451 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.config.Keys;
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 @@ public class Gt06ProtocolEncoder extends BaseProtocolEncoder {
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 @@ public class Gt06ProtocolEncoder extends BaseProtocolEncoder {
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/Gt30Protocol.java b/src/main/java/org/traccar/protocol/Gt30Protocol.java
index 6b79ba58b..fdfc80502 100644
--- a/src/main/java/org/traccar/protocol/Gt30Protocol.java
+++ b/src/main/java/org/traccar/protocol/Gt30Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Gt30Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/H02Protocol.java b/src/main/java/org/traccar/protocol/H02Protocol.java
index 4e5f8c96a..ba5aeaa26 100644
--- a/src/main/java/org/traccar/protocol/H02Protocol.java
+++ b/src/main/java/org/traccar/protocol/H02Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class H02Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/HaicomProtocol.java b/src/main/java/org/traccar/protocol/HaicomProtocol.java
index f56c605f0..bcc491ada 100644
--- a/src/main/java/org/traccar/protocol/HaicomProtocol.java
+++ b/src/main/java/org/traccar/protocol/HaicomProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class HaicomProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/HomtecsProtocol.java b/src/main/java/org/traccar/protocol/HomtecsProtocol.java
index aa2d7d852..c04efb945 100644
--- a/src/main/java/org/traccar/protocol/HomtecsProtocol.java
+++ b/src/main/java/org/traccar/protocol/HomtecsProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class HomtecsProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/HoopoProtocol.java b/src/main/java/org/traccar/protocol/HoopoProtocol.java
index 02d8e5a8e..3fc0887d8 100644
--- a/src/main/java/org/traccar/protocol/HoopoProtocol.java
+++ b/src/main/java/org/traccar/protocol/HoopoProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class HoopoProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/HoopoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HoopoProtocolDecoder.java
index 708c74f2a..7433e7fce 100644
--- a/src/main/java/org/traccar/protocol/HoopoProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/HoopoProtocolDecoder.java
@@ -21,8 +21,8 @@ import org.traccar.session.DeviceSession;
import org.traccar.Protocol;
import org.traccar.model.Position;
-import javax.json.Json;
-import javax.json.JsonObject;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
import java.io.StringReader;
import java.net.SocketAddress;
import java.time.OffsetDateTime;
diff --git a/src/main/java/org/traccar/protocol/HuaShengProtocol.java b/src/main/java/org/traccar/protocol/HuaShengProtocol.java
index b1b61e977..7246e97e6 100644
--- a/src/main/java/org/traccar/protocol/HuaShengProtocol.java
+++ b/src/main/java/org/traccar/protocol/HuaShengProtocol.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,17 +19,25 @@ import org.traccar.BaseProtocol;
import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
+import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class HuaShengProtocol extends BaseProtocol {
@Inject
public HuaShengProtocol(Config config) {
+ setSupportedDataCommands(
+ Command.TYPE_POSITION_PERIODIC,
+ Command.TYPE_OUTPUT_CONTROL,
+ Command.TYPE_ALARM_ARM,
+ Command.TYPE_ALARM_DISARM,
+ Command.TYPE_SET_SPEED_LIMIT);
addServer(new TrackerServer(config, getName(), false) {
@Override
protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
pipeline.addLast(new HuaShengFrameDecoder());
+ pipeline.addLast(new HuaShengProtocolEncoder(HuaShengProtocol.this));
pipeline.addLast(new HuaShengProtocolDecoder(HuaShengProtocol.this));
}
});
diff --git a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java
index 371691d82..7d634b0f2 100644
--- a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,6 +48,10 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_UPFAULT_RSP = 0xFF13;
public static final int MSG_HSO_REQ = 0x0002;
public static final int MSG_HSO_RSP = 0x0003;
+ public static final int MSG_SET_REQ = 0xAA04;
+ public static final int MSG_SET_RSP = 0xFF05;
+ public static final int MSG_CTRL_REQ = 0xAA16;
+ public static final int MSG_CTRL_RSP = 0xFF17;
private void sendResponse(Channel channel, int type, int index, ByteBuf content) {
if (channel != null) {
@@ -225,13 +229,14 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder {
position.setCourse(buf.readUnsignedShort());
position.setAltitude(buf.readUnsignedShort());
- position.set(Position.KEY_ODOMETER, buf.readUnsignedShort() * 1000);
+ buf.readUnsignedShort(); // odometer speed
Network network = new Network();
while (buf.readableBytes() > 4) {
int subtype = buf.readUnsignedShort();
int length = buf.readUnsignedShort() - 4;
+ int endIndex = buf.readerIndex() + length;
switch (subtype) {
case 0x0001:
int coolantTemperature = buf.readUnsignedByte() - 40;
@@ -249,6 +254,9 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01);
position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte() * 0.4);
buf.readUnsignedInt(); // trip id
+ if (buf.readerIndex() < endIndex) {
+ position.set("adBlueLevel", buf.readUnsignedByte() * 0.4);
+ }
break;
case 0x0005:
position.set(Position.KEY_RSSI, buf.readUnsignedByte());
@@ -256,8 +264,11 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder {
buf.readUnsignedInt(); // run time
break;
case 0x0009:
- position.set(
- Position.KEY_VIN, buf.readCharSequence(length, StandardCharsets.US_ASCII).toString());
+ position.set(Position.KEY_VIN, buf.readCharSequence(length, StandardCharsets.US_ASCII).toString());
+ break;
+ case 0x0010:
+ position.set(Position.KEY_ODOMETER, Double.parseDouble(
+ buf.readCharSequence(length, StandardCharsets.US_ASCII).toString()) * 1000);
break;
case 0x0011:
position.set(Position.KEY_HOURS, buf.readUnsignedInt() * 0.05);
@@ -276,7 +287,7 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder {
String[] values = cell.split("@");
network.addCellTower(CellTower.from(
Integer.parseInt(values[0]), Integer.parseInt(values[1]),
- Integer.parseInt(values[2], 16), Integer.parseInt(values[3], 16)));
+ Integer.parseInt(values[2], 16), Long.parseLong(values[3], 16)));
}
break;
case 0x0021:
@@ -291,6 +302,7 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder {
buf.skipBytes(length);
break;
}
+ buf.readerIndex(endIndex);
}
if (network.getCellTowers() != null || network.getWifiAccessPoints() != null) {
diff --git a/src/main/java/org/traccar/protocol/HuaShengProtocolEncoder.java b/src/main/java/org/traccar/protocol/HuaShengProtocolEncoder.java
new file mode 100644
index 000000000..dc34f7b4e
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/HuaShengProtocolEncoder.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.traccar.BaseProtocolEncoder;
+import org.traccar.Protocol;
+import org.traccar.model.Command;
+
+public class HuaShengProtocolEncoder extends BaseProtocolEncoder {
+
+ public HuaShengProtocolEncoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ private ByteBuf encodeContent(int type, ByteBuf content) {
+
+ ByteBuf buf = Unpooled.buffer();
+ buf.writeByte(0xC0);
+ buf.writeShort(0x0000); // flag and version
+ buf.writeShort(12 + content.readableBytes());
+ buf.writeShort(type);
+ buf.writeShort(0); // checksum
+ buf.writeInt(1); // index
+ buf.writeBytes(content);
+ content.release();
+ buf.writeByte(0xC0);
+
+ return buf;
+ }
+
+ @Override
+ protected Object encodeCommand(Command command) {
+
+ ByteBuf content = Unpooled.buffer(0);
+ switch (command.getType()) {
+ case Command.TYPE_POSITION_PERIODIC:
+ content.writeShort(0x0002);
+ content.writeShort(6); // length
+ content.writeShort(command.getInteger(Command.KEY_FREQUENCY));
+ return encodeContent(HuaShengProtocolDecoder.MSG_SET_REQ, content);
+ case Command.TYPE_OUTPUT_CONTROL:
+ /*
+0x01: Lock the relay1; //relay on
+0x02: Unlock the relay1; //relay off
+0x03: Lock the relay2; //relay2 on
+0x04: Unlock the relay2; //relay2 off
+0x05: Lock the relay3; //relay3 on
+0x06: Unlock the relay3; //realy3 off
+ */
+ content.writeByte(
+ (command.getInteger(Command.KEY_INDEX) - 1) * 2
+ + (2 - command.getInteger(Command.KEY_DATA)));
+ return encodeContent(HuaShengProtocolDecoder.MSG_CTRL_REQ, content);
+ case Command.TYPE_ALARM_ARM:
+ case Command.TYPE_ALARM_DISARM:
+ content.writeShort(0x0001);
+ content.writeShort(5); // length
+ content.writeByte(command.getType().equals(Command.TYPE_ALARM_ARM) ? 1 : 0);
+ return encodeContent(HuaShengProtocolDecoder.MSG_SET_REQ, content);
+ case Command.TYPE_SET_SPEED_LIMIT:
+ content.writeShort(0x0004);
+ content.writeShort(6); // length
+ content.writeShort(command.getInteger(Command.KEY_DATA));
+ return encodeContent(HuaShengProtocolDecoder.MSG_SET_REQ, content);
+ default:
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocol.java b/src/main/java/org/traccar/protocol/HuabaoProtocol.java
index c37918b0e..fc12d7d71 100644
--- a/src/main/java/org/traccar/protocol/HuabaoProtocol.java
+++ b/src/main/java/org/traccar/protocol/HuabaoProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class HuabaoProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
index d0bbeebb5..0a8540543 100644
--- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -131,7 +131,10 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
if (BitUtil.check(value, 8)) {
return Position.ALARM_POWER_OFF;
}
- if (BitUtil.check(value, 17)) {
+ if (BitUtil.check(value, 15)) {
+ return Position.ALARM_VIBRATION;
+ }
+ if (BitUtil.check(value, 16) || BitUtil.check(value, 17)) {
return Position.ALARM_TAMPERING;
}
if (BitUtil.check(value, 20)) {
@@ -140,7 +143,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
if (BitUtil.check(value, 28)) {
return Position.ALARM_MOVEMENT;
}
- if (BitUtil.check(value, 29)) {
+ if (BitUtil.check(value, 29) || BitUtil.check(value, 30)) {
return Position.ALARM_ACCIDENT;
}
return null;
@@ -169,7 +172,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
} else {
long imei = id.getUnsignedShort(0);
imei = (imei << 32) + id.getUnsignedInt(2);
- return String.valueOf(imei);
+ return String.valueOf(imei) + Checksum.luhn(imei);
}
}
@@ -294,6 +297,8 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
} else if (type == MSG_TRANSPARENT) {
+ sendGeneralResponse(channel, remoteAddress, id, type, index);
+
return decodeTransparent(deviceSession, buf);
}
@@ -327,6 +332,16 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
case 0x03:
position.set(Position.KEY_OBD_SPEED, buf.readUnsignedShort() * 0.1);
break;
+ case 0x56:
+ buf.readUnsignedByte(); // power level
+ position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte());
+ break;
+ case 0x61:
+ position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01);
+ break;
+ case 0x69:
+ position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01);
+ break;
case 0x80:
position.set(Position.KEY_OBD_SPEED, buf.readUnsignedByte());
break;
@@ -388,6 +403,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
int status = buf.readInt();
position.set(Position.KEY_IGNITION, BitUtil.check(status, 0));
+ position.set(Position.KEY_MOTION, BitUtil.check(status, 4));
position.set(Position.KEY_BLOCKED, BitUtil.check(status, 10));
position.set(Position.KEY_CHARGE, BitUtil.check(status, 26));
@@ -448,6 +464,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
int subtype = buf.readUnsignedByte();
int length = buf.readUnsignedByte();
int endIndex = buf.readerIndex() + length;
+ String stringValue;
switch (subtype) {
case 0x01:
position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 100);
@@ -455,8 +472,12 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
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());
@@ -465,12 +486,75 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
break;
case 0x33:
- String sentence = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString();
- if (sentence.startsWith("*M00")) {
- String lockStatus = sentence.substring(8, 8 + 7);
+ stringValue = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString();
+ if (stringValue.startsWith("*M00")) {
+ String lockStatus = stringValue.substring(8, 8 + 7);
position.set(Position.KEY_BATTERY, Integer.parseInt(lockStatus.substring(2, 5)) * 0.01);
}
break;
+ case 0x56:
+ position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte() * 10);
+ buf.readUnsignedByte(); // reserved
+ break;
+ case 0x57:
+ int alarm = buf.readUnsignedShort();
+ position.set(Position.KEY_ALARM, BitUtil.check(alarm, 8) ? Position.ALARM_ACCELERATION : null);
+ position.set(Position.KEY_ALARM, BitUtil.check(alarm, 9) ? Position.ALARM_BRAKING : null);
+ position.set(Position.KEY_ALARM, BitUtil.check(alarm, 10) ? Position.ALARM_CORNERING : null);
+ buf.readUnsignedShort(); // external switch state
+ buf.skipBytes(4); // reserved
+ break;
+ case 0x60:
+ int event = buf.readUnsignedShort();
+ position.set(Position.KEY_EVENT, event);
+ if (event >= 0x0061 && event <= 0x0066) {
+ buf.skipBytes(6); // lock id
+ stringValue = buf.readCharSequence(8, StandardCharsets.US_ASCII).toString();
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, stringValue);
+ }
+ break;
+ case 0x63:
+ for (int i = 1; i <= length / 11; i++) {
+ position.set("lock" + i + "Id", ByteBufUtil.hexDump(buf.readSlice(6)));
+ position.set("lock" + i + "Battery", buf.readUnsignedShort() * 0.001);
+ position.set("lock" + i + "Seal", buf.readUnsignedByte() == 0x31);
+ buf.readUnsignedByte(); // physical state
+ 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;
case 0x80:
buf.readUnsignedByte(); // content
endIndex = buf.writerIndex() - 2;
@@ -492,8 +576,8 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
break;
case 0x94:
if (length > 0) {
- position.set(
- Position.KEY_VIN, buf.readCharSequence(length, StandardCharsets.US_ASCII).toString());
+ stringValue = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString();
+ position.set(Position.KEY_VIN, stringValue);
}
break;
case 0xA7:
@@ -503,6 +587,14 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
case 0xAC:
position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());
break;
+ case 0xBC:
+ stringValue = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString();
+ position.set("driver", stringValue.trim());
+ break;
+ case 0xBD:
+ stringValue = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString();
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, stringValue);
+ break;
case 0xD0:
long userStatus = buf.readUnsignedInt();
if (BitUtil.check(userStatus, 3)) {
@@ -513,7 +605,12 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1);
break;
case 0xD4:
- position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte());
+ case 0xE1:
+ 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) {
@@ -536,6 +633,9 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
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();
@@ -589,8 +689,8 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
}
break;
case 0xED:
- String license = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString().trim();
- position.set("driverLicense", license);
+ stringValue = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString();
+ position.set(Position.KEY_CARD, stringValue.trim());
break;
case 0xEE:
position.set(Position.KEY_RSSI, buf.readUnsignedByte());
@@ -663,6 +763,8 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
case 0xFE:
if (length == 1) {
position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte());
+ } else if (length == 2) {
+ position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1);
} else {
int mark = buf.readUnsignedByte();
if (mark == 0x7C) {
@@ -721,12 +823,15 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
int battery = buf.readUnsignedByte();
if (battery <= 100) {
position.set(Position.KEY_BATTERY_LEVEL, battery);
- } else if (battery == 0xAA) {
+ } else if (battery == 0xAA || battery == 0xAB) {
position.set(Position.KEY_CHARGE, true);
}
- position.setNetwork(new Network(CellTower.fromCidLac(
- getConfig(), buf.readUnsignedInt(), buf.readUnsignedShort())));
+ long cid = buf.readUnsignedInt();
+ int lac = buf.readUnsignedShort();
+ if (cid > 0 && lac > 0) {
+ position.setNetwork(new Network(CellTower.fromCidLac(getConfig(), cid, lac)));
+ }
int product = buf.readUnsignedByte();
int status = buf.readUnsignedShort();
@@ -738,6 +843,9 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
}
} else if (product == 3) {
position.set(Position.KEY_BLOCKED, BitUtil.check(status, 5));
+ if (BitUtil.check(alarm, 0)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED);
+ }
if (BitUtil.check(alarm, 1)) {
position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER);
}
@@ -747,10 +855,69 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
if (BitUtil.check(alarm, 3)) {
position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY);
}
+ if (BitUtil.check(alarm, 5)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_ENTER);
+ }
+ if (BitUtil.check(alarm, 6)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_EXIT);
+ }
}
position.set(Position.KEY_STATUS, status);
+ while (buf.readableBytes() > 2) {
+ int id = buf.readUnsignedByte();
+ int length = buf.readUnsignedByte();
+ switch (id) {
+ case 0x02:
+ position.setAltitude(buf.readShort());
+ break;
+ case 0x10:
+ position.set("wakeSource", buf.readUnsignedByte());
+ break;
+ case 0x0A:
+ if (length == 3) {
+ buf.readUnsignedShort(); // mcc
+ buf.readUnsignedByte(); // mnc
+ } else {
+ buf.skipBytes(length);
+ }
+ break;
+ case 0x0B:
+ position.set("lockCommand", buf.readUnsignedByte());
+ if (length >= 5 && length <= 6) {
+ position.set("lockCard", buf.readUnsignedInt());
+ } else if (length >= 7) {
+ position.set("lockPassword", buf.readCharSequence(6, StandardCharsets.US_ASCII).toString());
+ }
+ if (length % 2 == 0) {
+ position.set("unlockResult", buf.readUnsignedByte());
+ }
+ break;
+ case 0x0C:
+ int x = buf.readUnsignedShort();
+ if (x > 0x8000) {
+ x -= 0x10000;
+ }
+ int y = buf.readUnsignedShort();
+ if (y > 0x8000) {
+ y -= 0x10000;
+ }
+ int z = buf.readUnsignedShort();
+ if (z > 0x8000) {
+ z -= 0x10000;
+ }
+ position.set("tilt", String.format("[%d,%d,%d]", x, y, z));
+ break;
+ case 0xFC:
+ position.set(Position.KEY_GEOFENCE, buf.readUnsignedByte());
+ break;
+ default:
+ buf.skipBytes(length);
+ break;
+ }
+ }
+
return position;
}
@@ -782,6 +949,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
int type = buf.readUnsignedByte();
if (type == 0xF0) {
+
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
@@ -823,6 +991,34 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
case 0x0539:
position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort() * 0.01);
break;
+ case 0x052B:
+ position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte());
+ break;
+ case 0x052D:
+ position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedByte() - 40);
+ break;
+ case 0x052E:
+ position.set("airTemp", buf.readUnsignedByte() - 40);
+ break;
+ case 0x0530:
+ position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.001);
+ break;
+ case 0x0535:
+ position.set(Position.KEY_OBD_SPEED, buf.readUnsignedShort() * 0.1);
+ break;
+ case 0x0536:
+ position.set(Position.KEY_RPM, buf.readUnsignedShort());
+ break;
+ case 0x053D:
+ position.set("intakePressure", buf.readUnsignedShort() * 0.1);
+ break;
+ case 0x0544:
+ position.set("liquidLevel", buf.readUnsignedByte());
+ break;
+ case 0x0547:
+ case 0x0548:
+ position.set(Position.KEY_THROTTLE, buf.readUnsignedByte());
+ break;
default:
switch (length) {
case 1:
@@ -841,15 +1037,40 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
break;
}
}
+ getLastLocation(position, time);
+ decodeCoordinates(position, buf);
+ position.setTime(time);
+ break;
+ case 0x02:
+ List<String> codes = new LinkedList<>();
+ count = buf.readUnsignedShort();
+ for (int i = 0; i < count; i++) {
+ buf.readUnsignedInt(); // system id
+ int codeCount = buf.readUnsignedShort();
+ for (int j = 0; j < codeCount; j++) {
+ buf.readUnsignedInt(); // dtc
+ buf.readUnsignedInt(); // status
+ codes.add(buf.readCharSequence(
+ buf.readUnsignedShort(), StandardCharsets.US_ASCII).toString().trim());
+ }
+ }
+ position.set(Position.KEY_DTCS, String.join(" ", codes));
+ getLastLocation(position, time);
decodeCoordinates(position, buf);
position.setTime(time);
break;
case 0x03:
count = buf.readUnsignedByte();
for (int i = 0; i < count; i++) {
- int id = buf.readUnsignedShort();
+ int id = buf.readUnsignedByte();
int length = buf.readUnsignedByte();
switch (id) {
+ case 0x01:
+ position.set(Position.KEY_ALARM, Position.ALARM_POWER_RESTORED);
+ break;
+ case 0x02:
+ position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT);
+ break;
case 0x1A:
position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION);
break;
@@ -867,11 +1088,21 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
case 0x23:
position.set(Position.KEY_ALARM, Position.ALARM_FATIGUE_DRIVING);
break;
+ case 0x26:
+ case 0x27:
+ case 0x28:
+ position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT);
+ break;
+ case 0x31:
+ case 0x32:
+ position.set(Position.KEY_ALARM, Position.ALARM_DOOR);
+ break;
default:
break;
}
buf.skipBytes(length);
}
+ getLastLocation(position, time);
decodeCoordinates(position, buf);
position.setTime(time);
break;
@@ -886,6 +1117,24 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder {
}
return position;
+
+ } else if (type == 0xFF) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.setValid(true);
+ position.setTime(readDate(buf, deviceSession.get(DeviceSession.KEY_TIMEZONE)));
+ position.setLatitude(buf.readInt() * 0.000001);
+ position.setLongitude(buf.readInt() * 0.000001);
+ position.setAltitude(buf.readShort());
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() * 0.1));
+ position.setCourse(buf.readUnsignedShort());
+
+ // TODO more positions and g sensor data
+
+ return position;
+
}
return null;
diff --git a/src/main/java/org/traccar/protocol/HunterProProtocol.java b/src/main/java/org/traccar/protocol/HunterProProtocol.java
index ed4289d73..64dab33b1 100644
--- a/src/main/java/org/traccar/protocol/HunterProProtocol.java
+++ b/src/main/java/org/traccar/protocol/HunterProProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class HunterProProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/IdplProtocol.java b/src/main/java/org/traccar/protocol/IdplProtocol.java
index aa1f4ff5b..1e44ad74c 100644
--- a/src/main/java/org/traccar/protocol/IdplProtocol.java
+++ b/src/main/java/org/traccar/protocol/IdplProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class IdplProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/IntellitracProtocol.java b/src/main/java/org/traccar/protocol/IntellitracProtocol.java
index b1a91cca9..a82e6a5db 100644
--- a/src/main/java/org/traccar/protocol/IntellitracProtocol.java
+++ b/src/main/java/org/traccar/protocol/IntellitracProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class IntellitracProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/IotmProtocol.java b/src/main/java/org/traccar/protocol/IotmProtocol.java
index 0d288f4bf..1631b67d8 100644
--- a/src/main/java/org/traccar/protocol/IotmProtocol.java
+++ b/src/main/java/org/traccar/protocol/IotmProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class IotmProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java b/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java
index 7bbe6c8de..d9e6670c6 100644
--- a/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2020 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,27 +17,19 @@ package org.traccar.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
-import io.netty.channel.Channel;
-import io.netty.handler.codec.mqtt.MqttConnectMessage;
-import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
-import io.netty.handler.codec.mqtt.MqttMessage;
-import io.netty.handler.codec.mqtt.MqttMessageBuilders;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
-import io.netty.handler.codec.mqtt.MqttSubscribeMessage;
-import org.traccar.BaseProtocolDecoder;
-import org.traccar.session.DeviceSession;
-import org.traccar.NetworkMessage;
+import org.traccar.BaseMqttProtocolDecoder;
import org.traccar.Protocol;
import org.traccar.helper.UnitsConverter;
import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
-import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
-public class IotmProtocolDecoder extends BaseProtocolDecoder {
+public class IotmProtocolDecoder extends BaseMqttProtocolDecoder {
public IotmProtocolDecoder(Protocol protocol) {
super(protocol);
@@ -236,121 +228,72 @@ public class IotmProtocolDecoder extends BaseProtocolDecoder {
@Override
protected Object decode(
- Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
-
- if (msg instanceof MqttConnectMessage) {
-
- MqttConnectMessage message = (MqttConnectMessage) msg;
+ DeviceSession deviceSession, MqttPublishMessage message) throws Exception {
- DeviceSession deviceSession = getDeviceSession(
- channel, remoteAddress, message.payload().clientIdentifier());
-
- MqttConnectReturnCode returnCode = deviceSession != null
- ? MqttConnectReturnCode.CONNECTION_ACCEPTED
- : MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED;
-
- MqttMessage response = MqttMessageBuilders.connAck().returnCode(returnCode).build();
-
- if (channel != null) {
- channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
- }
+ List<Position> positions = new LinkedList<>();
- } else if (msg instanceof MqttSubscribeMessage) {
+ ByteBuf buf = message.payload();
- MqttSubscribeMessage message = (MqttSubscribeMessage) msg;
-
- MqttMessage response = MqttMessageBuilders.subAck()
- .packetId(message.variableHeader().messageId())
- .build();
-
- if (channel != null) {
- channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
- }
-
- } else if (msg instanceof MqttPublishMessage) {
-
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
- if (deviceSession == null) {
- return null;
- }
+ buf.readUnsignedByte(); // structure version
- List<Position> positions = new LinkedList<>();
+ while (buf.readableBytes() > 1) {
+ int type = buf.readUnsignedByte();
+ int length = buf.readUnsignedShortLE();
+ ByteBuf record = buf.readSlice(length);
+ if (type == 1) {
- MqttPublishMessage message = (MqttPublishMessage) msg;
- ByteBuf buf = message.payload();
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+ position.setTime(new Date(record.readUnsignedIntLE() * 1000));
- buf.readUnsignedByte(); // structure version
+ while (record.readableBytes() > 0) {
+ int sensorType = record.readUnsignedByte();
+ int sensorId = record.readUnsignedShortLE();
+ if (sensorType == 14) {
- while (buf.readableBytes() > 1) {
- int type = buf.readUnsignedByte();
- int length = buf.readUnsignedShortLE();
- ByteBuf record = buf.readSlice(length);
- if (type == 1) {
+ position.setValid(true);
+ position.setLatitude(record.readFloatLE());
+ position.setLongitude(record.readFloatLE());
+ position.setSpeed(UnitsConverter.knotsFromKph(record.readUnsignedShortLE()));
- Position position = new Position(getProtocolName());
- position.setDeviceId(deviceSession.getDeviceId());
- position.setTime(new Date(record.readUnsignedIntLE() * 1000));
+ position.set(Position.KEY_HDOP, record.readUnsignedByte());
+ position.set(Position.KEY_SATELLITES, record.readUnsignedByte());
- while (record.readableBytes() > 0) {
- int sensorType = record.readUnsignedByte();
- int sensorId = record.readUnsignedShortLE();
- if (sensorType == 14) {
+ position.setCourse(record.readUnsignedShortLE());
+ position.setAltitude(record.readShortLE());
- position.setValid(true);
- position.setLatitude(record.readFloatLE());
- position.setLongitude(record.readFloatLE());
- position.setSpeed(UnitsConverter.knotsFromKph(record.readUnsignedShortLE()));
-
- position.set(Position.KEY_HDOP, record.readUnsignedByte());
- position.set(Position.KEY_SATELLITES, record.readUnsignedByte());
-
- position.setCourse(record.readUnsignedShortLE());
- position.setAltitude(record.readShortLE());
-
- } else {
-
- if (sensorType == 3) {
- continue;
- }
-
- decodeSensor(position, record, sensorType, sensorId);
+ } else {
+ if (sensorType == 3) {
+ continue;
}
- }
-
- positions.add(position);
- } else if (type == 3) {
+ decodeSensor(position, record, sensorType, sensorId);
- Position position = new Position(getProtocolName());
- position.setDeviceId(deviceSession.getDeviceId());
+ }
+ }
- getLastLocation(position, new Date(record.readUnsignedIntLE() * 1000));
+ positions.add(position);
- record.readUnsignedByte(); // function identifier
+ } else if (type == 3) {
- position.set(Position.KEY_EVENT, record.readUnsignedByte());
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
- positions.add(position);
+ getLastLocation(position, new Date(record.readUnsignedIntLE() * 1000));
- }
- }
+ record.readUnsignedByte(); // function identifier
- buf.readUnsignedByte(); // checksum
+ position.set(Position.KEY_EVENT, record.readUnsignedByte());
- MqttMessage response = MqttMessageBuilders.pubAck()
- .packetId(message.variableHeader().packetId())
- .build();
+ positions.add(position);
- if (channel != null) {
- channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
}
-
- return positions.isEmpty() ? null : positions;
-
}
- return null;
+ buf.readUnsignedByte(); // checksum
+
+ return positions.isEmpty() ? null : positions;
}
}
diff --git a/src/main/java/org/traccar/protocol/ItsProtocol.java b/src/main/java/org/traccar/protocol/ItsProtocol.java
index 5148e8ab0..7d59ea60c 100644
--- a/src/main/java/org/traccar/protocol/ItsProtocol.java
+++ b/src/main/java/org/traccar/protocol/ItsProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ItsProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Ivt401Protocol.java b/src/main/java/org/traccar/protocol/Ivt401Protocol.java
index 763457641..5132c7467 100644
--- a/src/main/java/org/traccar/protocol/Ivt401Protocol.java
+++ b/src/main/java/org/traccar/protocol/Ivt401Protocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Ivt401Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/JidoProtocol.java b/src/main/java/org/traccar/protocol/JidoProtocol.java
index 78aa6c81c..b30cc586a 100644
--- a/src/main/java/org/traccar/protocol/JidoProtocol.java
+++ b/src/main/java/org/traccar/protocol/JidoProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class JidoProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/JpKorjarProtocol.java b/src/main/java/org/traccar/protocol/JpKorjarProtocol.java
index 30c8e9977..ae312ea3e 100644
--- a/src/main/java/org/traccar/protocol/JpKorjarProtocol.java
+++ b/src/main/java/org/traccar/protocol/JpKorjarProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class JpKorjarProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Jt600FrameDecoder.java b/src/main/java/org/traccar/protocol/Jt600FrameDecoder.java
index bfefb94a7..f7890f814 100644
--- a/src/main/java/org/traccar/protocol/Jt600FrameDecoder.java
+++ b/src/main/java/org/traccar/protocol/Jt600FrameDecoder.java
@@ -35,7 +35,7 @@ public class Jt600FrameDecoder extends BaseFrameDecoder {
char type = (char) buf.getByte(buf.readerIndex());
if (type == '$') {
- boolean longFormat = Jt600ProtocolDecoder.isLongFormat(buf, buf.readerIndex() + 1);
+ boolean longFormat = Jt600ProtocolDecoder.isLongFormat(buf);
int length = buf.getUnsignedShort(buf.readerIndex() + (longFormat ? 8 : 7)) + 10;
if (length <= buf.readableBytes()) {
return buf.readRetainedSlice(length);
diff --git a/src/main/java/org/traccar/protocol/Jt600Protocol.java b/src/main/java/org/traccar/protocol/Jt600Protocol.java
index bf0b3379e..9dc62662f 100644
--- a/src/main/java/org/traccar/protocol/Jt600Protocol.java
+++ b/src/main/java/org/traccar/protocol/Jt600Protocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Jt600Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java
index 9ed44f565..eca7e2d11 100644
--- a/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java
@@ -86,8 +86,8 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder {
}
- static boolean isLongFormat(ByteBuf buf, int flagIndex) {
- return buf.getUnsignedByte(flagIndex) >> 4 >= 7;
+ static boolean isLongFormat(ByteBuf buf) {
+ return buf.getUnsignedByte(buf.readerIndex() + 8) == 0;
}
static void decodeBinaryLocation(ByteBuf buf, Position position) {
@@ -105,15 +105,9 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder {
double longitude = convertCoordinate(BcdUtil.readInteger(buf, 9));
byte flags = buf.readByte();
- position.setValid((flags & 0x1) == 0x1);
- if ((flags & 0x2) == 0) {
- latitude = -latitude;
- }
- position.setLatitude(latitude);
- if ((flags & 0x4) == 0) {
- longitude = -longitude;
- }
- position.setLongitude(longitude);
+ position.setValid(BitUtil.check(flags, 0));
+ position.setLatitude(BitUtil.check(flags, 1) ? latitude : -latitude);
+ position.setLongitude(BitUtil.check(flags, 2) ? longitude : -longitude);
position.setSpeed(BcdUtil.readInteger(buf, 2));
position.setCourse(buf.readUnsignedByte() * 2.0);
@@ -123,9 +117,9 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder {
List<Position> positions = new LinkedList<>();
- buf.readByte(); // header
+ boolean longFormat = isLongFormat(buf);
- boolean longFormat = isLongFormat(buf, buf.readerIndex());
+ buf.readByte(); // header
String id = String.valueOf(Long.parseLong(ByteBufUtil.hexDump(buf.readSlice(5))));
DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id);
@@ -386,7 +380,7 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder {
.expression("([AV]),") // validity
.number("(d+),") // speed
.number("(d+),") // course
- .number("d+,") // event source
+ .number("(d+),") // event source
.number("d+,") // unlock verification
.number("(d+),") // rfid
.number("d+,") // password verification
@@ -419,6 +413,8 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder {
position.setSpeed(UnitsConverter.knotsFromMph(parser.nextDouble()));
position.setCourse(parser.nextDouble());
+ position.set("eventSource", parser.nextInt());
+
String rfid = parser.next();
if (!rfid.equals("0000000000")) {
position.set(Position.KEY_DRIVER_UNIQUE_ID, rfid);
diff --git a/src/main/java/org/traccar/protocol/KenjiProtocol.java b/src/main/java/org/traccar/protocol/KenjiProtocol.java
index 8d78c8c56..b4e610cbd 100644
--- a/src/main/java/org/traccar/protocol/KenjiProtocol.java
+++ b/src/main/java/org/traccar/protocol/KenjiProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class KenjiProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/KhdProtocol.java b/src/main/java/org/traccar/protocol/KhdProtocol.java
index 521274de5..add13ef16 100644
--- a/src/main/java/org/traccar/protocol/KhdProtocol.java
+++ b/src/main/java/org/traccar/protocol/KhdProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class KhdProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java
index d7c236c4f..e88b34478 100644
--- a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java
@@ -20,6 +20,7 @@ import io.netty.buffer.ByteBufUtil;
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;
@@ -169,7 +170,9 @@ public class KhdProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_FUEL_LEVEL, BitUtil.from(odometer, 16));
}
- position.set(Position.KEY_STATUS, buf.readUnsignedInt());
+ long status = buf.readUnsignedInt();
+ position.set(Position.KEY_IGNITION, BitUtil.check(status, 7 + 3 * 8));
+ position.set(Position.KEY_STATUS, status);
buf.readUnsignedShort();
buf.readUnsignedByte();
@@ -185,8 +188,7 @@ public class KhdProtocolDecoder extends BaseProtocolDecoder {
buf.readUnsignedShort(); // data length
int dataType = buf.readUnsignedByte();
-
- buf.readUnsignedByte(); // content length
+ int dataLength = buf.readUnsignedByte();
switch (dataType) {
case 0x01:
@@ -197,6 +199,20 @@ public class KhdProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.PREFIX_TEMP + 1,
buf.readUnsignedByte() * 100 + buf.readUnsignedByte());
break;
+ case 0x05:
+ int sign = buf.readUnsignedByte();
+ switch (sign) {
+ case 1:
+ position.set("sign", true);
+ break;
+ case 2:
+ position.set("sign", false);
+ break;
+ default:
+ break;
+ }
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, BufferUtil.readString(buf, dataLength - 1));
+ break;
case 0x18:
for (int i = 1; i <= 4; i++) {
double value = buf.readUnsignedShort();
@@ -205,6 +221,9 @@ public class KhdProtocolDecoder extends BaseProtocolDecoder {
}
}
break;
+ case 0x20:
+ position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte());
+ break;
case 0x23:
Network network = new Network();
int count = buf.readUnsignedByte();
diff --git a/src/main/java/org/traccar/protocol/L100Protocol.java b/src/main/java/org/traccar/protocol/L100Protocol.java
index 0edea6095..fa6d1b07e 100644
--- a/src/main/java/org/traccar/protocol/L100Protocol.java
+++ b/src/main/java/org/traccar/protocol/L100Protocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class L100Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/LacakProtocol.java b/src/main/java/org/traccar/protocol/LacakProtocol.java
index bbebd51ed..ddaf5078d 100644
--- a/src/main/java/org/traccar/protocol/LacakProtocol.java
+++ b/src/main/java/org/traccar/protocol/LacakProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class LacakProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/LacakProtocolDecoder.java b/src/main/java/org/traccar/protocol/LacakProtocolDecoder.java
index 809fafc90..66aab3490 100644
--- a/src/main/java/org/traccar/protocol/LacakProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/LacakProtocolDecoder.java
@@ -24,8 +24,8 @@ import org.traccar.Protocol;
import org.traccar.helper.DateUtil;
import org.traccar.model.Position;
-import javax.json.Json;
-import javax.json.JsonObject;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
import java.io.StringReader;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
diff --git a/src/main/java/org/traccar/protocol/LaipacProtocol.java b/src/main/java/org/traccar/protocol/LaipacProtocol.java
index 249d3bcbe..65b1a57e9 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocol.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class LaipacProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
index e9570ee11..343d42e09 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
@@ -28,6 +28,7 @@ import org.traccar.helper.PatternBuilder;
import org.traccar.model.CellTower;
import org.traccar.model.Network;
import org.traccar.model.Position;
+import org.traccar.helper.BitUtil;
import java.net.SocketAddress;
import java.util.regex.Pattern;
@@ -108,19 +109,31 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
}
}
- private String decodeEvent(String event, Position position) {
+ private String decodeEvent(String event, Position position, String model) {
if (event.length() == 1) {
char inputStatus = event.charAt(0);
if (inputStatus >= 'A' && inputStatus <= 'D') {
int inputStatusInt = inputStatus - 'A';
- position.set(Position.PREFIX_IN + 1, inputStatusInt & 1);
- position.set(Position.PREFIX_IN + 2, inputStatusInt & 2);
+ position.set(Position.PREFIX_IN + 1, (boolean) BitUtil.check(inputStatusInt, 0));
+ position.set(Position.PREFIX_IN + 2, (boolean) BitUtil.check(inputStatusInt, 1));
+ if ("SF-Lite".equals(model)) {
+ position.set(Position.PREFIX_IN + 3, false);
+ }
+ return null;
+ } else if (inputStatus >= 'O' && inputStatus <= 'R') {
+ int inputStatusInt = inputStatus - 'O';
+ position.set(Position.PREFIX_IN + 1, (boolean) BitUtil.check(inputStatusInt, 0));
+ position.set(Position.PREFIX_IN + 2, (boolean) BitUtil.check(inputStatusInt, 1));
+ if ("SF-Lite".equals(model)) {
+ position.set(Position.PREFIX_IN + 3, true);
+ }
return null;
}
}
return event;
+
}
private void sendEventResponse(
@@ -132,6 +145,9 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
case "3":
responseCode = "d";
break;
+ case "M":
+ responseCode = "m";
+ break;
case "S":
case "T":
responseCode = "t";
@@ -209,6 +225,8 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
return null;
}
+ String model = getDeviceModel(deviceSession);
+
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
@@ -230,12 +248,17 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
String event = parser.next();
position.set(Position.KEY_ALARM, decodeAlarm(event));
- position.set(Position.KEY_EVENT, decodeEvent(event, position));
+ position.set(Position.KEY_EVENT, decodeEvent(event, position, model));
position.set(Position.KEY_BATTERY, Double.parseDouble(parser.next().replaceAll("\\.", "")) * 0.001);
position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000);
position.set(Position.KEY_GPS, parser.nextInt());
position.set(Position.PREFIX_ADC + 1, parser.nextDouble() * 0.001);
- position.set(Position.PREFIX_ADC + 2, parser.nextDouble() * 0.001);
+
+ if ("AVL110".equals(model) || "AVL120".equals(model)) {
+ position.set(Position.PREFIX_ADC + 2, parser.nextDouble() * 0.001);
+ } else {
+ parser.next();
+ }
Integer lac = parser.nextHexInt();
Integer cid = parser.nextHexInt();
diff --git a/src/main/java/org/traccar/protocol/LeafSpyProtocol.java b/src/main/java/org/traccar/protocol/LeafSpyProtocol.java
index 7e13e23d0..9e167e7ba 100644
--- a/src/main/java/org/traccar/protocol/LeafSpyProtocol.java
+++ b/src/main/java/org/traccar/protocol/LeafSpyProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class LeafSpyProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/M2cProtocol.java b/src/main/java/org/traccar/protocol/M2cProtocol.java
index a23ea0f57..8abc30f60 100644
--- a/src/main/java/org/traccar/protocol/M2cProtocol.java
+++ b/src/main/java/org/traccar/protocol/M2cProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class M2cProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/M2mProtocol.java b/src/main/java/org/traccar/protocol/M2mProtocol.java
index 6809d800c..03a069d66 100644
--- a/src/main/java/org/traccar/protocol/M2mProtocol.java
+++ b/src/main/java/org/traccar/protocol/M2mProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class M2mProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/MaestroProtocol.java b/src/main/java/org/traccar/protocol/MaestroProtocol.java
index 38a67f9a4..29f0b8897 100644
--- a/src/main/java/org/traccar/protocol/MaestroProtocol.java
+++ b/src/main/java/org/traccar/protocol/MaestroProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class MaestroProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ManPowerProtocol.java b/src/main/java/org/traccar/protocol/ManPowerProtocol.java
index 492e86605..ba2414ca7 100644
--- a/src/main/java/org/traccar/protocol/ManPowerProtocol.java
+++ b/src/main/java/org/traccar/protocol/ManPowerProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ManPowerProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Mavlink2Protocol.java b/src/main/java/org/traccar/protocol/Mavlink2Protocol.java
index cf65a2db3..916fb7467 100644
--- a/src/main/java/org/traccar/protocol/Mavlink2Protocol.java
+++ b/src/main/java/org/traccar/protocol/Mavlink2Protocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Mavlink2Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/MegastekProtocol.java b/src/main/java/org/traccar/protocol/MegastekProtocol.java
index 10215eb7c..9f8937f01 100644
--- a/src/main/java/org/traccar/protocol/MegastekProtocol.java
+++ b/src/main/java/org/traccar/protocol/MegastekProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class MegastekProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/MeiligaoProtocol.java b/src/main/java/org/traccar/protocol/MeiligaoProtocol.java
index 492094ce3..d86a00fb3 100644
--- a/src/main/java/org/traccar/protocol/MeiligaoProtocol.java
+++ b/src/main/java/org/traccar/protocol/MeiligaoProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class MeiligaoProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java
index f3b56973a..1f8c4d2da 100644
--- a/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java
@@ -282,7 +282,7 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_RSSI, parser.nextHexInt());
position.set(Position.KEY_ODOMETER, parser.nextHexLong());
position.set(Position.KEY_SATELLITES, parser.nextHexInt());
- position.set("driverLicense", parser.next());
+ position.set(Position.KEY_CARD, parser.next());
position.set(Position.KEY_ODOMETER, parser.nextLong());
position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next());
diff --git a/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java b/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java
index 5859d91ce..6d3b4f7e9 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.Checksum;
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 @@ public class MeiligaoProtocolEncoder extends BaseProtocolEncoder {
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/MeitrackProtocol.java b/src/main/java/org/traccar/protocol/MeitrackProtocol.java
index c6eba8fe1..4109b22c9 100644
--- a/src/main/java/org/traccar/protocol/MeitrackProtocol.java
+++ b/src/main/java/org/traccar/protocol/MeitrackProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class MeitrackProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java
index 343141dca..88b6380a5 100644
--- a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,10 @@
package org.traccar.protocol;
import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
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;
@@ -30,12 +30,14 @@ import org.traccar.helper.UnitsConverter;
import org.traccar.model.CellTower;
import org.traccar.model.Network;
import org.traccar.model.Position;
+import org.traccar.model.WifiAccessPoint;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
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 {
@@ -204,11 +206,7 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
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);
@@ -394,6 +392,8 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
+ Network network = new Network();
+
buf.readUnsignedShortLE(); // length
buf.readUnsignedShortLE(); // index
@@ -420,6 +420,12 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
case 0x15:
position.set(Position.KEY_INPUT, buf.readUnsignedByte());
break;
+ case 0x47:
+ int lockState = buf.readUnsignedByte();
+ if (lockState > 0) {
+ position.set(Position.KEY_LOCK, lockState == 2);
+ }
+ break;
case 0x97:
position.set(Position.KEY_THROTTLE, buf.readUnsignedByte());
break;
@@ -455,12 +461,18 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
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;
case 0x1A:
position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.01);
break;
+ case 0x29:
+ position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShortLE() * 0.01);
+ break;
case 0x40:
position.set(Position.KEY_EVENT, buf.readUnsignedShortLE());
break;
@@ -504,18 +516,26 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
position.setTime(new Date((946684800 + buf.readUnsignedIntLE()) * 1000)); // 2000-01-01
break;
case 0x0C:
- case 0x9B:
position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE());
break;
case 0x0D:
position.set("runtime", buf.readUnsignedIntLE());
break;
+ case 0x25:
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(buf.readUnsignedIntLE()));
+ break;
+ case 0x9B:
+ position.set(Position.KEY_OBD_ODOMETER, buf.readUnsignedIntLE());
+ break;
case 0xA0:
position.set(Position.KEY_FUEL_USED, buf.readUnsignedIntLE() * 0.001);
break;
case 0xA2:
position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedIntLE() * 0.01);
break;
+ case 0xFEF4:
+ position.set(Position.KEY_HOURS, buf.readUnsignedIntLE() * 60000);
+ break;
default:
buf.readUnsignedIntLE();
break;
@@ -528,6 +548,28 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
int id = extension ? buf.readUnsignedShort() : buf.readUnsignedByte();
int length = buf.readUnsignedByte();
switch (id) {
+ case 0x1D:
+ case 0x1E:
+ case 0x1F:
+ case 0x20:
+ case 0x21:
+ case 0x22:
+ case 0x23:
+ case 0x24:
+ case 0x25:
+ String wifiMac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:");
+ network.addWifiAccessPoint(WifiAccessPoint.from(
+ wifiMac.substring(0, wifiMac.length() - 1), buf.readShortLE()));
+ break;
+ case 0x0E:
+ case 0x0F:
+ case 0x10:
+ case 0x12:
+ case 0x13:
+ network.addCellTower(CellTower.from(
+ buf.readUnsignedShortLE(), buf.readUnsignedShortLE(),
+ buf.readUnsignedShortLE(), buf.readUnsignedIntLE(), buf.readShortLE()));
+ break;
case 0x2A:
case 0x2B:
case 0x2C:
@@ -539,17 +581,48 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
buf.readUnsignedByte(); // label
position.set(Position.PREFIX_TEMP + (id - 0x2A), buf.readShortLE() * 0.01);
break;
+ case 0x4B:
+ buf.skipBytes(length); // network information
+ break;
case 0xFE31:
buf.readUnsignedByte(); // alarm protocol
buf.readUnsignedByte(); // alarm type
buf.skipBytes(length - 2);
break;
+ case 0xFE73:
+ buf.readUnsignedByte(); // version
+ position.set(
+ "tagName",
+ buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString());
+ buf.skipBytes(6); // mac
+ position.set("tagBattery", buf.readUnsignedByte());
+ position.set("tagTemp", buf.readUnsignedShortLE() / 256.0);
+ position.set("tagHumidity", buf.readUnsignedShortLE() / 256.0);
+ buf.readUnsignedShortLE(); // high temperature threshold
+ buf.readUnsignedShortLE(); // low temperature threshold
+ buf.readUnsignedShortLE(); // high humidity threshold
+ buf.readUnsignedShortLE(); // low humidity threshold
+ break;
+ case 0xFEA8:
+ for (int k = 1; k <= 3; k++) {
+ if (buf.readUnsignedByte() > 0) {
+ String key = k == 1 ? Position.KEY_BATTERY_LEVEL : "battery" + k + "Level";
+ position.set(key, buf.readUnsignedByte());
+ } else {
+ buf.readUnsignedByte();
+ }
+ }
+ buf.readUnsignedByte(); // battery alert
+ break;
default:
buf.skipBytes(length);
break;
}
}
+ if (network.getCellTowers() != null || network.getWifiAccessPoints() != null) {
+ position.setNetwork(network);
+ }
positions.add(position);
}
@@ -624,6 +697,13 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder {
photo = Unpooled.buffer();
requestPhotoPacket(channel, remoteAddress, imei, "camera_picture.jpg", 0);
return null;
+ case "D82":
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(getDeviceSession(channel, remoteAddress, imei).getDeviceId());
+ getLastLocation(position, null);
+ String result = buf.toString(index + 1, buf.writerIndex() - index - 4, StandardCharsets.US_ASCII);
+ position.set(Position.KEY_RESULT, result);
+ return position;
case "CCC":
return decodeBinaryC(channel, remoteAddress, buf);
case "CCE":
diff --git a/src/main/java/org/traccar/protocol/MictrackProtocol.java b/src/main/java/org/traccar/protocol/MictrackProtocol.java
index ccbc4db4c..08bbe0c82 100644
--- a/src/main/java/org/traccar/protocol/MictrackProtocol.java
+++ b/src/main/java/org/traccar/protocol/MictrackProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class MictrackProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/MilesmateProtocol.java b/src/main/java/org/traccar/protocol/MilesmateProtocol.java
index 59212e791..607dfc5bf 100644
--- a/src/main/java/org/traccar/protocol/MilesmateProtocol.java
+++ b/src/main/java/org/traccar/protocol/MilesmateProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class MilesmateProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/MiniFinderProtocol.java b/src/main/java/org/traccar/protocol/MiniFinderProtocol.java
index 44599accc..1cb2a0007 100644
--- a/src/main/java/org/traccar/protocol/MiniFinderProtocol.java
+++ b/src/main/java/org/traccar/protocol/MiniFinderProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class MiniFinderProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java b/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java
index f2e5eb905..1fdb1ece0 100644
--- a/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java
@@ -143,7 +143,7 @@ public class MiniFinderProtocolDecoder extends BaseProtocolDecoder {
}
DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
- if (deviceSession == null || !sentence.matches("![35A-D],.*")) {
+ if (deviceSession == null || !sentence.matches("![345A-D],.*")) {
return null;
}
@@ -161,6 +161,20 @@ public class MiniFinderProtocolDecoder extends BaseProtocolDecoder {
return position;
+ } else if (type.equals("4")) {
+
+ String[] values = sentence.split(",");
+
+ getLastLocation(position, null);
+
+ for (int i = 1; i <= 3; i++) {
+ if (!values[i + 1].isEmpty()) {
+ position.set("phone" + i, values[i + 1]);
+ }
+ }
+
+ return position;
+
} else if (type.equals("5")) {
String[] values = sentence.split(",");
diff --git a/src/main/java/org/traccar/protocol/Minifinder2Protocol.java b/src/main/java/org/traccar/protocol/Minifinder2Protocol.java
index 5499a274e..082b9146d 100644
--- a/src/main/java/org/traccar/protocol/Minifinder2Protocol.java
+++ b/src/main/java/org/traccar/protocol/Minifinder2Protocol.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,19 +20,24 @@ import org.traccar.BaseProtocol;
import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
+import org.traccar.model.Command;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Minifinder2Protocol extends BaseProtocol {
@Inject
public Minifinder2Protocol(Config config) {
+ setSupportedDataCommands(
+ Command.TYPE_FIRMWARE_UPDATE,
+ Command.TYPE_CONFIGURATION);
addServer(new TrackerServer(config, getName(), false) {
@Override
protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
- pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1200, 2, 2, 4, 0, true));
+ pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 2048, 2, 2, 4, 0, true));
+ pipeline.addLast(new Minifinder2ProtocolEncoder(Minifinder2Protocol.this));
pipeline.addLast(new Minifinder2ProtocolDecoder(Minifinder2Protocol.this));
}
});
diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java
index 0b08badb8..64373e344 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 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2024 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import io.netty.buffer.ByteBufUtil;
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;
@@ -48,6 +49,8 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_DATA = 0x01;
public static final int MSG_CONFIGURATION = 0x02;
public static final int MSG_SERVICES = 0x03;
+ public static final int MSG_SYSTEM_CONTROL = 0x04;
+ public static final int MSG_FIRMWARE = 0x7E;
public static final int MSG_RESPONSE = 0x7F;
private String decodeAlarm(long code) {
@@ -146,14 +149,13 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
int type = buf.readUnsignedByte();
if (BitUtil.check(flags, 4)) {
- sendResponse(channel, remoteAddress, index, type, buf);
+ sendResponse(channel, remoteAddress, index, type, buf.slice());
}
- if (type == MSG_DATA) {
+ if (type == MSG_DATA || type == MSG_SERVICES) {
List<Position> positions = new LinkedList<>();
Set<Integer> keys = new HashSet<>();
- boolean hasLocation = false;
Position position = new Position(getProtocolName());
DeviceSession deviceSession = null;
@@ -163,12 +165,8 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
int key = buf.readUnsignedByte();
if (keys.contains(key)) {
- if (!hasLocation) {
- getLastLocation(position, null);
- }
positions.add(position);
keys.clear();
- hasLocation = false;
position = new Position(getProtocolName());
}
keys.add(key);
@@ -177,8 +175,9 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
case 0x01:
deviceSession = getDeviceSession(
channel, remoteAddress, buf.readCharSequence(15, StandardCharsets.US_ASCII).toString());
-
- position.setDeviceId(deviceSession.getDeviceId());
+ if (deviceSession == null) {
+ return null;
+ }
break;
case 0x02:
long alarm = buf.readUnsignedIntLE();
@@ -192,7 +191,6 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001);
break;
case 0x20:
- hasLocation = true;
position.setLatitude(buf.readIntLE() * 0.0000001);
position.setLongitude(buf.readIntLE() * 0.0000001);
position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE()));
@@ -232,11 +230,18 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
position.setLatitude(buf.readIntLE() * 0.0000001);
position.setLongitude(buf.readIntLE() * 0.0000001);
position.setValid(true);
- hasLocation = true;
break;
case 0x24:
position.setTime(new Date(buf.readUnsignedIntLE() * 1000));
long status = buf.readUnsignedIntLE();
+ if (BitUtil.check(status, 4)) {
+ position.set(Position.KEY_CHARGE, true);
+ }
+ if (BitUtil.check(status, 7)) {
+ position.set(Position.KEY_ARCHIVE, true);
+ }
+ position.set(Position.KEY_MOTION, BitUtil.check(status, 9));
+ position.set(Position.KEY_RSSI, BitUtil.between(status, 19, 24));
position.set(Position.KEY_BATTERY_LEVEL, BitUtil.from(status, 24));
position.set(Position.KEY_STATUS, status);
break;
@@ -249,7 +254,6 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
position.setLatitude(buf.readIntLE() * 0.0000001);
position.setLongitude(buf.readIntLE() * 0.0000001);
position.setValid(true);
- hasLocation = true;
}
if (BitUtil.check(beaconFlags, 6)) {
position.set("description", buf.readCharSequence(
@@ -263,7 +267,6 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
position.setLatitude(buf.readIntLE() * 0.0000001);
position.setLongitude(buf.readIntLE() * 0.0000001);
position.setValid(true);
- hasLocation = true;
break;
case 0x30:
buf.readUnsignedIntLE(); // timestamp
@@ -277,6 +280,14 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
i += 1;
}
break;
+ case 0x37:
+ buf.readUnsignedIntLE(); // timestamp
+ long barking = buf.readUnsignedIntLE();
+ if (BitUtil.check(barking, 31)) {
+ position.set("barkStop", true);
+ }
+ position.set("barkCount", BitUtil.to(barking, 31));
+ break;
case 0x40:
buf.readUnsignedIntLE(); // timestamp
int heartRate = buf.readUnsignedByte();
@@ -290,14 +301,14 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
buf.readerIndex(endIndex);
}
- if (!hasLocation) {
- getLastLocation(position, null);
- }
positions.add(position);
if (deviceSession != null) {
for (Position p : positions) {
p.setDeviceId(deviceSession.getDeviceId());
+ if (!p.getValid() && !p.hasAttribute(Position.KEY_HDOP)) {
+ getLastLocation(p, null);
+ }
}
} else {
return null;
@@ -305,9 +316,195 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder {
return positions;
+ } else if (type == MSG_CONFIGURATION) {
+
+ return decodeConfiguration(channel, remoteAddress, buf);
+
+ } else if (type == MSG_RESPONSE) {
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ getLastLocation(position, null);
+
+ buf.readUnsignedByte(); // length
+ position.set(Position.KEY_RESULT, String.valueOf(buf.readUnsignedByte()));
+
+ return position;
+
}
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/main/java/org/traccar/protocol/Minifinder2ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolEncoder.java
new file mode 100644
index 000000000..6e330a4dd
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/Minifinder2ProtocolEncoder.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.traccar.BaseProtocolEncoder;
+import org.traccar.Protocol;
+import org.traccar.helper.Checksum;
+import org.traccar.model.Command;
+
+import java.nio.charset.StandardCharsets;
+
+public class Minifinder2ProtocolEncoder extends BaseProtocolEncoder {
+
+ public Minifinder2ProtocolEncoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ private ByteBuf encodeContent(ByteBuf content) {
+
+ ByteBuf buf = Unpooled.buffer();
+
+ buf.writeByte(0xAB); // header
+ buf.writeByte(0x00); // properties
+ buf.writeShortLE(content.readableBytes());
+ buf.writeShortLE(Checksum.crc16(Checksum.CRC16_XMODEM, content.nioBuffer()));
+ buf.writeShortLE(1); // index
+ buf.writeBytes(content);
+
+ return buf;
+ }
+
+ @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
+ }
+
+ 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);
+ content.writeByte(Minifinder2ProtocolDecoder.MSG_SYSTEM_CONTROL);
+ content.writeByte(1 + url.length());
+ content.writeByte(0x30); // type
+ content.writeCharSequence(url, StandardCharsets.US_ASCII);
+ return encodeContent(content);
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/MobilogixProtocol.java b/src/main/java/org/traccar/protocol/MobilogixProtocol.java
index 1b06c2249..36d6b5ed2 100644
--- a/src/main/java/org/traccar/protocol/MobilogixProtocol.java
+++ b/src/main/java/org/traccar/protocol/MobilogixProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class MobilogixProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/MoovboxProtocol.java b/src/main/java/org/traccar/protocol/MoovboxProtocol.java
index 16438e122..af853fe67 100644
--- a/src/main/java/org/traccar/protocol/MoovboxProtocol.java
+++ b/src/main/java/org/traccar/protocol/MoovboxProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class MoovboxProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/MotorProtocol.java b/src/main/java/org/traccar/protocol/MotorProtocol.java
index 3101c9b75..f17886577 100644
--- a/src/main/java/org/traccar/protocol/MotorProtocol.java
+++ b/src/main/java/org/traccar/protocol/MotorProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class MotorProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Mta6Protocol.java b/src/main/java/org/traccar/protocol/Mta6Protocol.java
index 019fe4fa9..c1c6eb829 100644
--- a/src/main/java/org/traccar/protocol/Mta6Protocol.java
+++ b/src/main/java/org/traccar/protocol/Mta6Protocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Mta6Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java
index 896c7a2d2..9704cf099 100644
--- a/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java
@@ -96,7 +96,7 @@ public class Mta6ProtocolDecoder extends BaseProtocolDecoder {
}
- private static class TimeReader extends FloatReader {
+ private static final class TimeReader extends FloatReader {
private long weekNumber;
diff --git a/src/main/java/org/traccar/protocol/MtxProtocol.java b/src/main/java/org/traccar/protocol/MtxProtocol.java
index e085b6221..12d324019 100644
--- a/src/main/java/org/traccar/protocol/MtxProtocol.java
+++ b/src/main/java/org/traccar/protocol/MtxProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class MtxProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/MxtProtocol.java b/src/main/java/org/traccar/protocol/MxtProtocol.java
index 1190bf527..2f0cc1658 100644
--- a/src/main/java/org/traccar/protocol/MxtProtocol.java
+++ b/src/main/java/org/traccar/protocol/MxtProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class MxtProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/NavigilProtocol.java b/src/main/java/org/traccar/protocol/NavigilProtocol.java
index 46a6c33a5..a309235c5 100644
--- a/src/main/java/org/traccar/protocol/NavigilProtocol.java
+++ b/src/main/java/org/traccar/protocol/NavigilProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class NavigilProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/NavisProtocol.java b/src/main/java/org/traccar/protocol/NavisProtocol.java
index 640a77803..96b5b0de0 100644
--- a/src/main/java/org/traccar/protocol/NavisProtocol.java
+++ b/src/main/java/org/traccar/protocol/NavisProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class NavisProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/NavisetProtocol.java b/src/main/java/org/traccar/protocol/NavisetProtocol.java
index 388f141f8..6df0b0436 100644
--- a/src/main/java/org/traccar/protocol/NavisetProtocol.java
+++ b/src/main/java/org/traccar/protocol/NavisetProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class NavisetProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/NavtelecomProtocol.java b/src/main/java/org/traccar/protocol/NavtelecomProtocol.java
index 50013d1a4..de5f93df1 100644
--- a/src/main/java/org/traccar/protocol/NavtelecomProtocol.java
+++ b/src/main/java/org/traccar/protocol/NavtelecomProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class NavtelecomProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java b/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java
index 08b1a8d0f..cd7ffa0e1 100644
--- a/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java
@@ -193,12 +193,12 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder {
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
- for (int j = 0; j < bits.length(); j++) {
- if (bits.get(j)) {
+ for (int j = 1; j <= bits.length(); j++) {
+ if (bits.get(j - 1)) {
int value;
- switch (j + 1) {
+ switch (j) {
case 1:
position.set(Position.KEY_INDEX, buf.readUnsignedIntLE());
break;
@@ -208,6 +208,18 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder {
case 3:
position.setDeviceTime(new Date(buf.readUnsignedIntLE() * 1000));
break;
+ case 4:
+ value = buf.readUnsignedByte();
+ position.set(
+ Position.KEY_ALARM,
+ BitUtil.check(value, 2) ? Position.ALARM_GENERAL : null);
+ int guardMode = BitUtil.between(value, 3, 4);
+ position.set(Position.KEY_ARMED, (0 < guardMode) && (guardMode < 3));
+ break;
+ case 5:
+ value = buf.readUnsignedByte();
+ position.set(Position.KEY_ROAMING, BitUtil.check(value, 6) ? true : null);
+ break;
case 8:
value = buf.readUnsignedByte();
position.setValid(BitUtil.check(value, 1));
@@ -232,7 +244,7 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder {
position.setCourse(buf.readUnsignedShortLE());
break;
case 15:
- position.set(Position.KEY_ODOMETER, buf.readFloatLE());
+ position.set(Position.KEY_ODOMETER, buf.readFloatLE() * 1000);
break;
case 19:
position.set(Position.KEY_POWER, buf.readShortLE() * 0.001);
@@ -246,7 +258,7 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder {
case 24:
case 25:
case 26:
- position.set(Position.PREFIX_ADC + (j + 2 - 21), buf.readUnsignedShortLE() * 0.001);
+ position.set(Position.PREFIX_ADC + (j + 1 - 21), buf.readUnsignedShortLE() * 0.001);
break;
case 29:
value = buf.readUnsignedByte();
@@ -262,14 +274,14 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder {
break;
case 33:
case 34:
- position.set(Position.PREFIX_COUNT + (j + 2 - 33), buf.readUnsignedIntLE());
+ position.set(Position.PREFIX_COUNT + (j + 1 - 33), buf.readUnsignedIntLE());
break;
case 35:
case 36:
- position.set("freq" + (j + 2 - 35), buf.readUnsignedShortLE());
+ position.set("freq" + (j + 1 - 35), buf.readUnsignedShortLE());
break;
case 37:
- position.set(Position.KEY_HOURS, buf.readUnsignedIntLE());
+ position.set(Position.KEY_HOURS, buf.readUnsignedIntLE() * 1000);
break;
case 38:
case 39:
@@ -278,7 +290,8 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder {
case 42:
case 43:
value = buf.readUnsignedShortLE();
- position.set("fuel" + (j + 2 - 38), (value < 65500) ? value : null);
+ position.set(
+ Position.KEY_FUEL_LEVEL + (j + 1 - 38), (value < 65500) ? value : null);
break;
case 44:
value = buf.readUnsignedShortLE();
@@ -294,8 +307,75 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder {
case 52:
value = buf.readByte();
position.set(
- Position.PREFIX_TEMP + (j + 2 - 45),
- (value != (byte) 0x80) ? value : null);
+ Position.PREFIX_TEMP + (j + 1 - 45), (value != (byte) 0x80) ? value : null);
+ break;
+ case 53:
+ value = buf.readUnsignedShortLE();
+ if (value != 0x7FFF) {
+ if (BitUtil.check(value, 15)) {
+ position.set("obdFuelLevel", BitUtil.to(value, 14));
+ } else {
+ position.set("obdFuel", BitUtil.to(value, 14) * 0.1);
+ }
+ }
+ break;
+ case 54:
+ double fuelUsed = buf.readFloatLE() * 0.5;
+ position.set(Position.KEY_FUEL_USED, (fuelUsed >= 0) ? fuelUsed : null);
+ break;
+ case 55:
+ value = buf.readUnsignedShortLE();
+ position.set(Position.KEY_RPM, (value != 0xFFFF) ? value : null);
+ break;
+ case 56:
+ value = buf.readByte();
+ position.set(Position.KEY_COOLANT_TEMP, (value != (byte) 0x80) ? value : null);
+ break;
+ case 57:
+ position.set(Position.KEY_OBD_ODOMETER, buf.readFloatLE() * 1000);
+ break;
+ case 58:
+ case 59:
+ case 60:
+ case 61:
+ case 62:
+ value = buf.readUnsignedShortLE();
+ position.set("axleWeight" + (j + 1 - 58), (value != 0xFFFF) ? value : null);
+ break;
+ case 63:
+ value = buf.readUnsignedByte();
+ position.set("acceleratorPosition", (value != 0xFF) ? value : null);
+ break;
+ case 64:
+ value = buf.readUnsignedByte();
+ position.set("brakePosition", (value != 0xFF) ? value : null);
+ break;
+ case 65:
+ value = buf.readUnsignedByte();
+ position.set(Position.KEY_ENGINE_LOAD, (value != 0xFF) ? value : null);
+ break;
+ case 66:
+ value = buf.readUnsignedShortLE();
+ if (value != 0x7FFF) {
+ if (BitUtil.check(value, 15)) {
+ position.set("obdAdBlueLevel", BitUtil.to(value, 14));
+ } else {
+ position.set("obdAdBlue", BitUtil.to(value, 14) * 0.1);
+ }
+ }
+ break;
+ case 67:
+ position.set("obdHours", buf.readUnsignedIntLE() * 1000);
+ break;
+ case 68:
+ value = buf.readUnsignedShortLE();
+ position.set(
+ Position.KEY_ODOMETER_SERVICE,
+ (value != 0xFFFF) ? (value * 5000) : null);
+ break;
+ case 69:
+ value = buf.readUnsignedByte();
+ position.set(Position.KEY_OBD_SPEED, (value != 0xFF) ? value : null);
break;
case 78:
case 79:
@@ -303,10 +383,39 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder {
case 81:
case 82:
case 83:
- position.set("fuelTemp" + (j + 2 - 78), (int) buf.readByte());
+ position.set("fuelTemp" + (j + 1 - 78), (int) buf.readByte());
+ break;
+ case 163:
+ case 164:
+ case 165:
+ case 166:
+ value = buf.readShortLE();
+ position.set(
+ Position.PREFIX_TEMP + (j + 1 + 8 - 163),
+ (value != (short) 0x8000) ? value * 0.05 : null);
+ break;
+ case 167:
+ case 168:
+ case 169:
+ case 170:
+ value = buf.readUnsignedByte();
+ position.set("humidity" + (j + 1 - 167), (value != 0xFF) ? value * 0.5 : null);
+ break;
+ case 206:
+ position.set("diagnostic", buf.readUnsignedIntLE());
break;
default:
- buf.skipBytes(getItemLength(j + 1));
+ if ((207 <= j) && (j <= 222)) {
+ position.set("user1Byte" + (j + 1 - 207), buf.readUnsignedByte());
+ } else if ((223 <= j) && (j <= 237)) {
+ position.set("user2Byte" + (j + 1 - 223), buf.readUnsignedShortLE());
+ } else if ((238 <= j) && (j <= 252)) {
+ position.set("user4Byte" + (j + 1 - 238), buf.readUnsignedIntLE());
+ } else if ((253 <= j) && (j <= 255)) {
+ position.set("user8Byte" + (j + 1 - 253), buf.readLongLE());
+ } else {
+ buf.skipBytes(getItemLength(j));
+ }
break;
}
}
diff --git a/src/main/java/org/traccar/protocol/NdtpV6Protocol.java b/src/main/java/org/traccar/protocol/NdtpV6Protocol.java
index ce0dbbef2..9493132f5 100644
--- a/src/main/java/org/traccar/protocol/NdtpV6Protocol.java
+++ b/src/main/java/org/traccar/protocol/NdtpV6Protocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class NdtpV6Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/NeosProtocol.java b/src/main/java/org/traccar/protocol/NeosProtocol.java
index 0787b6562..16a6ba5a0 100644
--- a/src/main/java/org/traccar/protocol/NeosProtocol.java
+++ b/src/main/java/org/traccar/protocol/NeosProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class NeosProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/NetProtocol.java b/src/main/java/org/traccar/protocol/NetProtocol.java
index f27e4afb8..e011660da 100644
--- a/src/main/java/org/traccar/protocol/NetProtocol.java
+++ b/src/main/java/org/traccar/protocol/NetProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class NetProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/NiotProtocol.java b/src/main/java/org/traccar/protocol/NiotProtocol.java
index 0fbe0c689..7eacd5ff3 100644
--- a/src/main/java/org/traccar/protocol/NiotProtocol.java
+++ b/src/main/java/org/traccar/protocol/NiotProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class NiotProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/NoranProtocol.java b/src/main/java/org/traccar/protocol/NoranProtocol.java
index 626991029..d03e52be5 100644
--- a/src/main/java/org/traccar/protocol/NoranProtocol.java
+++ b/src/main/java/org/traccar/protocol/NoranProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class NoranProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/NtoProtocol.java b/src/main/java/org/traccar/protocol/NtoProtocol.java
new file mode 100644
index 000000000..d3596e287
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/NtoProtocol.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.handler.codec.string.StringDecoder;
+import io.netty.handler.codec.string.StringEncoder;
+import jakarta.inject.Inject;
+import org.traccar.BaseProtocol;
+import org.traccar.CharacterDelimiterFrameDecoder;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+import org.traccar.config.Config;
+
+public class NtoProtocol extends BaseProtocol {
+
+ @Inject
+ public NtoProtocol(Config config) {
+ addServer(new TrackerServer(config, getName(), false) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '&'));
+ pipeline.addLast(new StringEncoder());
+ pipeline.addLast(new StringDecoder());
+ pipeline.addLast(new NtoProtocolDecoder(NtoProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/NtoProtocolDecoder.java b/src/main/java/org/traccar/protocol/NtoProtocolDecoder.java
new file mode 100644
index 000000000..ba9ebd95d
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/NtoProtocolDecoder.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.channel.Channel;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.Protocol;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.Parser;
+import org.traccar.helper.PatternBuilder;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.net.SocketAddress;
+import java.util.regex.Pattern;
+
+public class NtoProtocolDecoder extends BaseProtocolDecoder {
+
+ public NtoProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ private static final Pattern PATTERN = new PatternBuilder()
+ .text("^NB,") // manufacturer
+ .number("(d+),") // imei
+ .expression("(...),") // type
+ .number("(dd)(dd)(dd),") // date (ddmmyy)
+ .number("(dd)(dd)(dd),") // time (hhmmss)
+ .expression("([AVM]),") // validity
+ .number("([NS]),(dd)(dd.d+),") // latitude
+ .number("([EW]),(ddd)(dd.d+),") // longitude
+ .number("(d+.?d*),") // speed
+ .number("(d+),") // course
+ .number("(x+),") // status
+ .any()
+ .compile();
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ Parser parser = new Parser(PATTERN, (String) msg);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.set(Position.KEY_TYPE, parser.next());
+
+ position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS));
+
+ position.setValid(parser.next().equals("A"));
+ position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN));
+ position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN));
+ position.setSpeed(parser.nextDouble());
+ position.setCourse(parser.nextInt());
+
+ long status = parser.nextHexLong();
+ position.set(Position.KEY_STATUS, status);
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 1) ? Position.ALARM_JAMMING : null);
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 3 * 8 + 1) ? Position.ALARM_POWER_CUT : null);
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 3 * 8 + 2) ? Position.ALARM_OVERSPEED : null);
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 3 * 8 + 3) ? Position.ALARM_VIBRATION : null);
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 3 * 8 + 4) ? Position.ALARM_GEOFENCE_ENTER : null);
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 3 * 8 + 5) ? Position.ALARM_GEOFENCE_EXIT : null);
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 4 * 8) ? Position.ALARM_LOW_BATTERY : null);
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 4 * 8 + 4) ? Position.ALARM_DOOR : null);
+
+ return position;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/NvsProtocol.java b/src/main/java/org/traccar/protocol/NvsProtocol.java
index 7ed488e38..8a4ece30d 100644
--- a/src/main/java/org/traccar/protocol/NvsProtocol.java
+++ b/src/main/java/org/traccar/protocol/NvsProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class NvsProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/NyitechProtocol.java b/src/main/java/org/traccar/protocol/NyitechProtocol.java
index e7ef10945..225b1bd5a 100644
--- a/src/main/java/org/traccar/protocol/NyitechProtocol.java
+++ b/src/main/java/org/traccar/protocol/NyitechProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.config.Config;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class NyitechProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ObdDongleProtocol.java b/src/main/java/org/traccar/protocol/ObdDongleProtocol.java
index 94f450426..9fcc35d0d 100644
--- a/src/main/java/org/traccar/protocol/ObdDongleProtocol.java
+++ b/src/main/java/org/traccar/protocol/ObdDongleProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ObdDongleProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/OigoProtocol.java b/src/main/java/org/traccar/protocol/OigoProtocol.java
index 0539bada6..3483f8270 100644
--- a/src/main/java/org/traccar/protocol/OigoProtocol.java
+++ b/src/main/java/org/traccar/protocol/OigoProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class OigoProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/OkoProtocol.java b/src/main/java/org/traccar/protocol/OkoProtocol.java
index 29c8bc1b9..6ca6c0e93 100644
--- a/src/main/java/org/traccar/protocol/OkoProtocol.java
+++ b/src/main/java/org/traccar/protocol/OkoProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class OkoProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/OmnicommProtocol.java b/src/main/java/org/traccar/protocol/OmnicommProtocol.java
index dd400c779..b59b84132 100644
--- a/src/main/java/org/traccar/protocol/OmnicommProtocol.java
+++ b/src/main/java/org/traccar/protocol/OmnicommProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class OmnicommProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/OpenGtsProtocol.java b/src/main/java/org/traccar/protocol/OpenGtsProtocol.java
index 5443b4ffc..24d6de706 100644
--- a/src/main/java/org/traccar/protocol/OpenGtsProtocol.java
+++ b/src/main/java/org/traccar/protocol/OpenGtsProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class OpenGtsProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/OrbcommProtocol.java b/src/main/java/org/traccar/protocol/OrbcommProtocol.java
index fb09f0abb..06b00619c 100644
--- a/src/main/java/org/traccar/protocol/OrbcommProtocol.java
+++ b/src/main/java/org/traccar/protocol/OrbcommProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerClient;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class OrbcommProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java b/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java
index 1164d72a1..7ed13d647 100644
--- a/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java
@@ -24,10 +24,10 @@ import org.traccar.Protocol;
import org.traccar.helper.UnitsConverter;
import org.traccar.model.Position;
-import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.json.JsonValue;
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonValue;
import java.io.StringReader;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
diff --git a/src/main/java/org/traccar/protocol/OrionProtocol.java b/src/main/java/org/traccar/protocol/OrionProtocol.java
index 2dec7cd06..b78af462b 100644
--- a/src/main/java/org/traccar/protocol/OrionProtocol.java
+++ b/src/main/java/org/traccar/protocol/OrionProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class OrionProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/OsmAndProtocol.java b/src/main/java/org/traccar/protocol/OsmAndProtocol.java
index a86bc70d7..e06580949 100644
--- a/src/main/java/org/traccar/protocol/OsmAndProtocol.java
+++ b/src/main/java/org/traccar/protocol/OsmAndProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class OsmAndProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/OutsafeProtocol.java b/src/main/java/org/traccar/protocol/OutsafeProtocol.java
index 0099be456..159534883 100644
--- a/src/main/java/org/traccar/protocol/OutsafeProtocol.java
+++ b/src/main/java/org/traccar/protocol/OutsafeProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class OutsafeProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java b/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java
index 62b873be7..f71778412 100644
--- a/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java
@@ -23,11 +23,11 @@ import org.traccar.session.DeviceSession;
import org.traccar.Protocol;
import org.traccar.model.Position;
-import javax.json.Json;
-import javax.json.JsonNumber;
-import javax.json.JsonObject;
-import javax.json.JsonString;
-import javax.json.JsonValue;
+import jakarta.json.Json;
+import jakarta.json.JsonNumber;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonString;
+import jakarta.json.JsonValue;
import java.io.StringReader;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
diff --git a/src/main/java/org/traccar/protocol/OwnTracksProtocol.java b/src/main/java/org/traccar/protocol/OwnTracksProtocol.java
index 9ad337f19..c509ad282 100644
--- a/src/main/java/org/traccar/protocol/OwnTracksProtocol.java
+++ b/src/main/java/org/traccar/protocol/OwnTracksProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class OwnTracksProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java b/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java
index 71ac87168..e54d07fa7 100644
--- a/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java
@@ -25,8 +25,8 @@ import org.traccar.Protocol;
import org.traccar.helper.UnitsConverter;
import org.traccar.model.Position;
-import javax.json.Json;
-import javax.json.JsonObject;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
import java.io.StringReader;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
diff --git a/src/main/java/org/traccar/protocol/PacificTrackProtocol.java b/src/main/java/org/traccar/protocol/PacificTrackProtocol.java
index 709729ef1..a315d4d9f 100644
--- a/src/main/java/org/traccar/protocol/PacificTrackProtocol.java
+++ b/src/main/java/org/traccar/protocol/PacificTrackProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class PacificTrackProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/PathAwayProtocol.java b/src/main/java/org/traccar/protocol/PathAwayProtocol.java
index 1d13eea95..a65740475 100644
--- a/src/main/java/org/traccar/protocol/PathAwayProtocol.java
+++ b/src/main/java/org/traccar/protocol/PathAwayProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class PathAwayProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/PiligrimProtocol.java b/src/main/java/org/traccar/protocol/PiligrimProtocol.java
index aa45a0def..9dd1bc491 100644
--- a/src/main/java/org/traccar/protocol/PiligrimProtocol.java
+++ b/src/main/java/org/traccar/protocol/PiligrimProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class PiligrimProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/PluginProtocol.java b/src/main/java/org/traccar/protocol/PluginProtocol.java
index b2101b18d..fff1830e8 100644
--- a/src/main/java/org/traccar/protocol/PluginProtocol.java
+++ b/src/main/java/org/traccar/protocol/PluginProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class PluginProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/PolteProtocol.java b/src/main/java/org/traccar/protocol/PolteProtocol.java
index 69666cc0e..0fbedfb09 100644
--- a/src/main/java/org/traccar/protocol/PolteProtocol.java
+++ b/src/main/java/org/traccar/protocol/PolteProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class PolteProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/PolteProtocolDecoder.java b/src/main/java/org/traccar/protocol/PolteProtocolDecoder.java
index 028de5424..8954db491 100644
--- a/src/main/java/org/traccar/protocol/PolteProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/PolteProtocolDecoder.java
@@ -23,8 +23,8 @@ import org.traccar.session.DeviceSession;
import org.traccar.Protocol;
import org.traccar.model.Position;
-import javax.json.Json;
-import javax.json.JsonObject;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
import java.io.StringReader;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
diff --git a/src/main/java/org/traccar/protocol/PortmanProtocol.java b/src/main/java/org/traccar/protocol/PortmanProtocol.java
index de78013fa..3a4b49289 100644
--- a/src/main/java/org/traccar/protocol/PortmanProtocol.java
+++ b/src/main/java/org/traccar/protocol/PortmanProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class PortmanProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/PortmanProtocolDecoder.java b/src/main/java/org/traccar/protocol/PortmanProtocolDecoder.java
index da9403313..716f2694b 100644
--- a/src/main/java/org/traccar/protocol/PortmanProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/PortmanProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2020 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,11 @@ public class PortmanProtocolDecoder extends BaseProtocolDecoder {
}
private static final Pattern PATTERN_STANDARD = new PatternBuilder()
+ .groupBegin()
.text("$PTMLA,") // header
+ .or()
+ .text("%%") // header
+ .groupEnd()
.expression("([^,]+),") // id
.expression("([ABCL]),") // validity
.number("(dd)(dd)(dd)") // date (yymmdd)
@@ -47,12 +51,19 @@ public class PortmanProtocolDecoder extends BaseProtocolDecoder {
.number("(d+),") // course
.number("(?:NA|C(-?d+)),") // temperature
.number("(x{8}),") // status
- .number("(?:NA|(d+)),") // card id
+ .groupBegin()
+ .text("NA")
+ .or()
+ .number("F(d+)") // fuel
+ .or()
+ .number("(d+)") // card id
+ .groupEnd(",")
.number("(d+),") // event
.number("(d+),") // satellites
.number("(d+.d+),") // odometer
- .number("(d+),") // rssi
- .number("(?:G(d+)|[^,]*)") // fuel
+ .number("(d+)") // rssi
+ .number(",G(d+)").optional() // fuel
+ .any()
.compile();
private Object decodeStandard(Channel channel, SocketAddress remoteAddress, String sentence) {
@@ -79,6 +90,9 @@ public class PortmanProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.PREFIX_TEMP + 1, parser.next());
position.set(Position.KEY_STATUS, parser.nextHexLong());
+ if (parser.hasNext()) {
+ position.set(Position.KEY_FUEL_LEVEL, parser.nextInt() * 0.1);
+ }
position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next());
int event = parser.nextInt();
@@ -159,7 +173,7 @@ public class PortmanProtocolDecoder extends BaseProtocolDecoder {
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
String sentence = (String) msg;
- if (sentence.startsWith("$PTMLA")) {
+ if (sentence.startsWith("%%") || sentence.startsWith("$PTMLA")) {
return decodeStandard(channel, remoteAddress, sentence);
} else if (sentence.startsWith("$EXT")) {
return decodeExtended(channel, remoteAddress, sentence);
diff --git a/src/main/java/org/traccar/protocol/PositrexProtocol.java b/src/main/java/org/traccar/protocol/PositrexProtocol.java
new file mode 100644
index 000000000..5cf389fbe
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/PositrexProtocol.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import jakarta.inject.Inject;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+import org.traccar.config.Config;
+
+public class PositrexProtocol extends BaseProtocol {
+
+ @Inject
+ public PositrexProtocol(Config config) {
+ addServer(new TrackerServer(config, getName(), true) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new PositrexProtocolDecoder(PositrexProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/PositrexProtocolDecoder.java b/src/main/java/org/traccar/protocol/PositrexProtocolDecoder.java
new file mode 100644
index 000000000..82ae2c134
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/PositrexProtocolDecoder.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.Protocol;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.DateBuilder;
+import org.traccar.helper.UnitsConverter;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.net.SocketAddress;
+import java.util.Date;
+
+public class PositrexProtocolDecoder extends BaseProtocolDecoder {
+
+ public PositrexProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ public static final int MSG_PING = 0x2E;
+
+ private Date readTime(ByteBuf buf) {
+ long time = buf.readUnsignedInt();
+ DateBuilder dateBuilder = new DateBuilder();
+ dateBuilder.setSecond((int) (time % 60));
+ time /= 60;
+ dateBuilder.setMinute((int) (time % 60));
+ time /= 60;
+ dateBuilder.setHour((int) (time % 24));
+ time /= 24;
+ dateBuilder.setDay((int) (time % 32));
+ time /= 32;
+ dateBuilder.setMonth((int) (time % 13));
+ dateBuilder.setYear((int) (2000 + time / 13));
+ return dateBuilder.getDate();
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ ByteBuf buf = (ByteBuf) msg;
+
+ int first = buf.getUnsignedByte(buf.readerIndex());
+ long deviceId;
+ if (BitUtil.check(first, 7)) {
+ if (BitUtil.check(first, 6)) {
+ deviceId = 73000000 + BitUtil.to(buf.readUnsignedInt(), 30);
+ } else if (!BitUtil.check(first, 5) && !BitUtil.check(first, 4)) {
+ deviceId = 7590000 + BitUtil.to(buf.readUnsignedMedium(), 20);
+ } else {
+ deviceId = 70000000 + BitUtil.to(buf.readUnsignedMedium(), 20);
+ }
+ } else {
+ deviceId = 7560000 + buf.readUnsignedShort();
+ }
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(deviceId));
+ if (deviceSession == null) {
+ return null;
+ }
+
+ int service = buf.readUnsignedByte();
+ if (service == MSG_PING) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.setTime(readTime(buf));
+
+ int latitude = buf.readMedium();
+ int longitude = buf.readMedium();
+
+ position.setValid(BitUtil.check(latitude, 23));
+ position.setLatitude(BitUtil.to(latitude, 23) * 0.000025);
+ position.setLongitude(longitude * 0.000025);
+
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte()));
+ position.setCourse(buf.readUnsignedByte() * 2);
+
+ position.set(Position.PREFIX_IO, buf.readUnsignedByte());
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());
+ buf.readUnsignedInt(); // report begin
+ buf.readUnsignedInt(); // report end
+ buf.readUnsignedInt(); // number of records
+
+ if (buf.isReadable()) {
+ position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01);
+ position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01);
+ }
+
+ return position;
+
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/PretraceProtocol.java b/src/main/java/org/traccar/protocol/PretraceProtocol.java
index b77dd97bf..54a34fc69 100644
--- a/src/main/java/org/traccar/protocol/PretraceProtocol.java
+++ b/src/main/java/org/traccar/protocol/PretraceProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class PretraceProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/PricolProtocol.java b/src/main/java/org/traccar/protocol/PricolProtocol.java
index f5e904541..7b0e7386c 100644
--- a/src/main/java/org/traccar/protocol/PricolProtocol.java
+++ b/src/main/java/org/traccar/protocol/PricolProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class PricolProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ProgressProtocol.java b/src/main/java/org/traccar/protocol/ProgressProtocol.java
index 49eb6847f..8d159ef24 100644
--- a/src/main/java/org/traccar/protocol/ProgressProtocol.java
+++ b/src/main/java/org/traccar/protocol/ProgressProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ProgressProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/PstProtocol.java b/src/main/java/org/traccar/protocol/PstProtocol.java
index 73f978cbd..01a83b31f 100644
--- a/src/main/java/org/traccar/protocol/PstProtocol.java
+++ b/src/main/java/org/traccar/protocol/PstProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class PstProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Pt215Protocol.java b/src/main/java/org/traccar/protocol/Pt215Protocol.java
index b272582a4..fd67b1241 100644
--- a/src/main/java/org/traccar/protocol/Pt215Protocol.java
+++ b/src/main/java/org/traccar/protocol/Pt215Protocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Pt215Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Pt3000Protocol.java b/src/main/java/org/traccar/protocol/Pt3000Protocol.java
index d72774f47..5f49084ed 100644
--- a/src/main/java/org/traccar/protocol/Pt3000Protocol.java
+++ b/src/main/java/org/traccar/protocol/Pt3000Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Pt3000Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Pt502Protocol.java b/src/main/java/org/traccar/protocol/Pt502Protocol.java
index d5d30e8e8..0257dd60f 100644
--- a/src/main/java/org/traccar/protocol/Pt502Protocol.java
+++ b/src/main/java/org/traccar/protocol/Pt502Protocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Pt502Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Pt60Protocol.java b/src/main/java/org/traccar/protocol/Pt60Protocol.java
index 58345f025..83e3bfbeb 100644
--- a/src/main/java/org/traccar/protocol/Pt60Protocol.java
+++ b/src/main/java/org/traccar/protocol/Pt60Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Pt60Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/PuiProtocol.java b/src/main/java/org/traccar/protocol/PuiProtocol.java
new file mode 100644
index 000000000..ac8291039
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/PuiProtocol.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.handler.codec.mqtt.MqttDecoder;
+import io.netty.handler.codec.mqtt.MqttEncoder;
+import jakarta.inject.Inject;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+import org.traccar.config.Config;
+
+public class PuiProtocol extends BaseProtocol {
+
+ @Inject
+ public PuiProtocol(Config config) {
+ addServer(new TrackerServer(config, getName(), false) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(MqttEncoder.INSTANCE);
+ pipeline.addLast(new MqttDecoder());
+ pipeline.addLast(new PuiProtocolDecoder(PuiProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/PuiProtocolDecoder.java b/src/main/java/org/traccar/protocol/PuiProtocolDecoder.java
new file mode 100644
index 000000000..f10ff3fe7
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/PuiProtocolDecoder.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.handler.codec.mqtt.MqttPublishMessage;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
+import org.apache.kafka.common.utils.ByteBufferInputStream;
+import org.traccar.BaseMqttProtocolDecoder;
+import org.traccar.Protocol;
+import org.traccar.helper.UnitsConverter;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+public class PuiProtocolDecoder extends BaseMqttProtocolDecoder {
+
+ public PuiProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ @Override
+ protected Object decode(DeviceSession deviceSession, MqttPublishMessage message) throws Exception {
+
+ JsonObject json;
+ try (ByteBufferInputStream inputStream = new ByteBufferInputStream(message.payload().nioBuffer())) {
+ json = Json.createReader(inputStream).readObject();
+ }
+
+ String type = json.getString("rpt");
+ switch (type) {
+ case "hf":
+ case "loc":
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.setValid(true);
+
+ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+ position.setTime(dateFormat.parse(json.getString("ts")));
+
+ JsonObject location = json.getJsonObject("location");
+ position.setLatitude(location.getJsonNumber("lat").doubleValue());
+ position.setLongitude(location.getJsonNumber("lon").doubleValue());
+
+ position.setCourse(json.getInt("bear"));
+ position.setSpeed(UnitsConverter.knotsFromCps(json.getInt("spd")));
+
+ position.set(Position.KEY_IGNITION, json.getString("ign").equals("on"));
+
+ return position;
+
+ default:
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/R12wProtocol.java b/src/main/java/org/traccar/protocol/R12wProtocol.java
index a406f6306..b5b3eff81 100644
--- a/src/main/java/org/traccar/protocol/R12wProtocol.java
+++ b/src/main/java/org/traccar/protocol/R12wProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class R12wProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java b/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java
index 63ca3476c..6f7340902 100644
--- a/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java
+++ b/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class RaceDynamicsProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/RadarProtocol.java b/src/main/java/org/traccar/protocol/RadarProtocol.java
index 9d88c6d72..8985e0e83 100644
--- a/src/main/java/org/traccar/protocol/RadarProtocol.java
+++ b/src/main/java/org/traccar/protocol/RadarProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class RadarProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/RamacProtocol.java b/src/main/java/org/traccar/protocol/RamacProtocol.java
new file mode 100644
index 000000000..42ce16fe8
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/RamacProtocol.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpRequestDecoder;
+import io.netty.handler.codec.http.HttpResponseEncoder;
+import jakarta.inject.Inject;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+import org.traccar.config.Config;
+
+public class RamacProtocol extends BaseProtocol {
+
+ @Inject
+ public RamacProtocol(Config config) {
+ addServer(new TrackerServer(config, getName(), false) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new HttpResponseEncoder());
+ pipeline.addLast(new HttpRequestDecoder());
+ pipeline.addLast(new HttpObjectAggregator(65535));
+ pipeline.addLast(new RamacProtocolDecoder(RamacProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/RamacProtocolDecoder.java b/src/main/java/org/traccar/protocol/RamacProtocolDecoder.java
new file mode 100644
index 000000000..ffdc68474
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/RamacProtocolDecoder.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
+import org.traccar.BaseHttpProtocolDecoder;
+import org.traccar.Protocol;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.io.StringReader;
+import java.net.SocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+public class RamacProtocolDecoder extends BaseHttpProtocolDecoder {
+
+ private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ public RamacProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ FullHttpRequest request = (FullHttpRequest) msg;
+ String content = request.content().toString(StandardCharsets.UTF_8);
+ JsonObject json = Json.createReader(new StringReader(content)).readObject();
+
+ String deviceId = json.getString("DeviceId");
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, deviceId);
+ if (deviceSession == null) {
+ sendResponse(channel, HttpResponseStatus.BAD_REQUEST);
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.set(Position.KEY_TYPE, json.getInt("PacketType"));
+ position.set(Position.KEY_INDEX, json.getInt("SeqNumber"));
+ position.setDeviceTime(dateFormat.parse(json.getString("UpdateDate")));
+
+ int alert = json.getInt("Alert");
+ if (alert > 0) {
+ position.set("alert", alert);
+ String alertMessage = json.getString("AlertMessage");
+ if (!alertMessage.isEmpty()) {
+ position.set("alertMessage", alertMessage);
+ }
+ }
+
+ if (json.containsKey("GpsEvent")) {
+ position.set("gpsEvent", json.getInt("GpsEvent"));
+ if (json.containsKey("GpsEventText")) {
+ position.set("gpsEventText", json.getString("GpsEventText"));
+ }
+ }
+
+ if (json.containsKey("Event")) {
+ position.set(Position.KEY_EVENT, json.getInt("Event"));
+ }
+ if (json.containsKey("BatteryPercentage")) {
+ position.set(Position.KEY_BATTERY_LEVEL, json.getInt("BatteryPercentage"));
+ }
+ if (json.containsKey("Battery")) {
+ position.set(Position.KEY_BATTERY, json.getJsonNumber("Battery").doubleValue());
+ }
+
+ position.set("deviceType", json.getString("DeviceTypeText"));
+
+ if (json.containsKey("Latitude") && json.containsKey("Longitude")) {
+ position.setValid(true);
+ if (json.containsKey("LocationDateTime")) {
+ position.setFixTime(dateFormat.parse(json.getString("LocationDateTime")));
+ } else {
+ position.setFixTime(position.getDeviceTime());
+ }
+ position.setLatitude(json.getJsonNumber("Latitude").doubleValue());
+ position.setLongitude(json.getJsonNumber("Longitude").doubleValue());
+ } else {
+ getLastLocation(position, position.getDeviceTime());
+ }
+
+ sendResponse(
+ channel, HttpResponseStatus.OK,
+ Unpooled.copiedBuffer("{\"CaseID\":1,\"EventID\":1}", StandardCharsets.UTF_8));
+ return position;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/RaveonProtocol.java b/src/main/java/org/traccar/protocol/RaveonProtocol.java
index db70396ee..aa1a79219 100644
--- a/src/main/java/org/traccar/protocol/RaveonProtocol.java
+++ b/src/main/java/org/traccar/protocol/RaveonProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class RaveonProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/RecodaProtocol.java b/src/main/java/org/traccar/protocol/RecodaProtocol.java
index 0d50db01e..7d2fadae4 100644
--- a/src/main/java/org/traccar/protocol/RecodaProtocol.java
+++ b/src/main/java/org/traccar/protocol/RecodaProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class RecodaProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/RetranslatorProtocol.java b/src/main/java/org/traccar/protocol/RetranslatorProtocol.java
index 1d4b419bb..a349a8191 100644
--- a/src/main/java/org/traccar/protocol/RetranslatorProtocol.java
+++ b/src/main/java/org/traccar/protocol/RetranslatorProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class RetranslatorProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/RfTrackProtocol.java b/src/main/java/org/traccar/protocol/RfTrackProtocol.java
index d3b41e93e..ac033c348 100644
--- a/src/main/java/org/traccar/protocol/RfTrackProtocol.java
+++ b/src/main/java/org/traccar/protocol/RfTrackProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class RfTrackProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/RfTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/RfTrackProtocolDecoder.java
index 28a3ac29c..cbb204e3b 100644
--- a/src/main/java/org/traccar/protocol/RfTrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/RfTrackProtocolDecoder.java
@@ -29,9 +29,9 @@ import org.traccar.model.Position;
import org.traccar.model.WifiAccessPoint;
import org.traccar.session.DeviceSession;
-import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
import java.io.StringReader;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
diff --git a/src/main/java/org/traccar/protocol/RitiProtocol.java b/src/main/java/org/traccar/protocol/RitiProtocol.java
index 9b9c00cb2..9916042a8 100644
--- a/src/main/java/org/traccar/protocol/RitiProtocol.java
+++ b/src/main/java/org/traccar/protocol/RitiProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class RitiProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/RoboTrackProtocol.java b/src/main/java/org/traccar/protocol/RoboTrackProtocol.java
index ab2bc5842..229c343bb 100644
--- a/src/main/java/org/traccar/protocol/RoboTrackProtocol.java
+++ b/src/main/java/org/traccar/protocol/RoboTrackProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class RoboTrackProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/RstProtocol.java b/src/main/java/org/traccar/protocol/RstProtocol.java
index 109d91b16..0bb809a49 100644
--- a/src/main/java/org/traccar/protocol/RstProtocol.java
+++ b/src/main/java/org/traccar/protocol/RstProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class RstProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java
index fcc96fbf1..eafa4d3d7 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 - 2021 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.
@@ -69,8 +69,10 @@ public class RstProtocolDecoder extends BaseProtocolDecoder {
.number("x{4};") // sensors
.number("(xx);") // status 1
.number("(xx);") // status 2
+ .expression("(.*)") // additional data
.groupEnd("?")
.any()
+ .text("FIM;")
.compile();
@Override
@@ -115,9 +117,16 @@ public class RstProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_SATELLITES, parser.nextInt());
position.set(Position.KEY_HDOP, parser.nextInt());
- position.set(Position.PREFIX_IN + 1, parser.nextHexInt());
- position.set(Position.PREFIX_IN + 2, parser.nextHexInt());
- position.set(Position.PREFIX_IN + 3, parser.nextHexInt());
+
+ int inputs1 = parser.nextHexInt();
+ int inputs2 = parser.nextHexInt();
+ int inputs3 = parser.nextHexInt();
+ position.set(Position.PREFIX_IN + 1, inputs1);
+ position.set(Position.PREFIX_IN + 2, inputs2);
+ position.set(Position.PREFIX_IN + 3, inputs3);
+
+ position.set(Position.KEY_IGNITION, BitUtil.check(inputs2, 7));
+
position.set(Position.PREFIX_OUT + 1, parser.nextHexInt());
position.set(Position.PREFIX_OUT + 2, parser.nextHexInt());
position.set(Position.KEY_POWER, parser.nextDouble());
@@ -125,10 +134,23 @@ public class RstProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_ODOMETER, parser.nextInt());
position.set(Position.KEY_RSSI, parser.nextInt());
position.set(Position.PREFIX_TEMP + 1, (int) parser.nextHexInt().byteValue());
+ position.set(Position.KEY_STATUS, (parser.nextHexInt() << 8) + parser.nextHexInt());
+
+ String[] values = parser.next().split(";");
+ if (type == 55) {
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, values[0]);
+ }
+
+ return position;
+
+ } else if (type == 134) {
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ getLastLocation(position, null);
- int status = (parser.nextHexInt() << 8) + parser.nextHexInt();
- position.set(Position.KEY_IGNITION, BitUtil.check(status, 7));
- position.set(Position.KEY_STATUS, status);
+ position.set(Position.KEY_RESULT, String.valueOf(type));
return position;
diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocol.java b/src/main/java/org/traccar/protocol/RuptelaProtocol.java
index 99a9686f6..9f399e299 100644
--- a/src/main/java/org/traccar/protocol/RuptelaProtocol.java
+++ b/src/main/java/org/traccar/protocol/RuptelaProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class RuptelaProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java
index 77df0deb7..e1efb5757 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.
@@ -22,6 +22,7 @@ import org.traccar.BaseProtocolDecoder;
import org.traccar.session.DeviceSession;
import org.traccar.NetworkMessage;
import org.traccar.Protocol;
+import org.traccar.helper.BitUtil;
import org.traccar.helper.DataConverter;
import org.traccar.helper.UnitsConverter;
import org.traccar.model.Position;
@@ -50,6 +51,7 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_SMS_VIA_GPRS = 8;
public static final int MSG_DTCS = 9;
public static final int MSG_IDENTIFICATION = 15;
+ public static final int MSG_HEARTBEAT = 16;
public static final int MSG_SET_IO = 17;
public static final int MSG_FILES = 37;
public static final int MSG_EXTENDED_RECORDS = 68;
@@ -92,22 +94,55 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder {
}
}
+ private void decodeDriver(Position position, String part1, String part2) {
+ Long driverIdPart1 = (Long) position.getAttributes().remove(part1);
+ Long driverIdPart2 = (Long) position.getAttributes().remove(part2);
+ if (driverIdPart1 != null && driverIdPart2 != null) {
+ ByteBuf driverId = Unpooled.copyLong(driverIdPart1, driverIdPart2);
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, driverId.toString(StandardCharsets.US_ASCII));
+ driverId.release();
+ }
+ }
+
private void decodeParameter(Position position, int id, ByteBuf buf, int length) {
switch (id) {
case 2:
case 3:
case 4:
- position.set("di" + (id - 1), readValue(buf, length, false));
- break;
case 5:
- position.set(Position.KEY_IGNITION, readValue(buf, length, false) == 1);
+ position.set(Position.PREFIX_IN + (id - 1), readValue(buf, length, false));
+ break;
+ case 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;
+ case 21:
+ position.set(Position.PREFIX_ADC + 4, readValue(buf, length, false));
+ break;
+ case 22:
+ position.set(Position.PREFIX_ADC + 1, readValue(buf, length, false));
+ break;
+ case 23:
+ position.set(Position.PREFIX_ADC + 2, readValue(buf, length, false));
break;
case 29:
- position.set(Position.KEY_POWER, readValue(buf, length, false));
+ position.set(Position.KEY_POWER, readValue(buf, length, false) * 0.001);
break;
case 30:
position.set(Position.KEY_BATTERY, readValue(buf, length, false) * 0.001);
break;
+ case 32:
+ position.set(Position.KEY_DEVICE_TEMP, readValue(buf, length, true));
+ break;
+ case 39:
+ position.set(Position.KEY_ENGINE_LOAD, readValue(buf, length, false));
+ break;
+ case 65:
+ position.set(Position.KEY_ODOMETER, readValue(buf, length, false));
+ break;
case 74:
position.set(Position.PREFIX_TEMP + 3, readValue(buf, length, true) * 0.1);
break;
@@ -116,6 +151,23 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder {
case 80:
position.set(Position.PREFIX_TEMP + (id - 78), readValue(buf, length, true) * 0.1);
break;
+ case 88:
+ if (readValue(buf, length, false) > 0) {
+ position.set(Position.KEY_ALARM, Position.ALARM_JAMMING);
+ }
+ break;
+ case 94:
+ position.set(Position.KEY_RPM, readValue(buf, length, false) * 0.25);
+ break;
+ case 95:
+ position.set(Position.KEY_OBD_SPEED, readValue(buf, length, false));
+ break;
+ case 98:
+ 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, false) / 20.0);
+ break;
case 134:
if (readValue(buf, length, false) > 0) {
position.set(Position.KEY_ALARM, Position.ALARM_BRAKING);
@@ -126,9 +178,61 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION);
}
break;
+ case 150:
+ position.set(Position.KEY_OPERATOR, readValue(buf, length, false));
+ break;
+ case 163:
+ position.set(Position.KEY_ODOMETER, readValue(buf, length, false) * 5);
+ break;
+ 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);
+ break;
+ case 410:
+ if (readValue(buf, length, false) > 0) {
+ position.set(Position.KEY_ALARM, Position.ALARM_TOW);
+ }
+ break;
+ case 411:
+ if (readValue(buf, length, false) > 0) {
+ position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT);
+ }
+ break;
+ case 415:
+ if (readValue(buf, length, false) == 0) {
+ position.set(Position.KEY_ALARM, Position.ALARM_GPS_ANTENNA_CUT);
+ }
+ break;
+ case 645:
+ position.set(Position.KEY_OBD_ODOMETER, readValue(buf, length, false) * 1000);
+ break;
+ case 758:
+ if (readValue(buf, length, false) == 1) {
+ position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING);
+ }
+ break;
default:
position.set(Position.PREFIX_IO + id, readValue(buf, length, false));
break;
@@ -166,22 +270,36 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder {
buf.readUnsignedByte(); // timestamp extension
if (type == MSG_EXTENDED_RECORDS) {
- buf.readUnsignedByte(); // record extension
+ int recordExtension = buf.readUnsignedByte();
+ int mergeRecordCount = BitUtil.from(recordExtension, 4);
+ int currentRecord = BitUtil.to(recordExtension, 4);
+
+ if (currentRecord > 0 && currentRecord <= mergeRecordCount) {
+ if (positions.size() == 0) {
+ getLastLocation(position, null);
+ } else {
+ position = positions.remove(positions.size() - 1);
+ }
+ }
}
buf.readUnsignedByte(); // priority (reserved)
- position.setValid(true);
- position.setLongitude(buf.readInt() / 10000000.0);
- position.setLatitude(buf.readInt() / 10000000.0);
- position.setAltitude(buf.readUnsignedShort() / 10.0);
- position.setCourse(buf.readUnsignedShort() / 100.0);
-
- position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
-
- position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort()));
-
- position.set(Position.KEY_HDOP, buf.readUnsignedByte() / 10.0);
+ int longitude = buf.readInt();
+ int latitude = buf.readInt();
+ if (longitude > Integer.MIN_VALUE && latitude > Integer.MIN_VALUE) {
+ position.setValid(true);
+ position.setLongitude(longitude / 10000000.0);
+ position.setLatitude(latitude / 10000000.0);
+ position.setAltitude(buf.readUnsignedShort() / 10.0);
+ position.setCourse(buf.readUnsignedShort() / 100.0);
+ position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort()));
+ position.set(Position.KEY_HDOP, buf.readUnsignedByte() / 10.0);
+ } else {
+ buf.skipBytes(8);
+ getLastLocation(position, null);
+ }
if (type == MSG_EXTENDED_RECORDS) {
position.set(Position.KEY_EVENT, buf.readUnsignedShort());
@@ -217,12 +335,13 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder {
decodeParameter(position, id, buf, 8);
}
- Long driverIdPart1 = (Long) position.getAttributes().remove(Position.PREFIX_IO + 126);
- Long driverIdPart2 = (Long) position.getAttributes().remove(Position.PREFIX_IO + 127);
- if (driverIdPart1 != null && driverIdPart2 != null) {
- ByteBuf driverId = Unpooled.copyLong(driverIdPart1, driverIdPart2);
- position.set(Position.KEY_DRIVER_UNIQUE_ID, driverId.toString(StandardCharsets.US_ASCII));
- driverId.release();
+ decodeDriver(position, Position.PREFIX_IO + 126, Position.PREFIX_IO + 127); // can driver
+ decodeDriver(position, Position.PREFIX_IO + 155, Position.PREFIX_IO + 156); // tco driver
+
+ Long tagIdPart1 = (Long) position.getAttributes().remove(Position.PREFIX_IO + 760);
+ Long tagIdPart2 = (Long) position.getAttributes().remove(Position.PREFIX_IO + 761);
+ if (tagIdPart1 != null && tagIdPart2 != null) {
+ position.set("tagId", Long.toHexString(tagIdPart1) + Long.toHexString(tagIdPart2));
}
positions.add(position);
@@ -306,7 +425,7 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder {
return null;
- } else if (type == MSG_IDENTIFICATION) {
+ } else if (type == MSG_IDENTIFICATION || type == MSG_HEARTBEAT) {
ByteBuf content = Unpooled.buffer();
content.writeByte(1);
diff --git a/src/main/java/org/traccar/protocol/S168Protocol.java b/src/main/java/org/traccar/protocol/S168Protocol.java
index f904ed9ff..5fb0c6e72 100644
--- a/src/main/java/org/traccar/protocol/S168Protocol.java
+++ b/src/main/java/org/traccar/protocol/S168Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class S168Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SabertekProtocol.java b/src/main/java/org/traccar/protocol/SabertekProtocol.java
index 403243cdc..cb3f2ab32 100644
--- a/src/main/java/org/traccar/protocol/SabertekProtocol.java
+++ b/src/main/java/org/traccar/protocol/SabertekProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SabertekProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SanavProtocol.java b/src/main/java/org/traccar/protocol/SanavProtocol.java
index 1a0e7b0e9..ac1941725 100644
--- a/src/main/java/org/traccar/protocol/SanavProtocol.java
+++ b/src/main/java/org/traccar/protocol/SanavProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SanavProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SanulProtocol.java b/src/main/java/org/traccar/protocol/SanulProtocol.java
index ea44bf868..cba162296 100644
--- a/src/main/java/org/traccar/protocol/SanulProtocol.java
+++ b/src/main/java/org/traccar/protocol/SanulProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.config.Config;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SanulProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SatsolProtocol.java b/src/main/java/org/traccar/protocol/SatsolProtocol.java
index d90033e38..7252f99f0 100644
--- a/src/main/java/org/traccar/protocol/SatsolProtocol.java
+++ b/src/main/java/org/traccar/protocol/SatsolProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.config.Config;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SatsolProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SigfoxProtocol.java b/src/main/java/org/traccar/protocol/SigfoxProtocol.java
index 9a268af62..edd624727 100644
--- a/src/main/java/org/traccar/protocol/SigfoxProtocol.java
+++ b/src/main/java/org/traccar/protocol/SigfoxProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SigfoxProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java
index 4ed2bb51d..e0dfab9b2 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.
@@ -32,11 +32,11 @@ import org.traccar.model.Network;
import org.traccar.model.Position;
import org.traccar.model.WifiAccessPoint;
-import javax.json.Json;
-import javax.json.JsonNumber;
-import javax.json.JsonObject;
-import javax.json.JsonString;
-import javax.json.JsonValue;
+import jakarta.json.Json;
+import jakarta.json.JsonNumber;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonString;
+import jakarta.json.JsonValue;
import java.io.StringReader;
import java.net.SocketAddress;
import java.net.URLDecoder;
@@ -105,7 +105,7 @@ public class SigfoxProtocolDecoder extends BaseHttpProtocolDecoder {
FullHttpRequest request = (FullHttpRequest) msg;
String content = request.content().toString(StandardCharsets.UTF_8);
if (!content.startsWith("{")) {
- content = URLDecoder.decode(content.split("=")[0], "UTF-8");
+ content = URLDecoder.decode(content.split("=")[0], StandardCharsets.UTF_8);
}
JsonObject json = Json.createReader(new StringReader(content)).readObject();
@@ -156,18 +156,30 @@ public class SigfoxProtocolDecoder extends BaseHttpProtocolDecoder {
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(getDeviceModel(deviceSession))) {
+
+ 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/main/java/org/traccar/protocol/SiwiProtocol.java b/src/main/java/org/traccar/protocol/SiwiProtocol.java
index f12958a50..59b96bf72 100644
--- a/src/main/java/org/traccar/protocol/SiwiProtocol.java
+++ b/src/main/java/org/traccar/protocol/SiwiProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SiwiProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SkypatrolProtocol.java b/src/main/java/org/traccar/protocol/SkypatrolProtocol.java
index 7ae26f634..615ef536d 100644
--- a/src/main/java/org/traccar/protocol/SkypatrolProtocol.java
+++ b/src/main/java/org/traccar/protocol/SkypatrolProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SkypatrolProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SmartSoleProtocol.java b/src/main/java/org/traccar/protocol/SmartSoleProtocol.java
index cb7efb7ee..e4838581a 100644
--- a/src/main/java/org/traccar/protocol/SmartSoleProtocol.java
+++ b/src/main/java/org/traccar/protocol/SmartSoleProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SmartSoleProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SmokeyProtocol.java b/src/main/java/org/traccar/protocol/SmokeyProtocol.java
index 22b343537..0aa2bcfa7 100644
--- a/src/main/java/org/traccar/protocol/SmokeyProtocol.java
+++ b/src/main/java/org/traccar/protocol/SmokeyProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SmokeyProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java b/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java
index 0676aa629..e00f27b9b 100644
--- a/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java
+++ b/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SolarPoweredProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SpotProtocol.java b/src/main/java/org/traccar/protocol/SpotProtocol.java
index 6bd802fed..4fc57f177 100644
--- a/src/main/java/org/traccar/protocol/SpotProtocol.java
+++ b/src/main/java/org/traccar/protocol/SpotProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SpotProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/StarLinkProtocol.java b/src/main/java/org/traccar/protocol/StarLinkProtocol.java
index d578fa705..6dcd40fbf 100644
--- a/src/main/java/org/traccar/protocol/StarLinkProtocol.java
+++ b/src/main/java/org/traccar/protocol/StarLinkProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class StarLinkProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java
index aa23bfac5..193005e28 100644
--- a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java
@@ -61,10 +61,10 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder {
@Override
protected void init() {
setFormat(getConfig().getString(
- getProtocolName() + ".format", "#EDT#,#EID#,#PDT#,#LAT#,#LONG#,#SPD#,#HEAD#,#ODO#,"
+ Keys.PROTOCOL_FORMAT.withPrefix(getProtocolName()), "#EDT#,#EID#,#PDT#,#LAT#,#LONG#,#SPD#,#HEAD#,#ODO#,"
+ "#IN1#,#IN2#,#IN3#,#IN4#,#OUT1#,#OUT2#,#OUT3#,#OUT4#,#LAC#,#CID#,#VIN#,#VBAT#,#DEST#,#IGN#,#ENG#"));
- setDateFormat(getConfig().getString(getProtocolName() + ".dateFormat", "yyMMddHHmmss"));
+ setDateFormat(getConfig().getString(Keys.PROTOCOL_DATE_FORMAT.withPrefix(getProtocolName()), "yyMMddHHmmss"));
}
public String[] getFormat(long deviceId) {
diff --git a/src/main/java/org/traccar/protocol/StarcomProtocol.java b/src/main/java/org/traccar/protocol/StarcomProtocol.java
index 33c3a4776..458220e59 100644
--- a/src/main/java/org/traccar/protocol/StarcomProtocol.java
+++ b/src/main/java/org/traccar/protocol/StarcomProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class StarcomProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java
index 56ab733c8..325847b16 100644
--- a/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java
@@ -75,7 +75,7 @@ public class StarcomProtocolDecoder extends BaseProtocolDecoder {
case "eventid":
position.set(Position.KEY_EVENT, Integer.parseInt(value));
break;
- case "mileage":
+ case "odometer":
position.set(Position.KEY_ODOMETER, (long) (Double.parseDouble(value) * 1000));
break;
case "satellites":
@@ -110,8 +110,8 @@ public class StarcomProtocolDecoder extends BaseProtocolDecoder {
case "extra1":
case "extra2":
case "extra3":
- position.set(key, value);
default:
+ position.set(key, value);
break;
}
}
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 000000000..608b128cd
--- /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 d010df858..9c01d24e2 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 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;
@@ -24,7 +23,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class StartekProtocol extends BaseProtocol {
@@ -38,7 +37,7 @@ public class StartekProtocol extends BaseProtocol {
addServer(new TrackerServer(config, getName(), false) {
@Override
protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
- pipeline.addLast(new LineBasedFrameDecoder(1024));
+ 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 8e3624cb5..0eeb5b2aa 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 class StartekProtocolDecoder extends BaseProtocolDecoder {
.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)
@@ -72,12 +73,10 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
.number("(x+)") // battery
.expression("([^,]+)?") // adc
.groupBegin()
- .text(",")
- .number("d,") // extended
- .expression("([^,]+)?") // fuel
+ .number(",d+") // extended
+ .expression(",([^,]+)?") // fuel
.groupBegin()
- .text(",")
- .expression("([^,]+)?") // temperature
+ .expression(",([^,]+)?") // temperature
.groupBegin()
.text(",")
.groupBegin()
@@ -91,16 +90,26 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
.number("(d+)?|") // instant fuel
.number("(d+)[%L]").optional() // fuel level
.groupEnd("?")
+ .number(",(d+)").optional() // hours
.groupEnd("?")
.groupEnd("?")
.groupEnd("?")
+ .any()
.compile();
private String decodeAlarm(int value) {
switch (value) {
+ case 1:
+ return Position.ALARM_SOS;
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:
@@ -126,26 +135,24 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
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()) {
@@ -176,7 +183,7 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
position.setCourse(parser.nextInt());
position.setAltitude(parser.nextInt());
- position.set(Position.KEY_ODOMETER, parser.nextInt());
+ position.set(Position.KEY_ODOMETER, parser.nextLong());
position.setNetwork(new Network(CellTower.from(
parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt(), parser.nextInt())));
@@ -186,6 +193,7 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
int input = parser.nextHexInt();
int output = parser.nextHexInt();
position.set(Position.KEY_IGNITION, BitUtil.check(input, 1));
+ position.set(Position.KEY_DOOR, BitUtil.check(input, 2));
position.set(Position.KEY_INPUT, input);
position.set(Position.KEY_OUTPUT, output);
@@ -221,7 +229,7 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
}
}
- if (parser.hasNext(6)) {
+ if (parser.hasNextAny(9)) {
position.set(Position.KEY_RPM, parser.nextInt());
position.set(Position.KEY_ENGINE_LOAD, parser.nextInt());
position.set("airFlow", parser.nextInt());
@@ -239,6 +247,87 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_FUEL_LEVEL, parser.nextInt());
}
+ if (parser.hasNext()) {
+ position.set(Position.KEY_HOURS, parser.nextInt() * 1000L);
+ }
+
+ return position;
+ }
+
+ 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/main/java/org/traccar/protocol/StbProtocol.java b/src/main/java/org/traccar/protocol/StbProtocol.java
index af4e0d2c4..0beaed39c 100644
--- a/src/main/java/org/traccar/protocol/StbProtocol.java
+++ b/src/main/java/org/traccar/protocol/StbProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class StbProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/StbProtocolDecoder.java b/src/main/java/org/traccar/protocol/StbProtocolDecoder.java
index 641359bfd..c52ab485f 100644
--- a/src/main/java/org/traccar/protocol/StbProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/StbProtocolDecoder.java
@@ -22,9 +22,9 @@ import org.traccar.Protocol;
import org.traccar.model.Position;
import org.traccar.session.DeviceSession;
-import javax.json.Json;
-import javax.json.JsonObject;
-import javax.json.JsonValue;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonValue;
import java.io.StringReader;
import java.net.SocketAddress;
import java.util.Date;
diff --git a/src/main/java/org/traccar/protocol/Stl060Protocol.java b/src/main/java/org/traccar/protocol/Stl060Protocol.java
index 83b5db3bb..ac23ab3ee 100644
--- a/src/main/java/org/traccar/protocol/Stl060Protocol.java
+++ b/src/main/java/org/traccar/protocol/Stl060Protocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Stl060Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SuntechProtocol.java b/src/main/java/org/traccar/protocol/SuntechProtocol.java
index 4253b761b..0cc5fc75c 100644
--- a/src/main/java/org/traccar/protocol/SuntechProtocol.java
+++ b/src/main/java/org/traccar/protocol/SuntechProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SuntechProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java
index 047a1822a..53c4a5d02 100644
--- a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java
@@ -271,18 +271,26 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
index += 1; // collaborative network
}
- DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHH:mm:ss");
- dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- position.setTime(dateFormat.parse(values[index++] + values[index++]));
+ if (values[index].isEmpty()) {
- position.setLatitude(Double.parseDouble(values[index++]));
- position.setLongitude(Double.parseDouble(values[index++]));
- position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[index++])));
- position.setCourse(Double.parseDouble(values[index++]));
+ getLastLocation(position, null);
- position.set(Position.KEY_SATELLITES, Integer.parseInt(values[index++]));
+ } else {
- position.setValid(values[index++].equals("1"));
+ DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHH:mm:ss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ position.setTime(dateFormat.parse(values[index++] + values[index++]));
+
+ position.setLatitude(Double.parseDouble(values[index++]));
+ position.setLongitude(Double.parseDouble(values[index++]));
+ position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[index++])));
+ position.setCourse(Double.parseDouble(values[index++]));
+
+ position.set(Position.KEY_SATELLITES, Integer.parseInt(values[index++]));
+
+ position.setValid(values[index++].equals("1"));
+
+ }
return position;
}
@@ -446,9 +454,10 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder {
if (values.length - index >= 2) {
String driverUniqueId = values[index++];
- if (values[index++].equals("1") && !driverUniqueId.isEmpty()) {
+ if (!driverUniqueId.isEmpty()) {
position.set(Position.KEY_DRIVER_UNIQUE_ID, driverUniqueId);
}
+ index += 1; // registered
}
if (isIncludeTemp(deviceSession.getDeviceId())) {
diff --git a/src/main/java/org/traccar/protocol/SupermateProtocol.java b/src/main/java/org/traccar/protocol/SupermateProtocol.java
index 4290b7126..064f12b4b 100644
--- a/src/main/java/org/traccar/protocol/SupermateProtocol.java
+++ b/src/main/java/org/traccar/protocol/SupermateProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SupermateProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SviasProtocol.java b/src/main/java/org/traccar/protocol/SviasProtocol.java
index 7c6624f7c..a903d503c 100644
--- a/src/main/java/org/traccar/protocol/SviasProtocol.java
+++ b/src/main/java/org/traccar/protocol/SviasProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SviasProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/SwiftechProtocol.java b/src/main/java/org/traccar/protocol/SwiftechProtocol.java
index 68cf40d84..d5fa5c5d3 100644
--- a/src/main/java/org/traccar/protocol/SwiftechProtocol.java
+++ b/src/main/java/org/traccar/protocol/SwiftechProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class SwiftechProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/T55Protocol.java b/src/main/java/org/traccar/protocol/T55Protocol.java
index cedac275f..e76959fea 100644
--- a/src/main/java/org/traccar/protocol/T55Protocol.java
+++ b/src/main/java/org/traccar/protocol/T55Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class T55Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java b/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java
index 3be161fb8..9e7518ce5 100644
--- a/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,7 +41,8 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder {
}
private static final Pattern PATTERN_GPRMC = new PatternBuilder()
- .text("$GPRMC,")
+ .text("$")
+ .expression("G[PLN]RMC,")
.number("(dd)(dd)(dd).?d*,") // time (hhmmss)
.expression("([AV]),") // validity
.number("(dd)(dd.d+),") // latitude
@@ -64,7 +65,8 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder {
.compile();
private static final Pattern PATTERN_GPGGA = new PatternBuilder()
- .text("$GPGGA,")
+ .text("$")
+ .expression("G[PLN]GGA,")
.number("(dd)(dd)(dd).?d*,") // time (hhmmss)
.number("(d+)(dd.d+),") // latitude
.expression("([NS]),")
@@ -150,6 +152,18 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder {
.number("xx") // checksum
.compile();
+ private static final Pattern PATTERN_GPTXT = new PatternBuilder()
+ .text("$GPTXT,")
+ .text("NET,")
+ .number("(d+),") // device id
+ .expression("([^,]+),") // network operator
+ .number("(-d+),") // rssi
+ .number("(d+) ") // mcc
+ .number("(d+)") // mnc
+ .text("*")
+ .number("xx") // checksum
+ .compile();
+
private Position position = null;
private Position decodeGprmc(
@@ -328,6 +342,31 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private Position decodeGptxt(Channel channel, SocketAddress remoteAddress, String sentence) {
+
+ Parser parser = new Parser(PATTERN_GPTXT, sentence);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ getLastLocation(position, null);
+
+ position.set(Position.KEY_OPERATOR, parser.next());
+ position.set(Position.KEY_RSSI, parser.nextInt());
+ position.set("mcc", parser.nextInt());
+ position.set("mnc", parser.nextInt());
+
+ return position;
+ }
+
private Position decodePubx(Channel channel, SocketAddress remoteAddress, String sentence) {
Parser parser = new Parser(PATTERN_PUBX, sentence);
@@ -407,9 +446,9 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder {
}
} else if (sentence.matches("^[0-9A-F]+$")) {
getDeviceSession(channel, remoteAddress, sentence);
- } else if (sentence.startsWith("$GPRMC")) {
+ } else if (sentence.startsWith("RMC", 3)) {
return decodeGprmc(deviceSession, sentence, remoteAddress, channel);
- } else if (sentence.startsWith("$GPGGA") && deviceSession != null) {
+ } else if (sentence.startsWith("GGA", 3) && deviceSession != null) {
return decodeGpgga(deviceSession, sentence);
} else if (sentence.startsWith("$GPRMA") && deviceSession != null) {
return decodeGprma(deviceSession, sentence);
@@ -421,6 +460,8 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder {
return decodeQze(channel, remoteAddress, sentence);
} else if (sentence.startsWith("$PUBX")) {
return decodePubx(channel, remoteAddress, sentence);
+ } else if (sentence.startsWith("$GPTXT")) {
+ return decodeGptxt(channel, remoteAddress, sentence);
}
return null;
diff --git a/src/main/java/org/traccar/protocol/T57Protocol.java b/src/main/java/org/traccar/protocol/T57Protocol.java
index 4bafe8c6d..e6ef4ccc9 100644
--- a/src/main/java/org/traccar/protocol/T57Protocol.java
+++ b/src/main/java/org/traccar/protocol/T57Protocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class T57Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/T622IridiumProtocol.java b/src/main/java/org/traccar/protocol/T622IridiumProtocol.java
new file mode 100644
index 000000000..22efa38a8
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/T622IridiumProtocol.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+import org.traccar.config.Config;
+
+import jakarta.inject.Inject;
+
+public class T622IridiumProtocol extends BaseProtocol {
+
+ @Inject
+ public T622IridiumProtocol(Config config) {
+ addServer(new TrackerServer(config, getName(), false) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 1, 2));
+ pipeline.addLast(new T622IridiumProtocolDecoder(T622IridiumProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/T622IridiumProtocolDecoder.java b/src/main/java/org/traccar/protocol/T622IridiumProtocolDecoder.java
new file mode 100644
index 000000000..9e64ec9be
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/T622IridiumProtocolDecoder.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.Protocol;
+import org.traccar.config.Keys;
+import org.traccar.helper.UnitsConverter;
+import org.traccar.helper.model.AttributeUtil;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.net.SocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class T622IridiumProtocolDecoder extends BaseProtocolDecoder {
+
+ private String format;
+
+ public T622IridiumProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ public List<Integer> getParameters(long deviceId) {
+ String value = AttributeUtil.lookup(
+ getCacheManager(), Keys.PROTOCOL_FORMAT.withPrefix(getProtocolName()), deviceId);
+ return Arrays.stream((value != null ? value : format).split(","))
+ .map(s -> Integer.parseInt(s, 16))
+ .collect(Collectors.toList());
+ }
+
+ public void setFormat(String format) {
+ this.format = format;
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ ByteBuf buf = (ByteBuf) msg;
+
+ buf.readUnsignedByte(); // protocol revision
+ buf.readUnsignedShort(); // length
+ buf.readUnsignedByte(); // header indicator
+ buf.readUnsignedShort(); // header length
+ buf.readUnsignedInt(); // reference
+
+ String imei = buf.readCharSequence(15, StandardCharsets.US_ASCII).toString();
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei);
+ if (deviceSession == null) {
+ return null;
+ }
+
+ buf.readUnsignedByte(); // session status
+ buf.readUnsignedShort(); // originator index
+ buf.readUnsignedShort(); // transfer index
+ buf.readUnsignedInt(); // session time
+ buf.readUnsignedByte(); // payload indicator
+ buf.readUnsignedShort(); // payload length
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ List<Integer> parameters = getParameters(deviceSession.getDeviceId());
+
+ for (int parameter : parameters) {
+ switch (parameter) {
+ case 0x01:
+ position.set(Position.KEY_EVENT, buf.readUnsignedByte());
+ break;
+ case 0x02:
+ position.setLatitude(buf.readIntLE() / 1000000.0);
+ break;
+ case 0x03:
+ position.setLongitude(buf.readIntLE() / 1000000.0);
+ break;
+ case 0x04:
+ position.setTime(new Date((buf.readUnsignedIntLE() + 946684800) * 1000));
+ break;
+ case 0x05:
+ position.setValid(buf.readUnsignedByte() > 0);
+ break;
+ case 0x06:
+ position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
+ break;
+ case 0x07:
+ position.set(Position.KEY_RSSI, buf.readUnsignedByte());
+ break;
+ case 0x08:
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE()));
+ break;
+ case 0x09:
+ position.setCourse(buf.readUnsignedShortLE());
+ break;
+ case 0x0A:
+ position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1);
+ break;
+ case 0x0B:
+ position.setAltitude(buf.readShortLE());
+ break;
+ case 0x0C:
+ position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE());
+ break;
+ case 0x0D:
+ position.set(Position.KEY_HOURS, buf.readUnsignedIntLE() * 1000);
+ break;
+ case 0x14:
+ position.set(Position.KEY_OUTPUT, buf.readUnsignedByte());
+ break;
+ case 0x15:
+ position.set(Position.KEY_INPUT, buf.readUnsignedByte());
+ break;
+ case 0x19:
+ position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.01);
+ break;
+ case 0x1A:
+ position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.01);
+ break;
+ case 0x1B:
+ buf.readUnsignedByte(); // geofence
+ break;
+ default:
+ break;
+ }
+ }
+
+ return position;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/T800xProtocol.java b/src/main/java/org/traccar/protocol/T800xProtocol.java
index 253c3cb73..f50f22a18 100644
--- a/src/main/java/org/traccar/protocol/T800xProtocol.java
+++ b/src/main/java/org/traccar/protocol/T800xProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class T800xProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java
index 6e09e6e3b..23750be8d 100644
--- a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@ import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
+import org.traccar.config.Keys;
+import org.traccar.helper.model.AttributeUtil;
import org.traccar.session.DeviceSession;
import org.traccar.NetworkMessage;
import org.traccar.Protocol;
@@ -161,7 +163,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder {
boolean positionType = type == MSG_GPS || type == MSG_GPS_2 || type == MSG_ALARM || type == MSG_ALARM_2;
if (!positionType) {
- sendResponse(channel, header, type, index, imei, 0);
+ sendResponse(channel, header, type, header == 0x2323 ? 1 : index, imei, 0);
}
if (positionType) {
@@ -389,7 +391,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder {
for (int i = 1; i <= adcCount; i++) {
String value = ByteBufUtil.hexDump(buf.readSlice(2));
if (!value.equals("ffff")) {
- position.set(Position.PREFIX_ADC + i, Integer.parseInt(value) * 0.01);
+ position.set(Position.PREFIX_ADC + i, Integer.parseInt(value, 16) * 0.01);
}
}
}
@@ -398,6 +400,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder {
int alarm = buf.readUnsignedByte();
position.set(Position.KEY_ALARM, header != 0x2727 ? decodeAlarm1(alarm) : decodeAlarm2(alarm));
+ position.set("alarmCode", alarm);
if (header != 0x2727) {
@@ -468,6 +471,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder {
int inputStatus = buf.readUnsignedShort();
position.set(Position.KEY_IGNITION, BitUtil.check(inputStatus, 2));
position.set(Position.KEY_RSSI, BitUtil.between(inputStatus, 4, 11));
+ position.set(Position.KEY_INPUT, inputStatus);
buf.readUnsignedShort(); // ignition on upload interval
buf.readUnsignedInt(); // ignition off upload interval
@@ -511,8 +515,10 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder {
}
}
- if (type == MSG_ALARM || type == MSG_ALARM_2) {
- sendResponse(channel, header, type, index, imei, alarm);
+ boolean acknowledgement = AttributeUtil.lookup(
+ getCacheManager(), Keys.PROTOCOL_ACK.withPrefix(getProtocolName()), deviceSession.getDeviceId());
+ if (acknowledgement || type == MSG_ALARM || type == MSG_ALARM_2) {
+ sendResponse(channel, header, type, header == 0x2323 ? 1 : index, imei, alarm);
}
return position;
diff --git a/src/main/java/org/traccar/protocol/TaipPrefixEncoder.java b/src/main/java/org/traccar/protocol/TaipPrefixEncoder.java
index 48419af2a..75fd447d0 100644
--- a/src/main/java/org/traccar/protocol/TaipPrefixEncoder.java
+++ b/src/main/java/org/traccar/protocol/TaipPrefixEncoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2021 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,40 +15,20 @@
*/
package org.traccar.protocol;
-import com.google.inject.Inject;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
-import org.traccar.Protocol;
-import org.traccar.config.Config;
-import org.traccar.config.Keys;
import java.util.List;
@ChannelHandler.Sharable
public class TaipPrefixEncoder extends MessageToMessageEncoder<ByteBuf> {
- private final Protocol protocol;
- private Config config;
-
- public TaipPrefixEncoder(Protocol protocol) {
- this.protocol = protocol;
- }
-
- @Inject
- public void setConfig(Config config) {
- this.config = config;
- }
-
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {
- if (config.getBoolean(Keys.PROTOCOL_PREFIX.withPrefix(protocol.getName()))) {
- out.add(Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(new byte[] {0x20, 0x20, 0x06, 0x00}), msg.retain()));
- } else {
- out.add(msg.retain());
- }
+ out.add(Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(new byte[] {0x20, 0x20, 0x06, 0x00}), msg.retain()));
}
}
diff --git a/src/main/java/org/traccar/protocol/TaipProtocol.java b/src/main/java/org/traccar/protocol/TaipProtocol.java
index 943ec98c5..f57bb296c 100644
--- a/src/main/java/org/traccar/protocol/TaipProtocol.java
+++ b/src/main/java/org/traccar/protocol/TaipProtocol.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,8 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
+import org.traccar.config.Keys;
public class TaipProtocol extends BaseProtocol {
@@ -33,7 +34,9 @@ public class TaipProtocol extends BaseProtocol {
@Override
protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '<'));
- pipeline.addLast(new TaipPrefixEncoder(TaipProtocol.this));
+ if (config.getBoolean(Keys.PROTOCOL_PREFIX.withPrefix(getName()))) {
+ pipeline.addLast(new TaipPrefixEncoder());
+ }
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new TaipProtocolDecoder(TaipProtocol.this));
@@ -42,7 +45,9 @@ public class TaipProtocol extends BaseProtocol {
addServer(new TrackerServer(config, getName(), true) {
@Override
protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
- pipeline.addLast(new TaipPrefixEncoder(TaipProtocol.this));
+ if (config.getBoolean(Keys.PROTOCOL_PREFIX.withPrefix(getName()))) {
+ pipeline.addLast(new TaipPrefixEncoder());
+ }
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new TaipProtocolDecoder(TaipProtocol.this));
diff --git a/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java b/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java
index e5e84b7c4..448d7ffca 100644
--- a/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2013 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@ import org.traccar.helper.DateBuilder;
import org.traccar.helper.DateUtil;
import org.traccar.helper.Parser;
import org.traccar.helper.PatternBuilder;
-import org.traccar.helper.UnitsConverter;
import org.traccar.model.Position;
import java.net.SocketAddress;
@@ -49,7 +48,7 @@ public class TaipProtocolDecoder extends BaseProtocolDecoder {
.groupEnd("?")
.number("(d{5})") // seconds
.or()
- .expression("(?:RGP|RCQ|RCV|RBR|RUS00),?") // type
+ .expression("(?:RGP|RCQ|RCV|RBR|RUS00|RPI),?") // type
.number("(dd)?") // event
.number("(dd)(dd)(dd)") // date (mmddyy)
.number("(dd)(dd)(dd)") // time (hhmmss)
@@ -84,7 +83,7 @@ public class TaipProtocolDecoder extends BaseProtocolDecoder {
.or()
.groupBegin()
.number("(xx)") // input
- .number("(xx)") // satellites
+ .number("xx") // satellites / outputs
.number("(ddd)") // battery
.number("(x{8})") // odometer
.number("[01]") // gps power
@@ -96,12 +95,14 @@ public class TaipProtocolDecoder extends BaseProtocolDecoder {
.number("[01]") // modem power
.number("[0-5]") // gsm status
.number("(dd)") // rssi
+ .groupBegin()
.number("([-+]dddd)") // temperature 1
.number("xx") // seconds from last
.number("([-+]dddd)") // temperature 2
.number("xx") // seconds from last
.groupEnd("?")
.groupEnd("?")
+ .groupEnd("?")
.groupEnd()
.any()
.compile();
@@ -192,7 +193,7 @@ public class TaipProtocolDecoder extends BaseProtocolDecoder {
position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN));
}
- position.setSpeed(UnitsConverter.knotsFromMph(parser.nextDouble(0)));
+ position.setSpeed(convertSpeed(parser.nextDouble(0), "mph"));
position.setCourse(parser.nextDouble(0));
if (parser.hasNext(2)) {
@@ -217,17 +218,18 @@ public class TaipProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_HDOP, parser.nextInt());
}
- if (parser.hasNext(4)) {
+ if (parser.hasNext(3)) {
position.set(Position.KEY_INPUT, parser.nextHexInt(0));
- position.set(Position.KEY_SATELLITES, parser.nextHexInt(0));
position.set(Position.KEY_BATTERY, parser.nextInt(0));
position.set(Position.KEY_ODOMETER, parser.nextLong(16, 0));
}
- if (parser.hasNext(4)) {
+ if (parser.hasNext(3)) {
valid = parser.nextInt() > 0;
position.set(Position.KEY_PDOP, parser.nextInt());
position.set(Position.KEY_RSSI, parser.nextInt());
+ }
+ if (parser.hasNext(2)) {
position.set(Position.PREFIX_TEMP + 1, parser.nextInt() * 0.01);
position.set(Position.PREFIX_TEMP + 2, parser.nextInt() * 0.01);
}
@@ -262,6 +264,7 @@ public class TaipProtocolDecoder extends BaseProtocolDecoder {
String uniqueId = null;
DeviceSession deviceSession = null;
String messageIndex = null;
+ boolean indexFirst = true;
if (attributes != null) {
for (String attribute : attributes) {
@@ -276,6 +279,9 @@ public class TaipProtocolDecoder extends BaseProtocolDecoder {
if (deviceSession != null) {
position.setDeviceId(deviceSession.getDeviceId());
}
+ if (messageIndex == null) {
+ indexFirst = false;
+ }
break;
case "io":
position.set(Position.KEY_IGNITION, BitUtil.check(value.charAt(0) - '0', 0));
@@ -315,7 +321,11 @@ public class TaipProtocolDecoder extends BaseProtocolDecoder {
if (messageIndex.startsWith("#IP")) {
response = ">SAK;ID=" + uniqueId + ";" + messageIndex + "<";
} else {
- response = ">ACK;ID=" + uniqueId + ";" + messageIndex + ";*";
+ if (indexFirst) {
+ response = ">ACK;" + messageIndex + ";ID=" + uniqueId + ";*";
+ } else {
+ response = ">ACK;ID=" + uniqueId + ";" + messageIndex + ";*";
+ }
response += String.format("%02X", Checksum.xor(response)) + "<";
}
channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
diff --git a/src/main/java/org/traccar/protocol/TechTltProtocol.java b/src/main/java/org/traccar/protocol/TechTltProtocol.java
index 191dd9ccc..a4a7460b0 100644
--- a/src/main/java/org/traccar/protocol/TechTltProtocol.java
+++ b/src/main/java/org/traccar/protocol/TechTltProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TechTltProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TechtoCruzProtocol.java b/src/main/java/org/traccar/protocol/TechtoCruzProtocol.java
index 265a3eb64..f0828a99e 100644
--- a/src/main/java/org/traccar/protocol/TechtoCruzProtocol.java
+++ b/src/main/java/org/traccar/protocol/TechtoCruzProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TechtoCruzProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TekProtocol.java b/src/main/java/org/traccar/protocol/TekProtocol.java
index 54e860d79..56714041b 100644
--- a/src/main/java/org/traccar/protocol/TekProtocol.java
+++ b/src/main/java/org/traccar/protocol/TekProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TekProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TelemaxProtocol.java b/src/main/java/org/traccar/protocol/TelemaxProtocol.java
index 9e9cbb50e..792a5b176 100644
--- a/src/main/java/org/traccar/protocol/TelemaxProtocol.java
+++ b/src/main/java/org/traccar/protocol/TelemaxProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TelemaxProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TelicProtocol.java b/src/main/java/org/traccar/protocol/TelicProtocol.java
index 9ef7864ca..fc5bdf0d1 100644
--- a/src/main/java/org/traccar/protocol/TelicProtocol.java
+++ b/src/main/java/org/traccar/protocol/TelicProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TelicProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java
index 3a0962584..aabc24887 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);
}
diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocol.java b/src/main/java/org/traccar/protocol/TeltonikaProtocol.java
index 38283cb64..f2d610251 100644
--- a/src/main/java/org/traccar/protocol/TeltonikaProtocol.java
+++ b/src/main/java/org/traccar/protocol/TeltonikaProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TeltonikaProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java
index 929eca8aa..6197c6c13 100644
--- a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2013 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
-import org.traccar.model.Device;
+import org.traccar.helper.BufferUtil;
import org.traccar.session.DeviceSession;
import org.traccar.NetworkMessage;
import org.traccar.Protocol;
@@ -33,6 +33,7 @@ import org.traccar.model.Network;
import org.traccar.model.Position;
import java.net.SocketAddress;
+import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
@@ -112,18 +113,6 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
}
}
- private boolean isPrintable(ByteBuf buf, int length) {
- boolean printable = true;
- for (int i = 0; i < length; i++) {
- byte b = buf.getByte(buf.readerIndex() + i);
- if (b < 32 && b != '\r' && b != '\n') {
- printable = false;
- break;
- }
- }
- return printable;
- }
-
private void decodeSerial(
Channel channel, SocketAddress remoteAddress, DeviceSession deviceSession, Position position, ByteBuf buf) {
@@ -169,7 +158,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_TYPE, type);
int length = buf.readInt();
- if (isPrintable(buf, length)) {
+ if (BufferUtil.isPrintable(buf, length)) {
String data = buf.readSlice(length).toString(StandardCharsets.US_ASCII).trim();
if (data.startsWith("UUUUww") && data.endsWith("SSS")) {
String[] values = data.substring(6, data.length() - 4).split(";");
@@ -231,6 +220,8 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
register(26, null, (p, b) -> p.set("bleTemp2", b.readShort() * 0.01));
register(27, null, (p, b) -> p.set("bleTemp3", b.readShort() * 0.01));
register(28, null, (p, b) -> p.set("bleTemp4", b.readShort() * 0.01));
+ register(30, fmbXXX, (p, b) -> p.set("faultCount", b.readUnsignedByte()));
+ register(32, fmbXXX, (p, b) -> p.set(Position.KEY_COOLANT_TEMP, b.readByte()));
register(66, null, (p, b) -> p.set(Position.KEY_POWER, b.readUnsignedShort() * 0.001));
register(67, null, (p, b) -> p.set(Position.KEY_BATTERY, b.readUnsignedShort() * 0.001));
register(68, fmbXXX, (p, b) -> p.set("batteryCurrent", b.readUnsignedShort() * 0.001));
@@ -239,28 +230,52 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
register(74, fmbXXX, (p, b) -> 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));
}
});
register(80, fmbXXX, (p, b) -> p.set("dataMode", b.readUnsignedByte()));
+ register(81, fmbXXX, (p, b) -> p.set(Position.KEY_OBD_SPEED, b.readUnsignedByte()));
+ register(82, fmbXXX, (p, b) -> p.set(Position.KEY_THROTTLE, b.readUnsignedByte()));
+ register(83, fmbXXX, (p, b) -> p.set(Position.KEY_FUEL_USED, b.readUnsignedInt() * 0.1));
+ register(84, fmbXXX, (p, b) -> p.set(Position.KEY_FUEL_LEVEL, b.readUnsignedShort() * 0.1));
+ register(85, fmbXXX, (p, b) -> p.set(Position.KEY_RPM, b.readUnsignedShort()));
+ register(87, fmbXXX, (p, b) -> p.set(Position.KEY_OBD_ODOMETER, b.readUnsignedInt()));
+ register(89, fmbXXX, (p, b) -> p.set("fuelLevelPercentage", b.readUnsignedByte()));
register(90, null, (p, b) -> p.set(Position.KEY_DOOR, b.readUnsignedShort()));
- register(115, fmbXXX, (p, b) -> p.set(Position.KEY_COOLANT_TEMP, b.readShort() * 0.1));
+ register(110, fmbXXX, (p, b) -> p.set(Position.KEY_FUEL_CONSUMPTION, b.readUnsignedShort() * 0.1));
+ register(113, fmbXXX, (p, b) -> p.set(Position.KEY_BATTERY_LEVEL, b.readUnsignedByte()));
register(179, null, (p, b) -> p.set(Position.PREFIX_OUT + 1, b.readUnsignedByte() > 0));
register(180, null, (p, b) -> p.set(Position.PREFIX_OUT + 2, b.readUnsignedByte() > 0));
register(181, null, (p, b) -> p.set(Position.KEY_PDOP, b.readUnsignedShort() * 0.1));
register(182, null, (p, b) -> p.set(Position.KEY_HDOP, b.readUnsignedShort() * 0.1));
register(199, null, (p, b) -> p.set(Position.KEY_ODOMETER_TRIP, b.readUnsignedInt()));
register(200, fmbXXX, (p, b) -> p.set("sleepMode", b.readUnsignedByte()));
- register(205, fmbXXX, (p, b) -> p.set("cid", b.readUnsignedShort()));
+ register(205, fmbXXX, (p, b) -> p.set("cid2g", b.readUnsignedShort()));
register(206, fmbXXX, (p, b) -> p.set("lac", b.readUnsignedShort()));
+ register(232, fmbXXX, (p, b) -> p.set("cngStatus", b.readUnsignedByte() > 0));
+ register(233, fmbXXX, (p, b) -> p.set("cngUsed", b.readUnsignedInt() * 0.1));
+ register(234, fmbXXX, (p, b) -> p.set("cngLevel", b.readUnsignedShort()));
+ register(235, fmbXXX, (p, b) -> p.set("oilLevel", b.readUnsignedByte()));
register(236, null, (p, b) -> {
p.set(Position.KEY_ALARM, b.readUnsignedByte() > 0 ? Position.ALARM_GENERAL : null);
});
register(239, null, (p, b) -> p.set(Position.KEY_IGNITION, b.readUnsignedByte() > 0));
register(240, null, (p, b) -> p.set(Position.KEY_MOTION, b.readUnsignedByte() > 0));
register(241, null, (p, b) -> p.set(Position.KEY_OPERATOR, b.readUnsignedInt()));
+ register(246, fmbXXX, (p, b) -> {
+ p.set(Position.KEY_ALARM, b.readUnsignedByte() > 0 ? Position.ALARM_TOW : null);
+ });
+ register(247, fmbXXX, (p, b) -> {
+ p.set(Position.KEY_ALARM, b.readUnsignedByte() > 0 ? Position.ALARM_ACCIDENT : null);
+ });
+ register(249, fmbXXX, (p, b) -> {
+ p.set(Position.KEY_ALARM, b.readUnsignedByte() > 0 ? Position.ALARM_JAMMING : null);
+ });
+ register(252, fmbXXX, (p, b) -> {
+ p.set(Position.KEY_ALARM, b.readUnsignedByte() > 0 ? Position.ALARM_POWER_CUT : null);
+ });
register(253, null, (p, b) -> {
switch (b.readUnsignedByte()) {
case 1:
@@ -276,6 +291,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
break;
}
});
+ register(636, fmbXXX, (p, b) -> p.set("cid4g", b.readUnsignedInt()));
}
private void decodeGh3000Parameter(Position position, int id, ByteBuf buf, int length) {
@@ -366,14 +382,23 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
position.setNetwork(network);
}
} else {
- Integer cid = (Integer) position.getAttributes().remove("cid");
+ Integer cid2g = (Integer) position.getAttributes().remove("cid2g");
+ Long cid4g = (Long) position.getAttributes().remove("cid4g");
Integer lac = (Integer) position.getAttributes().remove("lac");
- if (cid != null && lac != null) {
- CellTower cellTower = CellTower.fromLacCid(getConfig(), lac, cid);
+ if (lac != null && (cid2g != null || cid4g != null)) {
+ Network network = new Network();
+ CellTower cellTower;
+ if (cid2g != null) {
+ cellTower = CellTower.fromLacCid(getConfig(), lac, cid2g);
+ } else {
+ cellTower = CellTower.fromLacCid(getConfig(), lac, cid4g);
+ network.setRadioType("lte");
+ }
long operator = position.getInteger(Position.KEY_OPERATOR);
if (operator >= 1000) {
cellTower.setOperator(operator);
}
+ network.addCellTower(cellTower);
position.setNetwork(new Network(cellTower));
}
}
@@ -563,6 +588,38 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
}
index += 1;
}
+ } else if (id == 548 || id == 10829 || id == 10831) {
+ ByteBuf data = buf.readSlice(length);
+ data.readUnsignedByte(); // header
+ for (int i = 1; data.isReadable(); i++) {
+ ByteBuf beacon = data.readSlice(data.readUnsignedByte());
+ while (beacon.isReadable()) {
+ int parameterId = beacon.readUnsignedByte();
+ int parameterLength = beacon.readUnsignedByte();
+ switch (parameterId) {
+ case 0:
+ position.set("tag" + i + "Rssi", (int) beacon.readByte());
+ break;
+ case 1:
+ String beaconId = ByteBufUtil.hexDump(beacon.readSlice(parameterLength));
+ position.set("tag" + i + "Id", beaconId);
+ break;
+ case 2:
+ String beaconData = ByteBufUtil.hexDump(beacon.readSlice(parameterLength));
+ position.set("tag" + i + "Data", beaconData);
+ break;
+ case 13:
+ position.set("tag" + i + "LowBattery", beacon.readUnsignedByte());
+ break;
+ case 14:
+ position.set("tag" + i + "Battery", beacon.readUnsignedShort());
+ break;
+ default:
+ beacon.skipBytes(parameterLength);
+ break;
+ }
+ }
+ }
} else {
position.set(Position.PREFIX_IO + id, ByteBufUtil.hexDump(buf.readSlice(length)));
}
@@ -571,6 +628,14 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
decodeNetwork(position, model);
+ if (model != null && model.matches("FM.6..")) {
+ Long driverMsb = (Long) position.getAttributes().get("io195");
+ Long driverLsb = (Long) position.getAttributes().get("io196");
+ if (driverMsb != null && driverLsb != null) {
+ String driver = new String(ByteBuffer.allocate(16).putLong(driverMsb).putLong(driverLsb).array());
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, driver);
+ }
+ }
}
private List<Position> parseData(
@@ -588,7 +653,6 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
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());
@@ -600,9 +664,13 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
buf.readUnsignedByte(); // type
int length = buf.readInt() - 4;
getLastLocation(position, new Date(buf.readUnsignedInt() * 1000));
- if (isPrintable(buf, length)) {
- position.set(Position.KEY_RESULT,
- buf.readCharSequence(length, StandardCharsets.US_ASCII).toString().trim());
+ if (BufferUtil.isPrintable(buf, length)) {
+ String data = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString().trim();
+ if (data.startsWith("GTSL")) {
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, data.split("\\|")[4]);
+ } else {
+ position.set(Position.KEY_RESULT, data);
+ }
} else {
position.set(Position.KEY_RESULT,
ByteBufUtil.hexDump(buf.readSlice(length)));
@@ -610,7 +678,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder {
} 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/protocol/TeraTrackProtocol.java b/src/main/java/org/traccar/protocol/TeraTrackProtocol.java
index 73219cc5e..e872ddf42 100644
--- a/src/main/java/org/traccar/protocol/TeraTrackProtocol.java
+++ b/src/main/java/org/traccar/protocol/TeraTrackProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TeraTrackProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TeraTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeraTrackProtocolDecoder.java
index 313210f63..be4b98e4c 100644
--- a/src/main/java/org/traccar/protocol/TeraTrackProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TeraTrackProtocolDecoder.java
@@ -23,8 +23,8 @@ import org.traccar.Protocol;
import org.traccar.helper.UnitsConverter;
import org.traccar.model.Position;
-import javax.json.Json;
-import javax.json.JsonObject;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
import java.io.StringReader;
import java.net.SocketAddress;
import java.text.DateFormat;
@@ -63,7 +63,7 @@ public class TeraTrackProtocolDecoder extends BaseProtocolDecoder {
position.setSpeed(UnitsConverter.knotsFromKph(Integer.parseInt(json.getString("Speed"))));
position.set(Position.KEY_ODOMETER, Integer.parseInt(json.getString("Mileage")));
- position.set(Position.KEY_BLOCKED, json.getString("LockOpen").equals("0"));
+ position.set(Position.KEY_LOCK, json.getString("LockOpen").equals("0"));
position.set(Position.KEY_DRIVER_UNIQUE_ID, json.getString("CardNo"));
position.set(Position.KEY_ALARM, json.getString("LowPower").equals("1") ? Position.ALARM_LOW_POWER : null);
position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(json.getString("Power")));
diff --git a/src/main/java/org/traccar/protocol/ThinkPowerProtocol.java b/src/main/java/org/traccar/protocol/ThinkPowerProtocol.java
index 38bf078aa..b23dadf08 100644
--- a/src/main/java/org/traccar/protocol/ThinkPowerProtocol.java
+++ b/src/main/java/org/traccar/protocol/ThinkPowerProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ThinkPowerProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ThinkRaceProtocol.java b/src/main/java/org/traccar/protocol/ThinkRaceProtocol.java
index 782b0a352..34b80ba87 100644
--- a/src/main/java/org/traccar/protocol/ThinkRaceProtocol.java
+++ b/src/main/java/org/traccar/protocol/ThinkRaceProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ThinkRaceProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ThurayaProtocol.java b/src/main/java/org/traccar/protocol/ThurayaProtocol.java
index f709a1183..33d486f6b 100644
--- a/src/main/java/org/traccar/protocol/ThurayaProtocol.java
+++ b/src/main/java/org/traccar/protocol/ThurayaProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class ThurayaProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Tk102Protocol.java b/src/main/java/org/traccar/protocol/Tk102Protocol.java
index 150e83ab3..b6a82981b 100644
--- a/src/main/java/org/traccar/protocol/Tk102Protocol.java
+++ b/src/main/java/org/traccar/protocol/Tk102Protocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Tk102Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Tk103Protocol.java b/src/main/java/org/traccar/protocol/Tk103Protocol.java
index cf09886f5..b641ef083 100644
--- a/src/main/java/org/traccar/protocol/Tk103Protocol.java
+++ b/src/main/java/org/traccar/protocol/Tk103Protocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Tk103Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java
index b343c3b33..6c926da90 100644
--- a/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,11 @@
*/
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.helper.DataConverter;
import org.traccar.session.DeviceSession;
import org.traccar.NetworkMessage;
import org.traccar.Protocol;
@@ -448,6 +451,106 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private Position decodeBms(Channel channel, SocketAddress remoteAddress, String sentence) {
+ String id = sentence.substring(1, 13);
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id);
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ getLastLocation(position, null);
+
+ String payload = sentence.substring(1 + 12 + 4, sentence.length() - 1);
+
+ if (sentence.startsWith("BS50", 1 + 12)) {
+
+ ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(payload));
+
+ buf.readUnsignedByte();
+ buf.readUnsignedByte();
+ buf.readUnsignedByte(); // header
+
+ int batteryCount = buf.readUnsignedByte();
+ for (int i = 1; i <= 24; i++) {
+ int voltage = buf.readUnsignedShortLE();
+ if (i <= batteryCount) {
+ position.set("battery" + i, voltage * 0.001);
+ }
+ }
+
+ position.set(Position.KEY_CHARGE, buf.readUnsignedByte() == 0);
+ position.set("current", buf.readUnsignedShortLE() * 0.1);
+ position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.01);
+ position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte());
+ position.set("batteryOverheat", buf.readUnsignedByte() > 0);
+ position.set("chargeProtection", buf.readUnsignedByte() > 0);
+ position.set("dischargeProtection", buf.readUnsignedByte() > 0);
+ buf.readUnsignedByte(); // drop line
+ buf.readUnsignedByte(); // balanced
+ position.set("cycles", buf.readUnsignedShortLE());
+ position.set("faultAlarm", buf.readUnsignedByte());
+
+ buf.skipBytes(6);
+
+ int temperatureCount = buf.readUnsignedByte();
+ position.set("powerTemp", buf.readUnsignedByte() - 40);
+ position.set("equilibriumTemp", buf.readUnsignedByte() - 40);
+ for (int i = 1; i <= 7; i++) {
+ int temperature = buf.readUnsignedByte() - 40;
+ if (i <= temperatureCount) {
+ position.set("batteryTemp" + i, temperature);
+ }
+ }
+
+ position.set("calibrationCapacity", buf.readUnsignedShortLE() * 0.01);
+ position.set("dischargeCapacity", buf.readUnsignedIntLE());
+
+ } else {
+
+ String[] values = payload.split(",");
+ for (String value : values) {
+ String[] pair = value.split(":");
+ int key = Integer.parseInt(pair[0], 16);
+ ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(pair[1]));
+ switch (key) {
+ case 0x90:
+ position.set("cumulativeVoltage", buf.readUnsignedShortLE() * 0.1);
+ position.set("gatherVoltage", buf.readUnsignedShortLE() * 0.1);
+ position.set("current", (buf.readUnsignedShortLE() - 30000) * 0.1);
+ position.set("soc", buf.readUnsignedShortLE() * 0.1);
+ break;
+ case 0x91:
+ position.set("maxCellVoltage", buf.readUnsignedShortLE() * 0.001);
+ position.set("maxCellVoltageCount", buf.readUnsignedByte());
+ position.set("minCellVoltage", buf.readUnsignedShortLE() * 0.001);
+ position.set("minCellVoltageCount", buf.readUnsignedByte());
+ break;
+ case 0x92:
+ position.set("maxTemp", buf.readUnsignedByte() - 40);
+ position.set("maxTempCount", buf.readUnsignedByte());
+ position.set("minTemp", buf.readUnsignedByte() - 40);
+ position.set("minTempCount", buf.readUnsignedByte());
+ break;
+ case 0x96:
+ buf.readUnsignedByte(); // frame
+ while (buf.isReadable()) {
+ position.set("cellTemp" + buf.readerIndex(), buf.readUnsignedByte() - 40);
+ }
+ break;
+ default:
+ break;
+ }
+
+ }
+
+ }
+
+ return position;
+ }
+
@Override
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
@@ -477,6 +580,8 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
return decodeLbsWifi(channel, remoteAddress, sentence);
} else if (sentence.contains("BV00")) {
return decodeVin(channel, remoteAddress, sentence);
+ } else if (sentence.contains("BS50") || sentence.contains("BS51")) {
+ return decodeBms(channel, remoteAddress, sentence);
}
Parser parser = new Parser(PATTERN, sentence);
diff --git a/src/main/java/org/traccar/protocol/Tlt2hProtocol.java b/src/main/java/org/traccar/protocol/Tlt2hProtocol.java
index b10271f7d..6763e9b6b 100644
--- a/src/main/java/org/traccar/protocol/Tlt2hProtocol.java
+++ b/src/main/java/org/traccar/protocol/Tlt2hProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Tlt2hProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java
index e85bdf9b3..6be3d2dc3 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.
@@ -55,16 +55,20 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder {
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
+ .text("#")
+ .groupBegin()
+ .number("(d+),") // mcc
.number("(d+),") // mnc
.number("(x+),") // lac
.number("(x+)") // cell id
.groupEnd("?")
+ .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,14 +76,16 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder {
.number("(d+.?d*)?,") // speed
.number("(d+.?d*)?,") // course
.number("(dd)(dd)(dd)") // date (ddmmyy)
+ .groupEnd("?")
.any()
.compile();
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
+ .number("(d+),") // mcc
.number("(d+),") // mnc
.number("(x+),") // lac
.number("(x+)") // cell id
@@ -178,7 +184,8 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder {
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)) {
@@ -188,17 +195,26 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder {
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;
@@ -209,7 +225,10 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder {
parser = new Parser(PATTERN_WIFI, message);
if (parser.matches()) {
- position.set(Position.KEY_BATTERY, parser.nextInt() * 0.1);
+ if (parser.hasNext()) {
+ int voltage = parser.nextInt();
+ position.set(Position.KEY_BATTERY, voltage > 100 ? voltage * 0.001 : voltage * 0.1);
+ }
Network network = new Network();
if (parser.hasNext(4)) {
@@ -230,6 +249,8 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder {
dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt());
getLastLocation(position, dateBuilder.getDate());
+ } else {
+ continue;
}
} else {
diff --git a/src/main/java/org/traccar/protocol/TlvProtocol.java b/src/main/java/org/traccar/protocol/TlvProtocol.java
index 9d83388c9..f99676d23 100644
--- a/src/main/java/org/traccar/protocol/TlvProtocol.java
+++ b/src/main/java/org/traccar/protocol/TlvProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TlvProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TmgProtocol.java b/src/main/java/org/traccar/protocol/TmgProtocol.java
index e078c425b..dbba648be 100644
--- a/src/main/java/org/traccar/protocol/TmgProtocol.java
+++ b/src/main/java/org/traccar/protocol/TmgProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TmgProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TopflytechProtocol.java b/src/main/java/org/traccar/protocol/TopflytechProtocol.java
index 339d2fc8d..a658235ab 100644
--- a/src/main/java/org/traccar/protocol/TopflytechProtocol.java
+++ b/src/main/java/org/traccar/protocol/TopflytechProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TopflytechProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TopinProtocol.java b/src/main/java/org/traccar/protocol/TopinProtocol.java
index b15373d71..1a558f617 100644
--- a/src/main/java/org/traccar/protocol/TopinProtocol.java
+++ b/src/main/java/org/traccar/protocol/TopinProtocol.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,16 +19,19 @@ import org.traccar.BaseProtocol;
import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
+import org.traccar.config.Keys;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TopinProtocol extends BaseProtocol {
@Inject
public TopinProtocol(Config config) {
- setSupportedDataCommands(
- Command.TYPE_SOS_NUMBER);
+ if (!config.getBoolean(Keys.PROTOCOL_DISABLE_COMMANDS.withPrefix(getName()))) {
+ setSupportedDataCommands(
+ Command.TYPE_SOS_NUMBER);
+ }
addServer(new TrackerServer(config, getName(), false) {
@Override
protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
diff --git a/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java
index a1d5481db..c7b816818 100644
--- a/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java
@@ -48,7 +48,12 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder {
public static final int MSG_GPS = 0x10;
public static final int MSG_GPS_OFFLINE = 0x11;
public static final int MSG_STATUS = 0x13;
+ public static final int MSG_SLEEP = 0x14;
+ public static final int MSG_FACTORY_RESET = 0x15;
public static final int MSG_WIFI_OFFLINE = 0x17;
+ public static final int MSG_LBS_WIFI = 0x18;
+ public static final int MSG_LBS_WIFI_OFFLINE = 0x19;
+ public static final int MSG_LBS_WIFI_2 = 0x1A;
public static final int MSG_TIME_UPDATE = 0x30;
public static final int MSG_SOS_NUMBER = 0x41;
public static final int MSG_WIFI = 0x69;
@@ -216,7 +221,8 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder {
return position;
- } else if (type == MSG_WIFI || type == MSG_WIFI_OFFLINE) {
+ } else if (type == MSG_WIFI || type == MSG_WIFI_OFFLINE
+ || type == MSG_LBS_WIFI || type == MSG_LBS_WIFI_2 || type == MSG_LBS_WIFI_OFFLINE) {
Position position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
diff --git a/src/main/java/org/traccar/protocol/TotemProtocol.java b/src/main/java/org/traccar/protocol/TotemProtocol.java
index 9ab36fd0b..b02d4f1fc 100644
--- a/src/main/java/org/traccar/protocol/TotemProtocol.java
+++ b/src/main/java/org/traccar/protocol/TotemProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TotemProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java b/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java
index 9d0d794f8..6f039c324 100644
--- a/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2013 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -135,7 +135,7 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
private static final Pattern PATTERN4 = new PatternBuilder()
.text("$$") // header
.number("dddd") // length
- .number("(xx)") // type
+ .number("xx") // type
.number("(d+)|") // imei
.number("(x{8})") // status
.number("(dd)(dd)(dd)") // date (yymmdd)
@@ -176,7 +176,21 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
.any()
.compile();
- private static final Pattern PATTERN_OBD = new PatternBuilder()
+ private static final Pattern PATTERN_E2 = new PatternBuilder()
+ .text("$$") // header
+ .number("dddd") // length
+ .number("xx") // type
+ .number("(d+)|") // imei
+ .number("(dd)(dd)(dd)") // date (yymmdd)
+ .number("(dd)(dd)(dd),") // time (hhmmss)
+ .number("(-?d+.d+),") // longitude
+ .number("(-?d+.d+),") // latitude
+ .expression("(.+)") // rfid
+ .number("|xx") // checksum
+ .any()
+ .compile();
+
+ private static final Pattern PATTERN_E5 = new PatternBuilder()
.text("$$") // header
.number("dddd") // length
.number("xx") // type
@@ -257,7 +271,20 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
}
}
- private boolean decode12(Position position, Parser parser, Pattern pattern) {
+ private Position decode12(Channel channel, SocketAddress remoteAddress, String sentence, Pattern pattern) {
+
+ Parser parser = new Parser(pattern, sentence);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
if (parser.hasNext()) {
position.set(Position.KEY_ALARM, decodeAlarm123(Short.parseShort(parser.next(), 16)));
@@ -283,7 +310,7 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
year = parser.nextInt(0);
}
if (year == 0) {
- return false; // ignore invalid data
+ return null; // ignore invalid data
}
dateBuilder.setDate(year, month, day);
position.setTime(dateBuilder.getDate());
@@ -331,10 +358,23 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.PREFIX_TEMP + 1, parser.next());
position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000);
- return true;
+ return position;
}
- private boolean decode3(Position position, Parser parser) {
+ private Position decode3(Channel channel, SocketAddress remoteAddress, String sentence) {
+
+ Parser parser = new Parser(PATTERN3, sentence);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
if (parser.hasNext()) {
position.set(Position.KEY_ALARM, decodeAlarm123(Short.parseShort(parser.next(), 16)));
@@ -363,10 +403,36 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
position.setLatitude(parser.nextCoordinate());
position.setLongitude(parser.nextCoordinate());
- return true;
+ return position;
}
- private boolean decode4(Position position, Parser parser) {
+ private Position decode4(Channel channel, SocketAddress remoteAddress, String sentence) {
+
+ int type = Integer.parseInt(sentence.substring(6, 8), 16);
+
+ switch (type) {
+ case 0xE2:
+ return decodeE2(channel, remoteAddress, sentence);
+ case 0xE5:
+ return decodeE5(channel, remoteAddress, sentence);
+ default:
+ break;
+ }
+
+ Parser parser = new Parser(PATTERN4, sentence);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.set(Position.KEY_ALARM, decodeAlarm4(type));
long status = parser.nextHexLong();
@@ -427,10 +493,48 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
position.setLatitude(parser.nextCoordinate());
position.setLongitude(parser.nextCoordinate());
- return true;
+ return position;
}
- private boolean decodeObd(Position position, Parser parser) {
+ private Position decodeE2(Channel channel, SocketAddress remoteAddress, String sentence) {
+
+ Parser parser = new Parser(PATTERN_E2, sentence);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.setValid(true);
+ position.setTime(parser.nextDateTime());
+ position.setLongitude(parser.nextDouble());
+ position.setLatitude(parser.nextDouble());
+
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next());
+
+ return position;
+ }
+
+ private Position decodeE5(Channel channel, SocketAddress remoteAddress, String sentence) {
+
+ Parser parser = new Parser(PATTERN_E5, sentence);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
position.setValid(true);
position.setTime(parser.nextDateTime());
@@ -451,7 +555,7 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_THROTTLE, parser.nextInt());
position.set(Position.KEY_FUEL_LEVEL, parser.nextInt());
- return true;
+ return position;
}
@Override
@@ -459,50 +563,23 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
String sentence = (String) msg;
- Pattern pattern = PATTERN3;
- if (sentence.contains("$Cloud")) {
- pattern = PATTERN_OBD;
- } else if (sentence.charAt(2) == '0') {
- pattern = PATTERN4;
+
+ Position position;
+ if (sentence.charAt(2) == '0') {
+ position = decode4(channel, remoteAddress, sentence);
} else if (sentence.contains("$GPRMC")) {
- pattern = PATTERN1;
+ position = decode12(channel, remoteAddress, sentence, PATTERN1);
} else {
int index = sentence.indexOf('|');
if (index != -1 && sentence.indexOf('|', index + 1) != -1) {
- pattern = PATTERN2;
+ position = decode12(channel, remoteAddress, sentence, PATTERN2);
+ } else {
+ position = decode3(channel, remoteAddress, sentence);
}
}
- Parser parser = new Parser(pattern, sentence);
- if (!parser.matches()) {
- return null;
- }
-
- Position position = new Position(getProtocolName());
-
- if (pattern == PATTERN4) {
- position.set(Position.KEY_ALARM, decodeAlarm4(parser.nextHexInt()));
- }
-
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
- if (deviceSession == null) {
- return null;
- }
- position.setDeviceId(deviceSession.getDeviceId());
-
- boolean result;
- if (pattern == PATTERN1 || pattern == PATTERN2) {
- result = decode12(position, parser, pattern);
- } else if (pattern == PATTERN3) {
- result = decode3(position, parser);
- } else if (pattern == PATTERN4) {
- result = decode4(position, parser);
- } else {
- result = decodeObd(position, parser);
- }
-
if (channel != null) {
- if (pattern == PATTERN4) {
+ if (sentence.charAt(2) == '0') {
String response = "$$0014AA" + sentence.substring(sentence.length() - 6, sentence.length() - 2);
response += String.format("%02X", Checksum.xor(response)).toUpperCase();
channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
@@ -511,7 +588,7 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder {
}
}
- return result ? position : null;
+ return position;
}
}
diff --git a/src/main/java/org/traccar/protocol/Tr20Protocol.java b/src/main/java/org/traccar/protocol/Tr20Protocol.java
index 615fdab28..3b3fc02b6 100644
--- a/src/main/java/org/traccar/protocol/Tr20Protocol.java
+++ b/src/main/java/org/traccar/protocol/Tr20Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Tr20Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Tr900Protocol.java b/src/main/java/org/traccar/protocol/Tr900Protocol.java
index 162cbe651..c5f357604 100644
--- a/src/main/java/org/traccar/protocol/Tr900Protocol.java
+++ b/src/main/java/org/traccar/protocol/Tr900Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Tr900Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TrackboxProtocol.java b/src/main/java/org/traccar/protocol/TrackboxProtocol.java
index 4236144a3..eadcd07f9 100644
--- a/src/main/java/org/traccar/protocol/TrackboxProtocol.java
+++ b/src/main/java/org/traccar/protocol/TrackboxProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TrackboxProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TrakMateProtocol.java b/src/main/java/org/traccar/protocol/TrakMateProtocol.java
index b7637e6f3..f4e7c5e60 100644
--- a/src/main/java/org/traccar/protocol/TrakMateProtocol.java
+++ b/src/main/java/org/traccar/protocol/TrakMateProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TrakMateProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TramigoFrameDecoder.java b/src/main/java/org/traccar/protocol/TramigoFrameDecoder.java
index e4c94dc77..72cadf21a 100644
--- a/src/main/java/org/traccar/protocol/TramigoFrameDecoder.java
+++ b/src/main/java/org/traccar/protocol/TramigoFrameDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,9 +29,13 @@ public class TramigoFrameDecoder extends BaseFrameDecoder {
return null;
}
+ int protocol = buf.getUnsignedByte(buf.readerIndex());
+
int length;
- if (buf.getUnsignedByte(buf.readerIndex()) == 0x80) {
+ if (protocol == 0x80) {
length = buf.getUnsignedShortLE(buf.readerIndex() + 6);
+ } else if (protocol == 0x02 || protocol == 0x04) {
+ length = buf.getUnsignedShortLE(buf.readerIndex() + 1);
} else {
length = buf.getUnsignedShort(buf.readerIndex() + 6);
}
diff --git a/src/main/java/org/traccar/protocol/TramigoProtocol.java b/src/main/java/org/traccar/protocol/TramigoProtocol.java
index 79a59abd3..5d8baf2a9 100644
--- a/src/main/java/org/traccar/protocol/TramigoProtocol.java
+++ b/src/main/java/org/traccar/protocol/TramigoProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TramigoProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TramigoProtocolDecoder.java b/src/main/java/org/traccar/protocol/TramigoProtocolDecoder.java
index 21dd78da3..4a9a9a58f 100644
--- a/src/main/java/org/traccar/protocol/TramigoProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TramigoProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2014 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.Checksum;
import org.traccar.session.DeviceSession;
import org.traccar.NetworkMessage;
import org.traccar.Protocol;
@@ -29,6 +31,7 @@ import org.traccar.model.Position;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
+import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
@@ -42,9 +45,6 @@ public class TramigoProtocolDecoder extends BaseProtocolDecoder {
super(protocol);
}
- public static final int MSG_COMPACT = 0x0100;
- public static final int MSG_FULL = 0x00FE;
-
private static final String[] DIRECTIONS = new String[] {"N", "NE", "E", "SE", "S", "SW", "W", "NW"};
@Override
@@ -54,34 +54,47 @@ public class TramigoProtocolDecoder extends BaseProtocolDecoder {
ByteBuf buf = (ByteBuf) msg;
int protocol = buf.readUnsignedByte();
- boolean legacy = protocol == 0x80;
+
+ if (protocol == 0x01) {
+ return decode01(channel, remoteAddress, buf);
+ } else if (protocol == 0x04) {
+ return decode04(channel, remoteAddress, buf);
+ } else if (protocol == 0x80) {
+ return decode80(channel, remoteAddress, buf);
+ }
+
+ return null;
+ }
+
+ private Position decode01(Channel channel, SocketAddress remoteAddress, ByteBuf buf) {
buf.readUnsignedByte(); // version id
- int index = legacy ? buf.readUnsignedShort() : buf.readUnsignedShortLE();
- int type = legacy ? buf.readUnsignedShort() : buf.readUnsignedShortLE();
- buf.readUnsignedShort(); // length
- buf.readUnsignedShort(); // mask
- buf.readUnsignedShort(); // checksum
- long id = legacy ? buf.readUnsignedInt() : buf.readUnsignedIntLE();
- buf.readUnsignedInt(); // time
+ int index = buf.readUnsignedShortLE();
+ int type = buf.readUnsignedShortLE();
- Position position = new Position(getProtocolName());
- position.set(Position.KEY_INDEX, index);
- position.setValid(true);
+ if (type == 0x0100 || type == 0x00FE) {
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(id));
- if (deviceSession == null) {
- return null;
- }
- position.setDeviceId(deviceSession.getDeviceId());
+ buf.readUnsignedShort(); // length
+ buf.readUnsignedShort(); // mask
+ buf.readUnsignedShort(); // checksum
+ long id = buf.readUnsignedIntLE();
+ buf.readUnsignedInt(); // time
- if (protocol == 0x01 && (type == MSG_COMPACT || type == MSG_FULL)) {
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(id));
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+ position.set(Position.KEY_INDEX, index);
// need to send ack?
buf.readUnsignedShortLE(); // report trigger
buf.readUnsignedShortLE(); // state flag
+ position.setValid(true);
position.setLatitude(buf.readUnsignedIntLE() * 0.0000001);
position.setLongitude(buf.readUnsignedIntLE() * 0.0000001);
@@ -105,56 +118,183 @@ public class TramigoProtocolDecoder extends BaseProtocolDecoder {
return position;
- } else if (legacy) {
+ }
- if (channel != null) {
- channel.writeAndFlush(new NetworkMessage(
- Unpooled.copiedBuffer("gprs,ack," + index, StandardCharsets.US_ASCII), remoteAddress));
- }
+ return null;
- String sentence = buf.toString(StandardCharsets.US_ASCII);
+ }
- Pattern pattern = Pattern.compile("(-?\\d+\\.\\d+), (-?\\d+\\.\\d+)");
- Matcher matcher = pattern.matcher(sentence);
- if (!matcher.find()) {
- return null;
- }
- position.setLatitude(Double.parseDouble(matcher.group(1)));
- position.setLongitude(Double.parseDouble(matcher.group(2)));
-
- pattern = Pattern.compile("([NSWE]{1,2}) with speed (\\d+) km/h");
- matcher = pattern.matcher(sentence);
- if (matcher.find()) {
- for (int i = 0; i < DIRECTIONS.length; i++) {
- if (matcher.group(1).equals(DIRECTIONS[i])) {
- position.setCourse(i * 45.0);
- break;
- }
- }
- position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(matcher.group(2))));
- }
+ private Position decode04(Channel channel, SocketAddress remoteAddress, ByteBuf buf) {
- pattern = Pattern.compile("(\\d{1,2}:\\d{2}(:\\d{2})? \\w{3} \\d{1,2})");
- matcher = pattern.matcher(sentence);
- if (!matcher.find()) {
- return null;
- }
- DateFormat dateFormat = new SimpleDateFormat(
- matcher.group(2) != null ? "HH:mm:ss MMM d yyyy" : "HH:mm MMM d yyyy", Locale.ENGLISH);
- position.setTime(DateUtil.correctYear(
- dateFormat.parse(matcher.group(1) + " " + Calendar.getInstance().get(Calendar.YEAR))));
-
- if (sentence.contains("Ignition on detected")) {
- position.set(Position.KEY_IGNITION, true);
- } else if (sentence.contains("Ignition off detected")) {
- position.set(Position.KEY_IGNITION, false);
+ buf.readUnsignedShortLE(); // length
+ buf.readUnsignedShortLE(); // checksum
+ int index = buf.readUnsignedShortLE();
+ long id1 = buf.readUnsignedIntLE();
+ long id2 = buf.readUnsignedIntLE();
+ long time = buf.readUnsignedIntLE();
+
+ String id = String.format("%08d%07d", id1, id2);
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id);
+ if (deviceSession == null) {
+ return null;
+ }
+
+ if (channel != null) {
+ ByteBuf response = Unpooled.buffer();
+ response.writeByte(0x04); // protocol
+ response.writeShortLE(24); // length
+ response.writeShortLE(0); // checksum
+ response.writeShortLE(index);
+ response.writeIntLE((int) id1);
+ response.writeIntLE((int) id2);
+ response.writeIntLE((int) time);
+
+ response.writeByte(0xff); // acknowledgement
+ response.writeShortLE(index);
+ response.writeShortLE(0); // success
+
+ response.setShortLE(3, Checksum.crc16(Checksum.CRC16_CCITT_FALSE, response.nioBuffer()));
+
+ channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress()));
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+ position.set(Position.KEY_INDEX, index);
+
+ position.setDeviceTime(new Date(time * 1000));
+
+ while (buf.isReadable()) {
+ int type = buf.readUnsignedByte();
+ switch (type) {
+ case 0:
+ position.set(Position.KEY_EVENT, buf.readUnsignedShortLE());
+ buf.readUnsignedIntLE(); // event data
+
+ int status = buf.readUnsignedShortLE();
+ position.set(Position.KEY_IGNITION, BitUtil.check(status, 5));
+ position.set(Position.KEY_STATUS, status);
+
+ position.setValid(true);
+ position.setLatitude(buf.readIntLE() * 0.00001);
+ position.setLongitude(buf.readIntLE() * 0.00001);
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE()));
+ position.setCourse(buf.readUnsignedShortLE());
+
+ position.set(Position.KEY_RSSI, buf.readUnsignedByte());
+ position.set(Position.KEY_GPS, buf.readUnsignedByte());
+ position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte());
+ position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedShortLE());
+ position.set("maxAcceleration", buf.readUnsignedShortLE() * 0.001);
+ position.set("maxDeceleration", buf.readUnsignedShortLE() * 0.001);
+ buf.readUnsignedShortLE(); // bearing to landmark
+ buf.readUnsignedIntLE(); // distance to landmark
+
+ position.setFixTime(new Date(buf.readUnsignedIntLE() * 1000));
+
+ buf.readUnsignedByte(); // reserved
+ break;
+ case 1:
+ buf.skipBytes(buf.readUnsignedShortLE() - 3); // landmark
+ break;
+ case 4:
+ buf.skipBytes(53); // trip
+ break;
+ case 20:
+ buf.skipBytes(32); // extended
+ break;
+ case 22:
+ buf.readUnsignedByte(); // zone flag
+ buf.skipBytes(buf.readUnsignedShortLE()); // zone name
+ break;
+ case 30:
+ buf.skipBytes(79); // system status
+ break;
+ case 40:
+ buf.skipBytes(40); // analog
+ break;
+ case 50:
+ buf.skipBytes(buf.readUnsignedShortLE() - 3); // console
+ break;
+ case 255:
+ buf.skipBytes(4); // acknowledgement
+ break;
+ default:
+ throw new IllegalArgumentException(String.format("Unknown type %d", type));
}
+ }
- return position;
+ return position.getValid() ? position : null;
+
+ }
+ private Position decode80(Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws ParseException {
+
+ buf.readUnsignedByte(); // version id
+ int index = buf.readUnsignedShort();
+ buf.readUnsignedShort(); // type
+
+ buf.readUnsignedShort(); // length
+ buf.readUnsignedShort(); // mask
+ buf.readUnsignedShort(); // checksum
+ long id = buf.readUnsignedInt();
+ buf.readUnsignedInt(); // time
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(id));
+ if (deviceSession == null) {
+ return null;
}
- return null;
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+ position.set(Position.KEY_INDEX, index);
+
+ if (channel != null) {
+ channel.writeAndFlush(new NetworkMessage(
+ Unpooled.copiedBuffer("gprs,ack," + index, StandardCharsets.US_ASCII), remoteAddress));
+ }
+
+ String sentence = buf.toString(StandardCharsets.US_ASCII);
+
+ Pattern pattern = Pattern.compile("(-?\\d+\\.\\d+), (-?\\d+\\.\\d+)");
+ Matcher matcher = pattern.matcher(sentence);
+ if (!matcher.find()) {
+ return null;
+ }
+ position.setLatitude(Double.parseDouble(matcher.group(1)));
+ position.setLongitude(Double.parseDouble(matcher.group(2)));
+ position.setValid(true);
+
+ pattern = Pattern.compile("([NSWE]{1,2}) with speed (\\d+) km/h");
+ matcher = pattern.matcher(sentence);
+ if (matcher.find()) {
+ for (int i = 0; i < DIRECTIONS.length; i++) {
+ if (matcher.group(1).equals(DIRECTIONS[i])) {
+ position.setCourse(i * 45.0);
+ break;
+ }
+ }
+ position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(matcher.group(2))));
+ }
+
+ pattern = Pattern.compile("(\\d{1,2}:\\d{2}(:\\d{2})? \\w{3} \\d{1,2})");
+ matcher = pattern.matcher(sentence);
+ if (!matcher.find()) {
+ return null;
+ }
+ DateFormat dateFormat = new SimpleDateFormat(
+ matcher.group(2) != null ? "HH:mm:ss MMM d yyyy" : "HH:mm MMM d yyyy", Locale.ENGLISH);
+ position.setTime(DateUtil.correctYear(
+ dateFormat.parse(matcher.group(1) + " " + Calendar.getInstance().get(Calendar.YEAR))));
+
+ if (sentence.contains("Ignition on detected")) {
+ position.set(Position.KEY_IGNITION, true);
+ } else if (sentence.contains("Ignition off detected")) {
+ position.set(Position.KEY_IGNITION, false);
+ }
+
+ return position;
+
}
}
diff --git a/src/main/java/org/traccar/protocol/TranSyncProtocol.java b/src/main/java/org/traccar/protocol/TranSyncProtocol.java
new file mode 100644
index 000000000..fb37a1ab4
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/TranSyncProtocol.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+import org.traccar.config.Config;
+
+import jakarta.inject.Inject;
+
+public class TranSyncProtocol extends BaseProtocol {
+
+ @Inject
+ public TranSyncProtocol(Config config) {
+ addServer(new TrackerServer(config, getName(), false) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new LengthFieldBasedFrameDecoder(256, 2, 1, 2, 0));
+ pipeline.addLast(new TranSyncProtocolDecoder(TranSyncProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/TranSyncProtocolDecoder.java b/src/main/java/org/traccar/protocol/TranSyncProtocolDecoder.java
new file mode 100644
index 000000000..816b5d2cf
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/TranSyncProtocolDecoder.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.channel.Channel;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.Protocol;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.DateBuilder;
+import org.traccar.helper.UnitsConverter;
+import org.traccar.model.CellTower;
+import org.traccar.model.Network;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.net.SocketAddress;
+
+public class TranSyncProtocolDecoder extends BaseProtocolDecoder {
+
+ public TranSyncProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ private String decodeAlarm(int value) {
+ switch (value) {
+ case 4:
+ return Position.ALARM_LOW_BATTERY;
+ case 6:
+ return Position.ALARM_POWER_RESTORED;
+ case 10:
+ return Position.ALARM_SOS;
+ case 13:
+ return Position.ALARM_BRAKING;
+ case 14:
+ return Position.ALARM_ACCELERATION;
+ case 17:
+ return Position.ALARM_OVERSPEED;
+ case 23:
+ return Position.ALARM_ACCIDENT;
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ ByteBuf buf = (ByteBuf) msg;
+
+ buf.readUnsignedShort(); // header
+ buf.readByte(); // length
+
+ int lac = buf.readUnsignedShort();
+
+ String deviceId = ByteBufUtil.hexDump(buf.readSlice(8));
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, deviceId);
+ if (deviceSession == null) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ buf.readUnsignedShort(); // index
+ buf.readUnsignedByte(); // type
+
+ position.setTime(new DateBuilder()
+ .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte())
+ .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte())
+ .getDate());
+
+ double latitude = buf.readUnsignedInt() / 1800000.0;
+ double longitude = buf.readUnsignedInt() / 1800000.0;
+
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte()));
+ position.setCourse(buf.readUnsignedShort());
+
+ int mnc = buf.readUnsignedByte();
+ int cid = buf.readUnsignedShort();
+ int status0 = buf.readUnsignedByte();
+
+ position.setValid(BitUtil.check(status0, 0));
+ position.setLatitude(BitUtil.check(status0, 1) ? latitude : -latitude);
+ position.setLongitude(BitUtil.check(status0, 2) ? longitude : -longitude);
+
+ position.set(Position.PREFIX_OUT + 1, BitUtil.check(status0, 7));
+ position.set(Position.PREFIX_OUT + 2, BitUtil.check(status0, 6));
+ position.set(Position.PREFIX_IN + 3, BitUtil.check(status0, 5));
+ if (BitUtil.check(status0, 4)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_POWER_OFF);
+ }
+ position.set(Position.KEY_IGNITION, BitUtil.check(status0, 3));
+
+ buf.readUnsignedByte(); // reserved
+
+ int event = buf.readUnsignedByte();
+ position.set(Position.KEY_ALARM, decodeAlarm(event));
+ position.set(Position.KEY_EVENT, event);
+
+ int status3 = buf.readUnsignedByte();
+ if (BitUtil.check(status3, 7)) {
+ position.set(Position.KEY_ARCHIVE, true);
+ }
+ if (BitUtil.check(status3, 5)) {
+ position.set(Position.KEY_ALARM, Position.ALARM_GPS_ANTENNA_CUT);
+ }
+
+ int rssi = buf.readUnsignedByte();
+ CellTower cellTower = CellTower.fromLacCid(getConfig(), lac, cid);
+ cellTower.setMobileNetworkCode(mnc);
+ cellTower.setSignalStrength(rssi);
+ position.setNetwork(new Network(cellTower));
+
+ position.set(Position.KEY_BATTERY, (double) (buf.readUnsignedByte() / 10));
+ position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
+ position.set(Position.KEY_HDOP, buf.readUnsignedByte());
+ position.set(Position.PREFIX_ADC + 1, (short) buf.readUnsignedShort());
+
+ if (buf.readableBytes() > 5) {
+ buf.readUnsignedByte(); // odometer id
+ int length = buf.readUnsignedByte();
+ if (length > 0) {
+ position.set(Position.KEY_ODOMETER, buf.readBytes(length).readInt());
+ }
+ }
+ if (buf.readableBytes() > 5) {
+ buf.readUnsignedByte(); // rfid id
+ int length = buf.readUnsignedByte();
+ if (length > 0) {
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, ByteBufUtil.hexDump(buf.readSlice(length)));
+ }
+ }
+ if (buf.readableBytes() > 5) {
+ buf.readUnsignedByte(); // adc2 id
+ int length = buf.readUnsignedByte();
+ if (length > 0) {
+ position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShort());
+ }
+ }
+ if (buf.readableBytes() > 5) {
+ buf.readUnsignedByte(); // adc3 id
+ int length = buf.readUnsignedByte();
+ if (length > 0 && length <= buf.readableBytes() - 2) {
+ position.set(Position.PREFIX_ADC + 3, buf.readUnsignedShort());
+ }
+ }
+
+ return position;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/TrvProtocol.java b/src/main/java/org/traccar/protocol/TrvProtocol.java
index e67afbda2..7bdf3d2d0 100644
--- a/src/main/java/org/traccar/protocol/TrvProtocol.java
+++ b/src/main/java/org/traccar/protocol/TrvProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TrvProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java b/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java
index 9df29ae1b..02744f8ab 100644
--- a/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java
@@ -64,10 +64,20 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
.number("(d+),") // mnc
.number("(d+),") // lac
.number("(d+)") // cell
+ .groupBegin()
+ .text(",")
+ .expression("(")
+ .groupBegin()
+ .expression("[^\\|]+") // name
+ .number("|xx-xx-xx-xx-xx-xx") // mac
+ .number("|d+&?") // signal
+ .groupEnd("+")
+ .expression(")")
+ .groupEnd("?")
.any()
.compile();
- private static final Pattern PATTERN_HEATRBEAT = new PatternBuilder()
+ private static final Pattern PATTERN_HEARTBEAT = new PatternBuilder()
.expression("[A-Z]{2,3}")
.text("CP01,")
.number("(ddd)") // gsm
@@ -130,6 +140,16 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
}
}
+ private void decodeWifi(Network network, String data) {
+ for (String wifi : data.split("&")) {
+ if (!wifi.isEmpty()) {
+ String[] values = wifi.split("\\|");
+ network.addWifiAccessPoint(WifiAccessPoint.from(
+ values[1].replace('-', ':'), Integer.parseInt(values[2])));
+ }
+ }
+ }
+
@Override
protected Object decode(
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
@@ -163,7 +183,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
if (type.equals("CP01")) {
- Parser parser = new Parser(PATTERN_HEATRBEAT, sentence);
+ Parser parser = new Parser(PATTERN_HEARTBEAT, sentence);
if (!parser.matches()) {
return null;
}
@@ -208,8 +228,16 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
decodeCommon(position, parser);
- position.setNetwork(new Network(CellTower.from(
- parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextInt())));
+ Network network = new Network();
+
+ network.addCellTower(CellTower.from(
+ parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextInt()));
+
+ if (parser.hasNext()) {
+ decodeWifi(network, parser.next());
+ }
+
+ position.setNetwork(network);
return position;
@@ -241,12 +269,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder {
}
}
- for (String wifi : parser.next().split("&")) {
- if (!wifi.isEmpty()) {
- String[] values = wifi.split("\\|");
- network.addWifiAccessPoint(WifiAccessPoint.from(values[1], Integer.parseInt(values[2])));
- }
- }
+ decodeWifi(network, parser.next());
position.setNetwork(network);
diff --git a/src/main/java/org/traccar/protocol/Tt8850Protocol.java b/src/main/java/org/traccar/protocol/Tt8850Protocol.java
index ab109e274..8e2800d90 100644
--- a/src/main/java/org/traccar/protocol/Tt8850Protocol.java
+++ b/src/main/java/org/traccar/protocol/Tt8850Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Tt8850Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TytanProtocol.java b/src/main/java/org/traccar/protocol/TytanProtocol.java
index cc3bc9b52..4fd3c807f 100644
--- a/src/main/java/org/traccar/protocol/TytanProtocol.java
+++ b/src/main/java/org/traccar/protocol/TytanProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TytanProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TzoneProtocol.java b/src/main/java/org/traccar/protocol/TzoneProtocol.java
index d25757b63..2df721049 100644
--- a/src/main/java/org/traccar/protocol/TzoneProtocol.java
+++ b/src/main/java/org/traccar/protocol/TzoneProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class TzoneProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java b/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java
index 8e84a6781..f0b1e709d 100644
--- a/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java
@@ -204,30 +204,39 @@ public class TzoneProtocolDecoder extends BaseProtocolDecoder {
}
- private void decodeTags(Position position, ByteBuf buf) {
+ private void decodeTags(Position position, ByteBuf buf, int hardware) {
int blockLength = buf.readUnsignedShort();
int blockEnd = buf.readerIndex() + blockLength;
if (blockLength > 0) {
- buf.readUnsignedByte(); // tag type
+ int type = buf.readUnsignedByte();
- int count = buf.readUnsignedByte();
- int tagLength = buf.readUnsignedByte();
+ if (hardware != 0x153 || type >= 2) {
- for (int i = 1; i <= count; i++) {
- int tagEnd = buf.readerIndex() + tagLength;
+ int count = buf.readUnsignedByte();
+ int tagLength = buf.readUnsignedByte();
+
+ for (int i = 1; i <= count; i++) {
+ int tagEnd = buf.readerIndex() + tagLength;
+
+ buf.readUnsignedByte(); // status
+ buf.readUnsignedShortLE(); // battery voltage
- buf.readUnsignedByte(); // status
- buf.readUnsignedShortLE(); // battery voltage
+ position.set(Position.PREFIX_TEMP + i, (buf.readShortLE() & 0x3fff) * 0.1);
+
+ buf.readUnsignedByte(); // humidity
+ buf.readUnsignedByte(); // rssi
+
+ buf.readerIndex(tagEnd);
+ }
- position.set(Position.PREFIX_TEMP + i, (buf.readShortLE() & 0x3fff) * 0.1);
+ } else if (type == 1) {
- buf.readUnsignedByte(); // humidity
- buf.readUnsignedByte(); // rssi
+ position.set(Position.KEY_CARD, buf.readCharSequence(
+ blockEnd - buf.readerIndex(), StandardCharsets.UTF_8).toString());
- buf.readerIndex(tagEnd);
}
}
@@ -364,9 +373,9 @@ public class TzoneProtocolDecoder extends BaseProtocolDecoder {
}
- if (hardware == 0x406) {
+ if (hardware == 0x153 || hardware == 0x406) {
- decodeTags(position, buf);
+ decodeTags(position, buf, hardware);
}
diff --git a/src/main/java/org/traccar/protocol/UlbotechProtocol.java b/src/main/java/org/traccar/protocol/UlbotechProtocol.java
index 57fc47644..f8c4f1960 100644
--- a/src/main/java/org/traccar/protocol/UlbotechProtocol.java
+++ b/src/main/java/org/traccar/protocol/UlbotechProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class UlbotechProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/UproProtocol.java b/src/main/java/org/traccar/protocol/UproProtocol.java
index e27088594..cbec9777d 100644
--- a/src/main/java/org/traccar/protocol/UproProtocol.java
+++ b/src/main/java/org/traccar/protocol/UproProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class UproProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java
index ed714e464..8d2e5de0a 100644
--- a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java
@@ -310,6 +310,10 @@ public class UproProtocolDecoder extends BaseProtocolDecoder {
position.set("serial", data.toString(StandardCharsets.US_ASCII).substring(3));
}
break;
+ case 'd':
+ position.set(Position.PREFIX_ADC + 1,
+ Integer.parseInt(data.toString(StandardCharsets.US_ASCII)) / 100.0);
+ break;
default:
break;
}
diff --git a/src/main/java/org/traccar/protocol/UuxProtocol.java b/src/main/java/org/traccar/protocol/UuxProtocol.java
index 3de4a4732..63727cb94 100644
--- a/src/main/java/org/traccar/protocol/UuxProtocol.java
+++ b/src/main/java/org/traccar/protocol/UuxProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class UuxProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/V680Protocol.java b/src/main/java/org/traccar/protocol/V680Protocol.java
index 53bca849c..587a0c8f7 100644
--- a/src/main/java/org/traccar/protocol/V680Protocol.java
+++ b/src/main/java/org/traccar/protocol/V680Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class V680Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/ValtrackProtocol.java b/src/main/java/org/traccar/protocol/ValtrackProtocol.java
new file mode 100644
index 000000000..8471a0fd8
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/ValtrackProtocol.java
@@ -0,0 +1,41 @@
+/*
+ * 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.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpRequestDecoder;
+import io.netty.handler.codec.http.HttpResponseEncoder;
+import jakarta.inject.Inject;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+import org.traccar.config.Config;
+
+public class ValtrackProtocol extends BaseProtocol {
+
+ @Inject
+ public ValtrackProtocol(Config config) {
+ addServer(new TrackerServer(config, getName(), false) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new HttpResponseEncoder());
+ pipeline.addLast(new HttpRequestDecoder());
+ pipeline.addLast(new HttpObjectAggregator(16384));
+ pipeline.addLast(new ValtrackProtocolDecoder(ValtrackProtocol.this));
+ }
+ });
+ }
+}
diff --git a/src/main/java/org/traccar/protocol/ValtrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/ValtrackProtocolDecoder.java
new file mode 100644
index 000000000..dc9ca8590
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/ValtrackProtocolDecoder.java
@@ -0,0 +1,84 @@
+/*
+ * 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.channel.Channel;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import org.traccar.BaseHttpProtocolDecoder;
+import org.traccar.Protocol;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.io.StringReader;
+import java.net.SocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ValtrackProtocolDecoder extends BaseHttpProtocolDecoder {
+
+ public ValtrackProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ FullHttpRequest request = (FullHttpRequest) msg;
+ String content = request.content().toString(StandardCharsets.UTF_8);
+ JsonObject object = Json.createReader(new StringReader(content)).readObject();
+ JsonArray messages = object.getJsonArray("resource");
+
+ List<Position> positions = new LinkedList<>();
+ for (int i = 0; i < messages.size(); i++) {
+
+ JsonObject message = messages.getJsonObject(i);
+ String id = message.getString("devid");
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id);
+ if (deviceSession == null) {
+ continue;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.setValid(true);
+ position.setTime(new Date());
+ position.setLatitude(Double.parseDouble(message.getString("lat")));
+ position.setLongitude(Double.parseDouble(message.getString("lon")));
+ String speed = message.getString("speed");
+ if (!speed.isEmpty()) {
+ position.setSpeed(Double.parseDouble(speed));
+ }
+
+ position.set(Position.KEY_BATTERY, Double.parseDouble(message.getString("vbat")));
+
+ positions.add(position);
+
+ }
+
+ sendResponse(channel, HttpResponseStatus.OK);
+ return positions;
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/VisiontekProtocol.java b/src/main/java/org/traccar/protocol/VisiontekProtocol.java
index 5296402b4..83bcd37ff 100644
--- a/src/main/java/org/traccar/protocol/VisiontekProtocol.java
+++ b/src/main/java/org/traccar/protocol/VisiontekProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class VisiontekProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/VltProtocol.java b/src/main/java/org/traccar/protocol/VltProtocol.java
new file mode 100644
index 000000000..005cd8ffb
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/VltProtocol.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpRequestDecoder;
+import io.netty.handler.codec.http.HttpResponseEncoder;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+import org.traccar.config.Config;
+
+import jakarta.inject.Inject;
+
+public class VltProtocol extends BaseProtocol {
+
+ @Inject
+ public VltProtocol(Config config) {
+ addServer(new TrackerServer(config, getName(), false) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) {
+ pipeline.addLast(new HttpResponseEncoder());
+ pipeline.addLast(new HttpRequestDecoder());
+ pipeline.addLast(new HttpObjectAggregator(65535));
+ pipeline.addLast(new VltProtocolDecoder(VltProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/VltProtocolDecoder.java b/src/main/java/org/traccar/protocol/VltProtocolDecoder.java
new file mode 100644
index 000000000..01c0563f5
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/VltProtocolDecoder.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.QueryStringDecoder;
+import org.traccar.BaseHttpProtocolDecoder;
+import org.traccar.Protocol;
+import org.traccar.helper.Parser;
+import org.traccar.helper.PatternBuilder;
+import org.traccar.model.CellTower;
+import org.traccar.model.Network;
+import org.traccar.model.Position;
+import org.traccar.session.DeviceSession;
+
+import java.net.SocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class VltProtocolDecoder extends BaseHttpProtocolDecoder {
+
+ public VltProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ private static final Pattern PATTERN = new PatternBuilder()
+ .number("(dd)") // alert id
+ .expression("([HL])") // history
+ .number("([01])") // validity
+ .number("(dd)(dd)(dd)") // date (ddmmyy)
+ .number("(dd)(dd)(dd)") // time (hhmmss)
+ .number("(d{3}.d{6})([NS])") // latitude
+ .number("(d{3}.d{6})([EW])") // longitude
+ .number("(d{3})") // mcc
+ .expression("(x*[0-9]+)") // mnc
+ .number("(x{4})") // lac
+ .number("(d{9})") // cid
+ .number("(d{3}.d{2})") // speed
+ .number("(d{3}.d{2})") // course
+ .number("(d{2})") // satellites
+ .number("(d{2})") // hdop
+ .number("(d{2})") // rssi
+ .number("([01])") // ignition
+ .number("([01])") // charging
+ .expression("([HMS])") // vehicle mode
+ .compile();
+
+ private Position decodePosition(DeviceSession deviceSession, String sentence) {
+
+ Parser parser = new Parser(PATTERN, sentence);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.set(Position.KEY_EVENT, parser.nextInt());
+ position.set(Position.KEY_ARCHIVE, parser.next().equals("H") ? true : null);
+
+ position.setValid(parser.nextInt() > 0);
+ position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS));
+ position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM));
+ position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM));
+
+ int mcc = parser.nextInt();
+ int mnc = Integer.parseInt(parser.next().replaceAll("x", ""));
+ int lac = parser.nextHexInt();
+ int cid = parser.nextInt();
+
+ position.setSpeed(parser.nextDouble());
+ position.setCourse(parser.nextDouble());
+
+ position.set(Position.KEY_SATELLITES, parser.nextInt());
+ position.set(Position.KEY_HDOP, parser.nextInt());
+
+ position.setNetwork(new Network(CellTower.from(mcc, mnc, lac, cid, parser.nextInt())));
+
+ position.set(Position.KEY_IGNITION, parser.nextInt() > 0);
+ position.set(Position.KEY_CHARGE, parser.nextInt() > 0);
+ position.set(Position.KEY_MOTION, parser.next().equals("M"));
+
+ return position;
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ FullHttpRequest request = (FullHttpRequest) msg;
+ QueryStringDecoder decoder = new QueryStringDecoder(
+ request.content().toString(StandardCharsets.US_ASCII), false);
+ String sentence = decoder.parameters().get("vltdata").iterator().next();
+
+ int index = 0;
+ String type = sentence.substring(index, index += 3);
+ String imei = sentence.substring(index, index += 15);
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei);
+ if (deviceSession == null) {
+ sendResponse(channel, HttpResponseStatus.BAD_REQUEST);
+ return null;
+ }
+
+ sendResponse(channel, HttpResponseStatus.OK);
+
+ switch (type) {
+ case "NRM":
+ return decodePosition(deviceSession, sentence.substring(3 + 15));
+ case "BTH":
+ List<Position> positions = new LinkedList<>();
+ int count = Integer.parseInt(sentence.substring(index, index += 3));
+ for (int i = 0; i < count; i++) {
+ positions.add(decodePosition(deviceSession, sentence.substring(index, index += 78)));
+ }
+ return positions;
+ default:
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/protocol/VnetProtocol.java b/src/main/java/org/traccar/protocol/VnetProtocol.java
index dd739f0d9..6ccc54483 100644
--- a/src/main/java/org/traccar/protocol/VnetProtocol.java
+++ b/src/main/java/org/traccar/protocol/VnetProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.config.Config;
import java.nio.ByteOrder;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class VnetProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Vt200Protocol.java b/src/main/java/org/traccar/protocol/Vt200Protocol.java
index efb5fe2fd..97e64b74f 100644
--- a/src/main/java/org/traccar/protocol/Vt200Protocol.java
+++ b/src/main/java/org/traccar/protocol/Vt200Protocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Vt200Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Vt200ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Vt200ProtocolDecoder.java
index a8fc801e7..1ad15f39c 100644
--- a/src/main/java/org/traccar/protocol/Vt200ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Vt200ProtocolDecoder.java
@@ -123,7 +123,7 @@ public class Vt200ProtocolDecoder extends BaseProtocolDecoder {
position.set("tripStart", decodeDate(buf).getTime());
position.set("tripEnd", decodeDate(buf).getTime());
- position.set("drivingTime", buf.readUnsignedShort());
+ position.set(Position.KEY_DRIVING_TIME, buf.readUnsignedShort());
position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedInt());
position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedInt());
diff --git a/src/main/java/org/traccar/protocol/VtfmsProtocol.java b/src/main/java/org/traccar/protocol/VtfmsProtocol.java
index 482ab4a37..91453c413 100644
--- a/src/main/java/org/traccar/protocol/VtfmsProtocol.java
+++ b/src/main/java/org/traccar/protocol/VtfmsProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class VtfmsProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/WatchFrameDecoder.java b/src/main/java/org/traccar/protocol/WatchFrameDecoder.java
index f99bd52e2..9dfae8726 100644
--- a/src/main/java/org/traccar/protocol/WatchFrameDecoder.java
+++ b/src/main/java/org/traccar/protocol/WatchFrameDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,26 @@ public class WatchFrameDecoder extends BaseFrameDecoder {
protected Object decode(
ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception {
- int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ']') + 1;
+ int brackets = 0;
+ int endIndex = -1;
+ for (int i = buf.readerIndex(); i < buf.writerIndex(); i++) {
+ byte b = buf.getByte(i);
+ switch (b) {
+ case '[':
+ brackets += 1;
+ break;
+ case ']':
+ brackets -= 1;
+ break;
+ default:
+ break;
+ }
+ if (brackets == 0 && i > buf.readerIndex()) {
+ endIndex = i + 1;
+ break;
+ }
+ }
+
if (endIndex > 0) {
ByteBuf frame = Unpooled.buffer();
while (buf.readerIndex() < endIndex) {
diff --git a/src/main/java/org/traccar/protocol/WatchProtocol.java b/src/main/java/org/traccar/protocol/WatchProtocol.java
index 600f81328..aee70b6ec 100644
--- a/src/main/java/org/traccar/protocol/WatchProtocol.java
+++ b/src/main/java/org/traccar/protocol/WatchProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class WatchProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java b/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java
index 40d56b130..b586f4e92 100644
--- a/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java
@@ -51,7 +51,7 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder {
.number("(dd)(dd)(dd),") // time (hhmmss)
.expression("([AV]),") // validity
.number(" *(-?d+.d+),") // latitude
- .expression("([NS]),")
+ .expression("([NS])?,")
.number(" *(-?d+.d+),") // longitude
.expression("([EW])?,")
.number("(d+.?d*),") // speed
@@ -285,7 +285,8 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder {
|| type.equalsIgnoreCase("BLOOD")
|| type.equalsIgnoreCase("BPHRT")
|| type.equalsIgnoreCase("TEMP")
- || type.equalsIgnoreCase("btemp2")) {
+ || type.equalsIgnoreCase("btemp2")
+ || type.equalsIgnoreCase("oxygen")) {
if (buf.isReadable()) {
@@ -303,6 +304,8 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder {
if (Integer.parseInt(values[valueIndex++]) > 0) {
position.set(Position.PREFIX_TEMP + 1, Double.parseDouble(values[valueIndex]));
}
+ } else if (type.equalsIgnoreCase("oxygen")) {
+ position.set("bloodOxygen", Integer.parseInt(values[++valueIndex]));
} else {
if (type.equalsIgnoreCase("BPHRT") || type.equalsIgnoreCase("BLOOD")) {
position.set("pressureHigh", values[valueIndex++]);
diff --git a/src/main/java/org/traccar/protocol/WialonProtocol.java b/src/main/java/org/traccar/protocol/WialonProtocol.java
index a744349cd..84033132d 100644
--- a/src/main/java/org/traccar/protocol/WialonProtocol.java
+++ b/src/main/java/org/traccar/protocol/WialonProtocol.java
@@ -27,7 +27,7 @@ import org.traccar.model.Command;
import java.nio.charset.StandardCharsets;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class WialonProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java b/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java
index 3d57525b7..4d1b34dba 100644
--- a/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java
@@ -63,8 +63,9 @@ public class WialonProtocolDecoder extends BaseProtocolDecoder {
.number("(?:NA|(d+));") // outputs
.expression("(?:NA|([^;]*));") // adc
.expression("(?:NA|([^;]*));") // ibutton
- .expression("(?:NA|(.*))") // params
+ .expression("(?:NA|([^;]*))") // params
.groupEnd("?")
+ .any()
.compile();
private void sendResponse(Channel channel, SocketAddress remoteAddress, String type, Integer number) {
@@ -101,7 +102,7 @@ public class WialonProtocolDecoder extends BaseProtocolDecoder {
position.setTime(new Date());
}
- if (parser.hasNext(9)) {
+ if (parser.hasNextAny(9)) {
position.setLatitude(parser.nextCoordinate());
position.setLongitude(parser.nextCoordinate());
position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0)));
@@ -135,10 +136,22 @@ public class WialonProtocolDecoder extends BaseProtocolDecoder {
for (String param : values) {
Matcher paramParser = Pattern.compile("(.*):[1-3]:(.*)").matcher(param);
if (paramParser.matches()) {
+ String key = paramParser.group(1).toLowerCase();
+ String value = paramParser.group(2);
try {
- position.set(paramParser.group(1).toLowerCase(), Double.parseDouble(paramParser.group(2)));
+ if (key.equals("accuracy")) {
+ position.setAccuracy(Double.parseDouble(value));
+ } else {
+ position.set(key, Double.parseDouble(value));
+ }
} catch (NumberFormatException e) {
- position.set(paramParser.group(1).toLowerCase(), paramParser.group(2));
+ if (value.equalsIgnoreCase("true")) {
+ position.set(key, true);
+ } else if (value.equalsIgnoreCase("false")) {
+ position.set(key, false);
+ } else {
+ position.set(key, value);
+ }
}
}
}
diff --git a/src/main/java/org/traccar/protocol/WliProtocol.java b/src/main/java/org/traccar/protocol/WliProtocol.java
index f7084e55b..5b9ebb520 100644
--- a/src/main/java/org/traccar/protocol/WliProtocol.java
+++ b/src/main/java/org/traccar/protocol/WliProtocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class WliProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/WondexProtocol.java b/src/main/java/org/traccar/protocol/WondexProtocol.java
index 5a0401df4..e27b8e2bb 100644
--- a/src/main/java/org/traccar/protocol/WondexProtocol.java
+++ b/src/main/java/org/traccar/protocol/WondexProtocol.java
@@ -22,7 +22,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class WondexProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/WristbandProtocol.java b/src/main/java/org/traccar/protocol/WristbandProtocol.java
index c5d8d4050..117daf8cf 100644
--- a/src/main/java/org/traccar/protocol/WristbandProtocol.java
+++ b/src/main/java/org/traccar/protocol/WristbandProtocol.java
@@ -21,7 +21,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class WristbandProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Xexun2Protocol.java b/src/main/java/org/traccar/protocol/Xexun2Protocol.java
index 52cf731f0..9dd517cfa 100644
--- a/src/main/java/org/traccar/protocol/Xexun2Protocol.java
+++ b/src/main/java/org/traccar/protocol/Xexun2Protocol.java
@@ -21,7 +21,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Xexun2Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java
index 913dfaf28..0e3c44e12 100644
--- a/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java
@@ -156,7 +156,7 @@ public class Xexun2ProtocolDecoder extends BaseProtocolDecoder {
for (int j = 0; j < wifiCount; j++) {
String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:");
network.addWifiAccessPoint(WifiAccessPoint.from(
- mac.substring(0, mac.length() - 1), buf.readUnsignedByte()));
+ mac.substring(0, mac.length() - 1), buf.readByte()));
}
}
if (BitUtil.check(positionMask, 2)) {
@@ -164,7 +164,7 @@ public class Xexun2ProtocolDecoder extends BaseProtocolDecoder {
for (int j = 0; j < cellCount; j++) {
network.addCellTower(CellTower.from(
buf.readUnsignedShort(), buf.readUnsignedShort(),
- buf.readInt(), buf.readUnsignedInt(), buf.readUnsignedByte()));
+ buf.readInt(), buf.readUnsignedInt(), buf.readByte()));
}
}
if (network.getWifiAccessPoints() != null || network.getCellTowers() != null) {
diff --git a/src/main/java/org/traccar/protocol/XexunProtocol.java b/src/main/java/org/traccar/protocol/XexunProtocol.java
index 5c7329603..e76e47d19 100644
--- a/src/main/java/org/traccar/protocol/XexunProtocol.java
+++ b/src/main/java/org/traccar/protocol/XexunProtocol.java
@@ -25,7 +25,7 @@ import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class XexunProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/XirgoProtocol.java b/src/main/java/org/traccar/protocol/XirgoProtocol.java
index 0841d86d5..7e14c6842 100644
--- a/src/main/java/org/traccar/protocol/XirgoProtocol.java
+++ b/src/main/java/org/traccar/protocol/XirgoProtocol.java
@@ -24,7 +24,7 @@ import org.traccar.TrackerServer;
import org.traccar.config.Config;
import org.traccar.model.Command;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class XirgoProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Xrb28Protocol.java b/src/main/java/org/traccar/protocol/Xrb28Protocol.java
index 65c2a1230..135fb0928 100644
--- a/src/main/java/org/traccar/protocol/Xrb28Protocol.java
+++ b/src/main/java/org/traccar/protocol/Xrb28Protocol.java
@@ -26,7 +26,7 @@ import org.traccar.model.Command;
import java.nio.charset.StandardCharsets;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Xrb28Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java
index 88f2d07e5..6033293c4 100644
--- a/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2024 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import org.traccar.model.Command;
import org.traccar.model.Position;
import java.net.SocketAddress;
+import java.util.Arrays;
import java.util.regex.Pattern;
public class Xrb28ProtocolDecoder extends BaseProtocolDecoder {
@@ -46,6 +47,7 @@ public class Xrb28ProtocolDecoder extends BaseProtocolDecoder {
.expression("....,")
.expression("..,") // vendor
.number("d{15},") // imei
+ .number("d{12},").optional() // time
.expression("..,") // type
.number("[01],") // reserved
.number("(dd)(dd)(dd).d+,") // time (hhmmss)
@@ -67,23 +69,44 @@ public class Xrb28ProtocolDecoder extends BaseProtocolDecoder {
Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
String sentence = (String) msg;
+ String[] values = sentence.replaceAll("#$", "").split(",");
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, sentence.substring(9, 24));
+ int index = 0;
+ String header = values[index++];
+ String vendor = values[index++];
+
+ String imei = values[index++];
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei);
if (deviceSession == null) {
return null;
}
- String type = sentence.substring(25, 27);
+ String time;
+ if (values[index].length() == 12) {
+ time = values[index++];
+ } else {
+ time = null;
+ }
+
+ String type = values[index++];
if (channel != null) {
+ StringBuilder response = new StringBuilder("\u00ff\u00ff");
+ response.append(header.replaceAll("R$", "S")).append(',');
+ response.append(vendor).append(',');
+ response.append(imei).append(',');
+ if (time != null) {
+ response.append(time).append(',');
+ }
if (type.matches("L0|L1|W0|E1")) {
- channel.write(new NetworkMessage(
- "\u00ff\u00ff*SCOS" + sentence.substring(5, 27) + "#\n",
- remoteAddress));
+ response.append(type).append("#\n");
+ channel.write(new NetworkMessage(response.toString(), remoteAddress));
} else if (type.equals("R0") && pendingCommand != null) {
- String command = pendingCommand.equals(Command.TYPE_ALARM_ARM) ? "L1," : "L0,";
- channel.write(new NetworkMessage(
- "\u00ff\u00ff*SCOS" + sentence.substring(5, 25) + command + sentence.substring(30) + "\n",
- remoteAddress));
+ String command = pendingCommand.equals(Command.TYPE_ALARM_ARM) ? "L1" : "L0";
+ response.append(command);
+ String[] remaining = Arrays.copyOfRange(values, index, values.length);
+ response.append(String.join(",", remaining));
+ response.append("#\n");
+ channel.write(new NetworkMessage(response.toString(), remoteAddress));
pendingCommand = null;
}
}
@@ -95,11 +118,6 @@ public class Xrb28ProtocolDecoder extends BaseProtocolDecoder {
getLastLocation(position, null);
- String payload = sentence.substring(25, sentence.length() - 1);
-
- int index = 0;
- String[] values = payload.substring(3).split(",");
-
switch (type) {
case "Q0":
position.set(Position.KEY_BATTERY, Integer.parseInt(values[index++]) * 0.01);
@@ -146,7 +164,8 @@ public class Xrb28ProtocolDecoder extends BaseProtocolDecoder {
case "K0":
case "I0":
case "M0":
- position.set(Position.KEY_RESULT, payload);
+ String[] remaining = Arrays.copyOfRange(values, index, values.length);
+ position.set(Position.KEY_RESULT, String.join(",", remaining));
break;
default:
break;
diff --git a/src/main/java/org/traccar/protocol/Xt013Protocol.java b/src/main/java/org/traccar/protocol/Xt013Protocol.java
index 9e9087609..25809463a 100644
--- a/src/main/java/org/traccar/protocol/Xt013Protocol.java
+++ b/src/main/java/org/traccar/protocol/Xt013Protocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Xt013Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Xt2400Protocol.java b/src/main/java/org/traccar/protocol/Xt2400Protocol.java
index e200adb9f..1b7fc840b 100644
--- a/src/main/java/org/traccar/protocol/Xt2400Protocol.java
+++ b/src/main/java/org/traccar/protocol/Xt2400Protocol.java
@@ -20,7 +20,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class Xt2400Protocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java
index edcb3f535..11f9e0654 100644
--- a/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java
@@ -177,7 +177,7 @@ public class Xt2400ProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_POWER, buf.readUnsignedByte() * 0.1);
break;
case 0x57:
- position.set(Position.KEY_OBD_SPEED, UnitsConverter.knotsFromKph(buf.readUnsignedShort()));
+ position.set(Position.KEY_OBD_SPEED, buf.readUnsignedShort());
break;
case 0x65:
position.set(Position.KEY_VIN, buf.readSlice(17).toString(StandardCharsets.US_ASCII));
diff --git a/src/main/java/org/traccar/protocol/YwtProtocol.java b/src/main/java/org/traccar/protocol/YwtProtocol.java
index fb44e2360..27c71cfa8 100644
--- a/src/main/java/org/traccar/protocol/YwtProtocol.java
+++ b/src/main/java/org/traccar/protocol/YwtProtocol.java
@@ -23,7 +23,7 @@ import org.traccar.PipelineBuilder;
import org.traccar.TrackerServer;
import org.traccar.config.Config;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
public class YwtProtocol extends BaseProtocol {
diff --git a/src/main/java/org/traccar/reports/CombinedReportProvider.java b/src/main/java/org/traccar/reports/CombinedReportProvider.java
new file mode 100644
index 000000000..bad3a61b3
--- /dev/null
+++ b/src/main/java/org/traccar/reports/CombinedReportProvider.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.reports;
+
+import org.traccar.helper.model.DeviceUtil;
+import org.traccar.helper.model.PositionUtil;
+import org.traccar.model.Device;
+import org.traccar.model.Event;
+import org.traccar.reports.common.ReportUtils;
+import org.traccar.reports.model.CombinedReportItem;
+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.Order;
+import org.traccar.storage.query.Request;
+
+import jakarta.inject.Inject;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class CombinedReportProvider {
+
+ private static final Set<String> EXCLUDE_TYPES = Set.of(Event.TYPE_DEVICE_MOVING);
+
+ private final ReportUtils reportUtils;
+ private final Storage storage;
+
+ @Inject
+ public CombinedReportProvider(ReportUtils reportUtils, Storage storage) {
+ this.reportUtils = reportUtils;
+ this.storage = storage;
+ }
+
+ public Collection<CombinedReportItem> getObjects(
+ long userId, Collection<Long> deviceIds, Collection<Long> groupIds,
+ Date from, Date to) throws StorageException {
+ reportUtils.checkPeriodLimit(from, to);
+
+ ArrayList<CombinedReportItem> result = new ArrayList<>();
+ for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) {
+ CombinedReportItem item = new CombinedReportItem();
+ item.setDeviceId(device.getId());
+ var positions = PositionUtil.getPositions(storage, device.getId(), from, to);
+ item.setRoute(positions.stream()
+ .map(p -> new double[] {p.getLongitude(), p.getLatitude()})
+ .collect(Collectors.toList()));
+ var events = storage.getObjects(Event.class, new Request(
+ new Columns.All(),
+ new Condition.And(
+ new Condition.Equals("deviceId", device.getId()),
+ new Condition.Between("eventTime", "from", from, "to", to)),
+ new Order("eventTime")));
+ item.setEvents(events.stream()
+ .filter(e -> e.getPositionId() > 0 && !EXCLUDE_TYPES.contains(e.getType()))
+ .collect(Collectors.toList()));
+ var eventPositions = events.stream()
+ .map(Event::getPositionId)
+ .collect(Collectors.toSet());
+ item.setPositions(positions.stream()
+ .filter(p -> eventPositions.contains(p.getId()))
+ .collect(Collectors.toList()));
+ result.add(item);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/org/traccar/reports/CsvExportProvider.java b/src/main/java/org/traccar/reports/CsvExportProvider.java
index df55c470e..521dc120a 100644
--- a/src/main/java/org/traccar/reports/CsvExportProvider.java
+++ b/src/main/java/org/traccar/reports/CsvExportProvider.java
@@ -21,7 +21,7 @@ import org.traccar.model.Position;
import org.traccar.storage.Storage;
import org.traccar.storage.StorageException;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Date;
diff --git a/src/main/java/org/traccar/reports/DevicesReportProvider.java b/src/main/java/org/traccar/reports/DevicesReportProvider.java
new file mode 100644
index 000000000..7b4294d53
--- /dev/null
+++ b/src/main/java/org/traccar/reports/DevicesReportProvider.java
@@ -0,0 +1,78 @@
+/*
+ * 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.reports;
+
+import jakarta.inject.Inject;
+import org.jxls.util.JxlsHelper;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+import org.traccar.helper.model.PositionUtil;
+import org.traccar.model.Device;
+import org.traccar.model.Message;
+import org.traccar.model.User;
+import org.traccar.reports.common.ReportUtils;
+import org.traccar.reports.model.DeviceReportItem;
+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.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+public class DevicesReportProvider {
+
+ private final Config config;
+ private final ReportUtils reportUtils;
+ private final Storage storage;
+
+ @Inject
+ public DevicesReportProvider(Config config, ReportUtils reportUtils, Storage storage) {
+ this.config = config;
+ this.reportUtils = reportUtils;
+ this.storage = storage;
+ }
+
+ public Collection<DeviceReportItem> getObjects(long userId) throws StorageException {
+
+ var positions = PositionUtil.getLatestPositions(storage, userId).stream()
+ .collect(Collectors.toMap(Message::getDeviceId, p -> p));
+
+ return storage.getObjects(Device.class, new Request(
+ new Columns.All(),
+ new Condition.Permission(User.class, userId, Device.class))).stream()
+ .map(device -> new DeviceReportItem(device, positions.get(device.getId())))
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ public void getExcel(OutputStream outputStream, long userId) throws StorageException, IOException {
+
+ File file = Paths.get(config.getString(Keys.TEMPLATES_ROOT), "export", "devices.xlsx").toFile();
+ try (InputStream inputStream = new FileInputStream(file)) {
+ var context = reportUtils.initializeContext(userId);
+ context.putVar("items", getObjects(userId));
+ JxlsHelper.getInstance().setUseFastFormulaProcessor(false)
+ .processTemplate(inputStream, outputStream, context);
+ }
+ }
+}
diff --git a/src/main/java/org/traccar/reports/EventsReportProvider.java b/src/main/java/org/traccar/reports/EventsReportProvider.java
index 30f55ba80..f252f28cc 100644
--- a/src/main/java/org/traccar/reports/EventsReportProvider.java
+++ b/src/main/java/org/traccar/reports/EventsReportProvider.java
@@ -19,6 +19,7 @@ package org.traccar.reports;
import org.apache.poi.ss.util.WorkbookUtil;
import org.traccar.config.Config;
import org.traccar.config.Keys;
+import org.traccar.helper.model.DeviceUtil;
import org.traccar.model.Device;
import org.traccar.model.Event;
import org.traccar.model.Geofence;
@@ -34,7 +35,7 @@ import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Order;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -76,7 +77,7 @@ public class EventsReportProvider {
reportUtils.checkPeriodLimit(from, to);
ArrayList<Event> result = new ArrayList<>();
- for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) {
+ for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) {
Collection<Event> events = getEvents(device.getId(), from, to);
boolean all = types.isEmpty() || types.contains(Event.ALL_EVENTS);
for (Event event : events) {
@@ -104,7 +105,7 @@ public class EventsReportProvider {
HashMap<Long, String> geofenceNames = new HashMap<>();
HashMap<Long, String> maintenanceNames = new HashMap<>();
HashMap<Long, Position> positions = new HashMap<>();
- for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) {
+ for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) {
Collection<Event> events = getEvents(device.getId(), from, to);
boolean all = types.isEmpty() || types.contains(Event.ALL_EVENTS);
for (Iterator<Event> iterator = events.iterator(); iterator.hasNext();) {
diff --git a/src/main/java/org/traccar/reports/GpxExportProvider.java b/src/main/java/org/traccar/reports/GpxExportProvider.java
index ccbd97fc3..1c45b6416 100644
--- a/src/main/java/org/traccar/reports/GpxExportProvider.java
+++ b/src/main/java/org/traccar/reports/GpxExportProvider.java
@@ -24,7 +24,7 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Date;
diff --git a/src/main/java/org/traccar/reports/KmlExportProvider.java b/src/main/java/org/traccar/reports/KmlExportProvider.java
index 24fcfb8ab..24dca018c 100644
--- a/src/main/java/org/traccar/reports/KmlExportProvider.java
+++ b/src/main/java/org/traccar/reports/KmlExportProvider.java
@@ -23,7 +23,7 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
diff --git a/src/main/java/org/traccar/reports/RouteReportProvider.java b/src/main/java/org/traccar/reports/RouteReportProvider.java
index 3ee651619..d761fe1e5 100644
--- a/src/main/java/org/traccar/reports/RouteReportProvider.java
+++ b/src/main/java/org/traccar/reports/RouteReportProvider.java
@@ -19,6 +19,7 @@ package org.traccar.reports;
import org.apache.poi.ss.util.WorkbookUtil;
import org.traccar.config.Config;
import org.traccar.config.Keys;
+import org.traccar.helper.model.DeviceUtil;
import org.traccar.helper.model.PositionUtil;
import org.traccar.model.Device;
import org.traccar.model.Group;
@@ -31,7 +32,7 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -41,6 +42,8 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
+import java.util.Map;
+import java.util.HashMap;
public class RouteReportProvider {
@@ -48,6 +51,8 @@ public class RouteReportProvider {
private final ReportUtils reportUtils;
private final Storage storage;
+ private final Map<String, Integer> namesCount = new HashMap<>();
+
@Inject
public RouteReportProvider(Config config, ReportUtils reportUtils, Storage storage) {
this.config = config;
@@ -60,12 +65,18 @@ public class RouteReportProvider {
reportUtils.checkPeriodLimit(from, to);
ArrayList<Position> result = new ArrayList<>();
- for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) {
+ for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) {
result.addAll(PositionUtil.getPositions(storage, device.getId(), from, to));
}
return result;
}
+
+ private String getUniqueSheetName(String key) {
+ namesCount.compute(key, (k, value) -> value == null ? 1 : (value + 1));
+ return namesCount.get(key) > 1 ? key + '-' + namesCount.get(key) : key;
+ }
+
public void getExcel(OutputStream outputStream,
long userId, Collection<Long> deviceIds, Collection<Long> groupIds,
Date from, Date to) throws StorageException, IOException {
@@ -73,11 +84,11 @@ public class RouteReportProvider {
ArrayList<DeviceReportSection> devicesRoutes = new ArrayList<>();
ArrayList<String> sheetNames = new ArrayList<>();
- for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) {
+ for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) {
var positions = PositionUtil.getPositions(storage, device.getId(), from, to);
DeviceReportSection deviceRoutes = new DeviceReportSection();
deviceRoutes.setDeviceName(device.getName());
- sheetNames.add(WorkbookUtil.createSafeSheetName(deviceRoutes.getDeviceName()));
+ sheetNames.add(WorkbookUtil.createSafeSheetName(getUniqueSheetName(deviceRoutes.getDeviceName())));
if (device.getGroupId() > 0) {
Group group = storage.getObject(Group.class, new Request(
new Columns.All(), new Condition.Equals("id", device.getGroupId())));
diff --git a/src/main/java/org/traccar/reports/StopsReportProvider.java b/src/main/java/org/traccar/reports/StopsReportProvider.java
index ec3fd2215..2160fec0e 100644
--- a/src/main/java/org/traccar/reports/StopsReportProvider.java
+++ b/src/main/java/org/traccar/reports/StopsReportProvider.java
@@ -19,7 +19,7 @@ package org.traccar.reports;
import org.apache.poi.ss.util.WorkbookUtil;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import org.traccar.helper.model.PositionUtil;
+import org.traccar.helper.model.DeviceUtil;
import org.traccar.model.Device;
import org.traccar.model.Group;
import org.traccar.reports.common.ReportUtils;
@@ -31,7 +31,7 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -55,20 +55,14 @@ public class StopsReportProvider {
this.storage = storage;
}
- private Collection<StopReportItem> detectStops(Device device, Date from, Date to) throws StorageException {
- boolean ignoreOdometer = config.getBoolean(Keys.REPORT_IGNORE_ODOMETER);
- var positions = PositionUtil.getPositions(storage, device.getId(), from, to);
- return reportUtils.detectTripsAndStops(device, positions, ignoreOdometer, StopReportItem.class);
- }
-
public Collection<StopReportItem> getObjects(
long userId, Collection<Long> deviceIds, Collection<Long> groupIds,
Date from, Date to) throws StorageException {
reportUtils.checkPeriodLimit(from, to);
ArrayList<StopReportItem> result = new ArrayList<>();
- for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) {
- result.addAll(detectStops(device, from, to));
+ for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) {
+ result.addAll(reportUtils.detectTripsAndStops(device, from, to, StopReportItem.class));
}
return result;
}
@@ -80,8 +74,8 @@ public class StopsReportProvider {
ArrayList<DeviceReportSection> devicesStops = new ArrayList<>();
ArrayList<String> sheetNames = new ArrayList<>();
- for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) {
- Collection<StopReportItem> stops = detectStops(device, from, to);
+ for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) {
+ Collection<StopReportItem> stops = reportUtils.detectTripsAndStops(device, from, to, StopReportItem.class);
DeviceReportSection deviceStops = new DeviceReportSection();
deviceStops.setDeviceName(device.getName());
sheetNames.add(WorkbookUtil.createSafeSheetName(deviceStops.getDeviceName()));
diff --git a/src/main/java/org/traccar/reports/SummaryReportProvider.java b/src/main/java/org/traccar/reports/SummaryReportProvider.java
index 9415f3e81..ffde0b067 100644
--- a/src/main/java/org/traccar/reports/SummaryReportProvider.java
+++ b/src/main/java/org/traccar/reports/SummaryReportProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
* Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,6 +21,7 @@ import org.traccar.api.security.PermissionsService;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.helper.UnitsConverter;
+import org.traccar.helper.model.DeviceUtil;
import org.traccar.helper.model.PositionUtil;
import org.traccar.helper.model.UserUtil;
import org.traccar.model.Device;
@@ -29,18 +30,25 @@ import org.traccar.reports.common.ReportUtils;
import org.traccar.reports.model.SummaryReportItem;
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.Order;
+import org.traccar.storage.query.Request;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
-import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
+import java.util.List;
public class SummaryReportProvider {
@@ -58,96 +66,105 @@ public class SummaryReportProvider {
this.storage = storage;
}
- private SummaryReportItem calculateSummaryResult(Device device, Collection<Position> positions) {
+ private Position getEdgePosition(long deviceId, Date from, Date to, boolean end) throws StorageException {
+ return storage.getObject(Position.class, new Request(
+ new Columns.All(),
+ new Condition.And(
+ new Condition.Equals("deviceId", deviceId),
+ new Condition.Between("fixTime", "from", from, "to", to)),
+ new Order("fixTime", end, 1)));
+ }
+
+ private Collection<SummaryReportItem> calculateDeviceResult(
+ Device device, Date from, Date to, boolean fast) throws StorageException {
+
SummaryReportItem result = new SummaryReportItem();
result.setDeviceId(device.getId());
result.setDeviceName(device.getName());
- if (positions != null && !positions.isEmpty()) {
- Position firstPosition = null;
- Position previousPosition = null;
+
+ Position first = null;
+ Position last = null;
+ if (fast) {
+ first = getEdgePosition(device.getId(), from, to, false);
+ last = getEdgePosition(device.getId(), from, to, true);
+ } else {
+ var positions = PositionUtil.getPositions(storage, device.getId(), from, to);
for (Position position : positions) {
- if (firstPosition == null) {
- firstPosition = position;
+ if (first == null) {
+ first = position;
}
- previousPosition = position;
if (position.getSpeed() > result.getMaxSpeed()) {
result.setMaxSpeed(position.getSpeed());
}
+ last = position;
}
+ }
+
+ if (first != null && last != null) {
boolean ignoreOdometer = config.getBoolean(Keys.REPORT_IGNORE_ODOMETER);
- result.setDistance(PositionUtil.calculateDistance(firstPosition, previousPosition, !ignoreOdometer));
- result.setSpentFuel(reportUtils.calculateFuel(firstPosition, previousPosition));
+ result.setDistance(PositionUtil.calculateDistance(first, last, !ignoreOdometer));
+ result.setSpentFuel(reportUtils.calculateFuel(first, last));
long durationMilliseconds;
- if (firstPosition.hasAttribute(Position.KEY_HOURS) && previousPosition.hasAttribute(Position.KEY_HOURS)) {
- durationMilliseconds =
- previousPosition.getLong(Position.KEY_HOURS) - firstPosition.getLong(Position.KEY_HOURS);
+ if (first.hasAttribute(Position.KEY_HOURS) && last.hasAttribute(Position.KEY_HOURS)) {
+ durationMilliseconds = last.getLong(Position.KEY_HOURS) - first.getLong(Position.KEY_HOURS);
result.setEngineHours(durationMilliseconds);
} else {
- durationMilliseconds =
- previousPosition.getFixTime().getTime() - firstPosition.getFixTime().getTime();
+ durationMilliseconds = last.getFixTime().getTime() - first.getFixTime().getTime();
}
if (durationMilliseconds > 0) {
- result.setAverageSpeed(
- UnitsConverter.knotsFromMps(result.getDistance() * 1000 / durationMilliseconds));
+ result.setAverageSpeed(UnitsConverter.knotsFromMps(result.getDistance() * 1000 / durationMilliseconds));
}
if (!ignoreOdometer
- && firstPosition.getDouble(Position.KEY_ODOMETER) != 0
- && previousPosition.getDouble(Position.KEY_ODOMETER) != 0) {
- result.setStartOdometer(firstPosition.getDouble(Position.KEY_ODOMETER));
- result.setEndOdometer(previousPosition.getDouble(Position.KEY_ODOMETER));
+ && first.getDouble(Position.KEY_ODOMETER) != 0 && last.getDouble(Position.KEY_ODOMETER) != 0) {
+ result.setStartOdometer(first.getDouble(Position.KEY_ODOMETER));
+ result.setEndOdometer(last.getDouble(Position.KEY_ODOMETER));
} else {
- result.setStartOdometer(firstPosition.getDouble(Position.KEY_TOTAL_DISTANCE));
- result.setEndOdometer(previousPosition.getDouble(Position.KEY_TOTAL_DISTANCE));
+ result.setStartOdometer(first.getDouble(Position.KEY_TOTAL_DISTANCE));
+ result.setEndOdometer(last.getDouble(Position.KEY_TOTAL_DISTANCE));
}
- result.setStartTime(firstPosition.getFixTime());
- result.setEndTime(previousPosition.getFixTime());
+ result.setStartTime(first.getFixTime());
+ result.setEndTime(last.getFixTime());
+ return List.of(result);
}
- return result;
- }
- private int getDay(long userId, Date date) throws StorageException {
- Calendar calendar = Calendar.getInstance(UserUtil.getTimezone(
- permissionsService.getServer(), permissionsService.getUser(userId)));
- calendar.setTime(date);
- return calendar.get(Calendar.DAY_OF_MONTH);
+ return List.of();
}
- private Collection<SummaryReportItem> calculateSummaryResults(
- long userId, Device device, Date from, Date to, boolean daily) throws StorageException {
+ private Collection<SummaryReportItem> calculateDeviceResults(
+ Device device, ZonedDateTime from, ZonedDateTime to, boolean daily) throws StorageException {
- var positions = PositionUtil.getPositions(storage, device.getId(), from, to);
+ boolean fast = Duration.between(from, to).toSeconds() > config.getLong(Keys.REPORT_FAST_THRESHOLD);
var results = new ArrayList<SummaryReportItem>();
- if (daily && !positions.isEmpty()) {
- int startIndex = 0;
- int startDay = getDay(userId, positions.iterator().next().getFixTime());
- for (int i = 0; i < positions.size(); i++) {
- int currentDay = getDay(userId, positions.get(i).getFixTime());
- if (currentDay != startDay) {
- results.add(calculateSummaryResult(device, positions.subList(startIndex, i)));
- startIndex = i;
- startDay = currentDay;
- }
+ if (daily) {
+ while (from.truncatedTo(ChronoUnit.DAYS).isBefore(to.truncatedTo(ChronoUnit.DAYS))) {
+ ZonedDateTime fromDay = from.truncatedTo(ChronoUnit.DAYS);
+ ZonedDateTime nextDay = fromDay.plus(1, ChronoUnit.DAYS);
+ results.addAll(calculateDeviceResult(
+ device, Date.from(from.toInstant()), Date.from(nextDay.toInstant()), fast));
+ from = nextDay;
}
- results.add(calculateSummaryResult(device, positions.subList(startIndex, positions.size())));
+ results.addAll(calculateDeviceResult(device, Date.from(from.toInstant()), Date.from(to.toInstant()), fast));
} else {
- results.add(calculateSummaryResult(device, positions));
+ results.addAll(calculateDeviceResult(device, Date.from(from.toInstant()), Date.from(to.toInstant()), fast));
}
-
return results;
}
public Collection<SummaryReportItem> getObjects(
- long userId, Collection<Long> deviceIds,
- Collection<Long> groupIds, Date from, Date to, boolean daily) throws StorageException {
+ long userId, Collection<Long> deviceIds, Collection<Long> groupIds,
+ Date from, Date to, boolean daily) throws StorageException {
reportUtils.checkPeriodLimit(from, to);
+ var tz = UserUtil.getTimezone(permissionsService.getServer(), permissionsService.getUser(userId)).toZoneId();
+
ArrayList<SummaryReportItem> result = new ArrayList<>();
- for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) {
- Collection<SummaryReportItem> deviceResults = calculateSummaryResults(userId, device, from, to, daily);
+ for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) {
+ var deviceResults = calculateDeviceResults(
+ device, from.toInstant().atZone(tz), to.toInstant().atZone(tz), daily);
for (SummaryReportItem summaryReport : deviceResults) {
if (summaryReport.getStartTime() != null && summaryReport.getEndTime() != null) {
result.add(summaryReport);
diff --git a/src/main/java/org/traccar/reports/TripsReportProvider.java b/src/main/java/org/traccar/reports/TripsReportProvider.java
index 265811354..9ff7232af 100644
--- a/src/main/java/org/traccar/reports/TripsReportProvider.java
+++ b/src/main/java/org/traccar/reports/TripsReportProvider.java
@@ -19,7 +19,7 @@ package org.traccar.reports;
import org.apache.poi.ss.util.WorkbookUtil;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import org.traccar.helper.model.PositionUtil;
+import org.traccar.helper.model.DeviceUtil;
import org.traccar.model.Device;
import org.traccar.model.Group;
import org.traccar.reports.common.ReportUtils;
@@ -31,7 +31,7 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -55,20 +55,14 @@ public class TripsReportProvider {
this.storage = storage;
}
- private Collection<TripReportItem> detectTrips(Device device, Date from, Date to) throws StorageException {
- boolean ignoreOdometer = config.getBoolean(Keys.REPORT_IGNORE_ODOMETER);
- var positions = PositionUtil.getPositions(storage, device.getId(), from, to);
- return reportUtils.detectTripsAndStops(device, positions, ignoreOdometer, TripReportItem.class);
- }
-
public Collection<TripReportItem> getObjects(
long userId, Collection<Long> deviceIds, Collection<Long> groupIds,
Date from, Date to) throws StorageException {
reportUtils.checkPeriodLimit(from, to);
ArrayList<TripReportItem> result = new ArrayList<>();
- for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) {
- result.addAll(detectTrips(device, from, to));
+ for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) {
+ result.addAll(reportUtils.detectTripsAndStops(device, from, to, TripReportItem.class));
}
return result;
}
@@ -80,8 +74,8 @@ public class TripsReportProvider {
ArrayList<DeviceReportSection> devicesTrips = new ArrayList<>();
ArrayList<String> sheetNames = new ArrayList<>();
- for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) {
- Collection<TripReportItem> trips = detectTrips(device, from, to);
+ for (Device device: DeviceUtil.getAccessibleDevices(storage, userId, deviceIds, groupIds)) {
+ Collection<TripReportItem> trips = reportUtils.detectTripsAndStops(device, from, to, TripReportItem.class);
DeviceReportSection deviceTrips = new DeviceReportSection();
deviceTrips.setDeviceName(device.getName());
sheetNames.add(WorkbookUtil.createSafeSheetName(deviceTrips.getDeviceName()));
diff --git a/src/main/java/org/traccar/reports/common/ExpressionEvaluatorFactory.java b/src/main/java/org/traccar/reports/common/ExpressionEvaluatorFactory.java
new file mode 100644
index 000000000..8b139a572
--- /dev/null
+++ b/src/main/java/org/traccar/reports/common/ExpressionEvaluatorFactory.java
@@ -0,0 +1,58 @@
+package org.traccar.reports.common;
+
+import org.apache.commons.jexl3.JexlBuilder;
+import org.apache.commons.jexl3.introspection.JexlPermissions;
+import org.jxls.expression.ExpressionEvaluator;
+import org.jxls.expression.JexlExpressionEvaluator;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class ExpressionEvaluatorFactory implements org.jxls.expression.ExpressionEvaluatorFactory {
+
+ private final JexlPermissions permissions = new JexlPermissions() {
+ @Override
+ public boolean allow(Package pack) {
+ return true;
+ }
+
+ @Override
+ public boolean allow(Class<?> clazz) {
+ return true;
+ }
+
+ @Override
+ public boolean allow(Constructor<?> ctor) {
+ return true;
+ }
+
+ @Override
+ public boolean allow(Method method) {
+ return true;
+ }
+
+ @Override
+ public boolean allow(Field field) {
+ return true;
+ }
+
+ @Override
+ public JexlPermissions compose(String... src) {
+ return this;
+ }
+ };
+
+ @Override
+ public ExpressionEvaluator createExpressionEvaluator(String expression) {
+ JexlExpressionEvaluator expressionEvaluator = expression == null
+ ? new JexlExpressionEvaluator()
+ : new JexlExpressionEvaluator(expression);
+ expressionEvaluator.setJexlEngine(new JexlBuilder()
+ .silent(true)
+ .strict(false)
+ .permissions(permissions)
+ .create());
+ return expressionEvaluator;
+ }
+}
diff --git a/src/main/java/org/traccar/reports/common/ReportMailer.java b/src/main/java/org/traccar/reports/common/ReportMailer.java
index 221b35ae1..9fb30fe9f 100644
--- a/src/main/java/org/traccar/reports/common/ReportMailer.java
+++ b/src/main/java/org/traccar/reports/common/ReportMailer.java
@@ -22,11 +22,11 @@ import org.traccar.mail.MailManager;
import org.traccar.model.User;
import org.traccar.storage.StorageException;
-import javax.activation.DataHandler;
-import javax.inject.Inject;
-import javax.mail.MessagingException;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.util.ByteArrayDataSource;
+import jakarta.activation.DataHandler;
+import jakarta.inject.Inject;
+import jakarta.mail.MessagingException;
+import jakarta.mail.internet.MimeBodyPart;
+import jakarta.mail.util.ByteArrayDataSource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -55,7 +55,7 @@ public class ReportMailer {
stream.toByteArray(), "application/octet-stream")));
User user = permissionsService.getUser(userId);
- mailManager.sendMessage(user, "Report", "The report is in the attachment.", attachment);
+ mailManager.sendMessage(user, false, "Report", "The report is in the attachment.", attachment);
} catch (StorageException | IOException | MessagingException e) {
LOGGER.warn("Email report failed", e);
}
diff --git a/src/main/java/org/traccar/reports/common/ReportUtils.java b/src/main/java/org/traccar/reports/common/ReportUtils.java
index a7c420095..3b8e84887 100644
--- a/src/main/java/org/traccar/reports/common/ReportUtils.java
+++ b/src/main/java/org/traccar/reports/common/ReportUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org)
* Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,6 +16,8 @@
*/
package org.traccar.reports.common;
+import jakarta.annotation.Nullable;
+import jakarta.inject.Inject;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.tools.generic.DateTool;
import org.apache.velocity.tools.generic.NumberTool;
@@ -31,12 +33,13 @@ import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.geocoder.Geocoder;
import org.traccar.helper.UnitsConverter;
+import org.traccar.helper.model.AttributeUtil;
import org.traccar.helper.model.PositionUtil;
import org.traccar.helper.model.UserUtil;
import org.traccar.model.BaseModel;
import org.traccar.model.Device;
import org.traccar.model.Driver;
-import org.traccar.model.Group;
+import org.traccar.model.Event;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.reports.model.BaseReportItem;
@@ -48,44 +51,35 @@ 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.Order;
import org.traccar.storage.query.Request;
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-import javax.inject.Singleton;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
+import java.time.Duration;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
-import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
-import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
-@Singleton
public class ReportUtils {
private final Config config;
private final Storage storage;
private final PermissionsService permissionsService;
- private final TripsConfig tripsConfig;
private final VelocityEngine velocityEngine;
private final Geocoder geocoder;
@Inject
public ReportUtils(
Config config, Storage storage, PermissionsService permissionsService,
- TripsConfig tripsConfig, VelocityEngine velocityEngine, @Nullable Geocoder geocoder) {
+ VelocityEngine velocityEngine, @Nullable Geocoder geocoder) {
this.config = config;
this.storage = storage;
this.permissionsService = permissionsService;
- this.tripsConfig = tripsConfig;
this.velocityEngine = velocityEngine;
this.geocoder = geocoder;
}
@@ -106,49 +100,11 @@ public class ReportUtils {
}
}
- public Collection<Device> getAccessibleDevices(
- long userId, Collection<Long> deviceIds, Collection<Long> groupIds) throws StorageException {
-
- var devices = storage.getObjects(Device.class, new Request(
- new Columns.All(),
- new Condition.Permission(User.class, userId, Device.class)));
- var deviceById = devices.stream()
- .collect(Collectors.toUnmodifiableMap(Device::getId, x -> x));
- var devicesByGroup = devices.stream()
- .filter(x -> x.getGroupId() > 0)
- .collect(Collectors.groupingBy(Device::getGroupId));
-
- var groups = storage.getObjects(Group.class, new Request(
- new Columns.All(),
- new Condition.Permission(User.class, userId, Group.class)));
- var groupsByGroup = groups.stream()
- .filter(x -> x.getGroupId() > 0)
- .collect(Collectors.groupingBy(Group::getGroupId));
-
- var results = deviceIds.stream()
- .map(deviceById::get)
- .filter(Objects::nonNull)
- .collect(Collectors.toSet());
-
- var groupQueue = new LinkedList<>(groupIds);
- while (!groupQueue.isEmpty()) {
- long groupId = groupQueue.pop();
- results.addAll(devicesByGroup.getOrDefault(groupId, Collections.emptyList()));
- groupQueue.addAll(groupsByGroup.getOrDefault(groupId, Collections.emptyList())
- .stream().map(Group::getId).collect(Collectors.toUnmodifiableList()));
- }
-
- return results;
- }
-
- public double calculateFuel(Position firstPosition, Position lastPosition) {
-
- if (firstPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null
- && lastPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null) {
-
- BigDecimal value = BigDecimal.valueOf(firstPosition.getDouble(Position.KEY_FUEL_LEVEL)
- - lastPosition.getDouble(Position.KEY_FUEL_LEVEL));
- return value.setScale(1, RoundingMode.HALF_EVEN).doubleValue();
+ public double calculateFuel(Position first, Position last) {
+ if (first.hasAttribute(Position.KEY_FUEL_USED) && last.hasAttribute(Position.KEY_FUEL_USED)) {
+ return last.getDouble(Position.KEY_FUEL_USED) - first.getDouble(Position.KEY_FUEL_USED);
+ } else if (first.hasAttribute(Position.KEY_FUEL_LEVEL) && last.hasAttribute(Position.KEY_FUEL_LEVEL)) {
+ return first.getDouble(Position.KEY_FUEL_LEVEL) - last.getDouble(Position.KEY_FUEL_LEVEL);
}
return 0;
}
@@ -205,20 +161,9 @@ public class ReportUtils {
}
private TripReportItem calculateTrip(
- Device device, ArrayList<Position> positions, int startIndex, int endIndex,
+ Device device, Position startTrip, Position endTrip, double maxSpeed,
boolean ignoreOdometer) throws StorageException {
- Position startTrip = positions.get(startIndex);
- Position endTrip = positions.get(endIndex);
-
- double speedMax = 0;
- for (int i = startIndex; i <= endIndex; i++) {
- double speed = positions.get(i).getSpeed();
- if (speed > speedMax) {
- speedMax = speed;
- }
- }
-
TripReportItem trip = new TripReportItem();
long tripDuration = endTrip.getFixTime().getTime() - startTrip.getFixTime().getTime();
@@ -251,7 +196,7 @@ public class ReportUtils {
if (tripDuration > 0) {
trip.setAverageSpeed(UnitsConverter.knotsFromMps(trip.getDistance() * 1000 / tripDuration));
}
- trip.setMaxSpeed(speedMax);
+ trip.setMaxSpeed(maxSpeed);
trip.setSpentFuel(calculateFuel(startTrip, endTrip));
trip.setDriverUniqueId(findDriver(startTrip, endTrip));
@@ -271,10 +216,7 @@ public class ReportUtils {
}
private StopReportItem calculateStop(
- Device device, ArrayList<Position> positions, int startIndex, int endIndex, boolean ignoreOdometer) {
-
- Position startStop = positions.get(startIndex);
- Position endStop = positions.get(endIndex);
+ Device device, Position startStop, Position endStop, boolean ignoreOdometer) {
StopReportItem stop = new StopReportItem();
@@ -318,17 +260,17 @@ public class ReportUtils {
@SuppressWarnings("unchecked")
private <T extends BaseReportItem> T calculateTripOrStop(
- Device device, ArrayList<Position> positions, int startIndex, int endIndex,
+ Device device, Position startPosition, Position endPosition, double maxSpeed,
boolean ignoreOdometer, Class<T> reportClass) throws StorageException {
if (reportClass.equals(TripReportItem.class)) {
- return (T) calculateTrip(device, positions, startIndex, endIndex, ignoreOdometer);
+ return (T) calculateTrip(device, startPosition, endPosition, maxSpeed, ignoreOdometer);
} else {
- return (T) calculateStop(device, positions, startIndex, endIndex, ignoreOdometer);
+ return (T) calculateStop(device, startPosition, endPosition, ignoreOdometer);
}
}
- private boolean isMoving(ArrayList<Position> positions, int index, TripsConfig tripsConfig) {
+ private boolean isMoving(List<Position> positions, int index, TripsConfig tripsConfig) {
if (tripsConfig.getMinimalNoDataDuration() > 0) {
boolean beforeGap = index < positions.size() - 1
&& positions.get(index + 1).getFixTime().getTime() - positions.get(index).getFixTime().getTime()
@@ -343,13 +285,26 @@ public class ReportUtils {
return positions.get(index).getBoolean(Position.KEY_MOTION);
}
- public <T extends BaseReportItem> Collection<T> detectTripsAndStops(
- Device device, Collection<Position> positionCollection, boolean ignoreOdometer,
- Class<T> reportClass) throws StorageException {
+ public <T extends BaseReportItem> List<T> detectTripsAndStops(
+ Device device, Date from, Date to, Class<T> reportClass) throws StorageException {
+
+ long threshold = config.getLong(Keys.REPORT_FAST_THRESHOLD);
+ if (Duration.between(from.toInstant(), to.toInstant()).toSeconds() > threshold) {
+ return fastTripsAndStops(device, from, to, reportClass);
+ } else {
+ return slowTripsAndStops(device, from, to, reportClass);
+ }
+ }
+
+ public <T extends BaseReportItem> List<T> slowTripsAndStops(
+ Device device, Date from, Date to, Class<T> reportClass) throws StorageException {
- Collection<T> result = new ArrayList<>();
+ List<T> result = new ArrayList<>();
+ TripsConfig tripsConfig = new TripsConfig(
+ new AttributeUtil.StorageProvider(config, storage, permissionsService, device));
+ boolean ignoreOdometer = config.getBoolean(Keys.REPORT_IGNORE_ODOMETER);
- ArrayList<Position> positions = new ArrayList<>(positionCollection);
+ var positions = PositionUtil.getPositions(storage, device.getId(), from, to);
if (!positions.isEmpty()) {
boolean trips = reportClass.equals(TripReportItem.class);
@@ -359,17 +314,23 @@ public class ReportUtils {
motionState.setMotionState(initialValue);
boolean detected = trips == motionState.getMotionState();
+ double maxSpeed = 0;
int startEventIndex = detected ? 0 : -1;
int startNoEventIndex = -1;
for (int i = 0; i < positions.size(); i++) {
boolean motion = isMoving(positions, i, tripsConfig);
if (motionState.getMotionState() != motion) {
if (motion == trips) {
- startEventIndex = detected ? startEventIndex : i;
+ if (!detected) {
+ startEventIndex = i;
+ maxSpeed = positions.get(i).getSpeed();
+ }
startNoEventIndex = -1;
} else {
startNoEventIndex = i;
}
+ } else {
+ maxSpeed = Math.max(maxSpeed, positions.get(i).getSpeed());
}
MotionProcessor.updateState(motionState, positions.get(i), motion, tripsConfig);
@@ -379,17 +340,58 @@ public class ReportUtils {
startNoEventIndex = -1;
} else if (startEventIndex >= 0 && startNoEventIndex >= 0) {
result.add(calculateTripOrStop(
- device, positions, startEventIndex, startNoEventIndex, ignoreOdometer, reportClass));
+ device, positions.get(startEventIndex), positions.get(startNoEventIndex),
+ maxSpeed, ignoreOdometer, reportClass));
detected = false;
startEventIndex = -1;
startNoEventIndex = -1;
}
}
}
- if (startEventIndex >= 0 && startEventIndex < positions.size() - 1) {
+ if (detected & startEventIndex >= 0 && startEventIndex < positions.size() - 1) {
int endIndex = startNoEventIndex >= 0 ? startNoEventIndex : positions.size() - 1;
result.add(calculateTripOrStop(
- device, positions, startEventIndex, endIndex, ignoreOdometer, reportClass));
+ device, positions.get(startEventIndex), positions.get(endIndex),
+ maxSpeed, ignoreOdometer, reportClass));
+ }
+ }
+
+ return result;
+ }
+
+ public <T extends BaseReportItem> List<T> fastTripsAndStops(
+ Device device, Date from, Date to, Class<T> reportClass) throws StorageException {
+
+ List<T> result = new ArrayList<>();
+ boolean ignoreOdometer = config.getBoolean(Keys.REPORT_IGNORE_ODOMETER);
+ boolean trips = reportClass.equals(TripReportItem.class);
+ Set<String> filter = Set.of(Event.TYPE_DEVICE_MOVING, Event.TYPE_DEVICE_STOPPED);
+
+ var events = storage.getObjects(Event.class, new Request(
+ new Columns.All(),
+ new Condition.And(
+ new Condition.Equals("deviceId", device.getId()),
+ new Condition.Between("eventTime", "from", from, "to", to)),
+ new Order("eventTime")));
+ var filteredEvents = events.stream()
+ .filter(event -> filter.contains(event.getType()))
+ .collect(Collectors.toList());
+
+ Event startEvent = null;
+ for (Event event : filteredEvents) {
+ boolean motion = event.getType().equals(Event.TYPE_DEVICE_MOVING);
+ if (motion == trips) {
+ startEvent = event;
+ } else if (startEvent != null) {
+ Position startPosition = storage.getObject(Position.class, new Request(
+ new Columns.All(), new Condition.Equals("id", startEvent.getPositionId())));
+ Position endPosition = storage.getObject(Position.class, new Request(
+ new Columns.All(), new Condition.Equals("id", event.getPositionId())));
+ if (startPosition != null && endPosition != null) {
+ result.add(calculateTripOrStop(
+ device, startPosition, endPosition, 0, ignoreOdometer, reportClass));
+ }
+ startEvent = null;
}
}
diff --git a/src/main/java/org/traccar/reports/common/TripsConfig.java b/src/main/java/org/traccar/reports/common/TripsConfig.java
index 52db97b74..2792114d4 100644
--- a/src/main/java/org/traccar/reports/common/TripsConfig.java
+++ b/src/main/java/org/traccar/reports/common/TripsConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org)
* Copyright 2017 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,37 +16,28 @@
*/
package org.traccar.reports.common;
-import org.traccar.config.Config;
import org.traccar.config.Keys;
+import org.traccar.helper.model.AttributeUtil;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-@Singleton
public class TripsConfig {
- @Inject
- public TripsConfig(Config config) {
+ public TripsConfig(AttributeUtil.Provider attributeProvider) {
this(
- config.getLong(Keys.REPORT_TRIP_MINIMAL_TRIP_DISTANCE),
- config.getLong(Keys.REPORT_TRIP_MINIMAL_TRIP_DURATION) * 1000,
- config.getLong(Keys.REPORT_TRIP_MINIMAL_PARKING_DURATION) * 1000,
- config.getLong(Keys.REPORT_TRIP_MINIMAL_NO_DATA_DURATION) * 1000,
- config.getBoolean(Keys.REPORT_TRIP_USE_IGNITION),
- config.getBoolean(Keys.EVENT_MOTION_PROCESS_INVALID_POSITIONS),
- config.getDouble(Keys.EVENT_MOTION_SPEED_THRESHOLD));
+ AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_MINIMAL_TRIP_DISTANCE),
+ AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_MINIMAL_TRIP_DURATION) * 1000,
+ AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_MINIMAL_PARKING_DURATION) * 1000,
+ AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_MINIMAL_NO_DATA_DURATION) * 1000,
+ AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_USE_IGNITION));
}
public TripsConfig(
double minimalTripDistance, long minimalTripDuration, long minimalParkingDuration,
- long minimalNoDataDuration, boolean useIgnition, boolean processInvalidPositions, double speedThreshold) {
+ long minimalNoDataDuration, boolean useIgnition) {
this.minimalTripDistance = minimalTripDistance;
this.minimalTripDuration = minimalTripDuration;
this.minimalParkingDuration = minimalParkingDuration;
this.minimalNoDataDuration = minimalNoDataDuration;
this.useIgnition = useIgnition;
- this.processInvalidPositions = processInvalidPositions;
- this.speedThreshold = speedThreshold;
}
private final double minimalTripDistance;
@@ -79,16 +70,4 @@ public class TripsConfig {
return useIgnition;
}
- private final boolean processInvalidPositions;
-
- public boolean getProcessInvalidPositions() {
- return processInvalidPositions;
- }
-
- private final double speedThreshold;
-
- public double getSpeedThreshold() {
- return speedThreshold;
- }
-
}
diff --git a/src/main/java/org/traccar/reports/model/CombinedReportItem.java b/src/main/java/org/traccar/reports/model/CombinedReportItem.java
new file mode 100644
index 000000000..810e00916
--- /dev/null
+++ b/src/main/java/org/traccar/reports/model/CombinedReportItem.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.reports.model;
+
+import org.traccar.model.Event;
+import org.traccar.model.Position;
+
+import java.util.List;
+
+public class CombinedReportItem {
+
+ private long deviceId;
+
+ public long getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(long deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ private List<double[]> route;
+
+ public List<double[]> getRoute() {
+ return route;
+ }
+
+ public void setRoute(List<double[]> route) {
+ this.route = route;
+ }
+
+ private List<Event> events;
+
+ public List<Event> getEvents() {
+ return events;
+ }
+
+ public void setEvents(List<Event> events) {
+ this.events = events;
+ }
+
+ private List<Position> positions;
+
+ public List<Position> getPositions() {
+ return positions;
+ }
+
+ public void setPositions(List<Position> positions) {
+ this.positions = positions;
+ }
+
+}
diff --git a/src/main/java/org/traccar/reports/model/DeviceReportItem.java b/src/main/java/org/traccar/reports/model/DeviceReportItem.java
new file mode 100644
index 000000000..74cd5f32c
--- /dev/null
+++ b/src/main/java/org/traccar/reports/model/DeviceReportItem.java
@@ -0,0 +1,48 @@
+/*
+ * 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.reports.model;
+
+import org.traccar.model.Device;
+import org.traccar.model.Position;
+
+public class DeviceReportItem {
+
+ public DeviceReportItem(Device device, Position position) {
+ this.device = device;
+ this.position = position;
+ }
+
+ private Device device;
+
+ public Device getDevice() {
+ return device;
+ }
+
+ public void setDevice(Device device) {
+ this.device = device;
+ }
+
+ private Position position;
+
+ public Position getPosition() {
+ return position;
+ }
+
+ public void setPosition(Position position) {
+ this.position = position;
+ }
+
+}
diff --git a/src/main/java/org/traccar/schedule/ScheduleManager.java b/src/main/java/org/traccar/schedule/ScheduleManager.java
index e1de3b3af..742428fd8 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.
@@ -18,11 +18,11 @@ package org.traccar.schedule;
import com.google.inject.Injector;
import org.traccar.LifecycleObject;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.util.List;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
+import java.util.stream.Stream;
@Singleton
public class ScheduleManager implements LifecycleObject {
@@ -38,12 +38,15 @@ public class ScheduleManager implements LifecycleObject {
@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 000000000..78fecc0ea
--- /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/TaskDeleteTemporary.java b/src/main/java/org/traccar/schedule/TaskDeleteTemporary.java
new file mode 100644
index 000000000..0cead59fb
--- /dev/null
+++ b/src/main/java/org/traccar/schedule/TaskDeleteTemporary.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.schedule;
+
+import jakarta.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.model.User;
+import org.traccar.storage.Storage;
+import org.traccar.storage.StorageException;
+import org.traccar.storage.query.Condition;
+import org.traccar.storage.query.Request;
+
+import java.util.Date;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class TaskDeleteTemporary implements ScheduleTask {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(TaskDeleteTemporary.class);
+
+ private static final long CHECK_PERIOD_HOURS = 1;
+
+ private final Storage storage;
+
+ @Inject
+ public TaskDeleteTemporary(Storage storage) {
+ this.storage = storage;
+ }
+
+ @Override
+ public void schedule(ScheduledExecutorService executor) {
+ executor.scheduleAtFixedRate(this, CHECK_PERIOD_HOURS, CHECK_PERIOD_HOURS, TimeUnit.HOURS);
+ }
+
+ @Override
+ public void run() {
+ try {
+ storage.removeObject(User.class, new Request(
+ new Condition.And(
+ new Condition.Equals("temporary", true),
+ new Condition.Compare("expirationTime", "<", "time", new Date()))));
+ } catch (StorageException e) {
+ LOGGER.warn("Failed to delete temporary users", e);
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/schedule/TaskDeviceInactivityCheck.java b/src/main/java/org/traccar/schedule/TaskDeviceInactivityCheck.java
index 57c64dc5b..8e45568d5 100644
--- a/src/main/java/org/traccar/schedule/TaskDeviceInactivityCheck.java
+++ b/src/main/java/org/traccar/schedule/TaskDeviceInactivityCheck.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2020 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,17 +20,19 @@ import org.slf4j.LoggerFactory;
import org.traccar.database.NotificationManager;
import org.traccar.model.Device;
import org.traccar.model.Event;
+import org.traccar.model.Group;
import org.traccar.model.Position;
import org.traccar.storage.Storage;
import org.traccar.storage.StorageException;
import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
public class TaskDeviceInactivityCheck implements ScheduleTask {
@@ -64,22 +66,45 @@ public class TaskDeviceInactivityCheck implements ScheduleTask {
Map<Event, Position> events = new HashMap<>();
try {
+ Map<Long, Group> groups = storage.getObjects(Group.class, new Request(new Columns.All()))
+ .stream().collect(Collectors.toMap(Group::getId, group -> group));
for (Device device : storage.getObjects(Device.class, new Request(new Columns.All()))) {
- if (device.getLastUpdate() != null && checkDevice(device, currentTime, checkPeriod)) {
+ if (device.getLastUpdate() != null && checkDevice(device, groups, currentTime, checkPeriod)) {
Event event = new Event(Event.TYPE_DEVICE_INACTIVE, device.getId());
event.set(ATTRIBUTE_LAST_UPDATE, device.getLastUpdate().getTime());
events.put(event, null);
}
}
} catch (StorageException e) {
- LOGGER.warn("Get devices error", e);
+ LOGGER.warn("Database error", e);
}
notificationManager.updateEvents(events);
}
- private boolean checkDevice(Device device, long currentTime, long checkPeriod) {
- long deviceInactivityStart = device.getLong(ATTRIBUTE_DEVICE_INACTIVITY_START);
+ private long getAttribute(Device device, Map<Long, Group> groups, String key) {
+ long deviceValue = device.getLong(key);
+ if (deviceValue > 0) {
+ return deviceValue;
+ } else {
+ long groupId = device.getGroupId();
+ while (groupId > 0) {
+ Group group = groups.get(groupId);
+ if (group == null) {
+ return 0;
+ }
+ long groupValue = group.getLong(key);
+ if (groupValue > 0) {
+ return groupValue;
+ }
+ groupId = group.getGroupId();
+ }
+ return 0;
+ }
+ }
+
+ private boolean checkDevice(Device device, Map<Long, Group> groups, long currentTime, long checkPeriod) {
+ long deviceInactivityStart = getAttribute(device, groups, ATTRIBUTE_DEVICE_INACTIVITY_START);
if (deviceInactivityStart > 0) {
long timeThreshold = device.getLastUpdate().getTime() + deviceInactivityStart;
if (currentTime >= timeThreshold) {
@@ -88,7 +113,7 @@ public class TaskDeviceInactivityCheck implements ScheduleTask {
return true;
}
- long deviceInactivityPeriod = device.getLong(ATTRIBUTE_DEVICE_INACTIVITY_PERIOD);
+ long deviceInactivityPeriod = getAttribute(device, groups, ATTRIBUTE_DEVICE_INACTIVITY_PERIOD);
if (deviceInactivityPeriod > 0) {
long count = (currentTime - timeThreshold - 1) / deviceInactivityPeriod;
timeThreshold += count * deviceInactivityPeriod;
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 000000000..94f855c5f
--- /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/src/main/java/org/traccar/schedule/TaskHealthCheck.java b/src/main/java/org/traccar/schedule/TaskHealthCheck.java
index a8c9873ce..a60935f18 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.
@@ -22,8 +22,8 @@ import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import javax.inject.Inject;
-import javax.ws.rs.client.Client;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.client.Client;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -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 class TaskHealthCheck implements ScheduleTask {
@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);
}
}
diff --git a/src/main/java/org/traccar/schedule/TaskReports.java b/src/main/java/org/traccar/schedule/TaskReports.java
index 004a6078c..e0fa6f8d6 100644
--- a/src/main/java/org/traccar/schedule/TaskReports.java
+++ b/src/main/java/org/traccar/schedule/TaskReports.java
@@ -18,7 +18,7 @@ package org.traccar.schedule;
import com.google.inject.Injector;
import com.google.inject.servlet.RequestScoper;
import com.google.inject.servlet.ServletScopes;
-import net.fortuna.ical4j.model.component.VEvent;
+import net.fortuna.ical4j.model.Period;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.model.BaseModel;
@@ -39,7 +39,7 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -51,7 +51,7 @@ public class TaskReports implements ScheduleTask {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskReports.class);
- private static final long CHECK_PERIOD_MINUTES = 1;
+ private static final long CHECK_PERIOD_MINUTES = 15;
private final Storage storage;
private final Injector injector;
@@ -77,16 +77,14 @@ public class TaskReports implements ScheduleTask {
Calendar calendar = storage.getObject(Calendar.class, new Request(
new Columns.All(), new Condition.Equals("id", report.getCalendarId())));
- var lastEvents = calendar.findEvents(lastCheck);
- var currentEvents = calendar.findEvents(currentCheck);
+ var lastEvents = calendar.findPeriods(lastCheck);
+ var currentEvents = calendar.findPeriods(currentCheck);
if (!lastEvents.isEmpty() && currentEvents.isEmpty()) {
- VEvent event = lastEvents.iterator().next();
- Date from = event.getStartDate().getDate();
- Date to = event.getEndDate().getDate();
+ Period period = lastEvents.iterator().next();
RequestScoper scope = ServletScopes.scopeRequest(Collections.emptyMap());
try (RequestScoper.CloseableScope ignored = scope.open()) {
- executeReport(report, from, to);
+ executeReport(report, period.getStart(), period.getEnd());
}
}
}
diff --git a/src/main/java/org/traccar/schedule/TaskWebSocketKeepalive.java b/src/main/java/org/traccar/schedule/TaskWebSocketKeepalive.java
index e6c2e8b6d..d9e0c6f0b 100644
--- a/src/main/java/org/traccar/schedule/TaskWebSocketKeepalive.java
+++ b/src/main/java/org/traccar/schedule/TaskWebSocketKeepalive.java
@@ -17,7 +17,7 @@ package org.traccar.schedule;
import org.traccar.session.ConnectionManager;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
diff --git a/src/main/java/org/traccar/session/ConnectionManager.java b/src/main/java/org/traccar/session/ConnectionManager.java
index 37a42d827..42dcf5ce9 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 - 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.
@@ -30,6 +30,7 @@ import org.traccar.database.NotificationManager;
import org.traccar.model.BaseModel;
import org.traccar.model.Device;
import org.traccar.model.Event;
+import org.traccar.model.LogRecord;
import org.traccar.model.Position;
import org.traccar.model.User;
import org.traccar.session.cache.CacheManager;
@@ -39,8 +40,8 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Arrays;
@@ -62,9 +63,11 @@ 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<Long, DeviceSession> sessionsByDeviceId = new ConcurrentHashMap<>();
- private final Map<Endpoint, Map<String, DeviceSession>> sessionsByEndpoint = new ConcurrentHashMap<>();
+ private final Map<SocketAddress, Map<String, DeviceSession>> sessionsByEndpoint = new ConcurrentHashMap<>();
+ private final Map<SocketAddress, String> unknownByEndpoint = new ConcurrentHashMap<>();
private final Config config;
private final CacheManager cacheManager;
@@ -93,6 +96,7 @@ public class ConnectionManager implements BroadcastInterface {
this.broadcastService = broadcastService;
this.deviceLookupService = deviceLookupService;
deviceTimeout = config.getLong(Keys.STATUS_TIMEOUT);
+ showUnknownDevices = config.getBoolean(Keys.WEB_SHOW_UNKNOWN_DEVICES);
broadcastService.registerListener(this);
}
@@ -102,11 +106,10 @@ public class ConnectionManager implements BroadcastInterface {
public DeviceSession getDeviceSession(
Protocol protocol, Channel channel, SocketAddress remoteAddress,
- String... uniqueIds) throws StorageException {
+ String... uniqueIds) throws Exception {
- Endpoint endpoint = new Endpoint(channel, remoteAddress);
Map<String, DeviceSession> endpointSessions = sessionsByEndpoint.getOrDefault(
- endpoint, new ConcurrentHashMap<>());
+ remoteAddress, new ConcurrentHashMap<>());
uniqueIds = Arrays.stream(uniqueIds).filter(Objects::nonNull).toArray(String[]::new);
if (uniqueIds.length > 0) {
@@ -122,28 +125,31 @@ public class ConnectionManager implements BroadcastInterface {
Device device = deviceLookupService.lookup(uniqueIds);
+ String firstUniqueId = uniqueIds[0];
if (device == null && config.getBoolean(Keys.DATABASE_REGISTER_UNKNOWN)) {
- device = addUnknownDevice(uniqueIds[0]);
+ if (firstUniqueId.matches(config.getString(Keys.DATABASE_REGISTER_UNKNOWN_REGEX))) {
+ device = addUnknownDevice(firstUniqueId);
+ }
}
if (device != null) {
+ unknownByEndpoint.remove(remoteAddress);
device.checkDisabled();
DeviceSession oldSession = sessionsByDeviceId.remove(device.getId());
if (oldSession != null) {
- Endpoint oldEndpoint = new Endpoint(oldSession.getChannel(), oldSession.getRemoteAddress());
- Map<String, DeviceSession> oldEndpointSessions = sessionsByEndpoint.get(oldEndpoint);
+ Map<String, DeviceSession> oldEndpointSessions = sessionsByEndpoint.get(oldSession.getRemoteAddress());
if (oldEndpointSessions != null && oldEndpointSessions.size() > 1) {
oldEndpointSessions.remove(device.getUniqueId());
} else {
- sessionsByEndpoint.remove(oldEndpoint);
+ sessionsByEndpoint.remove(oldSession.getRemoteAddress());
}
}
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(endpoint, endpointSessions);
+ sessionsByEndpoint.put(remoteAddress, endpointSessions);
sessionsByDeviceId.put(device.getId(), deviceSession);
if (oldSession == null) {
@@ -152,6 +158,7 @@ public class ConnectionManager implements BroadcastInterface {
return deviceSession;
} else {
+ unknownByEndpoint.put(remoteAddress, firstUniqueId);
LOGGER.warn("Unknown device - " + String.join(" ", uniqueIds)
+ " (" + ((InetSocketAddress) remoteAddress).getHostString() + ")");
return null;
@@ -180,16 +187,19 @@ public class ConnectionManager implements BroadcastInterface {
}
public void deviceDisconnected(Channel channel, boolean supportsOffline) {
- Endpoint endpoint = new Endpoint(channel, channel.remoteAddress());
- Map<String, DeviceSession> endpointSessions = sessionsByEndpoint.remove(endpoint);
- if (endpointSessions != null) {
- for (DeviceSession deviceSession : endpointSessions.values()) {
- if (supportsOffline) {
- updateDevice(deviceSession.getDeviceId(), Device.STATUS_OFFLINE, null);
+ SocketAddress remoteAddress = channel.remoteAddress();
+ if (remoteAddress != null) {
+ Map<String, DeviceSession> 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);
}
}
@@ -202,8 +212,7 @@ public class ConnectionManager implements BroadcastInterface {
DeviceSession deviceSession = sessionsByDeviceId.remove(deviceId);
if (deviceSession != null) {
cacheManager.removeDevice(deviceId);
- Endpoint endpoint = new Endpoint(deviceSession.getChannel(), deviceSession.getRemoteAddress());
- sessionsByEndpoint.computeIfPresent(endpoint, (e, sessions) -> {
+ sessionsByEndpoint.computeIfPresent(deviceSession.getRemoteAddress(), (e, sessions) -> {
sessions.remove(deviceSession.getUniqueId());
return sessions.isEmpty() ? null : sessions;
});
@@ -325,11 +334,9 @@ public class ConnectionManager implements BroadcastInterface {
}
@Override
- public synchronized void invalidatePermission(
- boolean local,
- Class<? extends BaseModel> clazz1, long id1,
- Class<? extends BaseModel> clazz2, long id2) {
- if (clazz1.equals(User.class) && clazz2.equals(Device.class)) {
+ public synchronized <T1 extends BaseModel, T2 extends BaseModel> void invalidatePermission(
+ boolean local, Class<T1> clazz1, long id1, Class<T2> clazz2, long id2, boolean link) {
+ if (link && clazz1.equals(User.class) && clazz2.equals(Device.class)) {
if (listeners.containsKey(id1)) {
userDevices.get(id1).add(id2);
deviceUsers.put(id2, new HashSet<>(List.of(id1)));
@@ -337,11 +344,34 @@ public class ConnectionManager implements BroadcastInterface {
}
}
+ 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 && showUnknownDevices) {
+ record.setUniqueId(unknownUniqueId);
+ listeners.values().stream()
+ .flatMap(Set::stream)
+ .forEach((listener) -> listener.onUpdateLog(record));
+ }
+ } else {
+ var firstEntry = sessions.entrySet().iterator().next();
+ record.setUniqueId(firstEntry.getKey());
+ record.setDeviceId(firstEntry.getValue().getDeviceId());
+ for (long userId : deviceUsers.getOrDefault(record.getDeviceId(), Set.of())) {
+ for (UpdateListener listener : listeners.getOrDefault(userId, Set.of())) {
+ listener.onUpdateLog(record);
+ }
+ }
+ }
+ }
+
public interface UpdateListener {
void onKeepalive();
void onUpdateDevice(Device device);
void onUpdatePosition(Position position);
void onUpdateEvent(Event event);
+ void onUpdateLog(LogRecord record);
}
public synchronized void addListener(long userId, UpdateListener listener) throws StorageException {
diff --git a/src/main/java/org/traccar/session/DeviceSession.java b/src/main/java/org/traccar/session/DeviceSession.java
index 009f90f5a..f124ca7f9 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 class DeviceSession {
return uniqueId;
}
+ public String getModel() {
+ return model;
+ }
+
public Channel getChannel() {
return channel;
}
diff --git a/src/main/java/org/traccar/session/Endpoint.java b/src/main/java/org/traccar/session/Endpoint.java
deleted file mode 100644
index 76aac3444..000000000
--- a/src/main/java/org/traccar/session/Endpoint.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.traccar.session;
-
-import io.netty.channel.Channel;
-
-import java.net.SocketAddress;
-import java.util.Objects;
-
-public class Endpoint {
-
- private final Channel channel;
- private final SocketAddress remoteAddress;
-
- public Endpoint(Channel channel, SocketAddress remoteAddress) {
- this.channel = channel;
- this.remoteAddress = remoteAddress;
- }
-
- public Channel getChannel() {
- return channel;
- }
-
- public SocketAddress getRemoteAddress() {
- return remoteAddress;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- Endpoint endpoint = (Endpoint) o;
- return channel.equals(endpoint.channel) && remoteAddress.equals(endpoint.remoteAddress);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(channel, remoteAddress);
- }
-
-}
diff --git a/src/main/java/org/traccar/session/cache/CacheGraph.java b/src/main/java/org/traccar/session/cache/CacheGraph.java
new file mode 100644
index 000000000..c99997288
--- /dev/null
+++ b/src/main/java/org/traccar/session/cache/CacheGraph.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.session.cache;
+
+import org.traccar.model.BaseModel;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+public class CacheGraph {
+
+ private final Map<CacheKey, CacheNode> roots = new HashMap<>();
+ private final WeakValueMap<CacheKey, CacheNode> nodes = new WeakValueMap<>();
+
+ void addObject(BaseModel value) {
+ CacheKey key = new CacheKey(value);
+ CacheNode node = new CacheNode(value);
+ roots.put(key, node);
+ nodes.put(key, node);
+ }
+
+ void removeObject(Class<? extends BaseModel> clazz, long id) {
+ CacheKey key = new CacheKey(clazz, id);
+ CacheNode node = nodes.remove(key);
+ if (node != null) {
+ node.getAllLinks(false).forEach(child -> child.getLinks(key.getClazz(), true).remove(node));
+ }
+ roots.remove(key);
+ }
+
+ @SuppressWarnings("unchecked")
+ <T extends BaseModel> T getObject(Class<T> clazz, long id) {
+ CacheNode node = nodes.get(new CacheKey(clazz, id));
+ return node != null ? (T) node.getValue() : null;
+ }
+
+ <T extends BaseModel> Stream<T> getObjects(
+ Class<? extends BaseModel> fromClass, long fromId,
+ Class<T> clazz, Set<Class<? extends BaseModel>> proxies, boolean forward) {
+
+ CacheNode rootNode = nodes.get(new CacheKey(fromClass, fromId));
+ if (rootNode != null) {
+ return getObjectStream(rootNode, clazz, proxies, forward);
+ } else {
+ return Stream.empty();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T extends BaseModel> Stream<T> getObjectStream(
+ CacheNode rootNode, Class<T> clazz, Set<Class<? extends BaseModel>> proxies, boolean forward) {
+
+ if (proxies.contains(clazz)) {
+ return Stream.empty();
+ }
+
+ var directSteam = rootNode.getLinks(clazz, forward).stream()
+ .map(node -> (T) node.getValue());
+
+ var proxyStream = proxies.stream()
+ .flatMap(proxyClass -> rootNode.getLinks(proxyClass, forward).stream()
+ .flatMap(node -> getObjectStream(node, clazz, proxies, forward)));
+
+ return Stream.concat(directSteam, proxyStream);
+ }
+
+ void updateObject(BaseModel value) {
+ CacheNode node = nodes.get(new CacheKey(value));
+ if (node != null) {
+ node.setValue(value);
+ }
+ }
+
+ boolean addLink(
+ Class<? extends BaseModel> fromClazz, long fromId,
+ BaseModel toValue) {
+ boolean stop = true;
+ CacheNode fromNode = nodes.get(new CacheKey(fromClazz, fromId));
+ if (fromNode != null) {
+ CacheKey toKey = new CacheKey(toValue);
+ CacheNode toNode = nodes.get(toKey);
+ if (toNode == null) {
+ stop = false;
+ toNode = new CacheNode(toValue);
+ nodes.put(toKey, toNode);
+ }
+ fromNode.getLinks(toValue.getClass(), true).add(toNode);
+ toNode.getLinks(fromClazz, false).add(fromNode);
+ }
+ return stop;
+ }
+
+ void removeLink(
+ Class<? extends BaseModel> fromClazz, long fromId,
+ Class<? extends BaseModel> toClazz, long toId) {
+ CacheNode fromNode = nodes.get(new CacheKey(fromClazz, fromId));
+ if (fromNode != null) {
+ CacheNode toNode = nodes.get(new CacheKey(toClazz, toId));
+ if (toNode != null) {
+ fromNode.getLinks(toClazz, true).remove(toNode);
+ toNode.getLinks(fromClazz, false).remove(fromNode);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (CacheNode node : roots.values()) {
+ printNode(stringBuilder, node, "");
+ }
+ return stringBuilder.toString().trim();
+ }
+
+ private void printNode(StringBuilder stringBuilder, CacheNode node, String indentation) {
+ stringBuilder
+ .append('\n')
+ .append(indentation)
+ .append(node.getValue().getClass().getSimpleName())
+ .append('(').append(node.getValue().getId()).append(')');
+ node.getAllLinks(true).forEach(child -> printNode(stringBuilder, child, indentation + " "));
+ }
+
+}
diff --git a/src/main/java/org/traccar/session/cache/CacheKey.java b/src/main/java/org/traccar/session/cache/CacheKey.java
index 23145e34b..f27d5fbf5 100644
--- a/src/main/java/org/traccar/session/cache/CacheKey.java
+++ b/src/main/java/org/traccar/session/cache/CacheKey.java
@@ -33,6 +33,10 @@ class CacheKey {
this.id = id;
}
+ public Class<? extends BaseModel> getClazz() {
+ return clazz;
+ }
+
public boolean classIs(Class<? extends BaseModel> clazz) {
return clazz.equals(this.clazz);
}
diff --git a/src/main/java/org/traccar/session/cache/CacheManager.java b/src/main/java/org/traccar/session/cache/CacheManager.java
index 9d2350012..064e5672f 100644
--- a/src/main/java/org/traccar/session/cache/CacheManager.java
+++ b/src/main/java/org/traccar/session/cache/CacheManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,8 @@
*/
package org.traccar.session.cache;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import org.traccar.broadcast.BroadcastInterface;
import org.traccar.broadcast.BroadcastService;
import org.traccar.config.Config;
@@ -30,8 +30,10 @@ import org.traccar.model.Group;
import org.traccar.model.GroupedModel;
import org.traccar.model.Maintenance;
import org.traccar.model.Notification;
+import org.traccar.model.ObjectOperation;
+import org.traccar.model.Permission;
import org.traccar.model.Position;
-import org.traccar.model.ScheduledModel;
+import org.traccar.model.Schedulable;
import org.traccar.model.Server;
import org.traccar.model.User;
import org.traccar.storage.Storage;
@@ -40,19 +42,10 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
@@ -60,10 +53,8 @@ import java.util.stream.Collectors;
@Singleton
public class CacheManager implements BroadcastInterface {
- private static final Logger LOGGER = LoggerFactory.getLogger(CacheManager.class);
- private static final int GROUP_DEPTH_LIMIT = 3;
- private static final Collection<Class<? extends BaseModel>> CLASSES = Arrays.asList(
- Attribute.class, Driver.class, Geofence.class, Maintenance.class, Notification.class);
+ private static final Set<Class<? extends BaseModel>> GROUPED_CLASSES =
+ Set.of(Attribute.class, Driver.class, Geofence.class, Maintenance.class, Notification.class);
private final Config config;
private final Storage storage;
@@ -71,24 +62,26 @@ public class CacheManager implements BroadcastInterface {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
- private final Map<CacheKey, CacheValue> deviceCache = new HashMap<>();
- private final Map<Long, Integer> deviceReferences = new HashMap<>();
- private final Map<Long, Map<Class<? extends BaseModel>, Set<Long>>> deviceLinks = new HashMap<>();
- private final Map<Long, Position> devicePositions = new HashMap<>();
+ private final CacheGraph graph = new CacheGraph();
private Server server;
- private final Map<Long, List<User>> notificationUsers = new HashMap<>();
+ private final Map<Long, Position> devicePositions = new HashMap<>();
+ private final Map<Long, AtomicInteger> deviceReferences = new HashMap<>();
@Inject
public CacheManager(Config config, Storage storage, BroadcastService broadcastService) throws StorageException {
this.config = config;
this.storage = storage;
this.broadcastService = broadcastService;
- invalidateServer();
- invalidateUsers();
+ server = storage.getObject(Server.class, new Request(new Columns.All()));
broadcastService.registerListener(this);
}
+ @Override
+ public String toString() {
+ return graph.toString();
+ }
+
public Config getConfig() {
return config;
}
@@ -96,29 +89,17 @@ public class CacheManager implements BroadcastInterface {
public <T extends BaseModel> T getObject(Class<T> clazz, long id) {
try {
lock.readLock().lock();
- var cacheValue = deviceCache.get(new CacheKey(clazz, id));
- return cacheValue != null ? cacheValue.getValue() : null;
+ return graph.getObject(clazz, id);
} finally {
lock.readLock().unlock();
}
}
- public <T extends BaseModel> List<T> getDeviceObjects(long deviceId, Class<T> clazz) {
+ public <T extends BaseModel> Set<T> getDeviceObjects(long deviceId, Class<T> clazz) {
try {
lock.readLock().lock();
- var links = deviceLinks.get(deviceId);
- if (links != null) {
- return links.getOrDefault(clazz, new LinkedHashSet<>()).stream()
- .map(id -> {
- var cacheValue = deviceCache.get(new CacheKey(clazz, id));
- return cacheValue != null ? cacheValue.<T>getValue() : null;
- })
- .filter(Objects::nonNull)
- .collect(Collectors.toList());
- } else {
- LOGGER.warn("Device {} cache missing", deviceId);
- return Collections.emptyList();
- }
+ return graph.getObjects(Device.class, deviceId, clazz, Set.of(Group.class), true)
+ .collect(Collectors.toUnmodifiableSet());
} finally {
lock.readLock().unlock();
}
@@ -142,37 +123,45 @@ public class CacheManager implements BroadcastInterface {
}
}
- public List<User> getNotificationUsers(long notificationId, long deviceId) {
+ public Set<User> getNotificationUsers(long notificationId, long deviceId) {
try {
lock.readLock().lock();
- var users = deviceLinks.get(deviceId).get(User.class).stream()
+ Set<User> deviceUsers = getDeviceObjects(deviceId, User.class);
+ return graph.getObjects(Notification.class, notificationId, User.class, Set.of(), false)
+ .filter(deviceUsers::contains)
.collect(Collectors.toUnmodifiableSet());
- return notificationUsers.getOrDefault(notificationId, new LinkedList<>()).stream()
- .filter(user -> users.contains(user.getId()))
- .collect(Collectors.toUnmodifiableList());
} finally {
lock.readLock().unlock();
}
}
- public Driver findDriverByUniqueId(long deviceId, String driverUniqueId) {
- return getDeviceObjects(deviceId, Driver.class).stream()
- .filter(driver -> driver.getUniqueId().equals(driverUniqueId))
- .findFirst()
- .orElse(null);
+ public Set<Notification> 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()))
+ .collect(Collectors.toUnmodifiableSet());
+ } finally {
+ lock.readLock().unlock();
+ }
}
- public void addDevice(long deviceId) throws StorageException {
+ public void addDevice(long deviceId) throws Exception {
try {
lock.writeLock().lock();
- Integer references = deviceReferences.get(deviceId);
- if (references != null) {
- references += 1;
- } else {
- unsafeAddDevice(deviceId);
- references = 1;
+ if (deviceReferences.computeIfAbsent(deviceId, k -> new AtomicInteger()).getAndIncrement() <= 0) {
+ Device device = storage.getObject(Device.class, new Request(
+ new Columns.All(), new Condition.Equals("id", deviceId)));
+ graph.addObject(device);
+ initializeCache(device);
+ if (device.getPositionId() > 0) {
+ devicePositions.put(deviceId, storage.getObject(Position.class, new Request(
+ new Columns.All(), new Condition.Equals("id", device.getPositionId()))));
+ }
}
- deviceReferences.put(deviceId, references);
} finally {
lock.writeLock().unlock();
}
@@ -181,15 +170,10 @@ public class CacheManager implements BroadcastInterface {
public void removeDevice(long deviceId) {
try {
lock.writeLock().lock();
- Integer references = deviceReferences.get(deviceId);
- if (references != null) {
- references -= 1;
- if (references <= 0) {
- unsafeRemoveDevice(deviceId);
- deviceReferences.remove(deviceId);
- } else {
- deviceReferences.put(deviceId, references);
- }
+ if (deviceReferences.computeIfAbsent(deviceId, k -> new AtomicInteger()).incrementAndGet() <= 0) {
+ graph.removeObject(Device.class, deviceId);
+ devicePositions.remove(deviceId);
+ deviceReferences.remove(deviceId);
}
} finally {
lock.writeLock().unlock();
@@ -199,7 +183,7 @@ public class CacheManager implements BroadcastInterface {
public void updatePosition(Position position) {
try {
lock.writeLock().lock();
- if (deviceLinks.containsKey(position.getDeviceId())) {
+ if (deviceReferences.containsKey(position.getDeviceId())) {
devicePositions.put(position.getDeviceId(), position);
}
} finally {
@@ -208,218 +192,140 @@ public class CacheManager implements BroadcastInterface {
}
@Override
- public void invalidateObject(boolean local, Class<? extends BaseModel> clazz, long id) {
- try {
- var object = storage.getObject(clazz, new Request(
- new Columns.All(), new Condition.Equals("id", id)));
- if (object != null) {
- updateOrInvalidate(local, object);
- } else {
- invalidate(clazz, id);
- }
- } catch (StorageException e) {
- throw new RuntimeException(e);
- }
- }
-
- public <T extends BaseModel> void updateOrInvalidate(boolean local, T object) throws StorageException {
+ public <T extends BaseModel> void invalidateObject(
+ boolean local, Class<T> clazz, long id, ObjectOperation operation) throws Exception {
if (local) {
- broadcastService.invalidateObject(true, object.getClass(), object.getId());
+ broadcastService.invalidateObject(true, clazz, id, operation);
}
- if (object instanceof Server) {
- invalidateServer();
+ if (operation == ObjectOperation.DELETE) {
+ graph.removeObject(clazz, id);
+ }
+ if (operation != ObjectOperation.UPDATE) {
return;
}
- if (object instanceof User) {
- invalidateUsers();
+
+ if (clazz.equals(Server.class)) {
+ server = storage.getObject(Server.class, new Request(new Columns.All()));
return;
}
- boolean invalidate = false;
- var before = getObject(object.getClass(), object.getId());
+ var after = storage.getObject(clazz, new Request(new Columns.All(), new Condition.Equals("id", id)));
+ if (after == null) {
+ return;
+ }
+ var before = getObject(after.getClass(), after.getId());
if (before == null) {
return;
- } else if (object instanceof GroupedModel) {
- if (((GroupedModel) before).getGroupId() != ((GroupedModel) object).getGroupId()) {
- invalidate = true;
- }
- } else if (object instanceof ScheduledModel) {
- if (((ScheduledModel) before).getCalendarId() != ((ScheduledModel) object).getCalendarId()) {
- invalidate = true;
- }
}
- if (invalidate) {
- invalidate(object.getClass(), object.getId());
- } else {
- try {
- lock.writeLock().lock();
- deviceCache.get(new CacheKey(object.getClass(), object.getId())).setValue(object);
- } finally {
- lock.writeLock().unlock();
+
+ if (after instanceof GroupedModel) {
+ long beforeGroupId = ((GroupedModel) before).getGroupId();
+ long afterGroupId = ((GroupedModel) after).getGroupId();
+ if (beforeGroupId != afterGroupId) {
+ if (beforeGroupId > 0) {
+ invalidatePermission(clazz, id, Group.class, beforeGroupId, false);
+ }
+ if (afterGroupId > 0) {
+ invalidatePermission(clazz, id, Group.class, afterGroupId, true);
+ }
}
+ } else if (after instanceof Schedulable) {
+ long beforeCalendarId = ((Schedulable) before).getCalendarId();
+ long afterCalendarId = ((Schedulable) after).getCalendarId();
+ if (beforeCalendarId != afterCalendarId) {
+ if (beforeCalendarId > 0) {
+ invalidatePermission(clazz, id, Calendar.class, beforeCalendarId, false);
+ }
+ if (afterCalendarId > 0) {
+ invalidatePermission(clazz, id, Calendar.class, afterCalendarId, true);
+ }
+ }
+ // TODO handle notification always change
}
- }
- public <T extends BaseModel> void invalidate(Class<T> clazz, long id) throws StorageException {
- invalidate(new CacheKey(clazz, id));
+ graph.updateObject(after);
}
@Override
- public void invalidatePermission(
- boolean local,
- Class<? extends BaseModel> clazz1, long id1,
- Class<? extends BaseModel> clazz2, long id2) {
+ public <T1 extends BaseModel, T2 extends BaseModel> void invalidatePermission(
+ boolean local, Class<T1> clazz1, long id1, Class<T2> clazz2, long id2, boolean link) throws Exception {
if (local) {
- broadcastService.invalidatePermission(true, clazz1, id1, clazz2, id2);
+ broadcastService.invalidatePermission(true, clazz1, id1, clazz2, id2, link);
}
- try {
- invalidate(new CacheKey(clazz1, id1), new CacheKey(clazz2, id2));
- } catch (StorageException e) {
- throw new RuntimeException(e);
+ if (clazz1.equals(User.class) && GroupedModel.class.isAssignableFrom(clazz2)) {
+ invalidatePermission(clazz2, id2, clazz1, id1, link);
+ } else {
+ invalidatePermission(clazz1, id1, clazz2, id2, link);
}
}
- private void invalidateServer() throws StorageException {
- server = storage.getObject(Server.class, new Request(new Columns.All()));
- }
+ private <T1 extends BaseModel, T2 extends BaseModel> void invalidatePermission(
+ Class<T1> fromClass, long fromId, Class<T2> toClass, long toId, boolean link) throws Exception {
- private void invalidateUsers() throws StorageException {
- notificationUsers.clear();
- Map<Long, User> users = new HashMap<>();
- storage.getObjects(User.class, new Request(new Columns.All()))
- .forEach(user -> users.put(user.getId(), user));
- storage.getPermissions(User.class, Notification.class).forEach(permission -> {
- long notificationId = permission.getPropertyId();
- var user = users.get(permission.getOwnerId());
- notificationUsers.computeIfAbsent(notificationId, k -> new LinkedList<>()).add(user);
- });
- }
+ boolean groupLink = GroupedModel.class.isAssignableFrom(fromClass) && toClass.equals(Group.class);
+ boolean calendarLink = Schedulable.class.isAssignableFrom(fromClass) && toClass.equals(Calendar.class);
+ boolean userLink = fromClass.equals(User.class) && toClass.equals(Notification.class);
- private void addObject(long deviceId, BaseModel object) {
- deviceCache.computeIfAbsent(new CacheKey(object), k -> new CacheValue(object)).retain(deviceId);
- }
+ boolean groupedLinks = GroupedModel.class.isAssignableFrom(fromClass)
+ && (GROUPED_CLASSES.contains(toClass) || toClass.equals(User.class));
+
+ if (!groupLink && !calendarLink && !userLink && !groupedLinks) {
+ return;
+ }
- private void unsafeAddDevice(long deviceId) throws StorageException {
- Map<Class<? extends BaseModel>, Set<Long>> links = new HashMap<>();
-
- Device device = storage.getObject(Device.class, new Request(
- new Columns.All(), new Condition.Equals("id", deviceId)));
- if (device != null) {
- addObject(deviceId, device);
-
- int groupDepth = 0;
- long groupId = device.getGroupId();
- while (groupDepth < GROUP_DEPTH_LIMIT && groupId > 0) {
- Group group = storage.getObject(Group.class, new Request(
- new Columns.All(), new Condition.Equals("id", groupId)));
- links.computeIfAbsent(Group.class, k -> new LinkedHashSet<>()).add(group.getId());
- addObject(deviceId, group);
- groupId = group.getGroupId();
- groupDepth += 1;
+ if (link) {
+ BaseModel object = storage.getObject(toClass, new Request(
+ new Columns.All(), new Condition.Equals("id", toId)));
+ if (!graph.addLink(fromClass, fromId, object)) {
+ initializeCache(object);
}
+ } else {
+ graph.removeLink(fromClass, fromId, toClass, toId);
+ }
+ }
- for (Class<? extends BaseModel> clazz : CLASSES) {
- var objects = storage.getObjects(clazz, new Request(
- new Columns.All(), new Condition.Permission(Device.class, deviceId, clazz)));
- links.put(clazz, objects.stream().map(BaseModel::getId).collect(Collectors.toSet()));
- for (var object : objects) {
- addObject(deviceId, object);
- if (object instanceof ScheduledModel) {
- var scheduled = (ScheduledModel) object;
- if (scheduled.getCalendarId() > 0) {
- var calendar = storage.getObject(Calendar.class, new Request(
- new Columns.All(), new Condition.Equals("id", scheduled.getCalendarId())));
- links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>())
- .add(calendar.getId());
- addObject(deviceId, calendar);
- }
- }
+ private void initializeCache(BaseModel object) throws Exception {
+ if (object instanceof User) {
+ for (Permission permission : storage.getPermissions(User.class, Notification.class)) {
+ if (permission.getOwnerId() == object.getId()) {
+ invalidatePermission(
+ permission.getOwnerClass(), permission.getOwnerId(),
+ permission.getPropertyClass(), permission.getPropertyId(), true);
}
}
+ } else {
+ if (object instanceof GroupedModel) {
+ long groupId = ((GroupedModel) object).getGroupId();
+ if (groupId > 0) {
+ invalidatePermission(object.getClass(), object.getId(), Group.class, groupId, true);
+ }
- var users = storage.getObjects(User.class, new Request(
- new Columns.All(), new Condition.Permission(User.class, Device.class, deviceId)));
- links.put(User.class, users.stream().map(BaseModel::getId).collect(Collectors.toSet()));
- for (var user : users) {
- addObject(deviceId, user);
- var notifications = storage.getObjects(Notification.class, new Request(
- new Columns.All(),
- new Condition.Permission(User.class, user.getId(), Notification.class))).stream()
- .filter(Notification::getAlways)
- .collect(Collectors.toList());
- for (var notification : notifications) {
- links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>())
- .add(notification.getId());
- addObject(deviceId, notification);
- if (notification.getCalendarId() > 0) {
- var calendar = storage.getObject(Calendar.class, new Request(
- new Columns.All(), new Condition.Equals("id", notification.getCalendarId())));
- links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>())
- .add(calendar.getId());
- addObject(deviceId, calendar);
+ for (Permission permission : storage.getPermissions(User.class, object.getClass())) {
+ if (permission.getPropertyId() == object.getId()) {
+ invalidatePermission(
+ object.getClass(), object.getId(), User.class, permission.getOwnerId(), true);
}
}
- }
- deviceLinks.put(deviceId, links);
-
- if (device.getPositionId() > 0) {
- devicePositions.put(deviceId, storage.getObject(Position.class, new Request(
- new Columns.All(), new Condition.Equals("id", device.getPositionId()))));
+ for (Class<? extends BaseModel> clazz : GROUPED_CLASSES) {
+ for (Permission permission : storage.getPermissions(object.getClass(), clazz)) {
+ if (permission.getOwnerId() == object.getId()) {
+ invalidatePermission(
+ object.getClass(), object.getId(), clazz, permission.getPropertyId(), true);
+ }
+ }
+ }
}
- }
- }
-
- private void unsafeRemoveDevice(long deviceId) {
- deviceCache.remove(new CacheKey(Device.class, deviceId));
- deviceLinks.remove(deviceId).forEach((clazz, ids) -> ids.forEach(id -> {
- var key = new CacheKey(clazz, id);
- deviceCache.computeIfPresent(key, (k, value) -> {
- value.release(deviceId);
- return value.getReferences().size() > 0 ? value : null;
- });
- }));
- devicePositions.remove(deviceId);
- }
-
- private void invalidate(CacheKey... keys) throws StorageException {
- try {
- lock.writeLock().lock();
- unsafeInvalidate(keys);
- } finally {
- lock.writeLock().unlock();
- }
- }
- private void unsafeInvalidate(CacheKey[] keys) throws StorageException {
- boolean invalidateServer = false;
- boolean invalidateUsers = false;
- Set<Long> linkedDevices = new HashSet<>();
- for (var key : keys) {
- if (key.classIs(Server.class)) {
- invalidateServer = true;
- } else {
- if (key.classIs(User.class) || key.classIs(Notification.class)) {
- invalidateUsers = true;
+ if (object instanceof Schedulable) {
+ long calendarId = ((Schedulable) object).getCalendarId();
+ if (calendarId > 0) {
+ invalidatePermission(object.getClass(), object.getId(), Calendar.class, calendarId, true);
}
- deviceCache.computeIfPresent(key, (k, value) -> {
- linkedDevices.addAll(value.getReferences());
- return value;
- });
}
}
- for (long deviceId : linkedDevices) {
- unsafeRemoveDevice(deviceId);
- unsafeAddDevice(deviceId);
- }
- if (invalidateServer) {
- invalidateServer();
- }
- if (invalidateUsers) {
- invalidateUsers();
- }
}
}
diff --git a/src/main/java/org/traccar/session/cache/CacheNode.java b/src/main/java/org/traccar/session/cache/CacheNode.java
new file mode 100644
index 000000000..7b584f81a
--- /dev/null
+++ b/src/main/java/org/traccar/session/cache/CacheNode.java
@@ -0,0 +1,40 @@
+package org.traccar.session.cache;
+
+import org.traccar.model.BaseModel;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+public class CacheNode {
+
+ private BaseModel value;
+
+ private final Map<Class<? extends BaseModel>, Set<CacheNode>> links = new HashMap<>();
+ private final Map<Class<? extends BaseModel>, Set<CacheNode>> backlinks = new HashMap<>();
+
+ public CacheNode(BaseModel value) {
+ this.value = value;
+ }
+
+ public BaseModel getValue() {
+ return value;
+ }
+
+ public void setValue(BaseModel value) {
+ this.value = value;
+ }
+
+ public Set<CacheNode> getLinks(Class<? extends BaseModel> clazz, boolean forward) {
+ var map = forward ? links : backlinks;
+ return map.computeIfAbsent(clazz, k -> new HashSet<>());
+ }
+
+ public Stream<CacheNode> getAllLinks(boolean forward) {
+ var map = forward ? links : backlinks;
+ return map.values().stream().flatMap(Set::stream);
+ }
+
+}
diff --git a/src/main/java/org/traccar/session/cache/CacheValue.java b/src/main/java/org/traccar/session/cache/CacheValue.java
deleted file mode 100644
index 1f0383ce5..000000000
--- a/src/main/java/org/traccar/session/cache/CacheValue.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.traccar.session.cache;
-
-import org.traccar.model.BaseModel;
-
-import java.util.HashSet;
-import java.util.Set;
-
-class CacheValue {
-
- private BaseModel value;
- private final Set<Long> references = new HashSet<>();
-
- CacheValue(BaseModel value) {
- this.value = value;
- }
-
- public void retain(long deviceId) {
- references.add(deviceId);
- }
-
- public void release(long deviceId) {
- references.remove(deviceId);
- }
-
- @SuppressWarnings("unchecked")
- public <T extends BaseModel> T getValue() {
- return (T) value;
- }
-
- public void setValue(BaseModel value) {
- this.value = value;
- }
-
- public Set<Long> getReferences() {
- return references;
- }
-
-}
diff --git a/src/main/java/org/traccar/session/cache/WeakValueMap.java b/src/main/java/org/traccar/session/cache/WeakValueMap.java
new file mode 100644
index 000000000..8323e2c30
--- /dev/null
+++ b/src/main/java/org/traccar/session/cache/WeakValueMap.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.session.cache;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+
+public class WeakValueMap<K, V> {
+
+ private final Map<K, WeakReference<V>> map = new HashMap<>();
+
+ public void put(K key, V value) {
+ map.put(key, new WeakReference<>(value));
+ }
+
+ public V get(K key) {
+ WeakReference<V> weakReference = map.get(key);
+ return (weakReference != null) ? weakReference.get() : null;
+ }
+
+ public V remove(K key) {
+ WeakReference<V> weakReference = map.remove(key);
+ return (weakReference != null) ? weakReference.get() : null;
+ }
+
+ private void clean() {
+ map.entrySet().removeIf(entry -> entry.getValue().get() == null);
+ }
+
+}
diff --git a/src/main/java/org/traccar/session/state/OverspeedProcessor.java b/src/main/java/org/traccar/session/state/OverspeedProcessor.java
index 62f6a3de2..221b51ff5 100644
--- a/src/main/java/org/traccar/session/state/OverspeedProcessor.java
+++ b/src/main/java/org/traccar/session/state/OverspeedProcessor.java
@@ -26,40 +26,46 @@ public final class OverspeedProcessor {
}
public static void updateState(
- OverspeedState state, Position position, double speedLimit, long minimalDuration, long geofenceId) {
+ OverspeedState state, Position position,
+ double speedLimit, double multiplier, long minimalDuration, long geofenceId) {
state.setEvent(null);
boolean oldState = state.getOverspeedState();
if (oldState) {
- boolean newState = position.getSpeed() > speedLimit;
+ boolean newState = position.getSpeed() > speedLimit * multiplier;
if (newState) {
- if (state.getOverspeedTime() != null) {
- long oldTime = state.getOverspeedTime().getTime();
- long newTime = position.getFixTime().getTime();
- if (newTime - oldTime > minimalDuration) {
-
- Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, position);
- event.set(ATTRIBUTE_SPEED, position.getSpeed());
- event.set(Position.KEY_SPEED_LIMIT, speedLimit);
- event.setGeofenceId(state.getOverspeedGeofenceId());
-
- state.setOverspeedTime(null);
- state.setOverspeedGeofenceId(0);
- state.setEvent(event);
-
- }
- }
+ checkEvent(state, position, speedLimit, minimalDuration);
} else {
state.setOverspeedState(false);
state.setOverspeedTime(null);
state.setOverspeedGeofenceId(0);
}
- } else if (position != null && position.getSpeed() > speedLimit) {
+ } else if (position != null && position.getSpeed() > speedLimit * multiplier) {
state.setOverspeedState(true);
state.setOverspeedTime(position.getFixTime());
state.setOverspeedGeofenceId(geofenceId);
+
+ checkEvent(state, position, speedLimit, minimalDuration);
}
}
+ private static void checkEvent(OverspeedState state, Position position, double speedLimit, long minimalDuration) {
+ if (state.getOverspeedTime() != null) {
+ long oldTime = state.getOverspeedTime().getTime();
+ long newTime = position.getFixTime().getTime();
+ if (newTime - oldTime >= minimalDuration) {
+
+ Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, position);
+ event.set(ATTRIBUTE_SPEED, position.getSpeed());
+ event.set(Position.KEY_SPEED_LIMIT, speedLimit);
+ event.setGeofenceId(state.getOverspeedGeofenceId());
+
+ state.setOverspeedTime(null);
+ state.setOverspeedGeofenceId(0);
+ state.setEvent(event);
+
+ }
+ }
+ }
}
diff --git a/src/main/java/org/traccar/sms/HttpSmsClient.java b/src/main/java/org/traccar/sms/HttpSmsClient.java
index 51b161594..a2a0dd57f 100644
--- a/src/main/java/org/traccar/sms/HttpSmsClient.java
+++ b/src/main/java/org/traccar/sms/HttpSmsClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2023 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,11 +21,11 @@ import org.traccar.config.Keys;
import org.traccar.helper.DataConverter;
import org.traccar.notification.MessageException;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.Invocation;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.Invocation;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@@ -57,7 +57,10 @@ public class HttpSmsClient implements SmsManager {
}
}
template = config.getString(Keys.SMS_HTTP_TEMPLATE).trim();
- if (template.charAt(0) == '{' || template.charAt(0) == '[') {
+ if (template.charAt(0) == '<') {
+ encode = false;
+ mediaType = MediaType.APPLICATION_XML_TYPE;
+ } else if (template.charAt(0) == '{' || template.charAt(0) == '[') {
encode = false;
mediaType = MediaType.APPLICATION_JSON_TYPE;
} else {
@@ -67,7 +70,7 @@ public class HttpSmsClient implements SmsManager {
}
private String prepareValue(String value) throws UnsupportedEncodingException {
- return encode ? URLEncoder.encode(value, StandardCharsets.UTF_8.name()) : value;
+ return encode ? URLEncoder.encode(value, StandardCharsets.UTF_8) : value;
}
private String preparePayload(String destAddress, String message) {
diff --git a/src/main/java/org/traccar/sms/SmsManager.java b/src/main/java/org/traccar/sms/SmsManager.java
index 9ab25d9cb..8cf99c9e8 100644
--- a/src/main/java/org/traccar/sms/SmsManager.java
+++ b/src/main/java/org/traccar/sms/SmsManager.java
@@ -20,7 +20,6 @@ import org.traccar.notification.MessageException;
public interface SmsManager {
- void sendMessage(
- String destAddress, String message, boolean command) throws InterruptedException, MessageException;
+ void sendMessage(String destAddress, String message, boolean command) throws MessageException;
}
diff --git a/src/main/java/org/traccar/speedlimit/OverpassSpeedLimitProvider.java b/src/main/java/org/traccar/speedlimit/OverpassSpeedLimitProvider.java
index edf089f37..a25eedb2c 100644
--- a/src/main/java/org/traccar/speedlimit/OverpassSpeedLimitProvider.java
+++ b/src/main/java/org/traccar/speedlimit/OverpassSpeedLimitProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2020 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,22 +15,25 @@
*/
package org.traccar.speedlimit;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
import org.traccar.helper.UnitsConverter;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.ws.rs.client.AsyncInvoker;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.InvocationCallback;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.client.AsyncInvoker;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.InvocationCallback;
public class OverpassSpeedLimitProvider implements SpeedLimitProvider {
private final Client client;
private final String url;
- public OverpassSpeedLimitProvider(Client client, String url) {
+ public OverpassSpeedLimitProvider(Config config, Client client, String url) {
+ int accuracy = config.getInteger(Keys.SPEED_LIMIT_ACCURACY);
this.client = client;
- this.url = url + "?data=[out:json];way[maxspeed](around:100.0,%f,%f);out%%20tags;";
+ this.url = url + "?data=[out:json];way[maxspeed](around:" + accuracy + ",%f,%f);out%%20tags;";
}
private Double parseSpeed(String value) {
diff --git a/src/main/java/org/traccar/storage/DatabaseModule.java b/src/main/java/org/traccar/storage/DatabaseModule.java
index 3e3483818..9d9e5bd5e 100644
--- a/src/main/java/org/traccar/storage/DatabaseModule.java
+++ b/src/main/java/org/traccar/storage/DatabaseModule.java
@@ -29,7 +29,7 @@ import liquibase.resource.ResourceAccessor;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import javax.inject.Singleton;
+import jakarta.inject.Singleton;
import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
diff --git a/src/main/java/org/traccar/storage/DatabaseStorage.java b/src/main/java/org/traccar/storage/DatabaseStorage.java
index a049a641c..d20429319 100644
--- a/src/main/java/org/traccar/storage/DatabaseStorage.java
+++ b/src/main/java/org/traccar/storage/DatabaseStorage.java
@@ -27,7 +27,7 @@ import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Order;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
diff --git a/src/main/java/org/traccar/storage/MemoryStorage.java b/src/main/java/org/traccar/storage/MemoryStorage.java
index f19897ff8..9b5db1209 100644
--- a/src/main/java/org/traccar/storage/MemoryStorage.java
+++ b/src/main/java/org/traccar/storage/MemoryStorage.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,35 +18,155 @@ package org.traccar.storage;
import org.traccar.model.BaseModel;
import org.traccar.model.Pair;
import org.traccar.model.Permission;
+import org.traccar.model.Server;
+import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
+import java.beans.Introspector;
+import java.lang.reflect.Method;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
public class MemoryStorage extends Storage {
+ private final Map<Class<?>, Map<Long, Object>> objects = new HashMap<>();
private final Map<Pair<Class<?>, Class<?>>, Set<Pair<Long, Long>>> permissions = new HashMap<>();
+ private final AtomicLong increment = new AtomicLong();
+
+ public MemoryStorage() {
+ Server server = new Server();
+ server.setId(1);
+ server.setRegistration(true);
+ objects.put(Server.class, Map.of(server.getId(), server));
+ }
+
@Override
public <T> List<T> getObjects(Class<T> clazz, Request request) {
- return null;
+ return objects.computeIfAbsent(clazz, key -> new HashMap<>()).values().stream()
+ .filter(object -> checkCondition(request.getCondition(), object))
+ .map(object -> (T) object)
+ .collect(Collectors.toList());
+ }
+
+ private boolean checkCondition(Condition genericCondition, Object object) {
+ if (genericCondition == null) {
+ return true;
+ }
+
+ if (genericCondition instanceof Condition.Compare) {
+
+ var condition = (Condition.Compare) genericCondition;
+ Object value = retrieveValue(object, condition.getVariable());
+ int result = ((Comparable) value).compareTo(condition.getValue());
+ switch (condition.getOperator()) {
+ case "<":
+ return result < 0;
+ case "<=":
+ return result <= 0;
+ case ">":
+ return result > 0;
+ case ">=":
+ return result >= 0;
+ case "=":
+ return result == 0;
+ default:
+ throw new RuntimeException("Unsupported comparison condition");
+ }
+
+ } else if (genericCondition instanceof Condition.Between) {
+
+ var condition = (Condition.Between) genericCondition;
+ Object fromValue = retrieveValue(object, condition.getFromVariable());
+ int fromResult = ((Comparable) fromValue).compareTo(condition.getFromValue());
+ Object toValue = retrieveValue(object, condition.getToVariable());
+ int toResult = ((Comparable) toValue).compareTo(condition.getToValue());
+ return fromResult >= 0 && toResult <= 0;
+
+ } else if (genericCondition instanceof Condition.Binary) {
+
+ var condition = (Condition.Binary) genericCondition;
+ if (condition.getOperator().equals("AND")) {
+ return checkCondition(condition.getFirst(), object) && checkCondition(condition.getSecond(), object);
+ } else if (condition.getOperator().equals("OR")) {
+ return checkCondition(condition.getFirst(), object) || checkCondition(condition.getSecond(), object);
+ }
+
+ } else if (genericCondition instanceof Condition.Permission) {
+
+ var condition = (Condition.Permission) genericCondition;
+ long id = (Long) retrieveValue(object, "id");
+ return getPermissionsSet(condition.getOwnerClass(), condition.getPropertyClass()).stream()
+ .anyMatch(pair -> {
+ if (condition.getOwnerId() > 0) {
+ return pair.getFirst() == condition.getOwnerId() && pair.getSecond() == id;
+ } else {
+ return pair.getFirst() == id && pair.getSecond() == condition.getPropertyId();
+ }
+ });
+
+ } else if (genericCondition instanceof Condition.LatestPositions) {
+
+ return false;
+
+ }
+
+ return false;
+ }
+
+ private Object retrieveValue(Object object, String key) {
+ try {
+ Method method = object.getClass().getMethod(
+ "get" + Character.toUpperCase(key.charAt(0)) + key.substring(1));
+ return method.invoke(object);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
}
@Override
public <T> long addObject(T entity, Request request) {
- return 0;
+ long id = increment.incrementAndGet();
+ objects.computeIfAbsent(entity.getClass(), key -> new HashMap<>()).put(id, entity);
+ return id;
}
@Override
public <T> void updateObject(T entity, Request request) {
+ Set<String> columns = new HashSet<>(request.getColumns().getColumns(entity.getClass(), "get"));
+ Collection<Object> items;
+ if (request.getCondition() != null) {
+ long id = (Long) ((Condition.Equals) request.getCondition()).getValue();
+ items = List.of(objects.computeIfAbsent(entity.getClass(), key -> new HashMap<>()).get(id));
+ } else {
+ items = objects.computeIfAbsent(entity.getClass(), key -> new HashMap<>()).values();
+ }
+ for (Method setter : entity.getClass().getMethods()) {
+ if (setter.getName().startsWith("set") && setter.getParameterCount() == 1
+ && columns.contains(Introspector.decapitalize(setter.getName()))) {
+ try {
+ Method getter = entity.getClass().getMethod(setter.getName().replaceFirst("set", "get"));
+ Object value = getter.invoke(entity);
+ for (Object object : items) {
+ setter.invoke(object, value);
+ }
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
}
@Override
public void removeObject(Class<?> clazz, Request request) {
+ long id = (Long) ((Condition.Equals) request.getCondition()).getValue();
+ objects.computeIfAbsent(clazz, key -> new HashMap<>()).remove(id);
}
private Set<Pair<Long, Long>> getPermissionsSet(Class<?> ownerClass, Class<?> propertyClass) {
diff --git a/src/main/java/org/traccar/web/ConsoleServlet.java b/src/main/java/org/traccar/web/ConsoleServlet.java
index 902a4f7a9..0012ba077 100644
--- a/src/main/java/org/traccar/web/ConsoleServlet.java
+++ b/src/main/java/org/traccar/web/ConsoleServlet.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
package org.traccar.web;
import org.h2.server.web.ConnectionInfo;
-import org.h2.server.web.WebServlet;
+import org.h2.server.web.JakartaWebServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.config.Config;
@@ -26,7 +26,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-public class ConsoleServlet extends WebServlet {
+public class ConsoleServlet extends JakartaWebServlet {
private static final Logger LOGGER = LoggerFactory.getLogger(ConsoleServlet.class);
@@ -41,7 +41,7 @@ public class ConsoleServlet extends WebServlet {
super.init();
try {
- Field field = WebServlet.class.getDeclaredField("server");
+ Field field = JakartaWebServlet.class.getDeclaredField("server");
field.setAccessible(true);
org.h2.server.web.WebServer server = (org.h2.server.web.WebServer) field.get(this);
diff --git a/src/main/java/org/traccar/web/ModernDefaultServlet.java b/src/main/java/org/traccar/web/ModernDefaultServlet.java
new file mode 100644
index 000000000..a7c8cdb29
--- /dev/null
+++ b/src/main/java/org/traccar/web/ModernDefaultServlet.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.web;
+
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.util.resource.Resource;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+
+import jakarta.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+
+public class ModernDefaultServlet extends DefaultServlet {
+
+ private Resource overrideResource;
+
+ @Inject
+ public ModernDefaultServlet(Config config) {
+ String override = config.getString(Keys.WEB_OVERRIDE);
+ if (override != null) {
+ overrideResource = Resource.newResource(new File(override));
+ }
+ }
+
+ @Override
+ public Resource getResource(String pathInContext) {
+ if (overrideResource != null) {
+ try {
+ Resource override = overrideResource.addPath(pathInContext);
+ if (override.exists()) {
+ return override;
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return super.getResource(pathInContext.indexOf('.') < 0 ? "/" : pathInContext);
+ }
+
+ @Override
+ public String getWelcomeFile(String pathInContext) {
+ return super.getWelcomeFile("/");
+ }
+
+}
diff --git a/src/main/java/org/traccar/web/OverrideFilter.java b/src/main/java/org/traccar/web/OverrideFilter.java
new file mode 100644
index 000000000..9780c9ede
--- /dev/null
+++ b/src/main/java/org/traccar/web/OverrideFilter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.web;
+
+import com.google.inject.Provider;
+import org.traccar.api.security.PermissionsService;
+import org.traccar.model.Server;
+import org.traccar.storage.StorageException;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Singleton
+public class OverrideFilter implements Filter {
+
+ private final Provider<PermissionsService> permissionsServiceProvider;
+
+ @Inject
+ public OverrideFilter(Provider<PermissionsService> permissionsServiceProvider) {
+ this.permissionsServiceProvider = permissionsServiceProvider;
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+
+ if (((HttpServletRequest) request).getServletPath().startsWith("/api")) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ ResponseWrapper wrappedResponse = new ResponseWrapper((HttpServletResponse) response);
+
+ chain.doFilter(request, wrappedResponse);
+
+ byte[] bytes = wrappedResponse.getCapture();
+ if (bytes != null) {
+ if (wrappedResponse.getContentType() != null && wrappedResponse.getContentType().contains("text/html")
+ || ((HttpServletRequest) request).getPathInfo().endsWith("manifest.webmanifest")) {
+
+ Server server;
+ try {
+ server = permissionsServiceProvider.get().getServer();
+ } catch (StorageException e) {
+ throw new RuntimeException(e);
+ }
+
+ String title = server.getString("title", "Traccar");
+ String description = server.getString("description", "Traccar GPS Tracking System");
+ String colorPrimary = server.getString("colorPrimary", "#1a237e");
+
+ String alteredContent = new String(wrappedResponse.getCapture())
+ .replace("${title}", title)
+ .replace("${description}", description)
+ .replace("${colorPrimary}", colorPrimary);
+
+ byte[] data = alteredContent.getBytes();
+ response.setContentLength(data.length);
+ response.getOutputStream().write(data);
+
+ } else {
+ response.getOutputStream().write(bytes);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/web/ResponseWrapper.java b/src/main/java/org/traccar/web/ResponseWrapper.java
new file mode 100644
index 000000000..a0eaf6788
--- /dev/null
+++ b/src/main/java/org/traccar/web/ResponseWrapper.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.web;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class ResponseWrapper extends HttpServletResponseWrapper {
+
+ private final ByteArrayOutputStream capture;
+ private ServletOutputStream output;
+
+ public ResponseWrapper(HttpServletResponse response) {
+ super(response);
+ capture = new ByteArrayOutputStream(response.getBufferSize());
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() {
+ if (output == null) {
+ output = new ServletOutputStream() {
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {
+ }
+
+ @Override
+ public void write(int b) {
+ capture.write(b);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ capture.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ capture.close();
+ }
+ };
+ }
+ return output;
+ }
+
+ @Override
+ public void flushBuffer() throws IOException {
+ super.flushBuffer();
+ if (output != null) {
+ output.flush();
+ }
+ }
+
+ public byte[] getCapture() throws IOException {
+ if (output != null) {
+ output.close();
+ return capture.toByteArray();
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/traccar/web/ThrottlingFilter.java b/src/main/java/org/traccar/web/ThrottlingFilter.java
index 054af652f..1bad33db6 100644
--- a/src/main/java/org/traccar/web/ThrottlingFilter.java
+++ b/src/main/java/org/traccar/web/ThrottlingFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,13 +19,13 @@ import org.eclipse.jetty.servlets.DoSFilter;
import org.traccar.config.Config;
import org.traccar.config.Keys;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpSession;
@Singleton
public class ThrottlingFilter extends DoSFilter {
@@ -39,6 +39,7 @@ public class ThrottlingFilter extends DoSFilter {
if (config.hasKey(Keys.WEB_MAX_REQUESTS_PER_SECOND)) {
setMaxRequestsPerSec(config.getInteger(Keys.WEB_MAX_REQUESTS_PER_SECOND));
}
+ setMaxRequestMs(config.getInteger(Keys.WEB_MAX_REQUEST_SECONDS) * 1000L);
}
@Override
diff --git a/src/main/java/org/traccar/web/WebInjectionManagerFactory.java b/src/main/java/org/traccar/web/WebInjectionManagerFactory.java
index 14d9d3dbc..3e73c41ad 100644
--- a/src/main/java/org/traccar/web/WebInjectionManagerFactory.java
+++ b/src/main/java/org/traccar/web/WebInjectionManagerFactory.java
@@ -23,7 +23,7 @@ import org.jvnet.hk2.guice.bridge.api.GuiceBridge;
import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge;
import org.traccar.Main;
-import javax.annotation.Priority;
+import jakarta.annotation.Priority;
@Priority(20)
public class WebInjectionManagerFactory implements InjectionManagerFactory {
diff --git a/src/main/java/org/traccar/web/WebModule.java b/src/main/java/org/traccar/web/WebModule.java
index 0722c5d1e..a32a6f447 100644
--- a/src/main/java/org/traccar/web/WebModule.java
+++ b/src/main/java/org/traccar/web/WebModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ public class WebModule extends ServletModule {
@Override
protected void configureServlets() {
+ filter("/*").through(OverrideFilter.class);
filter("/api/*").through(ThrottlingFilter.class);
filter("/api/media/*").through(MediaFilter.class);
serve("/api/socket").with(AsyncSocketServlet.class);
diff --git a/src/main/java/org/traccar/web/WebRequestLog.java b/src/main/java/org/traccar/web/WebRequestLog.java
new file mode 100644
index 000000000..3f3286003
--- /dev/null
+++ b/src/main/java/org/traccar/web/WebRequestLog.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.web;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.traccar.api.resource.SessionResource;
+
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class WebRequestLog extends ContainerLifeCycle implements RequestLog {
+
+ private final Writer writer;
+
+ private final DateCache dateCache = new DateCache(
+ "dd/MMM/yyyy:HH:mm:ss ZZZ", Locale.getDefault(), TimeZone.getTimeZone("GMT"));
+
+ public WebRequestLog(Writer writer) {
+ this.writer = writer;
+ addBean(writer);
+ }
+
+ @Override
+ public void log(Request request, Response response) {
+ try {
+ Long userId = (Long) request.getSession().getAttribute(SessionResource.USER_ID_KEY);
+ writer.write(String.format("%s - %s [%s] \"%s %s %s\" %d %d",
+ request.getRemoteHost(),
+ userId != null ? String.valueOf(userId) : "-",
+ dateCache.format(request.getTimeStamp()),
+ request.getMethod(),
+ request.getOriginalURI(),
+ request.getProtocol(),
+ response.getCommittedMetaData().getStatus(),
+ response.getHttpChannel().getBytesWritten()));
+ } catch (Throwable ignored) {
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/web/WebServer.java b/src/main/java/org/traccar/web/WebServer.java
index 79d19cc9b..4759942b1 100644
--- a/src/main/java/org/traccar/web/WebServer.java
+++ b/src/main/java/org/traccar/web/WebServer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2012 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.proxy.AsyncProxyServlet;
-import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLogWriter;
import org.eclipse.jetty.server.Server;
@@ -52,19 +51,16 @@ import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.helper.ObjectMapperContextResolver;
-import javax.servlet.DispatcherType;
-import javax.servlet.ServletException;
-import javax.servlet.SessionCookieConfig;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.DispatcherType;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.SessionCookieConfig;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.net.InetSocketAddress;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.EnumSet;
public class WebServer implements LifecycleObject {
@@ -103,14 +99,8 @@ public class WebServer implements LifecycleObject {
@Override
protected void handleErrorPage(
HttpServletRequest request, Writer writer, int code, String message) throws IOException {
- Path index = Paths.get(config.getString(Keys.WEB_PATH), "index.html");
- if (code == HttpStatus.NOT_FOUND_404
- && !request.getPathInfo().startsWith("/api/") && Files.exists(index)) {
- writer.write(Files.readString(index));
- } else {
- writer.write("<!DOCTYPE><html><head><title>Error</title></head><html><body>"
- + code + " - " + HttpStatus.getMessage(code) + "</body></html>");
- }
+ writer.write("<!DOCTYPE><html><head><title>Error</title></head><html><body>"
+ + code + " - " + HttpStatus.getMessage(code) + "</body></html>");
}
});
@@ -124,8 +114,7 @@ public class WebServer implements LifecycleObject {
RequestLogWriter logWriter = new RequestLogWriter(config.getString(Keys.WEB_REQUEST_LOG_PATH));
logWriter.setAppend(true);
logWriter.setRetainDays(config.getInteger(Keys.WEB_REQUEST_LOG_RETAIN_DAYS));
- CustomRequestLog requestLog = new CustomRequestLog(logWriter, CustomRequestLog.NCSA_FORMAT);
- server.setRequestLog(requestLog);
+ server.setRequestLog(new WebRequestLog(logWriter));
}
}
@@ -150,7 +139,7 @@ public class WebServer implements LifecycleObject {
}
private void initWebApp(ServletContextHandler servletHandler) {
- ServletHolder servletHolder = new ServletHolder(DefaultServlet.class);
+ ServletHolder servletHolder = new ServletHolder(new ModernDefaultServlet(config));
servletHolder.setInitParameter("resourceBase", new File(config.getString(Keys.WEB_PATH)).getAbsolutePath());
servletHolder.setInitParameter("dirAllowed", "false");
if (config.getBoolean(Keys.WEB_DEBUG)) {
@@ -202,14 +191,16 @@ public class WebServer implements LifecycleObject {
sessionHandler.setSessionCache(sessionCache);
}
+ SessionCookieConfig sessionCookieConfig = servletHandler.getServletContext().getSessionCookieConfig();
+
int sessionTimeout = config.getInteger(Keys.WEB_SESSION_TIMEOUT);
if (sessionTimeout > 0) {
servletHandler.getSessionHandler().setMaxInactiveInterval(sessionTimeout);
+ sessionCookieConfig.setMaxAge(sessionTimeout);
}
String sameSiteCookie = config.getString(Keys.WEB_SAME_SITE_COOKIE);
if (sameSiteCookie != null) {
- SessionCookieConfig sessionCookieConfig = servletHandler.getServletContext().getSessionCookieConfig();
switch (sameSiteCookie.toLowerCase()) {
case "lax":
sessionCookieConfig.setComment(HttpCookie.SAME_SITE_LAX_COMMENT);