diff options
Diffstat (limited to 'src/main')
86 files changed, 2918 insertions, 441 deletions
diff --git a/src/main/java/org/traccar/Context.java b/src/main/java/org/traccar/Context.java index 3ba8843f0..fe494dabf 100644 --- a/src/main/java/org/traccar/Context.java +++ b/src/main/java/org/traccar/Context.java @@ -60,6 +60,7 @@ import org.traccar.reports.model.TripsConfig; import org.traccar.schedule.ScheduleManager; import org.traccar.sms.HttpSmsClient; import org.traccar.sms.SmsManager; +import org.traccar.sms.SnsSmsClient; import org.traccar.web.WebServer; import javax.ws.rs.client.Client; @@ -317,6 +318,8 @@ public final class Context { if (config.hasKey(Keys.SMS_HTTP_URL)) { smsManager = new HttpSmsClient(); + } else if (config.hasKey(Keys.SMS_AWS_REGION)) { + smsManager = new SnsSmsClient(); } initEventsModule(); diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java index 77f0c89d1..350af6bd7 100644 --- a/src/main/java/org/traccar/MainModule.java +++ b/src/main/java/org/traccar/MainModule.java @@ -45,6 +45,7 @@ import org.traccar.geocoder.NominatimGeocoder; import org.traccar.geocoder.OpenCageGeocoder; import org.traccar.geocoder.PositionStackGeocoder; import org.traccar.geocoder.TomTomGeocoder; +import org.traccar.geocoder.MapboxGeocoder; import org.traccar.geolocation.GeolocationProvider; import org.traccar.geolocation.GoogleGeolocationProvider; import org.traccar.geolocation.MozillaGeolocationProvider; @@ -184,6 +185,8 @@ public class MainModule extends AbstractModule { return new TomTomGeocoder(url, key, cacheSize, addressFormat); case "positionstack": return new PositionStackGeocoder(key, cacheSize, addressFormat); + case "mapbox": + return new MapboxGeocoder(key, cacheSize, addressFormat); default: return new GoogleGeocoder(key, language, cacheSize, addressFormat); } @@ -283,10 +286,9 @@ public class MainModule extends AbstractModule { @Singleton @Provides public static GeocoderHandler provideGeocoderHandler( - Config config, @Nullable Geocoder geocoder, IdentityManager identityManager, - StatisticsManager statisticsManager) { + Config config, @Nullable Geocoder geocoder, IdentityManager identityManager) { if (geocoder != null) { - return new GeocoderHandler(config, geocoder, identityManager, statisticsManager); + return new GeocoderHandler(config, geocoder, identityManager); } return null; } diff --git a/src/main/java/org/traccar/api/AsyncSocketServlet.java b/src/main/java/org/traccar/api/AsyncSocketServlet.java index 490fc89fc..a964ead10 100644 --- a/src/main/java/org/traccar/api/AsyncSocketServlet.java +++ b/src/main/java/org/traccar/api/AsyncSocketServlet.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,20 +15,23 @@ */ package org.traccar.api; -import org.eclipse.jetty.websocket.servlet.WebSocketServlet; -import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; +import org.eclipse.jetty.websocket.server.JettyWebSocketServlet; +import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory; import org.traccar.Context; import org.traccar.api.resource.SessionResource; import org.traccar.config.Keys; -public class AsyncSocketServlet extends WebSocketServlet { +import javax.servlet.http.HttpSession; +import java.time.Duration; + +public class AsyncSocketServlet extends JettyWebSocketServlet { @Override - public void configure(WebSocketServletFactory factory) { - factory.getPolicy().setIdleTimeout(Context.getConfig().getLong(Keys.WEB_TIMEOUT)); + public void configure(JettyWebSocketServletFactory factory) { + factory.setIdleTimeout(Duration.ofMillis(Context.getConfig().getLong(Keys.WEB_TIMEOUT))); factory.setCreator((req, resp) -> { if (req.getSession() != null) { - long userId = (Long) req.getSession().getAttribute(SessionResource.USER_ID_KEY); + long userId = (Long) ((HttpSession) req.getSession()).getAttribute(SessionResource.USER_ID_KEY); return new AsyncSocket(userId); } else { return null; diff --git a/src/main/java/org/traccar/api/resource/PasswordResource.java b/src/main/java/org/traccar/api/resource/PasswordResource.java new file mode 100644 index 000000000..20e8d768d --- /dev/null +++ b/src/main/java/org/traccar/api/resource/PasswordResource.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021 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.api.resource; + +import org.apache.velocity.VelocityContext; +import org.traccar.Context; +import org.traccar.api.BaseResource; +import org.traccar.model.User; +import org.traccar.notification.FullMessage; +import org.traccar.notification.TextTemplateFormatter; + +import javax.annotation.security.PermitAll; +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 java.sql.SQLException; +import java.util.UUID; + +@Path("password") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_FORM_URLENCODED) +public class PasswordResource extends BaseResource { + + private static final String PASSWORD_RESET_TOKEN = "passwordToken"; + + @Path("reset") + @PermitAll + @POST + public Response reset(@FormParam("email") String email) throws SQLException, MessagingException { + for (long userId : Context.getUsersManager().getAllItems()) { + User user = Context.getUsersManager().getById(userId); + if (email.equals(user.getEmail())) { + String token = UUID.randomUUID().toString().replaceAll("-", ""); + user.set(PASSWORD_RESET_TOKEN, token); + Context.getUsersManager().updateItem(user); + VelocityContext velocityContext = TextTemplateFormatter.prepareContext(null); + velocityContext.put("token", token); + FullMessage message = TextTemplateFormatter.formatFullMessage(velocityContext, "passwordReset"); + Context.getMailManager().sendMessage(userId, message.getSubject(), message.getBody()); + break; + } + } + return Response.ok().build(); + } + + @Path("update") + @PermitAll + @POST + public Response update( + @FormParam("token") String token, @FormParam("password") String password) throws SQLException { + for (long userId : Context.getUsersManager().getAllItems()) { + User user = Context.getUsersManager().getById(userId); + if (token.equals(user.getString(PASSWORD_RESET_TOKEN))) { + user.getAttributes().remove(PASSWORD_RESET_TOKEN); + user.setPassword(password); + Context.getUsersManager().updateItem(user); + return Response.ok().build(); + } + } + return Response.status(Response.Status.NOT_FOUND).build(); + } + +} diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 5f8b36c6d..6d2296fb1 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2021 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. @@ -155,6 +155,13 @@ public final class Keys { Collections.singletonList(KeyType.GLOBAL)); /** + * Indicates whether TAIP protocol should have prefixes for messages. + */ + public static final ConfigSuffix<Boolean> PROTOCOL_PREFIX = new ConfigSuffix<>( + ".prefix", + Collections.singletonList(KeyType.GLOBAL)); + + /** * Some devices require server address confirmation. Use this parameter to configure correct public address. */ public static final ConfigSuffix<String> PROTOCOL_SERVER = new ConfigSuffix<>( @@ -688,6 +695,28 @@ public final class Keys { Collections.singletonList(KeyType.GLOBAL)); /** + * AWS Access Key with SNS permission. + */ + public static final ConfigKey<String> SMS_AWS_ACCESS = new ConfigKey<>( + "sms.aws.access", + Collections.singletonList(KeyType.GLOBAL)); + + /** + * AWS Secret Access Key with SNS permission. + */ + public static final ConfigKey<String> SMS_AWS_SECRET = new ConfigKey<>( + "sms.aws.secret", + Collections.singletonList(KeyType.GLOBAL)); + + /** + * AWS Region for SNS service. + * Make sure to use regions that are supported for messaging. + */ + public static final ConfigKey<String> SMS_AWS_REGION = new ConfigKey<>( + "sms.aws.region", + Collections.singletonList(KeyType.GLOBAL)); + + /** * Traccar notification API key. */ public static final ConfigKey<String> NOTIFICATOR_TRACCAR_KEY = new ConfigKey<>( diff --git a/src/main/java/org/traccar/database/DataManager.java b/src/main/java/org/traccar/database/DataManager.java index a3b31350f..15137ad91 100644 --- a/src/main/java/org/traccar/database/DataManager.java +++ b/src/main/java/org/traccar/database/DataManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2021 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. @@ -272,11 +272,11 @@ public class DataManager { return result; } - private void initDatabaseSchema() throws SQLException, LiquibaseException { + private void initDatabaseSchema() throws LiquibaseException { if (config.hasKey(Keys.DATABASE_CHANGELOG)) { - ResourceAccessor resourceAccessor = new FileSystemResourceAccessor(); + ResourceAccessor resourceAccessor = new FileSystemResourceAccessor(new File(".")); Database database = DatabaseFactory.getInstance().openDatabase( config.getString(Keys.DATABASE_URL), diff --git a/src/main/java/org/traccar/database/MailManager.java b/src/main/java/org/traccar/database/MailManager.java index 8a2f002cd..d94f55cda 100644 --- a/src/main/java/org/traccar/database/MailManager.java +++ b/src/main/java/org/traccar/database/MailManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -87,6 +87,10 @@ public final class MailManager { return properties; } + public boolean getEmailEnabled() { + return Context.getConfig().hasKey("mail.smtp.host"); + } + public void sendMessage( long userId, String subject, String body) throws MessagingException { sendMessage(userId, subject, body, null); diff --git a/src/main/java/org/traccar/database/NotificationManager.java b/src/main/java/org/traccar/database/NotificationManager.java index ccad192f6..9f9a83cd2 100644 --- a/src/main/java/org/traccar/database/NotificationManager.java +++ b/src/main/java/org/traccar/database/NotificationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,7 +41,7 @@ public class NotificationManager extends ExtendedObjectManager<Notification> { private static final Logger LOGGER = LoggerFactory.getLogger(NotificationManager.class); - private boolean geocodeOnRequest; + private final boolean geocodeOnRequest; public NotificationManager(DataManager dataManager) { super(dataManager, Notification.class); @@ -70,11 +70,6 @@ public class NotificationManager extends ExtendedObjectManager<Notification> { LOGGER.warn("Event save error", error); } - if (position != null && geocodeOnRequest && Context.getGeocoder() != null && position.getAddress() == null) { - position.setAddress(Context.getGeocoder() - .getAddress(position.getLatitude(), position.getLongitude(), null)); - } - long deviceId = event.getDeviceId(); Set<Long> users = Context.getPermissionsManager().getDeviceUsers(deviceId); Set<Long> usersToForward = null; @@ -90,7 +85,7 @@ public class NotificationManager extends ExtendedObjectManager<Notification> { usersToForward.add(userId); } final Set<String> notificators = new HashSet<>(); - for (long notificationId : getEffectiveNotifications(userId, deviceId, event.getServerTime())) { + for (long notificationId : getEffectiveNotifications(userId, deviceId, event.getEventTime())) { Notification notification = getById(notificationId); if (getById(notificationId).getType().equals(event.getType())) { boolean filter = false; @@ -108,6 +103,13 @@ public class NotificationManager extends ExtendedObjectManager<Notification> { } } } + + if (position != null && position.getAddress() == null + && geocodeOnRequest && Context.getGeocoder() != null) { + position.setAddress(Context.getGeocoder() + .getAddress(position.getLatitude(), position.getLongitude(), null)); + } + for (String notificator : notificators) { Context.getNotificatorManager().getNotificator(notificator).sendAsync(userId, event, position); } diff --git a/src/main/java/org/traccar/geocoder/JsonGeocoder.java b/src/main/java/org/traccar/geocoder/JsonGeocoder.java index 8c442def3..4f34fb973 100644 --- a/src/main/java/org/traccar/geocoder/JsonGeocoder.java +++ b/src/main/java/org/traccar/geocoder/JsonGeocoder.java @@ -18,6 +18,8 @@ package org.traccar.geocoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.Context; +import org.traccar.Main; +import org.traccar.database.StatisticsManager; import javax.json.JsonObject; import javax.ws.rs.ClientErrorException; @@ -95,6 +97,8 @@ public abstract class JsonGeocoder implements Geocoder { } } + Main.getInjector().getInstance(StatisticsManager.class).registerGeocoderRequest(); + Invocation.Builder request = Context.getClient().target(String.format(url, latitude, longitude)).request(); if (callback != null) { diff --git a/src/main/java/org/traccar/geocoder/MapboxGeocoder.java b/src/main/java/org/traccar/geocoder/MapboxGeocoder.java new file mode 100644 index 000000000..9b987c9d8 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/MapboxGeocoder.java @@ -0,0 +1,90 @@ +/* + * Copyright 2021 Rafael Miquelino (rafaelmiquelino@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.geocoder; + +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonString; + +public class MapboxGeocoder extends JsonGeocoder { + + private static String formatUrl(String key) { + return "https://api.mapbox.com/geocoding/v5/mapbox.places/%2$f,%1$f.json?access_token=" + key; + } + + public MapboxGeocoder(String key, int cacheSize, AddressFormat addressFormat) { + super(formatUrl(key), cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + JsonArray features = json.getJsonArray("features"); + + if (!features.isEmpty()) { + Address address = new Address(); + + JsonObject mostSpecificFeature = (JsonObject) features.get(0); + + if (mostSpecificFeature.containsKey("place_name")) { + address.setFormattedAddress(mostSpecificFeature.getString("place_name")); + } + + if (mostSpecificFeature.containsKey("address")) { + address.setHouse(mostSpecificFeature.getString("address")); + } + + for (JsonObject feature : features.getValuesAs(JsonObject.class)) { + + String value = feature.getString("text"); + + typesLoop: + for (JsonString type : feature.getJsonArray("place_type").getValuesAs(JsonString.class)) { + + switch (type.getString()) { + case "address": + address.setStreet(value); + break typesLoop; + case "neighborhood": + address.setSuburb(value); + break typesLoop; + case "postcode": + address.setPostcode(value); + break typesLoop; + case "locality": + address.setSettlement(value); + break typesLoop; + case "district": + case "place": + address.setDistrict(value); + break typesLoop; + case "region": + address.setState(value); + break typesLoop; + case "country": + address.setCountry(value); + break typesLoop; + default: + break; + } + } + } + + return address; + } + return null; + } + +} diff --git a/src/main/java/org/traccar/handler/CopyAttributesHandler.java b/src/main/java/org/traccar/handler/CopyAttributesHandler.java index 3cd7d144d..f386116b0 100644 --- a/src/main/java/org/traccar/handler/CopyAttributesHandler.java +++ b/src/main/java/org/traccar/handler/CopyAttributesHandler.java @@ -34,11 +34,6 @@ public class CopyAttributesHandler extends BaseDataHandler { protected Position handlePosition(Position position) { String attributesString = identityManager.lookupAttributeString( position.getDeviceId(), "processing.copyAttributes", "", false, true); - if (attributesString.isEmpty()) { - attributesString = Position.KEY_DRIVER_UNIQUE_ID; - } else { - attributesString += "," + Position.KEY_DRIVER_UNIQUE_ID; - } Position last = identityManager.getLastPosition(position.getDeviceId()); if (last != null) { for (String attribute : attributesString.split("[ ,]")) { diff --git a/src/main/java/org/traccar/handler/GeocoderHandler.java b/src/main/java/org/traccar/handler/GeocoderHandler.java index b96f01b3a..614cf97d6 100644 --- a/src/main/java/org/traccar/handler/GeocoderHandler.java +++ b/src/main/java/org/traccar/handler/GeocoderHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2021 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,6 @@ import org.traccar.Context; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.database.IdentityManager; -import org.traccar.database.StatisticsManager; import org.traccar.geocoder.Geocoder; import org.traccar.model.Position; @@ -35,16 +34,14 @@ public class GeocoderHandler extends ChannelInboundHandlerAdapter { private final Geocoder geocoder; private final IdentityManager identityManager; - private final StatisticsManager statisticsManager; private final boolean ignorePositions; private final boolean processInvalidPositions; private final int geocoderReuseDistance; public GeocoderHandler( - Config config, Geocoder geocoder, IdentityManager identityManager, StatisticsManager statisticsManager) { + Config config, Geocoder geocoder, IdentityManager identityManager) { this.geocoder = geocoder; this.identityManager = identityManager; - this.statisticsManager = statisticsManager; ignorePositions = Context.getConfig().getBoolean(Keys.GEOCODER_IGNORE_POSITIONS); processInvalidPositions = config.getBoolean(Keys.GEOCODER_PROCESS_INVALID_POSITIONS); geocoderReuseDistance = config.getInteger(Keys.GEOCODER_REUSE_DISTANCE, 0); @@ -65,10 +62,6 @@ public class GeocoderHandler extends ChannelInboundHandlerAdapter { } } - if (statisticsManager != null) { - statisticsManager.registerGeocoderRequest(); - } - geocoder.getAddress(position.getLatitude(), position.getLongitude(), new Geocoder.ReverseGeocoderCallback() { @Override diff --git a/src/main/java/org/traccar/handler/events/AlertEventHandler.java b/src/main/java/org/traccar/handler/events/AlertEventHandler.java index 0b7c8d23e..05dbc516e 100644 --- a/src/main/java/org/traccar/handler/events/AlertEventHandler.java +++ b/src/main/java/org/traccar/handler/events/AlertEventHandler.java @@ -48,7 +48,7 @@ public class AlertEventHandler extends BaseEventHandler { } } if (!ignoreAlert) { - Event event = new Event(Event.TYPE_ALARM, position.getDeviceId(), position.getId()); + Event event = new Event(Event.TYPE_ALARM, position); event.set(Position.KEY_ALARM, (String) alarm); return Collections.singletonMap(event, position); } diff --git a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java index cfe676653..9b7ff554e 100644 --- a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java +++ b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java @@ -29,7 +29,7 @@ public class CommandResultEventHandler extends BaseEventHandler { protected Map<Event, Position> analyzePosition(Position position) { Object commandResult = position.getAttributes().get(Position.KEY_RESULT); if (commandResult != null) { - Event event = new Event(Event.TYPE_COMMAND_RESULT, position.getDeviceId(), position.getId()); + Event event = new Event(Event.TYPE_COMMAND_RESULT, position); event.set(Position.KEY_RESULT, (String) commandResult); return Collections.singletonMap(event, position); } diff --git a/src/main/java/org/traccar/handler/events/DriverEventHandler.java b/src/main/java/org/traccar/handler/events/DriverEventHandler.java index 994df93fa..6fdf4246b 100644 --- a/src/main/java/org/traccar/handler/events/DriverEventHandler.java +++ b/src/main/java/org/traccar/handler/events/DriverEventHandler.java @@ -46,7 +46,7 @@ public class DriverEventHandler extends BaseEventHandler { oldDriverUniqueId = lastPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); } if (!driverUniqueId.equals(oldDriverUniqueId)) { - Event event = new Event(Event.TYPE_DRIVER_CHANGED, position.getDeviceId(), position.getId()); + Event event = new Event(Event.TYPE_DRIVER_CHANGED, position); event.set(Position.KEY_DRIVER_UNIQUE_ID, driverUniqueId); return Collections.singletonMap(event, position); } diff --git a/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java b/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java index bc1426b86..343a17311 100644 --- a/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java +++ b/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java @@ -57,7 +57,7 @@ public class FuelDropEventHandler extends BaseEventHandler { double drop = lastPosition.getDouble(Position.KEY_FUEL_LEVEL) - position.getDouble(Position.KEY_FUEL_LEVEL); if (drop >= fuelDropThreshold) { - Event event = new Event(Event.TYPE_DEVICE_FUEL_DROP, position.getDeviceId(), position.getId()); + Event event = new Event(Event.TYPE_DEVICE_FUEL_DROP, position); event.set(ATTRIBUTE_FUEL_DROP_THRESHOLD, fuelDropThreshold); return Collections.singletonMap(event, position); } diff --git a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java index 067c97957..f4807e56b 100644 --- a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java +++ b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java @@ -69,7 +69,7 @@ public class GeofenceEventHandler extends BaseEventHandler { long calendarId = geofenceManager.getById(geofenceId).getCalendarId(); Calendar calendar = calendarId != 0 ? calendarManager.getById(calendarId) : null; if (calendar == null || calendar.checkMoment(position.getFixTime())) { - Event event = new Event(Event.TYPE_GEOFENCE_EXIT, position.getDeviceId(), position.getId()); + Event event = new Event(Event.TYPE_GEOFENCE_EXIT, position); event.setGeofenceId(geofenceId); events.put(event, position); } @@ -78,7 +78,7 @@ public class GeofenceEventHandler extends BaseEventHandler { long calendarId = geofenceManager.getById(geofenceId).getCalendarId(); Calendar calendar = calendarId != 0 ? calendarManager.getById(calendarId) : null; if (calendar == null || calendar.checkMoment(position.getFixTime())) { - Event event = new Event(Event.TYPE_GEOFENCE_ENTER, position.getDeviceId(), position.getId()); + Event event = new Event(Event.TYPE_GEOFENCE_ENTER, position); event.setGeofenceId(geofenceId); events.put(event, position); } diff --git a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java index ec133bafc..69df9a46b 100644 --- a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java +++ b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java @@ -52,10 +52,10 @@ public class IgnitionEventHandler extends BaseEventHandler { if (ignition && !oldIgnition) { result = Collections.singletonMap( - new Event(Event.TYPE_IGNITION_ON, position.getDeviceId(), position.getId()), position); + new Event(Event.TYPE_IGNITION_ON, position), position); } else if (!ignition && oldIgnition) { result = Collections.singletonMap( - new Event(Event.TYPE_IGNITION_OFF, position.getDeviceId(), position.getId()), position); + new Event(Event.TYPE_IGNITION_OFF, position), position); } } } diff --git a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java index 93ae74142..0f960ad1f 100644 --- a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java +++ b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java @@ -58,7 +58,7 @@ public class MaintenanceEventHandler extends BaseEventHandler { if (oldValue != 0.0 && newValue != 0.0 && (long) ((oldValue - maintenance.getStart()) / maintenance.getPeriod()) < (long) ((newValue - maintenance.getStart()) / maintenance.getPeriod())) { - Event event = new Event(Event.TYPE_MAINTENANCE, position.getDeviceId(), position.getId()); + Event event = new Event(Event.TYPE_MAINTENANCE, position); event.setMaintenanceId(maintenanceId); event.set(maintenance.getType(), newValue); events.put(event, position); diff --git a/src/main/java/org/traccar/handler/events/MotionEventHandler.java b/src/main/java/org/traccar/handler/events/MotionEventHandler.java index 9ec02ccfb..db276f32b 100644 --- a/src/main/java/org/traccar/handler/events/MotionEventHandler.java +++ b/src/main/java/org/traccar/handler/events/MotionEventHandler.java @@ -45,7 +45,7 @@ public class MotionEventHandler extends BaseEventHandler { private Map<Event, Position> newEvent(DeviceState deviceState, boolean newMotion) { String eventType = newMotion ? Event.TYPE_DEVICE_MOVING : Event.TYPE_DEVICE_STOPPED; Position position = deviceState.getMotionPosition(); - Event event = new Event(eventType, position.getDeviceId(), position.getId()); + Event event = new Event(eventType, position); deviceState.setMotionState(newMotion); deviceState.setMotionPosition(null); return Collections.singletonMap(event, position); diff --git a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java index c396b28e9..347ad9005 100644 --- a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java +++ b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java @@ -53,7 +53,7 @@ public class OverspeedEventHandler extends BaseEventHandler { private Map<Event, Position> newEvent(DeviceState deviceState, double speedLimit) { Position position = deviceState.getOverspeedPosition(); - Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, position.getDeviceId(), position.getId()); + Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, position); event.set(ATTRIBUTE_SPEED, deviceState.getOverspeedPosition().getSpeed()); event.set(ATTRIBUTE_SPEED_LIMIT, speedLimit); event.setGeofenceId(deviceState.getOverspeedGeofenceId()); diff --git a/src/main/java/org/traccar/model/Calendar.java b/src/main/java/org/traccar/model/Calendar.java index 56d3eb74c..1010325b6 100644 --- a/src/main/java/org/traccar/model/Calendar.java +++ b/src/main/java/org/traccar/model/Calendar.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,24 +16,22 @@ */ package org.traccar.model; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.Collection; -import java.util.Date; - import com.fasterxml.jackson.annotation.JsonIgnore; - import net.fortuna.ical4j.data.CalendarBuilder; import net.fortuna.ical4j.data.ParserException; import net.fortuna.ical4j.filter.Filter; import net.fortuna.ical4j.filter.PeriodRule; import net.fortuna.ical4j.model.DateTime; -import net.fortuna.ical4j.model.Dur; import net.fortuna.ical4j.model.Period; import net.fortuna.ical4j.model.component.CalendarComponent; -import org.apache.commons.collections4.Predicate; import org.traccar.database.QueryIgnore; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.time.Duration; +import java.util.Collection; +import java.util.Date; + public class Calendar extends ExtendedModel { private String name; @@ -68,13 +66,10 @@ public class Calendar extends ExtendedModel { public boolean checkMoment(Date date) { if (calendar != null) { - Period period = new Period(new DateTime(date), new Dur(0, 0, 0, 0)); - Predicate<CalendarComponent> periodRule = new PeriodRule<>(period); - Filter<CalendarComponent> filter = new Filter<>(new Predicate[] {periodRule}, Filter.MATCH_ANY); + Period period = new Period(new DateTime(date), Duration.ZERO); + Filter<CalendarComponent> filter = new Filter<>(new PeriodRule<>(period)); Collection<CalendarComponent> events = filter.filter(calendar.getComponents(CalendarComponent.VEVENT)); - if (events != null && !events.isEmpty()) { - return true; - } + return events != null && !events.isEmpty(); } return false; } diff --git a/src/main/java/org/traccar/model/Command.java b/src/main/java/org/traccar/model/Command.java index 099fb152d..99930d1e6 100644 --- a/src/main/java/org/traccar/model/Command.java +++ b/src/main/java/org/traccar/model/Command.java @@ -36,6 +36,7 @@ public class Command extends Message implements Cloneable { public static final String TYPE_REQUEST_PHOTO = "requestPhoto"; public static final String TYPE_POWER_OFF = "powerOff"; public static final String TYPE_REBOOT_DEVICE = "rebootDevice"; + public static final String TYPE_FACTORY_RESET = "factoryReset"; public static final String TYPE_SEND_SMS = "sendSms"; public static final String TYPE_SEND_USSD = "sendUssd"; public static final String TYPE_SOS_NUMBER = "sosNumber"; @@ -54,6 +55,7 @@ public class Command extends Message implements Cloneable { public static final String TYPE_SET_ODOMETER = "setOdometer"; public static final String TYPE_GET_MODEM_STATUS = "getModemStatus"; public static final String TYPE_GET_DEVICE_STATUS = "getDeviceStatus"; + public static final String TYPE_SET_SPEED_LIMIT = "setSpeedLimit"; public static final String TYPE_MODE_POWER_SAVING = "modePowerSaving"; public static final String TYPE_MODE_DEEP_SLEEP = "modeDeepSleep"; diff --git a/src/main/java/org/traccar/model/Event.java b/src/main/java/org/traccar/model/Event.java index 5eee2a0a0..a7a134ecf 100644 --- a/src/main/java/org/traccar/model/Event.java +++ b/src/main/java/org/traccar/model/Event.java @@ -19,15 +19,17 @@ import java.util.Date; public class Event extends Message { - public Event(String type, long deviceId, long positionId) { - this(type, deviceId); - setPositionId(positionId); + public Event(String type, Position position) { + setType(type); + setPositionId(position.getId()); + setDeviceId(position.getDeviceId()); + eventTime = position.getDeviceTime(); } public Event(String type, long deviceId) { setType(type); setDeviceId(deviceId); - this.serverTime = new Date(); + eventTime = new Date(); } public Event() { @@ -62,14 +64,14 @@ public class Event extends Message { public static final String TYPE_DRIVER_CHANGED = "driverChanged"; - private Date serverTime; + private Date eventTime; - public Date getServerTime() { - return serverTime; + public Date getEventTime() { + return eventTime; } - public void setServerTime(Date serverTime) { - this.serverTime = serverTime; + public void setEventTime(Date eventTime) { + this.eventTime = eventTime; } private long positionId; diff --git a/src/main/java/org/traccar/model/Server.java b/src/main/java/org/traccar/model/Server.java index e84943efc..7bdb53b22 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2021 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.Context; import org.traccar.database.QueryIgnore; @JsonIgnoreProperties(ignoreUnknown = true) @@ -176,4 +177,9 @@ public class Server extends ExtendedModel { return getClass().getPackage().getImplementationVersion(); } + @QueryIgnore + public Boolean getEmailEnabled() { + return Context.getMailManager().getEmailEnabled(); + } + } diff --git a/src/main/java/org/traccar/notification/NotificationFormatter.java b/src/main/java/org/traccar/notification/NotificationFormatter.java index 2f8100226..dabc75b8b 100644 --- a/src/main/java/org/traccar/notification/NotificationFormatter.java +++ b/src/main/java/org/traccar/notification/NotificationFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,18 +16,7 @@ */ package org.traccar.notification; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; -import java.util.Locale; - -import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; -import org.apache.velocity.exception.ResourceNotFoundException; -import org.apache.velocity.tools.generic.DateTool; -import org.apache.velocity.tools.generic.NumberTool; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.traccar.Context; import org.traccar.model.Device; import org.traccar.model.Event; @@ -37,8 +26,6 @@ import org.traccar.reports.ReportUtils; public final class NotificationFormatter { - private static final Logger LOGGER = LoggerFactory.getLogger(NotificationFormatter.class); - private NotificationFormatter() { } @@ -47,8 +34,8 @@ public final class NotificationFormatter { User user = Context.getPermissionsManager().getUser(userId); Device device = Context.getIdentityManager().getById(event.getDeviceId()); - VelocityContext velocityContext = new VelocityContext(); - velocityContext.put("user", user); + VelocityContext velocityContext = TextTemplateFormatter.prepareContext(user); + velocityContext.put("device", device); velocityContext.put("event", event); if (position != null) { @@ -67,52 +54,18 @@ public final class NotificationFormatter { if (driverUniqueId != null) { velocityContext.put("driver", Context.getDriversManager().getDriverByUniqueId(driverUniqueId)); } - velocityContext.put("webUrl", Context.getVelocityEngine().getProperty("web.url")); - velocityContext.put("dateTool", new DateTool()); - velocityContext.put("numberTool", new NumberTool()); - velocityContext.put("timezone", ReportUtils.getTimezone(userId)); - velocityContext.put("locale", Locale.getDefault()); - return velocityContext; - } - - public static Template getTemplate(Event event, String path) { - String templateFilePath; - Template template; - - try { - templateFilePath = Paths.get(path, event.getType() + ".vm").toString(); - template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); - } catch (ResourceNotFoundException error) { - LOGGER.warn("Notification template error", error); - templateFilePath = Paths.get(path, "unknown.vm").toString(); - template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); - } - return template; + return velocityContext; } public static FullMessage formatFullMessage(long userId, Event event, Position position) { VelocityContext velocityContext = prepareContext(userId, event, position); - String formattedMessage = formatMessage(velocityContext, userId, event, position, "full"); - - return new FullMessage((String) velocityContext.get("subject"), formattedMessage); + return TextTemplateFormatter.formatFullMessage(velocityContext, event.getType()); } public static String formatShortMessage(long userId, Event event, Position position) { - return formatMessage(null, userId, event, position, "short"); - } - - private static String formatMessage(VelocityContext vc, Long userId, Event event, Position position, - String templatePath) { - - VelocityContext velocityContext = vc; - if (velocityContext == null) { - velocityContext = prepareContext(userId, event, position); - } - StringWriter writer = new StringWriter(); - getTemplate(event, templatePath).merge(velocityContext, writer); - - return writer.toString(); + VelocityContext velocityContext = prepareContext(userId, event, position); + return TextTemplateFormatter.formatShortMessage(velocityContext, event.getType()); } } diff --git a/src/main/java/org/traccar/notification/TextTemplateFormatter.java b/src/main/java/org/traccar/notification/TextTemplateFormatter.java new file mode 100644 index 000000000..c7cac2d4d --- /dev/null +++ b/src/main/java/org/traccar/notification/TextTemplateFormatter.java @@ -0,0 +1,91 @@ +/* + * Copyright 2021 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.notification; + +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.tools.generic.DateTool; +import org.apache.velocity.tools.generic.NumberTool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.model.User; +import org.traccar.reports.ReportUtils; + +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.util.Locale; + +public final class TextTemplateFormatter { + + private static final Logger LOGGER = LoggerFactory.getLogger(TextTemplateFormatter.class); + + private TextTemplateFormatter() { + } + + public static VelocityContext prepareContext(User user) { + + VelocityContext velocityContext = new VelocityContext(); + + if (user != null) { + velocityContext.put("user", user); + velocityContext.put("timezone", ReportUtils.getTimezone(user.getId())); + } + + velocityContext.put("webUrl", Context.getVelocityEngine().getProperty("web.url")); + velocityContext.put("dateTool", new DateTool()); + velocityContext.put("numberTool", new NumberTool()); + velocityContext.put("locale", Locale.getDefault()); + + return velocityContext; + } + + public static Template getTemplate(String name, String path) { + + String templateFilePath; + Template template; + + try { + templateFilePath = Paths.get(path, name + ".vm").toString(); + template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); + } catch (ResourceNotFoundException error) { + LOGGER.warn("Notification template error", error); + templateFilePath = Paths.get(path, "unknown.vm").toString(); + template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); + } + return template; + } + + public static FullMessage formatFullMessage(VelocityContext velocityContext, String name) { + String formattedMessage = formatMessage(velocityContext, name, "full"); + return new FullMessage((String) velocityContext.get("subject"), formattedMessage); + } + + public static String formatShortMessage(VelocityContext velocityContext, String name) { + return formatMessage(velocityContext, name, "short"); + } + + private static String formatMessage( + VelocityContext velocityContext, String name, String templatePath) { + + StringWriter writer = new StringWriter(); + getTemplate(name, templatePath).merge(velocityContext, writer); + return writer.toString(); + } + +} diff --git a/src/main/java/org/traccar/notificators/NotificatorFirebase.java b/src/main/java/org/traccar/notificators/NotificatorFirebase.java index 89cdbcb14..78d5da1e2 100644 --- a/src/main/java/org/traccar/notificators/NotificatorFirebase.java +++ b/src/main/java/org/traccar/notificators/NotificatorFirebase.java @@ -39,6 +39,8 @@ public class NotificatorFirebase extends Notificator { public static class Notification { @JsonProperty("body") private String body; + @JsonProperty("sound") + private String sound; } public static class Message { @@ -66,6 +68,7 @@ public class NotificatorFirebase extends Notificator { Notification notification = new Notification(); notification.body = NotificationFormatter.formatShortMessage(userId, event, position).trim(); + notification.sound = "default"; Message message = new Message(); message.tokens = user.getString("notificationTokens").split("[, ]"); diff --git a/src/main/java/org/traccar/notificators/NotificatorTelegram.java b/src/main/java/org/traccar/notificators/NotificatorTelegram.java index 6b8bc426d..963bdb091 100644 --- a/src/main/java/org/traccar/notificators/NotificatorTelegram.java +++ b/src/main/java/org/traccar/notificators/NotificatorTelegram.java @@ -31,10 +31,11 @@ public class NotificatorTelegram extends Notificator { private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorTelegram.class); - private final String url; + private final String urlSendText; + private final String urlSendLocation; private final String chatId; - public static class Message { + public static class TextMessage { @JsonProperty("chat_id") private String chatId; @JsonProperty("text") @@ -43,20 +44,30 @@ public class NotificatorTelegram extends Notificator { private String parseMode = "html"; } + public static class LocationMessage { + @JsonProperty("chat_id") + private String chatId; + @JsonProperty("latitude") + private double latitude; + @JsonProperty("longitude") + private double longitude; + @JsonProperty("horizontal_accuracy") + private double accuracy; + @JsonProperty("bearing") + private int bearing; + } + public NotificatorTelegram() { - url = String.format( + urlSendText = String.format( "https://api.telegram.org/bot%s/sendMessage", Context.getConfig().getString(Keys.NOTIFICATOR_TELEGRAM_KEY)); + urlSendLocation = String.format( + "https://api.telegram.org/bot%s/sendLocation", + Context.getConfig().getString(Keys.NOTIFICATOR_TELEGRAM_KEY)); chatId = Context.getConfig().getString(Keys.NOTIFICATOR_TELEGRAM_CHAT_ID); } - @Override - public void sendSync(long userId, Event event, Position position) { - - Message message = new Message(); - message.chatId = chatId; - message.text = NotificationFormatter.formatShortMessage(userId, event, position); - + private void executeRequest(String url, Object message) { Context.getClient().target(url).request() .async().post(Entity.json(message), new InvocationCallback<Object>() { @Override @@ -70,6 +81,27 @@ public class NotificatorTelegram extends Notificator { }); } + private LocationMessage createLocationMessage(Position position) { + LocationMessage locationMessage = new LocationMessage(); + locationMessage.chatId = chatId; + locationMessage.latitude = position.getLatitude(); + locationMessage.longitude = position.getLongitude(); + locationMessage.bearing = (int) Math.ceil(position.getCourse()); + locationMessage.accuracy = position.getAccuracy(); + return locationMessage; + } + + @Override + public void sendSync(long userId, Event event, Position position) { + if (position != null) { + executeRequest(urlSendLocation, createLocationMessage(position)); + } + TextMessage message = new TextMessage(); + message.chatId = chatId; + message.text = NotificationFormatter.formatFullMessage(userId, event, position).getBody(); + executeRequest(urlSendText, message); + } + @Override public void sendAsync(long userId, Event event, Position position) { sendSync(userId, event, position); diff --git a/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java b/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java index 31064286e..7e3478704 100644 --- a/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2021 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. @@ -97,7 +97,7 @@ public class AdmProtocolDecoder extends BaseProtocolDecoder { if (BitUtil.check(type, 5)) { for (int i = 1; i <= 3; i++) { - buf.readUnsignedShortLE(); // fuel level + position.set("fuel" + i, buf.readUnsignedShortLE()); } for (int i = 1; i <= 3; i++) { position.set(Position.PREFIX_TEMP + i, buf.readUnsignedByte()); diff --git a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java index ff7ef6c4a..186b81470 100644 --- a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2021 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.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; @@ -24,6 +25,8 @@ import org.traccar.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.DateBuilder; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; @@ -115,6 +118,89 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { return result; } + private void decodeBeaconData(Position position, int mode, int mask, ByteBuf data) { + int i = 1; + while (data.isReadable()) { + if (BitUtil.check(mask, 7)) { + position.set("tag" + i + "Id", ByteBufUtil.hexDump(data.readSlice(6))); + } + switch (mode) { + case 1: + if (BitUtil.check(mask, 6)) { + data.readUnsignedShort(); // major + } + if (BitUtil.check(mask, 5)) { + data.readUnsignedShort(); // minor + } + if (BitUtil.check(mask, 4)) { + data.readUnsignedByte(); // tx power + } + if (BitUtil.check(mask, 3)) { + position.set("tag" + i + "Rssi", data.readUnsignedByte()); + } + break; + case 2: + if (BitUtil.check(mask, 6)) { + data.readUnsignedShort(); // battery voltage + } + if (BitUtil.check(mask, 5)) { + position.set("tag" + i + "Temp", data.readUnsignedShort()); + } + if (BitUtil.check(mask, 4)) { + data.readUnsignedByte(); // tx power + } + if (BitUtil.check(mask, 3)) { + position.set("tag" + i + "Rssi", data.readUnsignedByte()); + } + break; + case 3: + if (BitUtil.check(mask, 6)) { + position.set("tag" + i + "Humidity", data.readUnsignedShort()); + } + if (BitUtil.check(mask, 5)) { + position.set("tag" + i + "Temp", data.readUnsignedShort()); + } + if (BitUtil.check(mask, 3)) { + position.set("tag" + i + "Rssi", data.readUnsignedByte()); + } + if (BitUtil.check(mask, 2)) { + data.readUnsignedShort(); + } + break; + case 4: + if (BitUtil.check(mask, 6)) { + int hardwareId = data.readUnsignedByte(); + if (BitUtil.check(mask, 5)) { + switch (hardwareId) { + case 1: + case 4: + data.skipBytes(11); // fuel + break; + case 2: + data.skipBytes(2); // temperature + break; + case 3: + data.skipBytes(6); // temperature and luminosity + break; + case 5: + data.skipBytes(10); // temperature, humidity, luminosity and pressure + break; + default: + break; + } + } + } + if (BitUtil.check(mask, 4)) { + data.skipBytes(9); // name + } + break; + default: + break; + } + i += 1; + } + } + private void readTextCustomData(Position position, String data, String form) { CellTower cellTower = new CellTower(); String[] keys = form.substring(1).split("%"); @@ -208,6 +294,12 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { case "MT": position.set(Position.KEY_MOTION, Integer.parseInt(values[i]) > 0); break; + case "BC": + String[] beaconValues = values[i].split(":"); + decodeBeaconData( + position, Integer.parseInt(beaconValues[0]), Integer.parseInt(beaconValues[1]), + Unpooled.wrappedBuffer(DataConverter.parseHex(beaconValues[2]))); + break; default: break; } diff --git a/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java b/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java index 75fdc3253..96b06557a 100644 --- a/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2021 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. @@ -197,7 +197,8 @@ public class DmtProtocolDecoder extends BaseProtocolDecoder { } else if (fieldId == 6) { while (buf.readerIndex() < fieldEnd) { - switch (buf.readUnsignedByte()) { + int number = buf.readUnsignedByte(); + switch (number) { case 1: position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); break; @@ -214,7 +215,7 @@ public class DmtProtocolDecoder extends BaseProtocolDecoder { position.set("solarPower", buf.readUnsignedShortLE() * 0.001); break; default: - buf.readUnsignedShortLE(); // other + position.set(Position.PREFIX_IO + number, buf.readUnsignedShortLE()); break; } } diff --git a/src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java b/src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java index 56d9314b2..e882c2378 100644 --- a/src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2021 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,9 +17,11 @@ 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.DeviceSession; +import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; @@ -43,7 +45,7 @@ public class DolphinProtocolDecoder extends BaseProtocolDecoder { ByteBuf buf = (ByteBuf) msg; buf.readUnsignedShort(); // header - buf.readUnsignedIntLE(); // index + int index = (int) buf.readUnsignedIntLE(); buf.readUnsignedShort(); // version buf.readUnsignedShort(); // flags int type = buf.readUnsignedShortLE(); @@ -61,6 +63,24 @@ public class DolphinProtocolDecoder extends BaseProtocolDecoder { DolphinMessages.DataPackRequest message = DolphinMessages.DataPackRequest.parseFrom( ByteBufUtil.getBytes(buf, buf.readerIndex(), length, false)); + if (channel != null) { + byte[] responseData = DolphinMessages.DataPackResponse.newBuilder() + .setResponse(DolphinMessages.DataPackResponseCode.DataPack_OK) + .build() + .toByteArray(); + + ByteBuf response = Unpooled.buffer(); + response.writeShort(0xABAB); // header + response.writeIntLE(index); + response.writeShort(0); // flags + response.writeShortLE(DolphinMessages.MessageType.DataPack_Response.getNumber()); + response.writeIntLE(responseData.length); + response.writeIntLE(0); // reserved + response.writeBytes(responseData); + + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + List<Position> positions = new LinkedList<>(); for (int i = 0; i < message.getPointsCount(); i++) { diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java index fe42a44d7..70972f847 100644 --- a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.traccar.Context; import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; +import org.traccar.helper.BitUtil; import org.traccar.helper.Checksum; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; @@ -95,6 +96,16 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { .number("(d+),") // size .compile(); + private static final Pattern PATTERN_RESULT = new PatternBuilder() + .text("$$") + .number("d+,") // length + .number("(d+),") // imei + .any() + .expression(",([A-Z]+)") // result + .text("*") + .number("xx") + .compile(); + private void requestPhoto(Channel channel, SocketAddress socketAddress, String imei, String file) { if (channel != null) { String content = "1,D06," + file + "," + photo.writerIndex() + "," + Math.min(1024, photo.writableBytes()); @@ -177,7 +188,12 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { position.setAltitude(parser.nextInt()); position.set(Position.KEY_ODOMETER, parser.nextLong()); - position.set(Position.KEY_STATUS, parser.nextHexLong()); + + long status = parser.nextHexLong(); + position.set(Position.KEY_RSSI, BitUtil.between(status, 3, 8)); + position.set(Position.KEY_SATELLITES, BitUtil.from(status, 28)); + position.set(Position.KEY_STATUS, status); + position.set(Position.KEY_INPUT, parser.nextHexInt()); position.set(Position.KEY_OUTPUT, parser.nextHexInt()); @@ -203,6 +219,27 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { return position; } + private Object decodeResult( + Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_RESULT, 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_RESULT, parser.next()); + + return position; + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -213,7 +250,12 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { typeIndex = buf.indexOf(typeIndex, buf.writerIndex(), (byte) ',') + 1; String type = buf.toString(typeIndex, 3, StandardCharsets.US_ASCII); - if (type.equals("D05")) { + if (type.startsWith("B")) { + + return decodeResult(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII)); + + } else if (type.equals("D05")) { + String sentence = buf.toString(StandardCharsets.US_ASCII); Parser parser = new Parser(PATTERN_PHOTO, sentence); if (parser.matches()) { @@ -223,7 +265,9 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { photo = Unpooled.buffer(length); requestPhoto(channel, remoteAddress, imei, photoId); } + } else if (type.equals("D06")) { + if (photo == null) { return null; } @@ -251,9 +295,11 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { return position; } } + } else { - String sentence = buf.toString(StandardCharsets.US_ASCII); - return decodeLocation(channel, remoteAddress, sentence); + + return decodeLocation(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII)); + } return null; diff --git a/src/main/java/org/traccar/protocol/FlexibleReportProtocol.java b/src/main/java/org/traccar/protocol/FlexibleReportProtocol.java new file mode 100644 index 000000000..0cd55343a --- /dev/null +++ b/src/main/java/org/traccar/protocol/FlexibleReportProtocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021 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 org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class FlexibleReportProtocol extends BaseProtocol { + + public FlexibleReportProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new FlexibleReportProtocolDecoder(FlexibleReportProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/FlexibleReportProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlexibleReportProtocolDecoder.java new file mode 100644 index 000000000..759f2cd6f --- /dev/null +++ b/src/main/java/org/traccar/protocol/FlexibleReportProtocolDecoder.java @@ -0,0 +1,169 @@ +/* + * Copyright 2021 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.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +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 java.net.SocketAddress; +import java.util.Date; + +public class FlexibleReportProtocolDecoder extends BaseProtocolDecoder { + + public FlexibleReportProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_GENERAL = 0x00; + + private void sendResponse(Channel channel, SocketAddress remoteAddress, int index) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(0x7E); // header + response.writeShort(2); // length + response.writeByte(0xE0); + response.writeByte(BitUtil.check(index, 0) ? 0x4F : 0x0F); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + private Date decodeTime(ByteBuf buf) { + int timestamp = buf.readInt(); + return new DateBuilder() + .setSecond(timestamp % 60) + .setMinute((timestamp / 60) % 60) + .setHour((timestamp / (60 * 60)) % 24) + .setDay(1 + timestamp / (60 * 60 * 24) % 31) + .setMonth(1 + timestamp / (60 * 60 * 24 * 31) % 12) + .setYear(2000 + timestamp / (60 * 60 * 24 * 31 * 12)) + .getDate(); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // header + int flags = buf.readUnsignedByte(); + + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(1); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + int index = buf.readUnsignedShort(); + + if (BitUtil.to(flags, 2) > 0) { + sendResponse(channel, remoteAddress, index); + } + + Date time = decodeTime(buf); + int event = buf.readUnsignedByte(); + + buf.readUnsignedByte(); // length + + int type = buf.readUnsignedByte(); + + if (type == MSG_GENERAL) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setDeviceTime(time); + + position.set(Position.KEY_EVENT, event); + + buf.readUnsignedByte(); // length + long mask = buf.readUnsignedInt(); + + if (BitUtil.check(mask, 0)) { + buf.readUnsignedByte(); // product id + } + if (BitUtil.check(mask, 1)) { + position.setFixTime(decodeTime(buf)); + } + if (BitUtil.check(mask, 2)) { + position.setValid(true); + position.setLatitude(buf.readUnsignedInt() / 1000000.0 - 90); + position.setLongitude(buf.readUnsignedInt() / 1000000.0 - 180); + } + if (BitUtil.check(mask, 3)) { + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.setCourse(buf.readUnsignedShort()); + } + if (BitUtil.check(mask, 4)) { + position.setAltitude(buf.readShort()); + } + if (BitUtil.check(mask, 5)) { + buf.readUnsignedShort(); // gps accuracy + } + if (BitUtil.check(mask, 6)) { + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.001); + } + if (BitUtil.check(mask, 7)) { + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.001); + } + if (BitUtil.check(mask, 8)) { + position.set("auxPower", buf.readUnsignedShort() * 0.001); + } + if (BitUtil.check(mask, 9)) { + position.set("solarPower", buf.readUnsignedShort() * 0.001); + } + if (BitUtil.check(mask, 10)) { + int cellService = buf.readUnsignedByte(); + position.set(Position.KEY_ROAMING, BitUtil.check(cellService, 7)); + position.set("service", BitUtil.to(cellService, 7)); + buf.skipBytes(4); // cell info + } + if (BitUtil.check(mask, 11)) { + buf.readUnsignedByte(); // rssi + } + if (BitUtil.check(mask, 12)) { + int inputs = buf.readUnsignedByte(); + position.set(Position.KEY_IGNITION, BitUtil.check(inputs, 0)); + position.set(Position.PREFIX_IO + 1, inputs); + } + if (BitUtil.check(mask, 13)) { + position.set(Position.PREFIX_IO + 2, buf.readUnsignedByte()); + } + if (BitUtil.check(mask, 14)) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 1000); + } + if (BitUtil.check(mask, 15)) { + position.set(Position.PREFIX_TEMP + 1, buf.readUnsignedShort() * 0.01); + } + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java b/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java index b6d7f4e45..aded35823 100644 --- a/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2021 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. @@ -75,9 +75,9 @@ public class FreematicsProtocolDecoder extends BaseProtocolDecoder { } private Object decodePosition( - Channel channel, SocketAddress remoteAddress, String sentence) throws Exception { + Channel channel, SocketAddress remoteAddress, String sentence, String id) { - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); if (deviceSession == null) { return null; } @@ -88,94 +88,104 @@ public class FreematicsProtocolDecoder extends BaseProtocolDecoder { for (String pair : sentence.split(",")) { String[] data = pair.split("[=:]"); - int key = Integer.parseInt(data[0], 16); + int key; + try { + key = Integer.parseInt(data[0], 16); + } catch (NumberFormatException e) { + continue; + } String value = data[1]; - switch (key) { - case 0x0: - if (position != null) { - position.setTime(dateBuilder.getDate()); - positions.add(position); - } - position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); - position.setValid(true); - dateBuilder = new DateBuilder(new Date()); - break; - case 0x11: - value = ("000000" + value).substring(value.length()); - dateBuilder.setDateReverse( - Integer.parseInt(value.substring(0, 2)), - Integer.parseInt(value.substring(2, 4)), - Integer.parseInt(value.substring(4))); - break; - case 0x10: - value = ("00000000" + value).substring(value.length()); - dateBuilder.setTime( - Integer.parseInt(value.substring(0, 2)), - Integer.parseInt(value.substring(2, 4)), - Integer.parseInt(value.substring(4, 6)), - Integer.parseInt(value.substring(6)) * 10); - break; - case 0xA: - position.setLatitude(Double.parseDouble(value)); - break; - case 0xB: - position.setLongitude(Double.parseDouble(value)); - break; - case 0xC: - position.setAltitude(Double.parseDouble(value)); - break; - case 0xD: - position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(value))); - break; - case 0xE: - position.setCourse(Integer.parseInt(value)); - break; - case 0xF: - position.set(Position.KEY_SATELLITES, Integer.parseInt(value)); - break; - case 0x12: - position.set(Position.KEY_HDOP, Integer.parseInt(value)); - break; - case 0x20: - position.set(Position.KEY_ACCELERATION, value); - break; - case 0x24: - position.set(Position.KEY_BATTERY, Integer.parseInt(value) * 0.01); - break; - case 0x81: - position.set(Position.KEY_RSSI, Integer.parseInt(value)); - break; - case 0x82: - position.set(Position.KEY_DEVICE_TEMP, Integer.parseInt(value) * 0.1); - break; - case 0x104: - position.set(Position.KEY_ENGINE_LOAD, Integer.parseInt(value)); - break; - case 0x105: - position.set(Position.KEY_COOLANT_TEMP, Integer.parseInt(value)); - break; - case 0x10c: - position.set(Position.KEY_RPM, Integer.parseInt(value)); - break; - case 0x10d: - position.set(Position.KEY_OBD_SPEED, UnitsConverter.knotsFromKph(Integer.parseInt(value))); - break; - case 0x111: - position.set(Position.KEY_THROTTLE, Integer.parseInt(value)); - break; - default: - position.set(Position.PREFIX_IO + key, value); - break; + if (key == 0x0) { + if (position != null) { + position.setTime(dateBuilder.getDate()); + positions.add(position); + } + position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + dateBuilder = new DateBuilder(new Date()); + } else if (position != null) { + switch (key) { + case 0x11: + value = ("000000" + value).substring(value.length()); + dateBuilder.setDateReverse( + Integer.parseInt(value.substring(0, 2)), + Integer.parseInt(value.substring(2, 4)), + Integer.parseInt(value.substring(4))); + break; + case 0x10: + value = ("00000000" + value).substring(value.length()); + dateBuilder.setTime( + Integer.parseInt(value.substring(0, 2)), + Integer.parseInt(value.substring(2, 4)), + Integer.parseInt(value.substring(4, 6)), + Integer.parseInt(value.substring(6)) * 10); + break; + case 0xA: + position.setValid(true); + position.setLatitude(Double.parseDouble(value)); + break; + case 0xB: + position.setValid(true); + position.setLongitude(Double.parseDouble(value)); + break; + case 0xC: + position.setAltitude(Double.parseDouble(value)); + break; + case 0xD: + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(value))); + break; + case 0xE: + position.setCourse(Integer.parseInt(value)); + break; + case 0xF: + position.set(Position.KEY_SATELLITES, Integer.parseInt(value)); + break; + case 0x12: + position.set(Position.KEY_HDOP, Integer.parseInt(value)); + break; + case 0x20: + position.set(Position.KEY_ACCELERATION, value); + break; + case 0x24: + position.set(Position.KEY_BATTERY, Integer.parseInt(value) * 0.01); + break; + case 0x81: + position.set(Position.KEY_RSSI, Integer.parseInt(value)); + break; + case 0x82: + position.set(Position.KEY_DEVICE_TEMP, Integer.parseInt(value) * 0.1); + break; + case 0x104: + position.set(Position.KEY_ENGINE_LOAD, Integer.parseInt(value)); + break; + case 0x105: + position.set(Position.KEY_COOLANT_TEMP, Integer.parseInt(value)); + break; + case 0x10c: + position.set(Position.KEY_RPM, Integer.parseInt(value)); + break; + case 0x10d: + position.set(Position.KEY_OBD_SPEED, UnitsConverter.knotsFromKph(Integer.parseInt(value))); + break; + case 0x111: + position.set(Position.KEY_THROTTLE, Integer.parseInt(value)); + break; + default: + position.set(Position.PREFIX_IO + key, value); + break; + } } } if (position != null) { + if (!position.getValid()) { + getLastLocation(position, null); + } position.setTime(dateBuilder.getDate()); positions.add(position); } - return positions; + return positions.isEmpty() ? null : positions; } @Override @@ -187,12 +197,13 @@ public class FreematicsProtocolDecoder extends BaseProtocolDecoder { int endIndex = sentence.indexOf('*'); if (startIndex > 0 && endIndex > 0) { + String id = sentence.substring(0, startIndex); sentence = sentence.substring(startIndex + 1, endIndex); if (sentence.startsWith("EV")) { return decodeEvent(channel, remoteAddress, sentence); } else { - return decodePosition(channel, remoteAddress, sentence); + return decodePosition(channel, remoteAddress, sentence, id); } } diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java index d438aa33d..683ba476e 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2021 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. @@ -75,7 +75,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .expression("(?:[0-9Ff]{20})?,") // iccid .number("(d{1,2}),") // rssi .number("d{1,2},") - .expression("[01],") // external power + .expression("[01]{1,2},") // external power .number("([d.]+)?,") // odometer or external power .number("d*,") // backup battery or lightness .number("(d+.d+),") // battery @@ -97,6 +97,8 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .number("(xx)?,") // digital output .number("[-+]dddd,") // timezone .expression("[01],") // daylight saving + .or() + .any() .groupEnd() .number("(dddd)(dd)(dd)") // date (yyyymmdd) .number("(dd)(dd)(dd),") // time (hhmmss) @@ -237,8 +239,14 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .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*,,") @@ -920,15 +928,21 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_POWER, power * 0.001); } - if (parser.hasNext(9)) { + 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.KEY_BATTERY_LEVEL, parser.nextInt()); - - decodeStatus(position, parser); + 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); + } int index = 0; String[] data = parser.next().split(","); @@ -1147,10 +1161,6 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { decodeDeviceTime(position, parser); - if (channel != null && Context.getConfig().getBoolean(Keys.PROTOCOL_ACK.withPrefix(getProtocolName()))) { - channel.writeAndFlush(new NetworkMessage("+SACK:" + parser.next() + "$", remoteAddress)); - } - return position; } @@ -1315,6 +1325,16 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { } } + if (channel != null && Context.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)); + } + return result; } diff --git a/src/main/java/org/traccar/protocol/Gs100Protocol.java b/src/main/java/org/traccar/protocol/Gs100Protocol.java new file mode 100644 index 000000000..a701815d0 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gs100Protocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021 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 org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Gs100Protocol extends BaseProtocol { + + public Gs100Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new Gs100ProtocolDecoder(Gs100Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Gs100ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gs100ProtocolDecoder.java new file mode 100644 index 000000000..2496aad48 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gs100ProtocolDecoder.java @@ -0,0 +1,138 @@ +/* + * Copyright 2021 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.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; + +public class Gs100ProtocolDecoder extends BaseProtocolDecoder { + + public Gs100ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + String header = buf.readCharSequence(2, StandardCharsets.US_ASCII).toString(); + + if (header.equals("GL")) { + + buf.skipBytes(1); + String imei = buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + + if (channel != null && deviceSession != null) { + ByteBuf response = Unpooled.copiedBuffer("GS100", StandardCharsets.US_ASCII); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + return null; + + } else { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + int count = buf.readUnsignedByte(); + for (int i = 0; i < count; i++) { + + int endIndex = buf.readUnsignedByte() + buf.readerIndex(); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int status = buf.readUnsignedMedium(); + position.set(Position.KEY_STATUS, status); + + if (BitUtil.check(status, 8 + 8 + 7)) { + + DateBuilder dateBuilder = new DateBuilder() + .setHour(BcdUtil.readInteger(buf, 2)) + .setMinute(BcdUtil.readInteger(buf, 2)) + .setSecond(BcdUtil.readInteger(buf, 2)) + .setDay(BcdUtil.readInteger(buf, 2)) + .setMonth(BcdUtil.readInteger(buf, 2)) + .setYear(BcdUtil.readInteger(buf, 2)); + position.setTime(dateBuilder.getDate()); + + position.setValid(true); + + String coordinates = ByteBufUtil.hexDump(buf.readSlice(9)); + position.setLongitude(Integer.parseInt(coordinates.substring(0, 3)) + + Integer.parseInt(coordinates.substring(3, 9)) * 0.0001 / 60); + position.setLatitude(Integer.parseInt(coordinates.substring(10, 12)) + + Integer.parseInt(coordinates.substring(12, 18)) * 0.0001 / 60); + int flags = Integer.parseInt(coordinates.substring(9, 10), 16); + if (!BitUtil.check(flags, 3)) { + position.setLongitude(-position.getLongitude()); + } + if (!BitUtil.check(flags, 2)) { + position.setLatitude(-position.getLatitude()); + } + + String other = ByteBufUtil.hexDump(buf.readSlice(4)); + position.setSpeed(UnitsConverter.knotsFromKph(Integer.parseInt(other.substring(0, 5)) * 0.01)); + position.setCourse(Integer.parseInt(other.substring(5, 8))); + + } else { + + getLastLocation(position, null); + + } + + positions.add(position); + + buf.readerIndex(endIndex); + + } + + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeCharSequence("GS100", StandardCharsets.US_ASCII); + response.writeByte(count); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + return positions.isEmpty() ? null : positions; + + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java index c5e8809e3..6d49be0ce 100644 --- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java @@ -295,7 +295,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return true; } - private boolean decodeStatus(Position position, ByteBuf buf) { + private boolean decodeStatus(Position position, ByteBuf buf, boolean batteryLevel) { int status = buf.readUnsignedByte(); @@ -324,7 +324,11 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { break; } - position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte() * 100 / 6); + if (batteryLevel) { + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte() * 100 / 6); + } else { + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); + } position.set(Position.KEY_RSSI, buf.readUnsignedByte()); position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); @@ -838,7 +842,9 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedShort(); // satellites buf.readUnsignedByte(); // alarm buf.readUnsignedByte(); // language - buf.readUnsignedByte(); // battery + + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + buf.readUnsignedByte(); // working mode buf.readUnsignedShort(); // working voltage buf.readUnsignedByte(); // reserved @@ -864,7 +870,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { } if (hasStatus(type)) { - decodeStatus(position, buf); + decodeStatus(position, buf, true); } if (type == MSG_GPS_LBS_1 && buf.readableBytes() > 75 + 6) { @@ -875,6 +881,17 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { position.set("driverLicense", data.trim()); } + if (type == MSG_GPS_LBS_1 && buf.readableBytes() == 18) { + decodeStatus(position, buf, false); + position.set("oil", buf.readUnsignedShort()); + int temperature = buf.readUnsignedByte(); + if (BitUtil.check(temperature, 7)) { + temperature = -BitUtil.to(temperature, 7); + } + position.set(Position.PREFIX_TEMP + 1, temperature); + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 10); + } + if (type == MSG_GPS_LBS_1 && buf.readableBytes() == 2 + 6) { int mask = buf.readUnsignedShort(); position.set(Position.KEY_IGNITION, BitUtil.check(mask, 8 + 7)); diff --git a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java index 3ad70bdca..2e1ddf5f2 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2021 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,8 +234,14 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder { int length = buf.readUnsignedShort() - 4; switch (subtype) { case 0x0001: - position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedByte() - 40); - position.set(Position.KEY_RPM, buf.readUnsignedShort()); + int coolantTemperature = buf.readUnsignedByte() - 40; + if (coolantTemperature <= 215) { + position.set(Position.KEY_COOLANT_TEMP, coolantTemperature); + } + int rpm = buf.readUnsignedShort(); + if (rpm <= 65535) { + position.set(Position.KEY_RPM, rpm); + } position.set("averageSpeed", buf.readUnsignedByte()); buf.readUnsignedShort(); // interval fuel consumption position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort() * 0.01); diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java index a3d16cb62..675a08aef 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2021 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. @@ -45,6 +45,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_GENERAL_RESPONSE = 0x8001; public static final int MSG_GENERAL_RESPONSE_2 = 0x4401; + public static final int MSG_HEARTBEAT = 0x0002; public static final int MSG_TERMINAL_REGISTER = 0x0100; public static final int MSG_TERMINAL_REGISTER_RESPONSE = 0x8100; public static final int MSG_TERMINAL_CONTROL = 0x8105; @@ -171,7 +172,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { formatMessage(MSG_TERMINAL_REGISTER_RESPONSE, id, false, response), remoteAddress)); } - } else if (type == MSG_TERMINAL_AUTH) { + } else if (type == MSG_TERMINAL_AUTH || type == MSG_HEARTBEAT) { sendGeneralResponse(channel, remoteAddress, id, type, index); @@ -334,24 +335,36 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { position.set("cover", BitUtil.check(deviceStatus, 3)); break; case 0xEB: - while (buf.readerIndex() < endIndex) { - int extendedLength = buf.readUnsignedShort(); - int extendedType = buf.readUnsignedShort(); - switch (extendedType) { - case 0x0001: - position.set("fuel1", buf.readUnsignedShort() * 0.1); - buf.readUnsignedByte(); // unused - break; - case 0x0023: - position.set("fuel2", Double.parseDouble( - buf.readCharSequence(6, StandardCharsets.US_ASCII).toString())); - break; - case 0x00CE: - position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); - break; - default: - buf.skipBytes(extendedLength - 2); - break; + if (buf.getUnsignedShort(buf.readerIndex()) > 200) { + Network network = new Network(); + int mcc = buf.readUnsignedShort(); + int mnc = buf.readUnsignedByte(); + while (buf.readerIndex() < endIndex) { + network.addCellTower(CellTower.from( + mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedShort(), + buf.readUnsignedByte())); + } + position.setNetwork(network); + } else { + while (buf.readerIndex() < endIndex) { + int extendedLength = buf.readUnsignedShort(); + int extendedType = buf.readUnsignedShort(); + switch (extendedType) { + case 0x0001: + position.set("fuel1", buf.readUnsignedShort() * 0.1); + buf.readUnsignedByte(); // unused + break; + case 0x0023: + position.set("fuel2", Double.parseDouble( + buf.readCharSequence(6, StandardCharsets.US_ASCII).toString())); + break; + case 0x00CE: + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); + break; + default: + buf.skipBytes(extendedLength - 2); + break; + } } } break; diff --git a/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java b/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java index bbb63fb65..9c94ffd4b 100644 --- a/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java @@ -84,6 +84,156 @@ public class IotmProtocolDecoder extends BaseProtocolDecoder { } } + private void decodeSensor(Position position, ByteBuf record, int sensorType, int sensorId) { + String key; + switch (sensorId) { + case 0x0002: + position.set(Position.KEY_MOTION, sensorType > 0); + break; + case 0x0008: + case 0x009B: + if (sensorType > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_JAMMING); + } + break; + case 0x0010: + case 0x0011: + case 0x0012: + case 0x0013: + case 0x0014: + case 0x0015: + key = Position.PREFIX_IN + (sensorId - 0x0010 + 2); + position.set(key, sensorType > 0); + break; + case 0x0062: + position.set("doorFL", sensorType > 0); + break; + case 0x0063: + position.set("doorFR", sensorType > 0); + break; + case 0x0064: + position.set("doorRL", sensorType > 0); + break; + case 0x0065: + position.set("doorRR", sensorType > 0); + break; + case 0x001E: + position.set("buttonPresent", sensorType > 0); + break; + case 0x006D: + position.set(Position.KEY_IGNITION, sensorType > 0); + break; + case 0x008B: + position.set("handBrake", sensorType > 0); + break; + case 0x008C: + position.set("footBrake", sensorType > 0); + break; + case 0x0094: + case 0x0095: + case 0x0096: + key = Position.PREFIX_OUT + (sensorId - 0x0094 + 1); + position.set(key, sensorType > 0); + break; + case 0x009A: + position.set(Position.PREFIX_OUT + 4, sensorType > 0); + break; + case 0x2000: + position.set(Position.KEY_OBD_SPEED, record.readUnsignedByte()); + break; + case 0x2001: + position.set(Position.KEY_SATELLITES, record.readUnsignedByte()); + break; + case 0x2006: + position.set(Position.KEY_THROTTLE, record.readUnsignedByte()); + break; + case 0x2007: + position.set(Position.KEY_FUEL_LEVEL, record.readUnsignedByte()); + break; + case 0x2008: + position.set(Position.KEY_COOLANT_TEMP, record.readUnsignedByte()); + break; + case 0x2009: + position.set("fuel2", record.readUnsignedByte()); + break; + case 0x200A: + position.set(Position.KEY_ENGINE_LOAD, record.readUnsignedByte()); + break; + case 0x2041: + position.set(Position.KEY_BATTERY_LEVEL, record.readUnsignedByte()); + break; + case 0x3000: + position.set(Position.KEY_POWER, record.readUnsignedShortLE() * 0.001); + break; + case 0x3001: + case 0x3002: + case 0x3003: + key = Position.PREFIX_ADC + (0x3003 - sensorId + 3); + position.set(key, record.readUnsignedShortLE() * 0.001); + break; + case 0x3004: + position.set(Position.KEY_BATTERY, record.readUnsignedShortLE() * 0.001); + break; + case 0x300C: + position.set(Position.KEY_RPM, record.readUnsignedShortLE()); + break; + case 0x3021: + position.set(Position.KEY_FUEL_CONSUMPTION, record.readUnsignedShortLE() * 0.05); + break; + case 0x3037: + position.set("cargoWeight", record.readUnsignedShortLE() * 2); + break; + case 0x4001: + position.set(Position.KEY_FUEL_USED, record.readUnsignedIntLE()); + break; + case 0x4002: + position.set(Position.KEY_HOURS, record.readUnsignedIntLE()); + break; + case 0x4003: + position.set(Position.KEY_ODOMETER, record.readUnsignedIntLE() * 5); + break; + case 0x4063: + position.set(Position.KEY_AXLE_WEIGHT, record.readUnsignedIntLE()); + break; + case 0x5000: + position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(record.readLongLE())); + break; + case 0x5004: + case 0x5005: + case 0x5006: + case 0x5007: + key = Position.PREFIX_TEMP + (sensorId - 0x5004 + 1); + position.set(key, record.readLongLE()); + break; + case 0x500D: + position.set("trailerId", String.valueOf(record.readLongLE())); + break; + case 0xA000: + position.set(Position.KEY_DEVICE_TEMP, record.readFloatLE()); + break; + case 0xA001: + position.set(Position.KEY_ACCELERATION, record.readFloatLE()); + break; + case 0xA002: + position.set("cornering", record.readFloatLE()); + break; + case 0xA017: + case 0xA018: + case 0xA019: + case 0xA01A: + key = Position.PREFIX_TEMP + (sensorId - 0xA017 + 1); + position.set(key, record.readFloatLE()); + break; + case 0xB002: + position.set(Position.KEY_OBD_ODOMETER, record.readDoubleLE()); + break; + default: + key = Position.PREFIX_IO + sensorId; + position.getAttributes().put(key, readValue(record, sensorType)); + break; + } + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -163,62 +313,7 @@ public class IotmProtocolDecoder extends BaseProtocolDecoder { continue; } - String key; - switch (sensorId) { - case 0x0008: - if (sensorType > 0) { - position.set(Position.KEY_ALARM, Position.ALARM_JAMMING); - } - break; - case 0x0010: - case 0x0011: - case 0x0012: - case 0x0013: - key = Position.PREFIX_IN + (sensorId - 0x0010 + 2); - position.set(key, sensorType > 0); - break; - case 0x001E: - position.set("buttonPresent", sensorType > 0); - break; - case 0x006D: - position.set(Position.KEY_IGNITION, sensorType > 0); - break; - case 0x3000: - position.set(Position.KEY_POWER, record.readUnsignedShortLE() * 0.001); - break; - case 0x3001: - case 0x3002: - case 0x3003: - key = Position.PREFIX_ADC + (0x3003 - sensorId + 3); - position.set(key, record.readUnsignedShortLE() * 0.001); - break; - case 0x3004: - position.set(Position.KEY_BATTERY, record.readUnsignedShortLE() * 0.001); - break; - case 0x300C: - position.set(Position.KEY_RPM, record.readUnsignedShortLE()); - break; - case 0x4003: - position.set(Position.KEY_ODOMETER, record.readUnsignedIntLE() * 5); - break; - case 0x5000: - position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(record.readLongLE())); - break; - case 0xA001: - position.set(Position.KEY_ACCELERATION, record.readFloatLE()); - break; - case 0xA002: - position.set("cornering", record.readFloatLE()); - break; - case 0xA017: - key = Position.PREFIX_TEMP + (sensorId - 0xA017 + 1); - position.set(key, record.readFloatLE()); - break; - default: - key = Position.PREFIX_IO + sensorId; - position.getAttributes().put(key, readValue(record, sensorType)); - break; - } + decodeSensor(position, record, sensorType, sensorId); } } diff --git a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java index 94c9a3038..9eed58347 100644 --- a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2021 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. @@ -28,6 +28,10 @@ import org.traccar.model.Network; import org.traccar.model.Position; import java.net.SocketAddress; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; import java.util.regex.Pattern; public class ItsProtocolDecoder extends BaseProtocolDecoder { @@ -75,7 +79,7 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { .number("(d+.?d*),") // power .number("(d+.?d*),") // battery .number("([01]),") // emergency - .expression("[CO]?,") // tamper + .expression("[COYN]?,") // tamper .expression("(.*),") // cells .number("([012]{4}),") // inputs .number("([01]{2}),") // outputs @@ -96,6 +100,12 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { .number("d+,") // index .number("(d+.?d*),") // adc1 .number("(d+.?d*),") // adc2 + .or() + .number("(d+.d+),") // adc1 + .number("(d+),") // odometer + .number("(d{6}),") // index + .expression("([^,]+),") // response format + .expression("([^,]+),") // response .groupEnd("?") .groupEnd("?") .or() @@ -137,8 +147,17 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { String sentence = (String) msg; - if (channel != null && sentence.startsWith("$,01,")) { - channel.writeAndFlush(new NetworkMessage("$,1,*", remoteAddress)); + if (channel != null) { + if (sentence.startsWith("$,01,")) { + channel.writeAndFlush(new NetworkMessage("$,1,*", remoteAddress)); + } else if (sentence.startsWith("$,LGN,")) { + DateFormat dateFormat = new SimpleDateFormat("ddMMyyyyHHmmss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + String time = dateFormat.format(new Date()); + channel.writeAndFlush(new NetworkMessage("$LGN" + time + "*", remoteAddress)); + } else if (sentence.startsWith("$,HBT,")) { + channel.writeAndFlush(new NetworkMessage("$HBT*", remoteAddress)); + } } Parser parser = new Parser(PATTERN, sentence); @@ -257,6 +276,14 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { position.set(Position.PREFIX_ADC + 2, parser.nextDouble()); } + if (parser.hasNext(5)) { + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.KEY_ODOMETER, parser.nextInt()); + position.set(Position.KEY_INDEX, parser.nextInt()); + position.set("responseFormat", parser.next()); + position.set("response", parser.next()); + } + if (parser.hasNext(2)) { position.setAltitude(parser.nextDouble()); position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); diff --git a/src/main/java/org/traccar/protocol/KhdProtocol.java b/src/main/java/org/traccar/protocol/KhdProtocol.java index f77f4c311..60a2aea7f 100644 --- a/src/main/java/org/traccar/protocol/KhdProtocol.java +++ b/src/main/java/org/traccar/protocol/KhdProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2021 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,13 @@ public class KhdProtocol extends BaseProtocol { public KhdProtocol() { setSupportedDataCommands( Command.TYPE_ENGINE_STOP, - Command.TYPE_ENGINE_RESUME); + Command.TYPE_ENGINE_RESUME, + Command.TYPE_GET_VERSION, + Command.TYPE_FACTORY_RESET, + Command.TYPE_SET_SPEED_LIMIT, + Command.TYPE_SET_ODOMETER, + Command.TYPE_POSITION_SINGLE); + addServer(new TrackerServer(false, getName()) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { diff --git a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java index 16ad616ed..251351a74 100644 --- a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java @@ -24,6 +24,7 @@ import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitUtil; import org.traccar.helper.Checksum; import org.traccar.helper.DateBuilder; import org.traccar.helper.UnitsConverter; @@ -65,6 +66,44 @@ public class KhdProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_SMS_ALARM_SWITCH = 0x86; public static final int MSG_PERIPHERAL = 0xA3; + private void decodeAlarmStatus(Position position, byte[] status) { + if (BitUtil.check(status[0], 4)) { + position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); + } else if (BitUtil.check(status[0], 6)) { + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_EXIT); + } else if (BitUtil.check(status[0], 7)) { + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_ENTER); + } else if (BitUtil.check(status[1], 0)) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } else if (BitUtil.check(status[1], 1)) { + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + } else if (BitUtil.check(status[1], 3)) { + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + } else if (BitUtil.check(status[1], 6)) { + position.set(Position.KEY_ALARM, Position.ALARM_TOW); + } else if (BitUtil.check(status[1], 7)) { + position.set(Position.KEY_ALARM, Position.ALARM_DOOR); + } else if (BitUtil.check(status[2], 2)) { + position.set(Position.KEY_ALARM, Position.ALARM_TEMPERATURE); + } else if (BitUtil.check(status[2], 4)) { + position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING); + } else if (BitUtil.check(status[2], 6)) { + position.set(Position.KEY_ALARM, Position.ALARM_FATIGUE_DRIVING); + } else if (BitUtil.check(status[2], 7)) { + position.set(Position.KEY_ALARM, Position.ALARM_IDLE); + } else if (BitUtil.check(status[6], 3)) { + position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION); + } else if (BitUtil.check(status[6], 4)) { + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + } else if (BitUtil.check(status[6], 5)) { + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + } else if (BitUtil.check(status[6], 6)) { + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + } else if (BitUtil.check(status[6], 7)) { + position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT); + } + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -125,11 +164,15 @@ public class KhdProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ODOMETER, buf.readUnsignedMedium()); position.set(Position.KEY_STATUS, buf.readUnsignedInt()); - position.set(Position.KEY_HDOP, buf.readUnsignedByte()); - position.set(Position.KEY_VDOP, buf.readUnsignedByte()); - position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); - buf.skipBytes(5); // other location data + buf.readUnsignedShort(); + buf.readUnsignedByte(); + buf.readUnsignedByte(); + buf.readUnsignedByte(); + buf.readUnsignedByte(); + buf.readUnsignedByte(); + + position.set(Position.KEY_RESULT, buf.readUnsignedByte()); if (type == MSG_PERIPHERAL) { @@ -174,6 +217,16 @@ public class KhdProtocolDecoder extends BaseProtocolDecoder { } + } else { + + buf.readUnsignedByte(); // overloaded state + buf.readUnsignedByte(); // logging status + + byte[] alarmStatus = new byte[8]; + buf.readBytes(alarmStatus); + + decodeAlarmStatus(position, alarmStatus); + } return position; diff --git a/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java b/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java index 4a8df26c8..8aeb9660d 100644 --- a/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2021 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,14 +24,19 @@ import org.traccar.Protocol; public class KhdProtocolEncoder extends BaseProtocolEncoder { + public static final int MSG_ON_DEMAND_TRACK = 0x30; public static final int MSG_CUT_OIL = 0x39; public static final int MSG_RESUME_OIL = 0x38; + public static final int MSG_CHECK_VERSION = 0x3D; + public static final int MSG_FACTORY_RESET = 0xC3; + public static final int MSG_SET_OVERSPEED = 0x3F; + public static final int MSG_DELETE_MILEAGE = 0x66; public KhdProtocolEncoder(Protocol protocol) { super(protocol); } - private ByteBuf encodeCommand(int command, String uniqueId) { + private ByteBuf encodeCommand(int command, String uniqueId, ByteBuf content) { ByteBuf buf = Unpooled.buffer(); @@ -39,7 +44,12 @@ public class KhdProtocolEncoder extends BaseProtocolEncoder { buf.writeByte(0x29); buf.writeByte(command); - buf.writeShort(6); // size + + int length = 6; + if (content != null) { + length += content.readableBytes(); + } + buf.writeShort(length); uniqueId = "00000000".concat(uniqueId); uniqueId = uniqueId.substring(uniqueId.length() - 8); @@ -48,6 +58,10 @@ public class KhdProtocolEncoder extends BaseProtocolEncoder { buf.writeByte(Integer.parseInt(uniqueId.substring(4, 6)) + 0x80); buf.writeByte(Integer.parseInt(uniqueId.substring(6, 8))); + if (content != null) { + buf.writeBytes(content); + } + buf.writeByte(Checksum.xor(buf.nioBuffer())); buf.writeByte(0x0D); // ending @@ -61,9 +75,21 @@ public class KhdProtocolEncoder extends BaseProtocolEncoder { switch (command.getType()) { case Command.TYPE_ENGINE_STOP: - return encodeCommand(MSG_CUT_OIL, uniqueId); + return encodeCommand(MSG_CUT_OIL, uniqueId, null); case Command.TYPE_ENGINE_RESUME: - return encodeCommand(MSG_RESUME_OIL, uniqueId); + return encodeCommand(MSG_RESUME_OIL, uniqueId, null); + case Command.TYPE_GET_VERSION: + return encodeCommand(MSG_CHECK_VERSION, uniqueId, null); + case Command.TYPE_FACTORY_RESET: + return encodeCommand(MSG_FACTORY_RESET, uniqueId, null); + case Command.TYPE_SET_SPEED_LIMIT: + ByteBuf content = Unpooled.buffer(); + content.writeByte(Integer.parseInt(command.getString(Command.KEY_DATA))); + return encodeCommand(MSG_RESUME_OIL, uniqueId, content); + case Command.TYPE_SET_ODOMETER: + return encodeCommand(MSG_DELETE_MILEAGE, uniqueId, null); + case Command.TYPE_POSITION_SINGLE: + return encodeCommand(MSG_ON_DEMAND_TRACK, uniqueId, null); default: return null; } diff --git a/src/main/java/org/traccar/protocol/Mavlink2Protocol.java b/src/main/java/org/traccar/protocol/Mavlink2Protocol.java new file mode 100644 index 000000000..d779648e4 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Mavlink2Protocol.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021 Chipeng Li (chplee@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 org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; + +public class Mavlink2Protocol extends BaseProtocol { + + public Mavlink2Protocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 1, 1, 10, 0)); + pipeline.addLast(new Mavlink2ProtocolDecoder(Mavlink2Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java new file mode 100644 index 000000000..431258388 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java @@ -0,0 +1,85 @@ +/* + * Copyright 2021 Chipeng Li (chplee@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.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; + +public class Mavlink2ProtocolDecoder extends BaseProtocolDecoder { + + public Mavlink2ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + if (buf.readUnsignedByte() != 0xFD) { + return null; + } + + buf.readUnsignedByte(); // length + buf.readUnsignedByte(); // incompatibility flags + buf.readUnsignedByte(); // compatibility flags + buf.readUnsignedByte(); // index + + int senderSystemId = buf.readUnsignedByte(); + buf.readUnsignedByte(); // component id + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, Integer.toString(senderSystemId)); + if (deviceSession == null) { + return null; + } + + int type = buf.readUnsignedMediumLE(); + if (type == 33) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set("timeBoot", buf.readUnsignedIntLE()); // time since system boot + + position.setValid(true); + position.setTime(new Date()); + position.setLatitude(buf.readIntLE() / 10000000.0); + position.setLongitude(buf.readIntLE() / 10000000.0); + position.setAltitude(buf.readIntLE() / 1000.0); + position.set("relativeAltitude", buf.readIntLE() / 1000.0); + + int groundSpeedX = buf.readShortLE(); + int groundSpeedY = buf.readShortLE(); + buf.readShortLE(); // ground speed z + double speed = Math.sqrt(Math.pow(groundSpeedX, 2) + Math.pow(groundSpeedY, 2)); + position.setSpeed(UnitsConverter.knotsFromCps(speed)); + + position.setCourse(buf.readUnsignedShortLE() / 100.0); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/MegastekProtocolDecoder.java b/src/main/java/org/traccar/protocol/MegastekProtocolDecoder.java index d81cc0eda..fae73d931 100644 --- a/src/main/java/org/traccar/protocol/MegastekProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MegastekProtocolDecoder.java @@ -275,9 +275,10 @@ public class MegastekProtocolDecoder extends BaseProtocolDecoder { .or().text(" ") .groupEnd("?").text(",") .number("(d+)?,") // rfid + .number("([01])(d)?").optional() // charge and belt status .expression("[^,]*,") .number("(d+)?,") // battery - .expression("([^,]*)") // alert + .expression("([^,]*)[,;]") // alert .any() .compile(); @@ -355,6 +356,13 @@ public class MegastekProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + if (parser.hasNext()) { + position.set(Position.KEY_CHARGE, parser.nextInt() > 0); + } + if (parser.hasNext()) { + position.set("belt", parser.nextInt()); + } + String battery = parser.next(); if (battery != null) { position.set(Position.KEY_BATTERY, Integer.parseInt(battery)); @@ -375,10 +383,11 @@ public class MegastekProtocolDecoder extends BaseProtocolDecoder { } } switch (value) { + case "pw on": case "poweron": return Position.ALARM_POWER_ON; case "poweroff": - return Position.ALARM_POWER_ON; + return Position.ALARM_POWER_OFF; case "sos": case "help": return Position.ALARM_SOS; @@ -390,12 +399,32 @@ public class MegastekProtocolDecoder extends BaseProtocolDecoder { case "low battery": case "lowbattery": return Position.ALARM_LOW_BATTERY; + case "low extern voltage": + return Position.ALARM_LOW_POWER; + case "gps cut": + return Position.ALARM_GPS_ANTENNA_CUT; case "vib": return Position.ALARM_VIBRATION; case "move in": return Position.ALARM_GEOFENCE_ENTER; case "move out": return Position.ALARM_GEOFENCE_EXIT; + case "corner": + return Position.ALARM_CORNERING; + case "fatigue": + return Position.ALARM_FATIGUE_DRIVING; + case "psd": + return Position.ALARM_POWER_CUT; + case "psr": + return Position.ALARM_POWER_RESTORED; + case "hit": + return Position.ALARM_SHOCK; + case "belt on": + case "belton": + return Position.ALARM_LOCK; + case "belt off": + case "beltoff": + return Position.ALARM_UNLOCK; case "error": return Position.ALARM_FAULT; default: diff --git a/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java index 5ea9f148c..992b5c43a 100644 --- a/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org) * Copyright 2020 Roeland Boeters (roeland@geodelta.com) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -168,8 +168,48 @@ public class MictrackProtocolDecoder extends BaseProtocolDecoder { if (sentence.startsWith("MT")) { return decodeStandard(channel, remoteAddress, sentence); - } else { + } else if (sentence.contains("$")) { return decodeLowAltitude(channel, remoteAddress, sentence); + } else { + return decodeResult(channel, remoteAddress, sentence); + } + } + + private Object decodeResult( + Channel channel, SocketAddress remoteAddress, String sentence) { + + if (sentence.matches("\\d{15} .+")) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, sentence.substring(0, 15)); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_RESULT, sentence.substring(16, sentence.length() - 1)); + + return position; + + } else { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_RESULT, sentence.substring(0, sentence.length() - 1)); + + return position; + } } @@ -184,6 +224,7 @@ public class MictrackProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); + position.set(Position.KEY_TYPE, Integer.parseInt(fragments[1])); switch (fragments[3]) { case "R0": diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java index 89217a39b..68e9e8dd5 100644 --- a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java @@ -189,13 +189,17 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { case 0x28: int beaconFlags = buf.readUnsignedByte(); position.set("tagId", ByteBufUtil.hexDump(buf.readSlice(6))); - buf.readUnsignedByte(); // rssi + position.set("tagRssi", buf.readUnsignedByte()); buf.readUnsignedByte(); // 1m rssi if (BitUtil.check(beaconFlags, 7)) { position.setLatitude(buf.readIntLE() * 0.0000001); position.setLongitude(buf.readIntLE() * 0.0000001); hasLocation = true; } + if (BitUtil.check(beaconFlags, 6)) { + position.set("description", buf.readCharSequence( + endIndex - buf.readerIndex(), StandardCharsets.US_ASCII).toString()); + } break; case 0x30: buf.readUnsignedInt(); // timestamp diff --git a/src/main/java/org/traccar/protocol/NavtelecomProtocol.java b/src/main/java/org/traccar/protocol/NavtelecomProtocol.java new file mode 100644 index 000000000..c76b42768 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NavtelecomProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021 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; + +public class NavtelecomProtocol extends BaseProtocol { + + public NavtelecomProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(256, 2, 1, 2, 0)); + pipeline.addLast(new Gt02ProtocolDecoder(NavtelecomProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java b/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java new file mode 100644 index 000000000..2362b1870 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021 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.Checksum; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; + +public class NavtelecomProtocolDecoder extends BaseProtocolDecoder { + + public NavtelecomProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(4); // preamble + int receiver = buf.readIntLE(); + int sender = buf.readIntLE(); + int length = buf.readUnsignedShortLE(); + buf.readUnsignedByte(); // data checksum + buf.readUnsignedByte(); // header checksum + + String sentence = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString(); + + if (sentence.startsWith("*>S")) { + + String data = "*<S"; + + ByteBuf response = Unpooled.buffer(); + response.writeCharSequence("@NTC", StandardCharsets.US_ASCII); + response.writeIntLE(sender); + response.writeIntLE(receiver); + response.writeShortLE(data.length()); + response.writeByte(Checksum.xor(data)); + response.writeByte(Checksum.xor(response.nioBuffer())); + response.writeCharSequence(data, StandardCharsets.US_ASCII); + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java index ea0966a5d..c9044fa2b 100644 --- a/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2021 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. @@ -83,6 +83,11 @@ public class PacificTrackProtocolDecoder extends BaseProtocolDecoder { position.setSpeed(UnitsConverter.knotsFromKph(BitUtil.to(speedAndCourse, 12) * 0.1)); position.set(Position.KEY_INDEX, buf.readUnsignedShort()); break; + case 0x20: + int voltage = buf.readUnsignedMedium(); + position.set(Position.KEY_BATTERY, BitUtil.between(voltage, 0, 12) * 0.01); + position.set(Position.KEY_POWER, BitUtil.between(voltage, 12, 24) * 0.01); + break; case 0x92: while (buf.readerIndex() < segmentEnd) { int field = buf.readUnsignedByte(); @@ -106,6 +111,24 @@ public class PacificTrackProtocolDecoder extends BaseProtocolDecoder { case 0b00001: position.set(Position.KEY_RPM, buf.readUnsignedByte() * 32); break; + case 0b00011: + position.set("oilPressure", buf.readUnsignedByte() * 4); + break; + case 0b00100: + position.set("oilLevel", buf.readUnsignedByte() * 0.4); + break; + case 0b00101: + position.set("oilTemp", buf.readUnsignedByte() - 40); + break; + case 0b00110: + position.set("coolantLevel", buf.readUnsignedByte() * 0.4); + break; + case 0b00111: + position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedByte() - 40); + break; + case 0b01000: + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte() * 0.4); + break; case 0b01001: position.set("defLevel", buf.readUnsignedByte() * 0.4); break; diff --git a/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java b/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java index 949c994ee..65de211ac 100644 --- a/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2021 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.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.Parser; @@ -75,6 +76,10 @@ public class PluginProtocolDecoder extends BaseProtocolDecoder { protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("$$hb,1#", remoteAddress)); + } + Parser parser = new Parser(PATTERN, (String) msg); if (!parser.matches()) { return null; @@ -98,7 +103,7 @@ public class PluginProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_SATELLITES, parser.nextInt()); position.set(Position.KEY_ODOMETER, (long) (parser.nextDouble() * 1000)); - int status = parser.nextInt(); + long status = parser.nextLong(); position.setValid(BitUtil.check(status, 0)); position.set(Position.KEY_IGNITION, BitUtil.check(status, 1)); for (int i = 0; i < 4; i++) { diff --git a/src/main/java/org/traccar/protocol/R12wProtocol.java b/src/main/java/org/traccar/protocol/R12wProtocol.java new file mode 100644 index 000000000..3726233b4 --- /dev/null +++ b/src/main/java/org/traccar/protocol/R12wProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021 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.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class R12wProtocol extends BaseProtocol { + + public R12wProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new R12wProtocolDecoder(R12wProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/R12wProtocolDecoder.java b/src/main/java/org/traccar/protocol/R12wProtocolDecoder.java new file mode 100644 index 000000000..d60318447 --- /dev/null +++ b/src/main/java/org/traccar/protocol/R12wProtocolDecoder.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021 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.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; + +import java.net.SocketAddress; + +public class R12wProtocolDecoder extends BaseProtocolDecoder { + + public R12wProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private void sendResponse(Channel channel, String type, String id, String data) { + if (channel != null) { + String sentence = String.format("$HX,%s,%s,%s,#", type, id, data); + sentence += String.format(",%02x,\r\n", Checksum.xor(sentence)); + channel.writeAndFlush(new NetworkMessage(sentence, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + String[] values = sentence.split(","); + String type = values[1]; + String id = values[2]; + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + if (type.equals("0001")) { + sendResponse(channel, "1001", id, values[3] + ",OK"); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java index 05601ed51..9e3261a04 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2021 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. @@ -43,6 +43,7 @@ public class RstProtocolDecoder extends BaseProtocolDecoder { .number("(d{9});") // serial number .number("(d+);") // index .number("(d+);") // type + .groupBegin() .number("(dd)-(dd)-(dddd) ") // event date .number("(dd):(dd):(dd);") // event time .number("(dd)-(dd)-(dddd) ") // fix date @@ -68,6 +69,7 @@ public class RstProtocolDecoder extends BaseProtocolDecoder { .number("x{4};") // sensors .number("(xx);") // status 1 .number("(xx);") // status 2 + .groupEnd("?") .any() .compile(); @@ -80,14 +82,14 @@ public class RstProtocolDecoder extends BaseProtocolDecoder { return null; } - String archive = parser.next(); + parser.next(); // archive String model = parser.next(); String firmware = parser.next(); String serial = parser.next(); int index = parser.nextInt(); - parser.nextInt(); // type + int type = parser.nextInt(); - if (channel != null && archive.equals("A")) { + if (channel != null) { String response = "RST;A;" + model + ";" + firmware + ";" + serial + ";" + index + ";6;FIM;"; channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } @@ -97,36 +99,44 @@ public class RstProtocolDecoder extends BaseProtocolDecoder { return null; } - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); - - position.setDeviceTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); - position.setFixTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); - position.setLatitude(parser.nextDouble()); - position.setLongitude(parser.nextDouble()); - position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); - position.setCourse(parser.nextInt()); - position.setAltitude(parser.nextInt()); - position.setValid(parser.nextInt() > 0); - - 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()); - position.set(Position.PREFIX_OUT + 1, parser.nextHexInt()); - position.set(Position.PREFIX_OUT + 2, parser.nextHexInt()); - position.set(Position.KEY_POWER, parser.nextDouble()); - position.set(Position.KEY_BATTERY, parser.nextDouble()); - position.set(Position.KEY_ODOMETER, parser.nextInt()); - position.set(Position.KEY_RSSI, parser.nextInt()); - position.set(Position.PREFIX_TEMP + 1, (int) parser.nextHexInt().byteValue()); - - int status = (parser.nextHexInt() << 8) + parser.nextHexInt(); - position.set(Position.KEY_IGNITION, BitUtil.check(status, 7)); - position.set(Position.KEY_STATUS, status); - - return position; + if (parser.hasNext()) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setDeviceTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setFixTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.setCourse(parser.nextInt()); + position.setAltitude(parser.nextInt()); + position.setValid(parser.nextInt() > 0); + + 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()); + position.set(Position.PREFIX_OUT + 1, parser.nextHexInt()); + position.set(Position.PREFIX_OUT + 2, parser.nextHexInt()); + position.set(Position.KEY_POWER, parser.nextDouble()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_ODOMETER, parser.nextInt()); + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, (int) parser.nextHexInt().byteValue()); + + int status = (parser.nextHexInt() << 8) + parser.nextHexInt(); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 7)); + position.set(Position.KEY_STATUS, status); + + return position; + + } else { + + return null; + + } } } diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java index 227a9ac91..b6378f416 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 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2021 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. @@ -110,6 +110,16 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { case 80: position.set(Position.PREFIX_TEMP + (id - 78), readValue(buf, length, true) * 0.1); break; + case 198: + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + break; + case 199: + case 200: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 201: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; default: position.set(Position.PREFIX_IO + id, readValue(buf, length, false)); break; diff --git a/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java b/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java index 6b97f5fe0..bf8bfab77 100644 --- a/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2021 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. @@ -38,12 +38,12 @@ public class SiwiProtocolDecoder extends BaseProtocolDecoder { .number("(d+),") // device id .number("d+,") // unit no .expression("([A-Z]),") // reason - .number("d+,") // command code + .number("d*,") // command code .number("[^,]*,") // command value .expression("([01]),") // ignition .expression("[01],") // power cut - .expression("[01],") // box open - .number("d+,") // message key + .number("d+,") // flags + .number("[^,]+,") .number("(d+),") // odometer .number("(d+),") // speed .number("(d+),") // satellites @@ -54,6 +54,19 @@ public class SiwiProtocolDecoder extends BaseProtocolDecoder { .number("(d+),") // course .number("(dd)(dd)(dd),") // time (hhmmss) .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("d+,") // signal strength + .number("d+,") // gsm status + .number("d+,") // error code + .number("d+,") // internal status + .number("(d+),") // battery + .number("(d+),") // adc + .number("(d+),") // digital inputs + .number("(d+),") // sensor 1 + .number("(d+),") // sensor 2 + .number("(d+),") // sensor 3 + .number("(d+),") // sensor 4 + .expression("([^,]+),") // hw version + .expression("([^,]+),") // sw version .any() .compile(); @@ -90,6 +103,20 @@ public class SiwiProtocolDecoder extends BaseProtocolDecoder { position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY, "IST")); + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001); + position.set(Position.PREFIX_ADC + 1, parser.nextInt() * 0.01); + position.set(Position.KEY_INPUT, parser.nextInt()); + + for (int i = 1; i <= 4; i++) { + int value = parser.nextInt(); + if (value != 0) { + position.set(Position.PREFIX_IO + i, value); + } + } + + position.set(Position.KEY_VERSION_HW, parser.next()); + position.set(Position.KEY_VERSION_FW, parser.next()); + return position; } diff --git a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java index 882bed81b..82f0e4061 100644 --- a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2021 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. @@ -193,6 +193,12 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { case "#TVI#": position.set(Position.KEY_DEVICE_TEMP, Double.parseDouble(data[i])); break; + case "#CFL#": + position.set(Position.KEY_FUEL_LEVEL, Integer.parseInt(data[i])); + break; + case "#CFL2#": + position.set("fuel2", Integer.parseInt(data[i])); + break; case "#IN1#": case "#IN2#": case "#IN3#": diff --git a/src/main/java/org/traccar/protocol/StartekProtocol.java b/src/main/java/org/traccar/protocol/StartekProtocol.java new file mode 100644 index 000000000..32f1c5a29 --- /dev/null +++ b/src/main/java/org/traccar/protocol/StartekProtocol.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021 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.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class StartekProtocol extends BaseProtocol { + + public StartekProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_OUTPUT_CONTROL, + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StartekProtocolEncoder(StartekProtocol.this)); + pipeline.addLast(new StartekProtocolDecoder(StartekProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java new file mode 100644 index 000000000..1cc69c6e6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java @@ -0,0 +1,171 @@ +/* + * Copyright 2021 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.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class StartekProtocolDecoder extends BaseProtocolDecoder { + + public StartekProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("&&") + .expression(".") // index + .number("d+,") // length + .number("(d+),") // imei + .number("xxx,") // command + .number("(d+),") // event + .expression("([^,]+)?,") // event data + .number("(dd)(dd)(dd)") // date (yyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([AV]),") // valid + .number("(-?d+.d+),") // longitude + .number("(-?d+.d+),") // latitude + .number("(d+),") // satellites + .number("(d+.d+),") // hdop + .number("(d+),") // speed + .number("(d+),") // course + .number("(-?d+),") // altitude + .number("(d+),") // odometer + .number("(d+)|") // mcc + .number("(d+)|") // mnc + .number("(x+)|") // lac + .number("(x+),") // cid + .number("(d+),") // rssi + .number("(x+),") // status + .number("(x+),") // inputs + .number("(x+),") // outputs + .number("(x+)|") // power + .number("(x+)|") // battery + .expression("([^,]+),") // adc + .number("d,") // extended + .expression("([^,]+)?,") // fuel + .expression("([^,]+)?") // temperature + .number("xx") // checksum + .compile(); + + private String decodeAlarm(int value) { + switch (value) { + case 5: + case 6: + return Position.ALARM_DOOR; + case 39: + return Position.ALARM_ACCELERATION; + case 40: + return Position.ALARM_BRAKING; + case 41: + return Position.ALARM_CORNERING; + default: + return null; + } + } + + @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()); + + int event = parser.nextInt(); + String eventData = parser.next(); + position.set(Position.KEY_EVENT, event); + if (event == 53) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, eventData); + } else { + position.set(Position.KEY_ALARM, decodeAlarm(event)); + } + + position.setTime(parser.nextDateTime()); + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_HDOP, parser.nextDouble()); + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.setCourse(parser.nextInt()); + position.setAltitude(parser.nextInt()); + + position.set(Position.KEY_ODOMETER, parser.nextInt()); + + position.setNetwork(new Network(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt(), parser.nextInt()))); + + position.set(Position.KEY_STATUS, parser.nextHexInt()); + position.set(Position.KEY_INPUT, parser.nextHexInt()); + position.set(Position.KEY_OUTPUT, parser.nextHexInt()); + + position.set(Position.KEY_POWER, parser.nextHexInt() * 0.01); + position.set(Position.KEY_BATTERY, parser.nextHexInt() * 0.01); + + String[] adc = parser.next().split("\\|"); + for (int i = 0; i < adc.length; i++) { + position.set(Position.PREFIX_ADC + (i + 1), Integer.parseInt(adc[i], 16) * 0.01); + } + + if (parser.hasNext()) { + String[] fuels = parser.next().split("\\|"); + for (String fuel : fuels) { + int index = Integer.parseInt(fuel.substring(0, 2)); + int value = Integer.parseInt(fuel.substring(2), 16); + position.set("fuel" + index, value * 0.1); + } + } + + if (parser.hasNext()) { + String[] temperatures = parser.next().split("\\|"); + for (String temperature : temperatures) { + int index = Integer.parseInt(temperature.substring(0, 2)); + int value = Integer.parseInt(temperature.substring(2), 16); + double convertedValue = BitUtil.to(value, 15); + if (BitUtil.check(value, 15)) { + convertedValue = -convertedValue; + } + position.set(Position.PREFIX_TEMP + index, convertedValue * 0.1); + } + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/StartekProtocolEncoder.java b/src/main/java/org/traccar/protocol/StartekProtocolEncoder.java new file mode 100644 index 000000000..011a8dfae --- /dev/null +++ b/src/main/java/org/traccar/protocol/StartekProtocolEncoder.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021 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.Protocol; +import org.traccar.StringProtocolEncoder; +import org.traccar.helper.Checksum; +import org.traccar.model.Command; + +public class StartekProtocolEncoder extends StringProtocolEncoder { + + public StartekProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected String formatCommand(Command command, String format, String... keys) { + String uniqueId = getUniqueId(command.getDeviceId()); + String payload = super.formatCommand(command, format, keys); + int length = 1 + uniqueId.length() + 1 + payload.length(); + String sentence = "$$:" + length + "," + uniqueId + "," + payload; + return sentence + Checksum.sum(sentence) + "\r\n"; + } + + @Override + protected Object encodeCommand(Channel channel, Command command) { + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return formatCommand(command, "%s", Command.KEY_DATA); + case Command.TYPE_OUTPUT_CONTROL: + return formatCommand(command, "900,%s,%s", Command.KEY_INDEX, Command.KEY_DATA); + case Command.TYPE_ENGINE_STOP: + return formatCommand(command, "900,1,1"); + case Command.TYPE_ENGINE_RESUME: + return formatCommand(command, "900,1,0"); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java index 76e3e6ecc..d8710a899 100644 --- a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java @@ -356,6 +356,8 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { totalFuel += fuel2; position.set("fuel2", fuel2); } + } else if (attribute.startsWith("GTSL")) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, attribute.split("\\|")[4]); } else { String[] pair = attribute.split("="); if (pair.length >= 2) { diff --git a/src/main/java/org/traccar/protocol/TaipPrefixEncoder.java b/src/main/java/org/traccar/protocol/TaipPrefixEncoder.java new file mode 100644 index 000000000..02c111b01 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TaipPrefixEncoder.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 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.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageEncoder; +import org.traccar.Context; +import org.traccar.Protocol; +import org.traccar.config.Keys; + +import java.util.List; + +@ChannelHandler.Sharable +public class TaipPrefixEncoder extends MessageToMessageEncoder<ByteBuf> { + + private final Protocol protocol; + + public TaipPrefixEncoder(Protocol protocol) { + this.protocol = protocol; + } + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception { + if (Context.getConfig().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()); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/TaipProtocol.java b/src/main/java/org/traccar/protocol/TaipProtocol.java index b8f40a183..0966cfd7c 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 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2021 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,6 +29,7 @@ public class TaipProtocol extends BaseProtocol { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '<')); + pipeline.addLast(new TaipPrefixEncoder(TaipProtocol.this)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new TaipProtocolDecoder(TaipProtocol.this)); @@ -37,6 +38,7 @@ public class TaipProtocol extends BaseProtocol { addServer(new TrackerServer(true, getName()) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new TaipPrefixEncoder(TaipProtocol.this)); 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 6f71b6c08..ec0ce1931 100644 --- a/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java @@ -313,7 +313,7 @@ public class TaipProtocolDecoder extends BaseProtocolDecoder { if (messageIndex != null) { String response; if (messageIndex.startsWith("#IP")) { - response = "\u0020\u0020\u0006\u0000>SAK;ID=" + uniqueId + ";" + messageIndex + "<"; + response = ">SAK;ID=" + uniqueId + ";" + messageIndex + "<"; } else { response = ">ACK;ID=" + uniqueId + ";" + messageIndex + ";*"; response += String.format("%02X", Checksum.xor(response)) + "<"; diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java index 716589e00..6ba183f9b 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2021 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. @@ -312,6 +312,11 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { break; } break; + case 389: + if (BitUtil.between(readValue(buf, length, false), 4, 8) == 1) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + break; default: position.set(Position.PREFIX_IO + id, readValue(buf, length, false)); break; @@ -537,7 +542,11 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { int id = buf.readUnsignedShort(); int length = buf.readUnsignedShort(); if (id == 256) { - position.set(Position.KEY_VIN, buf.readSlice(length).toString(StandardCharsets.US_ASCII)); + position.set(Position.KEY_VIN, + buf.readSlice(length).toString(StandardCharsets.US_ASCII)); + } else if (id == 281) { + position.set(Position.KEY_DTCS, + buf.readSlice(length).toString(StandardCharsets.US_ASCII).replace(',', ' ')); } else if (id == 385) { ByteBuf data = buf.readSlice(length); data.readUnsignedByte(); // data part @@ -617,19 +626,17 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { } if (channel != null && codec != CODEC_12 && codec != CODEC_13) { + ByteBuf response = Unpooled.buffer(); if (connectionless) { - ByteBuf response = Unpooled.buffer(); response.writeShort(5); response.writeShort(0); response.writeByte(0x01); response.writeByte(locationPacketId); response.writeByte(count); - channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } else { - ByteBuf response = Unpooled.buffer(); response.writeInt(count); - channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } return positions.isEmpty() ? null : positions; diff --git a/src/main/java/org/traccar/protocol/ThinkPowerProtocol.java b/src/main/java/org/traccar/protocol/ThinkPowerProtocol.java new file mode 100644 index 000000000..ece1b0544 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ThinkPowerProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021 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; + +public class ThinkPowerProtocol extends BaseProtocol { + + public ThinkPowerProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 2, 2, 0)); + pipeline.addLast(new ThinkPowerProtocolDecoder(ThinkPowerProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ThinkPowerProtocolDecoder.java b/src/main/java/org/traccar/protocol/ThinkPowerProtocolDecoder.java new file mode 100644 index 000000000..b3f943078 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ThinkPowerProtocolDecoder.java @@ -0,0 +1,182 @@ +/* + * Copyright 2021 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.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class ThinkPowerProtocolDecoder extends BaseProtocolDecoder { + + public ThinkPowerProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN_REQUEST = 0x01; + public static final int MSG_LOGIN_RESPONSE = 0x02; + public static final int MSG_HEARTBEAT_REQUEST = 0x03; + public static final int MSG_HEARTBEAT_RESPONSE = 0x04; + public static final int MSG_RECORD_REPORT = 0x05; + public static final int MSG_RECORD_RESPONSE = 0x06; + + private void sendResponse(Channel channel, int type, int index, ByteBuf content) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(type); + response.writeByte(index); + if (content != null) { + response.writeShort(content.readableBytes()); + response.writeBytes(content); + content.release(); + } else { + response.writeShort(0); + } + response.writeShort(Checksum.crc16(Checksum.CRC16_CCITT_FALSE, response.nioBuffer())); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + private void decodeValue(Position position, int type, ByteBuf buf) { + switch (type) { + case 0x01: + position.setValid(true); + position.setLatitude(buf.readInt() * 0.0000001); + position.setLongitude(buf.readInt() * 0.0000001); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() * 0.1)); + position.setCourse(buf.readUnsignedShort() * 0.01); + break; + case 0x02: + position.setValid(buf.readUnsignedByte() > 0); + break; + case 0x03: + buf.skipBytes(3); // geofence + break; + case 0x06: + case 0x07: + case 0x08: + buf.skipBytes(2); // g-sensor x/y/z + break; + case 0x09: + buf.readUnsignedByte(); // collision alarm + break; + case 0x0A: + buf.readUnsignedByte(); // drop alarm + break; + case 0x10: + if (buf.readUnsignedByte() > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + break; + case 0x12: + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.1); + break; + case 0x13: + if (buf.readUnsignedByte() > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + } + break; + case 0x16: + buf.readUnsignedShort(); // temperature + break; + case 0x17: + buf.readUnsignedByte(); // humidity + break; + case 0x18: + buf.readUnsignedShort(); // high temperature + break; + case 0x19: + buf.readUnsignedByte(); // high humidity + break; + case 0x50: + if (buf.readUnsignedByte() > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_REMOVING); + } + break; + case 0x51: + if (buf.readUnsignedByte() > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING); + } + break; + default: + break; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int type = buf.readUnsignedByte(); + int index = buf.readUnsignedByte(); + buf.readUnsignedShort(); // length + + if (type == MSG_LOGIN_REQUEST) { + + buf.readUnsignedByte(); // protocol major version + buf.readUnsignedByte(); // protocol minor version + + String id = buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + + ByteBuf content = Unpooled.buffer(); + content.writeByte(deviceSession != null ? 0 : 4); + sendResponse(channel, MSG_LOGIN_RESPONSE, index, content); + + } else if (type == MSG_HEARTBEAT_REQUEST) { + + sendResponse(channel, MSG_HEARTBEAT_RESPONSE, index, null); + + } else if (type == MSG_RECORD_REPORT) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + buf.readUnsignedByte(); // count + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + + while (buf.readableBytes() > 2) { + decodeValue(position, buf.readUnsignedByte(), buf); + } + + sendResponse(channel, MSG_RECORD_RESPONSE, index, null); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java index 5dad1458d..ff33cb103 100644 --- a/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java @@ -60,9 +60,9 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder { .groupEnd() .number("(dd)(dd)(dd),?") // date (mmddyy if comma-delimited, otherwise yyddmm) .expression("([AV]),?") // validity - .number(" *(d+)(dd.d+)") // latitude + .number(" *(d*)(dd.d+)") // latitude .expression("([NS]),?") - .number(" *(d+)(dd.d+)") // longitude + .number(" *(d*)(dd.d+)") // longitude .expression("([EW]),?") .number("([ d.]{1,5})(?:d*,)?") // speed .number("(dd)(dd)(dd),?") // time (hhmmss) diff --git a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java index 3ce6438f8..ad7dfa886 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 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2021 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,9 @@ import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; +import org.traccar.model.Network; import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; import java.net.SocketAddress; import java.util.LinkedList; @@ -51,10 +53,11 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder { .compile(); private static final Pattern PATTERN_POSITION = new PatternBuilder() - .number("#(x+)?") // cell info + .text("#") + .number("(?:(dd)|x*)") // cell or voltage .text("$GPRMC,") .number("(dd)(dd)(dd).d+,") // time (hhmmss.sss) - .expression("([AV]),") // validity + .expression("([AVL]),") // validity .number("(d+)(dd.d+),") // latitude .expression("([NS]),") .number("(d+)(dd.d+),") // longitude @@ -65,6 +68,18 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder { .any() .compile(); + private static final Pattern PATTERN_WIFI = new PatternBuilder() + .text("#") + .number("(?:(dd)|x+)") // cell or voltage + .text("$WIFI,") + .number("(dd)(dd)(dd).d+,") // time (hhmmss.sss) + .expression("[AVL],") // validity + .expression("(.*)") // access points + .number("(dd)(dd)(dd)") // date (ddmmyy) + .text("*") + .number("xx") // checksum + .compile(); + private void decodeStatus(Position position, String status) { switch (status) { case "AUTOSTART": @@ -141,38 +156,74 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder { List<Position> positions = new LinkedList<>(); for (String message : messages) { - parser = new Parser(PATTERN_POSITION, message); - if (parser.matches()) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (message.contains("$GPRMC")) { + + parser = new Parser(PATTERN_POSITION, message); + if (parser.matches()) { + + if (parser.hasNext()) { + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.1); + } + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); - parser.next(); // base station info + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); - DateBuilder dateBuilder = new DateBuilder() - .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + } else { + continue; + } - position.setValid(parser.next().equals("A")); - position.setLatitude(parser.nextCoordinate()); - position.setLongitude(parser.nextCoordinate()); - position.setSpeed(parser.nextDouble(0)); - position.setCourse(parser.nextDouble(0)); + } else if (message.contains("$WIFI")) { - dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); - position.setTime(dateBuilder.getDate()); + parser = new Parser(PATTERN_WIFI, message); + if (parser.matches()) { - position.set(Position.KEY_DOOR, door); - position.set(Position.PREFIX_ADC + 1, adc); - position.set(Position.KEY_POWER, power); - position.set(Position.KEY_BATTERY, battery); - position.set(Position.PREFIX_TEMP + 1, temperature); - decodeStatus(position, status); + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.1); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + String[] values = parser.next().split(","); + Network network = new Network(); + for (int i = 0; i < values.length / 2; i++) { + String mac = values[i * 2 + 1].replaceAll("(..)", "$1:").substring(0, 17); + network.addWifiAccessPoint(WifiAccessPoint.from(mac, Integer.parseInt(values[i * 2]))); + } + position.setNetwork(network); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + getLastLocation(position, dateBuilder.getDate()); + } + + } else { + + getLastLocation(position, null); - positions.add(position); } + + position.set(Position.KEY_DOOR, door); + position.set(Position.PREFIX_ADC + 1, adc); + position.set(Position.KEY_POWER, power); + position.set(Position.KEY_BATTERY, battery); + position.set(Position.PREFIX_TEMP + 1, temperature); + decodeStatus(position, status); + + positions.add(position); } - return positions; + return positions.isEmpty() ? null : positions; } } diff --git a/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java index 4042e348c..87db95946 100644 --- a/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2021 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,6 +24,7 @@ import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; import org.traccar.helper.UnitsConverter; import org.traccar.model.CellTower; @@ -151,6 +152,21 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { Gt06ProtocolDecoder.decodeGps(position, buf, false, TimeZone.getTimeZone("UTC")); + if (buf.readableBytes() >= 5) { + position.setAltitude(buf.readShort()); + + int alarms = buf.readUnsignedByte(); + if (BitUtil.check(alarms, 0)) { + position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION); + } + if (BitUtil.check(alarms, 1)) { + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + } + if (BitUtil.check(alarms, 4)) { + position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); + } + } + ByteBuf content = Unpooled.buffer(); content.writeBytes(time); sendResponse(channel, length, type, content); @@ -174,9 +190,18 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_VERSION_FW, buf.readUnsignedByte()); buf.readUnsignedByte(); // timezone int interval = buf.readUnsignedByte(); - if (length >= 7) { + if (buf.readableBytes() >= 1 + 2) { position.set(Position.KEY_RSSI, buf.readUnsignedByte()); } + if (buf.readableBytes() >= 3 + 2) { + buf.skipBytes(3); // temperature + } + if (buf.readableBytes() >= 1 + 2) { + position.set(Position.KEY_CHARGE, buf.readUnsignedByte() > 0); + } + if (buf.readableBytes() >= 1 + 2) { + position.set(Position.KEY_HEART_RATE, buf.readUnsignedByte()); + } ByteBuf content = Unpooled.buffer(); content.writeByte(interval); diff --git a/src/main/java/org/traccar/protocol/Tr20Protocol.java b/src/main/java/org/traccar/protocol/Tr20Protocol.java index 3eee9d9c3..1b71db03f 100644 --- a/src/main/java/org/traccar/protocol/Tr20Protocol.java +++ b/src/main/java/org/traccar/protocol/Tr20Protocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,14 @@ public class Tr20Protocol extends BaseProtocol { pipeline.addLast(new Tr20ProtocolDecoder(Tr20Protocol.this)); } }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Tr20ProtocolDecoder(Tr20Protocol.this)); + } + }); } } diff --git a/src/main/java/org/traccar/protocol/Tr20ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tr20ProtocolDecoder.java index c2e6c381f..2f11bd152 100644 --- a/src/main/java/org/traccar/protocol/Tr20ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Tr20ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2021 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. @@ -52,7 +52,7 @@ public class Tr20ProtocolDecoder extends BaseProtocolDecoder { .number("(ddd)(dd.d+),") // longitude .number("(d+),") // speed .number("(d+),") // course - .number("(?:NA|[FC]?(-?d+)),") // temperature + .number("(?:NA|[FC]?(-?d+)[^,]*),") // temperature .number("(x{8}),") // status .number("(d+)") // event .any() diff --git a/src/main/java/org/traccar/protocol/UlbotechProtocol.java b/src/main/java/org/traccar/protocol/UlbotechProtocol.java index b99ec1cc6..dfe5235f0 100644 --- a/src/main/java/org/traccar/protocol/UlbotechProtocol.java +++ b/src/main/java/org/traccar/protocol/UlbotechProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2021 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,14 +18,18 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.model.Command; public class UlbotechProtocol extends BaseProtocol { public UlbotechProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM); addServer(new TrackerServer(false, getName()) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { pipeline.addLast(new UlbotechFrameDecoder()); + pipeline.addLast(new UlbotechProtocolEncoder(UlbotechProtocol.this)); pipeline.addLast(new UlbotechProtocolDecoder(UlbotechProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/UlbotechProtocolEncoder.java b/src/main/java/org/traccar/protocol/UlbotechProtocolEncoder.java new file mode 100644 index 000000000..5528c7242 --- /dev/null +++ b/src/main/java/org/traccar/protocol/UlbotechProtocolEncoder.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021 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 org.traccar.BaseProtocolEncoder; +import org.traccar.Protocol; +import org.traccar.model.Command; + +import java.nio.charset.StandardCharsets; + +public class UlbotechProtocolEncoder extends BaseProtocolEncoder { + + public UlbotechProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return Unpooled.copiedBuffer( + "*TS01," + command.getString(Command.KEY_DATA) + "#", StandardCharsets.US_ASCII); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/UuxProtocol.java b/src/main/java/org/traccar/protocol/UuxProtocol.java new file mode 100644 index 000000000..41b59d829 --- /dev/null +++ b/src/main/java/org/traccar/protocol/UuxProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021 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; + +public class UuxProtocol extends BaseProtocol { + + public UuxProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 1)); + pipeline.addLast(new UuxProtocolDecoder(UuxProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/UuxProtocolDecoder.java b/src/main/java/org/traccar/protocol/UuxProtocolDecoder.java new file mode 100644 index 000000000..cb8656545 --- /dev/null +++ b/src/main/java/org/traccar/protocol/UuxProtocolDecoder.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021 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.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Calendar; + +public class UuxProtocolDecoder extends BaseProtocolDecoder { + + public UuxProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_IMMOBILIZER = 0x9E; + public static final int MSG_ACK = 0xD0; + public static final int MSG_NACK = 0xF0; + + private void sendResponse(Channel channel, int productCode, int protocolVersion, int type) { + if (channel != null && BitUtil.check(protocolVersion, 7)) { + ByteBuf response = Unpooled.buffer(); + response.writeShort(productCode); + response.writeByte(BitUtil.to(protocolVersion, 7)); + response.writeByte(1); // length + response.writeByte(type); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int productCode = buf.readUnsignedShort(); + int protocolVersion = buf.readUnsignedByte(); + buf.readUnsignedByte(); // length + int type = buf.readUnsignedByte(); + + String vehicleId = buf.readCharSequence(10, StandardCharsets.US_ASCII).toString(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, vehicleId); + if (deviceSession == null) { + sendResponse(channel, productCode, protocolVersion, MSG_NACK); + return null; + } + + if (type == MSG_IMMOBILIZER) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(Calendar.getInstance().get(Calendar.YEAR), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + + getLastLocation(position, dateBuilder.getDate()); + + position.set("companyId", buf.readCharSequence(6, StandardCharsets.US_ASCII).toString()); + position.set("tripId", buf.readUnsignedShort()); + + return position; + + } + + sendResponse(channel, productCode, protocolVersion, MSG_ACK); + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java b/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java index 223d032b8..80299ff08 100644 --- a/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2021 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. @@ -49,12 +49,12 @@ public class WialonProtocolDecoder extends BaseProtocolDecoder { private static final Pattern PATTERN = new PatternBuilder() .number("(dd)(dd)(dd);") // date (ddmmyy) .number("(dd)(dd)(dd);") // time (hhmmss) - .number("(dd)(dd.d+);") // latitude - .expression("([NS]);") - .number("(ddd)(dd.d+);") // longitude - .expression("([EW]);") + .number("(?:NA|(dd)(dd.d+));") // latitude + .expression("(?:NA|([NS]));") + .number("(?:NA|(ddd)(dd.d+));") // longitude + .expression("(?:NA|([EW]));") .number("(d+.?d*)?;") // speed - .number("(d+.?d*)?;") // course + .number("(?:NA|(d+.?d*))?;") // course .number("(?:NA|(-?d+.?d*));") // altitude .number("(?:NA|(d+))") // satellites .groupBegin().text(";") @@ -97,11 +97,15 @@ public class WialonProtocolDecoder extends BaseProtocolDecoder { position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); - position.setLatitude(parser.nextCoordinate()); - position.setLongitude(parser.nextCoordinate()); - position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); - position.setCourse(parser.nextDouble(0)); - position.setAltitude(parser.nextDouble(0)); + if (parser.hasNext(9)) { + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + } else { + getLastLocation(position, position.getDeviceTime()); + } if (parser.hasNext()) { int satellites = parser.nextInt(0); diff --git a/src/main/java/org/traccar/sms/SnsSmsClient.java b/src/main/java/org/traccar/sms/SnsSmsClient.java new file mode 100644 index 000000000..bdd4104f5 --- /dev/null +++ b/src/main/java/org/traccar/sms/SnsSmsClient.java @@ -0,0 +1,82 @@ +/* + * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 Subodh Ranadive (subodhranadive3103@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.sms; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.handlers.AsyncHandler; +import com.amazonaws.services.sns.AmazonSNSAsync; +import com.amazonaws.services.sns.AmazonSNSAsyncClientBuilder; +import com.amazonaws.services.sns.model.MessageAttributeValue; +import com.amazonaws.services.sns.model.PublishRequest; + +import com.amazonaws.services.sns.model.PublishResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.config.Keys; + +import java.util.HashMap; +import java.util.Map; + +public class SnsSmsClient implements SmsManager { + private static final Logger LOGGER = LoggerFactory.getLogger(SnsSmsClient.class); + + private final AmazonSNSAsync snsClient; + + public SnsSmsClient() { + if (Context.getConfig().hasKey(Keys.SMS_AWS_REGION) + && Context.getConfig().hasKey(Keys.SMS_AWS_ACCESS) + && Context.getConfig().hasKey(Keys.SMS_AWS_SECRET)) { + BasicAWSCredentials awsCredentials = + new BasicAWSCredentials(Context.getConfig().getString(Keys.SMS_AWS_ACCESS), + Context.getConfig().getString(Keys.SMS_AWS_SECRET)); + snsClient = AmazonSNSAsyncClientBuilder.standard() + .withRegion(Context.getConfig().getString(Keys.SMS_AWS_REGION)) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)).build(); + } else { + throw new RuntimeException("SNS Not Configured Properly. Please provide valid config."); + } + } + + @Override + public void sendMessageSync(String destAddress, String message, boolean command) { + Map<String, MessageAttributeValue> smsAttributes = new HashMap<>(); + smsAttributes.put("AWS.SNS.SMS.SenderID", + new MessageAttributeValue().withStringValue("SNS").withDataType("String")); + smsAttributes.put("AWS.SNS.SMS.SMSType", + new MessageAttributeValue().withStringValue("Transactional").withDataType("String")); + + PublishRequest publishRequest = new PublishRequest().withMessage(message) + .withPhoneNumber(destAddress).withMessageAttributes(smsAttributes); + + snsClient.publishAsync(publishRequest, new AsyncHandler<PublishRequest, PublishResult>() { + @Override + public void onError(Exception exception) { + LOGGER.error("SMS send failed", exception); + } + @Override + public void onSuccess(PublishRequest request, PublishResult result) { + } + }); + } + + @Override + public void sendMessageAsync(String destAddress, String message, boolean command) { + sendMessageSync(destAddress, message, command); + } +} diff --git a/src/main/java/org/traccar/web/WebServer.java b/src/main/java/org/traccar/web/WebServer.java index 82e9397e4..ffa06adfd 100644 --- a/src/main/java/org/traccar/web/WebServer.java +++ b/src/main/java/org/traccar/web/WebServer.java @@ -29,6 +29,7 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.glassfish.jersey.jackson.JacksonFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.servlet.ServletContainer; @@ -149,6 +150,7 @@ public class WebServer { private void initApi(Config config, ServletContextHandler servletHandler) { servletHandler.addServlet(new ServletHolder(new AsyncSocketServlet()), "/api/socket"); + JettyWebSocketServletContainerInitializer.configure(servletHandler, null); String mediaPath = config.getString(Keys.MEDIA_PATH); if (mediaPath != null) { diff --git a/src/main/resources/ical4j.properties b/src/main/resources/ical4j.properties new file mode 100644 index 000000000..a08674709 --- /dev/null +++ b/src/main/resources/ical4j.properties @@ -0,0 +1 @@ +net.fortuna.ical4j.timezone.cache.impl=net.fortuna.ical4j.util.MapTimeZoneCache |