diff options
Diffstat (limited to 'src/main')
111 files changed, 2592 insertions, 618 deletions
diff --git a/src/main/java/org/traccar/BasePipelineFactory.java b/src/main/java/org/traccar/BasePipelineFactory.java index 5a5850a86..ffce45342 100644 --- a/src/main/java/org/traccar/BasePipelineFactory.java +++ b/src/main/java/org/traccar/BasePipelineFactory.java @@ -26,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.config.Keys; import org.traccar.handler.DefaultDataHandler; +import org.traccar.handler.SpeedLimitHandler; import org.traccar.handler.TimeHandler; import org.traccar.handler.events.AlertEventHandler; import org.traccar.handler.events.CommandResultEventHandler; @@ -58,7 +59,7 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { private final TrackerServer server; private final String protocol; - private boolean eventsEnabled; + private final boolean eventsEnabled; private int timeout; public BasePipelineFactory(TrackerServer server, String protocol) { @@ -133,6 +134,7 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { pipeline, FilterHandler.class, GeocoderHandler.class, + SpeedLimitHandler.class, MotionHandler.class, CopyAttributesHandler.class, EngineHoursHandler.class, diff --git a/src/main/java/org/traccar/BaseProtocolDecoder.java b/src/main/java/org/traccar/BaseProtocolDecoder.java index 99f3bc627..50e80a457 100644 --- a/src/main/java/org/traccar/BaseProtocolDecoder.java +++ b/src/main/java/org/traccar/BaseProtocolDecoder.java @@ -104,7 +104,7 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { } private DeviceSession channelDeviceSession; // connection-based protocols - private Map<SocketAddress, DeviceSession> addressDeviceSessions = new HashMap<>(); // connectionless protocols + private final Map<SocketAddress, DeviceSession> addressDeviceSessions = new HashMap<>(); // connectionless protocols private long findDeviceId(SocketAddress remoteAddress, String... uniqueIds) { if (uniqueIds.length > 0) { diff --git a/src/main/java/org/traccar/Context.java b/src/main/java/org/traccar/Context.java index 9c20db9e4..af7e03837 100644 --- a/src/main/java/org/traccar/Context.java +++ b/src/main/java/org/traccar/Context.java @@ -40,6 +40,7 @@ import org.traccar.database.MaintenancesManager; import org.traccar.database.MediaManager; import org.traccar.database.NotificationManager; import org.traccar.database.PermissionsManager; +import org.traccar.schedule.ScheduleManager; import org.traccar.database.UsersManager; import org.traccar.geocoder.Geocoder; import org.traccar.helper.Log; @@ -165,6 +166,12 @@ public final class Context { return serverManager; } + private static ScheduleManager scheduleManager; + + public static ScheduleManager getScheduleManager() { + return scheduleManager; + } + private static GeofenceManager geofenceManager; public static GeofenceManager getGeofenceManager() { @@ -332,6 +339,7 @@ public final class Context { } serverManager = new ServerManager(); + scheduleManager = new ScheduleManager(); if (config.getBoolean("event.forward.enable")) { eventForwarder = new JsonTypeEventForwarder(); diff --git a/src/main/java/org/traccar/ExtendedObjectDecoder.java b/src/main/java/org/traccar/ExtendedObjectDecoder.java index 681924e87..46720da52 100644 --- a/src/main/java/org/traccar/ExtendedObjectDecoder.java +++ b/src/main/java/org/traccar/ExtendedObjectDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.ReferenceCountUtil; +import org.traccar.config.Keys; import org.traccar.helper.DataConverter; import org.traccar.model.Position; @@ -31,14 +32,14 @@ import java.util.Collection; public abstract class ExtendedObjectDecoder extends ChannelInboundHandlerAdapter { private void saveOriginal(Object decodedMessage, Object originalMessage) { - if (Context.getConfig().getBoolean("database.saveOriginal") && decodedMessage instanceof Position) { + if (Context.getConfig().getBoolean(Keys.DATABASE_SAVE_ORIGINAL) && decodedMessage instanceof Position) { Position position = (Position) decodedMessage; if (originalMessage instanceof ByteBuf) { ByteBuf buf = (ByteBuf) originalMessage; - position.set(Position.KEY_ORIGINAL, ByteBufUtil.hexDump(buf)); + position.set(Position.KEY_ORIGINAL, ByteBufUtil.hexDump(buf, 0, buf.writerIndex())); } else if (originalMessage instanceof String) { position.set(Position.KEY_ORIGINAL, DataConverter.printHex( - ((String) originalMessage).getBytes(StandardCharsets.US_ASCII))); + ((String) originalMessage).getBytes(StandardCharsets.US_ASCII))); } } } diff --git a/src/main/java/org/traccar/Main.java b/src/main/java/org/traccar/Main.java index a0d93dbc8..59afab3a5 100644 --- a/src/main/java/org/traccar/Main.java +++ b/src/main/java/org/traccar/Main.java @@ -144,6 +144,7 @@ public final class Main { if (Context.getWebServer() != null) { Context.getWebServer().start(); } + Context.getScheduleManager().start(); scheduleHealthCheck(); scheduleDatabaseCleanup(); @@ -153,6 +154,7 @@ public final class Main { Runtime.getRuntime().addShutdownHook(new Thread(() -> { LOGGER.info("Shutting down server..."); + Context.getScheduleManager().stop(); if (Context.getWebServer() != null) { Context.getWebServer().stop(); } diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java index de6ec5b87..e751cb453 100644 --- a/src/main/java/org/traccar/MainModule.java +++ b/src/main/java/org/traccar/MainModule.java @@ -61,6 +61,7 @@ import org.traccar.handler.GeolocationHandler; import org.traccar.handler.HemisphereHandler; import org.traccar.handler.MotionHandler; import org.traccar.handler.RemoteAddressHandler; +import org.traccar.handler.SpeedLimitHandler; import org.traccar.handler.TimeHandler; import org.traccar.handler.events.AlertEventHandler; import org.traccar.handler.events.CommandResultEventHandler; @@ -76,6 +77,8 @@ import org.traccar.reports.model.TripsConfig; import javax.annotation.Nullable; import javax.ws.rs.client.Client; import io.netty.util.Timer; +import org.traccar.speedlimit.OverpassSpeedLimitProvider; +import org.traccar.speedlimit.SpeedLimitProvider; public class MainModule extends AbstractModule { @@ -211,6 +214,21 @@ public class MainModule extends AbstractModule { @Singleton @Provides + public static SpeedLimitProvider provideSpeedLimitProvider(Config config) { + if (config.getBoolean(Keys.SPEED_LIMIT_ENABLE)) { + String type = config.getString(Keys.SPEED_LIMIT_TYPE, "overpass"); + String url = config.getString(Keys.SPEED_LIMIT_URL); + switch (type) { + case "overpass": + default: + return new OverpassSpeedLimitProvider(url); + } + } + return null; + } + + @Singleton + @Provides public static DistanceHandler provideDistanceHandler(Config config, IdentityManager identityManager) { return new DistanceHandler(config, identityManager); } @@ -275,6 +293,15 @@ public class MainModule extends AbstractModule { @Singleton @Provides + public static SpeedLimitHandler provideSpeedLimitHandler(@Nullable SpeedLimitProvider speedLimitProvider) { + if (speedLimitProvider != null) { + return new SpeedLimitHandler(speedLimitProvider); + } + return null; + } + + @Singleton + @Provides public static MotionHandler provideMotionHandler(TripsConfig tripsConfig) { return new MotionHandler(tripsConfig.getSpeedThreshold()); } diff --git a/src/main/java/org/traccar/WebDataHandler.java b/src/main/java/org/traccar/WebDataHandler.java index 3be575b48..a7ea0b2a5 100644 --- a/src/main/java/org/traccar/WebDataHandler.java +++ b/src/main/java/org/traccar/WebDataHandler.java @@ -74,7 +74,7 @@ public class WebDataHandler extends BaseDataHandler { private final int retryCount; private final int retryLimit; - private AtomicInteger deliveryPending; + private final AtomicInteger deliveryPending; @Inject public WebDataHandler( @@ -117,7 +117,7 @@ public class WebDataHandler extends BaseDataHandler { f.format("%1$td%1$tm%1$ty,,", calendar); } - s.append(Checksum.nmea(s.toString())); + s.append(Checksum.nmea(s.substring(1))); return s.toString(); } diff --git a/src/main/java/org/traccar/WindowsService.java b/src/main/java/org/traccar/WindowsService.java index 4a8955608..831cd3ebc 100644 --- a/src/main/java/org/traccar/WindowsService.java +++ b/src/main/java/org/traccar/WindowsService.java @@ -38,16 +38,14 @@ public abstract class WindowsService { private final Object waitObject = new Object(); - private String serviceName; - private ServiceMain serviceMain; - private ServiceControl serviceControl; + private final String serviceName; private SERVICE_STATUS_HANDLE serviceStatusHandle; public WindowsService(String serviceName) { this.serviceName = serviceName; } - public boolean install( + public void install( String displayName, String description, String[] dependencies, String account, String password, String config) throws URISyntaxException { @@ -60,7 +58,6 @@ public abstract class WindowsService { + " -jar \"" + jar.getAbsolutePath() + "\"" + " --service \"" + config + "\""; - boolean success = false; StringBuilder dep = new StringBuilder(); if (dependencies != null) { @@ -84,29 +81,25 @@ public abstract class WindowsService { null, null, dep.toString(), account, password); if (service != null) { - success = ADVAPI_32.ChangeServiceConfig2(service, Winsvc.SERVICE_CONFIG_DESCRIPTION, desc); + ADVAPI_32.ChangeServiceConfig2(service, Winsvc.SERVICE_CONFIG_DESCRIPTION, desc); ADVAPI_32.CloseServiceHandle(service); } ADVAPI_32.CloseServiceHandle(serviceManager); } - return success; } - public boolean uninstall() { - boolean success = false; - + public void uninstall() { SC_HANDLE serviceManager = openServiceControlManager(null, Winsvc.SC_MANAGER_ALL_ACCESS); if (serviceManager != null) { SC_HANDLE service = ADVAPI_32.OpenService(serviceManager, serviceName, Winsvc.SERVICE_ALL_ACCESS); if (service != null) { - success = ADVAPI_32.DeleteService(service); + ADVAPI_32.DeleteService(service); ADVAPI_32.CloseServiceHandle(service); } ADVAPI_32.CloseServiceHandle(serviceManager); } - return success; } public boolean start() { @@ -152,7 +145,7 @@ public abstract class WindowsService { POSIXFactory.getPOSIX().chdir(path); - serviceMain = new ServiceMain(); + ServiceMain serviceMain = new ServiceMain(); SERVICE_TABLE_ENTRY entry = new SERVICE_TABLE_ENTRY(); entry.lpServiceName = serviceName; entry.lpServiceProc = serviceMain; @@ -180,7 +173,7 @@ public abstract class WindowsService { private class ServiceMain implements SERVICE_MAIN_FUNCTION { public void callback(int dwArgc, Pointer lpszArgv) { - serviceControl = new ServiceControl(); + ServiceControl serviceControl = new ServiceControl(); serviceStatusHandle = ADVAPI_32.RegisterServiceCtrlHandlerEx(serviceName, serviceControl, null); reportStatus(Winsvc.SERVICE_START_PENDING, WinError.NO_ERROR, 3000); @@ -194,7 +187,8 @@ public abstract class WindowsService { synchronized (waitObject) { waitObject.wait(); } - } catch (InterruptedException ex) { + } catch (InterruptedException e) { + e.printStackTrace(); } reportStatus(Winsvc.SERVICE_STOPPED, WinError.NO_ERROR, 0); diff --git a/src/main/java/org/traccar/WrapperInboundHandler.java b/src/main/java/org/traccar/WrapperInboundHandler.java index ca33d021f..5e2b1d304 100644 --- a/src/main/java/org/traccar/WrapperInboundHandler.java +++ b/src/main/java/org/traccar/WrapperInboundHandler.java @@ -20,7 +20,7 @@ import io.netty.channel.ChannelInboundHandler; public class WrapperInboundHandler implements ChannelInboundHandler { - private ChannelInboundHandler handler; + private final ChannelInboundHandler handler; public ChannelInboundHandler getWrappedHandler() { return handler; diff --git a/src/main/java/org/traccar/WrapperOutboundHandler.java b/src/main/java/org/traccar/WrapperOutboundHandler.java index 0136c5b22..ae2b06ad2 100644 --- a/src/main/java/org/traccar/WrapperOutboundHandler.java +++ b/src/main/java/org/traccar/WrapperOutboundHandler.java @@ -23,7 +23,7 @@ import java.net.SocketAddress; public class WrapperOutboundHandler implements ChannelOutboundHandler { - private ChannelOutboundHandler handler; + private final ChannelOutboundHandler handler; public ChannelOutboundHandler getWrappedHandler() { return handler; diff --git a/src/main/java/org/traccar/api/AsyncSocket.java b/src/main/java/org/traccar/api/AsyncSocket.java index 906d16b5b..b2ff5031a 100644 --- a/src/main/java/org/traccar/api/AsyncSocket.java +++ b/src/main/java/org/traccar/api/AsyncSocket.java @@ -39,7 +39,7 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U private static final String KEY_POSITIONS = "positions"; private static final String KEY_EVENTS = "events"; - private long userId; + private final long userId; public AsyncSocket(long userId) { this.userId = userId; diff --git a/src/main/java/org/traccar/api/AsyncSocketServlet.java b/src/main/java/org/traccar/api/AsyncSocketServlet.java index 9318b6fc6..51fbfb478 100644 --- a/src/main/java/org/traccar/api/AsyncSocketServlet.java +++ b/src/main/java/org/traccar/api/AsyncSocketServlet.java @@ -15,9 +15,6 @@ */ package org.traccar.api; -import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; -import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; -import org.eclipse.jetty.websocket.servlet.WebSocketCreator; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; import org.traccar.Context; @@ -30,15 +27,12 @@ public class AsyncSocketServlet extends WebSocketServlet { @Override public void configure(WebSocketServletFactory factory) { factory.getPolicy().setIdleTimeout(Context.getConfig().getLong("web.timeout", ASYNC_TIMEOUT)); - factory.setCreator(new WebSocketCreator() { - @Override - public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) { - if (req.getSession() != null) { - long userId = (Long) req.getSession().getAttribute(SessionResource.USER_ID_KEY); - return new AsyncSocket(userId); - } else { - return null; - } + factory.setCreator((req, resp) -> { + if (req.getSession() != null) { + long userId = (Long) req.getSession().getAttribute(SessionResource.USER_ID_KEY); + return new AsyncSocket(userId); + } else { + return null; } }); } diff --git a/src/main/java/org/traccar/api/BaseObjectResource.java b/src/main/java/org/traccar/api/BaseObjectResource.java index c4206f385..bc7d3c466 100644 --- a/src/main/java/org/traccar/api/BaseObjectResource.java +++ b/src/main/java/org/traccar/api/BaseObjectResource.java @@ -44,7 +44,7 @@ import org.traccar.model.User; public abstract class BaseObjectResource<T extends BaseModel> extends BaseResource { - private Class<T> baseClass; + private final Class<T> baseClass; public BaseObjectResource(Class<T> baseClass) { this.baseClass = baseClass; diff --git a/src/main/java/org/traccar/api/DateParameterConverterProvider.java b/src/main/java/org/traccar/api/DateParameterConverterProvider.java new file mode 100644 index 000000000..f07ece984 --- /dev/null +++ b/src/main/java/org/traccar/api/DateParameterConverterProvider.java @@ -0,0 +1,52 @@ +/* + * Copyright 2020 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; + +import org.traccar.helper.DateUtil; + +import javax.ws.rs.ext.ParamConverter; +import javax.ws.rs.ext.ParamConverterProvider; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Date; + +public class DateParameterConverterProvider implements ParamConverterProvider { + + public static class DateParameterConverter implements ParamConverter<Date> { + + @Override + public Date fromString(String value) { + return value != null ? DateUtil.parseDate(value) : null; + } + + @Override + public String toString(Date value) { + return value != null ? DateUtil.formatDate(value) : null; + } + + } + + @Override + public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) { + if (Date.class.equals(rawType)) { + @SuppressWarnings("unchecked") + ParamConverter<T> paramConverter = (ParamConverter<T>) new DateParameterConverter(); + return paramConverter; + } + return null; + } + +} diff --git a/src/main/java/org/traccar/api/HealthCheckService.java b/src/main/java/org/traccar/api/HealthCheckService.java index 1e8f0d731..c9ce5a6c9 100644 --- a/src/main/java/org/traccar/api/HealthCheckService.java +++ b/src/main/java/org/traccar/api/HealthCheckService.java @@ -20,6 +20,7 @@ import com.sun.jna.Native; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.Context; +import org.traccar.config.Keys; import java.util.TimerTask; @@ -33,7 +34,7 @@ public class HealthCheckService { private long period; public HealthCheckService() { - if (Context.getConfig().getBoolean("web.healthCheck") + if (!Context.getConfig().getBoolean(Keys.WEB_DISABLE_HEALTH_CHECK) && System.getProperty("os.name").toLowerCase().startsWith("linux")) { try { systemD = Native.load("systemd", SystemD.class); diff --git a/src/main/java/org/traccar/api/UserPrincipal.java b/src/main/java/org/traccar/api/UserPrincipal.java index 80e92c2dd..175bca391 100644 --- a/src/main/java/org/traccar/api/UserPrincipal.java +++ b/src/main/java/org/traccar/api/UserPrincipal.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import java.security.Principal; public class UserPrincipal implements Principal { - private long userId; + private final long userId; public UserPrincipal(long userId) { this.userId = userId; diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java index f9c9a139d..7006cdb84 100644 --- a/src/main/java/org/traccar/api/resource/DeviceResource.java +++ b/src/main/java/org/traccar/api/resource/DeviceResource.java @@ -52,7 +52,7 @@ public class DeviceResource extends BaseObjectResource<Device> { @QueryParam("uniqueId") List<String> uniqueIds, @QueryParam("id") List<Long> deviceIds) throws SQLException { DeviceManager deviceManager = Context.getDeviceManager(); - Set<Long> result = null; + Set<Long> result; if (all) { if (Context.getPermissionsManager().getUserAdmin(getUserId())) { result = deviceManager.getAllItems(); diff --git a/src/main/java/org/traccar/api/resource/PositionResource.java b/src/main/java/org/traccar/api/resource/PositionResource.java index 67aa6dd32..e93feaccf 100644 --- a/src/main/java/org/traccar/api/resource/PositionResource.java +++ b/src/main/java/org/traccar/api/resource/PositionResource.java @@ -17,7 +17,6 @@ package org.traccar.api.resource; import org.traccar.Context; import org.traccar.api.BaseResource; -import org.traccar.helper.DateUtil; import org.traccar.model.Position; import org.traccar.web.CsvBuilder; import org.traccar.web.GpxBuilder; @@ -35,6 +34,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.List; @Path("positions") @@ -50,7 +50,7 @@ public class PositionResource extends BaseResource { @GET public Collection<Position> getJson( @QueryParam("deviceId") long deviceId, @QueryParam("id") List<Long> positionIds, - @QueryParam("from") String from, @QueryParam("to") String to) + @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { if (!positionIds.isEmpty()) { ArrayList<Position> positions = new ArrayList<>(); @@ -65,8 +65,7 @@ public class PositionResource extends BaseResource { } else { Context.getPermissionsManager().checkDevice(getUserId(), deviceId); if (from != null && to != null) { - return Context.getDataManager().getPositions( - deviceId, DateUtil.parseDate(from), DateUtil.parseDate(to)); + return Context.getDataManager().getPositions(deviceId, from, to); } else { return Collections.singleton(Context.getDeviceManager().getLastPosition(deviceId)); } @@ -76,25 +75,23 @@ public class PositionResource extends BaseResource { @GET @Produces(TEXT_CSV) public Response getCsv( - @QueryParam("deviceId") long deviceId, @QueryParam("from") String from, @QueryParam("to") String to) + @QueryParam("deviceId") long deviceId, @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { Context.getPermissionsManager().checkDevice(getUserId(), deviceId); CsvBuilder csv = new CsvBuilder(); csv.addHeaderLine(new Position()); - csv.addArray(Context.getDataManager().getPositions( - deviceId, DateUtil.parseDate(from), DateUtil.parseDate(to))); + csv.addArray(Context.getDataManager().getPositions(deviceId, from, to)); return Response.ok(csv.build()).header(HttpHeaders.CONTENT_DISPOSITION, CONTENT_DISPOSITION_VALUE_CSV).build(); } @GET @Produces(GPX) public Response getGpx( - @QueryParam("deviceId") long deviceId, @QueryParam("from") String from, @QueryParam("to") String to) + @QueryParam("deviceId") long deviceId, @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { Context.getPermissionsManager().checkDevice(getUserId(), deviceId); GpxBuilder gpx = new GpxBuilder(Context.getIdentityManager().getById(deviceId).getName()); - gpx.addPositions(Context.getDataManager().getPositions( - deviceId, DateUtil.parseDate(from), DateUtil.parseDate(to))); + gpx.addPositions(Context.getDataManager().getPositions(deviceId, from, to)); return Response.ok(gpx.build()).header(HttpHeaders.CONTENT_DISPOSITION, CONTENT_DISPOSITION_VALUE_GPX).build(); } diff --git a/src/main/java/org/traccar/api/resource/ReportResource.java b/src/main/java/org/traccar/api/resource/ReportResource.java index d371cf987..7347bfd64 100644 --- a/src/main/java/org/traccar/api/resource/ReportResource.java +++ b/src/main/java/org/traccar/api/resource/ReportResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.sql.SQLException; import java.util.Collection; +import java.util.Date; import java.util.List; import javax.activation.DataHandler; @@ -39,7 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.Context; import org.traccar.api.BaseResource; -import org.traccar.helper.DateUtil; +import org.traccar.helper.LogAction; import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.reports.Events; @@ -97,9 +98,9 @@ public class ReportResource extends BaseResource { @GET public Collection<Position> getRoute( @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { - return Route.getObjects(getUserId(), deviceIds, groupIds, - DateUtil.parseDate(from), DateUtil.parseDate(to)); + @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { + LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds); + return Route.getObjects(getUserId(), deviceIds, groupIds, from, to); } @Path("route") @@ -107,11 +108,11 @@ public class ReportResource extends BaseResource { @Produces(XLSX) public Response getRouteExcel( @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") String from, @QueryParam("to") String to, @QueryParam("mail") boolean mail) + @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) throws SQLException, IOException { return executeReport(getUserId(), mail, stream -> { - Route.getExcel(stream, getUserId(), deviceIds, groupIds, - DateUtil.parseDate(from), DateUtil.parseDate(to)); + LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds); + Route.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } @@ -120,9 +121,9 @@ public class ReportResource extends BaseResource { public Collection<Event> getEvents( @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, @QueryParam("type") final List<String> types, - @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { - return Events.getObjects(getUserId(), deviceIds, groupIds, types, - DateUtil.parseDate(from), DateUtil.parseDate(to)); + @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { + LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds); + return Events.getObjects(getUserId(), deviceIds, groupIds, types, from, to); } @Path("events") @@ -131,11 +132,11 @@ public class ReportResource extends BaseResource { public Response getEventsExcel( @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, @QueryParam("type") final List<String> types, - @QueryParam("from") String from, @QueryParam("to") String to, @QueryParam("mail") boolean mail) + @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) throws SQLException, IOException { return executeReport(getUserId(), mail, stream -> { - Events.getExcel(stream, getUserId(), deviceIds, groupIds, types, - DateUtil.parseDate(from), DateUtil.parseDate(to)); + LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds); + Events.getExcel(stream, getUserId(), deviceIds, groupIds, types, from, to); }); } @@ -143,9 +144,10 @@ public class ReportResource extends BaseResource { @GET public Collection<SummaryReport> getSummary( @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { - return Summary.getObjects(getUserId(), deviceIds, groupIds, - DateUtil.parseDate(from), DateUtil.parseDate(to)); + @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily) + throws SQLException { + LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds); + return Summary.getObjects(getUserId(), deviceIds, groupIds, from, to, daily); } @Path("summary") @@ -153,11 +155,12 @@ public class ReportResource extends BaseResource { @Produces(XLSX) public Response getSummaryExcel( @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") String from, @QueryParam("to") String to, @QueryParam("mail") boolean mail) + @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily, + @QueryParam("mail") boolean mail) throws SQLException, IOException { return executeReport(getUserId(), mail, stream -> { - Summary.getExcel(stream, getUserId(), deviceIds, groupIds, - DateUtil.parseDate(from), DateUtil.parseDate(to)); + LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds); + Summary.getExcel(stream, getUserId(), deviceIds, groupIds, from, to, daily); }); } @@ -166,9 +169,9 @@ public class ReportResource extends BaseResource { @Produces(MediaType.APPLICATION_JSON) public Collection<TripReport> getTrips( @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { - return Trips.getObjects(getUserId(), deviceIds, groupIds, - DateUtil.parseDate(from), DateUtil.parseDate(to)); + @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { + LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds); + return Trips.getObjects(getUserId(), deviceIds, groupIds, from, to); } @Path("trips") @@ -176,11 +179,11 @@ public class ReportResource extends BaseResource { @Produces(XLSX) public Response getTripsExcel( @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") String from, @QueryParam("to") String to, @QueryParam("mail") boolean mail) + @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) throws SQLException, IOException { return executeReport(getUserId(), mail, stream -> { - Trips.getExcel(stream, getUserId(), deviceIds, groupIds, - DateUtil.parseDate(from), DateUtil.parseDate(to)); + LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds); + Trips.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } @@ -189,9 +192,9 @@ public class ReportResource extends BaseResource { @Produces(MediaType.APPLICATION_JSON) public Collection<StopReport> getStops( @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { - return Stops.getObjects(getUserId(), deviceIds, groupIds, - DateUtil.parseDate(from), DateUtil.parseDate(to)); + @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { + LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds); + return Stops.getObjects(getUserId(), deviceIds, groupIds, from, to); } @Path("stops") @@ -199,11 +202,11 @@ public class ReportResource extends BaseResource { @Produces(XLSX) public Response getStopsExcel( @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") String from, @QueryParam("to") String to, @QueryParam("mail") boolean mail) + @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) throws SQLException, IOException { return executeReport(getUserId(), mail, stream -> { - Stops.getExcel(stream, getUserId(), deviceIds, groupIds, - DateUtil.parseDate(from), DateUtil.parseDate(to)); + LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds); + Stops.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java index e7cad2a0c..91488afff 100644 --- a/src/main/java/org/traccar/api/resource/ServerResource.java +++ b/src/main/java/org/traccar/api/resource/ServerResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 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,8 +38,12 @@ public class ServerResource extends BaseResource { @PermitAll @GET - public Server get() throws SQLException { - return Context.getPermissionsManager().getServer(); + public Server get(@QueryParam("force") boolean force) throws SQLException { + if (force) { + return Context.getDataManager().getServer(); + } else { + return Context.getPermissionsManager().getServer(); + } } @PUT diff --git a/src/main/java/org/traccar/api/resource/StatisticsResource.java b/src/main/java/org/traccar/api/resource/StatisticsResource.java index e801d4ff3..58073e7d1 100644 --- a/src/main/java/org/traccar/api/resource/StatisticsResource.java +++ b/src/main/java/org/traccar/api/resource/StatisticsResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.traccar.api.resource; import org.traccar.Context; import org.traccar.api.BaseResource; -import org.traccar.helper.DateUtil; import org.traccar.model.Statistics; import javax.ws.rs.Consumes; @@ -28,6 +27,7 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import java.sql.SQLException; import java.util.Collection; +import java.util.Date; @Path("statistics") @Produces(MediaType.APPLICATION_JSON) @@ -36,9 +36,9 @@ public class StatisticsResource extends BaseResource { @GET public Collection<Statistics> get( - @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { + @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { Context.getPermissionsManager().checkAdmin(getUserId()); - return Context.getDataManager().getStatistics(DateUtil.parseDate(from), DateUtil.parseDate(to)); + return Context.getDataManager().getStatistics(from, to); } } diff --git a/src/main/java/org/traccar/api/resource/UserResource.java b/src/main/java/org/traccar/api/resource/UserResource.java index 0b42d8d92..813ace6d6 100644 --- a/src/main/java/org/traccar/api/resource/UserResource.java +++ b/src/main/java/org/traccar/api/resource/UserResource.java @@ -48,7 +48,7 @@ public class UserResource extends BaseObjectResource<User> { @GET public Collection<User> get(@QueryParam("userId") long userId) throws SQLException { UsersManager usersManager = Context.getUsersManager(); - Set<Long> result = null; + Set<Long> result; if (Context.getPermissionsManager().getUserAdmin(getUserId())) { if (userId != 0) { result = usersManager.getUserItems(userId); diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 441898feb..77b42943b 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 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 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. @@ -76,6 +76,12 @@ public final class Keys { "extra.handlers", String.class); /** + * Store original HEX or string data as "raw" attribute in the corresponding position. + */ + public static final ConfigKey DATABASE_SAVE_ORIGINAL = new ConfigKey( + "database.saveOriginal", Boolean.class); + + /** * Enable positions forwarding to other web server. */ public static final ConfigKey FORWARD_ENABLE = new ConfigKey( @@ -389,6 +395,24 @@ public final class Keys { "geolocation.processInvalidPositions", Boolean.class); /** + * Boolean flag to enable speed limit API to get speed limit values depending on location. Default value is false. + */ + public static final ConfigKey SPEED_LIMIT_ENABLE = new ConfigKey( + "speedLimit.enable", Boolean.class); + + /** + * Provider to use for speed limit. Available options: overpass. By default overpass is used. + */ + public static final ConfigKey SPEED_LIMIT_TYPE = new ConfigKey( + "speedLimit.type", String.class); + + /** + * Speed limit provider API URL address. + */ + public static final ConfigKey SPEED_LIMIT_URL = new ConfigKey( + "speedLimit.url", String.class); + + /** * Override latitude sign / hemisphere. Useful in cases where value is incorrect because of device bug. Value can be * N for North or S for South. */ @@ -423,6 +447,19 @@ public final class Keys { public static final ConfigKey WEB_REQUEST_LOG_RETAIN_DAYS = new ConfigKey( "web.requestLog.retainDays", Integer.class); + /** + * Disable systemd health checks. + */ + public static final ConfigKey WEB_DISABLE_HEALTH_CHECK = new ConfigKey( + "web.disableHealthCheck", Boolean.class); + + /** + * Sets SameSite cookie attribute value. + * Supported options: Lax, Strict, None. + */ + public static final ConfigKey WEB_SAME_SITE_COOKIE = new ConfigKey( + "web.sameSiteCookie", String.class); + private Keys() { } diff --git a/src/main/java/org/traccar/database/BaseObjectManager.java b/src/main/java/org/traccar/database/BaseObjectManager.java index d0024c005..be6310033 100644 --- a/src/main/java/org/traccar/database/BaseObjectManager.java +++ b/src/main/java/org/traccar/database/BaseObjectManager.java @@ -38,8 +38,8 @@ public class BaseObjectManager<T extends BaseModel> { private final DataManager dataManager; + private final Class<T> baseClass; private Map<Long, T> items; - private Class<T> baseClass; protected BaseObjectManager(DataManager dataManager, Class<T> baseClass) { this.dataManager = dataManager; diff --git a/src/main/java/org/traccar/database/CommandsManager.java b/src/main/java/org/traccar/database/CommandsManager.java index 485402807..843c89e82 100644 --- a/src/main/java/org/traccar/database/CommandsManager.java +++ b/src/main/java/org/traccar/database/CommandsManager.java @@ -41,7 +41,7 @@ public class CommandsManager extends ExtendedObjectManager<Command> { private final Map<Long, Queue<Command>> deviceQueues = new ConcurrentHashMap<>(); - private boolean queueing; + private final boolean queueing; public CommandsManager(DataManager dataManager, boolean queueing) { super(dataManager, Command.class); diff --git a/src/main/java/org/traccar/database/ConnectionManager.java b/src/main/java/org/traccar/database/ConnectionManager.java index 4d43bc71b..9342fd3de 100644 --- a/src/main/java/org/traccar/database/ConnectionManager.java +++ b/src/main/java/org/traccar/database/ConnectionManager.java @@ -17,7 +17,6 @@ package org.traccar.database; import io.netty.channel.Channel; import io.netty.util.Timeout; -import io.netty.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.Context; @@ -122,12 +121,9 @@ public class ConnectionManager { } if (status.equals(Device.STATUS_ONLINE)) { - timeouts.put(deviceId, GlobalTimer.getTimer().newTimeout(new TimerTask() { - @Override - public void run(Timeout timeout) { - if (!timeout.isCancelled()) { - updateDevice(deviceId, Device.STATUS_UNKNOWN, null); - } + timeouts.put(deviceId, GlobalTimer.getTimer().newTimeout(timeout1 -> { + if (!timeout1.isCancelled()) { + updateDevice(deviceId, Device.STATUS_UNKNOWN, null); } }, deviceTimeout, TimeUnit.MILLISECONDS)); } diff --git a/src/main/java/org/traccar/database/DataManager.java b/src/main/java/org/traccar/database/DataManager.java index 8e9071736..8b9690f77 100644 --- a/src/main/java/org/traccar/database/DataManager.java +++ b/src/main/java/org/traccar/database/DataManager.java @@ -79,7 +79,7 @@ public class DataManager { private boolean generateQueries; - private boolean forceLdap; + private final boolean forceLdap; public DataManager(Config config) throws Exception { this.config = config; diff --git a/src/main/java/org/traccar/database/DeviceManager.java b/src/main/java/org/traccar/database/DeviceManager.java index fe17f7ced..e621e6058 100644 --- a/src/main/java/org/traccar/database/DeviceManager.java +++ b/src/main/java/org/traccar/database/DeviceManager.java @@ -45,11 +45,11 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity private final Config config; private final long dataRefreshDelay; - private boolean lookupGroupsAttribute; + private final boolean lookupGroupsAttribute; private Map<String, Device> devicesByUniqueId; private Map<String, Device> devicesByPhone; - private AtomicLong devicesLastUpdate = new AtomicLong(); + private final AtomicLong devicesLastUpdate = new AtomicLong(); private final Map<Long, Position> positions = new ConcurrentHashMap<>(); diff --git a/src/main/java/org/traccar/database/PermissionsManager.java b/src/main/java/org/traccar/database/PermissionsManager.java index ced0df1c0..d2dc62394 100644 --- a/src/main/java/org/traccar/database/PermissionsManager.java +++ b/src/main/java/org/traccar/database/PermissionsManager.java @@ -196,7 +196,7 @@ public class PermissionsManager { public void checkDeviceLimit(long userId) throws SecurityException { int deviceLimit = getUser(userId).getDeviceLimit(); if (deviceLimit != -1) { - int deviceCount = 0; + int deviceCount; if (getUserManager(userId)) { deviceCount = Context.getDeviceManager().getAllManagedItems(userId).size(); } else { diff --git a/src/main/java/org/traccar/database/QueryBuilder.java b/src/main/java/org/traccar/database/QueryBuilder.java index 5528b2320..084d2940b 100644 --- a/src/main/java/org/traccar/database/QueryBuilder.java +++ b/src/main/java/org/traccar/database/QueryBuilder.java @@ -114,11 +114,7 @@ public final class QueryBuilder { name = name.toLowerCase(); // Add to list - List<Integer> indexList = paramMap.get(name); - if (indexList == null) { - indexList = new LinkedList<>(); - paramMap.put(name, indexList); - } + List<Integer> indexList = paramMap.computeIfAbsent(name, k -> new LinkedList<>()); indexList.add(index); index++; @@ -318,96 +314,72 @@ public final class QueryBuilder { final Class<?> parameterType, final Method method, final String name) { if (parameterType.equals(boolean.class)) { - processors.add(new ResultSetProcessor<T>() { - @Override - public void process(T object, ResultSet resultSet) throws SQLException { - try { - method.invoke(object, resultSet.getBoolean(name)); - } catch (IllegalAccessException | InvocationTargetException error) { - LOGGER.warn("Set property error", error); - } + processors.add((object, resultSet) -> { + try { + method.invoke(object, resultSet.getBoolean(name)); + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); } }); } else if (parameterType.equals(int.class)) { - processors.add(new ResultSetProcessor<T>() { - @Override - public void process(T object, ResultSet resultSet) throws SQLException { - try { - method.invoke(object, resultSet.getInt(name)); - } catch (IllegalAccessException | InvocationTargetException error) { - LOGGER.warn("Set property error", error); - } + processors.add((object, resultSet) -> { + try { + method.invoke(object, resultSet.getInt(name)); + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); } }); } else if (parameterType.equals(long.class)) { - processors.add(new ResultSetProcessor<T>() { - @Override - public void process(T object, ResultSet resultSet) throws SQLException { - try { - method.invoke(object, resultSet.getLong(name)); - } catch (IllegalAccessException | InvocationTargetException error) { - LOGGER.warn("Set property error", error); - } + processors.add((object, resultSet) -> { + try { + method.invoke(object, resultSet.getLong(name)); + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); } }); } else if (parameterType.equals(double.class)) { - processors.add(new ResultSetProcessor<T>() { - @Override - public void process(T object, ResultSet resultSet) throws SQLException { - try { - method.invoke(object, resultSet.getDouble(name)); - } catch (IllegalAccessException | InvocationTargetException error) { - LOGGER.warn("Set property error", error); - } + processors.add((object, resultSet) -> { + try { + method.invoke(object, resultSet.getDouble(name)); + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); } }); } else if (parameterType.equals(String.class)) { - processors.add(new ResultSetProcessor<T>() { - @Override - public void process(T object, ResultSet resultSet) throws SQLException { - try { - method.invoke(object, resultSet.getString(name)); - } catch (IllegalAccessException | InvocationTargetException error) { - LOGGER.warn("Set property error", error); - } + processors.add((object, resultSet) -> { + try { + method.invoke(object, resultSet.getString(name)); + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); } }); } else if (parameterType.equals(Date.class)) { - processors.add(new ResultSetProcessor<T>() { - @Override - public void process(T object, ResultSet resultSet) throws SQLException { - try { - Timestamp timestamp = resultSet.getTimestamp(name); - if (timestamp != null) { - method.invoke(object, new Date(timestamp.getTime())); - } - } catch (IllegalAccessException | InvocationTargetException error) { - LOGGER.warn("Set property error", error); + processors.add((object, resultSet) -> { + try { + Timestamp timestamp = resultSet.getTimestamp(name); + if (timestamp != null) { + method.invoke(object, new Date(timestamp.getTime())); } + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); } }); } else if (parameterType.equals(byte[].class)) { - processors.add(new ResultSetProcessor<T>() { - @Override - public void process(T object, ResultSet resultSet) throws SQLException { - try { - method.invoke(object, resultSet.getBytes(name)); - } catch (IllegalAccessException | InvocationTargetException error) { - LOGGER.warn("Set property error", error); - } + processors.add((object, resultSet) -> { + try { + method.invoke(object, resultSet.getBytes(name)); + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); } }); } else { - processors.add(new ResultSetProcessor<T>() { - @Override - public void process(T object, ResultSet resultSet) throws SQLException { - String value = resultSet.getString(name); - if (value != null && !value.isEmpty()) { - try { - method.invoke(object, Context.getObjectMapper().readValue(value, parameterType)); - } catch (InvocationTargetException | IllegalAccessException | IOException error) { - LOGGER.warn("Set property error", error); - } + processors.add((object, resultSet) -> { + String value = resultSet.getString(name); + if (value != null && !value.isEmpty()) { + try { + method.invoke(object, Context.getObjectMapper().readValue(value, parameterType)); + } catch (InvocationTargetException | IllegalAccessException | IOException error) { + LOGGER.warn("Set property error", error); } } }); @@ -453,12 +425,12 @@ public final class QueryBuilder { while (resultSet.next()) { try { - T object = clazz.newInstance(); + T object = clazz.getDeclaredConstructor().newInstance(); for (ResultSetProcessor<T> processor : processors) { processor.process(object, resultSet); } result.add(object); - } catch (InstantiationException | IllegalAccessException e) { + } catch (ReflectiveOperationException e) { throw new IllegalArgumentException(); } } diff --git a/src/main/java/org/traccar/geofence/GeofenceCircle.java b/src/main/java/org/traccar/geofence/GeofenceCircle.java index f6fca63ca..eda63832d 100644 --- a/src/main/java/org/traccar/geofence/GeofenceCircle.java +++ b/src/main/java/org/traccar/geofence/GeofenceCircle.java @@ -50,7 +50,7 @@ public class GeofenceCircle extends GeofenceGeometry { @Override public String toWkt() { - String wkt = ""; + String wkt; wkt = "CIRCLE ("; wkt += String.valueOf(centerLatitude); wkt += " "; diff --git a/src/main/java/org/traccar/handler/SpeedLimitHandler.java b/src/main/java/org/traccar/handler/SpeedLimitHandler.java new file mode 100644 index 000000000..65f2c9cfe --- /dev/null +++ b/src/main/java/org/traccar/handler/SpeedLimitHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.model.Position; +import org.traccar.speedlimit.SpeedLimitProvider; + +@ChannelHandler.Sharable +public class SpeedLimitHandler extends ChannelInboundHandlerAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(SpeedLimitHandler.class); + + private final SpeedLimitProvider speedLimitProvider; + + public SpeedLimitHandler(SpeedLimitProvider speedLimitProvider) { + this.speedLimitProvider = speedLimitProvider; + } + + @Override + public void channelRead(final ChannelHandlerContext ctx, Object message) { + if (message instanceof Position) { + final Position position = (Position) message; + speedLimitProvider.getSpeedLimit(position.getLatitude(), position.getLongitude(), + new SpeedLimitProvider.SpeedLimitProviderCallback() { + @Override + public void onSuccess(double speedLimit) { + position.set(Position.KEY_SPEED_LIMIT, speedLimit); + ctx.fireChannelRead(position); + } + + @Override + public void onFailure(Throwable e) { + LOGGER.warn("Speed limit provider failed", e); + ctx.fireChannelRead(position); + } + }); + } else { + ctx.fireChannelRead(message); + } + } + +} diff --git a/src/main/java/org/traccar/handler/TimeHandler.java b/src/main/java/org/traccar/handler/TimeHandler.java index d8039518c..822c22a0a 100644 --- a/src/main/java/org/traccar/handler/TimeHandler.java +++ b/src/main/java/org/traccar/handler/TimeHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 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. @@ -53,8 +53,10 @@ public class TimeHandler extends ChannelInboundHandlerAdapter { Position position = (Position) msg; if (useServerTime) { position.setDeviceTime(position.getServerTime()); + position.setFixTime(position.getServerTime()); + } else { + position.setFixTime(position.getDeviceTime()); } - position.setFixTime(position.getDeviceTime()); } ctx.fireChannelRead(msg); diff --git a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java index e534df9de..c396b28e9 100644 --- a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java +++ b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -122,6 +122,11 @@ public class OverspeedEventHandler extends BaseEventHandler { double speedLimit = deviceManager.lookupAttributeDouble(deviceId, ATTRIBUTE_SPEED_LIMIT, 0, true, false); + double positionSpeedLimit = position.getDouble(Position.KEY_SPEED_LIMIT); + if (positionSpeedLimit > 0) { + speedLimit = positionSpeedLimit; + } + double geofenceSpeedLimit = 0; long overspeedGeofenceId = 0; diff --git a/src/main/java/org/traccar/helper/BitBuffer.java b/src/main/java/org/traccar/helper/BitBuffer.java index f30a4557b..323807ba9 100644 --- a/src/main/java/org/traccar/helper/BitBuffer.java +++ b/src/main/java/org/traccar/helper/BitBuffer.java @@ -93,7 +93,7 @@ public class BitBuffer { return result; } else { result &= signBit - 1; - result += ~(signBit - 1); + result += -signBit; return result; } } diff --git a/src/main/java/org/traccar/helper/Checksum.java b/src/main/java/org/traccar/helper/Checksum.java index d41dc2992..8c3d0063a 100644 --- a/src/main/java/org/traccar/helper/Checksum.java +++ b/src/main/java/org/traccar/helper/Checksum.java @@ -26,12 +26,12 @@ public final class Checksum { public static class Algorithm { - private int poly; - private int init; - private boolean refIn; - private boolean refOut; - private int xorOut; - private int[] table; + private final int poly; + private final int init; + private final boolean refIn; + private final boolean refOut; + private final int xorOut; + private final int[] table; public Algorithm(int bits, int poly, int init, boolean refIn, boolean refOut, int xorOut) { this.poly = poly; @@ -151,13 +151,8 @@ public final class Checksum { return checksum; } - public static String nmea(String msg) { - int checksum = 0; - byte[] bytes = msg.getBytes(StandardCharsets.US_ASCII); - for (int i = 1; i < bytes.length; i++) { - checksum ^= bytes[i]; - } - return String.format("*%02x", checksum).toUpperCase(); + public static String nmea(String string) { + return String.format("*%02X", xor(string)); } public static int sum(ByteBuffer buf) { diff --git a/src/main/java/org/traccar/helper/DateBuilder.java b/src/main/java/org/traccar/helper/DateBuilder.java index 9752f6977..24b7d027a 100644 --- a/src/main/java/org/traccar/helper/DateBuilder.java +++ b/src/main/java/org/traccar/helper/DateBuilder.java @@ -21,7 +21,7 @@ import java.util.TimeZone; public class DateBuilder { - private Calendar calendar; + private final Calendar calendar; public DateBuilder() { this(TimeZone.getTimeZone("UTC")); diff --git a/src/main/java/org/traccar/helper/LogAction.java b/src/main/java/org/traccar/helper/LogAction.java index 16d55ec60..d16b25483 100644 --- a/src/main/java/org/traccar/helper/LogAction.java +++ b/src/main/java/org/traccar/helper/LogAction.java @@ -17,6 +17,10 @@ package org.traccar.helper; import java.beans.Introspector; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,6 +50,7 @@ public final class LogAction { private static final String PATTERN_LOGIN = "user: %d, action: %s"; private static final String PATTERN_LOGIN_FAILED = "login failed from: %s"; private static final String PATTERN_DEVICE_ACCUMULATORS = "user: %d, action: %s, deviceId: %d"; + private static final String PATTERN_REPORT = "user: %d, report: %s, from: %s, to: %s, devices: %s, groups: %s"; public static void create(long userId, BaseModel object) { logObjectAction(ACTION_CREATE, userId, object.getClass(), object.getId()); @@ -92,8 +97,8 @@ public final class LogAction { PATTERN_OBJECT, userId, action, Introspector.decapitalize(clazz.getSimpleName()), objectId)); } - private static void logLinkAction(String action, long userId, - Class<?> owner, long ownerId, Class<?> property, long propertyId) { + private static void logLinkAction( + String action, long userId, Class<?> owner, long ownerId, Class<?> property, long propertyId) { LOGGER.info(String.format( PATTERN_LINK, userId, action, Introspector.decapitalize(owner.getSimpleName()), ownerId, @@ -104,4 +109,13 @@ public final class LogAction { LOGGER.info(String.format(PATTERN_LOGIN, userId, action)); } + public static void logReport( + long userId, String report, Date from, Date to, List<Long> deviceIds, List<Long> groupIds) { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + LOGGER.info(String.format( + PATTERN_REPORT, userId, report, + dateFormat.format(from), dateFormat.format(to), + deviceIds.toString(), groupIds.toString())); + } + } diff --git a/src/main/java/org/traccar/helper/ObdDecoder.java b/src/main/java/org/traccar/helper/ObdDecoder.java index 1bdcce352..b22065f4e 100644 --- a/src/main/java/org/traccar/helper/ObdDecoder.java +++ b/src/main/java/org/traccar/helper/ObdDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 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. @@ -35,7 +35,7 @@ public final class ObdDecoder { case MODE_FREEZE_FRAME: return decodeData( Integer.parseInt(value.substring(0, 2), 16), - Integer.parseInt(value.substring(2), 16), true); + Long.parseLong(value.substring(2), 16), true); case MODE_CODES: return decodeCodes(value); default: @@ -75,7 +75,7 @@ public final class ObdDecoder { } } - public static Map.Entry<String, Object> decodeData(int pid, int value, boolean convert) { + public static Map.Entry<String, Object> decodeData(int pid, long value, boolean convert) { switch (pid) { case 0x04: return createEntry(Position.KEY_ENGINE_LOAD, convert ? value * 100 / 255 : value); diff --git a/src/main/java/org/traccar/helper/Parser.java b/src/main/java/org/traccar/helper/Parser.java index 1471ec237..75106e2ba 100644 --- a/src/main/java/org/traccar/helper/Parser.java +++ b/src/main/java/org/traccar/helper/Parser.java @@ -242,7 +242,7 @@ public class Parser { public Date nextDateTime(DateTimeFormat format, String timeZone) { int year = 0, month = 0, day = 0; - int hour = 0, minute = 0, second = 0, millisecond = 0; + int hour, minute, second, millisecond = 0; switch (format) { case HMS: diff --git a/src/main/java/org/traccar/model/Command.java b/src/main/java/org/traccar/model/Command.java index abe538a10..099fb152d 100644 --- a/src/main/java/org/traccar/model/Command.java +++ b/src/main/java/org/traccar/model/Command.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 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. @@ -31,6 +31,7 @@ public class Command extends Message implements Cloneable { public static final String TYPE_ENGINE_RESUME = "engineResume"; public static final String TYPE_ALARM_ARM = "alarmArm"; public static final String TYPE_ALARM_DISARM = "alarmDisarm"; + public static final String TYPE_ALARM_DISMISS = "alarmDismiss"; public static final String TYPE_SET_TIMEZONE = "setTimezone"; public static final String TYPE_REQUEST_PHOTO = "requestPhoto"; public static final String TYPE_POWER_OFF = "powerOff"; diff --git a/src/main/java/org/traccar/model/Event.java b/src/main/java/org/traccar/model/Event.java index ee7fcc679..5eee2a0a0 100644 --- a/src/main/java/org/traccar/model/Event.java +++ b/src/main/java/org/traccar/model/Event.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2020 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. @@ -40,6 +40,7 @@ public class Event extends Message { public static final String TYPE_DEVICE_ONLINE = "deviceOnline"; public static final String TYPE_DEVICE_UNKNOWN = "deviceUnknown"; public static final String TYPE_DEVICE_OFFLINE = "deviceOffline"; + public static final String TYPE_DEVICE_INACTIVE = "deviceInactive"; public static final String TYPE_DEVICE_MOVING = "deviceMoving"; public static final String TYPE_DEVICE_STOPPED = "deviceStopped"; diff --git a/src/main/java/org/traccar/model/Position.java b/src/main/java/org/traccar/model/Position.java index 2c0e22c9e..6f70c8e21 100644 --- a/src/main/java/org/traccar/model/Position.java +++ b/src/main/java/org/traccar/model/Position.java @@ -85,6 +85,7 @@ public class Position extends Message { public static final String KEY_G_SENSOR = "gSensor"; public static final String KEY_ICCID = "iccid"; public static final String KEY_PHONE = "phone"; + public static final String KEY_SPEED_LIMIT = "speedLimit"; public static final String KEY_DTCS = "dtcs"; public static final String KEY_OBD_SPEED = "obdSpeed"; // knots diff --git a/src/main/java/org/traccar/model/Server.java b/src/main/java/org/traccar/model/Server.java index ad37e7078..112bfab26 100644 --- a/src/main/java/org/traccar/model/Server.java +++ b/src/main/java/org/traccar/model/Server.java @@ -166,4 +166,14 @@ public class Server extends ExtendedModel { public void setPoiLayer(String poiLayer) { this.poiLayer = poiLayer; } + + private String announcement; + + public String getAnnouncement() { + return announcement; + } + + public void setAnnouncement(String announcement) { + this.announcement = announcement; + } } diff --git a/src/main/java/org/traccar/model/Typed.java b/src/main/java/org/traccar/model/Typed.java index 313ec7bcd..fc671ac70 100644 --- a/src/main/java/org/traccar/model/Typed.java +++ b/src/main/java/org/traccar/model/Typed.java @@ -1,4 +1,5 @@ /* + * Copyright 2020 Anton Tananaev (anton@traccar.org) * Copyright 2016 Gabor Somogyi (gabor.g.somogyi@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +16,8 @@ */ package org.traccar.model; +import java.util.Objects; + public class Typed { private String type; @@ -30,4 +33,20 @@ public class Typed { public void setType(String type) { this.type = type; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return Objects.equals(type, ((Typed) o).type); + } + + @Override + public int hashCode() { + return Objects.hash(type); + } } diff --git a/src/main/java/org/traccar/notificators/Notificator.java b/src/main/java/org/traccar/notificators/Notificator.java index 5e40971c6..dd888bae9 100644 --- a/src/main/java/org/traccar/notificators/Notificator.java +++ b/src/main/java/org/traccar/notificators/Notificator.java @@ -27,13 +27,11 @@ public abstract class Notificator { private static final Logger LOGGER = LoggerFactory.getLogger(Notificator.class); public void sendAsync(final long userId, final Event event, final Position position) { - new Thread(new Runnable() { - public void run() { - try { - sendSync(userId, event, position); - } catch (MessageException | InterruptedException error) { - LOGGER.warn("Event send error", error); - } + new Thread(() -> { + try { + sendSync(userId, event, position); + } catch (MessageException | InterruptedException error) { + LOGGER.warn("Event send error", error); } }).start(); } diff --git a/src/main/java/org/traccar/protocol/AtrackFrameDecoder.java b/src/main/java/org/traccar/protocol/AtrackFrameDecoder.java index f071e2d97..8ed1fc8e8 100644 --- a/src/main/java/org/traccar/protocol/AtrackFrameDecoder.java +++ b/src/main/java/org/traccar/protocol/AtrackFrameDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ public class AtrackFrameDecoder extends BaseFrameDecoder { return buf.readRetainedSlice(KEEPALIVE_LENGTH); } - } else if (buf.getUnsignedShort(buf.readerIndex()) == 0x4050 && buf.getByte(buf.readerIndex() + 2) != ',') { + } else if (buf.getUnsignedByte(buf.readerIndex()) == 0x40 && buf.getByte(buf.readerIndex() + 2) != ',') { if (buf.readableBytes() > 6) { int length = buf.getUnsignedShort(buf.readerIndex() + 4) + 4 + 2; diff --git a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java index 428b69cd9..56c00c7c1 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 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2020 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. @@ -50,10 +50,12 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { private static final int MIN_DATA_LENGTH = 40; private boolean longDate; - private boolean decimalFuel; + private final boolean decimalFuel; private boolean custom; private String form; + private ByteBuf photo; + private final Map<Integer, String> alarmMap = new HashMap<>(); public AtrackProtocolDecoder(Protocol protocol) { @@ -510,20 +512,34 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { return position; } - private List<Position> decodeBinary(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + private Position decodePhoto(DeviceSession deviceSession, ByteBuf buf, long id) { - buf.skipBytes(2); // prefix - buf.readUnsignedShort(); // checksum - buf.readUnsignedShort(); // length - int index = buf.readUnsignedShort(); + long time = buf.readUnsignedInt(); + int index = buf.readUnsignedByte(); + int count = buf.readUnsignedByte(); - long id = buf.readLong(); - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(id)); - if (deviceSession == null) { - return null; + if (photo == null) { + photo = Unpooled.buffer(); + } + photo.writeBytes(buf.readSlice(buf.readUnsignedShort())); + + if (index == count - 1) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, new Date(time * 1000)); + + position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(String.valueOf(id), photo, "jpg")); + photo.release(); + photo = null; + + return position; } - sendResponse(channel, remoteAddress, id, index); + return null; + } + + private List<Position> decodeBinary(DeviceSession deviceSession, ByteBuf buf) { List<Position> positions = new LinkedList<>(); @@ -613,7 +629,26 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { } else if (buf.getByte(buf.readerIndex() + 2) == ',') { return decodeText(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII).trim()); } else { - return decodeBinary(channel, remoteAddress, buf); + + String prefix = buf.readCharSequence(2, StandardCharsets.US_ASCII).toString(); + buf.readUnsignedShort(); // checksum + buf.readUnsignedShort(); // length + int index = buf.readUnsignedShort(); + + long id = buf.readLong(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(id)); + if (deviceSession == null) { + return null; + } + + sendResponse(channel, remoteAddress, id, index); + + if (prefix.equals("@R")) { + return decodePhoto(deviceSession, buf, id); + } else { + return decodeBinary(deviceSession, buf); + } + } } diff --git a/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java b/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java index bc74b6576..09bd3572f 100644 --- a/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java @@ -73,7 +73,7 @@ public class CellocatorProtocolDecoder extends BaseProtocolDecoder { content.writeByte(packetNumber); content.writeZero(11); - ByteBuf reply = encodeContent(MSG_SERVER_ACKNOWLEDGE, (int) deviceId, packetNumber, content); + ByteBuf reply = encodeContent(MSG_SERVER_ACKNOWLEDGE, (int) deviceId, 0, content); channel.writeAndFlush(new NetworkMessage(reply, remoteAddress)); } } diff --git a/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java index d22106a80..115884e2c 100644 --- a/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java @@ -97,7 +97,7 @@ public class EasyTrackProtocolDecoder extends BaseProtocolDecoder { String sentence = (String) msg; String type = sentence.substring(20, 22); - if (type.equals("TX") && channel != null) { + if ((type.equals("TX") || type.equals("MQ")) && channel != null) { channel.writeAndFlush(new NetworkMessage(sentence + "#", remoteAddress)); } diff --git a/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java index c3fe7121e..613710587 100644 --- a/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2014 - 2020 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. @@ -35,6 +35,8 @@ import org.traccar.model.Position; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.Date; +import java.util.LinkedList; +import java.util.List; import java.util.regex.Pattern; public class EelinkProtocolDecoder extends BaseProtocolDecoder { @@ -76,6 +78,8 @@ public class EelinkProtocolDecoder extends BaseProtocolDecoder { case 0x08: case 0x09: return Position.ALARM_GPS_ANTENNA_CUT; + case 0x25: + return Position.ALARM_REMOVING; case 0x81: return Position.ALARM_LOW_SPEED; case 0x82: @@ -388,9 +392,29 @@ public class EelinkProtocolDecoder extends BaseProtocolDecoder { deviceSession = getDeviceSession(channel, remoteAddress); } + List<Position> positions = new LinkedList<>(); + + while (buf.isReadable()) { + Position position = decodePackage(channel, remoteAddress, buf, uniqueId, deviceSession); + if (position != null) { + positions.add(position); + } + } + + if (!positions.isEmpty()) { + return positions.size() > 1 ? positions : positions.iterator().next(); + } else { + return null; + } + } + + protected Position decodePackage( + Channel channel, SocketAddress remoteAddress, ByteBuf buf, + String uniqueId, DeviceSession deviceSession) throws Exception { + buf.skipBytes(2); // header int type = buf.readUnsignedByte(); - buf.readShort(); // length + buf = buf.readSlice(buf.readUnsignedShort()); int index = buf.readUnsignedShort(); if (type != MSG_GPS && type != MSG_DATA) { diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java index 9bc7cb504..fe42a44d7 100644 --- a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java @@ -60,7 +60,7 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { .number("(-?d+),") // altitude .number("(d+),") // odometer .number("d+,") // runtime - .number("(x{4,8}),") // status + .number("(x+),") // status .number("(x+)?,") // input .number("(x+)?,") // output .number("(d+)|") // mcc diff --git a/src/main/java/org/traccar/protocol/FutureWayFrameDecoder.java b/src/main/java/org/traccar/protocol/FutureWayFrameDecoder.java new file mode 100644 index 000000000..812f78d02 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FutureWayFrameDecoder.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020 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 io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; +import org.traccar.helper.DataConverter; + +import java.nio.charset.StandardCharsets; + +public class FutureWayFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 10) { + return null; + } + + int length = Unpooled.wrappedBuffer(DataConverter.parseHex( + buf.getCharSequence(buf.readerIndex() + 2, 8, StandardCharsets.US_ASCII).toString())).readInt() + 17; + + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/FutureWayProtocol.java b/src/main/java/org/traccar/protocol/FutureWayProtocol.java new file mode 100644 index 000000000..73b53ee12 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FutureWayProtocol.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class FutureWayProtocol extends BaseProtocol { + + public FutureWayProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new FutureWayFrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new FutureWayProtocolDecoder(FutureWayProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/FutureWayProtocolDecoder.java b/src/main/java/org/traccar/protocol/FutureWayProtocolDecoder.java new file mode 100644 index 000000000..4b0f46e34 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FutureWayProtocolDecoder.java @@ -0,0 +1,134 @@ +/* + * Copyright 2020 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.Protocol; +import org.traccar.helper.DataConverter; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class FutureWayProtocolDecoder extends BaseProtocolDecoder { + + public FutureWayProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_GPS = new PatternBuilder() + .text("GPS:") + .expression("([AV]),") // validity + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d+.d+)([NS]),") // latitude + .number("(d+.d+)([EW]),") // longitude + .number("(d+.d+),") // speed + .number("(d+.d+)") // course + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + ByteBuf header = Unpooled.wrappedBuffer(DataConverter.parseHex(sentence.substring(0, 16))); + sentence = sentence.substring(16, sentence.length() - 4); + + header.readUnsignedByte(); // header + header.readUnsignedInt(); // length + int type = header.readUnsignedByte(); + header.readUnsignedShort(); // index + + if (type == 0x20) { + + getDeviceSession(channel, remoteAddress, sentence.split(",")[1].substring(5)); + + } else if (type == 0xA0) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + Network network = new Network(); + + for (String line : sentence.split("\r\n")) { + + if (line.startsWith("GPS")) { + + Parser parser = new Parser(PATTERN_GPS, line); + if (!parser.matches()) { + return null; + } + + position.setValid(parser.next().equals("A")); + position.setTime(parser.nextDateTime()); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setSpeed(parser.nextDouble()); + position.setCourse(parser.nextDouble()); + + } else if (line.startsWith("WIFI")) { + + for (String item : line.substring(line.indexOf(',') + 1).split("&")) { + String[] values = item.split("\\|"); + network.addWifiAccessPoint( + WifiAccessPoint.from(values[1].replace('-', ':'), Integer.parseInt(values[2]))); + } + + } else if (line.startsWith("LBS")) { + + String[] values = line.substring("LBS:".length()).split(","); + network.addCellTower(CellTower.from( + Integer.parseInt(values[0]), + Integer.parseInt(values[1]), + Integer.parseInt(values[3]), + Integer.parseInt(values[2]))); + + } + + } + + if (!network.getCellTowers().isEmpty() || !network.getWifiAccessPoints().isEmpty()) { + position.setNetwork(network); + } + + if (position.getFixTime() == null) { + getLastLocation(position, null); + } + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java index 283dbeb37..ec569b71b 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 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2020 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,7 +45,7 @@ import java.util.regex.Pattern; public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { - private boolean ignoreFixTime; + private final boolean ignoreFixTime; public Gl200TextProtocolDecoder(Protocol protocol) { super(protocol); @@ -190,6 +190,12 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .expression(PATTERN_LOCATION.pattern()) .expression(")+)") .groupBegin() + .number("d{1,2},,") + .number("(d{1,3}),") // battery + .number("[01],") // mode + .number("(?:[01])?,") // motion + .number("(?:-?d{1,2}.d)?,") // temperature + .or() .number("(d{1,7}.d)?,") // odometer .number("(d{5}:dd:dd)?,") // hour meter .number("(x+)?,") // adc 1 @@ -226,6 +232,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .expression("((?:") .expression(PATTERN_LOCATION.pattern()) .expression(")+)") + .groupBegin() .number("(d{1,7}.d)?,") // odometer .number("(d{5}:dd:dd)?,") // hour meter .number("(x+)?,") // adc 1 @@ -233,6 +240,11 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .number("(d{1,3})?,") // battery .number("(?:(xx)(xx)(xx))?,") // device status .expression("(.*)") // additional data + .or() + .number("d*,,") + .number("(d+),") // battery + .any() + .groupEnd() .number("(dddd)(dd)(dd)") // date (yyyymmdd) .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) .text(",") @@ -789,8 +801,14 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { } else if (BitUtil.check(ignition, 5)) { position.set(Position.KEY_IGNITION, true); } - position.set(Position.KEY_INPUT, parser.nextHexInt()); - position.set(Position.KEY_OUTPUT, parser.nextHexInt()); + int input = parser.nextHexInt(); + int output = parser.nextHexInt(); + position.set(Position.KEY_INPUT, input); + position.set(Position.PREFIX_IN + 1, BitUtil.check(input, 1)); + position.set(Position.PREFIX_IN + 2, BitUtil.check(input, 2)); + position.set(Position.KEY_OUTPUT, output); + position.set(Position.PREFIX_OUT + 1, BitUtil.check(output, 0)); + position.set(Position.PREFIX_OUT + 2, BitUtil.check(output, 1)); } } @@ -835,6 +853,10 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { } if (parser.hasNext()) { + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + } + + if (parser.hasNext()) { position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); } position.set(Position.KEY_HOURS, parseHours(parser.next())); @@ -897,49 +919,58 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { if (power != null) { position.set(Position.KEY_POWER, power * 0.001); } - 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); + if (parser.hasNext(9)) { - int index = 0; - String[] data = parser.next().split(","); + 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()); - index += 1; // device type + decodeStatus(position, parser); - if (BitUtil.check(mask, 0)) { - index += 1; // digital fuel sensor data - } + int index = 0; + String[] data = parser.next().split(","); + + index += 1; // device type + + if (BitUtil.check(mask, 0)) { + index += 1; // digital fuel sensor data + } - if (BitUtil.check(mask, 1)) { - int deviceCount = Integer.parseInt(data[index++]); - for (int i = 1; i <= deviceCount; i++) { - index += 1; // id - index += 1; // type - if (!data[index++].isEmpty()) { - position.set(Position.PREFIX_TEMP + i, (short) Integer.parseInt(data[index - 1], 16) * 0.0625); + if (BitUtil.check(mask, 1)) { + int deviceCount = Integer.parseInt(data[index++]); + for (int i = 1; i <= deviceCount; i++) { + index += 1; // id + index += 1; // type + if (!data[index++].isEmpty()) { + position.set(Position.PREFIX_TEMP + i, (short) Integer.parseInt(data[index - 1], 16) * 0.0625); + } } } - } - if (BitUtil.check(mask, 2)) { - index += 1; // can data - } + if (BitUtil.check(mask, 2)) { + index += 1; // can data + } - if (BitUtil.check(mask, 3) || BitUtil.check(mask, 4)) { - int deviceCount = Integer.parseInt(data[index++]); - for (int i = 1; i <= deviceCount; i++) { - index += 1; // type - if (BitUtil.check(mask, 3)) { - position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(data[index++])); - } - if (BitUtil.check(mask, 4)) { - index += 1; // volume + if (BitUtil.check(mask, 3) || BitUtil.check(mask, 4)) { + int deviceCount = Integer.parseInt(data[index++]); + for (int i = 1; i <= deviceCount; i++) { + index += 1; // type + if (BitUtil.check(mask, 3)) { + position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(data[index++])); + } + if (BitUtil.check(mask, 4)) { + index += 1; // volume + } } } + + } + + if (parser.hasNext()) { + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); } decodeDeviceTime(position, parser); diff --git a/src/main/java/org/traccar/protocol/GlobalSatProtocol.java b/src/main/java/org/traccar/protocol/GlobalSatProtocol.java index 7243be72a..e86b5dc30 100644 --- a/src/main/java/org/traccar/protocol/GlobalSatProtocol.java +++ b/src/main/java/org/traccar/protocol/GlobalSatProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,16 +21,22 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.model.Command; public class GlobalSatProtocol extends BaseProtocol { public GlobalSatProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_ALARM_DISMISS, + Command.TYPE_OUTPUT_CONTROL); addServer(new TrackerServer(false, getName()) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "!\r\n", "!")); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); + pipeline.addLast(new GlobalSatProtocolEncoder(GlobalSatProtocol.this)); pipeline.addLast(new GlobalSatProtocolDecoder(GlobalSatProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/GlobalSatProtocolEncoder.java b/src/main/java/org/traccar/protocol/GlobalSatProtocolEncoder.java new file mode 100644 index 000000000..4f56274da --- /dev/null +++ b/src/main/java/org/traccar/protocol/GlobalSatProtocolEncoder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2020 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.Protocol; +import org.traccar.StringProtocolEncoder; +import org.traccar.helper.Checksum; +import org.traccar.model.Command; + +public class GlobalSatProtocolEncoder extends StringProtocolEncoder { + + public GlobalSatProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + String formattedCommand = null; + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + formattedCommand = formatCommand( + command, "GSC,%s,%s", Command.KEY_UNIQUE_ID, Command.KEY_DATA); + break; + case Command.TYPE_ALARM_DISMISS: + formattedCommand = formatCommand( + command, "GSC,%s,Na", Command.KEY_UNIQUE_ID); + break; + case Command.TYPE_OUTPUT_CONTROL: + formattedCommand = formatCommand( + command, "GSC,%s,Lo(%s,%s)", Command.KEY_UNIQUE_ID, Command.KEY_INDEX, Command.KEY_DATA); + break; + default: + break; + } + + if (formattedCommand != null) { + return formattedCommand + Checksum.nmea(formattedCommand) + '!'; + } else { + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java index de23ea170..b742d0cac 100644 --- a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java @@ -60,9 +60,9 @@ import java.util.List; public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder { - private DocumentBuilder documentBuilder; - private XPath xPath; - private XPathExpression messageExpression; + private final DocumentBuilder documentBuilder; + private final XPath xPath; + private final XPathExpression messageExpression; public GlobalstarProtocolDecoder(Protocol protocol) { super(protocol); @@ -161,17 +161,20 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder { position.setLongitude(position.getLongitude() - 360); } - position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + int speed = buf.readUnsignedByte(); + position.setSpeed(UnitsConverter.knotsFromKph(speed)); position.set("batteryReplace", BitUtil.check(buf.readUnsignedByte(), 7)); - positions.add(position); + if (speed != 0xff) { + positions.add(position); + } } } sendResponse(channel, document.getFirstChild().getAttributes().getNamedItem("messageID").getNodeValue()); - return positions; + return !positions.isEmpty() ? positions : null; } } diff --git a/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java index e00d83061..9b672cacc 100644 --- a/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java @@ -408,7 +408,7 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder { } } - if (sentence.substring(21, 21 + 2).equals("vr")) { + if (sentence.startsWith("vr", 21)) { return decodePhoto(channel, remoteAddress, sentence); } else if (sentence.substring(21, 21 + 3).contains("OBD")) { return decodeObd(channel, remoteAddress, sentence); diff --git a/src/main/java/org/traccar/protocol/GpsGateProtocolDecoder.java b/src/main/java/org/traccar/protocol/GpsGateProtocolDecoder.java index cc187225b..c158d3212 100644 --- a/src/main/java/org/traccar/protocol/GpsGateProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GpsGateProtocolDecoder.java @@ -69,7 +69,8 @@ public class GpsGateProtocolDecoder extends BaseProtocolDecoder { private void send(Channel channel, SocketAddress remoteAddress, String message) { if (channel != null) { - channel.writeAndFlush(new NetworkMessage(message + Checksum.nmea(message) + "\r\n", remoteAddress)); + channel.writeAndFlush(new NetworkMessage( + message + Checksum.nmea(message.substring(1)) + "\r\n", remoteAddress)); } } diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java index 2c2ca7365..e003db51a 100644 --- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java @@ -59,6 +59,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_LBS = 0x11; public static final int MSG_GPS_LBS_1 = 0x12; public static final int MSG_GPS_LBS_2 = 0x22; + public static final int MSG_GPS_LBS_3 = 0x37; public static final int MSG_STATUS = 0x13; public static final int MSG_SATELLITE = 0x14; public static final int MSG_STRING = 0x15; @@ -87,7 +88,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_COMMAND_2 = 0x82; public static final int MSG_TIME_REQUEST = 0x8A; public static final int MSG_INFO = 0x94; - public static final int MSG_RFID = 0x9B; + public static final int MSG_SERIAL = 0x9B; public static final int MSG_STRING_INFO = 0x21; public static final int MSG_GPS_2 = 0xA0; public static final int MSG_LBS_2 = 0xA1; @@ -114,6 +115,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { case MSG_GPS: case MSG_GPS_LBS_1: case MSG_GPS_LBS_2: + case MSG_GPS_LBS_3: case MSG_GPS_LBS_STATUS_1: case MSG_GPS_LBS_STATUS_2: case MSG_GPS_LBS_STATUS_3: @@ -134,6 +136,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { case MSG_LBS_STATUS: case MSG_GPS_LBS_1: case MSG_GPS_LBS_2: + case MSG_GPS_LBS_3: case MSG_GPS_LBS_STATUS_1: case MSG_GPS_LBS_STATUS_2: case MSG_GPS_LBS_STATUS_3: @@ -736,57 +739,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { } else if (isSupported(type)) { - if (hasGps(type)) { - decodeGps(position, buf, false, deviceSession.getTimeZone()); - } else { - getLastLocation(position, null); - } - - if (hasLbs(type)) { - decodeLbs(position, buf, hasStatus(type)); - } - - if (hasStatus(type)) { - decodeStatus(position, buf); - } - - if (type == MSG_GPS_LBS_1 && buf.readableBytes() > 75 + 6) { - position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); - String data = buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); - buf.readUnsignedByte(); // alarm - buf.readUnsignedByte(); // swiped - position.set("driverLicense", data.trim()); - } - - if (type == MSG_GPS_LBS_1 && buf.readableBytes() == 2 + 6) { - int mask = buf.readUnsignedShort(); - position.set(Position.KEY_IGNITION, BitUtil.check(mask, 8 + 7)); - position.set(Position.PREFIX_IN + 2, BitUtil.check(mask, 8 + 6)); - if (BitUtil.check(mask, 8 + 4)) { - int value = BitUtil.to(mask, 8 + 1); - if (BitUtil.check(mask, 8 + 1)) { - value = -value; - } - position.set(Position.PREFIX_TEMP + 1, value); - } else { - int value = BitUtil.to(mask, 8 + 2); - if (BitUtil.check(mask, 8 + 5)) { - position.set(Position.PREFIX_ADC + 1, value); - } else { - position.set(Position.PREFIX_ADC + 1, value * 0.1); - } - } - } - - if (type == MSG_GPS_LBS_2 && buf.readableBytes() == 3 + 6) { - position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0); - position.set(Position.KEY_EVENT, buf.readUnsignedByte()); // reason - position.set(Position.KEY_ARCHIVE, buf.readUnsignedByte() > 0); - } - - if (buf.readableBytes() == 4 + 6) { - position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); - } + decodeBasicUniversal(buf, deviceSession, type, position); } else if (type == MSG_ALARM) { boolean extendedAlarm = dataLength > 7; @@ -850,6 +803,80 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return position; } + private void decodeBasicUniversal(ByteBuf buf, DeviceSession deviceSession, int type, Position position) { + + if (hasGps(type)) { + decodeGps(position, buf, false, deviceSession.getTimeZone()); + } else { + getLastLocation(position, null); + } + + if (hasLbs(type)) { + decodeLbs(position, buf, hasStatus(type)); + } + + if (hasStatus(type)) { + decodeStatus(position, buf); + } + + if (type == MSG_GPS_LBS_1 && buf.readableBytes() > 75 + 6) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + String data = buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); + buf.readUnsignedByte(); // alarm + buf.readUnsignedByte(); // swiped + position.set("driverLicense", data.trim()); + } + + if (type == MSG_GPS_LBS_1 && buf.readableBytes() == 2 + 6) { + int mask = buf.readUnsignedShort(); + position.set(Position.KEY_IGNITION, BitUtil.check(mask, 8 + 7)); + position.set(Position.PREFIX_IN + 2, BitUtil.check(mask, 8 + 6)); + if (BitUtil.check(mask, 8 + 4)) { + int value = BitUtil.to(mask, 8 + 1); + if (BitUtil.check(mask, 8 + 1)) { + value = -value; + } + position.set(Position.PREFIX_TEMP + 1, value); + } else { + int value = BitUtil.to(mask, 8 + 2); + if (BitUtil.check(mask, 8 + 5)) { + position.set(Position.PREFIX_ADC + 1, value); + } else { + position.set(Position.PREFIX_ADC + 1, value * 0.1); + } + } + } + + if ((type == MSG_GPS_LBS_2 || type == MSG_GPS_LBS_3) && buf.readableBytes() >= 3 + 6) { + position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0); + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); // reason + position.set(Position.KEY_ARCHIVE, buf.readUnsignedByte() > 0); + } + + if (type == MSG_GPS_LBS_3) { + int module = buf.readUnsignedShort(); + int length = buf.readUnsignedByte(); + switch (module) { + case 0x0027: + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); + break; + case 0x002E: + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + break; + case 0x003B: + position.setAccuracy(buf.readUnsignedShort() * 0.01); + break; + default: + buf.skipBytes(length); + break; + } + } + + if (buf.readableBytes() == 4 + 6) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + } + } + private Object decodeExtended(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); @@ -1026,18 +1053,6 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return position; - } else if (type == MSG_RFID) { - - getLastLocation(position, null); - - buf.readUnsignedByte(); // external device type code - buf.readUnsignedByte(); // card type - position.set( - Position.KEY_DRIVER_UNIQUE_ID, - buf.readCharSequence(buf.readableBytes() - 9, StandardCharsets.US_ASCII).toString()); - - return position; - } else if (type == MSG_GPS_MODULAR) { return decodeExtendedModular(buf, deviceSession); @@ -1205,6 +1220,27 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return position; + } else if (type == MSG_SERIAL) { + + position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, null); + + buf.readUnsignedByte(); // external device type code + int length = buf.readableBytes() - 9; // line break + checksum + index + checksum + footer + if (length < 8) { + position.set( + Position.PREFIX_TEMP + 1, + Double.parseDouble(buf.readCharSequence(length - 1, StandardCharsets.US_ASCII).toString())); + } else { + buf.readUnsignedByte(); // card type + position.set( + Position.KEY_DRIVER_UNIQUE_ID, + buf.readCharSequence(length - 1, StandardCharsets.US_ASCII).toString()); + } + + return position; + } return null; diff --git a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java index 8d39abead..f1d146bda 100644 --- a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java @@ -190,6 +190,9 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder { position.set( Position.KEY_VIN, buf.readCharSequence(length, StandardCharsets.US_ASCII).toString()); break; + case 0x0011: + position.set(Position.KEY_HOURS, buf.readUnsignedInt() * 0.05); + break; case 0x0020: String[] cells = buf.readCharSequence( length, StandardCharsets.US_ASCII).toString().split("\\+"); diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java index 841e2235d..b9b156f93 100644 --- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java @@ -57,13 +57,17 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { public static final int RESULT_SUCCESS = 0; - public static ByteBuf formatMessage(int type, ByteBuf id, ByteBuf data) { + public static ByteBuf formatMessage(int type, ByteBuf id, boolean shortIndex, ByteBuf data) { ByteBuf buf = Unpooled.buffer(); buf.writeByte(0x7e); buf.writeShort(type); buf.writeShort(data.readableBytes()); buf.writeBytes(id); - buf.writeShort(0); // index + if (shortIndex) { + buf.writeByte(1); + } else { + buf.writeShort(1); + } buf.writeBytes(data); data.release(); buf.writeByte(Checksum.xor(buf.nioBuffer(1, buf.readableBytes() - 1))); @@ -79,18 +83,18 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { response.writeShort(type); response.writeByte(RESULT_SUCCESS); channel.writeAndFlush(new NetworkMessage( - formatMessage(MSG_GENERAL_RESPONSE, id, response), remoteAddress)); + formatMessage(MSG_GENERAL_RESPONSE, id, false, response), remoteAddress)); } } private void sendGeneralResponse2( - Channel channel, SocketAddress remoteAddress, ByteBuf id, int index) { + Channel channel, SocketAddress remoteAddress, ByteBuf id, int type) { if (channel != null) { ByteBuf response = Unpooled.buffer(); - response.writeShort(index); + response.writeShort(type); response.writeByte(RESULT_SUCCESS); channel.writeAndFlush(new NetworkMessage( - formatMessage(MSG_GENERAL_RESPONSE_2, id, response), remoteAddress)); + formatMessage(MSG_GENERAL_RESPONSE_2, id, true, response), remoteAddress)); } } @@ -161,7 +165,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { response.writeByte(RESULT_SUCCESS); response.writeBytes(ByteBufUtil.hexDump(id).getBytes(StandardCharsets.US_ASCII)); channel.writeAndFlush(new NetworkMessage( - formatMessage(MSG_TERMINAL_REGISTER_RESPONSE, id, response), remoteAddress)); + formatMessage(MSG_TERMINAL_REGISTER_RESPONSE, id, false, response), remoteAddress)); } } else if (type == MSG_TERMINAL_AUTH) { @@ -170,12 +174,14 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { } else if (type == MSG_LOCATION_REPORT) { + sendGeneralResponse(channel, remoteAddress, id, type, index); + return decodeLocation(deviceSession, buf); } else if (type == MSG_LOCATION_REPORT_2 || type == MSG_LOCATION_REPORT_BLIND) { if (BitUtil.check(attribute, 15)) { - sendGeneralResponse2(channel, remoteAddress, id, index); + sendGeneralResponse2(channel, remoteAddress, id, type); } return decodeLocation2(deviceSession, buf, type); @@ -296,11 +302,24 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { case 0xD3: position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1); break; + case 0xD4: + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + break; + case 0xD5: + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); + break; + case 0xDA: + buf.readUnsignedShort(); // string cut count + int deviceStatus = buf.readUnsignedByte(); + position.set("string", BitUtil.check(deviceStatus, 0)); + position.set(Position.KEY_MOTION, BitUtil.check(deviceStatus, 2)); + position.set("cover", BitUtil.check(deviceStatus, 3)); + break; case 0xEB: while (buf.readerIndex() < endIndex) { - int tenetLength = buf.readUnsignedShort(); - int tenetType = buf.readUnsignedShort(); - switch (tenetType) { + int extendedLength = buf.readUnsignedShort(); + int extendedType = buf.readUnsignedShort(); + switch (extendedType) { case 0x0001: position.set("fuel1", buf.readUnsignedShort() * 0.1); buf.readUnsignedByte(); // unused @@ -309,8 +328,11 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { 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(tenetLength - 2); + buf.skipBytes(extendedLength - 2); break; } } diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolEncoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolEncoder.java index 40d07230d..55c1e0c3b 100644 --- a/src/main/java/org/traccar/protocol/HuabaoProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/HuabaoProtocolEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 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. @@ -50,22 +50,22 @@ public class HuabaoProtocolEncoder extends BaseProtocolEncoder { data.writeByte(0x01); data.writeBytes(time); return HuabaoProtocolDecoder.formatMessage( - HuabaoProtocolDecoder.MSG_OIL_CONTROL, id, data); + HuabaoProtocolDecoder.MSG_OIL_CONTROL, id, false, data); } else { data.writeByte(0xf0); return HuabaoProtocolDecoder.formatMessage( - HuabaoProtocolDecoder.MSG_TERMINAL_CONTROL, id, data); + HuabaoProtocolDecoder.MSG_TERMINAL_CONTROL, id, false, data); } case Command.TYPE_ENGINE_RESUME: if (alternative) { data.writeByte(0x00); data.writeBytes(time); return HuabaoProtocolDecoder.formatMessage( - HuabaoProtocolDecoder.MSG_OIL_CONTROL, id, data); + HuabaoProtocolDecoder.MSG_OIL_CONTROL, id, false, data); } else { data.writeByte(0xf1); return HuabaoProtocolDecoder.formatMessage( - HuabaoProtocolDecoder.MSG_TERMINAL_CONTROL, id, data); + HuabaoProtocolDecoder.MSG_TERMINAL_CONTROL, id, false, data); } default: return null; diff --git a/src/main/java/org/traccar/protocol/IntellitracProtocolDecoder.java b/src/main/java/org/traccar/protocol/IntellitracProtocolDecoder.java index 897606270..930d4f23b 100644 --- a/src/main/java/org/traccar/protocol/IntellitracProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/IntellitracProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2020 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. @@ -44,7 +44,7 @@ public class IntellitracProtocolDecoder extends BaseProtocolDecoder { .number("(d+.?d*),") // course .number("(-?d+.?d*),") // altitude .number("(d+),") // satellites - .number("(d+),") // index + .number("(d+),") // event .number("(d+),") // input .number("(d+),?") // output .number("(d+.d+)?,?") // adc1 @@ -65,6 +65,30 @@ public class IntellitracProtocolDecoder extends BaseProtocolDecoder { .any() .compile(); + private String decodeAlarm(int value) { + switch (value) { + case 164: + return Position.ALARM_GEOFENCE_ENTER; + case 165: + return Position.ALARM_GEOFENCE_EXIT; + case 168: + case 169: + return Position.ALARM_LOW_POWER; + case 170: + return Position.ALARM_POWER_OFF; + case 176: + return Position.ALARM_POWER_RESTORED; + case 180: + return Position.ALARM_FALL_DOWN; + case 225: + return Position.ALARM_JAMMING; + case 995: + return Position.ALARM_SOS; + default: + return null; + } + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -74,12 +98,12 @@ public class IntellitracProtocolDecoder extends BaseProtocolDecoder { return null; } - Position position = new Position(getProtocolName()); - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); if (deviceSession == null) { return null; } + + Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); position.setTime(parser.nextDateTime()); @@ -92,7 +116,11 @@ public class IntellitracProtocolDecoder extends BaseProtocolDecoder { position.setAltitude(parser.nextDouble()); position.set(Position.KEY_SATELLITES, parser.nextInt()); - position.set(Position.KEY_INDEX, parser.nextLong()); + + int event = parser.nextInt(); + position.set(Position.KEY_ALARM, decodeAlarm(event)); + position.set(Position.KEY_EVENT, event); + position.set(Position.KEY_INPUT, parser.nextInt()); position.set(Position.KEY_OUTPUT, parser.nextInt()); @@ -100,16 +128,18 @@ public class IntellitracProtocolDecoder extends BaseProtocolDecoder { position.set(Position.PREFIX_ADC + 2, parser.nextDouble()); // J1939 data - position.set(Position.KEY_OBD_SPEED, parser.nextInt()); - position.set(Position.KEY_RPM, parser.nextInt()); - position.set("coolant", parser.nextInt()); - position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); - position.set(Position.KEY_FUEL_CONSUMPTION, parser.nextInt()); - position.set(Position.PREFIX_TEMP + 1, parser.nextInt()); - position.set("chargerPressure", parser.nextInt()); - position.set("tpl", parser.nextInt()); - position.set(Position.KEY_AXLE_WEIGHT, parser.nextInt()); - position.set(Position.KEY_OBD_ODOMETER, parser.nextInt()); + if (parser.hasNext(10)) { + position.set(Position.KEY_OBD_SPEED, parser.nextInt()); + position.set(Position.KEY_RPM, parser.nextInt()); + position.set("coolant", parser.nextInt()); + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); + position.set(Position.KEY_FUEL_CONSUMPTION, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, parser.nextInt()); + position.set("chargerPressure", parser.nextInt()); + position.set("tpl", parser.nextInt()); + position.set(Position.KEY_AXLE_WEIGHT, parser.nextInt()); + position.set(Position.KEY_OBD_ODOMETER, parser.nextInt()); + } return position; } diff --git a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java index f66669a98..94c9a3038 100644 --- a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java @@ -90,6 +90,10 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { .number("(-?d+),") // tilt x .or() .number("d+,") // index + .number("(d+.?d*),") // odometer + .number("x+,") // checksum + .or() + .number("d+,") // index .number("(d+.?d*),") // adc1 .number("(d+.?d*),") // adc2 .groupEnd("?") @@ -244,6 +248,10 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { position.set("tiltX", parser.nextInt()); } + if (parser.hasNext()) { + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); + } + if (parser.hasNext(2)) { position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); position.set(Position.PREFIX_ADC + 2, parser.nextDouble()); diff --git a/src/main/java/org/traccar/protocol/L100ProtocolDecoder.java b/src/main/java/org/traccar/protocol/L100ProtocolDecoder.java index 9868de435..5b5eb7d60 100644 --- a/src/main/java/org/traccar/protocol/L100ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/L100ProtocolDecoder.java @@ -138,7 +138,7 @@ public class L100ProtocolDecoder extends BaseProtocolDecoder { String sentence = (String) msg; if (sentence.startsWith("L") || sentence.startsWith("H")) { - if (sentence.substring(2, 8).equals("ATLOBD")) { + if (sentence.startsWith("ATLOBD", 2)) { return decodeObdData(channel, remoteAddress, sentence); } else { return decodeObdLocation(channel, remoteAddress, sentence); diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java index 0c72568f3..4abb75025 100644 --- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java @@ -152,7 +152,7 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder { if (responseCode != null) { String response = "$AVCFG," + devicePassword + "," + responseCode; - response += Checksum.nmea(response) + "\r\n"; + response += Checksum.nmea(response.substring(1)) + "\r\n"; channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } @@ -163,7 +163,7 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder { if (Character.isLowerCase(status.charAt(0))) { String response = "$EAVACK," + event + "," + checksum; - response += Checksum.nmea(response) + "\r\n"; + response += Checksum.nmea(response.substring(1)) + "\r\n"; channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java index 0c9f8ebb8..aaa5a70f7 100644 --- a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java @@ -29,7 +29,7 @@ public class LaipacProtocolEncoder extends StringProtocolEncoder { @Override protected String formatCommand(Command command, String format, String... keys) { String sentence = super.formatCommand(command, "$" + format, keys); - sentence += Checksum.nmea(sentence) + "\r\n"; + sentence += Checksum.nmea(sentence.substring(1)) + "\r\n"; return sentence; } diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java index 529496928..c43f1ea83 100644 --- a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java @@ -177,8 +177,8 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { position.setNetwork(new Network(CellTower.from( parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt(), rssi))); - position.set(Position.KEY_OUTPUT, parser.nextHexInt()); position.set(Position.KEY_INPUT, parser.nextHexInt()); + position.set(Position.KEY_OUTPUT, parser.nextHexInt()); for (int i = 1; i <= 3; i++) { position.set(Position.PREFIX_ADC + i, parser.nextHexInt()); @@ -421,6 +421,22 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { case 0x1A: position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.01); break; + case 0x91: + case 0x92: + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedShortLE()); + break; + case 0x98: + position.set(Position.KEY_FUEL_USED, buf.readUnsignedShortLE()); + break; + case 0x99: + position.set(Position.KEY_RPM, buf.readUnsignedShortLE()); + break; + case 0x9C: + position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedShortLE()); + break; + case 0xC9: + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShortLE()); + break; default: buf.readUnsignedShortLE(); break; @@ -440,9 +456,18 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { case 0x04: position.setTime(new Date((946684800 + buf.readUnsignedIntLE()) * 1000)); // 2000-01-01 break; + case 0x0C: + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + break; case 0x0D: position.set("runtime", buf.readUnsignedIntLE()); break; + case 0xA0: + position.set(Position.KEY_FUEL_USED, buf.readUnsignedIntLE() * 0.001); + break; + case 0xA2: + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedIntLE() * 0.01); + break; default: buf.readUnsignedIntLE(); break; diff --git a/src/main/java/org/traccar/protocol/MictrackProtocol.java b/src/main/java/org/traccar/protocol/MictrackProtocol.java index c8d64fd81..9fd9666e4 100644 --- a/src/main/java/org/traccar/protocol/MictrackProtocol.java +++ b/src/main/java/org/traccar/protocol/MictrackProtocol.java @@ -15,7 +15,6 @@ */ package org.traccar.protocol; -import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; @@ -28,7 +27,6 @@ public class MictrackProtocol extends BaseProtocol { 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 MictrackProtocolDecoder(MictrackProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java index 0f815ca7b..5ea9f148c 100644 --- a/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java @@ -66,6 +66,8 @@ public class MictrackProtocolDecoder extends BaseProtocolDecoder { private String decodeAlarm(int event) { switch (event) { + case 0: + return Position.ALARM_POWER_ON; case 5: return Position.ALARM_SOS; case 8: @@ -209,8 +211,12 @@ public class MictrackProtocolDecoder extends BaseProtocolDecoder { } private Object decodeLowAltitude( - Channel channel, SocketAddress remoteAddress, String sentence) throws Exception { - String deviceId = sentence.substring(0, sentence.indexOf("$")); + Channel channel, SocketAddress remoteAddress, String sentence) { + int separator = sentence.indexOf("$"); + if (separator < 0) { + return null; + } + String deviceId = sentence.substring(0, separator); DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, deviceId); if (deviceSession == null) { diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java index b8ab134c5..5b04992ec 100644 --- a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 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,10 @@ import org.traccar.model.WifiAccessPoint; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { @@ -100,18 +104,33 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { if (type == MSG_DATA) { + List<Position> positions = new LinkedList<>(); + Set<Integer> keys = new HashSet<>(); + boolean hasLocation = false; Position position = new Position(getProtocolName()); + DeviceSession deviceSession = null; + while (buf.isReadable()) { int endIndex = buf.readUnsignedByte() + buf.readerIndex(); int key = buf.readUnsignedByte(); + + if (keys.contains(key)) { + if (!hasLocation) { + getLastLocation(position, null); + } + positions.add(position); + keys.clear(); + hasLocation = false; + position = new Position(getProtocolName()); + } + keys.add(key); + switch (key) { case 0x01: - DeviceSession deviceSession = getDeviceSession( + deviceSession = getDeviceSession( channel, remoteAddress, buf.readCharSequence(15, StandardCharsets.US_ASCII).toString()); - if (deviceSession == null) { - return null; - } + position.setDeviceId(deviceSession.getDeviceId()); break; case 0x02: @@ -122,6 +141,7 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); break; case 0x20: + hasLocation = true; position.setLatitude(buf.readIntLE() * 0.0000001); position.setLongitude(buf.readIntLE() * 0.0000001); position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE())); @@ -169,6 +189,10 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY_LEVEL, BitUtil.from(status, 24)); position.set(Position.KEY_STATUS, status); break; + case 0x30: + buf.readUnsignedInt(); // timestamp + position.set(Position.KEY_STEPS, buf.readUnsignedInt()); + break; case 0x40: buf.readUnsignedIntLE(); // timestamp int heartRate = buf.readUnsignedByte(); @@ -182,11 +206,20 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { buf.readerIndex(endIndex); } - if (!position.getAttributes().containsKey(Position.KEY_SATELLITES)) { + if (!hasLocation) { getLastLocation(position, null); } + positions.add(position); + + if (deviceSession != null) { + for (Position p : positions) { + p.setDeviceId(deviceSession.getDeviceId()); + } + } else { + return null; + } - return position.getDeviceId() > 0 ? position : null; + return positions; } diff --git a/src/main/java/org/traccar/protocol/MoovboxProtocol.java b/src/main/java/org/traccar/protocol/MoovboxProtocol.java new file mode 100644 index 000000000..7b554266f --- /dev/null +++ b/src/main/java/org/traccar/protocol/MoovboxProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class MoovboxProtocol extends BaseProtocol { + + public MoovboxProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(65535)); + pipeline.addLast(new MoovboxProtocolDecoder(MoovboxProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/MoovboxProtocolDecoder.java b/src/main/java/org/traccar/protocol/MoovboxProtocolDecoder.java new file mode 100644 index 000000000..3116d073c --- /dev/null +++ b/src/main/java/org/traccar/protocol/MoovboxProtocolDecoder.java @@ -0,0 +1,106 @@ +/* + * Copyright 2020 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 com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.model.Position; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import java.net.SocketAddress; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class MoovboxProtocolDecoder extends BaseHttpProtocolDecoder { + + private final DocumentBuilder documentBuilder; + private final XPath xPath; + private final XPathExpression messageExpression; + + public MoovboxProtocolDecoder(Protocol protocol) { + super(protocol); + try { + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + builderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + builderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); + builderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + builderFactory.setXIncludeAware(false); + builderFactory.setExpandEntityReferences(false); + documentBuilder = builderFactory.newDocumentBuilder(); + xPath = XPathFactory.newInstance().newXPath(); + messageExpression = xPath.compile("//gps/coordinates/coordinate"); + } catch (ParserConfigurationException | XPathExpressionException e) { + throw new RuntimeException(e); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + + Document document = documentBuilder.parse(new ByteBufferBackedInputStream(request.content().nioBuffer())); + + String id = document.getDocumentElement().getAttribute("id"); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + NodeList nodes = (NodeList) messageExpression.evaluate(document, XPathConstants.NODESET); + List<Position> positions = new LinkedList<>(); + + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + position.setTime(new Date(Long.parseLong(xPath.evaluate("time", node)) * 1000)); + position.setLatitude(Double.parseDouble(xPath.evaluate("longitude", node))); + position.setLongitude(Double.parseDouble(xPath.evaluate("latitude", node))); + position.setAltitude(Double.parseDouble(xPath.evaluate("altitude", node))); + position.setSpeed(Double.parseDouble(xPath.evaluate("speed", node))); + + position.set(Position.KEY_SATELLITES, Integer.parseInt(xPath.evaluate("satellites", node))); + + positions.add(position); + } + + sendResponse(channel, HttpResponseStatus.OK); + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/NetProtocol.java b/src/main/java/org/traccar/protocol/NetProtocol.java new file mode 100644 index 000000000..c114d19fc --- /dev/null +++ b/src/main/java/org/traccar/protocol/NetProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class NetProtocol extends BaseProtocol { + + public NetProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '!')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new NetProtocolDecoder(NetProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/NetProtocolDecoder.java b/src/main/java/org/traccar/protocol/NetProtocolDecoder.java new file mode 100644 index 000000000..61cb29b6b --- /dev/null +++ b/src/main/java/org/traccar/protocol/NetProtocolDecoder.java @@ -0,0 +1,92 @@ +/* + * Copyright 2020 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.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class NetProtocolDecoder extends BaseProtocolDecoder { + + public NetProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("@L") + .number("ddd") + .number("(d{15})") // imei + .number("xx") + .number("(dd)(dd)(dd)") // date (ddmmyy) + .number("(dd)(dd)(dd)") // time (hhmmss) + .number("(x)") // flags + .number("(dd)(dd)(dddd)") // latitude + .number("(ddd)(dd)(dddd)") // longitude + .number("(x{8})") // status + .number("(x{4})") // speed + .number("(x{6})") // odometer + .number("(xxx)") // course + .number("(xxx)") // alarm + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + int flags = parser.nextHexInt(); + + position.setValid(BitUtil.check(flags, 3)); + int hemisphereLatitude = BitUtil.check(flags, 1) ? -1 : 1; + int hemisphereLongitude = BitUtil.check(flags, 0) ? -1 : 1; + + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN) * hemisphereLatitude); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN) * hemisphereLongitude); + + position.set(Position.KEY_STATUS, parser.nextHexLong()); + position.setSpeed(parser.nextHexInt() * 0.01); + position.set(Position.KEY_ODOMETER, parser.nextHexInt()); + position.setCourse(parser.nextHexInt()); + + parser.nextHexInt(); // alarm + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/NiotProtocolDecoder.java b/src/main/java/org/traccar/protocol/NiotProtocolDecoder.java index 58de0d38f..47c6e2ffd 100644 --- a/src/main/java/org/traccar/protocol/NiotProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/NiotProtocolDecoder.java @@ -31,6 +31,7 @@ import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; public class NiotProtocolDecoder extends BaseProtocolDecoder { @@ -95,12 +96,65 @@ public class NiotProtocolDecoder extends BaseProtocolDecoder { .setSecond(BcdUtil.readInteger(buf, 2)); position.setTime(dateBuilder.getDate()); - position.setValid(true); position.setLatitude(readCoordinate(buf)); position.setLongitude(readCoordinate(buf)); - position.setSpeed(UnitsConverter.knotsFromKph(BcdUtil.readInteger(buf, 4))); + BcdUtil.readInteger(buf, 4); // reserved position.setCourse(BcdUtil.readInteger(buf, 4)); + int statusX = buf.readUnsignedByte(); + position.setValid(BitUtil.check(statusX, 7)); + switch (BitUtil.between(statusX, 3, 5)) { + case 0b10: + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + break; + case 0b01: + position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); + break; + default: + break; + } + + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + + int statusA = buf.readUnsignedByte(); + position.set(Position.KEY_IGNITION, !BitUtil.check(statusA, 7)); + if (!BitUtil.check(statusA, 6)) { + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + } + + buf.readUnsignedByte(); // statusB + buf.readUnsignedByte(); // statusC + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 0.1); + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1); + buf.readUnsignedByte(); // speed limit + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + buf.readUnsignedByte(); // sensor speed + buf.readUnsignedByte(); // reserved + buf.readUnsignedByte(); // reserved + + while (buf.readableBytes() > 4) { + int extendedLength = buf.readUnsignedShort(); + int extendedType = buf.readUnsignedShort(); + switch (extendedType) { + case 0x0001: + position.set(Position.KEY_ICCID, + buf.readCharSequence(20, StandardCharsets.US_ASCII).toString()); + break; + case 0x0002: + int statusD = buf.readUnsignedByte(); + position.set(Position.KEY_ALARM, BitUtil.check(statusD, 5) ? Position.ALARM_REMOVING : null); + position.set(Position.KEY_ALARM, BitUtil.check(statusD, 4) ? Position.ALARM_TAMPERING : null); + buf.readUnsignedByte(); // run mode + buf.readUnsignedByte(); // reserved + break; + default: + buf.skipBytes(extendedLength - 2); + break; + } + + } + return position; } diff --git a/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java b/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java index 323d97fa3..509d14ae4 100644 --- a/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java @@ -110,7 +110,7 @@ public class OwnTracksProtocolDecoder extends BaseHttpProtocolDecoder { if (root.containsKey("t")) { String trigger = root.getString("t"); position.set("t", trigger); - Integer reportType = -1; + int reportType = -1; if (root.containsKey("rty")) { reportType = root.getInt("rty"); } @@ -148,8 +148,8 @@ public class OwnTracksProtocolDecoder extends BaseHttpProtocolDecoder { } if (root.containsKey("anum")) { - Integer numberOfAnalogueInputs = root.getInt("anum"); - for (Integer i = 0; i < numberOfAnalogueInputs; i++) { + int numberOfAnalogueInputs = root.getInt("anum"); + for (int i = 0; i < numberOfAnalogueInputs; i++) { String indexString = String.format("%02d", i); if (root.containsKey("adda-" + indexString)) { position.set(Position.PREFIX_ADC + (i + 1), root.getString("adda-" + indexString)); diff --git a/src/main/java/org/traccar/protocol/PolteProtocol.java b/src/main/java/org/traccar/protocol/PolteProtocol.java new file mode 100644 index 000000000..a3e548716 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PolteProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class PolteProtocol extends BaseProtocol { + + public PolteProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(65535)); + pipeline.addLast(new PolteProtocolDecoder(PolteProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/PolteProtocolDecoder.java b/src/main/java/org/traccar/protocol/PolteProtocolDecoder.java new file mode 100644 index 000000000..ce45abef6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PolteProtocolDecoder.java @@ -0,0 +1,84 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.model.Position; + +import javax.json.Json; +import javax.json.JsonObject; +import java.io.StringReader; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class PolteProtocolDecoder extends BaseHttpProtocolDecoder { + + public PolteProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + String content = request.content().toString(StandardCharsets.UTF_8); + JsonObject json = Json.createReader(new StringReader(content)).readObject(); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, json.getString("ueToken")); + if (deviceSession == null) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + + if (json.containsKey("location")) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + JsonObject location = json.getJsonObject("location"); + + position.setValid(true); + position.setTime(new Date(location.getInt("detected_at") * 1000L)); + position.setLatitude(location.getJsonNumber("latitude").doubleValue()); + position.setLongitude(location.getJsonNumber("longitude").doubleValue()); + position.setAltitude(location.getJsonNumber("altitude").doubleValue()); + + if (json.containsKey("report")) { + JsonObject report = json.getJsonObject("report"); + position.set(Position.KEY_EVENT, report.getInt("event")); + if (report.containsKey("battery")) { + JsonObject battery = report.getJsonObject("battery"); + position.set(Position.KEY_BATTERY_LEVEL, battery.getInt("level")); + position.set(Position.KEY_BATTERY, battery.getJsonNumber("voltage").doubleValue()); + } + } + + return position; + + } + + sendResponse(channel, HttpResponseStatus.OK); + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/PstFrameEncoder.java b/src/main/java/org/traccar/protocol/PstFrameEncoder.java new file mode 100644 index 000000000..5a9ede3e6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PstFrameEncoder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +public class PstFrameEncoder extends MessageToByteEncoder<ByteBuf> { + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) { + + out.writeByte('('); + while (msg.isReadable()) { + int b = msg.readUnsignedByte(); + if (b == 0x27 || b == 0x28 || b == 0x29) { + out.writeByte(0x27); + out.writeByte(b ^ 0x40); + } else { + out.writeByte(b); + } + } + out.writeByte(')'); + } +} diff --git a/src/main/java/org/traccar/protocol/PstProtocol.java b/src/main/java/org/traccar/protocol/PstProtocol.java index 0ed9affd8..d8c7008cb 100644 --- a/src/main/java/org/traccar/protocol/PstProtocol.java +++ b/src/main/java/org/traccar/protocol/PstProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 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,20 +18,27 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.model.Command; public class PstProtocol extends BaseProtocol { public PstProtocol() { + setSupportedDataCommands( + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME); addServer(new TrackerServer(true, getName()) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new PstProtocolEncoder(PstProtocol.this)); pipeline.addLast(new PstProtocolDecoder(PstProtocol.this)); } }); addServer(new TrackerServer(false, getName()) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new PstFrameEncoder()); pipeline.addLast(new PstFrameDecoder()); + pipeline.addLast(new PstProtocolEncoder(PstProtocol.this)); pipeline.addLast(new PstProtocolDecoder(PstProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/PstProtocolDecoder.java b/src/main/java/org/traccar/protocol/PstProtocolDecoder.java index 62cc203d2..e3fe1af62 100644 --- a/src/main/java/org/traccar/protocol/PstProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PstProtocolDecoder.java @@ -38,6 +38,7 @@ public class PstProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_ACK = 0x00; public static final int MSG_STATUS = 0x05; + public static final int MSG_COMMAND = 0x06; private Date readDate(ByteBuf buf) { long value = buf.readUnsignedInt(); @@ -61,21 +62,13 @@ public class PstProtocolDecoder extends BaseProtocolDecoder { Channel channel, SocketAddress remoteAddress, long id, int version, long index, int type) { if (channel != null) { - ByteBuf content = Unpooled.buffer(); - content.writeInt((int) id); - content.writeByte(version); - content.writeInt((int) index); - content.writeByte(MSG_ACK); - content.writeByte(type); - - int checksum = Checksum.crc16(Checksum.CRC16_XMODEM, content.nioBuffer()); - ByteBuf response = Unpooled.buffer(); - response.writeByte('('); - response.writeBytes(content); - content.release(); - response.writeShort(checksum); - response.writeByte(')'); + response.writeInt((int) id); + response.writeByte(version); + response.writeInt((int) index); + response.writeByte(MSG_ACK); + response.writeByte(type); + response.writeShort(Checksum.crc16(Checksum.CRC16_XMODEM, response.nioBuffer())); channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); diff --git a/src/main/java/org/traccar/protocol/PstProtocolEncoder.java b/src/main/java/org/traccar/protocol/PstProtocolEncoder.java new file mode 100644 index 000000000..f3d193324 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PstProtocolEncoder.java @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.model.Command; + +public class PstProtocolEncoder extends BaseProtocolEncoder { + + public PstProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf encodeContent(long deviceId, int type, int data1, int data2) { + + ByteBuf buf = Unpooled.buffer(); + + buf.writeInt((int) Long.parseLong(getUniqueId(deviceId))); + buf.writeByte(0x06); // version + + buf.writeInt(1); // index + buf.writeByte(PstProtocolDecoder.MSG_COMMAND); + buf.writeShort(type); + buf.writeShort(data1); + buf.writeShort(data2); + + buf.writeShort(Checksum.crc16(Checksum.CRC16_XMODEM, buf.nioBuffer())); + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_ENGINE_STOP: + return encodeContent(command.getDeviceId(), 0x0002, 0xffff, 0xffff); + case Command.TYPE_ENGINE_RESUME: + return encodeContent(command.getDeviceId(), 0x0001, 0xffff, 0xffff); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt502ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Pt502ProtocolDecoder.java index 0afec67ad..ff92b51f1 100644 --- a/src/main/java/org/traccar/protocol/Pt502ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Pt502ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2012 Luis Parada (luis.parada@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -55,8 +55,8 @@ public class Pt502ProtocolDecoder extends BaseProtocolDecoder { .expression("([EW]),") .number("(d+.d+)?,") // speed .number("(d+.d+)?,") // course - .number("(dd)(dd)(dd),,,") // date (ddmmyy) - .expression("./") + .number("(dd)(dd)(dd),,,?") // date (ddmmyy) + .expression(".?/") .expression("([01])+,") // input .expression("([01])+/") // output .expression("([^/]+)?/") // adc @@ -108,7 +108,7 @@ public class Pt502ProtocolDecoder extends BaseProtocolDecoder { position.setDeviceId(deviceSession.getDeviceId()); DateBuilder dateBuilder = new DateBuilder() - .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextInt()); position.setValid(parser.next().equals("A")); position.setLatitude(parser.nextCoordinate()); @@ -116,7 +116,7 @@ public class Pt502ProtocolDecoder extends BaseProtocolDecoder { position.setSpeed(parser.nextDouble(0)); position.setCourse(parser.nextDouble(0)); - dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); position.setTime(dateBuilder.getDate()); position.set(Position.KEY_INPUT, parser.next()); diff --git a/src/main/java/org/traccar/protocol/RadarProtocolDecoder.java b/src/main/java/org/traccar/protocol/RadarProtocolDecoder.java index b56a081c7..d87f77b84 100644 --- a/src/main/java/org/traccar/protocol/RadarProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RadarProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 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. @@ -70,6 +70,7 @@ public class RadarProtocolDecoder extends BaseProtocolDecoder { for (int index = 0; index < count; index++) { Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); position.set(Position.KEY_EVENT, buf.readUnsignedShort()); diff --git a/src/main/java/org/traccar/protocol/SpotProtocolDecoder.java b/src/main/java/org/traccar/protocol/SpotProtocolDecoder.java index da36c2048..34417d95f 100644 --- a/src/main/java/org/traccar/protocol/SpotProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SpotProtocolDecoder.java @@ -42,9 +42,9 @@ import java.util.List; public class SpotProtocolDecoder extends BaseHttpProtocolDecoder { - private DocumentBuilder documentBuilder; - private XPath xPath; - private XPathExpression messageExpression; + private final DocumentBuilder documentBuilder; + private final XPath xPath; + private final XPathExpression messageExpression; public SpotProtocolDecoder(Protocol protocol) { super(protocol); diff --git a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java index 2d1613e03..7ba41ad56 100644 --- a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java @@ -20,11 +20,13 @@ import org.traccar.BaseProtocolDecoder; import org.traccar.Context; import org.traccar.DeviceSession; import org.traccar.Protocol; +import org.traccar.helper.DataConverter; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; import org.traccar.model.CellTower; import org.traccar.model.Network; import org.traccar.model.Position; +import org.traccar.protobuf.StarLinkMessage; import java.net.SocketAddress; import java.text.DateFormat; @@ -47,8 +49,8 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { .number("xx") // checksum .compile(); - private String[] dataTags; - private DateFormat dateFormat; + private String format; + private String dateFormat; public StarLinkProtocolDecoder(Protocol protocol) { super(protocol); @@ -60,13 +62,24 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { setDateFormat(Context.getConfig().getString(getProtocolName() + ".dateFormat", "yyMMddHHmmss")); } + public String[] getFormat(long deviceId) { + return Context.getIdentityManager().lookupAttributeString( + deviceId, getProtocolName() + ".format", format, false, false).split(","); + } + public void setFormat(String format) { - dataTags = format.split(","); + this.format = format; + } + + public DateFormat getDateFormat(long deviceId) { + DateFormat dateFormat = new SimpleDateFormat(Context.getIdentityManager().lookupAttributeString( + deviceId, getProtocolName() + ".dateFormat", this.dateFormat, false, false)); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + return dateFormat; } public void setDateFormat(String dateFormat) { - this.dateFormat = new SimpleDateFormat(dateFormat); - this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + this.dateFormat = dateFormat; } private double parseCoordinate(String value) { @@ -128,6 +141,9 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { Integer lac = null, cid = null; int event = 0; + String[] dataTags = getFormat(deviceSession.getDeviceId()); + DateFormat dateFormat = getDateFormat(deviceSession.getDeviceId()); + for (int i = 0; i < Math.min(data.length, dataTags.length); i++) { if (data[i].isEmpty()) { continue; @@ -186,6 +202,9 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { cid = Integer.parseInt(data[i]); } break; + case "#CSS#": + position.set(Position.KEY_RSSI, Integer.parseInt(data[i])); + break; case "#VIN#": position.set(Position.KEY_POWER, Double.parseDouble(data[i])); break; @@ -201,6 +220,32 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { case "#ENG#": position.set("engine", data[i].equals("1")); break; + case "#SATU#": + position.set(Position.KEY_SATELLITES, Integer.parseInt(data[i])); + break; + case "#TS1#": + position.set("sensor1State", Integer.parseInt(data[i])); + break; + case "#TS2#": + position.set("sensor2State", Integer.parseInt(data[i])); + break; + case "#TD1#": + case "#TD2#": + StarLinkMessage.mEventReport_TDx message = + StarLinkMessage.mEventReport_TDx.parseFrom(DataConverter.parseBase64(data[i])); + position.set( + "sensor" + message.getSensorNumber() + "Id", + message.getSensorID()); + position.set( + "sensor" + message.getSensorNumber() + "Temp", + message.getTemperature() * 0.1); + position.set( + "sensor" + message.getSensorNumber() + "Humidity", + message.getTemperature() * 0.1); + position.set( + "sensor" + message.getSensorNumber() + "Voltage", + message.getVoltage() * 0.001); + break; default: break; } diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java index 451b9ba32..76e3e6ecc 100644 --- a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java @@ -479,20 +479,21 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { position.setTime(dateFormat.parse(values[index++] + values[index++])); } + CellTower cellTower = new CellTower(); if (BitUtil.check(mask, 6)) { - index += 1; // cell + cellTower.setCellId(Long.parseLong(values[index++], 16)); } - if (BitUtil.check(mask, 7)) { - index += 1; // mcc + cellTower.setMobileCountryCode(Integer.parseInt(values[index++])); } - if (BitUtil.check(mask, 8)) { - index += 1; // mnc + cellTower.setMobileNetworkCode(Integer.parseInt(values[index++])); } - if (BitUtil.check(mask, 9)) { - index += 1; // lac + cellTower.setLocationAreaCode(Integer.parseInt(values[index++], 16)); + } + if (cellTower.getCellId() != null) { + position.setNetwork(new Network(cellTower)); } if (BitUtil.check(mask, 10)) { @@ -531,16 +532,26 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_OUTPUT, Integer.parseInt(values[index++])); } - if (BitUtil.check(mask, 19)) { - position.set("alertId", values[index++]); - } - - if (BitUtil.check(mask, 20)) { - position.set("alertModifier", values[index++]); - } - - if (BitUtil.check(mask, 21)) { - position.set("alertData", values[index++]); + if (type.equals("ALT")) { + if (BitUtil.check(mask, 19)) { + position.set("alertId", values[index++]); + } + if (BitUtil.check(mask, 20)) { + position.set("alertModifier", values[index++]); + } + if (BitUtil.check(mask, 21)) { + position.set("alertData", values[index++]); + } + } else { + if (BitUtil.check(mask, 19)) { + position.set("mode", Integer.parseInt(values[index++])); + } + if (BitUtil.check(mask, 20)) { + position.set("reason", Integer.parseInt(values[index++])); + } + if (BitUtil.check(mask, 21)) { + position.set(Position.KEY_INDEX, Integer.parseInt(values[index++])); + } } if (BitUtil.check(mask, 22)) { @@ -676,6 +687,24 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { return position; } + private Position decodeTravelReport(Channel channel, SocketAddress remoteAddress, String[] values) { + int index = 1; + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[index++]); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_DRIVER_UNIQUE_ID, values[values.length - 1]); + + return position; + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -693,6 +722,8 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { if (prefix.length() < 5) { return decodeUniversal(channel, remoteAddress, values); + } else if (prefix.endsWith("HTE")) { + return decodeTravelReport(channel, remoteAddress, values); } else if (prefix.startsWith("ST9")) { return decode9(channel, remoteAddress, values); } else if (prefix.startsWith("ST4")) { diff --git a/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java b/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java index b75addfae..230d29216 100644 --- a/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2020 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.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; import java.net.SocketAddress; @@ -109,6 +110,20 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { .any() .compile(); + private static final Pattern PATTERN_QZE = new PatternBuilder() + .text("QZE,") + .number("(d{15}),") // imei + .number("(d+),") // event + .number("(dd)(dd)(dddd),") // date (mmddyyyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .expression("([AV]),") // validity + .expression("([01])") // ignition + .compile(); + private Position position = null; private Position decodeGprmc( @@ -256,6 +271,35 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { return position; } + private Position decodeQze(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_QZE, 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_EVENT, parser.nextInt()); + + position.setTime(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.setValid(parser.next().equals("A")); + + position.set(Position.KEY_IGNITION, parser.nextInt() > 0); + + return position; + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -308,6 +352,8 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { return decodeTrccr(deviceSession, sentence); } else if (sentence.startsWith("$GPIOP")) { return decodeGpiop(deviceSession, sentence); + } else if (sentence.startsWith("QZE")) { + return decodeQze(channel, remoteAddress, sentence); } return null; diff --git a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java index 3331ebb71..5822d6b8f 100644 --- a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java @@ -230,10 +230,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); int battery = BcdUtil.readInteger(buf, 2); - if (battery == 0) { - battery = 100; - } - position.set(Position.KEY_BATTERY, battery); + position.set(Position.KEY_BATTERY_LEVEL, battery > 0 ? battery : 100); } @@ -284,7 +281,8 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { } position.set(Position.KEY_G_SENSOR, "[" + accelerationX + "," + accelerationY + "," + accelerationZ + "]"); - position.set(Position.KEY_BATTERY_LEVEL, BcdUtil.readInteger(buf, 2)); + int battery = BcdUtil.readInteger(buf, 2); + position.set(Position.KEY_BATTERY_LEVEL, battery > 0 ? battery : 100); position.set(Position.KEY_DEVICE_TEMP, (int) buf.readByte()); position.set("lightSensor", BcdUtil.readInteger(buf, 2) * 0.1); position.set(Position.KEY_BATTERY, BcdUtil.readInteger(buf, 2) * 0.1); diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java index 40b769869..0ea02b157 100644 --- a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java @@ -43,9 +43,9 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { private static final int IMAGE_PACKET_MAX = 2048; - private boolean connectionless; + private final boolean connectionless; private boolean extended; - private Map<Long, ByteBuf> photos = new HashMap<>(); + private final Map<Long, ByteBuf> photos = new HashMap<>(); public void setExtended(boolean extended) { this.extended = extended; @@ -208,7 +208,6 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { position.set(Position.PREFIX_ADC + 2, readValue(buf, length, false)); break; case 16: - case 87: position.set(Position.KEY_ODOMETER, readValue(buf, length, false)); break; case 17: @@ -223,9 +222,6 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { case 21: position.set(Position.KEY_RSSI, readValue(buf, length, false)); break; - case 24: - readValue(buf, length, false); // speed - break; case 25: case 26: case 27: @@ -255,34 +251,9 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { case 80: position.set("workMode", readValue(buf, length, false)); break; - case 81: - position.set(Position.KEY_OBD_SPEED, readValue(buf, length, false)); - break; - case 82: - position.set(Position.KEY_THROTTLE, readValue(buf, length, false)); - break; - case 83: - position.set(Position.KEY_FUEL_USED, readValue(buf, length, false) * 0.1); - break; - case 84: - position.set(Position.KEY_FUEL_LEVEL, readValue(buf, length, false) * 0.1); - break; - case 85: - position.set(Position.KEY_RPM, readValue(buf, length, false)); - break; case 90: position.set(Position.KEY_DOOR, readValue(buf, length, false)); break; - case 110: - position.set(Position.KEY_FUEL_CONSUMPTION, readValue(buf, length, true) * 0.1); - break; - case 113: - if (length == 1) { - position.set(Position.KEY_BATTERY_LEVEL, readValue(buf, length, true)); - } else { - position.set(Position.PREFIX_IO + id, readValue(buf, length, false)); - } - break; case 115: position.set(Position.KEY_COOLANT_TEMP, readValue(buf, length, true) * 0.1); break; @@ -311,9 +282,6 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { case 199: position.set(Position.KEY_ODOMETER_TRIP, readValue(buf, length, false)); break; - case 235: - position.set("oilLevel", readValue(buf, length, false)); - break; case 236: if (readValue(buf, length, false) == 1) { position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); @@ -569,6 +537,29 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { int length = buf.readUnsignedShort(); if (id == 256) { position.set(Position.KEY_VIN, buf.readSlice(length).toString(StandardCharsets.US_ASCII)); + } else if (id == 385) { + ByteBuf data = buf.readSlice(length); + data.readUnsignedByte(); // data part + int index = 1; + while (data.isReadable()) { + int flags = data.readUnsignedByte(); + if (BitUtil.from(flags, 4) > 0) { + position.set("beacon" + index + "Uuid", ByteBufUtil.hexDump(data.readSlice(16))); + position.set("beacon" + index + "Major", data.readUnsignedShort()); + position.set("beacon" + index + "Minor", data.readUnsignedShort()); + } else { + position.set("beacon" + index + "Namespace", ByteBufUtil.hexDump(data.readSlice(10))); + position.set("beacon" + index + "Instance", ByteBufUtil.hexDump(data.readSlice(6))); + } + position.set("beacon" + index + "Rssi", (int) data.readByte()); + if (BitUtil.check(flags, 1)) { + position.set("beacon" + index + "Battery", data.readUnsignedShort() * 0.01); + } + if (BitUtil.check(flags, 2)) { + position.set("beacon" + index + "Temp", data.readUnsignedShort()); + } + index += 1; + } } else { position.set(Position.PREFIX_IO + id, ByteBufUtil.hexDump(buf.readSlice(length))); } diff --git a/src/main/java/org/traccar/protocol/V680ProtocolDecoder.java b/src/main/java/org/traccar/protocol/V680ProtocolDecoder.java index 0342404a6..40267022b 100644 --- a/src/main/java/org/traccar/protocol/V680ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/V680ProtocolDecoder.java @@ -62,7 +62,7 @@ public class V680ProtocolDecoder extends BaseProtocolDecoder { if (sentence.length() == 16) { - getDeviceSession(channel, remoteAddress, sentence.substring(1, sentence.length())); + getDeviceSession(channel, remoteAddress, sentence.substring(1)); } else { diff --git a/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java index c132f194b..31a1bbb11 100644 --- a/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java @@ -91,7 +91,7 @@ public class Xt2400ProtocolDecoder extends BaseProtocolDecoder { return length; } - private Map<Short, byte[]> formats = new HashMap<>(); + private final Map<Short, byte[]> formats = new HashMap<>(); public void setConfig(String configString) { Pattern pattern = Pattern.compile(":wycfg pcr\\[\\d+] ([0-9a-fA-F]{2})[0-9a-fA-F]{2}([0-9a-fA-F]+)"); diff --git a/src/main/java/org/traccar/reports/ReportUtils.java b/src/main/java/org/traccar/reports/ReportUtils.java index 3a631e0d9..870264fad 100644 --- a/src/main/java/org/traccar/reports/ReportUtils.java +++ b/src/main/java/org/traccar/reports/ReportUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -81,8 +81,7 @@ public final class ReportUtils { } public static Collection<Long> getDeviceList(Collection<Long> deviceIds, Collection<Long> groupIds) { - Collection<Long> result = new ArrayList<>(); - result.addAll(deviceIds); + Collection<Long> result = new ArrayList<>(deviceIds); for (long groupId : groupIds) { result.addAll(Context.getPermissionsManager().getGroupDevices(groupId)); } @@ -98,7 +97,7 @@ public final class ReportUtils { double firstOdometer = firstPosition.getDouble(Position.KEY_ODOMETER); double lastOdometer = lastPosition.getDouble(Position.KEY_ODOMETER); - if (useOdometer && (firstOdometer != 0.0 || lastOdometer != 0.0)) { + if (useOdometer && firstOdometer != 0.0 && lastOdometer != 0.0) { distance = lastOdometer - firstOdometer; } else if (firstPosition.getAttributes().containsKey(Position.KEY_TOTAL_DISTANCE) && lastPosition.getAttributes().containsKey(Position.KEY_TOTAL_DISTANCE)) { @@ -113,7 +112,7 @@ public final class ReportUtils { if (firstPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null && lastPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null) { - BigDecimal value = new BigDecimal(firstPosition.getDouble(Position.KEY_FUEL_LEVEL) + BigDecimal value = BigDecimal.valueOf(firstPosition.getDouble(Position.KEY_FUEL_LEVEL) - lastPosition.getDouble(Position.KEY_FUEL_LEVEL)); return value.setScale(1, RoundingMode.HALF_EVEN).doubleValue(); } @@ -264,21 +263,10 @@ public final class ReportUtils { stop.setDuration(stopDuration); stop.setSpentFuel(calculateFuel(startStop, endStop)); - long engineHours = 0; if (startStop.getAttributes().containsKey(Position.KEY_HOURS) && endStop.getAttributes().containsKey(Position.KEY_HOURS)) { - engineHours = endStop.getLong(Position.KEY_HOURS) - startStop.getLong(Position.KEY_HOURS); - } else if (Context.getConfig().getBoolean("processing.engineHours.enable")) { - // Temporary fallback for old data, to be removed in May 2019 - for (int i = startIndex + 1; i <= endIndex; i++) { - if (positions.get(i).getBoolean(Position.KEY_IGNITION) - && positions.get(i - 1).getBoolean(Position.KEY_IGNITION)) { - engineHours += positions.get(i).getFixTime().getTime() - - positions.get(i - 1).getFixTime().getTime(); - } - } + stop.setEngineHours(endStop.getLong(Position.KEY_HOURS) - startStop.getLong(Position.KEY_HOURS)); } - stop.setEngineHours(engineHours); if (!ignoreOdometer && startStop.getDouble(Position.KEY_ODOMETER) != 0 diff --git a/src/main/java/org/traccar/reports/Summary.java b/src/main/java/org/traccar/reports/Summary.java index 6d179a873..612761844 100644 --- a/src/main/java/org/traccar/reports/Summary.java +++ b/src/main/java/org/traccar/reports/Summary.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collection; import java.util.Date; @@ -35,27 +36,18 @@ public final class Summary { private Summary() { } - private static SummaryReport calculateSummaryResult(long deviceId, Date from, Date to) throws SQLException { + private static SummaryReport calculateSummaryResult(long deviceId, Collection<Position> positions) { SummaryReport result = new SummaryReport(); result.setDeviceId(deviceId); result.setDeviceName(Context.getIdentityManager().getById(deviceId).getName()); - Collection<Position> positions = Context.getDataManager().getPositions(deviceId, from, to); if (positions != null && !positions.isEmpty()) { Position firstPosition = null; Position previousPosition = null; double speedSum = 0; - boolean engineHoursEnabled = Context.getConfig().getBoolean("processing.engineHours.enable"); for (Position position : positions) { if (firstPosition == null) { firstPosition = position; } - if (engineHoursEnabled && previousPosition != null - && position.getBoolean(Position.KEY_IGNITION) - && previousPosition.getBoolean(Position.KEY_IGNITION)) { - // Temporary fallback for old data, to be removed in May 2019 - result.addEngineHours(position.getFixTime().getTime() - - previousPosition.getFixTime().getTime()); - } previousPosition = position; speedSum += position.getSpeed(); result.setMaxSpeed(position.getSpeed()); @@ -66,8 +58,7 @@ public final class Summary { result.setAverageSpeed(speedSum / positions.size()); result.setSpentFuel(ReportUtils.calculateFuel(firstPosition, previousPosition)); - if (engineHoursEnabled - && firstPosition.getAttributes().containsKey(Position.KEY_HOURS) + if (firstPosition.getAttributes().containsKey(Position.KEY_HOURS) && previousPosition.getAttributes().containsKey(Position.KEY_HOURS)) { result.setEngineHours( previousPosition.getLong(Position.KEY_HOURS) - firstPosition.getLong(Position.KEY_HOURS)); @@ -83,26 +74,59 @@ public final class Summary { result.setEndOdometer(previousPosition.getDouble(Position.KEY_TOTAL_DISTANCE)); } + result.setStartTime(firstPosition.getFixTime()); + result.setEndTime(previousPosition.getServerTime()); } return result; } + private static int getDay(long userId, Date date) { + Calendar calendar = Calendar.getInstance(ReportUtils.getTimezone(userId)); + calendar.setTime(date); + return calendar.get(Calendar.DAY_OF_MONTH); + } + + private static Collection<SummaryReport> calculateSummaryResults( + long userId, long deviceId, Date from, Date to, boolean daily) throws SQLException { + + ArrayList<Position> positions = new ArrayList<>(Context.getDataManager().getPositions(deviceId, from, to)); + + ArrayList<SummaryReport> results = new ArrayList<>(); + if (daily && !positions.isEmpty()) { + int startIndex = 0; + int startDay = getDay(userId, positions.iterator().next().getFixTime()); + for (int i = 0; i < positions.size(); i++) { + int currentDay = getDay(userId, positions.get(i).getFixTime()); + if (currentDay != startDay) { + results.add(calculateSummaryResult(deviceId, positions.subList(startIndex, i))); + startIndex = i; + startDay = currentDay; + } + } + results.add(calculateSummaryResult(deviceId, positions.subList(startIndex, positions.size()))); + } else { + results.add(calculateSummaryResult(deviceId, positions)); + } + + return results; + } + public static Collection<SummaryReport> getObjects(long userId, Collection<Long> deviceIds, - Collection<Long> groupIds, Date from, Date to) throws SQLException { + Collection<Long> groupIds, Date from, Date to, boolean daily) throws SQLException { ReportUtils.checkPeriodLimit(from, to); ArrayList<SummaryReport> result = new ArrayList<>(); for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { Context.getPermissionsManager().checkDevice(userId, deviceId); - result.add(calculateSummaryResult(deviceId, from, to)); + result.addAll(calculateSummaryResults(userId, deviceId, from, to, daily)); } return result; } public static void getExcel(OutputStream outputStream, long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to) throws SQLException, IOException { + Date from, Date to, boolean daily) throws SQLException, IOException { ReportUtils.checkPeriodLimit(from, to); - Collection<SummaryReport> summaries = getObjects(userId, deviceIds, groupIds, from, to); + Collection<SummaryReport> summaries = getObjects(userId, deviceIds, groupIds, from, to, daily); String templatePath = Context.getConfig().getString("report.templatesPath", "templates/export/"); try (InputStream inputStream = new FileInputStream(templatePath + "/summary.xlsx")) { diff --git a/src/main/java/org/traccar/reports/model/BaseReport.java b/src/main/java/org/traccar/reports/model/BaseReport.java index 9f2d1188c..deb42bb8a 100644 --- a/src/main/java/org/traccar/reports/model/BaseReport.java +++ b/src/main/java/org/traccar/reports/model/BaseReport.java @@ -16,6 +16,8 @@ */ package org.traccar.reports.model; +import java.util.Date; + public class BaseReport { private long deviceId; @@ -103,4 +105,24 @@ public class BaseReport { this.endOdometer = endOdometer; } + private Date startTime; + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + private Date endTime; + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + } diff --git a/src/main/java/org/traccar/reports/model/StopReport.java b/src/main/java/org/traccar/reports/model/StopReport.java index 245292b63..e20f2c503 100644 --- a/src/main/java/org/traccar/reports/model/StopReport.java +++ b/src/main/java/org/traccar/reports/model/StopReport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,6 @@ */ package org.traccar.reports.model; -import java.util.Date; - public class StopReport extends BaseReport { private long positionId; @@ -50,26 +48,6 @@ public class StopReport extends BaseReport { this.longitude = longitude; } - private Date startTime; - - public Date getStartTime() { - return startTime; - } - - public void setStartTime(Date startTime) { - this.startTime = startTime; - } - - private Date endTime; - - public Date getEndTime() { - return endTime; - } - - public void setEndTime(Date endTime) { - this.endTime = endTime; - } - private String address; public String getAddress() { @@ -99,8 +77,4 @@ public class StopReport extends BaseReport { public void setEngineHours(long engineHours) { this.engineHours = engineHours; } - - public void addEngineHours(long engineHours) { - this.engineHours += engineHours; - } } diff --git a/src/main/java/org/traccar/reports/model/SummaryReport.java b/src/main/java/org/traccar/reports/model/SummaryReport.java index 6f9e9459f..886f8b9e2 100644 --- a/src/main/java/org/traccar/reports/model/SummaryReport.java +++ b/src/main/java/org/traccar/reports/model/SummaryReport.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,8 +27,4 @@ public class SummaryReport extends BaseReport { public void setEngineHours(long engineHours) { this.engineHours = engineHours; } - - public void addEngineHours(long engineHours) { - this.engineHours += engineHours; - } } diff --git a/src/main/java/org/traccar/reports/model/TripReport.java b/src/main/java/org/traccar/reports/model/TripReport.java index 3140f3019..151c34bd5 100644 --- a/src/main/java/org/traccar/reports/model/TripReport.java +++ b/src/main/java/org/traccar/reports/model/TripReport.java @@ -16,8 +16,6 @@ */ package org.traccar.reports.model; -import java.util.Date; - public class TripReport extends BaseReport { private long startPositionId; @@ -80,16 +78,6 @@ public class TripReport extends BaseReport { this.endLon = endLon; } - private Date startTime; - - public Date getStartTime() { - return startTime; - } - - public void setStartTime(Date startTime) { - this.startTime = startTime; - } - private String startAddress; public String getStartAddress() { @@ -100,16 +88,6 @@ public class TripReport extends BaseReport { this.startAddress = address; } - private Date endTime; - - public Date getEndTime() { - return endTime; - } - - public void setEndTime(Date endTime) { - this.endTime = endTime; - } - private String endAddress; public String getEndAddress() { diff --git a/src/main/java/org/traccar/schedule/ScheduleManager.java b/src/main/java/org/traccar/schedule/ScheduleManager.java new file mode 100644 index 000000000..4def211d0 --- /dev/null +++ b/src/main/java/org/traccar/schedule/ScheduleManager.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.schedule; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +public class ScheduleManager { + + private ScheduledExecutorService executor; + + public void start() { + + executor = Executors.newSingleThreadScheduledExecutor(); + + new TaskDeviceInactivityCheck().schedule(executor); + + } + + public void stop() { + + if (executor != null) { + executor.shutdown(); + executor = null; + } + + } + +} diff --git a/src/main/java/org/traccar/schedule/TaskDeviceInactivityCheck.java b/src/main/java/org/traccar/schedule/TaskDeviceInactivityCheck.java new file mode 100644 index 000000000..80641d7d4 --- /dev/null +++ b/src/main/java/org/traccar/schedule/TaskDeviceInactivityCheck.java @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.schedule; + +import org.traccar.Context; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Position; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class TaskDeviceInactivityCheck implements Runnable { + + public static final String ATTRIBUTE_DEVICE_INACTIVITY_START = "deviceInactivityStart"; + public static final String ATTRIBUTE_DEVICE_INACTIVITY_PERIOD = "deviceInactivityPeriod"; + public static final String ATTRIBUTE_LAST_UPDATE = "lastUpdate"; + + private static final long CHECK_PERIOD_MINUTES = 15; + + public void schedule(ScheduledExecutorService executor) { + executor.scheduleAtFixedRate(this, CHECK_PERIOD_MINUTES, CHECK_PERIOD_MINUTES, TimeUnit.MINUTES); + } + + @Override + public void run() { + long currentTime = System.currentTimeMillis(); + long checkPeriod = TimeUnit.MINUTES.toMillis(CHECK_PERIOD_MINUTES); + + Map<Event, Position> events = new HashMap<>(); + for (Device device : Context.getDeviceManager().getAllDevices()) { + if (device.getLastUpdate() != null && checkDevice(device, currentTime, checkPeriod)) { + Event event = new Event(Event.TYPE_DEVICE_INACTIVE, device.getId()); + event.set(ATTRIBUTE_LAST_UPDATE, device.getLastUpdate().getTime()); + events.put(event, null); + } + } + + Context.getNotificationManager().updateEvents(events); + } + + private boolean checkDevice(Device device, long currentTime, long checkPeriod) { + long deviceInactivityStart = device.getLong(ATTRIBUTE_DEVICE_INACTIVITY_START); + if (deviceInactivityStart > 0) { + long timeThreshold = device.getLastUpdate().getTime() + deviceInactivityStart; + if (currentTime >= timeThreshold) { + + if (currentTime - checkPeriod < timeThreshold) { + return true; + } + + long deviceInactivityPeriod = device.getLong(ATTRIBUTE_DEVICE_INACTIVITY_PERIOD); + if (deviceInactivityPeriod > 0) { + long count = (currentTime - timeThreshold - 1) / deviceInactivityPeriod; + timeThreshold += count * deviceInactivityPeriod; + return currentTime - checkPeriod < timeThreshold; + } + + } + } + return false; + } + +} diff --git a/src/main/java/org/traccar/sms/smpp/SmppClient.java b/src/main/java/org/traccar/sms/smpp/SmppClient.java index 874253d36..6eb850b6a 100644 --- a/src/main/java/org/traccar/sms/smpp/SmppClient.java +++ b/src/main/java/org/traccar/sms/smpp/SmppClient.java @@ -20,7 +20,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; @@ -122,25 +121,19 @@ public class SmppClient implements SmsManager { enquireLinkPeriod = Context.getConfig().getInteger("sms.smpp.enquireLinkPeriod", 60000); enquireLinkTimeout = Context.getConfig().getInteger("sms.smpp.enquireLinkTimeout", 10000); - enquireLinkExecutor = Executors.newScheduledThreadPool(1, new ThreadFactory() { - @Override - public Thread newThread(Runnable runnable) { - Thread thread = new Thread(runnable); - String name = sessionConfig.getName(); - thread.setName("EnquireLink-" + name); - return thread; - } + enquireLinkExecutor = Executors.newScheduledThreadPool(1, runnable -> { + Thread thread = new Thread(runnable); + String name = sessionConfig.getName(); + thread.setName("EnquireLink-" + name); + return thread; }); reconnectionDelay = Context.getConfig().getInteger("sms.smpp.reconnectionDelay", 10000); - reconnectionExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable runnable) { - Thread thread = new Thread(runnable); - String name = sessionConfig.getName(); - thread.setName("Reconnection-" + name); - return thread; - } + reconnectionExecutor = Executors.newSingleThreadScheduledExecutor(runnable -> { + Thread thread = new Thread(runnable); + String name = sessionConfig.getName(); + thread.setName("Reconnection-" + name); + return thread; }); scheduleReconnect(); @@ -257,14 +250,11 @@ public class SmppClient implements SmsManager { @Override public void sendMessageAsync(final String destAddress, final String message, final boolean command) { - executorService.execute(new Runnable() { - @Override - public void run() { - try { - sendMessageSync(destAddress, message, command); - } catch (MessageException | InterruptedException | IllegalStateException error) { - LOGGER.warn("SMS sending error", error); - } + executorService.execute(() -> { + try { + sendMessageSync(destAddress, message, command); + } catch (MessageException | InterruptedException | IllegalStateException error) { + LOGGER.warn("SMS sending error", error); } }); } diff --git a/src/main/java/org/traccar/speedlimit/OverpassSpeedLimitProvider.java b/src/main/java/org/traccar/speedlimit/OverpassSpeedLimitProvider.java new file mode 100644 index 000000000..429a47c76 --- /dev/null +++ b/src/main/java/org/traccar/speedlimit/OverpassSpeedLimitProvider.java @@ -0,0 +1,74 @@ +/* + * Copyright 2020 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.speedlimit; + +import org.traccar.Context; +import org.traccar.helper.UnitsConverter; + +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.ws.rs.client.AsyncInvoker; +import javax.ws.rs.client.InvocationCallback; + +public class OverpassSpeedLimitProvider implements SpeedLimitProvider { + + private final String url; + + public OverpassSpeedLimitProvider(String url) { + this.url = url + "?data=[out:json];way[maxspeed](around:100.0,%f,%f);out%%20tags;"; + } + + private Double parseSpeed(String value) { + if (value.endsWith(" mph")) { + return UnitsConverter.knotsFromMph(Double.parseDouble(value.substring(0, value.length() - 4))); + } else if (value.endsWith(" knots")) { + return Double.parseDouble(value.substring(0, value.length() - 6)); + } else if (value.matches("\\d+")) { + return UnitsConverter.knotsFromKph(Double.parseDouble(value)); + } else { + return null; + } + } + + @Override + public void getSpeedLimit(double latitude, double longitude, SpeedLimitProviderCallback callback) { + String formattedUrl = String.format(url, latitude, longitude); + AsyncInvoker invoker = Context.getClient().target(formattedUrl).request().async(); + invoker.get(new InvocationCallback<JsonObject>() { + @Override + public void completed(JsonObject json) { + JsonArray elements = json.getJsonArray("elements"); + if (!elements.isEmpty()) { + Double maxSpeed = parseSpeed( + elements.getJsonObject(0).getJsonObject("tags").getString("maxspeed")); + if (maxSpeed != null) { + callback.onSuccess(maxSpeed); + } else { + callback.onFailure(new SpeedLimitException("Parsing failed")); + } + } else { + callback.onFailure(new SpeedLimitException("Not found")); + } + } + + @Override + public void failed(Throwable throwable) { + callback.onFailure(throwable); + } + }); + } + +} diff --git a/src/main/java/org/traccar/speedlimit/SpeedLimitException.java b/src/main/java/org/traccar/speedlimit/SpeedLimitException.java new file mode 100644 index 000000000..453c952d3 --- /dev/null +++ b/src/main/java/org/traccar/speedlimit/SpeedLimitException.java @@ -0,0 +1,24 @@ +/* + * Copyright 2020 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.speedlimit; + +public class SpeedLimitException extends RuntimeException { + + public SpeedLimitException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/traccar/speedlimit/SpeedLimitProvider.java b/src/main/java/org/traccar/speedlimit/SpeedLimitProvider.java new file mode 100644 index 000000000..f6579f87f --- /dev/null +++ b/src/main/java/org/traccar/speedlimit/SpeedLimitProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright 2020 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.speedlimit; + +public interface SpeedLimitProvider { + + interface SpeedLimitProviderCallback { + + void onSuccess(double speedLimit); + + void onFailure(Throwable e); + + } + + void getSpeedLimit(double latitude, double longitude, SpeedLimitProviderCallback callback); + +} diff --git a/src/main/java/org/traccar/web/CsvBuilder.java b/src/main/java/org/traccar/web/CsvBuilder.java index 3fe7e408f..b962be072 100644 --- a/src/main/java/org/traccar/web/CsvBuilder.java +++ b/src/main/java/org/traccar/web/CsvBuilder.java @@ -21,7 +21,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; import java.util.Date; import java.util.Map; import java.util.SortedSet; @@ -45,23 +44,21 @@ public class CsvBuilder { private void addLineEnding() { builder.append(LINE_ENDING); } + private void addSeparator() { builder.append(SEPARATOR); } private SortedSet<Method> getSortedMethods(Object object) { Method[] methodArray = object.getClass().getMethods(); - SortedSet<Method> methods = new TreeSet<>(new Comparator<Method>() { - @Override - public int compare(Method m1, Method m2) { - if (m1.getName().equals("getAttributes") && !m1.getName().equals(m2.getName())) { - return 1; - } - if (m2.getName().equals("getAttributes") && !m1.getName().equals(m2.getName())) { - return -1; - } - return m1.getName().compareTo(m2.getName()); + SortedSet<Method> methods = new TreeSet<>((m1, m2) -> { + if (m1.getName().equals("getAttributes") && !m1.getName().equals(m2.getName())) { + return 1; + } + if (m2.getName().equals("getAttributes") && !m1.getName().equals(m2.getName())) { + return -1; } + return m1.getName().compareTo(m2.getName()); }); methods.addAll(Arrays.asList(methodArray)); return methods; diff --git a/src/main/java/org/traccar/web/WebServer.java b/src/main/java/org/traccar/web/WebServer.java index 12fa80d10..44d78cd27 100644 --- a/src/main/java/org/traccar/web/WebServer.java +++ b/src/main/java/org/traccar/web/WebServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package org.traccar.web; +import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.proxy.AsyncProxyServlet; @@ -32,6 +33,7 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.servlet.ServletContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.traccar.api.DateParameterConverterProvider; import org.traccar.config.Config; import org.traccar.api.AsyncSocketServlet; import org.traccar.api.CorsResponseFilter; @@ -44,6 +46,7 @@ import org.traccar.config.Keys; import javax.servlet.DispatcherType; import javax.servlet.ServletException; +import javax.servlet.SessionCookieConfig; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; @@ -75,12 +78,8 @@ public class WebServer { ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); - int sessionTimeout = config.getInteger("web.sessionTimeout"); - if (sessionTimeout > 0) { - servletHandler.getSessionHandler().setMaxInactiveInterval(sessionTimeout); - } - initApi(config, servletHandler); + initSessionConfig(config, servletHandler); if (config.getBoolean("web.console")) { servletHandler.addServlet(new ServletHolder(new ConsoleServlet()), "/console/*"); @@ -161,12 +160,39 @@ public class WebServer { } ResourceConfig resourceConfig = new ResourceConfig(); - resourceConfig.registerClasses(JacksonFeature.class, ObjectMapperProvider.class, ResourceErrorHandler.class); - resourceConfig.registerClasses(SecurityRequestFilter.class, CorsResponseFilter.class); + resourceConfig.registerClasses( + JacksonFeature.class, ObjectMapperProvider.class, ResourceErrorHandler.class, + SecurityRequestFilter.class, CorsResponseFilter.class, DateParameterConverterProvider.class); resourceConfig.packages(ServerResource.class.getPackage().getName()); servletHandler.addServlet(new ServletHolder(new ServletContainer(resourceConfig)), "/api/*"); } + private void initSessionConfig(Config config, ServletContextHandler servletHandler) { + int sessionTimeout = config.getInteger("web.sessionTimeout"); + if (sessionTimeout > 0) { + servletHandler.getSessionHandler().setMaxInactiveInterval(sessionTimeout); + } + + String sameSiteCookie = config.getString(Keys.WEB_SAME_SITE_COOKIE); + if (sameSiteCookie != null) { + SessionCookieConfig sessionCookieConfig = servletHandler.getServletContext().getSessionCookieConfig(); + switch (sameSiteCookie.toLowerCase()) { + case "lax": + sessionCookieConfig.setComment(HttpCookie.SAME_SITE_LAX_COMMENT); + break; + case "strict": + sessionCookieConfig.setComment(HttpCookie.SAME_SITE_STRICT_COMMENT); + break; + case "none": + sessionCookieConfig.setSecure(true); + sessionCookieConfig.setComment(HttpCookie.SAME_SITE_NONE_COMMENT); + break; + default: + break; + } + } + } + public void start() { try { server.start(); diff --git a/src/main/proto/StarLinkMessage.proto b/src/main/proto/StarLinkMessage.proto new file mode 100644 index 000000000..167b9f283 --- /dev/null +++ b/src/main/proto/StarLinkMessage.proto @@ -0,0 +1,330 @@ +syntax = "proto2"; + +package org.traccar.protobuf; + +// StarLink Message +message StarLink_Message { + optional uint32 FID = 1; // Unit factory serial + optional string IMEI = 2; // IMEI + optional uint32 UID = 3; // Unit serial + optional uint32 AckRef = 4; // Ack reference number + + repeated mMessage Message = 5; +} + +// Message body +message mMessage { + optional uint64 ReferenceNumber = 100; + optional uint32 ReferenceNumberDiff = 101; + optional uint32 MessageType = 102; + + optional mWakeUp WakeUp = 1; + optional mConfirmation Confirmation = 2; + optional mParamResponse ParamResponse = 3; + optional mEventReport EventReport = 6; + optional mGeoZone GeoZone = 10; + optional mOutputs Outputs = 12; + optional mEventConfig EventConfig = 14; + optional mInputs Inputs = 16; + optional mInputConfig InputConfig = 17; + optional mSpeedLimit SpeedLimit = 27; + optional mUnitIDChanged UnitIDChanged = 29; + optional mGenDeviceMsg GenericDeviceMsg= 30; + optional mGenDeviceFW GenericDeviceFW = 35; + optional mReadIButton ReadIButton = 42; + optional mGetPattern GetPattern = 43; + optional mMultiParam MultiParameter = 45; + optional mTempConfig TempConfig = 52; + optional mDeviceList DeviceList = 56; + optional mTeachRemote TeachRemote = 58; + optional mAuthenticate Authenticate = 95; + optional mEventReport CurrentLocation = 96; + optional mConfigServer ConfigServer = 98; +} + +// Wake-Up event +message mWakeUp { + required string UnitVersion = 1; + required string ProtocolVersion = 2; +} + +// Confirmation message +message mConfirmation { + // standard response + required uint32 ResultCode = 1; + optional uint32 ReferenceNumber = 2; // confirmation by reference number + optional uint32 MessageType = 3; // confirmation by message type + + // parameter ack (response to command #4) + optional uint32 ParameterAck = 4; + + // multi-parameter ack (response to command #46) + optional uint32 ParametersChanged = 5; + optional uint32 ParametersFailed = 6; + repeated uint32 ParameterFailedList = 9; + + // modify list of allowed drivers (response to command #50) + optional uint32 CodesInList = 7; + optional uint32 EmptySlots = 8; +} + +// Parameter response +message mParamResponse { + required uint32 ParameterNumber = 1; + required string Value = 2; +} + +// Event report +message mEventReport { + optional uint32 EID = 1; // https://sweb.erm.co.il/protocol/?i=D12 + optional string EDESC = 2; // https://sweb.erm.co.il/protocol/?i=D13 + optional uint32 EDT = 3; // https://sweb.erm.co.il/protocol/?i=D27 (seconds since 1/1/2000) + optional sint32 EDT_Diff = 4; // seconds since previous message's EDT + optional uint32 NXT = 5; // https://sweb.erm.co.il/protocol/?i=D111 + optional uint32 NXTS = 81; // https://sweb.erm.co.il/protocol/?i=D178 + optional string ExtraData = 254; + + optional uint32 PDT = 6; // https://sweb.erm.co.il/protocol/?i=D35 (seconds since 1/1/2000) + optional sint32 PDT_Diff = 7; // seconds since previous message's PDT + optional sint32 LAT = 8; // https://sweb.erm.co.il/protocol/?i=D49 (multiplied by 1000000) + optional sint32 LAT_Diff = 9; // difference between previous latitude (add this value to previous value) + optional sint32 LONG = 10; // https://sweb.erm.co.il/protocol/?i=D57 (multiplied by 1000000) + optional sint32 LONG_Diff = 11; // difference between previous longitude (add this value to previous value) + optional sint32 ALT = 12; // https://sweb.erm.co.il/protocol/?i=D69 (multiplied by 10) + optional sint32 ALT_Diff = 13; // difference between previous altitude (add this value to previous value) + optional uint32 SPD = 14; // https://sweb.erm.co.il/protocol/?i=D61 (multiplied by 10, in knots) + optional uint32 SPDK = 15; // https://sweb.erm.co.il/protocol/?i=D62 + optional uint32 HEAD = 16; // https://sweb.erm.co.il/protocol/?i=D63 + optional uint32 HDOP = 17; // https://sweb.erm.co.il/protocol/?i=D71 (multiplied by 10) + optional uint32 VDOP = 18; // https://sweb.erm.co.il/protocol/?i=D72 (multiplied by 10) + optional uint32 PDOP = 19; // https://sweb.erm.co.il/protocol/?i=D70 (multiplied by 10) + optional uint32 SAT = 20; // https://sweb.erm.co.il/protocol/?i=D73 + optional uint32 SATN = 21; // https://sweb.erm.co.il/protocol/?i=D99 + optional uint32 SATU = 22; // https://sweb.erm.co.il/protocol/?i=D74 + optional uint32 GSS = 23; // https://sweb.erm.co.il/protocol/?i=D98 + optional uint32 GSSN = 24; // https://sweb.erm.co.il/protocol/?i=D100 + optional uint32 FIX = 25; // https://sweb.erm.co.il/protocol/?i=D75 + optional uint32 LOCA = 26; // https://sweb.erm.co.il/protocol/?i=D126 + optional uint32 LOCS = 79; // https://sweb.erm.co.il/protocol/?i=D167 + + optional uint32 ODO = 27; // https://sweb.erm.co.il/protocol/?i=D14 + optional sint32 ODO_Diff = 28; // difference between previous odometer (add this value to previous value) + optional uint32 ODOD = 29; // https://sweb.erm.co.il/protocol/?i=D15 (multiplied by 1000) + optional sint32 ODOD_Diff = 30; // difference between previous odometer (add this value to previous value) + + optional uint32 CID = 31; // https://sweb.erm.co.il/protocol/?i=D5 + optional uint32 LAC = 32; // https://sweb.erm.co.il/protocol/?i=D4 + optional uint32 CSS = 33; // https://sweb.erm.co.il/protocol/?i=D89 + optional sint32 CSS_Diff = 34; // difference between previous rx power (add this value to previous value) + optional uint32 CTA = 35; // https://sweb.erm.co.il/protocol/?i=D95 + optional uint32 CCN = 36; // https://sweb.erm.co.il/protocol/?i=D89 + optional bool JAM = 37; // https://sweb.erm.co.il/protocol/?i=D66 + optional uint32 NC = 38; // https://sweb.erm.co.il/protocol/?i=D94 + optional uint32 NM = 39; // https://sweb.erm.co.il/protocol/?i=D125 + optional uint32 NT = 76; // https://sweb.erm.co.il/protocol/?i=D153 + optional uint32 CU = 77; // https://sweb.erm.co.il/protocol/?i=D154 + + optional uint32 VIN = 40; // https://sweb.erm.co.il/protocol/?i=D64 (multiplied by 100) + optional sint32 VIN_Diff = 41; // difference between previous VIN (add this value to previous value) + optional uint32 VBAT = 42; // https://sweb.erm.co.il/protocol/?i=D65 (multiplied by 100) + optional sint32 VBAT_Diff = 43; // difference between previous VBAT (add this value to previous value) + optional uint32 V3 = 44; // https://sweb.erm.co.il/protocol/?i=D92 (multiplied by 100) + optional sint32 V3_Diff = 45; // difference between previous V3 (add this value to previous value) + optional uint32 V4 = 46; // https://sweb.erm.co.il/protocol/?i=D93 (multiplied by 100) + optional sint32 V4_Diff = 47; // difference between previous V4 (add this value to previous value) + optional uint32 BATH = 48; + optional uint32 BATC = 49; + optional uint32 STRT = 50; // (multiplied by 100) + optional sint32 TVI = 51; // https://sweb.erm.co.il/protocol/?i=D110 (multiplied by 10) + + optional uint32 Inputs = 52; // b0 = IN1/INC, b1 = IN2/IND, b2 = IN3/INE, b3 = IN4/INF + optional uint32 DInputs = 53; // b0 = DI1, b1 = DI2, b2 = DI3, b3 = DI4, b4 = DI5, b5 = DI6, b6 = DI7, b7 = DI8 + optional uint32 IgnitionEngine = 54; // b0 = IGN, b1 = ENG, b2 = DRV + optional uint32 Outputs = 55; // b0 = OUT1/OUTA, b1 = OUT2/OUTB, b2 = OUT3/OUTC, b3 = OUT4/OUTD, b4 = OUT5, b5 = OUT6, b6 = OUT7, b7 = OUT8 + + optional string USER = 56; // https://sweb.erm.co.il/protocol/?i=D97 + optional string EDV1 = 57; // https://sweb.erm.co.il/protocol/?i=D179 + optional string EDV2 = 58; // https://sweb.erm.co.il/protocol/?i=D180 + optional string EDV3 = 59; // https://sweb.erm.co.il/protocol/?i=D181 + optional string EDV4 = 60; // https://sweb.erm.co.il/protocol/?i=D182 + + optional sint32 CV1 = 61; // https://sweb.erm.co.il/protocol/?i=D121 + optional sint32 CV2 = 62; // https://sweb.erm.co.il/protocol/?i=D122 + optional sint32 CV3 = 63; // https://sweb.erm.co.il/protocol/?i=D123 + optional sint32 CV4 = 64; // https://sweb.erm.co.il/protocol/?i=D124 + optional sint32 CV5 = 82; // https://sweb.erm.co.il/protocol/?i=D174 + optional sint32 CV6 = 83; // https://sweb.erm.co.il/protocol/?i=D175 + optional sint32 CV7 = 84; // https://sweb.erm.co.il/protocol/?i=D176 + optional sint32 CV8 = 85; // https://sweb.erm.co.il/protocol/?i=D177 + optional sint32 CV9 = 86; // https://sweb.erm.co.il/protocol/?i=D188 + optional sint32 CV10 = 87; // https://sweb.erm.co.il/protocol/?i=D189 + optional sint32 CV11 = 88; // https://sweb.erm.co.il/protocol/?i=D190 + optional sint32 CV12 = 89; // https://sweb.erm.co.il/protocol/?i=D191 + + optional bool IARM = 65; // https://sweb.erm.co.il/protocol/?i=D7 + optional string DID = 66; // https://sweb.erm.co.il/protocol/?i=D101 + optional string DAL = 67; // https://sweb.erm.co.il/protocol/?i=D76 + optional uint32 DEST = 68; // https://sweb.erm.co.il/protocol/?i=D8 + optional bool OVM = 73; // https://sweb.erm.co.il/protocol/?i=D150 + + optional uint32 CFL = 69; // https://sweb.erm.co.il/protocol/?i=D26 + optional uint32 CFL2 = 80; // https://sweb.erm.co.il/protocol/?i=D26 + optional uint32 RPM = 70; // https://sweb.erm.co.il/protocol/?i=D144 + + optional uint32 DUR = 71; // https://sweb.erm.co.il/protocol/?i=D145 + optional uint32 TDUR = 72; // https://sweb.erm.co.il/protocol/?i=D146 + optional uint32 IDL = 74; // https://sweb.erm.co.il/protocol/?i=D151 + optional uint32 STP = 75; // https://sweb.erm.co.il/protocol/?i=D152 + + repeated mEventReport_TDx TDx = 78; // https://sweb.erm.co.il/protocol/?i=D168 +} + +// Sensor data +message mEventReport_TDx { + required uint32 SensorNumber = 1; // Sensor # + optional string SensorID = 3; // TIx (binary) + + optional sint32 Temperature = 4; // TVx (multiplied by 10) + optional uint32 Temperature_State = 2; // TSx + + optional uint32 Humidity = 5; // THx (multiplied by 10) + optional uint32 Humidity_State = 6; + + optional uint32 RSSI = 7; + optional uint32 RSSI_State = 8; + + optional uint32 Voltage = 9; // mV (multiplied by 1000) + optional uint32 Voltage_State = 10; + + optional uint32 Light = 11; // percent + optional uint32 Light_State = 12; + + optional uint32 MagnetSwitch = 13; + optional uint32 MagnetSwitch_State = 14; + + optional uint32 Accident = 15; + optional uint32 Button = 16; + optional uint32 Movement = 17; +} + +// Geozone reqsponse +message mGeoZone { + required uint32 ZoneNumber = 1; + repeated mLatLong Point = 2; // single point for radius geozone, multiple points for polygon + optional uint32 Radius = 3; +} + +// Outputs response +message mOutputs { + required uint32 OutputBitmask = 1; +} + +// Event configuration response +message mEventConfig { + required uint32 EventID = 1; + required uint32 Configuration = 2; // bitmask +} + +// Inputs response +message mInputs { + required uint32 InputBitmask = 1; +} + +// Inputs configuration +message mInputConfig { + required uint32 InputID = 1; + required uint32 DebounceLow = 2; + required uint32 DebounceHigh = 3; +} + +// Speed limit configuration +message mSpeedLimit { + required uint32 Number = 1; + required bool Active = 2; + optional uint32 MinSpeed = 3; // when using speed limit with minimum speed + optional uint32 MaxSpeed = 4; // when using speed limit with maximum speed + required uint32 Duration = 5; + required bool ReportDuration = 6; + optional bool StartAndEnd = 7; + optional bool AutoTrack = 8; + optional bool OutputPattern = 9; + optional uint32 Step = 10; +} + +// Unit ID changed +message mUnitIDChanged { + required uint32 FID = 1; + required string IMEI = 2; + required sint32 PreviousSetting = 3; + required sint32 NewSetting = 4; +} + +// Generic device message +message mGenDeviceMsg { + required uint32 DeviceID = 1; + required uint32 DataType = 2; + optional string Data = 3; +} + +// Generic device FW update +message mGenDeviceFW { + optional uint32 BlockRequest = 1; + optional uint32 Status = 2; + optional uint32 MaxBlocks = 3; +} + +// Configuration server message +message mConfigServer { + required bool Success = 1; +} + +// Read iButton +message mReadIButton { + required string iButtonID = 1; + optional string Data = 2; +} + +// Read pattern configuration +message mGetPattern { + required uint32 PatternID = 1; + required string Pattern = 2; +} + +// Multiple parameters response +message mMultiParam { + repeated uint32 ParameterNumbers = 1; + repeated string Values = 2; +} + +// Temperature sensor configuration +message mTempConfig { + required uint32 SensorCount = 1; + repeated string SensorID = 2; +} + +// Device list +message mDeviceList { + repeated uint32 DeviceID = 1; + repeated uint32 Version = 2; +} + +// Teach remote +message mTeachRemote { + required uint32 RuleID = 1; + optional uint32 RemoteID = 2; +} + +// Authentication message +message mAuthenticate { + required uint32 Version = 1; + optional string Token = 2; + optional uint32 Key = 3; + optional uint32 Step = 4; + optional string Digest = 5; +} + +// Point (Lat / Long) +message mLatLong { + required sint32 Latitude = 1; + required sint32 Longitude = 2; +} |