diff options
Diffstat (limited to 'src/main/java/org/traccar')
787 files changed, 19275 insertions, 9873 deletions
diff --git a/src/main/java/org/traccar/BasePipelineFactory.java b/src/main/java/org/traccar/BasePipelineFactory.java index c9f3a2346..b184da45c 100644 --- a/src/main/java/org/traccar/BasePipelineFactory.java +++ b/src/main/java/org/traccar/BasePipelineFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package org.traccar; +import com.google.inject.Injector; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelInboundHandler; @@ -22,6 +23,7 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOutboundHandler; import io.netty.channel.ChannelPipeline; import io.netty.handler.timeout.IdleStateHandler; +import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.handler.ComputedAttributesHandler; import org.traccar.handler.CopyAttributesHandler; @@ -43,10 +45,11 @@ import org.traccar.handler.events.AlertEventHandler; import org.traccar.handler.events.BehaviorEventHandler; import org.traccar.handler.events.CommandResultEventHandler; import org.traccar.handler.events.DriverEventHandler; -import org.traccar.handler.events.FuelDropEventHandler; +import org.traccar.handler.events.FuelEventHandler; import org.traccar.handler.events.GeofenceEventHandler; import org.traccar.handler.events.IgnitionEventHandler; import org.traccar.handler.events.MaintenanceEventHandler; +import org.traccar.handler.events.MediaEventHandler; import org.traccar.handler.events.MotionEventHandler; import org.traccar.handler.events.OverspeedEventHandler; @@ -54,26 +57,30 @@ import java.util.Map; public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { - private final TrackerServer server; + private final Injector injector; + private final TrackerConnector connector; private final String protocol; private int timeout; - public BasePipelineFactory(TrackerServer server, String protocol) { - this.server = server; + public BasePipelineFactory(TrackerConnector connector, Config config, String protocol) { + this.injector = Main.getInjector(); + this.connector = connector; this.protocol = protocol; - timeout = Context.getConfig().getInteger(Keys.PROTOCOL_TIMEOUT.withPrefix(protocol)); + timeout = config.getInteger(Keys.PROTOCOL_TIMEOUT.withPrefix(protocol)); if (timeout == 0) { - timeout = Context.getConfig().getInteger(Keys.SERVER_TIMEOUT); + timeout = config.getInteger(Keys.SERVER_TIMEOUT); } } + protected abstract void addTransportHandlers(PipelineBuilder pipeline); + protected abstract void addProtocolHandlers(PipelineBuilder pipeline); @SafeVarargs - private final void addHandlers(ChannelPipeline pipeline, Class<? extends ChannelHandler>... handlerClasses) { + private void addHandlers(ChannelPipeline pipeline, Class<? extends ChannelHandler>... handlerClasses) { for (Class<? extends ChannelHandler> handlerClass : handlerClasses) { if (handlerClass != null) { - pipeline.addLast(Main.getInjector().getInstance(handlerClass)); + pipeline.addLast(injector.getInstance(handlerClass)); } } } @@ -97,15 +104,19 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { protected void initChannel(Channel channel) { final ChannelPipeline pipeline = channel.pipeline(); - if (timeout > 0 && !server.isDatagram()) { + addTransportHandlers(pipeline::addLast); + + if (timeout > 0 && !connector.isDatagram()) { pipeline.addLast(new IdleStateHandler(timeout, 0, 0)); } - pipeline.addLast(new OpenChannelHandler(server)); + pipeline.addLast(new OpenChannelHandler(connector)); pipeline.addLast(new NetworkMessageHandler()); pipeline.addLast(new StandardLoggingHandler(protocol)); addProtocolHandlers(handler -> { - if (!(handler instanceof BaseProtocolDecoder || handler instanceof BaseProtocolEncoder)) { + if (handler instanceof BaseProtocolDecoder || handler instanceof BaseProtocolEncoder) { + injector.injectMembers(handler); + } else { if (handler instanceof ChannelInboundHandler) { handler = new WrapperInboundHandler((ChannelInboundHandler) handler); } else { @@ -129,20 +140,20 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { CopyAttributesHandler.class, EngineHoursHandler.class, ComputedAttributesHandler.class, - WebDataHandler.class, + PositionForwardingHandler.class, DefaultDataHandler.class, + MediaEventHandler.class, CommandResultEventHandler.class, OverspeedEventHandler.class, BehaviorEventHandler.class, - FuelDropEventHandler.class, + FuelEventHandler.class, MotionEventHandler.class, GeofenceEventHandler.class, AlertEventHandler.class, IgnitionEventHandler.class, MaintenanceEventHandler.class, - DriverEventHandler.class); - - pipeline.addLast(new MainEventHandler()); + DriverEventHandler.class, + MainEventHandler.class); } } diff --git a/src/main/java/org/traccar/BaseProtocol.java b/src/main/java/org/traccar/BaseProtocol.java index bd3391822..d19fc307e 100644 --- a/src/main/java/org/traccar/BaseProtocol.java +++ b/src/main/java/org/traccar/BaseProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,10 @@ import io.netty.channel.Channel; import io.netty.handler.codec.string.StringEncoder; import org.traccar.helper.DataConverter; import org.traccar.model.Command; +import org.traccar.sms.SmsManager; +import javax.annotation.Nullable; +import javax.inject.Inject; import java.net.SocketAddress; import java.util.Arrays; import java.util.Collection; @@ -35,7 +38,9 @@ public abstract class BaseProtocol implements Protocol { private final String name; private final Set<String> supportedDataCommands = new HashSet<>(); private final Set<String> supportedTextCommands = new HashSet<>(); - private final List<TrackerServer> serverList = new LinkedList<>(); + private final List<TrackerConnector> connectorList = new LinkedList<>(); + + private SmsManager smsManager; private StringProtocolEncoder textCommandEncoder = null; @@ -48,18 +53,27 @@ public abstract class BaseProtocol implements Protocol { name = nameFromClass(getClass()); } + @Inject + public void setSmsManager(@Nullable SmsManager smsManager) { + this.smsManager = smsManager; + } + @Override public String getName() { return name; } protected void addServer(TrackerServer server) { - serverList.add(server); + connectorList.add(server); + } + + protected void addClient(TrackerClient client) { + connectorList.add(client); } @Override - public Collection<TrackerServer> getServerList() { - return serverList; + public Collection<TrackerConnector> getConnectorList() { + return connectorList; } public void setSupportedDataCommands(String... commands) { @@ -107,13 +121,13 @@ public abstract class BaseProtocol implements Protocol { @Override public void sendTextCommand(String destAddress, Command command) throws Exception { - if (Context.getSmsManager() != null) { + if (smsManager != null) { if (command.getType().equals(Command.TYPE_CUSTOM)) { - Context.getSmsManager().sendMessageSync(destAddress, command.getString(Command.KEY_DATA), true); + smsManager.sendMessage(destAddress, command.getString(Command.KEY_DATA), true); } else if (supportedTextCommands.contains(command.getType()) && textCommandEncoder != null) { String encodedCommand = (String) textCommandEncoder.encodeCommand(command); if (encodedCommand != null) { - Context.getSmsManager().sendMessageSync(destAddress, encodedCommand, true); + smsManager.sendMessage(destAddress, encodedCommand, true); } else { throw new RuntimeException("Failed to encode command"); } diff --git a/src/main/java/org/traccar/BaseProtocolDecoder.java b/src/main/java/org/traccar/BaseProtocolDecoder.java index a40756796..382daf92f 100644 --- a/src/main/java/org/traccar/BaseProtocolDecoder.java +++ b/src/main/java/org/traccar/BaseProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,47 +15,82 @@ */ package org.traccar; +import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; -import io.netty.channel.socket.DatagramChannel; -import io.netty.handler.codec.http.HttpRequestDecoder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.database.CommandsManager; -import org.traccar.database.ConnectionManager; -import org.traccar.database.IdentityManager; +import org.traccar.database.MediaManager; import org.traccar.database.StatisticsManager; import org.traccar.helper.UnitsConverter; +import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Command; import org.traccar.model.Device; import org.traccar.model.Position; +import org.traccar.session.ConnectionManager; +import org.traccar.session.DeviceSession; +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.StorageException; +import javax.inject.Inject; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Collection; import java.util.Date; -import java.util.HashMap; import java.util.HashSet; -import java.util.Map; import java.util.Set; import java.util.TimeZone; public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { - private static final Logger LOGGER = LoggerFactory.getLogger(BaseProtocolDecoder.class); - private static final String PROTOCOL_UNKNOWN = "unknown"; - private final Config config = Context.getConfig(); - private final IdentityManager identityManager = Context.getIdentityManager(); - private final ConnectionManager connectionManager = Context.getConnectionManager(); - private final StatisticsManager statisticsManager; private final Protocol protocol; + private CacheManager cacheManager; + private ConnectionManager connectionManager; + private StatisticsManager statisticsManager; + private MediaManager mediaManager; + private CommandsManager commandsManager; + public BaseProtocolDecoder(Protocol protocol) { this.protocol = protocol; - statisticsManager = Main.getInjector() != null ? Main.getInjector().getInstance(StatisticsManager.class) : null; + } + + public CacheManager getCacheManager() { + return cacheManager; + } + + @Inject + public void setCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + + @Inject + public void setConnectionManager(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + @Inject + public void setStatisticsManager(StatisticsManager statisticsManager) { + this.statisticsManager = statisticsManager; + } + + @Inject + public void setMediaManager(MediaManager mediaManager) { + this.mediaManager = mediaManager; + } + + @Inject + public void setCommandsManager(CommandsManager commandsManager) { + this.commandsManager = commandsManager; + } + + public CommandsManager getCommandsManager() { + return commandsManager; + } + + public String writeMediaFile(String uniqueId, ByteBuf buf, String extension) { + return mediaManager.writeFile(uniqueId, buf, extension); } public String getProtocolName() { @@ -63,7 +98,7 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { } public String getServer(Channel channel, char delimiter) { - String server = config.getString(Keys.PROTOCOL_SERVER.withPrefix(getProtocolName())); + String server = getConfig().getString(Keys.PROTOCOL_SERVER.withPrefix(getProtocolName())); if (server == null && channel != null) { InetSocketAddress address = (InetSocketAddress) channel.localAddress(); server = address.getAddress().getHostAddress() + ":" + address.getPort(); @@ -72,7 +107,7 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { } protected double convertSpeed(double value, String defaultUnits) { - switch (config.getString(getProtocolName() + ".speed", defaultUnits)) { + switch (getConfig().getString(getProtocolName() + ".speed", defaultUnits)) { case "kmh": return UnitsConverter.knotsFromKph(value); case "mps": @@ -91,101 +126,18 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { protected TimeZone getTimeZone(long deviceId, String defaultTimeZone) { TimeZone result = TimeZone.getTimeZone(defaultTimeZone); - String timeZoneName = identityManager.lookupAttributeString(deviceId, "decoder.timezone", null, false, true); + String timeZoneName = AttributeUtil.lookup(cacheManager, Keys.DECODER_TIMEZONE, deviceId); if (timeZoneName != null) { result = TimeZone.getTimeZone(timeZoneName); } return result; } - private DeviceSession channelDeviceSession; // connection-based protocols - private final Map<SocketAddress, DeviceSession> addressDeviceSessions = new HashMap<>(); // connectionless protocols - - private long findDeviceId(SocketAddress remoteAddress, String... uniqueIds) { - if (uniqueIds.length > 0) { - long deviceId = 0; - Device device = null; - try { - for (String uniqueId : uniqueIds) { - if (uniqueId != null) { - device = identityManager.getByUniqueId(uniqueId); - if (device != null) { - deviceId = device.getId(); - break; - } - } - } - } catch (Exception e) { - LOGGER.warn("Find device error", e); - } - if (deviceId == 0 && config.getBoolean(Keys.DATABASE_REGISTER_UNKNOWN)) { - return identityManager.addUnknownDevice(uniqueIds[0]); - } - if (device != null && !device.getDisabled()) { - return deviceId; - } - StringBuilder message = new StringBuilder(); - if (deviceId == 0) { - message.append("Unknown device -"); - } else { - message.append("Disabled device -"); - } - for (String uniqueId : uniqueIds) { - message.append(" ").append(uniqueId); - } - if (remoteAddress != null) { - message.append(" (").append(((InetSocketAddress) remoteAddress).getHostString()).append(")"); - } - LOGGER.warn(message.toString()); - } - return 0; - } - public DeviceSession getDeviceSession(Channel channel, SocketAddress remoteAddress, String... uniqueIds) { - return getDeviceSession(channel, remoteAddress, false, uniqueIds); - } - - public DeviceSession getDeviceSession( - Channel channel, SocketAddress remoteAddress, boolean ignoreCache, String... uniqueIds) { - if (channel != null && BasePipelineFactory.getHandler(channel.pipeline(), HttpRequestDecoder.class) != null - || ignoreCache || config.getBoolean(Keys.PROTOCOL_IGNORE_SESSIONS_CACHE.withPrefix(getProtocolName())) - || config.getBoolean(Keys.DECODER_IGNORE_SESSIONS_CACHE)) { - long deviceId = findDeviceId(remoteAddress, uniqueIds); - if (deviceId != 0) { - if (connectionManager != null) { - connectionManager.addActiveDevice(deviceId, protocol, channel, remoteAddress); - } - return new DeviceSession(deviceId); - } else { - return null; - } - } - if (channel instanceof DatagramChannel) { - long deviceId = findDeviceId(remoteAddress, uniqueIds); - DeviceSession deviceSession = addressDeviceSessions.get(remoteAddress); - if (deviceSession != null && (deviceSession.getDeviceId() == deviceId || uniqueIds.length == 0)) { - return deviceSession; - } else if (deviceId != 0) { - deviceSession = new DeviceSession(deviceId); - addressDeviceSessions.put(remoteAddress, deviceSession); - if (connectionManager != null) { - connectionManager.addActiveDevice(deviceId, protocol, channel, remoteAddress); - } - return deviceSession; - } else { - return null; - } - } else { - if (channelDeviceSession == null) { - long deviceId = findDeviceId(remoteAddress, uniqueIds); - if (deviceId != 0) { - channelDeviceSession = new DeviceSession(deviceId); - if (connectionManager != null) { - connectionManager.addActiveDevice(deviceId, protocol, channel, remoteAddress); - } - } - } - return channelDeviceSession; + try { + return connectionManager.getDeviceSession(protocol, channel, remoteAddress, uniqueIds); + } catch (StorageException e) { + throw new RuntimeException(e); } } @@ -193,7 +145,7 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { if (position.getDeviceId() != 0) { position.setOutdated(true); - Position last = identityManager.getLastPosition(position.getDeviceId()); + Position last = cacheManager.getPosition(position.getDeviceId()); if (last != null) { position.setFixTime(last.getFixTime()); position.setValid(last.getValid()); @@ -245,18 +197,15 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { } protected void sendQueuedCommands(Channel channel, SocketAddress remoteAddress, long deviceId) { - CommandsManager commandsManager = Context.getCommandsManager(); - if (commandsManager != null) { - for (Command command : commandsManager.readQueuedCommands(deviceId)) { - protocol.sendDataCommand(channel, remoteAddress, command); - } + for (Command command : commandsManager.readQueuedCommands(deviceId)) { + protocol.sendDataCommand(channel, remoteAddress, command); } } @Override protected Object handleEmptyMessage(Channel channel, SocketAddress remoteAddress, Object msg) { DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); - if (config.getBoolean(Keys.DATABASE_SAVE_EMPTY) && deviceSession != null) { + if (getConfig().getBoolean(Keys.DATABASE_SAVE_EMPTY) && deviceSession != null) { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); getLastLocation(position, null); diff --git a/src/main/java/org/traccar/BaseProtocolEncoder.java b/src/main/java/org/traccar/BaseProtocolEncoder.java index b6df07b98..9c3934184 100644 --- a/src/main/java/org/traccar/BaseProtocolEncoder.java +++ b/src/main/java/org/traccar/BaseProtocolEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,13 @@ import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPromise; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.traccar.helper.NetworkUtil; +import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Command; +import org.traccar.model.Device; +import org.traccar.session.cache.CacheManager; + +import javax.inject.Inject; public abstract class BaseProtocolEncoder extends ChannelOutboundHandlerAdapter { @@ -31,22 +37,33 @@ public abstract class BaseProtocolEncoder extends ChannelOutboundHandlerAdapter private final Protocol protocol; + private CacheManager cacheManager; + public BaseProtocolEncoder(Protocol protocol) { this.protocol = protocol; } + public CacheManager getCacheManager() { + return cacheManager; + } + + @Inject + public void setCacheManager(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + public String getProtocolName() { return protocol != null ? protocol.getName() : PROTOCOL_UNKNOWN; } protected String getUniqueId(long deviceId) { - return Context.getIdentityManager().getById(deviceId).getUniqueId(); + return cacheManager.getObject(Device.class, deviceId).getUniqueId(); } protected void initDevicePassword(Command command, String defaultPassword) { - if (!command.getAttributes().containsKey(Command.KEY_DEVICE_PASSWORD)) { - String password = Context.getIdentityManager() - .getDevicePassword(command.getDeviceId(), getProtocolName(), defaultPassword); + if (!command.hasAttribute(Command.KEY_DEVICE_PASSWORD)) { + String password = AttributeUtil.getDevicePassword( + cacheManager, command.getDeviceId(), getProtocolName(), defaultPassword); command.set(Command.KEY_DEVICE_PASSWORD, password); } } @@ -62,7 +79,7 @@ public abstract class BaseProtocolEncoder extends ChannelOutboundHandlerAdapter Object encodedCommand = encodeCommand(ctx.channel(), command); StringBuilder s = new StringBuilder(); - s.append("[").append(ctx.channel().id().asShortText()).append("] "); + s.append("[").append(NetworkUtil.session(ctx.channel())).append("] "); s.append("id: ").append(getUniqueId(command.getDeviceId())).append(", "); s.append("command type: ").append(command.getType()).append(" "); if (encodedCommand != null) { diff --git a/src/main/java/org/traccar/BaseProtocolPoller.java b/src/main/java/org/traccar/BaseProtocolPoller.java new file mode 100644 index 000000000..be6556374 --- /dev/null +++ b/src/main/java/org/traccar/BaseProtocolPoller.java @@ -0,0 +1,55 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.concurrent.Future; + +import java.net.SocketAddress; +import java.util.concurrent.TimeUnit; + +public abstract class BaseProtocolPoller extends ChannelDuplexHandler { + + private final long interval; + private Future<?> timeout; + + public BaseProtocolPoller(long interval) { + this.interval = interval; + } + + protected abstract void sendRequest(Channel channel, SocketAddress remoteAddress); + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + if (interval > 0) { + timeout = ctx.executor().scheduleAtFixedRate( + () -> sendRequest(ctx.channel(), ctx.channel().remoteAddress()), 0, interval, TimeUnit.SECONDS); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + if (timeout != null) { + timeout.cancel(false); + timeout = null; + } + } + +} diff --git a/src/main/java/org/traccar/Context.java b/src/main/java/org/traccar/Context.java deleted file mode 100644 index aeba9c4c9..000000000 --- a/src/main/java/org/traccar/Context.java +++ /dev/null @@ -1,416 +0,0 @@ -/* - * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr353.JSR353Module; -import org.apache.velocity.app.VelocityEngine; -import org.eclipse.jetty.util.URIUtil; -import org.traccar.config.Config; -import org.traccar.config.Keys; -import org.traccar.database.AttributesManager; -import org.traccar.database.BaseObjectManager; -import org.traccar.database.CalendarManager; -import org.traccar.database.CommandsManager; -import org.traccar.database.ConnectionManager; -import org.traccar.database.DataManager; -import org.traccar.database.DeviceManager; -import org.traccar.database.DriversManager; -import org.traccar.database.GeofenceManager; -import org.traccar.database.GroupsManager; -import org.traccar.database.IdentityManager; -import org.traccar.database.LdapProvider; -import org.traccar.database.MailManager; -import org.traccar.database.MaintenancesManager; -import org.traccar.database.MediaManager; -import org.traccar.database.NotificationManager; -import org.traccar.database.OrderManager; -import org.traccar.database.PermissionsManager; -import org.traccar.database.UsersManager; -import org.traccar.geocoder.Geocoder; -import org.traccar.helper.Log; -import org.traccar.helper.SanitizerModule; -import org.traccar.model.Attribute; -import org.traccar.model.BaseModel; -import org.traccar.model.Calendar; -import org.traccar.model.Command; -import org.traccar.model.Device; -import org.traccar.model.Driver; -import org.traccar.model.Geofence; -import org.traccar.model.Group; -import org.traccar.model.Maintenance; -import org.traccar.model.Notification; -import org.traccar.model.Order; -import org.traccar.model.User; -import org.traccar.notification.EventForwarder; -import org.traccar.notification.NotificatorManager; -import org.traccar.reports.model.TripsConfig; -import org.traccar.schedule.ScheduleManager; -import org.traccar.sms.HttpSmsClient; -import org.traccar.sms.SmsManager; -import org.traccar.sms.SnsSmsClient; -import org.traccar.web.WebServer; - -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.ext.ContextResolver; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Properties; - -public final class Context { - - private Context() { - } - - private static Config config; - - public static Config getConfig() { - return config; - } - - private static ObjectMapper objectMapper; - - public static ObjectMapper getObjectMapper() { - return objectMapper; - } - - private static IdentityManager identityManager; - - public static IdentityManager getIdentityManager() { - return identityManager; - } - - private static DataManager dataManager; - - public static DataManager getDataManager() { - return dataManager; - } - - private static LdapProvider ldapProvider; - - public static LdapProvider getLdapProvider() { - return ldapProvider; - } - - private static MailManager mailManager; - - public static MailManager getMailManager() { - return mailManager; - } - - private static MediaManager mediaManager; - - public static MediaManager getMediaManager() { - return mediaManager; - } - - private static UsersManager usersManager; - - public static UsersManager getUsersManager() { - return usersManager; - } - - private static GroupsManager groupsManager; - - public static GroupsManager getGroupsManager() { - return groupsManager; - } - - private static DeviceManager deviceManager; - - public static DeviceManager getDeviceManager() { - return deviceManager; - } - - private static ConnectionManager connectionManager; - - public static ConnectionManager getConnectionManager() { - return connectionManager; - } - - private static PermissionsManager permissionsManager; - - public static PermissionsManager getPermissionsManager() { - return permissionsManager; - } - - public static Geocoder getGeocoder() { - return Main.getInjector() != null ? Main.getInjector().getInstance(Geocoder.class) : null; - } - - private static WebServer webServer; - - public static WebServer getWebServer() { - return webServer; - } - - private static ServerManager serverManager; - - public static ServerManager getServerManager() { - return serverManager; - } - - private static ScheduleManager scheduleManager; - - public static ScheduleManager getScheduleManager() { - return scheduleManager; - } - - private static GeofenceManager geofenceManager; - - public static GeofenceManager getGeofenceManager() { - return geofenceManager; - } - - private static CalendarManager calendarManager; - - public static CalendarManager getCalendarManager() { - return calendarManager; - } - - private static NotificationManager notificationManager; - - public static NotificationManager getNotificationManager() { - return notificationManager; - } - - private static NotificatorManager notificatorManager; - - public static NotificatorManager getNotificatorManager() { - return notificatorManager; - } - - private static VelocityEngine velocityEngine; - - public static VelocityEngine getVelocityEngine() { - return velocityEngine; - } - - private static Client client = ClientBuilder.newClient(); - - public static Client getClient() { - return client; - } - - private static EventForwarder eventForwarder; - - public static EventForwarder getEventForwarder() { - return eventForwarder; - } - - private static AttributesManager attributesManager; - - public static AttributesManager getAttributesManager() { - return attributesManager; - } - - private static DriversManager driversManager; - - public static DriversManager getDriversManager() { - return driversManager; - } - - private static CommandsManager commandsManager; - - public static CommandsManager getCommandsManager() { - return commandsManager; - } - - private static MaintenancesManager maintenancesManager; - - public static MaintenancesManager getMaintenancesManager() { - return maintenancesManager; - } - - private static OrderManager orderManager; - - public static OrderManager getOrderManager() { - return orderManager; - } - - private static SmsManager smsManager; - - public static SmsManager getSmsManager() { - return smsManager; - } - - private static TripsConfig tripsConfig; - - public static TripsConfig getTripsConfig() { - return tripsConfig; - } - - public static TripsConfig initTripsConfig() { - return new TripsConfig( - config.getLong(Keys.REPORT_TRIP_MINIMAL_TRIP_DISTANCE), - config.getLong(Keys.REPORT_TRIP_MINIMAL_TRIP_DURATION) * 1000, - config.getLong(Keys.REPORT_TRIP_MINIMAL_PARKING_DURATION) * 1000, - config.getLong(Keys.REPORT_TRIP_MINIMAL_NO_DATA_DURATION) * 1000, - config.getBoolean(Keys.REPORT_TRIP_USE_IGNITION), - config.getBoolean(Keys.EVENT_MOTION_PROCESS_INVALID_POSITIONS), - config.getDouble(Keys.EVENT_MOTION_SPEED_THRESHOLD)); - } - - private static class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> { - - @Override - public ObjectMapper getContext(Class<?> clazz) { - return objectMapper; - } - - } - - public static void init(String configFile) throws Exception { - - try { - config = new Config(configFile); - Log.setupLogger(config); - } catch (Exception e) { - config = new Config(); - Log.setupDefaultLogger(); - throw e; - } - - objectMapper = new ObjectMapper(); - objectMapper.registerModule(new SanitizerModule()); - objectMapper.registerModule(new JSR353Module()); - objectMapper.setConfig( - objectMapper.getSerializationConfig().without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)); - - client = ClientBuilder.newClient().register(new ObjectMapperContextResolver()); - - if (config.hasKey(Keys.DATABASE_URL)) { - dataManager = new DataManager(config); - } - - if (config.hasKey(Keys.LDAP_URL)) { - ldapProvider = new LdapProvider(config); - } - - mailManager = new MailManager(); - - mediaManager = new MediaManager(config.getString(Keys.MEDIA_PATH)); - - if (dataManager != null) { - usersManager = new UsersManager(dataManager); - groupsManager = new GroupsManager(dataManager); - deviceManager = new DeviceManager(dataManager); - } - - identityManager = deviceManager; - - if (config.hasKey(Keys.WEB_PORT)) { - webServer = new WebServer(config); - } - - permissionsManager = new PermissionsManager(dataManager, usersManager); - - connectionManager = new ConnectionManager(); - - tripsConfig = initTripsConfig(); - - if (config.hasKey(Keys.SMS_HTTP_URL)) { - smsManager = new HttpSmsClient(); - } else if (config.hasKey(Keys.SMS_AWS_REGION)) { - smsManager = new SnsSmsClient(); - } - - initEventsModule(); - - serverManager = new ServerManager(); - scheduleManager = new ScheduleManager(); - - if (config.hasKey(Keys.EVENT_FORWARD_URL)) { - eventForwarder = new EventForwarder(); - } - - attributesManager = new AttributesManager(dataManager); - - driversManager = new DriversManager(dataManager); - - commandsManager = new CommandsManager(dataManager, config.getBoolean(Keys.COMMANDS_QUEUEING)); - - orderManager = new OrderManager(dataManager); - - } - - private static void initEventsModule() { - - geofenceManager = new GeofenceManager(dataManager); - calendarManager = new CalendarManager(dataManager); - maintenancesManager = new MaintenancesManager(dataManager); - notificationManager = new NotificationManager(dataManager); - notificatorManager = new NotificatorManager(); - Properties velocityProperties = new Properties(); - velocityProperties.setProperty("file.resource.loader.path", - Context.getConfig().getString("templates.rootPath", "templates") + "/"); - velocityProperties.setProperty("runtime.log.logsystem.class", - "org.apache.velocity.runtime.log.NullLogChute"); - - String address; - try { - address = config.getString(Keys.WEB_ADDRESS, InetAddress.getLocalHost().getHostAddress()); - } catch (UnknownHostException e) { - address = "localhost"; - } - - String webUrl = URIUtil.newURI("http", address, config.getInteger(Keys.WEB_PORT), "", ""); - webUrl = Context.getConfig().getString("web.url", webUrl); - velocityProperties.setProperty("web.url", webUrl); - - velocityEngine = new VelocityEngine(); - velocityEngine.init(velocityProperties); - } - - public static void init(IdentityManager testIdentityManager, MediaManager testMediaManager) { - config = new Config(); - objectMapper = new ObjectMapper(); - objectMapper.registerModule(new JSR353Module()); - client = ClientBuilder.newClient().register(new ObjectMapperContextResolver()); - identityManager = testIdentityManager; - mediaManager = testMediaManager; - } - - public static <T extends BaseModel> BaseObjectManager<T> getManager(Class<T> clazz) { - if (clazz.equals(Device.class)) { - return (BaseObjectManager<T>) deviceManager; - } else if (clazz.equals(Group.class)) { - return (BaseObjectManager<T>) groupsManager; - } else if (clazz.equals(User.class)) { - return (BaseObjectManager<T>) usersManager; - } else if (clazz.equals(Calendar.class)) { - return (BaseObjectManager<T>) calendarManager; - } else if (clazz.equals(Attribute.class)) { - return (BaseObjectManager<T>) attributesManager; - } else if (clazz.equals(Geofence.class)) { - return (BaseObjectManager<T>) geofenceManager; - } else if (clazz.equals(Driver.class)) { - return (BaseObjectManager<T>) driversManager; - } else if (clazz.equals(Command.class)) { - return (BaseObjectManager<T>) commandsManager; - } else if (clazz.equals(Maintenance.class)) { - return (BaseObjectManager<T>) maintenancesManager; - } else if (clazz.equals(Notification.class)) { - return (BaseObjectManager<T>) notificationManager; - } else if (clazz.equals(Order.class)) { - return (BaseObjectManager<T>) orderManager; - } - return null; - } - -} diff --git a/src/main/java/org/traccar/ExtendedObjectDecoder.java b/src/main/java/org/traccar/ExtendedObjectDecoder.java index 46720da52..f79a36c85 100644 --- a/src/main/java/org/traccar/ExtendedObjectDecoder.java +++ b/src/main/java/org/traccar/ExtendedObjectDecoder.java @@ -21,18 +21,38 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.ReferenceCountUtil; +import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.helper.DataConverter; import org.traccar.model.Position; +import javax.inject.Inject; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.Collection; public abstract class ExtendedObjectDecoder extends ChannelInboundHandlerAdapter { + private Config config; + + public Config getConfig() { + return config; + } + + @Inject + public void setConfig(Config config) { + this.config = config; + init(); + } + + /** + * Method called when config is initialized. + */ + protected void init() { + } + private void saveOriginal(Object decodedMessage, Object originalMessage) { - if (Context.getConfig().getBoolean(Keys.DATABASE_SAVE_ORIGINAL) && decodedMessage instanceof Position) { + if (getConfig().getBoolean(Keys.DATABASE_SAVE_ORIGINAL) && decodedMessage instanceof Position) { Position position = (Position) decodedMessage; if (originalMessage instanceof ByteBuf) { ByteBuf buf = (ByteBuf) originalMessage; diff --git a/src/main/java/org/traccar/LifecycleObject.java b/src/main/java/org/traccar/LifecycleObject.java new file mode 100644 index 000000000..fe0dc698a --- /dev/null +++ b/src/main/java/org/traccar/LifecycleObject.java @@ -0,0 +1,21 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +public interface LifecycleObject { + void start() throws Exception; + void stop() throws Exception; +} diff --git a/src/main/java/org/traccar/Main.java b/src/main/java/org/traccar/Main.java index 63e5c1f90..e34fbb72a 100644 --- a/src/main/java/org/traccar/Main.java +++ b/src/main/java/org/traccar/Main.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,13 @@ import com.google.inject.Guice; import com.google.inject.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.api.HealthCheckService; +import org.traccar.broadcast.BroadcastService; +import org.traccar.helper.model.DeviceUtil; +import org.traccar.schedule.ScheduleManager; +import org.traccar.storage.DatabaseModule; +import org.traccar.storage.Storage; +import org.traccar.web.WebModule; +import org.traccar.web.WebServer; import java.io.File; import java.lang.management.ManagementFactory; @@ -28,7 +34,9 @@ import java.lang.management.OperatingSystemMXBean; import java.lang.management.RuntimeMXBean; import java.nio.charset.Charset; import java.util.Locale; -import java.util.Timer; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; public final class Main { @@ -107,40 +115,39 @@ public final class Main { } } - private static void scheduleHealthCheck() { - HealthCheckService service = new HealthCheckService(); - if (service.isEnabled()) { - new Timer().scheduleAtFixedRate( - service.createTask(), service.getPeriod(), service.getPeriod()); - } - } - public static void run(String configFile) { try { - Context.init(configFile); - injector = Guice.createInjector(new MainModule()); + injector = Guice.createInjector(new MainModule(configFile), new DatabaseModule(), new WebModule()); logSystemInfo(); LOGGER.info("Version: " + Main.class.getPackage().getImplementationVersion()); LOGGER.info("Starting server..."); - Context.getServerManager().start(); - if (Context.getWebServer() != null) { - Context.getWebServer().start(); + if (injector.getInstance(BroadcastService.class).singleInstance()) { + DeviceUtil.resetStatus(injector.getInstance(Storage.class)); } - Context.getScheduleManager().start(); - scheduleHealthCheck(); + var services = Stream.of( + ServerManager.class, WebServer.class, ScheduleManager.class, BroadcastService.class) + .map(injector::getInstance) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + for (var service : services) { + service.start(); + } Thread.setDefaultUncaughtExceptionHandler((t, e) -> LOGGER.error("Thread exception", e)); Runtime.getRuntime().addShutdownHook(new Thread(() -> { - LOGGER.info("Shutting down server..."); - - Context.getScheduleManager().stop(); - if (Context.getWebServer() != null) { - Context.getWebServer().stop(); + LOGGER.info("Stopping server..."); + + for (var service : services) { + try { + service.stop(); + } catch (Exception e) { + throw new RuntimeException(e); + } } - Context.getServerManager().stop(); })); } catch (Exception e) { LOGGER.error("Main method error", e); diff --git a/src/main/java/org/traccar/MainEventHandler.java b/src/main/java/org/traccar/MainEventHandler.java index 4889f6a2e..877f03ae7 100644 --- a/src/main/java/org/traccar/MainEventHandler.java +++ b/src/main/java/org/traccar/MainEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.traccar; import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.socket.DatagramChannel; @@ -23,17 +24,31 @@ import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.timeout.IdleStateEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.database.StatisticsManager; import org.traccar.helper.DateUtil; +import org.traccar.helper.NetworkUtil; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; import org.traccar.model.Position; - -import java.sql.SQLException; +import org.traccar.session.ConnectionManager; +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import javax.inject.Singleton; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; +@Singleton +@ChannelHandler.Sharable public class MainEventHandler extends ChannelInboundHandlerAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(MainEventHandler.class); @@ -41,13 +56,24 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter { private final Set<String> connectionlessProtocols = new HashSet<>(); private final Set<String> logAttributes = new LinkedHashSet<>(); - public MainEventHandler() { - String connectionlessProtocolList = Context.getConfig().getString(Keys.STATUS_IGNORE_OFFLINE); + private final CacheManager cacheManager; + private final Storage storage; + private final ConnectionManager connectionManager; + private final StatisticsManager statisticsManager; + + @Inject + public MainEventHandler( + Config config, CacheManager cacheManager, Storage storage, ConnectionManager connectionManager, + StatisticsManager statisticsManager) { + this.cacheManager = cacheManager; + this.storage = storage; + this.connectionManager = connectionManager; + this.statisticsManager = statisticsManager; + String connectionlessProtocolList = config.getString(Keys.STATUS_IGNORE_OFFLINE); if (connectionlessProtocolList != null) { connectionlessProtocols.addAll(Arrays.asList(connectionlessProtocolList.split("[, ]"))); } - logAttributes.addAll(Arrays.asList( - Context.getConfig().getString(Keys.LOGGER_ATTRIBUTES).split("[, ]"))); + logAttributes.addAll(Arrays.asList(config.getString(Keys.LOGGER_ATTRIBUTES).split("[, ]"))); } @Override @@ -55,17 +81,27 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter { if (msg instanceof Position) { Position position = (Position) msg; + Device device = cacheManager.getObject(Device.class, position.getDeviceId()); + try { - Context.getDeviceManager().updateLatestPosition(position); - } catch (SQLException error) { + if (PositionUtil.isLatest(cacheManager, position)) { + Device updatedDevice = new Device(); + updatedDevice.setId(position.getDeviceId()); + updatedDevice.setPositionId(position.getId()); + storage.updateObject(updatedDevice, new Request( + new Columns.Include("positionId"), + new Condition.Equals("id", updatedDevice.getId()))); + + cacheManager.updatePosition(position); + connectionManager.updatePosition(true, position); + } + } catch (StorageException error) { LOGGER.warn("Failed to update device", error); } - String uniqueId = Context.getIdentityManager().getById(position.getDeviceId()).getUniqueId(); - StringBuilder builder = new StringBuilder(); - builder.append(formatChannel(ctx.channel())).append(" "); - builder.append("id: ").append(uniqueId); + builder.append("[").append(NetworkUtil.session(ctx.channel())).append("] "); + builder.append("id: ").append(device.getUniqueId()); for (String attribute : logAttributes) { switch (attribute) { case "time": @@ -108,31 +144,25 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter { } LOGGER.info(builder.toString()); - Main.getInjector().getInstance(StatisticsManager.class) - .registerMessageStored(position.getDeviceId(), position.getProtocol()); + statisticsManager.registerMessageStored(position.getDeviceId(), position.getProtocol()); } } - private static String formatChannel(Channel channel) { - return String.format("[%s]", channel.id().asShortText()); - } - @Override public void channelActive(ChannelHandlerContext ctx) { if (!(ctx.channel() instanceof DatagramChannel)) { - LOGGER.info(formatChannel(ctx.channel()) + " connected"); + LOGGER.info("[{}] connected", NetworkUtil.session(ctx.channel())); } } @Override public void channelInactive(ChannelHandlerContext ctx) { - LOGGER.info(formatChannel(ctx.channel()) + " disconnected"); + LOGGER.info("[{}] disconnected", NetworkUtil.session(ctx.channel())); closeChannel(ctx.channel()); - if (BasePipelineFactory.getHandler(ctx.pipeline(), HttpRequestDecoder.class) == null - && !connectionlessProtocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName())) { - Context.getConnectionManager().removeActiveDevice(ctx.channel()); - } + boolean supportsOffline = BasePipelineFactory.getHandler(ctx.pipeline(), HttpRequestDecoder.class) == null + && !connectionlessProtocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName()); + connectionManager.deviceDisconnected(ctx.channel(), supportsOffline); } @Override @@ -140,14 +170,14 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter { while (cause.getCause() != null && cause.getCause() != cause) { cause = cause.getCause(); } - LOGGER.warn(formatChannel(ctx.channel()) + " error", cause); + LOGGER.info("[{}] error", NetworkUtil.session(ctx.channel()), cause); closeChannel(ctx.channel()); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof IdleStateEvent) { - LOGGER.info(formatChannel(ctx.channel()) + " timed out"); + LOGGER.info("[{}] timed out", NetworkUtil.session(ctx.channel())); closeChannel(ctx.channel()); } } diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java index 11100f66e..55211d109 100644 --- a/src/main/java/org/traccar/MainModule.java +++ b/src/main/java/org/traccar/MainModule.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,146 +16,161 @@ package org.traccar; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr353.JSR353Module; import com.google.inject.AbstractModule; +import com.google.inject.Injector; import com.google.inject.Provides; -import com.google.inject.Singleton; +import com.google.inject.Scopes; +import com.google.inject.name.Names; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.runtime.log.NullLogChute; +import org.eclipse.jetty.util.URIUtil; +import org.traccar.broadcast.BroadcastService; +import org.traccar.broadcast.MulticastBroadcastService; +import org.traccar.broadcast.NullBroadcastService; import org.traccar.config.Config; import org.traccar.config.Keys; -import org.traccar.database.AttributesManager; -import org.traccar.database.CalendarManager; -import org.traccar.database.ConnectionManager; -import org.traccar.database.DataManager; -import org.traccar.database.DeviceManager; -import org.traccar.database.GeofenceManager; -import org.traccar.database.IdentityManager; -import org.traccar.database.MaintenancesManager; +import org.traccar.database.LdapProvider; import org.traccar.database.StatisticsManager; +import org.traccar.forward.EventForwarder; +import org.traccar.forward.EventForwarderJson; +import org.traccar.forward.EventForwarderKafka; +import org.traccar.forward.EventForwarderMqtt; +import org.traccar.forward.PositionForwarder; +import org.traccar.forward.PositionForwarderJson; +import org.traccar.forward.PositionForwarderKafka; +import org.traccar.forward.PositionForwarderUrl; import org.traccar.geocoder.AddressFormat; import org.traccar.geocoder.BanGeocoder; import org.traccar.geocoder.BingMapsGeocoder; import org.traccar.geocoder.FactualGeocoder; +import org.traccar.geocoder.GeoapifyGeocoder; import org.traccar.geocoder.GeocodeFarmGeocoder; import org.traccar.geocoder.GeocodeXyzGeocoder; import org.traccar.geocoder.Geocoder; import org.traccar.geocoder.GisgraphyGeocoder; import org.traccar.geocoder.GoogleGeocoder; import org.traccar.geocoder.HereGeocoder; +import org.traccar.geocoder.LocationIqGeocoder; import org.traccar.geocoder.MapQuestGeocoder; import org.traccar.geocoder.MapTilerGeocoder; +import org.traccar.geocoder.MapboxGeocoder; import org.traccar.geocoder.MapmyIndiaGeocoder; import org.traccar.geocoder.NominatimGeocoder; import org.traccar.geocoder.OpenCageGeocoder; import org.traccar.geocoder.PositionStackGeocoder; +import org.traccar.geocoder.TestGeocoder; import org.traccar.geocoder.TomTomGeocoder; -import org.traccar.geocoder.MapboxGeocoder; import org.traccar.geolocation.GeolocationProvider; import org.traccar.geolocation.GoogleGeolocationProvider; import org.traccar.geolocation.MozillaGeolocationProvider; import org.traccar.geolocation.OpenCellIdGeolocationProvider; import org.traccar.geolocation.UnwiredGeolocationProvider; -import org.traccar.handler.ComputedAttributesHandler; -import org.traccar.handler.CopyAttributesHandler; -import org.traccar.handler.DefaultDataHandler; -import org.traccar.handler.DistanceHandler; -import org.traccar.handler.EngineHoursHandler; -import org.traccar.handler.FilterHandler; import org.traccar.handler.GeocoderHandler; import org.traccar.handler.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.BehaviorEventHandler; -import org.traccar.handler.events.CommandResultEventHandler; -import org.traccar.handler.events.DriverEventHandler; -import org.traccar.handler.events.FuelDropEventHandler; -import org.traccar.handler.events.GeofenceEventHandler; -import org.traccar.handler.events.IgnitionEventHandler; -import org.traccar.handler.events.MaintenanceEventHandler; -import org.traccar.handler.events.MotionEventHandler; -import org.traccar.handler.events.OverspeedEventHandler; -import org.traccar.reports.model.TripsConfig; +import org.traccar.helper.ObjectMapperContextResolver; +import org.traccar.helper.SanitizerModule; +import org.traccar.mail.LogMailManager; +import org.traccar.mail.MailManager; +import org.traccar.mail.SmtpMailManager; +import org.traccar.session.cache.CacheManager; +import org.traccar.sms.HttpSmsClient; +import org.traccar.sms.SmsManager; +import org.traccar.sms.SnsSmsClient; +import org.traccar.speedlimit.OverpassSpeedLimitProvider; +import org.traccar.speedlimit.SpeedLimitProvider; +import org.traccar.storage.DatabaseStorage; +import org.traccar.storage.Storage; +import org.traccar.web.WebServer; import javax.annotation.Nullable; +import javax.inject.Singleton; import javax.ws.rs.client.Client; -import io.netty.util.Timer; -import org.traccar.speedlimit.OverpassSpeedLimitProvider; -import org.traccar.speedlimit.SpeedLimitProvider; +import javax.ws.rs.client.ClientBuilder; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Properties; public class MainModule extends AbstractModule { - @Provides - public static ObjectMapper provideObjectMapper() { - return Context.getObjectMapper(); - } - - @Provides - public static Config provideConfig() { - return Context.getConfig(); - } - - @Provides - public static DataManager provideDataManager() { - return Context.getDataManager(); - } - - @Provides - public static IdentityManager provideIdentityManager() { - return Context.getIdentityManager(); - } + private final String configFile; - @Provides - public static ConnectionManager provideConnectionManager() { - return Context.getConnectionManager(); + public MainModule(String configFile) { + this.configFile = configFile; } - @Provides - public static Client provideClient() { - return Context.getClient(); - } - - @Provides - public static TripsConfig provideTripsConfig() { - return Context.getTripsConfig(); + @Override + protected void configure() { + bindConstant().annotatedWith(Names.named("configFile")).to(configFile); + bind(Config.class).asEagerSingleton(); + bind(Storage.class).to(DatabaseStorage.class).in(Scopes.SINGLETON); + bind(Timer.class).to(HashedWheelTimer.class).in(Scopes.SINGLETON); } + @Singleton @Provides - public static DeviceManager provideDeviceManager() { - return Context.getDeviceManager(); + public static ObjectMapper provideObjectMapper(Config config) { + ObjectMapper objectMapper = new ObjectMapper(); + if (config.getBoolean(Keys.WEB_SANITIZE)) { + objectMapper.registerModule(new SanitizerModule()); + } + objectMapper.registerModule(new JSR353Module()); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + return objectMapper; } + @Singleton @Provides - public static GeofenceManager provideGeofenceManager() { - return Context.getGeofenceManager(); + public static Client provideClient(ObjectMapperContextResolver objectMapperContextResolver) { + return ClientBuilder.newClient().register(objectMapperContextResolver); } + @Singleton @Provides - public static CalendarManager provideCalendarManager() { - return Context.getCalendarManager(); + public static SmsManager provideSmsManager(Config config, Client client) { + if (config.hasKey(Keys.SMS_HTTP_URL)) { + return new HttpSmsClient(config, client); + } else if (config.hasKey(Keys.SMS_AWS_REGION)) { + return new SnsSmsClient(config); + } + return null; } + @Singleton @Provides - public static AttributesManager provideAttributesManager() { - return Context.getAttributesManager(); + public static MailManager provideMailManager(Config config, StatisticsManager statisticsManager) { + if (config.getBoolean(Keys.MAIL_DEBUG)) { + return new LogMailManager(); + } else { + return new SmtpMailManager(config, statisticsManager); + } } + @Singleton @Provides - public static MaintenancesManager provideMaintenancesManager() { - return Context.getMaintenancesManager(); + public static LdapProvider provideLdapProvider(Config config) { + if (config.hasKey(Keys.LDAP_URL)) { + return new LdapProvider(config); + } + return null; } - @Singleton @Provides - public static StatisticsManager provideStatisticsManager( - Config config, DataManager dataManager, Client client, ObjectMapper objectMapper) { - return new StatisticsManager(config, dataManager, client, objectMapper); + public static WebServer provideWebServer(Injector injector, Config config) { + if (config.hasKey(Keys.WEB_PORT)) { + return new WebServer(injector, config); + } + return null; } @Singleton @Provides - public static Geocoder provideGeocoder(Config config) { + public static Geocoder provideGeocoder(Config config, Client client, StatisticsManager statisticsManager) { if (config.getBoolean(Keys.GEOCODER_ENABLE)) { String type = config.getString(Keys.GEOCODER_TYPE, "google"); String url = config.getString(Keys.GEOCODER_URL); @@ -166,60 +181,88 @@ public class MainModule extends AbstractModule { AddressFormat addressFormat = formatString != null ? new AddressFormat(formatString) : new AddressFormat(); int cacheSize = config.getInteger(Keys.GEOCODER_CACHE_SIZE); + Geocoder geocoder; switch (type) { + case "test": + geocoder = new TestGeocoder(); + break; case "nominatim": - return new NominatimGeocoder(url, key, language, cacheSize, addressFormat); + geocoder = new NominatimGeocoder(client, url, key, language, cacheSize, addressFormat); + break; + case "locationiq": + geocoder = new LocationIqGeocoder(client, url, key, language, cacheSize, addressFormat); + break; case "gisgraphy": - return new GisgraphyGeocoder(url, cacheSize, addressFormat); + geocoder = new GisgraphyGeocoder(client, url, cacheSize, addressFormat); + break; case "mapquest": - return new MapQuestGeocoder(url, key, cacheSize, addressFormat); + geocoder = new MapQuestGeocoder(client, url, key, cacheSize, addressFormat); + break; case "opencage": - return new OpenCageGeocoder(url, key, cacheSize, addressFormat); + geocoder = new OpenCageGeocoder(client, url, key, language, cacheSize, addressFormat); + break; case "bingmaps": - return new BingMapsGeocoder(url, key, cacheSize, addressFormat); + geocoder = new BingMapsGeocoder(client, url, key, cacheSize, addressFormat); + break; case "factual": - return new FactualGeocoder(url, key, cacheSize, addressFormat); + geocoder = new FactualGeocoder(client, url, key, cacheSize, addressFormat); + break; case "geocodefarm": - return new GeocodeFarmGeocoder(key, language, cacheSize, addressFormat); + geocoder = new GeocodeFarmGeocoder(client, key, language, cacheSize, addressFormat); + break; case "geocodexyz": - return new GeocodeXyzGeocoder(key, cacheSize, addressFormat); + geocoder = new GeocodeXyzGeocoder(client, key, cacheSize, addressFormat); + break; case "ban": - return new BanGeocoder(cacheSize, addressFormat); + geocoder = new BanGeocoder(client, cacheSize, addressFormat); + break; case "here": - return new HereGeocoder(url, id, key, language, cacheSize, addressFormat); + geocoder = new HereGeocoder(client, url, id, key, language, cacheSize, addressFormat); + break; case "mapmyindia": - return new MapmyIndiaGeocoder(url, key, cacheSize, addressFormat); + geocoder = new MapmyIndiaGeocoder(client, url, key, cacheSize, addressFormat); + break; case "tomtom": - return new TomTomGeocoder(url, key, cacheSize, addressFormat); + geocoder = new TomTomGeocoder(client, url, key, cacheSize, addressFormat); + break; case "positionstack": - return new PositionStackGeocoder(key, cacheSize, addressFormat); + geocoder = new PositionStackGeocoder(client, key, cacheSize, addressFormat); + break; case "mapbox": - return new MapboxGeocoder(key, cacheSize, addressFormat); + geocoder = new MapboxGeocoder(client, key, cacheSize, addressFormat); + break; case "maptiler": - return new MapTilerGeocoder(key, cacheSize, addressFormat); + geocoder = new MapTilerGeocoder(client, key, cacheSize, addressFormat); + break; + case "geoapify": + geocoder = new GeoapifyGeocoder(client, key, language, cacheSize, addressFormat); + break; default: - return new GoogleGeocoder(key, language, cacheSize, addressFormat); + geocoder = new GoogleGeocoder(client, key, language, cacheSize, addressFormat); + break; } + geocoder.setStatisticsManager(statisticsManager); + return geocoder; } return null; } @Singleton @Provides - public static GeolocationProvider provideGeolocationProvider(Config config) { + public static GeolocationProvider provideGeolocationProvider(Config config, Client client) { if (config.getBoolean(Keys.GEOLOCATION_ENABLE)) { String type = config.getString(Keys.GEOLOCATION_TYPE, "mozilla"); String url = config.getString(Keys.GEOLOCATION_URL); String key = config.getString(Keys.GEOLOCATION_KEY); switch (type) { case "google": - return new GoogleGeolocationProvider(key); + return new GoogleGeolocationProvider(client, key); case "opencellid": - return new OpenCellIdGeolocationProvider(url, key); + return new OpenCellIdGeolocationProvider(client, url, key); case "unwired": - return new UnwiredGeolocationProvider(url, key); + return new UnwiredGeolocationProvider(client, url, key); default: - return new MozillaGeolocationProvider(key); + return new MozillaGeolocationProvider(client, key); } } return null; @@ -227,14 +270,14 @@ public class MainModule extends AbstractModule { @Singleton @Provides - public static SpeedLimitProvider provideSpeedLimitProvider(Config config) { + public static SpeedLimitProvider provideSpeedLimitProvider(Config config, Client client) { 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 new OverpassSpeedLimitProvider(client, url); } } return null; @@ -242,53 +285,11 @@ public class MainModule extends AbstractModule { @Singleton @Provides - public static DistanceHandler provideDistanceHandler(Config config, IdentityManager identityManager) { - return new DistanceHandler(config, identityManager); - } - - @Singleton - @Provides - public static FilterHandler provideFilterHandler(Config config) { - if (config.getBoolean(Keys.FILTER_ENABLE)) { - return new FilterHandler(config); - } - return null; - } - - @Singleton - @Provides - public static HemisphereHandler provideHemisphereHandler(Config config) { - if (config.hasKey(Keys.LOCATION_LATITUDE_HEMISPHERE) || config.hasKey(Keys.LOCATION_LONGITUDE_HEMISPHERE)) { - return new HemisphereHandler(config); - } - return null; - } - - @Singleton - @Provides - public static RemoteAddressHandler provideRemoteAddressHandler(Config config) { - if (config.getBoolean(Keys.PROCESSING_REMOTE_ADDRESS_ENABLE)) { - return new RemoteAddressHandler(); - } - return null; - } - - @Singleton - @Provides - public static WebDataHandler provideWebDataHandler( - Config config, IdentityManager identityManager, ObjectMapper objectMapper, Client client) { - if (config.hasKey(Keys.FORWARD_URL)) { - return new WebDataHandler(config, identityManager, objectMapper, client); - } - return null; - } - - @Singleton - @Provides public static GeolocationHandler provideGeolocationHandler( - Config config, @Nullable GeolocationProvider geolocationProvider, StatisticsManager statisticsManager) { + Config config, @Nullable GeolocationProvider geolocationProvider, CacheManager cacheManager, + StatisticsManager statisticsManager) { if (geolocationProvider != null) { - return new GeolocationHandler(config, geolocationProvider, statisticsManager); + return new GeolocationHandler(config, geolocationProvider, cacheManager, statisticsManager); } return null; } @@ -296,9 +297,9 @@ public class MainModule extends AbstractModule { @Singleton @Provides public static GeocoderHandler provideGeocoderHandler( - Config config, @Nullable Geocoder geocoder, IdentityManager identityManager) { + Config config, @Nullable Geocoder geocoder, CacheManager cacheManager) { if (geocoder != null) { - return new GeocoderHandler(config, geocoder, identityManager); + return new GeocoderHandler(config, geocoder, cacheManager); } return null; } @@ -314,130 +315,70 @@ public class MainModule extends AbstractModule { @Singleton @Provides - public static MotionHandler provideMotionHandler(TripsConfig tripsConfig) { - return new MotionHandler(tripsConfig.getSpeedThreshold()); - } - - @Singleton - @Provides - public static EngineHoursHandler provideEngineHoursHandler(Config config, IdentityManager identityManager) { - if (config.getBoolean(Keys.PROCESSING_ENGINE_HOURS_ENABLE)) { - return new EngineHoursHandler(identityManager); + public static BroadcastService provideBroadcastService( + Config config, ObjectMapper objectMapper) throws IOException { + if (config.hasKey(Keys.BROADCAST_ADDRESS)) { + return new MulticastBroadcastService(config, objectMapper); } - return null; + return new NullBroadcastService(); } @Singleton @Provides - public static CopyAttributesHandler provideCopyAttributesHandler(Config config, IdentityManager identityManager) { - if (config.getBoolean(Keys.PROCESSING_COPY_ATTRIBUTES_ENABLE)) { - return new CopyAttributesHandler(identityManager); - } - return null; - } - - @Singleton - @Provides - public static ComputedAttributesHandler provideComputedAttributesHandler( - Config config, IdentityManager identityManager, AttributesManager attributesManager) { - if (config.getBoolean(Keys.PROCESSING_COMPUTED_ATTRIBUTES_ENABLE)) { - return new ComputedAttributesHandler(config, identityManager, attributesManager); + public static EventForwarder provideEventForwarder(Config config, Client client, ObjectMapper objectMapper) { + if (config.hasKey(Keys.EVENT_FORWARD_URL)) { + String forwardType = config.getString(Keys.EVENT_FORWARD_TYPE); + switch (forwardType) { + case "kafka": + return new EventForwarderKafka(config, objectMapper); + case "mqtt": + return new EventForwarderMqtt(config, objectMapper); + default: + return new EventForwarderJson(config, client); + } } return null; } @Singleton @Provides - public static TimeHandler provideTimeHandler(Config config) { - if (config.hasKey(Keys.TIME_OVERRIDE)) { - return new TimeHandler(config); + public static PositionForwarder providePositionForwarder(Config config, Client client, ObjectMapper objectMapper) { + if (config.hasKey(Keys.FORWARD_URL)) { + switch (config.getString(Keys.FORWARD_TYPE)) { + case "json": + return new PositionForwarderJson(config, client, objectMapper); + case "kafka": + return new PositionForwarderKafka(config, objectMapper); + default: + return new PositionForwarderUrl(config, client, objectMapper); + } } return null; } @Singleton @Provides - public static DefaultDataHandler provideDefaultDataHandler(@Nullable DataManager dataManager) { - if (dataManager != null) { - return new DefaultDataHandler(dataManager); + public static VelocityEngine provideVelocityEngine(Config config) { + Properties properties = new Properties(); + properties.setProperty("file.resource.loader.path", config.getString(Keys.TEMPLATES_ROOT) + "/"); + properties.setProperty("runtime.log.logsystem.class", NullLogChute.class.getName()); + + if (config.hasKey(Keys.WEB_URL)) { + properties.setProperty("web.url", config.getString(Keys.WEB_URL).replaceAll("/$", "")); + } else { + String address; + try { + address = config.getString(Keys.WEB_ADDRESS, InetAddress.getLocalHost().getHostAddress()); + } catch (UnknownHostException e) { + address = "localhost"; + } + String url = URIUtil.newURI("http", address, config.getInteger(Keys.WEB_PORT), "", ""); + properties.setProperty("web.url", url); } - return null; - } - @Singleton - @Provides - public static CommandResultEventHandler provideCommandResultEventHandler() { - return new CommandResultEventHandler(); - } - - @Singleton - @Provides - public static OverspeedEventHandler provideOverspeedEventHandler( - Config config, DeviceManager deviceManager, GeofenceManager geofenceManager) { - return new OverspeedEventHandler(config, deviceManager, geofenceManager); - } - - @Singleton - @Provides - public static BehaviorEventHandler provideBehaviorEventHandler(Config config, IdentityManager identityManager) { - return new BehaviorEventHandler(config, identityManager); - } - - @Singleton - @Provides - public static FuelDropEventHandler provideFuelDropEventHandler(IdentityManager identityManager) { - return new FuelDropEventHandler(identityManager); - } - - @Singleton - @Provides - public static MotionEventHandler provideMotionEventHandler( - IdentityManager identityManager, DeviceManager deviceManager, TripsConfig tripsConfig) { - return new MotionEventHandler(identityManager, deviceManager, tripsConfig); - } - - @Singleton - @Provides - public static GeofenceEventHandler provideGeofenceEventHandler( - IdentityManager identityManager, GeofenceManager geofenceManager, CalendarManager calendarManager, - ConnectionManager connectionManager) { - return new GeofenceEventHandler(identityManager, geofenceManager, calendarManager, connectionManager); - } - - @Singleton - @Provides - public static AlertEventHandler provideAlertEventHandler(Config config, IdentityManager identityManager) { - return new AlertEventHandler(config, identityManager); - } - - @Singleton - @Provides - public static IgnitionEventHandler provideIgnitionEventHandler(IdentityManager identityManager) { - return new IgnitionEventHandler(identityManager); - } - - @Singleton - @Provides - public static MaintenanceEventHandler provideMaintenanceEventHandler( - IdentityManager identityManager, MaintenancesManager maintenancesManager) { - return new MaintenanceEventHandler(identityManager, maintenancesManager); - } - - @Singleton - @Provides - public static DriverEventHandler provideDriverEventHandler(IdentityManager identityManager) { - return new DriverEventHandler(identityManager); - } - - @Singleton - @Provides - public static Timer provideTimer() { - return GlobalTimer.getTimer(); - } - - @Override - protected void configure() { - binder().requireExplicitBindings(); + VelocityEngine velocityEngine = new VelocityEngine(); + velocityEngine.init(properties); + return velocityEngine; } } diff --git a/src/main/java/org/traccar/PositionForwardingHandler.java b/src/main/java/org/traccar/PositionForwardingHandler.java new file mode 100644 index 000000000..83f91e937 --- /dev/null +++ b/src/main/java/org/traccar/PositionForwardingHandler.java @@ -0,0 +1,141 @@ +/* + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.ChannelHandler; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.forward.PositionData; +import org.traccar.forward.PositionForwarder; +import org.traccar.forward.ResultHandler; +import org.traccar.model.Device; +import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +@Singleton +@ChannelHandler.Sharable +public class PositionForwardingHandler extends BaseDataHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(PositionForwardingHandler.class); + + private final CacheManager cacheManager; + private final Timer timer; + + private final PositionForwarder positionForwarder; + + private final boolean retryEnabled; + private final int retryDelay; + private final int retryCount; + private final int retryLimit; + + private final AtomicInteger deliveryPending; + + @Inject + public PositionForwardingHandler( + Config config, CacheManager cacheManager, Timer timer, @Nullable PositionForwarder positionForwarder) { + + this.cacheManager = cacheManager; + this.timer = timer; + this.positionForwarder = positionForwarder; + + this.retryEnabled = config.getBoolean(Keys.FORWARD_RETRY_ENABLE); + this.retryDelay = config.getInteger(Keys.FORWARD_RETRY_DELAY); + this.retryCount = config.getInteger(Keys.FORWARD_RETRY_COUNT); + this.retryLimit = config.getInteger(Keys.FORWARD_RETRY_LIMIT); + + this.deliveryPending = new AtomicInteger(); + } + + class AsyncRequestAndCallback implements ResultHandler, TimerTask { + + private final PositionData positionData; + + private int retries = 0; + + AsyncRequestAndCallback(PositionData positionData) { + this.positionData = positionData; + deliveryPending.incrementAndGet(); + } + + private void send() { + positionForwarder.forward(positionData, this); + } + + private void retry(Throwable throwable) { + boolean scheduled = false; + try { + if (retryEnabled && deliveryPending.get() <= retryLimit && retries < retryCount) { + schedule(); + scheduled = true; + } + } finally { + int pending = scheduled ? deliveryPending.get() : deliveryPending.decrementAndGet(); + LOGGER.warn("Position forwarding failed: " + pending + " pending", throwable); + } + } + + private void schedule() { + timer.newTimeout(this, retryDelay * (long) Math.pow(2, retries++), TimeUnit.MILLISECONDS); + } + + @Override + public void onResult(boolean success, Throwable throwable) { + if (success) { + deliveryPending.decrementAndGet(); + } else { + retry(throwable); + } + } + + @Override + public void run(Timeout timeout) { + boolean sent = false; + try { + if (!timeout.isCancelled()) { + send(); + sent = true; + } + } finally { + if (!sent) { + deliveryPending.decrementAndGet(); + } + } + } + } + + @Override + protected Position handlePosition(Position position) { + if (positionForwarder != null) { + PositionData positionData = new PositionData(); + positionData.setPosition(position); + positionData.setDevice(cacheManager.getObject(Device.class, position.getDeviceId())); + new AsyncRequestAndCallback(positionData).send(); + } + return position; + } + +} diff --git a/src/main/java/org/traccar/Protocol.java b/src/main/java/org/traccar/Protocol.java index aea69b353..bc9c99557 100644 --- a/src/main/java/org/traccar/Protocol.java +++ b/src/main/java/org/traccar/Protocol.java @@ -25,7 +25,7 @@ public interface Protocol { String getName(); - Collection<TrackerServer> getServerList(); + Collection<TrackerConnector> getConnectorList(); Collection<String> getSupportedDataCommands(); diff --git a/src/main/java/org/traccar/ServerManager.java b/src/main/java/org/traccar/ServerManager.java index 935a821aa..57afb01fd 100644 --- a/src/main/java/org/traccar/ServerManager.java +++ b/src/main/java/org/traccar/ServerManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,95 +15,80 @@ */ package org.traccar; +import com.google.inject.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.traccar.config.Config; import org.traccar.config.Keys; +import org.traccar.helper.ClassScanner; -import java.io.File; +import javax.inject.Inject; +import javax.inject.Singleton; import java.io.IOException; import java.net.BindException; -import java.net.URI; +import java.net.ConnectException; import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.Enumeration; +import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -public class ServerManager { +@Singleton +public class ServerManager implements LifecycleObject { private static final Logger LOGGER = LoggerFactory.getLogger(ServerManager.class); - private final List<TrackerServer> serverList = new LinkedList<>(); + private final List<TrackerConnector> connectorList = new LinkedList<>(); private final Map<String, BaseProtocol> protocolList = new ConcurrentHashMap<>(); - private void loadPackage(String packageName) throws IOException, URISyntaxException, ReflectiveOperationException { - - List<String> names = new LinkedList<>(); - String packagePath = packageName.replace('.', '/'); - URL packageUrl = getClass().getClassLoader().getResource(packagePath); - - if (packageUrl.getProtocol().equals("jar")) { - String jarFileName = URLDecoder.decode(packageUrl.getFile(), StandardCharsets.UTF_8.name()); - try (JarFile jf = new JarFile(jarFileName.substring(5, jarFileName.indexOf("!")))) { - Enumeration<JarEntry> jarEntries = jf.entries(); - while (jarEntries.hasMoreElements()) { - String entryName = jarEntries.nextElement().getName(); - if (entryName.startsWith(packagePath) && entryName.length() > packagePath.length() + 5) { - names.add(entryName.substring(packagePath.length() + 1, entryName.lastIndexOf('.'))); - } - } - } - } else { - File folder = new File(new URI(packageUrl.toString())); - File[] files = folder.listFiles(); - if (files != null) { - for (File actual: files) { - String entryName = actual.getName(); - names.add(entryName.substring(0, entryName.lastIndexOf('.'))); - } - } + @Inject + public ServerManager( + Injector injector, Config config) throws IOException, URISyntaxException, ReflectiveOperationException { + Set<String> enabledProtocols = null; + if (config.hasKey(Keys.PROTOCOLS_ENABLE)) { + enabledProtocols = new HashSet<>(Arrays.asList(config.getString(Keys.PROTOCOLS_ENABLE).split("[, ]"))); } - - for (String name : names) { - Class<?> protocolClass = Class.forName(packageName + '.' + name); - if (BaseProtocol.class.isAssignableFrom(protocolClass) && Context.getConfig().hasKey( - Keys.PROTOCOL_PORT.withPrefix(BaseProtocol.nameFromClass(protocolClass)))) { - BaseProtocol protocol = (BaseProtocol) protocolClass.getDeclaredConstructor().newInstance(); - serverList.addAll(protocol.getServerList()); - protocolList.put(protocol.getName(), protocol); + for (Class<?> protocolClass : ClassScanner.findSubclasses(BaseProtocol.class, "org.traccar.protocol")) { + String protocolName = BaseProtocol.nameFromClass(protocolClass); + if (enabledProtocols == null || enabledProtocols.contains(protocolName)) { + if (config.hasKey(Keys.PROTOCOL_PORT.withPrefix(protocolName))) { + BaseProtocol protocol = (BaseProtocol) injector.getInstance(protocolClass); + connectorList.addAll(protocol.getConnectorList()); + protocolList.put(protocol.getName(), protocol); + } } } } - public ServerManager() throws IOException, URISyntaxException, ReflectiveOperationException { - loadPackage("org.traccar.protocol"); - } - public BaseProtocol getProtocol(String name) { return protocolList.get(name); } + @Override public void start() throws Exception { - for (TrackerServer server: serverList) { + for (TrackerConnector connector: connectorList) { try { - server.start(); + connector.start(); } catch (BindException e) { - LOGGER.warn("Port {} is disabled due to conflict", server.getPort()); + LOGGER.warn("Port disabled due to conflict", e); + } catch (ConnectException e) { + LOGGER.warn("Connection failed", e); } } } - public void stop() { - for (TrackerServer server: serverList) { - server.stop(); + @Override + public void stop() throws Exception { + try { + for (TrackerConnector connector : connectorList) { + connector.stop(); + } + } finally { + GlobalTimer.release(); } - GlobalTimer.release(); } } diff --git a/src/main/java/org/traccar/TrackerClient.java b/src/main/java/org/traccar/TrackerClient.java new file mode 100644 index 000000000..2d6b227da --- /dev/null +++ b/src/main/java/org/traccar/TrackerClient.java @@ -0,0 +1,125 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.ssl.SslHandler; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import io.netty.util.concurrent.GlobalEventExecutor; +import org.traccar.config.Config; +import org.traccar.config.Keys; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import java.util.concurrent.TimeUnit; + +public abstract class TrackerClient implements TrackerConnector { + + private final boolean secure; + private final long interval; + + private final Bootstrap bootstrap; + + private final int port; + private final String address; + private final String[] devices; + + private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + @Override + public boolean isDatagram() { + return false; + } + + @Override + public boolean isSecure() { + return secure; + } + + public TrackerClient(Config config, String protocol) { + secure = config.getBoolean(Keys.PROTOCOL_SSL.withPrefix(protocol)); + interval = config.getLong(Keys.PROTOCOL_INTERVAL.withPrefix(protocol)); + address = config.getString(Keys.PROTOCOL_ADDRESS.withPrefix(protocol)); + port = config.getInteger(Keys.PROTOCOL_PORT.withPrefix(protocol), secure ? 443 : 80); + devices = config.getString(Keys.PROTOCOL_DEVICES.withPrefix(protocol)).split("[, ]"); + + BasePipelineFactory pipelineFactory = new BasePipelineFactory(this, config, protocol) { + @Override + protected void addTransportHandlers(PipelineBuilder pipeline) { + try { + if (isSecure()) { + SSLEngine engine = SSLContext.getDefault().createSSLEngine(); + engine.setUseClientMode(true); + pipeline.addLast(new SslHandler(engine)); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + try { + TrackerClient.this.addProtocolHandlers(pipeline, config); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + + bootstrap = new Bootstrap() + .group(EventLoopGroupFactory.getWorkerGroup()) + .channel(NioSocketChannel.class) + .handler(pipelineFactory); + } + + protected abstract void addProtocolHandlers(PipelineBuilder pipeline, Config config) throws Exception; + + public String[] getDevices() { + return devices; + } + + @Override + public ChannelGroup getChannelGroup() { + return channelGroup; + } + + @Override + public void start() throws Exception { + bootstrap.connect(address, port) + .syncUninterruptibly().channel().closeFuture().addListener(new GenericFutureListener<>() { + @Override + public void operationComplete(Future<? super Void> future) { + if (interval > 0) { + GlobalEventExecutor.INSTANCE.schedule(() -> { + bootstrap.connect(address, port) + .syncUninterruptibly().channel().closeFuture().addListener(this); + }, interval, TimeUnit.SECONDS); + } + } + }); + } + + @Override + public void stop() { + channelGroup.close().awaitUninterruptibly(); + } + +} diff --git a/src/main/java/org/traccar/database/ManagableObjects.java b/src/main/java/org/traccar/TrackerConnector.java index ec9549493..fc6e93399 100644 --- a/src/main/java/org/traccar/database/ManagableObjects.java +++ b/src/main/java/org/traccar/TrackerConnector.java @@ -1,6 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.database; +package org.traccar; -import java.util.Set; +import io.netty.channel.group.ChannelGroup; -public interface ManagableObjects { +public interface TrackerConnector extends LifecycleObject { - Set<Long> getUserItems(long userId); + boolean isDatagram(); - Set<Long> getManagedItems(long userId); + boolean isSecure(); + + ChannelGroup getChannelGroup(); } diff --git a/src/main/java/org/traccar/TrackerServer.java b/src/main/java/org/traccar/TrackerServer.java index 59ba123e2..0e0837cfb 100644 --- a/src/main/java/org/traccar/TrackerServer.java +++ b/src/main/java/org/traccar/TrackerServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,78 +23,92 @@ import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.ssl.SslHandler; import io.netty.util.concurrent.GlobalEventExecutor; +import org.traccar.config.Config; import org.traccar.config.Keys; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import java.net.InetSocketAddress; -public abstract class TrackerServer { +public abstract class TrackerServer implements TrackerConnector { private final boolean datagram; + private final boolean secure; + + @SuppressWarnings("rawtypes") private final AbstractBootstrap bootstrap; + private final int port; + private final String address; + + private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + @Override public boolean isDatagram() { return datagram; } - public TrackerServer(boolean datagram, String protocol) { - this.datagram = datagram; + @Override + public boolean isSecure() { + return secure; + } - address = Context.getConfig().getString(Keys.PROTOCOL_ADDRESS.withPrefix(protocol)); - port = Context.getConfig().getInteger(Keys.PROTOCOL_PORT.withPrefix(protocol)); + public TrackerServer(Config config, String protocol, boolean datagram) { + secure = config.getBoolean(Keys.PROTOCOL_SSL.withPrefix(protocol)); + address = config.getString(Keys.PROTOCOL_ADDRESS.withPrefix(protocol)); + port = config.getInteger(Keys.PROTOCOL_PORT.withPrefix(protocol)); + + BasePipelineFactory pipelineFactory = new BasePipelineFactory(this, config, protocol) { + @Override + protected void addTransportHandlers(PipelineBuilder pipeline) { + try { + if (isSecure()) { + SSLEngine engine = SSLContext.getDefault().createSSLEngine(); + pipeline.addLast(new SslHandler(engine)); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } - BasePipelineFactory pipelineFactory = new BasePipelineFactory(this, protocol) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { - TrackerServer.this.addProtocolHandlers(pipeline); + TrackerServer.this.addProtocolHandlers(pipeline, config); } }; + this.datagram = datagram; if (datagram) { - - this.bootstrap = new Bootstrap() + bootstrap = new Bootstrap() .group(EventLoopGroupFactory.getWorkerGroup()) .channel(NioDatagramChannel.class) .handler(pipelineFactory); - } else { - - this.bootstrap = new ServerBootstrap() + bootstrap = new ServerBootstrap() .group(EventLoopGroupFactory.getBossGroup(), EventLoopGroupFactory.getWorkerGroup()) .channel(NioServerSocketChannel.class) .childHandler(pipelineFactory); - } } - protected abstract void addProtocolHandlers(PipelineBuilder pipeline); - - private int port; + protected abstract void addProtocolHandlers(PipelineBuilder pipeline, Config config); public int getPort() { return port; } - public void setPort(int port) { - this.port = port; - } - - private String address; - public String getAddress() { return address; } - public void setAddress(String address) { - this.address = address; - } - - private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); - + @Override public ChannelGroup getChannelGroup() { return channelGroup; } + @Override public void start() throws Exception { InetSocketAddress endpoint; if (address == null) { @@ -103,12 +117,13 @@ public abstract class TrackerServer { endpoint = new InetSocketAddress(address, port); } - Channel channel = bootstrap.bind(endpoint).sync().channel(); + Channel channel = bootstrap.bind(endpoint).syncUninterruptibly().channel(); if (channel != null) { getChannelGroup().add(channel); } } + @Override public void stop() { channelGroup.close().awaitUninterruptibly(); } diff --git a/src/main/java/org/traccar/WebDataHandler.java b/src/main/java/org/traccar/WebDataHandler.java deleted file mode 100644 index 678096d34..000000000 --- a/src/main/java/org/traccar/WebDataHandler.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.netty.channel.ChannelHandler; -import io.netty.util.Timer; -import io.netty.util.Timeout; -import io.netty.util.TimerTask; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.traccar.config.Config; -import org.traccar.config.Keys; -import org.traccar.database.IdentityManager; -import org.traccar.helper.Checksum; -import org.traccar.model.Device; -import org.traccar.model.Position; -import org.traccar.model.Group; - -import javax.inject.Inject; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.Invocation; -import javax.ws.rs.client.InvocationCallback; -import java.util.HashMap; -import java.util.Map; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Calendar; -import java.util.Formatter; -import java.util.Locale; -import java.util.TimeZone; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -@ChannelHandler.Sharable -public class WebDataHandler extends BaseDataHandler { - - private static final Logger LOGGER = LoggerFactory.getLogger(WebDataHandler.class); - - private static final String KEY_POSITION = "position"; - private static final String KEY_DEVICE = "device"; - - private final IdentityManager identityManager; - private final ObjectMapper objectMapper; - private final Client client; - - private final String url; - private final String header; - private final boolean json; - private final boolean urlVariables; - - private final boolean retryEnabled; - private final int retryDelay; - private final int retryCount; - private final int retryLimit; - - private final AtomicInteger deliveryPending; - - @Inject - public WebDataHandler( - Config config, IdentityManager identityManager, ObjectMapper objectMapper, Client client) { - - this.identityManager = identityManager; - this.objectMapper = objectMapper; - this.client = client; - this.url = config.getString(Keys.FORWARD_URL); - this.header = config.getString(Keys.FORWARD_HEADER); - this.json = config.getBoolean(Keys.FORWARD_JSON); - this.urlVariables = config.getBoolean(Keys.FORWARD_URL_VARIABLES); - - this.retryEnabled = config.getBoolean(Keys.FORWARD_RETRY_ENABLE); - this.retryDelay = config.getInteger(Keys.FORWARD_RETRY_DELAY, 100); - this.retryCount = config.getInteger(Keys.FORWARD_RETRY_COUNT, 10); - this.retryLimit = config.getInteger(Keys.FORWARD_RETRY_LIMIT, 100); - - this.deliveryPending = new AtomicInteger(0); - } - - private static String formatSentence(Position position) { - - StringBuilder s = new StringBuilder("$GPRMC,"); - - try (Formatter f = new Formatter(s, Locale.ENGLISH)) { - - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ENGLISH); - calendar.setTimeInMillis(position.getFixTime().getTime()); - - f.format("%1$tH%1$tM%1$tS.%1$tL,A,", calendar); - - double lat = position.getLatitude(); - double lon = position.getLongitude(); - - f.format("%02d%07.4f,%c,", (int) Math.abs(lat), Math.abs(lat) % 1 * 60, lat < 0 ? 'S' : 'N'); - f.format("%03d%07.4f,%c,", (int) Math.abs(lon), Math.abs(lon) % 1 * 60, lon < 0 ? 'W' : 'E'); - - f.format("%.2f,%.2f,", position.getSpeed(), position.getCourse()); - f.format("%1$td%1$tm%1$ty,,", calendar); - } - - s.append(Checksum.nmea(s.substring(1))); - - return s.toString(); - } - - private String calculateStatus(Position position) { - if (position.getAttributes().containsKey(Position.KEY_ALARM)) { - return "0xF841"; // STATUS_PANIC_ON - } else if (position.getSpeed() < 1.0) { - return "0xF020"; // STATUS_LOCATION - } else { - return "0xF11C"; // STATUS_MOTION_MOVING - } - } - - public String formatRequest(Position position) throws UnsupportedEncodingException, JsonProcessingException { - - Device device = identityManager.getById(position.getDeviceId()); - - String request = url - .replace("{name}", URLEncoder.encode(device.getName(), StandardCharsets.UTF_8.name())) - .replace("{uniqueId}", device.getUniqueId()) - .replace("{status}", device.getStatus()) - .replace("{deviceId}", String.valueOf(position.getDeviceId())) - .replace("{protocol}", String.valueOf(position.getProtocol())) - .replace("{deviceTime}", String.valueOf(position.getDeviceTime().getTime())) - .replace("{fixTime}", String.valueOf(position.getFixTime().getTime())) - .replace("{valid}", String.valueOf(position.getValid())) - .replace("{latitude}", String.valueOf(position.getLatitude())) - .replace("{longitude}", String.valueOf(position.getLongitude())) - .replace("{altitude}", String.valueOf(position.getAltitude())) - .replace("{speed}", String.valueOf(position.getSpeed())) - .replace("{course}", String.valueOf(position.getCourse())) - .replace("{accuracy}", String.valueOf(position.getAccuracy())) - .replace("{statusCode}", calculateStatus(position)); - - if (position.getAddress() != null) { - request = request.replace( - "{address}", URLEncoder.encode(position.getAddress(), StandardCharsets.UTF_8.name())); - } - - if (request.contains("{attributes}")) { - String attributes = objectMapper.writeValueAsString(position.getAttributes()); - request = request.replace( - "{attributes}", URLEncoder.encode(attributes, StandardCharsets.UTF_8.name())); - } - - if (request.contains("{gprmc}")) { - request = request.replace("{gprmc}", formatSentence(position)); - } - - if (request.contains("{group}")) { - String deviceGroupName = ""; - if (device.getGroupId() != 0) { - Group group = Context.getGroupsManager().getById(device.getGroupId()); - if (group != null) { - deviceGroupName = group.getName(); - } - } - - request = request.replace("{group}", URLEncoder.encode(deviceGroupName, StandardCharsets.UTF_8.name())); - } - - return request; - } - - class AsyncRequestAndCallback implements InvocationCallback<Response>, TimerTask { - - private int retries = 0; - private Map<String, Object> payload; - private final Invocation.Builder requestBuilder; - private MediaType mediaType = MediaType.APPLICATION_JSON_TYPE; - - AsyncRequestAndCallback(Position position) { - - String formattedUrl; - try { - formattedUrl = json && !urlVariables ? url : formatRequest(position); - } catch (UnsupportedEncodingException | JsonProcessingException e) { - throw new RuntimeException("Forwarding formatting error", e); - } - - requestBuilder = client.target(formattedUrl).request(); - if (header != null && !header.isEmpty()) { - for (String line: header.split("\\r?\\n")) { - String[] values = line.split(":", 2); - String headerName = values[0].trim(); - String headerValue = values[1].trim(); - if (headerName.equals(HttpHeaders.CONTENT_TYPE)) { - mediaType = MediaType.valueOf(headerValue); - } else { - requestBuilder.header(headerName, headerValue); - } - } - } - - if (json) { - payload = prepareJsonPayload(position); - } - - deliveryPending.incrementAndGet(); - } - - private void send() { - LOGGER.debug("Position forwarding initiated"); - if (json) { - try { - Entity<String> entity = Entity.entity(objectMapper.writeValueAsString(payload), mediaType); - requestBuilder.async().post(entity, this); - } catch (JsonProcessingException e) { - throw new RuntimeException("Failed to serialize location to json", e); - } - } else { - requestBuilder.async().get(this); - } - } - - private void retry(Throwable throwable) { - boolean scheduled = false; - try { - if (retryEnabled && deliveryPending.get() <= retryLimit && retries < retryCount) { - schedule(); - scheduled = true; - } - } finally { - int pending = scheduled ? deliveryPending.get() : deliveryPending.decrementAndGet(); - LOGGER.warn("Position forwarding failed: " + pending + " pending", throwable); - } - } - - private void schedule() { - Main.getInjector().getInstance(Timer.class).newTimeout( - this, retryDelay * (long) Math.pow(2, retries++), TimeUnit.MILLISECONDS); - } - - @Override - public void completed(Response response) { - if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) { - deliveryPending.decrementAndGet(); - LOGGER.debug("Position forwarding succeeded"); - } else { - retry(new RuntimeException("Status code 2xx expected")); - } - } - - @Override - public void failed(Throwable throwable) { - retry(throwable); - } - - @Override - public void run(Timeout timeout) { - boolean sent = false; - try { - if (!timeout.isCancelled()) { - send(); - sent = true; - } - } finally { - if (!sent) { - deliveryPending.decrementAndGet(); - } - } - } - - } - - @Override - protected Position handlePosition(Position position) { - - AsyncRequestAndCallback request = new AsyncRequestAndCallback(position); - request.send(); - - return position; - } - - private Map<String, Object> prepareJsonPayload(Position position) { - - Map<String, Object> data = new HashMap<>(); - Device device = identityManager.getById(position.getDeviceId()); - - data.put(KEY_POSITION, position); - - if (device != null) { - data.put(KEY_DEVICE, device); - } - - return data; - } - -} diff --git a/src/main/java/org/traccar/api/AsyncSocket.java b/src/main/java/org/traccar/api/AsyncSocket.java index b1853822d..5fc4b4412 100644 --- a/src/main/java/org/traccar/api/AsyncSocket.java +++ b/src/main/java/org/traccar/api/AsyncSocket.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,18 @@ package org.traccar.api; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.database.ConnectionManager; +import org.traccar.helper.model.PositionUtil; +import org.traccar.session.ConnectionManager; import org.traccar.model.Device; import org.traccar.model.Event; import org.traccar.model.Position; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; import java.util.Collection; import java.util.Collections; @@ -39,9 +42,15 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U private static final String KEY_POSITIONS = "positions"; private static final String KEY_EVENTS = "events"; + private final ObjectMapper objectMapper; + private final ConnectionManager connectionManager; + private final Storage storage; private final long userId; - public AsyncSocket(long userId) { + public AsyncSocket(ObjectMapper objectMapper, ConnectionManager connectionManager, Storage storage, long userId) { + this.objectMapper = objectMapper; + this.connectionManager = connectionManager; + this.storage = storage; this.userId = userId; } @@ -49,18 +58,21 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U public void onWebSocketConnect(Session session) { super.onWebSocketConnect(session); - Map<String, Collection<?>> data = new HashMap<>(); - data.put(KEY_POSITIONS, Context.getDeviceManager().getInitialState(userId)); - sendData(data); - - Context.getConnectionManager().addListener(userId, this); + try { + Map<String, Collection<?>> data = new HashMap<>(); + data.put(KEY_POSITIONS, PositionUtil.getLatestPositions(storage, userId)); + sendData(data); + connectionManager.addListener(userId, this); + } catch (StorageException e) { + throw new RuntimeException(e); + } } @Override public void onWebSocketClose(int statusCode, String reason) { super.onWebSocketClose(statusCode, reason); - Context.getConnectionManager().removeListener(userId, this); + connectionManager.removeListener(userId, this); } @Override @@ -92,7 +104,7 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U private void sendData(Map<String, Collection<?>> data) { if (isConnected()) { try { - getRemote().sendString(Context.getObjectMapper().writeValueAsString(data), null); + getRemote().sendString(objectMapper.writeValueAsString(data), null); } catch (JsonProcessingException e) { LOGGER.warn("Socket JSON formatting error", e); } diff --git a/src/main/java/org/traccar/api/AsyncSocketServlet.java b/src/main/java/org/traccar/api/AsyncSocketServlet.java index a964ead10..91a745eeb 100644 --- a/src/main/java/org/traccar/api/AsyncSocketServlet.java +++ b/src/main/java/org/traccar/api/AsyncSocketServlet.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,27 +15,48 @@ */ package org.traccar.api; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jetty.websocket.server.JettyWebSocketServlet; import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory; -import org.traccar.Context; import org.traccar.api.resource.SessionResource; +import org.traccar.config.Config; import org.traccar.config.Keys; +import org.traccar.session.ConnectionManager; +import org.traccar.storage.Storage; +import javax.inject.Inject; +import javax.inject.Singleton; import javax.servlet.http.HttpSession; import java.time.Duration; +@Singleton public class AsyncSocketServlet extends JettyWebSocketServlet { + private final Config config; + private final ObjectMapper objectMapper; + private final ConnectionManager connectionManager; + private final Storage storage; + + @Inject + public AsyncSocketServlet( + Config config, ObjectMapper objectMapper, ConnectionManager connectionManager, Storage storage) { + this.config = config; + this.objectMapper = objectMapper; + this.connectionManager = connectionManager; + this.storage = storage; + } + @Override public void configure(JettyWebSocketServletFactory factory) { - factory.setIdleTimeout(Duration.ofMillis(Context.getConfig().getLong(Keys.WEB_TIMEOUT))); + factory.setIdleTimeout(Duration.ofMillis(config.getLong(Keys.WEB_TIMEOUT))); factory.setCreator((req, resp) -> { if (req.getSession() != null) { - long userId = (Long) ((HttpSession) req.getSession()).getAttribute(SessionResource.USER_ID_KEY); - return new AsyncSocket(userId); - } else { - return null; + Long userId = (Long) ((HttpSession) req.getSession()).getAttribute(SessionResource.USER_ID_KEY); + if (userId != null) { + return new AsyncSocket(objectMapper, connectionManager, storage, userId); + } } + return null; }); } diff --git a/src/main/java/org/traccar/api/BaseObjectResource.java b/src/main/java/org/traccar/api/BaseObjectResource.java index 71f3939cb..904781e54 100644 --- a/src/main/java/org/traccar/api/BaseObjectResource.java +++ b/src/main/java/org/traccar/api/BaseObjectResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,9 +16,19 @@ */ package org.traccar.api; -import java.sql.SQLException; -import java.util.Set; - +import org.traccar.helper.LogAction; +import org.traccar.model.BaseModel; +import org.traccar.model.Group; +import org.traccar.model.Permission; +import org.traccar.model.User; +import org.traccar.session.ConnectionManager; +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -27,58 +37,26 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.core.Response; -import org.traccar.Context; -import org.traccar.database.BaseObjectManager; -import org.traccar.database.ExtendedObjectManager; -import org.traccar.database.ManagableObjects; -import org.traccar.database.SimpleObjectManager; -import org.traccar.helper.LogAction; -import org.traccar.model.BaseModel; -import org.traccar.model.Calendar; -import org.traccar.model.Command; -import org.traccar.model.Device; -import org.traccar.model.Group; -import org.traccar.model.GroupedModel; -import org.traccar.model.ScheduledModel; -import org.traccar.model.User; - public abstract class BaseObjectResource<T extends BaseModel> extends BaseResource { - private final Class<T> baseClass; + @Inject + private CacheManager cacheManager; - public BaseObjectResource(Class<T> baseClass) { - this.baseClass = baseClass; - } + @Inject + private ConnectionManager connectionManager; - protected final Class<T> getBaseClass() { - return baseClass; - } + protected final Class<T> baseClass; - protected final Set<Long> getSimpleManagerItems(BaseObjectManager<T> manager, boolean all, long userId) { - Set<Long> result; - if (all) { - if (Context.getPermissionsManager().getUserAdmin(getUserId())) { - result = manager.getAllItems(); - } else { - Context.getPermissionsManager().checkManager(getUserId()); - result = ((ManagableObjects) manager).getManagedItems(getUserId()); - } - } else { - if (userId == 0) { - userId = getUserId(); - } - Context.getPermissionsManager().checkUser(getUserId(), userId); - result = ((ManagableObjects) manager).getUserItems(userId); - } - return result; + public BaseObjectResource(Class<T> baseClass) { + this.baseClass = baseClass; } @Path("{id}") @GET - public Response getSingle(@PathParam("id") long id) throws SQLException { - Context.getPermissionsManager().checkPermission(baseClass, getUserId(), id); - BaseObjectManager<T> manager = Context.getManager(baseClass); - T entity = manager.getById(id); + public Response getSingle(@PathParam("id") long id) throws StorageException { + permissionsService.checkPermission(baseClass, getUserId(), id); + T entity = storage.getObject(baseClass, new Request( + new Columns.All(), new Condition.Equals("id", id))); if (entity != null) { return Response.ok(entity).build(); } else { @@ -87,103 +65,64 @@ public abstract class BaseObjectResource<T extends BaseModel> extends BaseResour } @POST - public Response add(T entity) throws SQLException { - Context.getPermissionsManager().checkReadonly(getUserId()); - if (baseClass.equals(Device.class)) { - Context.getPermissionsManager().checkDeviceReadonly(getUserId()); - Context.getPermissionsManager().checkDeviceLimit(getUserId()); - } else if (baseClass.equals(Command.class)) { - Context.getPermissionsManager().checkLimitCommands(getUserId()); - } else if (entity instanceof GroupedModel && ((GroupedModel) entity).getGroupId() != 0) { - Context.getPermissionsManager().checkPermission( - Group.class, getUserId(), ((GroupedModel) entity).getGroupId()); - } else if (entity instanceof ScheduledModel && ((ScheduledModel) entity).getCalendarId() != 0) { - Context.getPermissionsManager().checkPermission( - Calendar.class, getUserId(), ((ScheduledModel) entity).getCalendarId()); - } + public Response add(T entity) throws StorageException { + permissionsService.checkEdit(getUserId(), entity, true); - BaseObjectManager<T> manager = Context.getManager(baseClass); - manager.addItem(entity); + entity.setId(storage.addObject(entity, new Request(new Columns.Exclude("id")))); LogAction.create(getUserId(), entity); - - Context.getDataManager().linkObject(User.class, getUserId(), baseClass, entity.getId(), true); + storage.addPermission(new Permission(User.class, getUserId(), baseClass, entity.getId())); + cacheManager.invalidatePermission(true, User.class, getUserId(), baseClass, entity.getId()); + connectionManager.invalidatePermission(true, User.class, getUserId(), baseClass, entity.getId()); LogAction.link(getUserId(), User.class, getUserId(), baseClass, entity.getId()); - if (manager instanceof SimpleObjectManager) { - ((SimpleObjectManager<T>) manager).refreshUserItems(); - } else if (baseClass.equals(Group.class) || baseClass.equals(Device.class)) { - Context.getPermissionsManager().refreshDeviceAndGroupPermissions(); - Context.getPermissionsManager().refreshAllExtendedPermissions(); - } return Response.ok(entity).build(); } @Path("{id}") @PUT - public Response update(T entity) throws SQLException { - Context.getPermissionsManager().checkReadonly(getUserId()); - if (baseClass.equals(Device.class)) { - Context.getPermissionsManager().checkDeviceReadonly(getUserId()); - } else if (baseClass.equals(User.class)) { - User before = Context.getPermissionsManager().getUser(entity.getId()); - Context.getPermissionsManager().checkUserUpdate(getUserId(), before, (User) entity); - } else if (baseClass.equals(Command.class)) { - Context.getPermissionsManager().checkLimitCommands(getUserId()); - } else if (entity instanceof GroupedModel && ((GroupedModel) entity).getGroupId() != 0) { - Context.getPermissionsManager().checkPermission( - Group.class, getUserId(), ((GroupedModel) entity).getGroupId()); - } else if (entity instanceof ScheduledModel && ((ScheduledModel) entity).getCalendarId() != 0) { - Context.getPermissionsManager().checkPermission( - Calendar.class, getUserId(), ((ScheduledModel) entity).getCalendarId()); + public Response update(T entity) throws StorageException { + permissionsService.checkEdit(getUserId(), entity, false); + permissionsService.checkPermission(baseClass, getUserId(), entity.getId()); + + if (entity instanceof User) { + User before = storage.getObject(User.class, new Request( + new Columns.All(), new Condition.Equals("id", entity.getId()))); + permissionsService.checkUserUpdate(getUserId(), before, (User) entity); + } else if (entity instanceof Group) { + Group group = (Group) entity; + if (group.getId() == group.getGroupId()) { + throw new IllegalArgumentException("Cycle in group hierarchy"); + } } - Context.getPermissionsManager().checkPermission(baseClass, getUserId(), entity.getId()); - Context.getManager(baseClass).updateItem(entity); + storage.updateObject(entity, new Request( + new Columns.Exclude("id"), + new Condition.Equals("id", entity.getId()))); + if (entity instanceof User) { + User user = (User) entity; + if (user.getHashedPassword() != null) { + storage.updateObject(entity, new Request( + new Columns.Include("hashedPassword", "salt"), + new Condition.Equals("id", entity.getId()))); + } + } + cacheManager.updateOrInvalidate(true, entity); LogAction.edit(getUserId(), entity); - if (baseClass.equals(Group.class) || baseClass.equals(Device.class)) { - Context.getPermissionsManager().refreshDeviceAndGroupPermissions(); - Context.getPermissionsManager().refreshAllExtendedPermissions(); - } return Response.ok(entity).build(); } @Path("{id}") @DELETE - public Response remove(@PathParam("id") long id) throws SQLException { - Context.getPermissionsManager().checkReadonly(getUserId()); - if (baseClass.equals(Device.class)) { - Context.getPermissionsManager().checkDeviceReadonly(getUserId()); - } else if (baseClass.equals(Command.class)) { - Context.getPermissionsManager().checkLimitCommands(getUserId()); - } - Context.getPermissionsManager().checkPermission(baseClass, getUserId(), id); + public Response remove(@PathParam("id") long id) throws StorageException { + permissionsService.checkEdit(getUserId(), baseClass, false); + permissionsService.checkPermission(baseClass, getUserId(), id); + + storage.removeObject(baseClass, new Request(new Condition.Equals("id", id))); + cacheManager.invalidate(baseClass, id); - BaseObjectManager<T> manager = Context.getManager(baseClass); - manager.removeItem(id); LogAction.remove(getUserId(), baseClass, id); - if (manager instanceof SimpleObjectManager) { - ((SimpleObjectManager<T>) manager).refreshUserItems(); - if (manager instanceof ExtendedObjectManager) { - ((ExtendedObjectManager<T>) manager).refreshExtendedPermissions(); - } - } - if (baseClass.equals(Group.class) || baseClass.equals(Device.class) || baseClass.equals(User.class)) { - if (baseClass.equals(Group.class)) { - Context.getGroupsManager().refreshItems(); - Context.getDeviceManager().updateDeviceCache(true); - } - Context.getPermissionsManager().refreshDeviceAndGroupPermissions(); - if (baseClass.equals(User.class)) { - Context.getPermissionsManager().refreshAllUsersPermissions(); - } else { - Context.getPermissionsManager().refreshAllExtendedPermissions(); - } - } else if (baseClass.equals(Calendar.class)) { - Context.getGeofenceManager().refreshItems(); - Context.getNotificationManager().refreshItems(); - } return Response.noContent().build(); } diff --git a/src/main/java/org/traccar/api/BaseResource.java b/src/main/java/org/traccar/api/BaseResource.java index cc272df9c..33abe73fa 100644 --- a/src/main/java/org/traccar/api/BaseResource.java +++ b/src/main/java/org/traccar/api/BaseResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,25 @@ */ package org.traccar.api; +import org.traccar.api.security.PermissionsService; +import org.traccar.api.security.UserPrincipal; +import org.traccar.storage.Storage; + +import javax.inject.Inject; +import javax.ws.rs.core.Context; import javax.ws.rs.core.SecurityContext; public class BaseResource { - @javax.ws.rs.core.Context + @Context private SecurityContext securityContext; + @Inject + protected Storage storage; + + @Inject + protected PermissionsService permissionsService; + protected long getUserId() { UserPrincipal principal = (UserPrincipal) securityContext.getUserPrincipal(); if (principal != null) { @@ -29,4 +41,5 @@ public class BaseResource { } return 0; } + } diff --git a/src/main/java/org/traccar/api/CorsResponseFilter.java b/src/main/java/org/traccar/api/CorsResponseFilter.java index 91aea5718..67d0341a1 100644 --- a/src/main/java/org/traccar/api/CorsResponseFilter.java +++ b/src/main/java/org/traccar/api/CorsResponseFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,26 @@ package org.traccar.api; import io.netty.handler.codec.http.HttpHeaderNames; -import org.traccar.Context; +import org.traccar.config.Config; import org.traccar.config.Keys; +import javax.inject.Inject; +import javax.inject.Singleton; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; import java.io.IOException; +@Singleton public class CorsResponseFilter implements ContainerResponseFilter { + private final String allowed; + + @Inject + public CorsResponseFilter(Config config) { + allowed = config.getString(Keys.WEB_ORIGIN); + } + private static final String ORIGIN_ALL = "*"; private static final String HEADERS_ALL = "origin, content-type, accept, authorization"; private static final String METHODS_ALL = "GET, POST, PUT, DELETE, OPTIONS"; @@ -46,8 +56,6 @@ public class CorsResponseFilter implements ContainerResponseFilter { if (!response.getHeaders().containsKey(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN.toString())) { String origin = request.getHeaderString(HttpHeaderNames.ORIGIN.toString()); - String allowed = Context.getConfig().getString(Keys.WEB_ORIGIN); - if (origin == null) { response.getHeaders().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN.toString(), ORIGIN_ALL); } else if (allowed == null || allowed.equals(ORIGIN_ALL) || allowed.contains(origin)) { diff --git a/src/main/java/org/traccar/api/ExtendedObjectResource.java b/src/main/java/org/traccar/api/ExtendedObjectResource.java index 9e554217e..8467b46c6 100644 --- a/src/main/java/org/traccar/api/ExtendedObjectResource.java +++ b/src/main/java/org/traccar/api/ExtendedObjectResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,17 +16,19 @@ */ package org.traccar.api; -import java.sql.SQLException; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import org.traccar.model.BaseModel; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.model.User; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; import javax.ws.rs.GET; import javax.ws.rs.QueryParam; - -import org.traccar.Context; -import org.traccar.database.ExtendedObjectManager; -import org.traccar.model.BaseModel; +import java.util.Collection; +import java.util.LinkedList; public class ExtendedObjectResource<T extends BaseModel> extends BaseObjectResource<T> { @@ -36,27 +38,34 @@ public class ExtendedObjectResource<T extends BaseModel> extends BaseObjectResou @GET public Collection<T> get( - @QueryParam("all") boolean all, @QueryParam("userId") long userId, @QueryParam("groupId") long groupId, - @QueryParam("deviceId") long deviceId, @QueryParam("refresh") boolean refresh) throws SQLException { - - ExtendedObjectManager<T> manager = (ExtendedObjectManager<T>) Context.getManager(getBaseClass()); - if (refresh) { - manager.refreshItems(); - } + @QueryParam("all") boolean all, @QueryParam("userId") long userId, + @QueryParam("groupId") long groupId, @QueryParam("deviceId") long deviceId) throws StorageException { - Set<Long> result = new HashSet<>(getSimpleManagerItems(manager, all, userId)); + var conditions = new LinkedList<Condition>(); - if (groupId != 0) { - Context.getPermissionsManager().checkGroup(getUserId(), groupId); - result.retainAll(manager.getGroupItems(groupId)); + if (all) { + if (permissionsService.notAdmin(getUserId())) { + conditions.add(new Condition.Permission(User.class, getUserId(), baseClass)); + } + } else { + if (userId == 0) { + conditions.add(new Condition.Permission(User.class, getUserId(), baseClass)); + } else { + permissionsService.checkUser(getUserId(), userId); + conditions.add(new Condition.Permission(User.class, userId, baseClass).excludeGroups()); + } } - if (deviceId != 0) { - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - result.retainAll(manager.getDeviceItems(deviceId)); + if (groupId > 0) { + permissionsService.checkPermission(Group.class, getUserId(), groupId); + conditions.add(new Condition.Permission(Group.class, groupId, baseClass).excludeGroups()); + } + if (deviceId > 0) { + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + conditions.add(new Condition.Permission(Device.class, deviceId, baseClass).excludeGroups()); } - return manager.getItems(result); + return storage.getObjects(baseClass, new Request(new Columns.All(), Condition.merge(conditions))); } } diff --git a/src/main/java/org/traccar/api/MediaFilter.java b/src/main/java/org/traccar/api/MediaFilter.java index 77731a810..ab75bdc5d 100644 --- a/src/main/java/org/traccar/api/MediaFilter.java +++ b/src/main/java/org/traccar/api/MediaFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,9 +16,20 @@ */ package org.traccar.api; -import java.io.IOException; -import java.sql.SQLException; +import com.google.inject.Provider; +import org.traccar.api.resource.SessionResource; +import org.traccar.api.security.PermissionsService; +import org.traccar.database.StatisticsManager; +import org.traccar.helper.Log; +import org.traccar.model.Device; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; +import javax.inject.Inject; +import javax.inject.Singleton; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -28,16 +39,24 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import java.io.IOException; -import org.traccar.Context; -import org.traccar.Main; -import org.traccar.api.resource.SessionResource; -import org.traccar.database.StatisticsManager; -import org.traccar.helper.Log; -import org.traccar.model.Device; - +@Singleton public class MediaFilter implements Filter { + private final Storage storage; + private final StatisticsManager statisticsManager; + private final Provider<PermissionsService> permissionsServiceProvider; + + @Inject + public MediaFilter( + Storage storage, StatisticsManager statisticsManager, + Provider<PermissionsService> permissionsServiceProvider) { + this.storage = storage; + this.statisticsManager = statisticsManager; + this.permissionsServiceProvider = permissionsServiceProvider; + } + @Override public void init(FilterConfig filterConfig) throws ServletException { } @@ -45,6 +64,7 @@ public class MediaFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletResponse httpResponse = (HttpServletResponse) response; try { HttpSession session = ((HttpServletRequest) request).getSession(false); @@ -52,8 +72,7 @@ public class MediaFilter implements Filter { if (session != null) { userId = (Long) session.getAttribute(SessionResource.USER_ID_KEY); if (userId != null) { - Context.getPermissionsManager().checkUserEnabled(userId); - Main.getInjector().getInstance(StatisticsManager.class).registerRequest(userId); + statisticsManager.registerRequest(userId); } } if (userId == null) { @@ -64,21 +83,19 @@ public class MediaFilter implements Filter { String path = ((HttpServletRequest) request).getPathInfo(); String[] parts = path != null ? path.split("/") : null; if (parts != null && parts.length >= 2) { - Device device = Context.getDeviceManager().getByUniqueId(parts[1]); + Device device = storage.getObject(Device.class, new Request( + new Columns.All(), new Condition.Equals("uniqueId", parts[1]))); if (device != null) { - Context.getPermissionsManager().checkDevice(userId, device.getId()); + permissionsServiceProvider.get().checkPermission(Device.class, userId, device.getId()); chain.doFilter(request, response); return; } } httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); - } catch (SecurityException e) { + } catch (SecurityException | StorageException e) { httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); httpResponse.getWriter().println(Log.exceptionStack(e)); - } catch (SQLException e) { - httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); - httpResponse.getWriter().println(Log.exceptionStack(e)); } } diff --git a/src/main/java/org/traccar/api/SimpleObjectResource.java b/src/main/java/org/traccar/api/SimpleObjectResource.java index a7fcae0e7..4a435ca7d 100644 --- a/src/main/java/org/traccar/api/SimpleObjectResource.java +++ b/src/main/java/org/traccar/api/SimpleObjectResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,15 +16,17 @@ */ package org.traccar.api; -import java.sql.SQLException; -import java.util.Collection; +import org.traccar.model.BaseModel; +import org.traccar.model.User; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; import javax.ws.rs.GET; import javax.ws.rs.QueryParam; - -import org.traccar.Context; -import org.traccar.database.BaseObjectManager; -import org.traccar.model.BaseModel; +import java.util.Collection; +import java.util.LinkedList; public class SimpleObjectResource<T extends BaseModel> extends BaseObjectResource<T> { @@ -34,10 +36,24 @@ public class SimpleObjectResource<T extends BaseModel> extends BaseObjectResourc @GET public Collection<T> get( - @QueryParam("all") boolean all, @QueryParam("userId") long userId) throws SQLException { - - BaseObjectManager<T> manager = Context.getManager(getBaseClass()); - return manager.getItems(getSimpleManagerItems(manager, all, userId)); + @QueryParam("all") boolean all, @QueryParam("userId") long userId) throws StorageException { + + var conditions = new LinkedList<Condition>(); + + if (all) { + if (permissionsService.notAdmin(getUserId())) { + conditions.add(new Condition.Permission(User.class, getUserId(), baseClass)); + } + } else { + if (userId == 0) { + userId = getUserId(); + } else { + permissionsService.checkUser(getUserId(), userId); + } + conditions.add(new Condition.Permission(User.class, userId, baseClass)); + } + + return storage.getObjects(baseClass, new Request(new Columns.All(), Condition.merge(conditions))); } } diff --git a/src/main/java/org/traccar/api/resource/AttributeResource.java b/src/main/java/org/traccar/api/resource/AttributeResource.java index de69d871c..f85e90133 100644 --- a/src/main/java/org/traccar/api/resource/AttributeResource.java +++ b/src/main/java/org/traccar/api/resource/AttributeResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,7 @@ */ package org.traccar.api.resource; -import java.sql.SQLException; - +import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.POST; @@ -29,68 +28,72 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.traccar.Context; import org.traccar.api.ExtendedObjectResource; import org.traccar.model.Attribute; +import org.traccar.model.Device; import org.traccar.model.Position; import org.traccar.handler.ComputedAttributesHandler; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; @Path("attributes/computed") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class AttributeResource extends ExtendedObjectResource<Attribute> { + @Inject + private ComputedAttributesHandler computedAttributesHandler; + public AttributeResource() { super(Attribute.class); } @POST @Path("test") - public Response test(@QueryParam("deviceId") long deviceId, Attribute entity) { - Context.getPermissionsManager().checkAdmin(getUserId()); - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - Position last = Context.getIdentityManager().getLastPosition(deviceId); - if (last != null) { - Object result = new ComputedAttributesHandler( - Context.getConfig(), - Context.getIdentityManager(), - Context.getAttributesManager()).computeAttribute(entity, last); - if (result != null) { - switch (entity.getType()) { - case "number": - Number numberValue = (Number) result; - return Response.ok(numberValue).build(); - case "boolean": - Boolean booleanValue = (Boolean) result; - return Response.ok(booleanValue).build(); - default: - return Response.ok(result.toString()).build(); - } - } else { - return Response.noContent().build(); + public Response test(@QueryParam("deviceId") long deviceId, Attribute entity) throws StorageException { + permissionsService.checkAdmin(getUserId()); + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + + Position position = storage.getObject(Position.class, new Request( + new Columns.All(), + new Condition.LatestPositions(deviceId))); + + Object result = computedAttributesHandler.computeAttribute(entity, position); + if (result != null) { + switch (entity.getType()) { + case "number": + Number numberValue = (Number) result; + return Response.ok(numberValue).build(); + case "boolean": + Boolean booleanValue = (Boolean) result; + return Response.ok(booleanValue).build(); + default: + return Response.ok(result.toString()).build(); } } else { - throw new IllegalArgumentException("Device has no last position"); + return Response.noContent().build(); } } @POST - public Response add(Attribute entity) throws SQLException { - Context.getPermissionsManager().checkAdmin(getUserId()); + public Response add(Attribute entity) throws StorageException { + permissionsService.checkAdmin(getUserId()); return super.add(entity); } @Path("{id}") @PUT - public Response update(Attribute entity) throws SQLException { - Context.getPermissionsManager().checkAdmin(getUserId()); + public Response update(Attribute entity) throws StorageException { + permissionsService.checkAdmin(getUserId()); return super.update(entity); } @Path("{id}") @DELETE - public Response remove(@PathParam("id") long id) throws SQLException { - Context.getPermissionsManager().checkAdmin(getUserId()); + public Response remove(@PathParam("id") long id) throws StorageException { + permissionsService.checkAdmin(getUserId()); return super.remove(id); } diff --git a/src/main/java/org/traccar/api/resource/CommandResource.java b/src/main/java/org/traccar/api/resource/CommandResource.java index a31345246..6ef6ee9c5 100644 --- a/src/main/java/org/traccar/api/resource/CommandResource.java +++ b/src/main/java/org/traccar/api/resource/CommandResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2016 Gabor Somogyi (gabor.g.somogyi@gmail.com) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * @@ -17,16 +17,24 @@ */ package org.traccar.api.resource; -import org.traccar.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BaseProtocol; +import org.traccar.ServerManager; import org.traccar.api.ExtendedObjectResource; import org.traccar.database.CommandsManager; import org.traccar.model.Command; +import org.traccar.model.Device; +import org.traccar.model.Position; import org.traccar.model.Typed; +import org.traccar.model.User; +import org.traccar.model.UserRestrictions; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - +import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -35,40 +43,80 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; @Path("commands") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class CommandResource extends ExtendedObjectResource<Command> { + private static final Logger LOGGER = LoggerFactory.getLogger(CommandResource.class); + + @Inject + private CommandsManager commandsManager; + + @Inject + private ServerManager serverManager; + public CommandResource() { super(Command.class); } + private BaseProtocol getDeviceProtocol(long deviceId) throws StorageException { + Position position = storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.LatestPositions(deviceId))); + if (position != null) { + return serverManager.getProtocol(position.getProtocol()); + } else { + return null; + } + } + @GET @Path("send") - public Collection<Command> get(@QueryParam("deviceId") long deviceId) { - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - CommandsManager commandsManager = Context.getCommandsManager(); - Set<Long> result = new HashSet<>(commandsManager.getUserItems(getUserId())); - result.retainAll(commandsManager.getSupportedCommands(deviceId)); - return commandsManager.getItems(result); + public Collection<Command> get(@QueryParam("deviceId") long deviceId) throws StorageException { + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + BaseProtocol protocol = getDeviceProtocol(deviceId); + + var commands = storage.getObjects(baseClass, new Request( + new Columns.All(), + Condition.merge(List.of( + new Condition.Permission(User.class, getUserId(), baseClass), + new Condition.Permission(Device.class, deviceId, baseClass) + )))); + + return commands.stream().filter(command -> { + String type = command.getType(); + if (protocol != null) { + return command.getTextChannel() && protocol.getSupportedTextCommands().contains(type) + || !command.getTextChannel() && protocol.getSupportedDataCommands().contains(type); + } else { + return type.equals(Command.TYPE_CUSTOM); + } + }).collect(Collectors.toList()); } @POST @Path("send") public Response send(Command entity) throws Exception { - Context.getPermissionsManager().checkReadonly(getUserId()); - long deviceId = entity.getDeviceId(); - long id = entity.getId(); - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - if (id != 0) { - Context.getPermissionsManager().checkPermission(Command.class, getUserId(), id); - Context.getPermissionsManager().checkUserDeviceCommand(getUserId(), deviceId, id); + permissionsService.checkRestriction(getUserId(), UserRestrictions::getReadonly); + if (entity.getId() > 0) { + permissionsService.checkPermission(baseClass, getUserId(), entity.getId()); + long deviceId = entity.getDeviceId(); + entity = storage.getObject(baseClass, new Request( + new Columns.All(), new Condition.Equals("id", entity.getId()))); + entity.setDeviceId(deviceId); } else { - Context.getPermissionsManager().checkLimitCommands(getUserId()); + permissionsService.checkRestriction(getUserId(), UserRestrictions::getLimitCommands); } - if (!Context.getCommandsManager().sendCommand(entity)) { + permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId()); + if (!commandsManager.sendCommand(entity)) { return Response.accepted(entity).build(); } return Response.ok(entity).build(); @@ -78,15 +126,33 @@ public class CommandResource extends ExtendedObjectResource<Command> { @Path("types") public Collection<Typed> get( @QueryParam("deviceId") long deviceId, - @QueryParam("protocol") String protocol, - @QueryParam("textChannel") boolean textChannel) { + @QueryParam("textChannel") boolean textChannel) throws StorageException { if (deviceId != 0) { - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - return Context.getCommandsManager().getCommandTypes(deviceId, textChannel); - } else if (protocol != null) { - return Context.getCommandsManager().getCommandTypes(protocol, textChannel); + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + BaseProtocol protocol = getDeviceProtocol(deviceId); + if (protocol != null) { + if (textChannel) { + return protocol.getSupportedTextCommands().stream().map(Typed::new).collect(Collectors.toList()); + } else { + return protocol.getSupportedDataCommands().stream().map(Typed::new).collect(Collectors.toList()); + } + } else { + return Collections.singletonList(new Typed(Command.TYPE_CUSTOM)); + } } else { - return Context.getCommandsManager().getAllCommandTypes(); + List<Typed> result = new ArrayList<>(); + Field[] fields = Command.class.getDeclaredFields(); + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers()) && field.getName().startsWith("TYPE_")) { + try { + result.add(new Typed(field.get(null).toString())); + } catch (IllegalArgumentException | IllegalAccessException error) { + LOGGER.warn("Get command types error", error); + } + } + } + return result; } } + } diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java index 7006cdb84..c0b0cea0d 100644 --- a/src/main/java/org/traccar/api/resource/DeviceResource.java +++ b/src/main/java/org/traccar/api/resource/DeviceResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,33 +15,58 @@ */ package org.traccar.api.resource; -import org.traccar.Context; import org.traccar.api.BaseObjectResource; -import org.traccar.database.DeviceManager; +import org.traccar.broadcast.BroadcastService; +import org.traccar.database.MediaManager; import org.traccar.helper.LogAction; import org.traccar.model.Device; import org.traccar.model.DeviceAccumulators; +import org.traccar.model.Position; +import org.traccar.model.User; +import org.traccar.session.ConnectionManager; +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; +import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; - -import java.sql.SQLException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.util.Collection; -import java.util.HashSet; +import java.util.LinkedList; import java.util.List; -import java.util.Set; @Path("devices") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class DeviceResource extends BaseObjectResource<Device> { + @Inject + private CacheManager cacheManager; + + @Inject + private ConnectionManager connectionManager; + + @Inject + private BroadcastService broadcastService; + + @Inject + private MediaManager mediaManager; + public DeviceResource() { super(Device.class); } @@ -50,51 +75,112 @@ public class DeviceResource extends BaseObjectResource<Device> { public Collection<Device> get( @QueryParam("all") boolean all, @QueryParam("userId") long userId, @QueryParam("uniqueId") List<String> uniqueIds, - @QueryParam("id") List<Long> deviceIds) throws SQLException { - DeviceManager deviceManager = Context.getDeviceManager(); - Set<Long> result; - if (all) { - if (Context.getPermissionsManager().getUserAdmin(getUserId())) { - result = deviceManager.getAllItems(); - } else { - Context.getPermissionsManager().checkManager(getUserId()); - result = deviceManager.getManagedItems(getUserId()); - } - } else if (uniqueIds.isEmpty() && deviceIds.isEmpty()) { - if (userId == 0) { - userId = getUserId(); - } - Context.getPermissionsManager().checkUser(getUserId(), userId); - if (Context.getPermissionsManager().getUserAdmin(getUserId())) { - result = deviceManager.getAllUserItems(userId); - } else { - result = deviceManager.getUserItems(userId); - } - } else { - result = new HashSet<>(); + @QueryParam("id") List<Long> deviceIds) throws StorageException { + + if (!uniqueIds.isEmpty() || !deviceIds.isEmpty()) { + + List<Device> result = new LinkedList<>(); for (String uniqueId : uniqueIds) { - Device device = deviceManager.getByUniqueId(uniqueId); - Context.getPermissionsManager().checkDevice(getUserId(), device.getId()); - result.add(device.getId()); + result.addAll(storage.getObjects(Device.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("uniqueId", uniqueId), + new Condition.Permission(User.class, getUserId(), Device.class))))); } for (Long deviceId : deviceIds) { - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - result.add(deviceId); + result.addAll(storage.getObjects(Device.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("id", deviceId), + new Condition.Permission(User.class, getUserId(), Device.class))))); } + return result; + + } else { + + var conditions = new LinkedList<Condition>(); + + if (all) { + if (permissionsService.notAdmin(getUserId())) { + conditions.add(new Condition.Permission(User.class, getUserId(), baseClass)); + } + } else { + if (userId == 0) { + conditions.add(new Condition.Permission(User.class, getUserId(), baseClass)); + } else { + permissionsService.checkUser(getUserId(), userId); + conditions.add(new Condition.Permission(User.class, userId, baseClass).excludeGroups()); + } + } + + return storage.getObjects(baseClass, new Request(new Columns.All(), Condition.merge(conditions))); + } - return deviceManager.getItems(result); } @Path("{id}/accumulators") @PUT - public Response updateAccumulators(DeviceAccumulators entity) throws SQLException { - if (!Context.getPermissionsManager().getUserAdmin(getUserId())) { - Context.getPermissionsManager().checkManager(getUserId()); - Context.getPermissionsManager().checkPermission(Device.class, getUserId(), entity.getDeviceId()); + public Response updateAccumulators(DeviceAccumulators entity) throws StorageException { + if (permissionsService.notAdmin(getUserId())) { + permissionsService.checkManager(getUserId()); + permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId()); } - Context.getDeviceManager().resetDeviceAccumulators(entity); + + Position position = storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.LatestPositions(entity.getDeviceId()))); + if (position != null) { + if (entity.getTotalDistance() != null) { + position.getAttributes().put(Position.KEY_TOTAL_DISTANCE, entity.getTotalDistance()); + } + if (entity.getHours() != null) { + position.getAttributes().put(Position.KEY_HOURS, entity.getHours()); + } + position.setId(storage.addObject(position, new Request(new Columns.Exclude("id")))); + + Device device = new Device(); + device.setId(position.getDeviceId()); + device.setPositionId(position.getId()); + storage.updateObject(device, new Request( + new Columns.Include("positionId"), + new Condition.Equals("id", device.getId()))); + + try { + cacheManager.addDevice(position.getDeviceId()); + cacheManager.updatePosition(position); + connectionManager.updatePosition(true, position); + } finally { + cacheManager.removeDevice(position.getDeviceId()); + } + } else { + throw new IllegalArgumentException(); + } + LogAction.resetDeviceAccumulators(getUserId(), entity.getDeviceId()); return Response.noContent().build(); } + @Path("{id}/image") + @POST + @Consumes("image/*") + public Response uploadImage( + @PathParam("id") long deviceId, File file, + @HeaderParam(HttpHeaders.CONTENT_TYPE) String type) throws StorageException, IOException { + + Device device = storage.getObject(Device.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("id", deviceId), + new Condition.Permission(User.class, getUserId(), Device.class)))); + if (device != null) { + String name = "device"; + String extension = type.substring("image/".length()); + try (var input = new FileInputStream(file); + var output = mediaManager.createFileStream(device.getUniqueId(), name, extension)) { + input.transferTo(output); + } + return Response.ok(name + "." + extension).build(); + } + return Response.status(Response.Status.NOT_FOUND).build(); + } + } diff --git a/src/main/java/org/traccar/api/resource/EventResource.java b/src/main/java/org/traccar/api/resource/EventResource.java index 34e4a94ce..afdaf52b5 100644 --- a/src/main/java/org/traccar/api/resource/EventResource.java +++ b/src/main/java/org/traccar/api/resource/EventResource.java @@ -15,7 +15,13 @@ */ package org.traccar.api.resource; -import java.sql.SQLException; +import org.traccar.api.BaseResource; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -26,32 +32,20 @@ import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.traccar.Context; -import org.traccar.api.BaseResource; -import org.traccar.model.Event; -import org.traccar.model.Geofence; -import org.traccar.model.Maintenance; - @Path("events") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - public class EventResource extends BaseResource { @Path("{id}") @GET - public Event get(@PathParam("id") long id) throws SQLException { - Event event = Context.getDataManager().getObject(Event.class, id); + public Event get(@PathParam("id") long id) throws StorageException { + Event event = storage.getObject(Event.class, new Request( + new Columns.All(), new Condition.Equals("id", id))); if (event == null) { throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build()); } - Context.getPermissionsManager().checkDevice(getUserId(), event.getDeviceId()); - if (event.getGeofenceId() != 0) { - Context.getPermissionsManager().checkPermission(Geofence.class, getUserId(), event.getGeofenceId()); - } - if (event.getMaintenanceId() != 0) { - Context.getPermissionsManager().checkPermission(Maintenance.class, getUserId(), event.getMaintenanceId()); - } + permissionsService.checkPermission(Device.class, getUserId(), event.getDeviceId()); return event; } diff --git a/src/main/java/org/traccar/api/resource/NotificationResource.java b/src/main/java/org/traccar/api/resource/NotificationResource.java index 9631a52b7..2e4ad12f3 100644 --- a/src/main/java/org/traccar/api/resource/NotificationResource.java +++ b/src/main/java/org/traccar/api/resource/NotificationResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,18 @@ */ package org.traccar.api.resource; -import java.util.Collection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.api.ExtendedObjectResource; +import org.traccar.model.Event; +import org.traccar.model.Notification; +import org.traccar.model.Typed; +import org.traccar.model.User; +import org.traccar.notification.MessageException; +import org.traccar.notification.NotificatorManager; +import org.traccar.storage.StorageException; +import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -25,20 +35,22 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; - -import org.traccar.Context; -import org.traccar.api.ExtendedObjectResource; -import org.traccar.model.Event; -import org.traccar.model.Notification; -import org.traccar.model.Typed; -import org.traccar.notification.MessageException; - +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; @Path("notifications") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class NotificationResource extends ExtendedObjectResource<Notification> { + private static final Logger LOGGER = LoggerFactory.getLogger(NotificationResource.class); + + @Inject + private NotificatorManager notificatorManager; + public NotificationResource() { super(Notification.class); } @@ -46,21 +58,32 @@ public class NotificationResource extends ExtendedObjectResource<Notification> { @GET @Path("types") public Collection<Typed> get() { - return Context.getNotificationManager().getAllNotificationTypes(); + List<Typed> types = new LinkedList<>(); + Field[] fields = Event.class.getDeclaredFields(); + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers()) && field.getName().startsWith("TYPE_")) { + try { + types.add(new Typed(field.get(null).toString())); + } catch (IllegalArgumentException | IllegalAccessException error) { + LOGGER.warn("Get event types error", error); + } + } + } + return types; } @GET @Path("notificators") public Collection<Typed> getNotificators() { - return Context.getNotificatorManager().getAllNotificatorTypes(); + return notificatorManager.getAllNotificatorTypes(); } @POST @Path("test") - public Response testMessage() throws MessageException, InterruptedException { - for (Typed method : Context.getNotificatorManager().getAllNotificatorTypes()) { - Context.getNotificatorManager() - .getNotificator(method.getType()).sendSync(getUserId(), new Event("test", 0), null); + public Response testMessage() throws MessageException, InterruptedException, StorageException { + User user = permissionsService.getUser(getUserId()); + for (Typed method : notificatorManager.getAllNotificatorTypes()) { + notificatorManager.getNotificator(method.getType()).send(user, new Event("test", 0), null); } return Response.noContent().build(); } @@ -68,8 +91,9 @@ public class NotificationResource extends ExtendedObjectResource<Notification> { @POST @Path("test/{notificator}") public Response testMessage(@PathParam("notificator") String notificator) - throws MessageException, InterruptedException { - Context.getNotificatorManager().getNotificator(notificator).sendSync(getUserId(), new Event("test", 0), null); + throws MessageException, InterruptedException, StorageException { + User user = permissionsService.getUser(getUserId()); + notificatorManager.getNotificator(notificator).send(user, new Event("test", 0), null); return Response.noContent().build(); } diff --git a/src/main/java/org/traccar/api/resource/PasswordResource.java b/src/main/java/org/traccar/api/resource/PasswordResource.java index 1868a6191..2d87a8665 100644 --- a/src/main/java/org/traccar/api/resource/PasswordResource.java +++ b/src/main/java/org/traccar/api/resource/PasswordResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,18 @@ */ package org.traccar.api.resource; -import org.apache.velocity.VelocityContext; -import org.traccar.Context; import org.traccar.api.BaseResource; +import org.traccar.api.signature.TokenManager; +import org.traccar.mail.MailManager; import org.traccar.model.User; -import org.traccar.notification.NotificationMessage; import org.traccar.notification.TextTemplateFormatter; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; import javax.annotation.security.PermitAll; +import javax.inject.Inject; import javax.mail.MessagingException; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; @@ -31,33 +35,35 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import java.sql.SQLException; -import java.util.UUID; +import java.io.IOException; +import java.security.GeneralSecurityException; @Path("password") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public class PasswordResource extends BaseResource { - private static final String PASSWORD_RESET_TOKEN = "passwordToken"; + @Inject + private MailManager mailManager; + + @Inject + private TokenManager tokenManager; + + @Inject + private TextTemplateFormatter textTemplateFormatter; @Path("reset") @PermitAll @POST - public Response reset(@FormParam("email") String email) throws SQLException, MessagingException { - for (long userId : Context.getUsersManager().getAllItems()) { - User user = Context.getUsersManager().getById(userId); - if (email.equals(user.getEmail())) { - String token = UUID.randomUUID().toString().replaceAll("-", ""); - user.set(PASSWORD_RESET_TOKEN, token); - Context.getUsersManager().updateItem(user); - VelocityContext velocityContext = TextTemplateFormatter.prepareContext(null); - velocityContext.put("token", token); - NotificationMessage fullMessage = - TextTemplateFormatter.formatMessage(velocityContext, "passwordReset", "full"); - Context.getMailManager().sendMessage(userId, fullMessage.getSubject(), fullMessage.getBody()); - break; - } + public Response reset(@FormParam("email") String email) + throws StorageException, MessagingException, GeneralSecurityException, IOException { + + User user = storage.getObject(User.class, new Request( + new Columns.All(), new Condition.Equals("email", email))); + if (user != null) { + var velocityContext = textTemplateFormatter.prepareContext(permissionsService.getServer(), user); + var fullMessage = textTemplateFormatter.formatMessage(velocityContext, "passwordReset", "full"); + mailManager.sendMessage(user, fullMessage.getSubject(), fullMessage.getBody()); } return Response.ok().build(); } @@ -66,15 +72,18 @@ public class PasswordResource extends BaseResource { @PermitAll @POST public Response update( - @FormParam("token") String token, @FormParam("password") String password) throws SQLException { - for (long userId : Context.getUsersManager().getAllItems()) { - User user = Context.getUsersManager().getById(userId); - if (token.equals(user.getString(PASSWORD_RESET_TOKEN))) { - user.getAttributes().remove(PASSWORD_RESET_TOKEN); - user.setPassword(password); - Context.getUsersManager().updateItem(user); - return Response.ok().build(); - } + @FormParam("token") String token, @FormParam("password") String password) + throws StorageException, GeneralSecurityException, IOException { + + long userId = tokenManager.verifyToken(token); + User user = storage.getObject(User.class, new Request( + new Columns.All(), new Condition.Equals("id", userId))); + if (user != null) { + user.setPassword(password); + storage.updateObject(user, new Request( + new Columns.Include("hashedPassword", "salt"), + new Condition.Equals("id", userId))); + return Response.ok().build(); } return Response.status(Response.Status.NOT_FOUND).build(); } diff --git a/src/main/java/org/traccar/api/resource/PermissionsResource.java b/src/main/java/org/traccar/api/resource/PermissionsResource.java index 54d3964b6..d35cb98bb 100644 --- a/src/main/java/org/traccar/api/resource/PermissionsResource.java +++ b/src/main/java/org/traccar/api/resource/PermissionsResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,12 +16,14 @@ */ package org.traccar.api.resource; -import java.sql.SQLException; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Set; +import org.traccar.api.BaseResource; +import org.traccar.helper.LogAction; +import org.traccar.model.Permission; +import org.traccar.model.UserRestrictions; +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.StorageException; +import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.POST; @@ -30,33 +32,24 @@ import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; - -import org.traccar.Context; -import org.traccar.api.BaseResource; -import org.traccar.helper.LogAction; -import org.traccar.model.Device; -import org.traccar.model.Permission; -import org.traccar.model.User; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Set; @Path("permissions") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class PermissionsResource extends BaseResource { - private void checkPermission(Permission permission, boolean link) { - if (!link && permission.getOwnerClass().equals(User.class) - && permission.getPropertyClass().equals(Device.class)) { - if (getUserId() != permission.getOwnerId()) { - Context.getPermissionsManager().checkUser(getUserId(), permission.getOwnerId()); - } else { - Context.getPermissionsManager().checkAdmin(getUserId()); - } - } else { - Context.getPermissionsManager().checkPermission( - permission.getOwnerClass(), getUserId(), permission.getOwnerId()); + @Inject + private CacheManager cacheManager; + + private void checkPermission(Permission permission) throws StorageException { + if (permissionsService.notAdmin(getUserId())) { + permissionsService.checkPermission(permission.getOwnerClass(), getUserId(), permission.getOwnerId()); + permissionsService.checkPermission(permission.getOwnerClass(), getUserId(), permission.getOwnerId()); } - Context.getPermissionsManager().checkPermission( - permission.getPropertyClass(), getUserId(), permission.getPropertyId()); } private void checkPermissionTypes(List<LinkedHashMap<String, Long>> entities) { @@ -71,49 +64,51 @@ public class PermissionsResource extends BaseResource { @Path("bulk") @POST - public Response add(List<LinkedHashMap<String, Long>> entities) throws SQLException, ClassNotFoundException { - Context.getPermissionsManager().checkReadonly(getUserId()); + public Response add(List<LinkedHashMap<String, Long>> entities) throws StorageException, ClassNotFoundException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getReadonly); checkPermissionTypes(entities); for (LinkedHashMap<String, Long> entity: entities) { Permission permission = new Permission(entity); - checkPermission(permission, true); - Context.getDataManager().linkObject(permission.getOwnerClass(), permission.getOwnerId(), - permission.getPropertyClass(), permission.getPropertyId(), true); - LogAction.link(getUserId(), permission.getOwnerClass(), permission.getOwnerId(), + checkPermission(permission); + storage.addPermission(permission); + cacheManager.invalidatePermission( + true, + permission.getOwnerClass(), permission.getOwnerId(), + permission.getPropertyClass(), permission.getPropertyId()); + LogAction.link(getUserId(), + permission.getOwnerClass(), permission.getOwnerId(), permission.getPropertyClass(), permission.getPropertyId()); - } - if (!entities.isEmpty()) { - Context.getPermissionsManager().refreshPermissions(new Permission(entities.get(0))); } return Response.noContent().build(); } @POST - public Response add(LinkedHashMap<String, Long> entity) throws SQLException, ClassNotFoundException { + public Response add(LinkedHashMap<String, Long> entity) throws StorageException, ClassNotFoundException { return add(Collections.singletonList(entity)); } @DELETE @Path("bulk") - public Response remove(List<LinkedHashMap<String, Long>> entities) throws SQLException, ClassNotFoundException { - Context.getPermissionsManager().checkReadonly(getUserId()); + public Response remove(List<LinkedHashMap<String, Long>> entities) throws StorageException, ClassNotFoundException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getReadonly); checkPermissionTypes(entities); for (LinkedHashMap<String, Long> entity: entities) { Permission permission = new Permission(entity); - checkPermission(permission, false); - Context.getDataManager().linkObject(permission.getOwnerClass(), permission.getOwnerId(), - permission.getPropertyClass(), permission.getPropertyId(), false); - LogAction.unlink(getUserId(), permission.getOwnerClass(), permission.getOwnerId(), + checkPermission(permission); + storage.removePermission(permission); + cacheManager.invalidatePermission( + true, + permission.getOwnerClass(), permission.getOwnerId(), + permission.getPropertyClass(), permission.getPropertyId()); + LogAction.unlink(getUserId(), + permission.getOwnerClass(), permission.getOwnerId(), permission.getPropertyClass(), permission.getPropertyId()); - } - if (!entities.isEmpty()) { - Context.getPermissionsManager().refreshPermissions(new Permission(entities.get(0))); } return Response.noContent().build(); } @DELETE - public Response remove(LinkedHashMap<String, Long> entity) throws SQLException, ClassNotFoundException { + public Response remove(LinkedHashMap<String, Long> entity) throws StorageException, ClassNotFoundException { return remove(Collections.singletonList(entity)); } diff --git a/src/main/java/org/traccar/api/resource/PositionResource.java b/src/main/java/org/traccar/api/resource/PositionResource.java index 998d59706..042dd1e23 100644 --- a/src/main/java/org/traccar/api/resource/PositionResource.java +++ b/src/main/java/org/traccar/api/resource/PositionResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,20 +15,32 @@ */ package org.traccar.api.resource; -import org.traccar.Context; import org.traccar.api.BaseResource; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; import org.traccar.model.Position; +import org.traccar.model.UserRestrictions; +import org.traccar.reports.CsvExportProvider; +import org.traccar.reports.GpxExportProvider; +import org.traccar.reports.KmlExportProvider; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; +import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; -import java.sql.SQLException; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.StreamingOutput; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.List; @@ -37,29 +49,95 @@ import java.util.List; @Consumes(MediaType.APPLICATION_JSON) public class PositionResource extends BaseResource { + @Inject + private KmlExportProvider kmlExportProvider; + + @Inject + private CsvExportProvider csvExportProvider; + + @Inject + private GpxExportProvider gpxExportProvider; + @GET public Collection<Position> getJson( @QueryParam("deviceId") long deviceId, @QueryParam("id") List<Long> positionIds, @QueryParam("from") Date from, @QueryParam("to") Date to) - throws SQLException { + throws StorageException { if (!positionIds.isEmpty()) { - ArrayList<Position> positions = new ArrayList<>(); - for (Long positionId : positionIds) { - Position position = Context.getDataManager().getObject(Position.class, positionId); - Context.getPermissionsManager().checkDevice(getUserId(), position.getDeviceId()); + var positions = new ArrayList<Position>(); + for (long positionId : positionIds) { + Position position = storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.Equals("id", positionId))); + permissionsService.checkPermission(Device.class, getUserId(), position.getDeviceId()); positions.add(position); } return positions; - } else if (deviceId == 0) { - return Context.getDeviceManager().getInitialState(getUserId()); - } else { - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); + } else if (deviceId > 0) { + permissionsService.checkPermission(Device.class, getUserId(), deviceId); if (from != null && to != null) { - return Context.getDataManager().getPositions(deviceId, from, to); + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); + return PositionUtil.getPositions(storage, deviceId, from, to); } else { - return Collections.singleton(Context.getDeviceManager().getLastPosition(deviceId)); + return storage.getObjects(Position.class, new Request( + new Columns.All(), new Condition.LatestPositions(deviceId))); } + } else { + return PositionUtil.getLatestPositions(storage, getUserId()); } } + @Path("kml") + @GET + @Produces("application/vnd.google-earth.kml+xml") + public Response getKml( + @QueryParam("deviceId") long deviceId, + @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + StreamingOutput stream = output -> { + try { + kmlExportProvider.generate(output, deviceId, from, to); + } catch (StorageException e) { + throw new WebApplicationException(e); + } + }; + return Response.ok(stream) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=positions.kml").build(); + } + + @Path("csv") + @GET + @Produces("text/csv") + public Response getCsv( + @QueryParam("deviceId") long deviceId, + @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + StreamingOutput stream = output -> { + try { + csvExportProvider.generate(output, deviceId, from, to); + } catch (StorageException e) { + throw new WebApplicationException(e); + } + }; + return Response.ok(stream) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=positions.csv").build(); + } + + @Path("gpx") + @GET + @Produces("application/gpx+xml") + public Response getGpx( + @QueryParam("deviceId") long deviceId, + @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + StreamingOutput stream = output -> { + try { + gpxExportProvider.generate(output, deviceId, from, to); + } catch (StorageException e) { + throw new WebApplicationException(e); + } + }; + return Response.ok(stream) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=positions.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 7347bfd64..70177dd4d 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,41 +16,47 @@ */ package org.traccar.api.resource; -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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.api.BaseResource; +import org.traccar.mail.MailManager; +import org.traccar.helper.LogAction; +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.model.User; +import org.traccar.model.UserRestrictions; +import org.traccar.reports.EventsReportProvider; +import org.traccar.reports.RouteReportProvider; +import org.traccar.reports.StopsReportProvider; +import org.traccar.reports.SummaryReportProvider; +import org.traccar.reports.TripsReportProvider; +import org.traccar.reports.model.StopReportItem; +import org.traccar.reports.model.SummaryReportItem; +import org.traccar.reports.model.TripReportItem; +import org.traccar.storage.StorageException; import javax.activation.DataHandler; +import javax.inject.Inject; import javax.mail.MessagingException; import javax.mail.internet.MimeBodyPart; import javax.mail.util.ByteArrayDataSource; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.api.BaseResource; -import org.traccar.helper.LogAction; -import org.traccar.model.Event; -import org.traccar.model.Position; -import org.traccar.reports.Events; -import org.traccar.reports.Summary; -import org.traccar.reports.Trips; -import org.traccar.reports.model.StopReport; -import org.traccar.reports.model.SummaryReport; -import org.traccar.reports.model.TripReport; -import org.traccar.reports.Route; -import org.traccar.reports.Stops; +import javax.ws.rs.core.StreamingOutput; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Date; +import java.util.List; @Path("reports") @Produces(MediaType.APPLICATION_JSON) @@ -59,155 +65,267 @@ public class ReportResource extends BaseResource { private static final Logger LOGGER = LoggerFactory.getLogger(ReportResource.class); - private static final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; - private static final String CONTENT_DISPOSITION_VALUE_XLSX = "attachment; filename=report.xlsx"; + private static final String EXCEL = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + + @Inject + private EventsReportProvider eventsReportProvider; + + @Inject + private RouteReportProvider routeReportProvider; + + @Inject + private StopsReportProvider stopsReportProvider; + + @Inject + private SummaryReportProvider summaryReportProvider; + + @Inject + private TripsReportProvider tripsReportProvider; + + @Inject + private MailManager mailManager; private interface ReportExecutor { - void execute(ByteArrayOutputStream stream) throws SQLException, IOException; + void execute(OutputStream stream) throws StorageException, IOException; } private Response executeReport( - long userId, boolean mail, ReportExecutor executor) throws SQLException, IOException { - final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + long userId, boolean mail, ReportExecutor executor) { if (mail) { new Thread(() -> { try { + var stream = new ByteArrayOutputStream(); executor.execute(stream); MimeBodyPart attachment = new MimeBodyPart(); - attachment.setFileName("report.xlsx"); attachment.setDataHandler(new DataHandler(new ByteArrayDataSource( stream.toByteArray(), "application/octet-stream"))); - Context.getMailManager().sendMessage( - userId, "Report", "The report is in the attachment.", attachment); - } catch (SQLException | IOException | MessagingException e) { + User user = permissionsService.getUser(userId); + mailManager.sendMessage(user, "Report", "The report is in the attachment.", attachment); + } catch (StorageException | IOException | MessagingException e) { LOGGER.warn("Report failed", e); } }).start(); return Response.noContent().build(); } else { - executor.execute(stream); - return Response.ok(stream.toByteArray()) - .header(HttpHeaders.CONTENT_DISPOSITION, CONTENT_DISPOSITION_VALUE_XLSX).build(); + StreamingOutput stream = output -> { + try { + executor.execute(output); + } catch (StorageException e) { + throw new WebApplicationException(e); + } + }; + return Response.ok(stream) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=report.xlsx").build(); } } @Path("route") @GET public Collection<Position> getRoute( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds); - return Route.getObjects(getUserId(), deviceIds, groupIds, from, to); + return routeReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); } @Path("route") @GET - @Produces(XLSX) + @Produces(EXCEL) public Response getRouteExcel( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) - throws SQLException, IOException { + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("mail") boolean mail) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds); - Route.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); + routeReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } + @Path("route/{type:xlsx|mail}") + @GET + @Produces(EXCEL) + public Response getRouteExcel( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @PathParam("type") String type) throws StorageException { + return getRouteExcel(deviceIds, groupIds, from, to, type.equals("mail")); + } + @Path("events") @GET public Collection<Event> getEvents( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("type") final List<String> types, - @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("type") List<String> types, + @QueryParam("from") Date from, + @QueryParam("to") Date to) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds); - return Events.getObjects(getUserId(), deviceIds, groupIds, types, from, to); + return eventsReportProvider.getObjects(getUserId(), deviceIds, groupIds, types, from, to); } @Path("events") @GET - @Produces(XLSX) + @Produces(EXCEL) public Response getEventsExcel( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("type") final List<String> types, - @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) - throws SQLException, IOException { + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("type") List<String> types, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("mail") boolean mail) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds); - Events.getExcel(stream, getUserId(), deviceIds, groupIds, types, from, to); + eventsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, types, from, to); }); } + @Path("events/{type:xlsx|mail}") + @GET + @Produces(EXCEL) + public Response getEventsExcel( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("type") List<String> types, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @PathParam("type") String type) throws StorageException { + return getEventsExcel(deviceIds, groupIds, types, from, to, type.equals("mail")); + } + @Path("summary") @GET - public Collection<SummaryReport> getSummary( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily) - throws SQLException { + public Collection<SummaryReportItem> getSummary( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("daily") boolean daily) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds); - return Summary.getObjects(getUserId(), deviceIds, groupIds, from, to, daily); + return summaryReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to, daily); } @Path("summary") @GET - @Produces(XLSX) + @Produces(EXCEL) public Response getSummaryExcel( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily, - @QueryParam("mail") boolean mail) - throws SQLException, IOException { + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("daily") boolean daily, + @QueryParam("mail") boolean mail) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds); - Summary.getExcel(stream, getUserId(), deviceIds, groupIds, from, to, daily); + summaryReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to, daily); }); } + @Path("summary/{type:xlsx|mail}") + @GET + @Produces(EXCEL) + public Response getSummaryExcel( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("daily") boolean daily, + @PathParam("type") String type) throws StorageException { + return getSummaryExcel(deviceIds, groupIds, from, to, daily, type.equals("mail")); + } + @Path("trips") @GET - @Produces(MediaType.APPLICATION_JSON) - public Collection<TripReport> getTrips( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { + public Collection<TripReportItem> getTrips( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds); - return Trips.getObjects(getUserId(), deviceIds, groupIds, from, to); + return tripsReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); } @Path("trips") @GET - @Produces(XLSX) + @Produces(EXCEL) public Response getTripsExcel( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) - throws SQLException, IOException { + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("mail") boolean mail) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds); - Trips.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); + tripsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } + @Path("trips/{type:xlsx|mail}") + @GET + @Produces(EXCEL) + public Response getTripsExcel( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @PathParam("type") String type) throws StorageException { + return getTripsExcel(deviceIds, groupIds, from, to, type.equals("mail")); + } + @Path("stops") @GET - @Produces(MediaType.APPLICATION_JSON) - public Collection<StopReport> getStops( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { + public Collection<StopReportItem> getStops( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds); - return Stops.getObjects(getUserId(), deviceIds, groupIds, from, to); + return stopsReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); } @Path("stops") @GET - @Produces(XLSX) + @Produces(EXCEL) public Response getStopsExcel( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) - throws SQLException, IOException { + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("mail") boolean mail) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds); - Stops.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); + stopsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } + @Path("stops/{type:xlsx|mail}") + @GET + @Produces(EXCEL) + public Response getStopsExcel( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @PathParam("type") String type) throws StorageException { + return getStopsExcel(deviceIds, groupIds, from, to, type.equals("mail")); + } + } diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java index 91488afff..4b7ee9189 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,23 @@ */ package org.traccar.api.resource; -import org.traccar.Context; import org.traccar.api.BaseResource; +import org.traccar.helper.model.UserUtil; +import org.traccar.mail.MailManager; +import org.traccar.geocoder.Geocoder; +import org.traccar.helper.Log; import org.traccar.helper.LogAction; import org.traccar.model.Server; +import org.traccar.model.User; +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; +import javax.annotation.Nullable; import javax.annotation.security.PermitAll; +import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.PUT; @@ -29,27 +40,52 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collection; +import java.util.TimeZone; @Path("server") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class ServerResource extends BaseResource { + @Inject + private CacheManager cacheManager; + + @Inject + private MailManager mailManager; + + @Inject + @Nullable + private Geocoder geocoder; + @PermitAll @GET - public Server get(@QueryParam("force") boolean force) throws SQLException { - if (force) { - return Context.getDataManager().getServer(); + public Server get() throws StorageException { + Server server = storage.getObject(Server.class, new Request(new Columns.All())); + server.setEmailEnabled(mailManager.getEmailEnabled()); + server.setGeocoderEnabled(geocoder != null); + User user = permissionsService.getUser(getUserId()); + if (user != null) { + if (user.getAdministrator()) { + server.setStorageSpace(Log.getStorageSpace()); + } } else { - return Context.getPermissionsManager().getServer(); + server.setNewServer(UserUtil.isEmpty(storage)); + } + if (user != null && user.getAdministrator()) { + server.setStorageSpace(Log.getStorageSpace()); } + return server; } @PUT - public Response update(Server entity) throws SQLException { - Context.getPermissionsManager().checkAdmin(getUserId()); - Context.getPermissionsManager().updateServer(entity); + public Response update(Server entity) throws StorageException { + permissionsService.checkAdmin(getUserId()); + storage.updateObject(entity, new Request( + new Columns.Exclude("id"), + new Condition.Equals("id", entity.getId()))); + cacheManager.updateOrInvalidate(true, entity); LogAction.edit(getUserId(), entity); return Response.ok(entity).build(); } @@ -57,11 +93,17 @@ public class ServerResource extends BaseResource { @Path("geocode") @GET public String geocode(@QueryParam("latitude") double latitude, @QueryParam("longitude") double longitude) { - if (Context.getGeocoder() != null) { - return Context.getGeocoder().getAddress(latitude, longitude, null); + if (geocoder != null) { + return geocoder.getAddress(latitude, longitude, null); } else { throw new RuntimeException("Reverse geocoding is not enabled"); } } + @Path("timezones") + @GET + public Collection<String> timezones() { + return Arrays.asList(TimeZone.getAvailableIDs()); + } + } diff --git a/src/main/java/org/traccar/api/resource/SessionResource.java b/src/main/java/org/traccar/api/resource/SessionResource.java index e3c5d457f..7025d5fa7 100644 --- a/src/main/java/org/traccar/api/resource/SessionResource.java +++ b/src/main/java/org/traccar/api/resource/SessionResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,20 @@ */ package org.traccar.api.resource; -import org.traccar.Context; import org.traccar.api.BaseResource; +import org.traccar.api.security.LoginService; +import org.traccar.api.signature.TokenManager; import org.traccar.helper.DataConverter; -import org.traccar.helper.ServletHelper; import org.traccar.helper.LogAction; +import org.traccar.helper.ServletHelper; import org.traccar.model.User; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; import javax.annotation.security.PermitAll; +import javax.inject.Inject; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; @@ -31,16 +37,18 @@ import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; - -import java.io.UnsupportedEncodingException; +import java.io.IOException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; -import java.sql.SQLException; +import java.security.GeneralSecurityException; +import java.util.Date; @Path("session") @Produces(MediaType.APPLICATION_JSON) @@ -51,60 +59,86 @@ public class SessionResource extends BaseResource { public static final String USER_COOKIE_KEY = "user"; public static final String PASS_COOKIE_KEY = "password"; - @javax.ws.rs.core.Context + @Inject + private LoginService loginService; + + @Inject + private TokenManager tokenManager; + + @Context private HttpServletRequest request; @PermitAll @GET - public User get(@QueryParam("token") String token) throws SQLException, UnsupportedEncodingException { + public User get(@QueryParam("token") String token) throws StorageException, IOException, GeneralSecurityException { + + if (token != null) { + User user = loginService.login(token); + if (user != null) { + request.getSession().setAttribute(USER_ID_KEY, user.getId()); + LogAction.login(user.getId(), ServletHelper.retrieveRemoteAddress(request)); + return user; + } + } + Long userId = (Long) request.getSession().getAttribute(USER_ID_KEY); if (userId == null) { + Cookie[] cookies = request.getCookies(); String email = null, password = null; if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().equals(USER_COOKIE_KEY)) { byte[] emailBytes = DataConverter.parseBase64( - URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII.name())); + URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII)); email = new String(emailBytes, StandardCharsets.UTF_8); } else if (cookie.getName().equals(PASS_COOKIE_KEY)) { byte[] passwordBytes = DataConverter.parseBase64( - URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII.name())); + URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII)); password = new String(passwordBytes, StandardCharsets.UTF_8); } } } if (email != null && password != null) { - User user = Context.getPermissionsManager().login(email, password); - if (user != null) { - userId = user.getId(); - request.getSession().setAttribute(USER_ID_KEY, userId); - } - } else if (token != null) { - User user = Context.getUsersManager().getUserByToken(token); + User user = loginService.login(email, password); if (user != null) { - userId = user.getId(); - request.getSession().setAttribute(USER_ID_KEY, userId); + request.getSession().setAttribute(USER_ID_KEY, user.getId()); + LogAction.login(user.getId(), ServletHelper.retrieveRemoteAddress(request)); + return user; } } - } - if (userId != null) { - Context.getPermissionsManager().checkUserEnabled(userId); - return Context.getPermissionsManager().getUser(userId); } else { - throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build()); + + User user = permissionsService.getUser(userId); + if (user != null) { + return user; + } + } + + throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build()); + } + + @Path("{id}") + @GET + public User get(@PathParam("id") long userId) throws StorageException { + permissionsService.checkAdmin(getUserId()); + User user = storage.getObject(User.class, new Request( + new Columns.All(), new Condition.Equals("id", userId))); + request.getSession().setAttribute(USER_ID_KEY, user.getId()); + LogAction.login(user.getId(), ServletHelper.retrieveRemoteAddress(request)); + return user; } @PermitAll @POST public User add( - @FormParam("email") String email, @FormParam("password") String password) throws SQLException { - User user = Context.getPermissionsManager().login(email, password); + @FormParam("email") String email, @FormParam("password") String password) throws StorageException { + User user = loginService.login(email, password); if (user != null) { request.getSession().setAttribute(USER_ID_KEY, user.getId()); - LogAction.login(user.getId()); + LogAction.login(user.getId(), ServletHelper.retrieveRemoteAddress(request)); return user; } else { LogAction.failedLogin(ServletHelper.retrieveRemoteAddress(request)); @@ -114,9 +148,16 @@ public class SessionResource extends BaseResource { @DELETE public Response remove() { - LogAction.logout(getUserId()); + LogAction.logout(getUserId(), ServletHelper.retrieveRemoteAddress(request)); request.getSession().removeAttribute(USER_ID_KEY); return Response.noContent().build(); } + @Path("token") + @POST + public String requestToken( + @FormParam("expiration") Date expiration) throws StorageException, GeneralSecurityException, IOException { + return tokenManager.generateToken(getUserId(), expiration); + } + } diff --git a/src/main/java/org/traccar/api/resource/StatisticsResource.java b/src/main/java/org/traccar/api/resource/StatisticsResource.java index 58073e7d1..1f2296f28 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,13 @@ */ package org.traccar.api.resource; -import org.traccar.Context; import org.traccar.api.BaseResource; import org.traccar.model.Statistics; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Order; +import org.traccar.storage.query.Request; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -25,7 +29,6 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; -import java.sql.SQLException; import java.util.Collection; import java.util.Date; @@ -36,9 +39,12 @@ public class StatisticsResource extends BaseResource { @GET public Collection<Statistics> get( - @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException { - Context.getPermissionsManager().checkAdmin(getUserId()); - return Context.getDataManager().getStatistics(from, to); + @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { + permissionsService.checkAdmin(getUserId()); + return storage.getObjects(Statistics.class, new Request( + new Columns.All(), + new Condition.Between("captureTime", "from", from, "to", to), + new Order("captureTime"))); } } diff --git a/src/main/java/org/traccar/api/resource/UserResource.java b/src/main/java/org/traccar/api/resource/UserResource.java index d54cc2382..e41ebbe61 100644 --- a/src/main/java/org/traccar/api/resource/UserResource.java +++ b/src/main/java/org/traccar/api/resource/UserResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,21 @@ */ package org.traccar.api.resource; -import org.traccar.Context; import org.traccar.api.BaseObjectResource; +import org.traccar.config.Config; import org.traccar.config.Keys; -import org.traccar.database.UsersManager; import org.traccar.helper.LogAction; +import org.traccar.helper.model.UserUtil; import org.traccar.model.ManagedUser; +import org.traccar.model.Permission; import org.traccar.model.User; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; import javax.annotation.security.PermitAll; +import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -32,63 +38,82 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import java.sql.SQLException; import java.util.Collection; import java.util.Date; -import java.util.Set; @Path("users") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class UserResource extends BaseObjectResource<User> { + @Inject + private Config config; + public UserResource() { super(User.class); } @GET - public Collection<User> get(@QueryParam("userId") long userId) throws SQLException { - UsersManager usersManager = Context.getUsersManager(); - Set<Long> result; - if (Context.getPermissionsManager().getUserAdmin(getUserId())) { - if (userId != 0) { - result = usersManager.getUserItems(userId); - } else { - result = usersManager.getAllItems(); - } - } else if (Context.getPermissionsManager().getUserManager(getUserId())) { - result = usersManager.getManagedItems(getUserId()); + public Collection<User> get(@QueryParam("userId") long userId) throws StorageException { + if (userId > 0) { + permissionsService.checkUser(getUserId(), userId); + return storage.getObjects(baseClass, new Request( + new Columns.All(), + new Condition.Permission(User.class, userId, ManagedUser.class).excludeGroups())); + } else if (permissionsService.notAdmin(getUserId())) { + return storage.getObjects(baseClass, new Request( + new Columns.All(), + new Condition.Permission(User.class, getUserId(), ManagedUser.class).excludeGroups())); } else { - throw new SecurityException("Admin or manager access required"); + return storage.getObjects(baseClass, new Request(new Columns.All())); } - return usersManager.getItems(result); } @Override @PermitAll @POST - public Response add(User entity) throws SQLException { - if (!Context.getPermissionsManager().getUserAdmin(getUserId())) { - Context.getPermissionsManager().checkUserUpdate(getUserId(), new User(), entity); - if (Context.getPermissionsManager().getUserManager(getUserId())) { - Context.getPermissionsManager().checkUserLimit(getUserId()); + public Response add(User entity) throws StorageException { + User currentUser = getUserId() > 0 ? permissionsService.getUser(getUserId()) : null; + if (currentUser == null || !currentUser.getAdministrator()) { + permissionsService.checkUserUpdate(getUserId(), new User(), entity); + if (currentUser != null && currentUser.getUserLimit() != 0) { + int userLimit = currentUser.getUserLimit(); + if (userLimit > 0) { + int userCount = storage.getObjects(baseClass, new Request( + new Columns.All(), + new Condition.Permission(User.class, getUserId(), ManagedUser.class).excludeGroups())) + .size(); + if (userCount >= userLimit) { + throw new SecurityException("Manager user limit reached"); + } + } } else { - Context.getPermissionsManager().checkRegistration(getUserId()); - entity.setDeviceLimit(Context.getConfig().getInteger(Keys.USERS_DEFAULT_DEVICE_LIMIT)); - int expirationDays = Context.getConfig().getInteger(Keys.USERS_DEFAULT_EXPIRATION_DAYS); + if (!permissionsService.getServer().getRegistration()) { + throw new SecurityException("Registration disabled"); + } + entity.setDeviceLimit(config.getInteger(Keys.USERS_DEFAULT_DEVICE_LIMIT)); + int expirationDays = config.getInteger(Keys.USERS_DEFAULT_EXPIRATION_DAYS); if (expirationDays > 0) { - entity.setExpirationTime( - new Date(System.currentTimeMillis() + (long) expirationDays * 24 * 3600 * 1000)); + entity.setExpirationTime(new Date(System.currentTimeMillis() + expirationDays * 86400000L)); } } } - Context.getUsersManager().addItem(entity); + + if (UserUtil.isEmpty(storage)) { + entity.setAdministrator(true); + } + + entity.setId(storage.addObject(entity, new Request(new Columns.Exclude("id")))); + storage.updateObject(entity, new Request( + new Columns.Include("hashedPassword", "salt"), + new Condition.Equals("id", entity.getId()))); + LogAction.create(getUserId(), entity); - if (Context.getPermissionsManager().getUserManager(getUserId())) { - Context.getDataManager().linkObject(User.class, getUserId(), ManagedUser.class, entity.getId(), true); + + if (currentUser != null && currentUser.getUserLimit() != 0) { + storage.addPermission(new Permission(User.class, getUserId(), ManagedUser.class, entity.getId())); LogAction.link(getUserId(), User.class, getUserId(), ManagedUser.class, entity.getId()); } - Context.getUsersManager().refreshUserItems(); return Response.ok(entity).build(); } diff --git a/src/main/java/org/traccar/api/security/LoginService.java b/src/main/java/org/traccar/api/security/LoginService.java new file mode 100644 index 000000000..88bafcfb5 --- /dev/null +++ b/src/main/java/org/traccar/api/security/LoginService.java @@ -0,0 +1,99 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.security; + +import org.traccar.api.signature.TokenManager; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.database.LdapProvider; +import org.traccar.model.User; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.security.GeneralSecurityException; + +@Singleton +public class LoginService { + + private final Storage storage; + private final TokenManager tokenManager; + private final LdapProvider ldapProvider; + + private final String serviceAccountToken; + private final boolean forceLdap; + + @Inject + public LoginService( + Config config, Storage storage, TokenManager tokenManager, @Nullable LdapProvider ldapProvider) { + this.storage = storage; + this.tokenManager = tokenManager; + this.ldapProvider = ldapProvider; + serviceAccountToken = config.getString(Keys.WEB_SERVICE_ACCOUNT_TOKEN); + forceLdap = config.getBoolean(Keys.LDAP_FORCE); + } + + public User login(String token) throws StorageException, GeneralSecurityException, IOException { + if (serviceAccountToken != null && serviceAccountToken.equals(token)) { + return new ServiceAccountUser(); + } + long userId = tokenManager.verifyToken(token); + User user = storage.getObject(User.class, new Request( + new Columns.All(), new Condition.Equals("id", userId))); + if (user != null) { + checkUserEnabled(user); + } + return user; + } + + public User login(String email, String password) throws StorageException { + email = email.trim(); + User user = storage.getObject(User.class, new Request( + new Columns.All(), + new Condition.Or( + new Condition.Equals("email", email), + new Condition.Equals("login", email)))); + if (user != null) { + if (ldapProvider != null && user.getLogin() != null && ldapProvider.login(user.getLogin(), password) + || !forceLdap && user.isPasswordValid(password)) { + checkUserEnabled(user); + return user; + } + } else { + if (ldapProvider != null && ldapProvider.login(email, password)) { + user = ldapProvider.getUser(email); + user.setId(storage.addObject(user, new Request(new Columns.Exclude("id")))); + checkUserEnabled(user); + return user; + } + } + return null; + } + + private void checkUserEnabled(User user) throws SecurityException { + if (user == null) { + throw new SecurityException("Unknown account"); + } + user.checkDisabled(); + } + +} diff --git a/src/main/java/org/traccar/api/security/PermissionsService.java b/src/main/java/org/traccar/api/security/PermissionsService.java new file mode 100644 index 000000000..4421572d7 --- /dev/null +++ b/src/main/java/org/traccar/api/security/PermissionsService.java @@ -0,0 +1,211 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.security; + +import com.google.inject.servlet.RequestScoped; +import org.traccar.model.BaseModel; +import org.traccar.model.Calendar; +import org.traccar.model.Command; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.model.GroupedModel; +import org.traccar.model.ManagedUser; +import org.traccar.model.ScheduledModel; +import org.traccar.model.Server; +import org.traccar.model.User; +import org.traccar.model.UserRestrictions; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import java.util.Objects; + +@RequestScoped +public class PermissionsService { + + private final Storage storage; + + private Server server; + private User user; + + @Inject + public PermissionsService(Storage storage) { + this.storage = storage; + } + + public Server getServer() throws StorageException { + if (server == null) { + server = storage.getObject( + Server.class, new Request(new Columns.All())); + } + return server; + } + + public User getUser(long userId) throws StorageException { + if (user == null && userId > 0) { + if (userId == ServiceAccountUser.ID) { + user = new ServiceAccountUser(); + } else { + user = storage.getObject( + User.class, new Request(new Columns.All(), new Condition.Equals("id", userId))); + } + } + return user; + } + + public boolean notAdmin(long userId) throws StorageException { + return !getUser(userId).getAdministrator(); + } + + public void checkAdmin(long userId) throws StorageException, SecurityException { + if (!getUser(userId).getAdministrator()) { + throw new SecurityException("Administrator access required"); + } + } + + public void checkManager(long userId) throws StorageException, SecurityException { + if (!getUser(userId).getAdministrator() && getUser(userId).getUserLimit() == 0) { + throw new SecurityException("Manager access required"); + } + } + + public interface CheckRestrictionCallback { + boolean denied(UserRestrictions userRestrictions); + } + + public void checkRestriction( + long userId, CheckRestrictionCallback callback) throws StorageException, SecurityException { + if (!getUser(userId).getAdministrator() + && (callback.denied(getServer()) || callback.denied(getUser(userId)))) { + throw new SecurityException("Operation restricted"); + } + } + + public void checkEdit(long userId, Class<?> clazz, boolean addition) throws StorageException, SecurityException { + if (!getUser(userId).getAdministrator()) { + boolean denied = false; + if (getServer().getReadonly() || getUser(userId).getReadonly()) { + denied = true; + } else if (clazz.equals(Device.class)) { + denied = getServer().getDeviceReadonly() || getUser(userId).getDeviceReadonly() + || addition && getUser(userId).getDeviceLimit() == 0; + if (!denied && addition && getUser(userId).getDeviceLimit() > 0) { + int deviceCount = storage.getObjects(Device.class, new Request( + new Columns.Include("id"), + new Condition.Permission(User.class, userId, Device.class))).size(); + denied = deviceCount >= getUser(userId).getDeviceLimit(); + } + } else if (clazz.equals(Command.class)) { + denied = getServer().getLimitCommands() || getUser(userId).getLimitCommands(); + } + if (denied) { + throw new SecurityException("Write access denied"); + } + } + } + + public void checkEdit(long userId, BaseModel object, boolean addition) throws StorageException, SecurityException { + if (!getUser(userId).getAdministrator()) { + checkEdit(userId, object.getClass(), addition); + if (object instanceof GroupedModel) { + GroupedModel after = ((GroupedModel) object); + if (after.getGroupId() > 0) { + GroupedModel before = null; + if (!addition) { + before = storage.getObject(after.getClass(), new Request( + new Columns.Include("groupId"), new Condition.Equals("id", object.getId()))); + } + if (before == null || before.getGroupId() != after.getGroupId()) { + checkPermission(Group.class, userId, after.getGroupId()); + } + } + } + if (object instanceof ScheduledModel) { + ScheduledModel after = ((ScheduledModel) object); + if (after.getCalendarId() > 0) { + ScheduledModel before = null; + if (!addition) { + before = storage.getObject(after.getClass(), new Request( + new Columns.Include("calendarId"), new Condition.Equals("id", object.getId()))); + } + if (before == null || before.getCalendarId() != after.getCalendarId()) { + checkPermission(Calendar.class, userId, after.getCalendarId()); + } + } + } + } + } + + public void checkUser(long userId, long managedUserId) throws StorageException, SecurityException { + if (userId != managedUserId && !getUser(userId).getAdministrator()) { + if (!getUser(userId).getManager() + || storage.getPermissions(User.class, userId, ManagedUser.class, managedUserId).isEmpty()) { + throw new SecurityException("User access denied"); + } + } + } + + public void checkUserUpdate(long userId, User before, User after) throws StorageException, SecurityException { + if (before.getAdministrator() != after.getAdministrator() + || before.getDeviceLimit() != after.getDeviceLimit() + || before.getUserLimit() != after.getUserLimit()) { + checkAdmin(userId); + } + User user = getUser(userId); + if (user != null && user.getExpirationTime() != null + && !Objects.equals(before.getExpirationTime(), after.getExpirationTime()) + && (after.getExpirationTime() == null + || user.getExpirationTime().compareTo(after.getExpirationTime()) < 0)) { + checkAdmin(userId); + } + if (before.getReadonly() != after.getReadonly() + || before.getDeviceReadonly() != after.getDeviceReadonly() + || before.getDisabled() != after.getDisabled() + || before.getLimitCommands() != after.getLimitCommands() + || before.getDisableReports() != after.getDisableReports() + || before.getFixedEmail() != after.getFixedEmail()) { + if (userId == after.getId()) { + checkAdmin(userId); + } else if (after.getId() > 0) { + checkUser(userId, after.getId()); + } else { + checkManager(userId); + } + } + if (before.getFixedEmail() && !before.getEmail().equals(after.getEmail())) { + checkAdmin(userId); + } + } + + public <T extends BaseModel> void checkPermission( + Class<T> clazz, long userId, long objectId) throws StorageException, SecurityException { + if (!getUser(userId).getAdministrator() && !(clazz.equals(User.class) && userId == objectId)) { + var object = storage.getObject(clazz, new Request( + new Columns.Include("id"), + new Condition.And( + new Condition.Equals("id", objectId), + new Condition.Permission( + User.class, userId, clazz.equals(User.class) ? ManagedUser.class : clazz)))); + if (object == null) { + throw new SecurityException(clazz.getSimpleName() + " access denied"); + } + } + } + +} diff --git a/src/main/java/org/traccar/api/SecurityRequestFilter.java b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java index 33b6b37df..94b6bbf05 100644 --- a/src/main/java/org/traccar/api/SecurityRequestFilter.java +++ b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,28 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.api; +package org.traccar.api.security; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.Main; import org.traccar.api.resource.SessionResource; import org.traccar.database.StatisticsManager; import org.traccar.helper.DataConverter; import org.traccar.model.User; +import org.traccar.storage.StorageException; import javax.annotation.security.PermitAll; +import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.WebApplicationException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; +import java.io.IOException; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; -import java.sql.SQLException; +import java.security.GeneralSecurityException; public class SecurityRequestFilter implements ContainerRequestFilter { @@ -43,6 +45,7 @@ public class SecurityRequestFilter implements ContainerRequestFilter { public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; public static final String BASIC_REALM = "Basic realm=\"api\""; + public static final String BEARER_PREFIX = "Bearer "; public static final String X_REQUESTED_WITH = "X-Requested-With"; public static final String XML_HTTP_REQUEST = "XMLHttpRequest"; @@ -55,12 +58,18 @@ public class SecurityRequestFilter implements ContainerRequestFilter { return null; } - @javax.ws.rs.core.Context + @Context private HttpServletRequest request; - @javax.ws.rs.core.Context + @Context private ResourceInfo resourceInfo; + @Inject + private LoginService loginService; + + @Inject + private StatisticsManager statisticsManager; + @Override public void filter(ContainerRequestContext requestContext) { @@ -76,13 +85,18 @@ public class SecurityRequestFilter implements ContainerRequestFilter { if (authHeader != null) { try { - String[] auth = decodeBasicAuth(authHeader); - User user = Context.getPermissionsManager().login(auth[0], auth[1]); + User user; + if (authHeader.startsWith(BEARER_PREFIX)) { + user = loginService.login(authHeader.substring(BEARER_PREFIX.length())); + } else { + String[] auth = decodeBasicAuth(authHeader); + user = loginService.login(auth[0], auth[1]); + } if (user != null) { - Main.getInjector().getInstance(StatisticsManager.class).registerRequest(user.getId()); + statisticsManager.registerRequest(user.getId()); securityContext = new UserSecurityContext(new UserPrincipal(user.getId())); } - } catch (SQLException e) { + } catch (StorageException | GeneralSecurityException | IOException e) { throw new WebApplicationException(e); } @@ -90,8 +104,7 @@ public class SecurityRequestFilter implements ContainerRequestFilter { Long userId = (Long) request.getSession().getAttribute(SessionResource.USER_ID_KEY); if (userId != null) { - Context.getPermissionsManager().checkUserEnabled(userId); - Main.getInjector().getInstance(StatisticsManager.class).registerRequest(userId); + statisticsManager.registerRequest(userId); securityContext = new UserSecurityContext(new UserPrincipal(userId)); } diff --git a/src/main/java/org/traccar/database/MaintenancesManager.java b/src/main/java/org/traccar/api/security/ServiceAccountUser.java index 4e266cb78..644142434 100644 --- a/src/main/java/org/traccar/database/MaintenancesManager.java +++ b/src/main/java/org/traccar/api/security/ServiceAccountUser.java @@ -1,6 +1,5 @@ /* - * Copyright 2018 Anton Tananaev (anton@traccar.org) - * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * Copyright 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.database; +package org.traccar.api.security; -import org.traccar.model.Maintenance; +import org.traccar.model.User; -public class MaintenancesManager extends ExtendedObjectManager<Maintenance> { +public class ServiceAccountUser extends User { - public MaintenancesManager(DataManager dataManager) { - super(dataManager, Maintenance.class); - } + public static final long ID = 9000000000000000000L; + public ServiceAccountUser() { + setId(ID); + setName("Service Account"); + setEmail("none"); + setAdministrator(true); + } } diff --git a/src/main/java/org/traccar/api/UserPrincipal.java b/src/main/java/org/traccar/api/security/UserPrincipal.java index 175bca391..18b84a0e1 100644 --- a/src/main/java/org/traccar/api/UserPrincipal.java +++ b/src/main/java/org/traccar/api/security/UserPrincipal.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.api; +package org.traccar.api.security; import java.security.Principal; diff --git a/src/main/java/org/traccar/api/UserSecurityContext.java b/src/main/java/org/traccar/api/security/UserSecurityContext.java index 55c0621bc..97df6b6c7 100644 --- a/src/main/java/org/traccar/api/UserSecurityContext.java +++ b/src/main/java/org/traccar/api/security/UserSecurityContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.api; +package org.traccar.api.security; import javax.ws.rs.core.SecurityContext; import java.security.Principal; public class UserSecurityContext implements SecurityContext { - private UserPrincipal principal; + private final UserPrincipal principal; public UserSecurityContext(UserPrincipal principal) { this.principal = principal; diff --git a/src/main/java/org/traccar/api/signature/CryptoManager.java b/src/main/java/org/traccar/api/signature/CryptoManager.java new file mode 100644 index 000000000..249d5bd97 --- /dev/null +++ b/src/main/java/org/traccar/api/signature/CryptoManager.java @@ -0,0 +1,103 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.signature; + +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +@Singleton +public class CryptoManager { + + private final Storage storage; + + private PublicKey publicKey; + private PrivateKey privateKey; + + @Inject + public CryptoManager(Storage storage) { + this.storage = storage; + } + + public byte[] sign(byte[] data) throws GeneralSecurityException, StorageException { + if (privateKey == null) { + initializeKeys(); + } + Signature signature = Signature.getInstance("SHA256withECDSA"); + signature.initSign(privateKey); + signature.update(data); + byte[] block = signature.sign(); + byte[] combined = new byte[1 + block.length + data.length]; + combined[0] = (byte) block.length; + System.arraycopy(block, 0, combined, 1, block.length); + System.arraycopy(data, 0, combined, 1 + block.length, data.length); + return combined; + } + + public byte[] verify(byte[] data) throws GeneralSecurityException, StorageException { + if (publicKey == null) { + initializeKeys(); + } + Signature signature = Signature.getInstance("SHA256withECDSA"); + signature.initVerify(publicKey); + int length = data[0]; + byte[] originalData = new byte[data.length - 1 - length]; + System.arraycopy(data, 1 + length, originalData, 0, originalData.length); + signature.update(originalData); + if (!signature.verify(data, 1, length)) { + throw new SecurityException("Invalid signature"); + } + return originalData; + } + + private void initializeKeys() throws StorageException, GeneralSecurityException { + KeystoreModel model = storage.getObject(KeystoreModel.class, new Request(new Columns.All())); + if (model != null) { + publicKey = KeyFactory.getInstance("EC") + .generatePublic(new X509EncodedKeySpec(model.getPublicKey())); + privateKey = KeyFactory.getInstance("EC") + .generatePrivate(new PKCS8EncodedKeySpec(model.getPrivateKey())); + } else { + KeyPairGenerator generator = KeyPairGenerator.getInstance("EC"); + generator.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom()); + KeyPair pair = generator.generateKeyPair(); + + publicKey = pair.getPublic(); + privateKey = pair.getPrivate(); + + model = new KeystoreModel(); + model.setPublicKey(publicKey.getEncoded()); + model.setPrivateKey(privateKey.getEncoded()); + storage.addObject(model, new Request(new Columns.Exclude("id"))); + } + } + +} diff --git a/src/main/java/org/traccar/api/signature/KeystoreModel.java b/src/main/java/org/traccar/api/signature/KeystoreModel.java new file mode 100644 index 000000000..7f3140e81 --- /dev/null +++ b/src/main/java/org/traccar/api/signature/KeystoreModel.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.signature; + +import org.traccar.model.BaseModel; +import org.traccar.storage.StorageName; + +@StorageName("tc_keystore") +public class KeystoreModel extends BaseModel { + + private byte[] publicKey; + + public byte[] getPublicKey() { + return publicKey; + } + + public void setPublicKey(byte[] publicKey) { + this.publicKey = publicKey; + } + + private byte[] privateKey; + + public byte[] getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(byte[] privateKey) { + this.privateKey = privateKey; + } + +} diff --git a/src/main/java/org/traccar/api/signature/TokenManager.java b/src/main/java/org/traccar/api/signature/TokenManager.java new file mode 100644 index 000000000..6a0d90b40 --- /dev/null +++ b/src/main/java/org/traccar/api/signature/TokenManager.java @@ -0,0 +1,77 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.signature; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.codec.binary.Base64; +import org.traccar.storage.StorageException; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +@Singleton +public class TokenManager { + + private static final int DEFAULT_EXPIRATION_DAYS = 7; + + private final ObjectMapper objectMapper; + private final CryptoManager cryptoManager; + + public static class Data { + @JsonProperty("u") + private long userId; + @JsonProperty("e") + private Date expiration; + } + + @Inject + public TokenManager(ObjectMapper objectMapper, CryptoManager cryptoManager) { + this.objectMapper = objectMapper; + this.cryptoManager = cryptoManager; + } + + public String generateToken(long userId) throws IOException, GeneralSecurityException, StorageException { + return generateToken(userId, null); + } + + public String generateToken( + long userId, Date expiration) throws IOException, GeneralSecurityException, StorageException { + Data data = new Data(); + data.userId = userId; + if (expiration != null) { + data.expiration = expiration; + } else { + data.expiration = new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(DEFAULT_EXPIRATION_DAYS)); + } + byte[] encoded = objectMapper.writeValueAsBytes(data); + return Base64.encodeBase64URLSafeString(cryptoManager.sign(encoded)); + } + + public long verifyToken(String token) throws IOException, GeneralSecurityException, StorageException { + byte[] encoded = cryptoManager.verify(Base64.decodeBase64(token)); + Data data = objectMapper.readValue(encoded, Data.class); + if (data.expiration.before(new Date())) { + throw new SecurityException("Token has expired"); + } + return data.userId; + } + +} diff --git a/src/main/java/org/traccar/broadcast/BroadcastInterface.java b/src/main/java/org/traccar/broadcast/BroadcastInterface.java new file mode 100644 index 000000000..673ebd8b8 --- /dev/null +++ b/src/main/java/org/traccar/broadcast/BroadcastInterface.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.broadcast; + +import org.traccar.model.BaseModel; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Position; + +public interface BroadcastInterface { + + default void updateDevice(boolean local, Device device) { + } + + default void updatePosition(boolean local, Position position) { + } + + default void updateEvent(boolean local, long userId, Event event) { + } + + default void updateCommand(boolean local, long deviceId) { + } + + default void invalidateObject(boolean local, Class<? extends BaseModel> clazz, long id) { + } + + default void invalidatePermission( + boolean local, + Class<? extends BaseModel> clazz1, long id1, + Class<? extends BaseModel> clazz2, long id2) { + } +} diff --git a/src/main/java/org/traccar/broadcast/BroadcastMessage.java b/src/main/java/org/traccar/broadcast/BroadcastMessage.java new file mode 100644 index 000000000..985848d04 --- /dev/null +++ b/src/main/java/org/traccar/broadcast/BroadcastMessage.java @@ -0,0 +1,85 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.broadcast; + +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Position; + +import java.util.Map; + +public class BroadcastMessage { + + private Device device; + + public Device getDevice() { + return device; + } + + public void setDevice(Device device) { + this.device = device; + } + + private Position position; + + public Position getPosition() { + return position; + } + + public void setPosition(Position position) { + this.position = position; + } + + private Long userId; + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + private Event event; + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + + private Long commandDeviceId; + + public Long getCommandDeviceId() { + return commandDeviceId; + } + + public void setCommandDeviceId(Long commandDeviceId) { + this.commandDeviceId = commandDeviceId; + } + + private Map<String, Long> changes; + + public Map<String, Long> getChanges() { + return changes; + } + + public void setChanges(Map<String, Long> changes) { + this.changes = changes; + } +} diff --git a/src/main/java/org/traccar/broadcast/BroadcastService.java b/src/main/java/org/traccar/broadcast/BroadcastService.java new file mode 100644 index 000000000..a86c43b5b --- /dev/null +++ b/src/main/java/org/traccar/broadcast/BroadcastService.java @@ -0,0 +1,23 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.broadcast; + +import org.traccar.LifecycleObject; + +public interface BroadcastService extends LifecycleObject, BroadcastInterface { + boolean singleInstance(); + void registerListener(BroadcastInterface listener); +} diff --git a/src/main/java/org/traccar/broadcast/MulticastBroadcastService.java b/src/main/java/org/traccar/broadcast/MulticastBroadcastService.java new file mode 100644 index 000000000..b1b66f1e3 --- /dev/null +++ b/src/main/java/org/traccar/broadcast/MulticastBroadcastService.java @@ -0,0 +1,200 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.broadcast; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.model.BaseModel; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Permission; +import org.traccar.model.Position; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.MulticastSocket; +import java.net.NetworkInterface; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class MulticastBroadcastService implements BroadcastService { + + private static final Logger LOGGER = LoggerFactory.getLogger(MulticastBroadcastService.class); + + private final ObjectMapper objectMapper; + + private final NetworkInterface networkInterface; + private final int port; + private final InetSocketAddress group; + + private DatagramSocket publisherSocket; + + private final ExecutorService service = Executors.newSingleThreadExecutor(); + private final byte[] receiverBuffer = new byte[4096]; + + private final Set<BroadcastInterface> listeners = new HashSet<>(); + + public MulticastBroadcastService(Config config, ObjectMapper objectMapper) throws IOException { + this.objectMapper = objectMapper; + port = config.getInteger(Keys.BROADCAST_PORT); + String interfaceName = config.getString(Keys.BROADCAST_INTERFACE); + if (interfaceName.indexOf('.') >= 0 || interfaceName.indexOf(':') >= 0) { + networkInterface = NetworkInterface.getByInetAddress(InetAddress.getByName(interfaceName)); + } else { + networkInterface = NetworkInterface.getByName(interfaceName); + } + InetAddress address = InetAddress.getByName(config.getString(Keys.BROADCAST_ADDRESS)); + group = new InetSocketAddress(address, port); + } + + @Override + public boolean singleInstance() { + return false; + } + + @Override + public void registerListener(BroadcastInterface listener) { + listeners.add(listener); + } + + @Override + public void updateDevice(boolean local, Device device) { + BroadcastMessage message = new BroadcastMessage(); + message.setDevice(device); + sendMessage(message); + } + + @Override + public void updatePosition(boolean local, Position position) { + BroadcastMessage message = new BroadcastMessage(); + message.setPosition(position); + sendMessage(message); + } + + @Override + public void updateEvent(boolean local, long userId, Event event) { + BroadcastMessage message = new BroadcastMessage(); + message.setUserId(userId); + message.setEvent(event); + sendMessage(message); + } + + @Override + public void updateCommand(boolean local, long deviceId) { + BroadcastMessage message = new BroadcastMessage(); + message.setCommandDeviceId(deviceId); + sendMessage(message); + } + + @Override + public void invalidateObject(boolean local, Class<? extends BaseModel> clazz, long id) { + BroadcastMessage message = new BroadcastMessage(); + message.setChanges(Map.of(Permission.getKey(clazz), id)); + sendMessage(message); + } + + @Override + public void invalidatePermission( + boolean local, + Class<? extends BaseModel> clazz1, long id1, + Class<? extends BaseModel> clazz2, long id2) { + BroadcastMessage message = new BroadcastMessage(); + message.setChanges(Map.of(Permission.getKey(clazz1), id1, Permission.getKey(clazz2), id2)); + sendMessage(message); + } + + private void sendMessage(BroadcastMessage message) { + try { + byte[] buffer = objectMapper.writeValueAsString(message).getBytes(StandardCharsets.UTF_8); + DatagramPacket packet = new DatagramPacket(buffer, buffer.length, group); + publisherSocket.send(packet); + } catch (IOException e) { + LOGGER.warn("Broadcast failed", e); + } + } + + private void handleMessage(BroadcastMessage message) { + if (message.getDevice() != null) { + listeners.forEach(listener -> listener.updateDevice(false, message.getDevice())); + } else if (message.getPosition() != null) { + listeners.forEach(listener -> listener.updatePosition(false, message.getPosition())); + } else if (message.getUserId() != null && message.getEvent() != null) { + listeners.forEach(listener -> listener.updateEvent(false, message.getUserId(), message.getEvent())); + } else if (message.getCommandDeviceId() != null) { + listeners.forEach(listener -> listener.updateCommand(false, message.getCommandDeviceId())); + } else if (message.getChanges() != null) { + var iterator = message.getChanges().entrySet().iterator(); + if (iterator.hasNext()) { + var first = iterator.next(); + if (iterator.hasNext()) { + var second = iterator.next(); + listeners.forEach(listener -> listener.invalidatePermission( + false, + Permission.getKeyClass(first.getKey()), first.getValue(), + Permission.getKeyClass(second.getKey()), second.getValue())); + } else { + listeners.forEach(listener -> listener.invalidateObject( + false, + Permission.getKeyClass(first.getKey()), first.getValue())); + } + } + } + } + + @Override + public void start() throws IOException { + service.submit(receiver); + } + + @Override + public void stop() { + service.shutdown(); + } + + private final Runnable receiver = new Runnable() { + @Override + public void run() { + try (MulticastSocket socket = new MulticastSocket(port)) { + socket.setNetworkInterface(networkInterface); + socket.joinGroup(group, networkInterface); + publisherSocket = socket; + while (!service.isShutdown()) { + DatagramPacket packet = new DatagramPacket(receiverBuffer, receiverBuffer.length); + socket.receive(packet); + if (networkInterface.inetAddresses().noneMatch(a -> a.equals(packet.getAddress()))) { + String data = new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8); + handleMessage(objectMapper.readValue(data, BroadcastMessage.class)); + } + } + publisherSocket = null; + socket.leaveGroup(group, networkInterface); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + +} diff --git a/src/main/java/org/traccar/broadcast/NullBroadcastService.java b/src/main/java/org/traccar/broadcast/NullBroadcastService.java new file mode 100644 index 000000000..f95037990 --- /dev/null +++ b/src/main/java/org/traccar/broadcast/NullBroadcastService.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.broadcast; + +public class NullBroadcastService implements BroadcastService { + + @Override + public boolean singleInstance() { + return true; + } + + @Override + public void registerListener(BroadcastInterface listener) { + } + + @Override + public void start() throws Exception { + } + + @Override + public void stop() throws Exception { + } +} diff --git a/src/main/java/org/traccar/config/Config.java b/src/main/java/org/traccar/config/Config.java index 815a6e86a..c73be6475 100644 --- a/src/main/java/org/traccar/config/Config.java +++ b/src/main/java/org/traccar/config/Config.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,19 @@ package org.traccar.config; import com.google.common.annotations.VisibleForTesting; +import com.google.inject.name.Named; +import org.traccar.helper.Log; +import javax.inject.Inject; +import javax.inject.Singleton; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.InvalidPropertiesFormatException; +import java.util.Objects; import java.util.Properties; +@Singleton public class Config { private final Properties properties = new Properties(); @@ -32,7 +38,8 @@ public class Config { public Config() { } - public Config(String file) throws IOException { + @Inject + public Config(@Named("configFile") String file) throws IOException { try { Properties mainProperties = new Properties(); try (InputStream inputStream = new FileInputStream(file)) { @@ -50,8 +57,14 @@ public class Config { useEnvironmentVariables = Boolean.parseBoolean(System.getenv("CONFIG_USE_ENVIRONMENT_VARIABLES")) || Boolean.parseBoolean(properties.getProperty("config.useEnvironmentVariables")); + + Log.setupLogger(this); } catch (InvalidPropertiesFormatException e) { + Log.setupDefaultLogger(); throw new RuntimeException("Configuration file is not a valid XML document", e); + } catch (Exception e) { + Log.setupDefaultLogger(); + throw e; } } @@ -59,8 +72,7 @@ public class Config { return hasKey(key.getKey()); } - @Deprecated - public boolean hasKey(String key) { + private boolean hasKey(String key) { return useEnvironmentVariables && System.getenv().containsKey(getEnvironmentVariableName(key)) || properties.containsKey(key); } @@ -90,12 +102,7 @@ public class Config { } public boolean getBoolean(ConfigKey<Boolean> key) { - return getBoolean(key.getKey()); - } - - @Deprecated - public boolean getBoolean(String key) { - return Boolean.parseBoolean(getString(key)); + return Boolean.parseBoolean(getString(key.getKey())); } public int getInteger(ConfigKey<Integer> key) { @@ -104,11 +111,7 @@ public class Config { return Integer.parseInt(value); } else { Integer defaultValue = key.getDefaultValue(); - if (defaultValue != null) { - return defaultValue; - } else { - return 0; - } + return Objects.requireNonNullElse(defaultValue, 0); } } @@ -127,11 +130,7 @@ public class Config { return Long.parseLong(value); } else { Long defaultValue = key.getDefaultValue(); - if (defaultValue != null) { - return defaultValue; - } else { - return 0; - } + return Objects.requireNonNullElse(defaultValue, 0L); } } @@ -141,11 +140,7 @@ public class Config { return Double.parseDouble(value); } else { Double defaultValue = key.getDefaultValue(); - if (defaultValue != null) { - return defaultValue; - } else { - return 0; - } + return Objects.requireNonNullElse(defaultValue, 0.0); } } diff --git a/src/main/java/org/traccar/config/ConfigKey.java b/src/main/java/org/traccar/config/ConfigKey.java index c046a46a5..b8151392c 100644 --- a/src/main/java/org/traccar/config/ConfigKey.java +++ b/src/main/java/org/traccar/config/ConfigKey.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,30 +15,34 @@ */ package org.traccar.config; +import java.util.HashSet; import java.util.List; +import java.util.Set; -public class ConfigKey<T> { +public abstract class ConfigKey<T> { private final String key; - private final List<KeyType> types; + private final Set<KeyType> types = new HashSet<>(); + private final Class<T> valueClass; private final T defaultValue; - ConfigKey(String key, List<KeyType> types) { - this(key, types, null); - } - - ConfigKey(String key, List<KeyType> types, T defaultValue) { + ConfigKey(String key, List<KeyType> types, Class<T> valueClass, T defaultValue) { this.key = key; - this.types = types; + this.types.addAll(types); + this.valueClass = valueClass; this.defaultValue = defaultValue; } - String getKey() { + public String getKey() { return key; } - public List<KeyType> getTypes() { - return types; + public boolean hasType(KeyType type) { + return types.contains(type); + } + + public Class<T> getValueClass() { + return valueClass; } public T getDefaultValue() { @@ -46,3 +50,48 @@ public class ConfigKey<T> { } } + +class StringConfigKey extends ConfigKey<String> { + StringConfigKey(String key, List<KeyType> types) { + super(key, types, String.class, null); + } + StringConfigKey(String key, List<KeyType> types, String defaultValue) { + super(key, types, String.class, defaultValue); + } +} + +class BooleanConfigKey extends ConfigKey<Boolean> { + BooleanConfigKey(String key, List<KeyType> types) { + super(key, types, Boolean.class, null); + } + BooleanConfigKey(String key, List<KeyType> types, Boolean defaultValue) { + super(key, types, Boolean.class, defaultValue); + } +} + +class IntegerConfigKey extends ConfigKey<Integer> { + IntegerConfigKey(String key, List<KeyType> types) { + super(key, types, Integer.class, null); + } + IntegerConfigKey(String key, List<KeyType> types, Integer defaultValue) { + super(key, types, Integer.class, defaultValue); + } +} + +class LongConfigKey extends ConfigKey<Long> { + LongConfigKey(String key, List<KeyType> types) { + super(key, types, Long.class, null); + } + LongConfigKey(String key, List<KeyType> types, Long defaultValue) { + super(key, types, Long.class, defaultValue); + } +} + +class DoubleConfigKey extends ConfigKey<Double> { + DoubleConfigKey(String key, List<KeyType> types) { + super(key, types, Double.class, null); + } + DoubleConfigKey(String key, List<KeyType> types, Double defaultValue) { + super(key, types, Double.class, defaultValue); + } +} diff --git a/src/main/java/org/traccar/config/ConfigSuffix.java b/src/main/java/org/traccar/config/ConfigSuffix.java index ede4c107d..aac3219c6 100644 --- a/src/main/java/org/traccar/config/ConfigSuffix.java +++ b/src/main/java/org/traccar/config/ConfigSuffix.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,11 @@ package org.traccar.config; import java.util.List; -public class ConfigSuffix<T> { +public abstract class ConfigSuffix<T> { - private final String keySuffix; - private final List<KeyType> types; - private final T defaultValue; - - ConfigSuffix(String keySuffix, List<KeyType> types) { - this(keySuffix, types, null); - } + protected final String keySuffix; + protected final List<KeyType> types; + protected final T defaultValue; ConfigSuffix(String keySuffix, List<KeyType> types, T defaultValue) { this.keySuffix = keySuffix; @@ -33,8 +29,71 @@ public class ConfigSuffix<T> { this.defaultValue = defaultValue; } - public ConfigKey<T> withPrefix(String prefix) { - return new ConfigKey<>(prefix + keySuffix, types, defaultValue); + public abstract ConfigKey<T> withPrefix(String prefix); + +} + +class StringConfigSuffix extends ConfigSuffix<String> { + StringConfigSuffix(String key, List<KeyType> types) { + super(key, types, null); + } + StringConfigSuffix(String key, List<KeyType> types, String defaultValue) { + super(key, types, defaultValue); + } + @Override + public ConfigKey<String> withPrefix(String prefix) { + return new StringConfigKey(prefix + keySuffix, types, defaultValue); + } +} + +class BooleanConfigSuffix extends ConfigSuffix<Boolean> { + BooleanConfigSuffix(String key, List<KeyType> types) { + super(key, types, null); + } + BooleanConfigSuffix(String key, List<KeyType> types, Boolean defaultValue) { + super(key, types, defaultValue); + } + @Override + public ConfigKey<Boolean> withPrefix(String prefix) { + return new BooleanConfigKey(prefix + keySuffix, types, defaultValue); + } +} + +class IntegerConfigSuffix extends ConfigSuffix<Integer> { + IntegerConfigSuffix(String key, List<KeyType> types) { + super(key, types, null); + } + IntegerConfigSuffix(String key, List<KeyType> types, Integer defaultValue) { + super(key, types, defaultValue); + } + @Override + public ConfigKey<Integer> withPrefix(String prefix) { + return new IntegerConfigKey(prefix + keySuffix, types, defaultValue); } +} +class LongConfigSuffix extends ConfigSuffix<Long> { + LongConfigSuffix(String key, List<KeyType> types) { + super(key, types, null); + } + LongConfigSuffix(String key, List<KeyType> types, Long defaultValue) { + super(key, types, defaultValue); + } + @Override + public ConfigKey<Long> withPrefix(String prefix) { + return new LongConfigKey(prefix + keySuffix, types, defaultValue); + } +} + +class DoubleConfigSuffix extends ConfigSuffix<Double> { + DoubleConfigSuffix(String key, List<KeyType> types) { + super(key, types, null); + } + DoubleConfigSuffix(String key, List<KeyType> types, Double defaultValue) { + super(key, types, defaultValue); + } + @Override + public ConfigKey<Double> withPrefix(String prefix) { + return new DoubleConfigKey(prefix + keySuffix, types, defaultValue); + } } diff --git a/src/main/java/org/traccar/config/KeyType.java b/src/main/java/org/traccar/config/KeyType.java index 57a95c9ec..46628f9fc 100644 --- a/src/main/java/org/traccar/config/KeyType.java +++ b/src/main/java/org/traccar/config/KeyType.java @@ -16,7 +16,7 @@ package org.traccar.config; public enum KeyType { - GLOBAL, + CONFIG, SERVER, USER, DEVICE, diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 1411e8a13..09662fc85 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 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package org.traccar.config; -import java.util.Collections; +import java.util.List; public final class Keys { @@ -25,17 +25,39 @@ public final class Keys { /** * Network interface for a the protocol. If not specified, server will bind all interfaces. */ - public static final ConfigSuffix<String> PROTOCOL_ADDRESS = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_ADDRESS = new StringConfigSuffix( ".address", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Port number for the protocol. Most protocols use TCP on the transport layer. Some protocols use UDP. Some * support both TCP and UDP. */ - public static final ConfigSuffix<Integer> PROTOCOL_PORT = new ConfigSuffix<>( + public static final ConfigSuffix<Integer> PROTOCOL_PORT = new IntegerConfigSuffix( ".port", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * List of devices for polling protocols. List should contain unique ids separated by commas. Used only for polling + * protocols. + */ + public static final ConfigSuffix<String> PROTOCOL_DEVICES = new StringConfigSuffix( + ".devices", + List.of(KeyType.CONFIG)); + + /** + * Polling interval in seconds. Used only for polling protocols. + */ + public static final ConfigSuffix<Long> PROTOCOL_INTERVAL = new LongConfigSuffix( + ".interval", + List.of(KeyType.CONFIG)); + + /** + * Enable SSL support for the protocol. Not all protocols support this. + */ + public static final ConfigSuffix<Boolean> PROTOCOL_SSL = new BooleanConfigSuffix( + ".ssl", + List.of(KeyType.CONFIG)); /** * Connection timeout value in seconds. Because sometimes there is no way to detect lost TCP connection old @@ -43,567 +65,676 @@ public final class Keys { * problems with establishing new connections when number of devices is high or devices data connections are * unstable. */ - public static final ConfigSuffix<Integer> PROTOCOL_TIMEOUT = new ConfigSuffix<>( + public static final ConfigSuffix<Integer> PROTOCOL_TIMEOUT = new IntegerConfigSuffix( ".timeout", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * Device password. Commonly used in some protocol for sending commands. + */ + public static final ConfigKey<String> DEVICE_PASSWORD = new StringConfigKey( + "devicePassword", + List.of(KeyType.DEVICE)); /** * Device password. Commonly used in some protocol for sending commands. */ - public static final ConfigSuffix<String> PROTOCOL_DEVICE_PASSWORD = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_DEVICE_PASSWORD = new StringConfigSuffix( ".devicePassword", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Default protocol mask to use. Currently used only by Skypatrol protocol. */ - public static final ConfigSuffix<Integer> PROTOCOL_MASK = new ConfigSuffix<>( + public static final ConfigSuffix<Integer> PROTOCOL_MASK = new IntegerConfigSuffix( ".mask", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Custom message length. Currently used only by H2 protocol for specifying binary message length. */ - public static final ConfigSuffix<Integer> PROTOCOL_MESSAGE_LENGTH = new ConfigSuffix<>( + public static final ConfigSuffix<Integer> PROTOCOL_MESSAGE_LENGTH = new IntegerConfigSuffix( ".messageLength", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Enable extended functionality for the protocol. The reason it's disabled by default is that not all devices * support it. */ - public static final ConfigSuffix<Boolean> PROTOCOL_EXTENDED = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_EXTENDED = new BooleanConfigSuffix( ".extended", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Decode string as UTF8 instead of ASCII. Only applicable for some protocols. */ - public static final ConfigSuffix<Boolean> PROTOCOL_UTF8 = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_UTF8 = new BooleanConfigSuffix( ".utf8", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Enable CAN decoding for the protocol. Similar to 'extended' configuration, it's not supported for some devices. */ - public static final ConfigSuffix<Boolean> PROTOCOL_CAN = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_CAN = new BooleanConfigSuffix( ".can", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Indicates whether server acknowledgement is required. Only applicable for some protocols. */ - public static final ConfigSuffix<Boolean> PROTOCOL_ACK = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_ACK = new BooleanConfigSuffix( ".ack", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG, KeyType.DEVICE), + false); /** * Ignore device reported fix time. Useful in case some devices report invalid time. Currently only available for * GL200 protocol. */ - public static final ConfigSuffix<Boolean> PROTOCOL_IGNORE_FIX_TIME = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_IGNORE_FIX_TIME = new BooleanConfigSuffix( ".ignoreFixTime", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Decode additional TK103 attributes. Not supported for some devices. */ - public static final ConfigSuffix<Boolean> PROTOCOL_DECODE_LOW = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_DECODE_LOW = new BooleanConfigSuffix( ".decodeLow", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Use long date format for Atrack protocol. */ - public static final ConfigSuffix<Boolean> PROTOCOL_LONG_DATE = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_LONG_DATE = new BooleanConfigSuffix( ".longDate", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Use decimal fuel value format for Atrack protocol. */ - public static final ConfigSuffix<Boolean> PROTOCOL_DECIMAL_FUEL = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_DECIMAL_FUEL = new BooleanConfigSuffix( ".decimalFuel", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Indicates additional custom attributes for Atrack protocol. */ - public static final ConfigSuffix<Boolean> PROTOCOL_CUSTOM = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_CUSTOM = new BooleanConfigSuffix( ".custom", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Custom format string for Atrack protocol. */ - public static final ConfigSuffix<String> PROTOCOL_FORM = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_FORM = new StringConfigSuffix( ".form", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Protocol configuration. Required for some devices for decoding incoming data. */ - public static final ConfigSuffix<String> PROTOCOL_CONFIG = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_CONFIG = new StringConfigSuffix( ".config", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Alarm mapping for Atrack protocol. */ - public static final ConfigSuffix<String> PROTOCOL_ALARM_MAP = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_ALARM_MAP = new StringConfigSuffix( ".alarmMap", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Indicates whether TAIP protocol should have prefixes for messages. */ - public static final ConfigSuffix<Boolean> PROTOCOL_PREFIX = new ConfigSuffix<>( + public static final ConfigSuffix<Boolean> PROTOCOL_PREFIX = new BooleanConfigSuffix( ".prefix", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Some devices require server address confirmation. Use this parameter to configure correct public address. */ - public static final ConfigSuffix<String> PROTOCOL_SERVER = new ConfigSuffix<>( + public static final ConfigSuffix<String> PROTOCOL_SERVER = new StringConfigSuffix( ".server", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * Protocol type for Suntech. + */ + public static final ConfigKey<Integer> PROTOCOL_TYPE = new IntegerConfigKey( + "suntech.protocolType", + List.of(KeyType.CONFIG, KeyType.DEVICE)); + + /** + * Suntech HBM configuration value. + */ + public static final ConfigKey<Boolean> PROTOCOL_HBM = new BooleanConfigKey( + "suntech.hbm", + List.of(KeyType.CONFIG, KeyType.DEVICE)); + + /** + * Format includes ADC value. + */ + public static final ConfigSuffix<Boolean> PROTOCOL_INCLUDE_ADC = new BooleanConfigSuffix( + ".includeAdc", + List.of(KeyType.CONFIG, KeyType.DEVICE)); /** - * Skip device connection session cache. Per protocol configuration. + * Format includes RPM value. */ - public static final ConfigSuffix<Boolean> PROTOCOL_IGNORE_SESSIONS_CACHE = new ConfigSuffix<>( - ".ignoreSessionCache", - Collections.singletonList(KeyType.GLOBAL)); + public static final ConfigSuffix<Boolean> PROTOCOL_INCLUDE_RPM = new BooleanConfigSuffix( + ".includeRpm", + List.of(KeyType.CONFIG, KeyType.DEVICE)); /** - * Skip device connection session cache. Global configuration. + * Format includes temperature values. */ - public static final ConfigKey<Boolean> DECODER_IGNORE_SESSIONS_CACHE = new ConfigKey<>( - "decoder.ignoreSessionCache", - Collections.singletonList(KeyType.GLOBAL)); + public static final ConfigSuffix<Boolean> PROTOCOL_INCLUDE_TEMPERATURE = new BooleanConfigSuffix( + ".includeTemp", + List.of(KeyType.CONFIG, KeyType.DEVICE)); + + /** + * Protocol format. Used by protocols that have configurable message format. + */ + public static final ConfigSuffix<String> PROTOCOL_FORMAT = new StringConfigSuffix( + ".format", + List.of(KeyType.DEVICE)); + + /** + * Protocol date format. Used by protocols that have configurable date format. + */ + public static final ConfigSuffix<String> PROTOCOL_DATE_FORMAT = new StringConfigSuffix( + ".dateFormat", + List.of(KeyType.DEVICE)); + + /** + * Device time zone. Most devices report UTC time, but in some cases devices report local time, so this parameter + * needs to be configured for the server to be able to decode the time correctly. + */ + public static final ConfigKey<String> DECODER_TIMEZONE = new StringConfigKey( + "decoder.timezone", + List.of(KeyType.CONFIG, KeyType.DEVICE)); + + /** + * ORBCOMM API access id. + */ + public static final ConfigKey<String> ORBCOMM_ACCESS_ID = new StringConfigKey( + "orbcomm.accessId", + List.of(KeyType.CONFIG)); + + /** + * ORBCOMM API password. + */ + public static final ConfigKey<String> ORBCOMM_PASSWORD = new StringConfigKey( + "orbcomm.password", + List.of(KeyType.CONFIG)); + + /** + * Use alternative format for the protocol of commands. + */ + public static final ConfigSuffix<Boolean> PROTOCOL_ALTERNATIVE = new BooleanConfigSuffix( + ".alternative", + List.of(KeyType.CONFIG, KeyType.DEVICE), + false); + + /** + * Protocol format includes a language field. + */ + public static final ConfigSuffix<Boolean> PROTOCOL_LANGUAGE = new BooleanConfigSuffix( + ".language", + List.of(KeyType.CONFIG, KeyType.DEVICE), + false); /** * Server wide connection timeout value in seconds. See protocol timeout for more information. */ - public static final ConfigKey<Integer> SERVER_TIMEOUT = new ConfigKey<>( + public static final ConfigKey<Integer> SERVER_TIMEOUT = new IntegerConfigKey( "server.timeout", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Address for uploading aggregated anonymous usage statistics. Uploaded information is the same you can see on the * statistics screen in the web app. It does not include any sensitive (e.g. locations). */ - public static final ConfigKey<String> SERVER_STATISTICS = new ConfigKey<>( + public static final ConfigKey<String> SERVER_STATISTICS = new StringConfigKey( "server.statistics", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** - * If true, the event is generated once at the beginning of overspeeding period. + * Fuel drop threshold value. When fuel level drops from one position to another for more the value, an event is + * generated. */ - public static final ConfigKey<Boolean> EVENT_OVERSPEED_NOT_REPEAT = new ConfigKey<>( - "event.overspeed.notRepeat", - Collections.singletonList(KeyType.GLOBAL)); + public static final ConfigKey<Double> EVENT_FUEL_DROP_THRESHOLD = new DoubleConfigKey( + "fuelDropThreshold", + List.of(KeyType.SERVER, KeyType.DEVICE), + 0.0); + + /** + * Fuel increase threshold value. When fuel level increases from one position to another for more the value, an + * event is generated. + */ + public static final ConfigKey<Double> EVENT_FUEL_INCREASE_THRESHOLD = new DoubleConfigKey( + "fuelIncreaseThreshold", + List.of(KeyType.SERVER, KeyType.DEVICE), + 0.0); + + /** + * Speed limit value in knots. + */ + public static final ConfigKey<Double> EVENT_OVERSPEED_LIMIT = new DoubleConfigKey( + "speedLimit", + List.of(KeyType.SERVER, KeyType.DEVICE), + 0.0); /** * Minimal over speed duration to trigger the event. Value in seconds. */ - public static final ConfigKey<Long> EVENT_OVERSPEED_MINIMAL_DURATION = new ConfigKey<>( + public static final ConfigKey<Long> EVENT_OVERSPEED_MINIMAL_DURATION = new LongConfigKey( "event.overspeed.minimalDuration", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Relevant only for geofence speed limits. Use the lowest speed limit from all geofences. */ - public static final ConfigKey<Boolean> EVENT_OVERSPEED_PREFER_LOWEST = new ConfigKey<>( + public static final ConfigKey<Boolean> EVENT_OVERSPEED_PREFER_LOWEST = new BooleanConfigKey( "event.overspeed.preferLowest", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Driver behavior acceleration threshold. Value is in meter per second squared. */ - public static final ConfigKey<Double> EVENT_BEHAVIOR_ACCELERATION_THRESHOLD = new ConfigKey<>( + public static final ConfigKey<Double> EVENT_BEHAVIOR_ACCELERATION_THRESHOLD = new DoubleConfigKey( "event.behavior.accelerationThreshold", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Driver behavior braking threshold. Value is in meter per second squared. */ - public static final ConfigKey<Double> EVENT_BEHAVIOR_BRAKING_THRESHOLD = new ConfigKey<>( + public static final ConfigKey<Double> EVENT_BEHAVIOR_BRAKING_THRESHOLD = new DoubleConfigKey( "event.behavior.brakingThreshold", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Do not generate alert event if same alert was present in last known location. */ - public static final ConfigKey<Boolean> EVENT_IGNORE_DUPLICATE_ALERTS = new ConfigKey<>( + public static final ConfigKey<Boolean> EVENT_IGNORE_DUPLICATE_ALERTS = new BooleanConfigKey( "event.ignoreDuplicateAlerts", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * If set to true, invalid positions will be considered for motion logic. */ - public static final ConfigKey<Boolean> EVENT_MOTION_PROCESS_INVALID_POSITIONS = new ConfigKey<>( + public static final ConfigKey<Boolean> EVENT_MOTION_PROCESS_INVALID_POSITIONS = new BooleanConfigKey( "event.motion.processInvalidPositions", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * If the speed is above specified value, the object is considered to be in motion. Default value is 0.01 knots. */ - public static final ConfigKey<Double> EVENT_MOTION_SPEED_THRESHOLD = new ConfigKey<>( + public static final ConfigKey<Double> EVENT_MOTION_SPEED_THRESHOLD = new DoubleConfigKey( "event.motion.speedThreshold", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), 0.01); /** * Global polyline geofence distance. Within that distance from the polyline, point is considered within the * geofence. Each individual geofence can also has 'polylineDistance' attribute which will take precedence. */ - public static final ConfigKey<Double> GEOFENCE_POLYLINE_DISTANCE = new ConfigKey<>( + public static final ConfigKey<Double> GEOFENCE_POLYLINE_DISTANCE = new DoubleConfigKey( "geofence.polylineDistance", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), 25.0); /** * Path to the database driver JAR file. Traccar includes drivers for MySQL, PostgreSQL and H2 databases. If you use * one of those, you don't need to specify this parameter. */ - public static final ConfigKey<String> DATABASE_DRIVER_FILE = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_DRIVER_FILE = new StringConfigKey( "database.driverFile", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Database driver Java class. For H2 use 'org.h2.Driver'. MySQL driver class name is 'com.mysql.jdbc.Driver'. */ - public static final ConfigKey<String> DATABASE_DRIVER = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_DRIVER = new StringConfigKey( "database.driver", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Database connection URL. By default Traccar uses H2 database. */ - public static final ConfigKey<String> DATABASE_URL = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_URL = new StringConfigKey( "database.url", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Database user name. Default administrator user for H2 database is 'sa'. */ - public static final ConfigKey<String> DATABASE_USER = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_USER = new StringConfigKey( "database.user", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Database user password. Default password for H2 admin (sa) user is empty. */ - public static final ConfigKey<String> DATABASE_PASSWORD = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_PASSWORD = new StringConfigKey( "database.password", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Path to Liquibase master changelog file. */ - public static final ConfigKey<String> DATABASE_CHANGELOG = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_CHANGELOG = new StringConfigKey( "database.changelog", - Collections.singletonList(KeyType.GLOBAL)); - - /** - * Automatically generate SQL database queries when possible. - */ - public static final ConfigKey<Boolean> DATABASE_GENERATE_QUERIES = new ConfigKey<>( - "database.generateQueries", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Database connection pool size. Default value is defined by the HikariCP library. */ - public static final ConfigKey<Integer> DATABASE_MAX_POOL_SIZE = new ConfigKey<>( + public static final ConfigKey<Integer> DATABASE_MAX_POOL_SIZE = new IntegerConfigKey( "database.maxPoolSize", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * SQL query to check connection status. Default value is 'SELECT 1'. For Oracle database you can use * 'SELECT 1 FROM DUAL'. */ - public static final ConfigKey<String> DATABASE_CHECK_CONNECTION = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_CHECK_CONNECTION = new StringConfigKey( "database.checkConnection", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), "SELECT 1"); /** * Store original HEX or string data as "raw" attribute in the corresponding position. */ - public static final ConfigKey<Boolean> DATABASE_SAVE_ORIGINAL = new ConfigKey<>( + public static final ConfigKey<Boolean> DATABASE_SAVE_ORIGINAL = new BooleanConfigKey( "database.saveOriginal", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * Throttle unknown device database queries when it sends repeated requests. + */ + public static final ConfigKey<Boolean> DATABASE_THROTTLE_UNKNOWN = new BooleanConfigKey( + "database.throttleUnknown", + List.of(KeyType.CONFIG)); /** - * By default server syncs with the database if it encounters and unknown device. This flag allows to disable that + * By default, server syncs with the database if it encounters and unknown device. This flag allows to disable that * behavior to improve performance in some cases. */ - public static final ConfigKey<Boolean> DATABASE_IGNORE_UNKNOWN = new ConfigKey<>( + public static final ConfigKey<Boolean> DATABASE_IGNORE_UNKNOWN = new BooleanConfigKey( "database.ignoreUnknown", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Automatically register unknown devices in the database. */ - public static final ConfigKey<Boolean> DATABASE_REGISTER_UNKNOWN = new ConfigKey<>( + public static final ConfigKey<Boolean> DATABASE_REGISTER_UNKNOWN = new BooleanConfigKey( "database.registerUnknown", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Default category for auto-registered devices. */ - public static final ConfigKey<String> DATABASE_REGISTER_UNKNOWN_DEFAULT_CATEGORY = new ConfigKey<>( + public static final ConfigKey<String> DATABASE_REGISTER_UNKNOWN_DEFAULT_CATEGORY = new StringConfigKey( "database.registerUnknown.defaultCategory", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * The group id assigned to auto-registered devices. */ - public static final ConfigKey<Long> DATABASE_REGISTER_UNKNOWN_DEFAULT_GROUP_ID = new ConfigKey<>( + public static final ConfigKey<Long> DATABASE_REGISTER_UNKNOWN_DEFAULT_GROUP_ID = new LongConfigKey( "database.registerUnknown.defaultGroupId", - Collections.singletonList(KeyType.GLOBAL)); - - /** - * Minimum device refresh timeout in seconds. Default timeout is 5 minutes. - */ - public static final ConfigKey<Long> DATABASE_REFRESH_DELAY = new ConfigKey<>( - "database.refreshDelay", - Collections.singletonList(KeyType.GLOBAL), - 300L); + List.of(KeyType.CONFIG)); /** * Store empty messages as positions. For example, heartbeats. */ - public static final ConfigKey<Boolean> DATABASE_SAVE_EMPTY = new ConfigKey<>( + public static final ConfigKey<Boolean> DATABASE_SAVE_EMPTY = new BooleanConfigKey( "database.saveEmpty", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Device limit for self registered users. Default value is -1, which indicates no limit. */ - public static final ConfigKey<Integer> USERS_DEFAULT_DEVICE_LIMIT = new ConfigKey<>( + public static final ConfigKey<Integer> USERS_DEFAULT_DEVICE_LIMIT = new IntegerConfigKey( "users.defaultDeviceLimit", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), -1); /** * Default user expiration for self registered users. Value is in days. By default no expiration is set. */ - public static final ConfigKey<Integer> USERS_DEFAULT_EXPIRATION_DAYS = new ConfigKey<>( + public static final ConfigKey<Integer> USERS_DEFAULT_EXPIRATION_DAYS = new IntegerConfigKey( "users.defaultExpirationDays", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * LDAP server URL. */ - public static final ConfigKey<String> LDAP_URL = new ConfigKey<>( + public static final ConfigKey<String> LDAP_URL = new StringConfigKey( "ldap.url", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * LDAP server login. */ - public static final ConfigKey<String> LDAP_USER = new ConfigKey<>( + public static final ConfigKey<String> LDAP_USER = new StringConfigKey( "ldap.user", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * LDAP server password. */ - public static final ConfigKey<String> LDAP_PASSWORD = new ConfigKey<>( + public static final ConfigKey<String> LDAP_PASSWORD = new StringConfigKey( "ldap.password", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Force LDAP authentication. */ - public static final ConfigKey<Boolean> LDAP_FORCE = new ConfigKey<>( + public static final ConfigKey<Boolean> LDAP_FORCE = new BooleanConfigKey( "ldap.force", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * LDAP user search base. */ - public static final ConfigKey<String> LDAP_BASE = new ConfigKey<>( + public static final ConfigKey<String> LDAP_BASE = new StringConfigKey( "ldap.base", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * LDAP attribute used as user id. Default value is 'uid'. */ - public static final ConfigKey<String> LDAP_ID_ATTRIBUTE = new ConfigKey<>( + public static final ConfigKey<String> LDAP_ID_ATTRIBUTE = new StringConfigKey( "ldap.idAttribute", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), "uid"); /** * LDAP attribute used as user name. Default value is 'cn'. */ - public static final ConfigKey<String> LDAP_NAME_ATTRIBUTE = new ConfigKey<>( + public static final ConfigKey<String> LDAP_NAME_ATTRIBUTE = new StringConfigKey( "ldap.nameAttribute", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), "cn"); /** * LDAP attribute used as user email. Default value is 'mail'. */ - public static final ConfigKey<String> LDAP_MAIN_ATTRIBUTE = new ConfigKey<>( + public static final ConfigKey<String> LDAP_MAIN_ATTRIBUTE = new StringConfigKey( "ldap.mailAttribute", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), "mail"); /** * LDAP custom search filter. If not specified, '({idAttribute}=:login)' will be used as a filter. */ - public static final ConfigKey<String> LDAP_SEARCH_FILTER = new ConfigKey<>( + public static final ConfigKey<String> LDAP_SEARCH_FILTER = new StringConfigKey( "ldap.searchFilter", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * LDAP custom admin search filter. */ - public static final ConfigKey<String> LDAP_ADMIN_FILTER = new ConfigKey<>( + public static final ConfigKey<String> LDAP_ADMIN_FILTER = new StringConfigKey( "ldap.adminFilter", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * LDAP admin user group. Used if custom admin filter is not specified. */ - public static final ConfigKey<String> LDAP_ADMIN_GROUP = new ConfigKey<>( + public static final ConfigKey<String> LDAP_ADMIN_GROUP = new StringConfigKey( "ldap.adminGroup", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * If no data is reported by a device for the given amount of time, status changes from online to unknown. Value is * in seconds. Default timeout is 10 minutes. */ - public static final ConfigKey<Long> STATUS_TIMEOUT = new ConfigKey<>( + public static final ConfigKey<Long> STATUS_TIMEOUT = new LongConfigKey( "status.timeout", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), 600L); /** - * Force additional state check when device status changes to 'offline' or 'unknown'. Default false. - */ - public static final ConfigKey<Boolean> STATUS_UPDATE_DEVICE_STATE = new ConfigKey<>( - "status.updateDeviceState", - Collections.singletonList(KeyType.GLOBAL)); - - /** * List of protocol names to ignore offline status. Can be useful to not trigger status change when devices are * configured to disconnect after reporting a batch of data. */ - public static final ConfigKey<String> STATUS_IGNORE_OFFLINE = new ConfigKey<>( + public static final ConfigKey<String> STATUS_IGNORE_OFFLINE = new StringConfigKey( "status.ignoreOffline", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Path to the media folder. Server stores audio, video and photo files in that folder. Sub-folders will be * automatically created for each device by unique id. */ - public static final ConfigKey<String> MEDIA_PATH = new ConfigKey<>( + public static final ConfigKey<String> MEDIA_PATH = new StringConfigKey( "media.path", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Optional parameter to specify network interface for web interface to bind to. By default server will bind to all * available interfaces. */ - public static final ConfigKey<String> WEB_ADDRESS = new ConfigKey<>( + public static final ConfigKey<String> WEB_ADDRESS = new StringConfigKey( "web.address", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Web interface TCP port number. By default Traccar uses port 8082. To avoid specifying port in the browser you * can set it to 80 (default HTTP port). */ - public static final ConfigKey<Integer> WEB_PORT = new ConfigKey<>( + public static final ConfigKey<Integer> WEB_PORT = new IntegerConfigKey( "web.port", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * Maximum API requests per second. Above this limit requests and delayed and throttled. + */ + public static final ConfigKey<Integer> WEB_MAX_REQUESTS_PER_SECOND = new IntegerConfigKey( + "web.maxRequestsPerSec", + List.of(KeyType.CONFIG)); + + /** + * Sanitize all strings returned via API. This is needed to fix XSS issues in the old web interface. New React-based + * interface doesn't require this. + */ + public static final ConfigKey<Boolean> WEB_SANITIZE = new BooleanConfigKey( + "web.sanitize", + List.of(KeyType.CONFIG)); /** * Path to the web app folder. */ - public static final ConfigKey<String> WEB_PATH = new ConfigKey<>( + public static final ConfigKey<String> WEB_PATH = new StringConfigKey( "web.path", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * WebSocket connection timeout in milliseconds. Default timeout is 10 minutes. */ - public static final ConfigKey<Long> WEB_TIMEOUT = new ConfigKey<>( + public static final ConfigKey<Long> WEB_TIMEOUT = new LongConfigKey( "web.timeout", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), 60000L); /** * Authentication sessions timeout in seconds. By default no timeout. */ - public static final ConfigKey<Integer> WEB_SESSION_TIMEOUT = new ConfigKey<>( + public static final ConfigKey<Integer> WEB_SESSION_TIMEOUT = new IntegerConfigKey( "web.sessionTimeout", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Enable database access console via '/console' URL. Use only for debugging. Never use in production. */ - public static final ConfigKey<Boolean> WEB_CONSOLE = new ConfigKey<>( + public static final ConfigKey<Boolean> WEB_CONSOLE = new BooleanConfigKey( "web.console", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Server debug version of the web app. Not recommended to use for performance reasons. It is intended to be used * for development and debugging purposes. */ - public static final ConfigKey<Boolean> WEB_DEBUG = new ConfigKey<>( + public static final ConfigKey<Boolean> WEB_DEBUG = new BooleanConfigKey( "web.debug", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * A token to login as a virtual admin account. Can be used to restore access in case of issues with regular admin + * login. For example, if password is lost and can't be restored. + */ + public static final ConfigKey<String> WEB_SERVICE_ACCOUNT_TOKEN = new StringConfigKey( + "web.serviceAccountToken", + List.of(KeyType.CONFIG)); /** * Cross-origin resource sharing origin header value. */ - public static final ConfigKey<String> WEB_ORIGIN = new ConfigKey<>( + public static final ConfigKey<String> WEB_ORIGIN = new StringConfigKey( "web.origin", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Cache control header value. By default resources are cached for one hour. */ - public static final ConfigKey<String> WEB_CACHE_CONTROL = new ConfigKey<>( + public static final ConfigKey<String> WEB_CACHE_CONTROL = new StringConfigKey( "web.cacheControl", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), "max-age=3600,public"); /** - * URL to forward positions. Data is passed through URL parameters. For example, {uniqueId} for device identifier, - * {latitude} and {longitude} for coordinates. + * Position forwarding format. Available options are "url", "json" and "kafka". Default is "url". */ - public static final ConfigKey<String> FORWARD_URL = new ConfigKey<>( - "forward.url", - Collections.singletonList(KeyType.GLOBAL)); + public static final ConfigKey<String> FORWARD_TYPE = new StringConfigKey( + "forward.type", + List.of(KeyType.CONFIG), + "url"); /** - * Additional HTTP header, can be used for authorization. + * Position forwarding Kafka topic. */ - public static final ConfigKey<String> FORWARD_HEADER = new ConfigKey<>( - "forward.header", - Collections.singletonList(KeyType.GLOBAL)); + public static final ConfigKey<String> FORWARD_TOPIC = new StringConfigKey( + "forward.topic", + List.of(KeyType.CONFIG), + "positions"); /** - * Boolean value to enable forwarding in JSON format. + * URL to forward positions. Data is passed through URL parameters. For example, {uniqueId} for device identifier, + * {latitude} and {longitude} for coordinates. */ - public static final ConfigKey<Boolean> FORWARD_JSON = new ConfigKey<>( - "forward.json", - Collections.singletonList(KeyType.GLOBAL)); + public static final ConfigKey<String> FORWARD_URL = new StringConfigKey( + "forward.url", + List.of(KeyType.CONFIG)); /** - * Boolean value to enable URL parameters in json mode. For example, {uniqueId} for device identifier, - * {latitude} and {longitude} for coordinates. + * Additional HTTP header, can be used for authorization. */ - public static final ConfigKey<Boolean> FORWARD_URL_VARIABLES = new ConfigKey<>( - "forward.urlVariables", - Collections.singletonList(KeyType.GLOBAL)); + public static final ConfigKey<String> FORWARD_HEADER = new StringConfigKey( + "forward.header", + List.of(KeyType.CONFIG)); /** * Position forwarding retrying enable. When enabled, additional attempts are made to deliver positions. If initial @@ -612,573 +743,745 @@ public final class Keys { * If forwarding is retried for 'forward.retry.count', retrying is canceled and the position is dropped. Positions * pending to be delivered are limited to 'forward.retry.limit'. If this limit is reached, positions get discarded. */ - public static final ConfigKey<Boolean> FORWARD_RETRY_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> FORWARD_RETRY_ENABLE = new BooleanConfigKey( "forward.retry.enable", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Position forwarding retry first delay in milliseconds. * Can be set to anything greater than 0. Defaults to 100 milliseconds. */ - public static final ConfigKey<Integer> FORWARD_RETRY_DELAY = new ConfigKey<>( + public static final ConfigKey<Integer> FORWARD_RETRY_DELAY = new IntegerConfigKey( "forward.retry.delay", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG), + 100); /** * Position forwarding retry maximum retries. * Can be set to anything greater than 0. Defaults to 10 retries. */ - public static final ConfigKey<Integer> FORWARD_RETRY_COUNT = new ConfigKey<>( + public static final ConfigKey<Integer> FORWARD_RETRY_COUNT = new IntegerConfigKey( "forward.retry.count", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG), + 10); /** * Position forwarding retry pending positions limit. * Can be set to anything greater than 0. Defaults to 100 positions. */ - public static final ConfigKey<Integer> FORWARD_RETRY_LIMIT = new ConfigKey<>( + public static final ConfigKey<Integer> FORWARD_RETRY_LIMIT = new IntegerConfigKey( "forward.retry.limit", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG), + 100); + + /** + * Events forwarding format. Available options are "json" and "kafka". Default is "json". + */ + public static final ConfigKey<String> EVENT_FORWARD_TYPE = new StringConfigKey( + "event.forward.type", + List.of(KeyType.CONFIG), + "json"); + + /** + * Events forwarding Kafka topic. + */ + public static final ConfigKey<String> EVENT_FORWARD_TOPIC = new StringConfigKey( + "event.forward.topic", + List.of(KeyType.CONFIG), + "events"); /** * Events forwarding URL. */ - public static final ConfigKey<String> EVENT_FORWARD_URL = new ConfigKey<>( + public static final ConfigKey<String> EVENT_FORWARD_URL = new StringConfigKey( "event.forward.url", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Events forwarding headers. Example value: * FirstHeader: hello * SecondHeader: world */ - public static final ConfigKey<String> EVENT_FORWARD_HEADERS = new ConfigKey<>( + public static final ConfigKey<String> EVENT_FORWARD_HEADERS = new StringConfigKey( "event.forward.header", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * Root folder for all template files. + */ + public static final ConfigKey<String> TEMPLATES_ROOT = new StringConfigKey( + "templates.root", + List.of(KeyType.CONFIG), + "templates"); + + /** + * Log emails instead of sending them via SMTP. Intended for testing purposes only. + */ + public static final ConfigKey<Boolean> MAIL_DEBUG = new BooleanConfigKey( + "mail.debug", + List.of(KeyType.CONFIG)); + + /** + * Force SMTP settings from the config file and ignore user attributes. + */ + public static final ConfigKey<Boolean> MAIL_SMTP_IGNORE_USER_CONFIG = new BooleanConfigKey( + "mail.smtp.ignoreUserConfig", + List.of(KeyType.CONFIG)); + + /** + * The SMTP server to connect to. + */ + public static final ConfigKey<String> MAIL_SMTP_HOST = new StringConfigKey( + "mail.smtp.host", + List.of(KeyType.CONFIG, KeyType.USER)); + + /** + * The SMTP server port to connect. Defaults to 25. + */ + public static final ConfigKey<Integer> MAIL_SMTP_PORT = new IntegerConfigKey( + "mail.smtp.port", + List.of(KeyType.CONFIG, KeyType.USER), + 25); + + /** + * Email transport protocol. Default value is "smtp". + */ + public static final ConfigKey<String> MAIL_TRANSPORT_PROTOCOL = new StringConfigKey( + "mail.transport.protocol", + List.of(KeyType.CONFIG, KeyType.USER), + "smtp"); + + /** + * If true, enables the use of the STARTTLS command (if supported by the server) to switch the connection to a + * TLS-protected connection before issuing any login commands. + */ + public static final ConfigKey<Boolean> MAIL_SMTP_STARTTLS_ENABLE = new BooleanConfigKey( + "mail.smtp.starttls.enable", + List.of(KeyType.CONFIG, KeyType.USER)); + + /** + * If true, requires the use of the STARTTLS command. If the server doesn't support the STARTTLS command, or the + * command fails, the connect method will fail. + */ + public static final ConfigKey<Boolean> MAIL_SMTP_STARTTLS_REQUIRED = new BooleanConfigKey( + "mail.smtp.starttls.required", + List.of(KeyType.CONFIG, KeyType.USER)); + + /** + * If set to true, use SSL to connect and use the SSL port by default. + */ + public static final ConfigKey<Boolean> MAIL_SMTP_SSL_ENABLE = new BooleanConfigKey( + "mail.smtp.ssl.enable", + List.of(KeyType.CONFIG, KeyType.USER)); + + /** + * If set to "*", all hosts are trusted. If set to a whitespace separated list of hosts, those hosts are trusted. + * Otherwise, trust depends on the certificate the server presents. + */ + public static final ConfigKey<String> MAIL_SMTP_SSL_TRUST = new StringConfigKey( + "mail.smtp.ssl.trust", + List.of(KeyType.CONFIG, KeyType.USER)); + + /** + * Specifies the SSL protocols that will be enabled for SSL connections. + */ + public static final ConfigKey<String> MAIL_SMTP_SSL_PROTOCOLS = new StringConfigKey( + "mail.smtp.ssl.protocols", + List.of(KeyType.CONFIG, KeyType.USER)); + + /** + * SMTP connection username. + */ + public static final ConfigKey<String> MAIL_SMTP_USERNAME = new StringConfigKey( + "mail.smtp.username", + List.of(KeyType.CONFIG, KeyType.USER)); + + /** + * SMTP connection password. + */ + public static final ConfigKey<String> MAIL_SMTP_PASSWORD = new StringConfigKey( + "mail.smtp.password", + List.of(KeyType.CONFIG, KeyType.USER)); + + /** + * Email address to use for SMTP MAIL command. + */ + public static final ConfigKey<String> MAIL_SMTP_FROM = new StringConfigKey( + "mail.smtp.from", + List.of(KeyType.CONFIG, KeyType.USER)); /** - * Enable commands queuing when devices are offline. Commands are buffered in memory only, so restarting service - * will clear the buffer. + * The personal name for the email from address. */ - public static final ConfigKey<Boolean> COMMANDS_QUEUEING = new ConfigKey<>( - "commands.queueing", - Collections.singletonList(KeyType.GLOBAL)); + public static final ConfigKey<String> MAIL_SMTP_FROM_NAME = new StringConfigKey( + "mail.smtp.fromName", + List.of(KeyType.CONFIG, KeyType.USER)); /** * SMS API service full URL. Enables SMS commands and notifications. */ - public static final ConfigKey<String> SMS_HTTP_URL = new ConfigKey<>( + public static final ConfigKey<String> SMS_HTTP_URL = new StringConfigKey( "sms.http.url", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * SMS API authorization header name. Default value is 'Authorization'. */ - public static final ConfigKey<String> SMS_HTTP_AUTHORIZATION_HEADER = new ConfigKey<>( + public static final ConfigKey<String> SMS_HTTP_AUTHORIZATION_HEADER = new StringConfigKey( "sms.http.authorizationHeader", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), "Authorization"); /** * SMS API authorization header value. This value takes precedence over user and password. */ - public static final ConfigKey<String> SMS_HTTP_AUTHORIZATION = new ConfigKey<>( + public static final ConfigKey<String> SMS_HTTP_AUTHORIZATION = new StringConfigKey( "sms.http.authorization", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * SMS API basic authentication user. */ - public static final ConfigKey<String> SMS_HTTP_USER = new ConfigKey<>( + public static final ConfigKey<String> SMS_HTTP_USER = new StringConfigKey( "sms.http.user", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * SMS API basic authentication password. */ - public static final ConfigKey<String> SMS_HTTP_PASSWORD = new ConfigKey<>( + public static final ConfigKey<String> SMS_HTTP_PASSWORD = new StringConfigKey( "sms.http.password", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * SMS API body template. Placeholders {phone} and {message} can be used in the template. * If value starts with '{' or '[', server automatically assumes JSON format. */ - public static final ConfigKey<String> SMS_HTTP_TEMPLATE = new ConfigKey<>( + public static final ConfigKey<String> SMS_HTTP_TEMPLATE = new StringConfigKey( "sms.http.template", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * AWS Access Key with SNS permission. */ - public static final ConfigKey<String> SMS_AWS_ACCESS = new ConfigKey<>( + public static final ConfigKey<String> SMS_AWS_ACCESS = new StringConfigKey( "sms.aws.access", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * AWS Secret Access Key with SNS permission. */ - public static final ConfigKey<String> SMS_AWS_SECRET = new ConfigKey<>( + public static final ConfigKey<String> SMS_AWS_SECRET = new StringConfigKey( "sms.aws.secret", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * AWS Region for SNS service. * Make sure to use regions that are supported for messaging. */ - public static final ConfigKey<String> SMS_AWS_REGION = new ConfigKey<>( + public static final ConfigKey<String> SMS_AWS_REGION = new StringConfigKey( "sms.aws.region", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * Enabled notification options. Comma-separated string is expected. + * Example: web,mail,sms + */ + public static final ConfigKey<String> NOTIFICATOR_TYPES = new StringConfigKey( + "notificator.types", + List.of(KeyType.CONFIG)); /** * Traccar notification API key. */ - public static final ConfigKey<String> NOTIFICATOR_TRACCAR_KEY = new ConfigKey<>( + public static final ConfigKey<String> NOTIFICATOR_TRACCAR_KEY = new StringConfigKey( "notificator.traccar.key", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** - * Firebase server API key for push notifications. + * Firebase service account JSON. */ - public static final ConfigKey<String> NOTIFICATOR_FIREBASE_KEY = new ConfigKey<>( - "notificator.firebase.key", - Collections.singletonList(KeyType.GLOBAL)); + public static final ConfigKey<String> NOTIFICATOR_FIREBASE_SERVICE_ACCOUNT = new StringConfigKey( + "notificator.firebase.serviceAccount", + List.of(KeyType.CONFIG)); /** * Pushover notification user name. */ - public static final ConfigKey<String> NOTIFICATOR_PUSHOVER_USER = new ConfigKey<>( + public static final ConfigKey<String> NOTIFICATOR_PUSHOVER_USER = new StringConfigKey( "notificator.pushover.user", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Pushover notification user token. */ - public static final ConfigKey<String> NOTIFICATOR_PUSHOVER_TOKEN = new ConfigKey<>( + public static final ConfigKey<String> NOTIFICATOR_PUSHOVER_TOKEN = new StringConfigKey( "notificator.pushover.token", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Telegram notification API key. */ - public static final ConfigKey<String> NOTIFICATOR_TELEGRAM_KEY = new ConfigKey<>( + public static final ConfigKey<String> NOTIFICATOR_TELEGRAM_KEY = new StringConfigKey( "notificator.telegram.key", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Telegram notification chat id to post messages to. */ - public static final ConfigKey<String> NOTIFICATOR_TELEGRAM_CHAT_ID = new ConfigKey<>( + public static final ConfigKey<String> NOTIFICATOR_TELEGRAM_CHAT_ID = new StringConfigKey( "notificator.telegram.chatId", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Telegram notification send location message. */ - public static final ConfigKey<Boolean> NOTIFICATOR_TELEGRAM_SEND_LOCATION = new ConfigKey<>( + public static final ConfigKey<Boolean> NOTIFICATOR_TELEGRAM_SEND_LOCATION = new BooleanConfigKey( "notificator.telegram.sendLocation", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Maximum time period for reports in seconds. Can be useful to prevent users to request unreasonably long reports. * By default there is no limit. */ - public static final ConfigKey<Long> REPORT_PERIOD_LIMIT = new ConfigKey<>( + public static final ConfigKey<Long> REPORT_PERIOD_LIMIT = new LongConfigKey( "report.periodLimit", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Trips less than minimal duration and minimal distance are ignored. 300 seconds and 500 meters are default. */ - public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_TRIP_DISTANCE = new ConfigKey<>( + public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_TRIP_DISTANCE = new LongConfigKey( "report.trip.minimalTripDistance", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), 500L); /** * Trips less than minimal duration and minimal distance are ignored. 300 seconds and 500 meters are default. */ - public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_TRIP_DURATION = new ConfigKey<>( + public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_TRIP_DURATION = new LongConfigKey( "report.trip.minimalTripDuration", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), 300L); /** * Parking less than minimal duration does not cut trip. Default 300 seconds. */ - public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_PARKING_DURATION = new ConfigKey<>( + public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_PARKING_DURATION = new LongConfigKey( "report.trip.minimalParkingDuration", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), 300L); /** * Gaps of more than specified time are counted as stops. Default value is one hour. */ - public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_NO_DATA_DURATION = new ConfigKey<>( + public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_NO_DATA_DURATION = new LongConfigKey( "report.trip.minimalNoDataDuration", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), 3600L); /** * Flag to enable ignition use for trips calculation. */ - public static final ConfigKey<Boolean> REPORT_TRIP_USE_IGNITION = new ConfigKey<>( + public static final ConfigKey<Boolean> REPORT_TRIP_USE_IGNITION = new BooleanConfigKey( "report.trip.useIgnition", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * Ignore odometer value reported by the device and use server-calculated total distance instead. This is useful + * if device reports invalid or zero odometer values. + */ + public static final ConfigKey<Boolean> REPORT_IGNORE_ODOMETER = new BooleanConfigKey( + "report.ignoreOdometer", + List.of(KeyType.CONFIG)); /** * Boolean flag to enable or disable position filtering. */ - public static final ConfigKey<Boolean> FILTER_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_ENABLE = new BooleanConfigKey( "filter.enable", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Filter invalid (valid field is set to false) positions. */ - public static final ConfigKey<Boolean> FILTER_INVALID = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_INVALID = new BooleanConfigKey( "filter.invalid", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Filter zero coordinates. Zero latitude and longitude are theoretically valid values, but it practice it usually * indicates invalid GPS data. */ - public static final ConfigKey<Boolean> FILTER_ZERO = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_ZERO = new BooleanConfigKey( "filter.zero", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Filter duplicate records (duplicates are detected by time value). */ - public static final ConfigKey<Boolean> FILTER_DUPLICATE = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_DUPLICATE = new BooleanConfigKey( "filter.duplicate", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** - * Filter records with fix time in future. The values is specified in seconds. Records that have fix time more than - * specified number of seconds later than current server time would be filtered out. + * Filter records with fix time in the future. The value is specified in seconds. Records that have fix time more + * than the specified number of seconds later than current server time would be filtered out. */ - public static final ConfigKey<Long> FILTER_FUTURE = new ConfigKey<>( + public static final ConfigKey<Long> FILTER_FUTURE = new LongConfigKey( "filter.future", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * Filter records with fix time in the past. The value is specified in seconds. Records that have fix time more + * than the specified number of seconds before current server time would be filtered out. + */ + public static final ConfigKey<Long> FILTER_PAST = new LongConfigKey( + "filter.past", + List.of(KeyType.CONFIG)); /** * Filter positions with accuracy less than specified value in meters. */ - public static final ConfigKey<Integer> FILTER_ACCURACY = new ConfigKey<>( + public static final ConfigKey<Integer> FILTER_ACCURACY = new IntegerConfigKey( "filter.accuracy", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Filter cell and wifi locations that are coming from geolocation provider. */ - public static final ConfigKey<Boolean> FILTER_APPROXIMATE = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_APPROXIMATE = new BooleanConfigKey( "filter.approximate", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Filter positions with exactly zero speed values. */ - public static final ConfigKey<Boolean> FILTER_STATIC = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_STATIC = new BooleanConfigKey( "filter.static", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Filter records by distance. The values is specified in meters. If the new position is less far than this value * from the last one it gets filtered out. */ - public static final ConfigKey<Integer> FILTER_DISTANCE = new ConfigKey<>( + public static final ConfigKey<Integer> FILTER_DISTANCE = new IntegerConfigKey( "filter.distance", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** - * Filter records by Maximum Speed value in knots. Can be used to filter jumps to far locations even if they're - * marked as valid. Shouldn't be too low. Start testing with values at about 25000. + * Filter records by Maximum Speed value in knots. Can be used to filter jumps to far locations even if Position + * appears valid or if Position `speed` field reported by the device is also within limits. Calculates speed from + * the distance to the previous position and the elapsed time. + * Tip: Shouldn't be too low. Start testing with values at about 25000. */ - public static final ConfigKey<Integer> FILTER_MAX_SPEED = new ConfigKey<>( + public static final ConfigKey<Integer> FILTER_MAX_SPEED = new IntegerConfigKey( "filter.maxSpeed", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Filter position if time from previous position is less than specified value in seconds. */ - public static final ConfigKey<Integer> FILTER_MIN_PERIOD = new ConfigKey<>( + public static final ConfigKey<Integer> FILTER_MIN_PERIOD = new IntegerConfigKey( "filter.minPeriod", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * If false, the server expects all locations to come sequentially (for each device). Filter checks for duplicates, + * distance, speed, or time period only against the location that was last received by server. + * If true, the server expects locations to come at random order (since tracking device might go offline). + * Filter checks for duplicates, distance, speed, or time period against the preceding Position's. + * Important: setting to true can cause potential performance issues. + */ + public static final ConfigKey<Boolean> FILTER_RELATIVE = new BooleanConfigKey( + "filter.relative", + List.of(KeyType.CONFIG)); /** - * Time limit for the filtering in seconds. If the time difference between last position and a new one is more than - * this limit, the new position will not be filtered out. + * Time limit for the filtering in seconds. If the time difference between the last position was received by server + * and a new position is received by server is more than this limit, the new position will not be filtered out. */ - public static final ConfigKey<Long> FILTER_SKIP_LIMIT = new ConfigKey<>( + public static final ConfigKey<Long> FILTER_SKIP_LIMIT = new LongConfigKey( "filter.skipLimit", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Enable attributes skipping. Attribute skipping can be enabled in the config or device attributes. + * If position contains any attribute mentioned in "filter.skipAttributes" config key, position is not filtered out. */ - public static final ConfigKey<Boolean> FILTER_SKIP_ATTRIBUTES_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> FILTER_SKIP_ATTRIBUTES_ENABLE = new BooleanConfigKey( "filter.skipAttributes.enable", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * Attribute skipping can be enabled in the config or device attributes. + * If position contains any attribute mentioned in "filter.skipAttributes" config key, position is not filtered out. + */ + public static final ConfigKey<String> FILTER_SKIP_ATTRIBUTES = new StringConfigKey( + "filter.skipAttributes", + List.of(KeyType.CONFIG, KeyType.DEVICE), + ""); /** * Override device time. Possible values are 'deviceTime' and 'serverTime' */ - public static final ConfigKey<String> TIME_OVERRIDE = new ConfigKey<>( + public static final ConfigKey<String> TIME_OVERRIDE = new StringConfigKey( "time.override", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * List of protocols to enable. If not specified, Traccar enabled all protocols that have port numbers listed. + * The value is a comma-separated list of protocol names. + * Example value: teltonika,osmand + */ + public static final ConfigKey<String> PROTOCOLS_ENABLE = new StringConfigKey( + "protocols.enable", + List.of(KeyType.CONFIG)); /** * List of protocols for overriding time. If not specified override is applied globally. List consist of protocol * names that can be separated by comma or single space character. */ - public static final ConfigKey<String> TIME_PROTOCOLS = new ConfigKey<>( + public static final ConfigKey<String> TIME_PROTOCOLS = new StringConfigKey( "time.protocols", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Replaces coordinates with last known if change is less than a 'coordinates.minError' meters * or more than a 'coordinates.maxError' meters. Helps to avoid coordinates jumps during parking period * or jumps to zero coordinates. */ - public static final ConfigKey<Boolean> COORDINATES_FILTER = new ConfigKey<>( + public static final ConfigKey<Boolean> COORDINATES_FILTER = new BooleanConfigKey( "coordinates.filter", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Distance in meters. Distances below this value gets handled like explained in 'coordinates.filter'. */ - public static final ConfigKey<Integer> COORDINATES_MIN_ERROR = new ConfigKey<>( + public static final ConfigKey<Integer> COORDINATES_MIN_ERROR = new IntegerConfigKey( "coordinates.minError", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Distance in meters. Distances above this value gets handled like explained in 'coordinates.filter', but only if * Position is also marked as 'invalid'. */ - public static final ConfigKey<Integer> COORDINATES_MAX_ERROR = new ConfigKey<>( + public static final ConfigKey<Integer> COORDINATES_MAX_ERROR = new IntegerConfigKey( "coordinates.maxError", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Enable to save device IP addresses information. Disabled by default. */ - public static final ConfigKey<Boolean> PROCESSING_REMOTE_ADDRESS_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> PROCESSING_REMOTE_ADDRESS_ENABLE = new BooleanConfigKey( "processing.remoteAddress.enable", - Collections.singletonList(KeyType.GLOBAL)); - - /** - * Enable engine hours calculation on the server side. It uses ignition value to determine engine state. - */ - public static final ConfigKey<Boolean> PROCESSING_ENGINE_HOURS_ENABLE = new ConfigKey<>( - "processing.engineHours.enable", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Enable copying of missing attributes from last position to the current one. Might be useful if device doesn't * send some values in every message. */ - public static final ConfigKey<Boolean> PROCESSING_COPY_ATTRIBUTES_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> PROCESSING_COPY_ATTRIBUTES_ENABLE = new BooleanConfigKey( "processing.copyAttributes.enable", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** - * Enable computed attributes processing. + * List of attributes to copy. Attributes should be separated by a comma without any spacing. + * For example: alarm,ignition */ - public static final ConfigKey<Boolean> PROCESSING_COMPUTED_ATTRIBUTES_ENABLE = new ConfigKey<>( - "processing.computedAttributes.enable", - Collections.singletonList(KeyType.GLOBAL)); + public static final ConfigKey<String> PROCESSING_COPY_ATTRIBUTES = new StringConfigKey( + "processing.copyAttributes", + List.of(KeyType.CONFIG, KeyType.DEVICE)); /** * Enable computed attributes processing. */ - public static final ConfigKey<Boolean> PROCESSING_COMPUTED_ATTRIBUTES_DEVICE_ATTRIBUTES = new ConfigKey<>( + public static final ConfigKey<Boolean> PROCESSING_COMPUTED_ATTRIBUTES_DEVICE_ATTRIBUTES = new BooleanConfigKey( "processing.computedAttributes.deviceAttributes", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Boolean flag to enable or disable reverse geocoder. */ - public static final ConfigKey<Boolean> GEOCODER_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> GEOCODER_ENABLE = new BooleanConfigKey( "geocoder.enable", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Reverse geocoder type. Check reverse geocoding documentation for more info. By default (if the value is not * specified) server uses Google API. */ - public static final ConfigKey<String> GEOCODER_TYPE = new ConfigKey<>( + public static final ConfigKey<String> GEOCODER_TYPE = new StringConfigKey( "geocoder.type", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Geocoder server URL. Applicable only to Nominatim and Gisgraphy providers. */ - public static final ConfigKey<String> GEOCODER_URL = new ConfigKey<>( + public static final ConfigKey<String> GEOCODER_URL = new StringConfigKey( "geocoder.url", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * App id for use with Here provider. */ - public static final ConfigKey<String> GEOCODER_ID = new ConfigKey<>( + public static final ConfigKey<String> GEOCODER_ID = new StringConfigKey( "geocoder.id", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Provider API key. Most providers require API keys. */ - public static final ConfigKey<String> GEOCODER_KEY = new ConfigKey<>( + public static final ConfigKey<String> GEOCODER_KEY = new StringConfigKey( "geocoder.key", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Language parameter for providers that support localization (e.g. Google and Nominatim). */ - public static final ConfigKey<String> GEOCODER_LANGUAGE = new ConfigKey<>( + public static final ConfigKey<String> GEOCODER_LANGUAGE = new StringConfigKey( "geocoder.language", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Address format string. Default value is %h %r, %t, %s, %c. See AddressFormat for more info. */ - public static final ConfigKey<String> GEOCODER_FORMAT = new ConfigKey<>( + public static final ConfigKey<String> GEOCODER_FORMAT = new StringConfigKey( "geocoder.format", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Cache size for geocoding results. */ - public static final ConfigKey<Integer> GEOCODER_CACHE_SIZE = new ConfigKey<>( + public static final ConfigKey<Integer> GEOCODER_CACHE_SIZE = new IntegerConfigKey( "geocoder.cacheSize", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Disable automatic reverse geocoding requests for all positions. */ - public static final ConfigKey<Boolean> GEOCODER_IGNORE_POSITIONS = new ConfigKey<>( + public static final ConfigKey<Boolean> GEOCODER_IGNORE_POSITIONS = new BooleanConfigKey( "geocoder.ignorePositions", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Boolean flag to apply reverse geocoding to invalid positions. */ - public static final ConfigKey<Boolean> GEOCODER_PROCESS_INVALID_POSITIONS = new ConfigKey<>( + public static final ConfigKey<Boolean> GEOCODER_PROCESS_INVALID_POSITIONS = new BooleanConfigKey( "geocoder.processInvalidPositions", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Optional parameter to specify minimum distance for new reverse geocoding request. If distance is less than * specified value (in meters), then Traccar will reuse last known address. */ - public static final ConfigKey<Integer> GEOCODER_REUSE_DISTANCE = new ConfigKey<>( + public static final ConfigKey<Integer> GEOCODER_REUSE_DISTANCE = new IntegerConfigKey( "geocoder.reuseDistance", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Perform geocoding when preparing reports and sending notifications. */ - public static final ConfigKey<Boolean> GEOCODER_ON_REQUEST = new ConfigKey<>( + public static final ConfigKey<Boolean> GEOCODER_ON_REQUEST = new BooleanConfigKey( "geocoder.onRequest", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Boolean flag to enable LBS location resolution. Some devices send cell towers information and WiFi point when GPS * location is not available. Traccar can determine coordinates based on that information using third party * services. Default value is false. */ - public static final ConfigKey<Boolean> GEOLOCATION_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> GEOLOCATION_ENABLE = new BooleanConfigKey( "geolocation.enable", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Provider to use for LBS location. Available options: google, mozilla and opencellid. By default opencellid is * used. You have to supply a key that you get from corresponding provider. For more information see LBS geolocation * documentation. */ - public static final ConfigKey<String> GEOLOCATION_TYPE = new ConfigKey<>( + public static final ConfigKey<String> GEOLOCATION_TYPE = new StringConfigKey( "geolocation.type", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Geolocation provider API URL address. Not required for most providers. */ - public static final ConfigKey<String> GEOLOCATION_URL = new ConfigKey<>( + public static final ConfigKey<String> GEOLOCATION_URL = new StringConfigKey( "geolocation.url", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Provider API key. OpenCellID service requires API key. */ - public static final ConfigKey<String> GEOLOCATION_KEY = new ConfigKey<>( + public static final ConfigKey<String> GEOLOCATION_KEY = new StringConfigKey( "geolocation.key", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Boolean flag to apply geolocation to invalid positions. */ - public static final ConfigKey<Boolean> GEOLOCATION_PROCESS_INVALID_POSITIONS = new ConfigKey<>( + public static final ConfigKey<Boolean> GEOLOCATION_PROCESS_INVALID_POSITIONS = new BooleanConfigKey( "geolocation.processInvalidPositions", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * Reuse last geolocation result if network details have not changed. + */ + public static final ConfigKey<Boolean> GEOLOCATION_REUSE = new BooleanConfigKey( + "geolocation.reuse", + List.of(KeyType.CONFIG)); /** * Default MCC value to use if device doesn't report MCC. */ - public static final ConfigKey<Integer> GEOLOCATION_MCC = new ConfigKey<>( + public static final ConfigKey<Integer> GEOLOCATION_MCC = new IntegerConfigKey( "geolocation.mcc", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Default MNC value to use if device doesn't report MNC. */ - public static final ConfigKey<Integer> GEOLOCATION_MNC = new ConfigKey<>( + public static final ConfigKey<Integer> GEOLOCATION_MNC = new IntegerConfigKey( "geolocation.mnc", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Boolean flag to enable speed limit API to get speed limit values depending on location. Default value is false. */ - public static final ConfigKey<Boolean> SPEED_LIMIT_ENABLE = new ConfigKey<>( + public static final ConfigKey<Boolean> SPEED_LIMIT_ENABLE = new BooleanConfigKey( "speedLimit.enable", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Provider to use for speed limit. Available options: overpass. By default overpass is used. */ - public static final ConfigKey<String> SPEED_LIMIT_TYPE = new ConfigKey<>( + public static final ConfigKey<String> SPEED_LIMIT_TYPE = new StringConfigKey( "speedLimit.type", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Speed limit provider API URL address. */ - public static final ConfigKey<String> SPEED_LIMIT_URL = new ConfigKey<>( + public static final ConfigKey<String> SPEED_LIMIT_URL = new StringConfigKey( "speedLimit.url", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * 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. */ - public static final ConfigKey<String> LOCATION_LATITUDE_HEMISPHERE = new ConfigKey<>( + public static final ConfigKey<String> LOCATION_LATITUDE_HEMISPHERE = new StringConfigKey( "location.latitudeHemisphere", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Override longitude sign / hemisphere. Useful in cases where value is incorrect because of device bug. Value can * be E for East or W for West. */ - public static final ConfigKey<String> LOCATION_LONGITUDE_HEMISPHERE = new ConfigKey<>( + public static final ConfigKey<String> LOCATION_LONGITUDE_HEMISPHERE = new StringConfigKey( "location.longitudeHemisphere", - Collections.singletonList(KeyType.GLOBAL)); - - /** - * Enable Jetty Request Log. - */ - public static final ConfigKey<Boolean> WEB_REQUEST_LOG_ENABLE = new ConfigKey<>( - "web.requestLog.enable", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Jetty Request Log Path. @@ -1186,82 +1489,118 @@ public final class Keys { * over the file. * Example: ./logs/jetty-yyyy_mm_dd.request.log */ - public static final ConfigKey<String> WEB_REQUEST_LOG_PATH = new ConfigKey<>( + public static final ConfigKey<String> WEB_REQUEST_LOG_PATH = new StringConfigKey( "web.requestLog.path", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Set the number of days before rotated request log files are deleted. */ - public static final ConfigKey<Integer> WEB_REQUEST_LOG_RETAIN_DAYS = new ConfigKey<>( + public static final ConfigKey<Integer> WEB_REQUEST_LOG_RETAIN_DAYS = new IntegerConfigKey( "web.requestLog.retainDays", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Disable systemd health checks. */ - public static final ConfigKey<Boolean> WEB_DISABLE_HEALTH_CHECK = new ConfigKey<>( + public static final ConfigKey<Boolean> WEB_DISABLE_HEALTH_CHECK = new BooleanConfigKey( "web.disableHealthCheck", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Sets SameSite cookie attribute value. * Supported options: Lax, Strict, None. */ - public static final ConfigKey<String> WEB_SAME_SITE_COOKIE = new ConfigKey<>( + public static final ConfigKey<String> WEB_SAME_SITE_COOKIE = new StringConfigKey( "web.sameSiteCookie", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Enables persisting Jetty session to the database */ - public static final ConfigKey<Boolean> WEB_PERSIST_SESSION = new ConfigKey<>( + public static final ConfigKey<Boolean> WEB_PERSIST_SESSION = new BooleanConfigKey( "web.persistSession", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * Public URL for the web app. Used for notification and report link. + * If not provided, Traccar will attempt to get a URL from the server IP address, but it might be a local address. + */ + public static final ConfigKey<String> WEB_URL = new StringConfigKey( + "web.url", + List.of(KeyType.CONFIG)); /** * Output logging to the standard terminal output instead of a log file. */ - public static final ConfigKey<Boolean> LOGGER_CONSOLE = new ConfigKey<>( + public static final ConfigKey<Boolean> LOGGER_CONSOLE = new BooleanConfigKey( "logger.console", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); + + /** + * Log executed SQL queries. + */ + public static final ConfigKey<Boolean> LOGGER_QUERIES = new BooleanConfigKey( + "logger.queries", + List.of(KeyType.CONFIG)); /** * Log file name. For rotating logs, a date is added at the end of the file name for non-current logs. */ - public static final ConfigKey<String> LOGGER_FILE = new ConfigKey<>( + public static final ConfigKey<String> LOGGER_FILE = new StringConfigKey( "logger.file", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Logging level. Default value is 'info'. * Available options: off, severe, warning, info, config, fine, finer, finest, all. */ - public static final ConfigKey<String> LOGGER_LEVEL = new ConfigKey<>( + public static final ConfigKey<String> LOGGER_LEVEL = new StringConfigKey( "logger.level", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Print full exception traces. Useful for debugging. By default shortened traces are logged. */ - public static final ConfigKey<Boolean> LOGGER_FULL_STACK_TRACES = new ConfigKey<>( + public static final ConfigKey<Boolean> LOGGER_FULL_STACK_TRACES = new BooleanConfigKey( "logger.fullStackTraces", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * Create a new log file daily. Helps with log management. For example, downloading and cleaning logs. Enabled by * default. */ - public static final ConfigKey<Boolean> LOGGER_ROTATE = new ConfigKey<>( + public static final ConfigKey<Boolean> LOGGER_ROTATE = new BooleanConfigKey( "logger.rotate", - Collections.singletonList(KeyType.GLOBAL)); + List.of(KeyType.CONFIG)); /** * A list of position attributes to log. */ - public static final ConfigKey<String> LOGGER_ATTRIBUTES = new ConfigKey<>( + public static final ConfigKey<String> LOGGER_ATTRIBUTES = new StringConfigKey( "logger.attributes", - Collections.singletonList(KeyType.GLOBAL), + List.of(KeyType.CONFIG), "time,position,speed,course,accuracy,result"); + /** + * Multicast interface. It can be either an IP address or an interface name. + */ + public static final ConfigKey<String> BROADCAST_INTERFACE = new StringConfigKey( + "broadcast.interface", + List.of(KeyType.CONFIG)); + + /** + * Multicast address for broadcasting synchronization events. + */ + public static final ConfigKey<String> BROADCAST_ADDRESS = new StringConfigKey( + "broadcast.address", + List.of(KeyType.CONFIG)); + + /** + * Multicast port for broadcasting synchronization events. + */ + public static final ConfigKey<Integer> BROADCAST_PORT = new IntegerConfigKey( + "broadcast.port", + List.of(KeyType.CONFIG)); + } diff --git a/src/main/java/org/traccar/database/AttributesManager.java b/src/main/java/org/traccar/database/AttributesManager.java deleted file mode 100644 index 28816645a..000000000 --- a/src/main/java/org/traccar/database/AttributesManager.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2017 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import org.traccar.model.Attribute; - -public class AttributesManager extends ExtendedObjectManager<Attribute> { - - public AttributesManager(DataManager dataManager) { - super(dataManager, Attribute.class); - } - - @Override - public void updateCachedItem(Attribute attribute) { - Attribute cachedAttribute = getById(attribute.getId()); - cachedAttribute.setDescription(attribute.getDescription()); - cachedAttribute.setAttribute(attribute.getAttribute()); - cachedAttribute.setExpression(attribute.getExpression()); - cachedAttribute.setType(attribute.getType()); - } - -} diff --git a/src/main/java/org/traccar/database/BaseObjectManager.java b/src/main/java/org/traccar/database/BaseObjectManager.java deleted file mode 100644 index be6310033..000000000 --- a/src/main/java/org/traccar/database/BaseObjectManager.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * 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"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import java.sql.SQLException; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.model.BaseModel; - -public class BaseObjectManager<T extends BaseModel> { - - private static final Logger LOGGER = LoggerFactory.getLogger(BaseObjectManager.class); - - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - - private final DataManager dataManager; - - private final Class<T> baseClass; - private Map<Long, T> items; - - protected BaseObjectManager(DataManager dataManager, Class<T> baseClass) { - this.dataManager = dataManager; - this.baseClass = baseClass; - refreshItems(); - } - - protected final void readLock() { - lock.readLock().lock(); - } - - protected final void readUnlock() { - lock.readLock().unlock(); - } - - protected final void writeLock() { - lock.writeLock().lock(); - } - - protected final void writeUnlock() { - lock.writeLock().unlock(); - } - - protected final DataManager getDataManager() { - return dataManager; - } - - protected final Class<T> getBaseClass() { - return baseClass; - } - - public T getById(long itemId) { - try { - readLock(); - return items.get(itemId); - } finally { - readUnlock(); - } - } - - public void refreshItems() { - if (dataManager != null) { - try { - writeLock(); - Collection<T> databaseItems = dataManager.getObjects(baseClass); - if (items == null) { - items = new ConcurrentHashMap<>(databaseItems.size()); - } - Set<Long> databaseItemIds = new HashSet<>(); - for (T item : databaseItems) { - databaseItemIds.add(item.getId()); - if (items.containsKey(item.getId())) { - updateCachedItem(item); - } else { - addNewItem(item); - } - } - for (Long cachedItemId : items.keySet()) { - if (!databaseItemIds.contains(cachedItemId)) { - removeCachedItem(cachedItemId); - } - } - } catch (SQLException error) { - LOGGER.warn("Error refreshing items", error); - } finally { - writeUnlock(); - } - } - } - - protected void addNewItem(T item) { - try { - writeLock(); - items.put(item.getId(), item); - } finally { - writeUnlock(); - } - } - - public void addItem(T item) throws SQLException { - dataManager.addObject(item); - addNewItem(item); - } - - protected void updateCachedItem(T item) { - try { - writeLock(); - items.put(item.getId(), item); - } finally { - writeUnlock(); - } - } - - public void updateItem(T item) throws SQLException { - dataManager.updateObject(item); - updateCachedItem(item); - } - - protected void removeCachedItem(long itemId) { - try { - writeLock(); - items.remove(itemId); - } finally { - writeUnlock(); - } - } - - public void removeItem(long itemId) throws SQLException { - BaseModel item = getById(itemId); - if (item != null) { - dataManager.removeObject(baseClass, itemId); - removeCachedItem(itemId); - } - } - - public final Collection<T> getItems(Set<Long> itemIds) { - Collection<T> result = new LinkedList<>(); - for (long itemId : itemIds) { - T item = getById(itemId); - if (item != null) { - result.add(item); - } - } - return result; - } - - public Set<Long> getAllItems() { - try { - readLock(); - return items.keySet(); - } finally { - readUnlock(); - } - } - -} diff --git a/src/main/java/org/traccar/database/CommandsManager.java b/src/main/java/org/traccar/database/CommandsManager.java index 843c89e82..df399cd7a 100644 --- a/src/main/java/org/traccar/database/CommandsManager.java +++ b/src/main/java/org/traccar/database/CommandsManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,177 +16,112 @@ */ package org.traccar.database; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.traccar.BaseProtocol; -import org.traccar.Context; +import org.traccar.ServerManager; +import org.traccar.broadcast.BroadcastInterface; +import org.traccar.broadcast.BroadcastService; import org.traccar.model.Command; -import org.traccar.model.Typed; +import org.traccar.model.Device; import org.traccar.model.Position; - -public class CommandsManager extends ExtendedObjectManager<Command> { - - private static final Logger LOGGER = LoggerFactory.getLogger(CommandsManager.class); - - private final Map<Long, Queue<Command>> deviceQueues = new ConcurrentHashMap<>(); - - private final boolean queueing; - - public CommandsManager(DataManager dataManager, boolean queueing) { - super(dataManager, Command.class); - this.queueing = queueing; - } - - public boolean checkDeviceCommand(long deviceId, long commandId) { - return !getAllDeviceItems(deviceId).contains(commandId); +import org.traccar.model.QueuedCommand; +import org.traccar.session.ConnectionManager; +import org.traccar.session.DeviceSession; +import org.traccar.sms.SmsManager; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Order; +import org.traccar.storage.query.Request; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Collection; +import java.util.stream.Collectors; + +@Singleton +public class CommandsManager implements BroadcastInterface { + + private final Storage storage; + private final ServerManager serverManager; + private final SmsManager smsManager; + private final ConnectionManager connectionManager; + private final BroadcastService broadcastService; + + @Inject + public CommandsManager( + Storage storage, ServerManager serverManager, @Nullable SmsManager smsManager, + ConnectionManager connectionManager, BroadcastService broadcastService) { + this.storage = storage; + this.serverManager = serverManager; + this.smsManager = smsManager; + this.connectionManager = connectionManager; + this.broadcastService = broadcastService; + broadcastService.registerListener(this); } public boolean sendCommand(Command command) throws Exception { long deviceId = command.getDeviceId(); - if (command.getId() != 0) { - command = getById(command.getId()).clone(); - command.setDeviceId(deviceId); - } if (command.getTextChannel()) { - Position lastPosition = Context.getIdentityManager().getLastPosition(deviceId); - String phone = Context.getIdentityManager().getById(deviceId).getPhone(); - if (lastPosition != null) { - BaseProtocol protocol = Context.getServerManager().getProtocol(lastPosition.getProtocol()); - protocol.sendTextCommand(phone, command); + if (smsManager == null) { + throw new RuntimeException("SMS not configured"); + } + Device device = storage.getObject(Device.class, new Request( + new Columns.Include("positionId", "phone"), new Condition.Equals("id", deviceId))); + Position position = storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.Equals("id", device.getPositionId()))); + if (position != null) { + BaseProtocol protocol = serverManager.getProtocol(position.getProtocol()); + protocol.sendTextCommand(device.getPhone(), command); } else if (command.getType().equals(Command.TYPE_CUSTOM)) { - if (Context.getSmsManager() != null) { - Context.getSmsManager().sendMessageSync(phone, command.getString(Command.KEY_DATA), true); - } else { - throw new RuntimeException("SMS is not enabled"); - } + smsManager.sendMessage(device.getPhone(), command.getString(Command.KEY_DATA), true); } else { throw new RuntimeException("Command " + command.getType() + " is not supported"); } } else { - ActiveDevice activeDevice = Context.getConnectionManager().getActiveDevice(deviceId); - if (activeDevice != null) { - if (activeDevice.supportsLiveCommands()) { - activeDevice.sendCommand(command); - } else { - getDeviceQueue(deviceId).add(command); - return false; - } - } else if (!queueing) { - throw new RuntimeException("Device is not online"); + DeviceSession deviceSession = connectionManager.getDeviceSession(deviceId); + if (deviceSession != null && deviceSession.supportsLiveCommands()) { + deviceSession.sendCommand(command); } else { - getDeviceQueue(deviceId).add(command); + storage.addObject(QueuedCommand.fromCommand(command), new Request(new Columns.Exclude("id"))); + broadcastService.updateCommand(true, deviceId); return false; } } return true; } - public Collection<Long> getSupportedCommands(long deviceId) { - List<Long> result = new ArrayList<>(); - Position lastPosition = Context.getIdentityManager().getLastPosition(deviceId); - for (long commandId : getAllDeviceItems(deviceId)) { - Command command = getById(commandId); - if (lastPosition != null) { - BaseProtocol protocol = Context.getServerManager().getProtocol(lastPosition.getProtocol()); - if (command.getTextChannel() && protocol.getSupportedTextCommands().contains(command.getType()) - || !command.getTextChannel() - && protocol.getSupportedDataCommands().contains(command.getType())) { - result.add(commandId); - } - } else if (command.getType().equals(Command.TYPE_CUSTOM)) { - result.add(commandId); - } - } - return result; - } - - public Collection<Typed> getCommandTypes(long deviceId, boolean textChannel) { - Position lastPosition = Context.getIdentityManager().getLastPosition(deviceId); - if (lastPosition != null) { - return getCommandTypes(lastPosition.getProtocol(), textChannel); - } else { - return Collections.singletonList(new Typed(Command.TYPE_CUSTOM)); - } - } - - public Collection<Typed> getCommandTypes(String protocolName, boolean textChannel) { - List<Typed> result = new ArrayList<>(); - BaseProtocol protocol = Context.getServerManager().getProtocol(protocolName); - Collection<String> commands; - commands = textChannel ? protocol.getSupportedTextCommands() : protocol.getSupportedDataCommands(); - for (String commandKey : commands) { - result.add(new Typed(commandKey)); - } - return result; - } - - public Collection<Typed> getAllCommandTypes() { - List<Typed> result = new ArrayList<>(); - Field[] fields = Command.class.getDeclaredFields(); - for (Field field : fields) { - if (Modifier.isStatic(field.getModifiers()) && field.getName().startsWith("TYPE_")) { - try { - result.add(new Typed(field.get(null).toString())); - } catch (IllegalArgumentException | IllegalAccessException error) { - LOGGER.warn("Get command types error", error); - } - } - } - return result; - } - - private Queue<Command> getDeviceQueue(long deviceId) { - Queue<Command> deviceQueue; - try { - readLock(); - deviceQueue = deviceQueues.get(deviceId); - } finally { - readUnlock(); - } - if (deviceQueue != null) { - return deviceQueue; - } else { - try { - writeLock(); - return deviceQueues.computeIfAbsent(deviceId, key -> new ConcurrentLinkedQueue<>()); - } finally { - writeUnlock(); - } - } - } - public Collection<Command> readQueuedCommands(long deviceId) { return readQueuedCommands(deviceId, Integer.MAX_VALUE); } public Collection<Command> readQueuedCommands(long deviceId, int count) { - Queue<Command> deviceQueue; try { - readLock(); - deviceQueue = deviceQueues.get(deviceId); - } finally { - readUnlock(); + var commands = storage.getObjects(QueuedCommand.class, new Request( + new Columns.All(), + new Condition.Equals("deviceId", deviceId), + new Order("id", false, count))); + for (var command : commands) { + storage.removeObject(QueuedCommand.class, new Request( + new Condition.Equals("id", command.getId()))); + } + return commands.stream().map(QueuedCommand::toCommand).collect(Collectors.toList()); + } catch (StorageException e) { + throw new RuntimeException(e); } - Collection<Command> result = new ArrayList<>(); - if (deviceQueue != null) { - Command command = deviceQueue.poll(); - while (command != null && result.size() < count) { - result.add(command); - command = deviceQueue.poll(); + } + + @Override + public void updateCommand(boolean local, long deviceId) { + if (!local) { + DeviceSession deviceSession = connectionManager.getDeviceSession(deviceId); + if (deviceSession != null && deviceSession.supportsLiveCommands()) { + for (Command command : readQueuedCommands(deviceId)) { + deviceSession.sendCommand(command); + } } } - return result; } } diff --git a/src/main/java/org/traccar/database/ConnectionManager.java b/src/main/java/org/traccar/database/ConnectionManager.java deleted file mode 100644 index e12d44612..000000000 --- a/src/main/java/org/traccar/database/ConnectionManager.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import io.netty.channel.Channel; -import io.netty.util.Timeout; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.GlobalTimer; -import org.traccar.Main; -import org.traccar.Protocol; -import org.traccar.config.Keys; -import org.traccar.handler.events.MotionEventHandler; -import org.traccar.handler.events.OverspeedEventHandler; -import org.traccar.model.Device; -import org.traccar.model.DeviceState; -import org.traccar.model.Event; -import org.traccar.model.Position; - -import java.net.SocketAddress; -import java.sql.SQLException; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -public class ConnectionManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class); - - private final long deviceTimeout; - private final boolean updateDeviceState; - - private final Map<Long, ActiveDevice> activeDevices = new ConcurrentHashMap<>(); - private final Map<Long, Set<UpdateListener>> listeners = new ConcurrentHashMap<>(); - private final Map<Long, Timeout> timeouts = new ConcurrentHashMap<>(); - - public ConnectionManager() { - deviceTimeout = Context.getConfig().getLong(Keys.STATUS_TIMEOUT) * 1000; - updateDeviceState = Context.getConfig().getBoolean(Keys.STATUS_UPDATE_DEVICE_STATE); - } - - public void addActiveDevice(long deviceId, Protocol protocol, Channel channel, SocketAddress remoteAddress) { - activeDevices.put(deviceId, new ActiveDevice(deviceId, protocol, channel, remoteAddress)); - } - - public void removeActiveDevice(Channel channel) { - for (ActiveDevice activeDevice : activeDevices.values()) { - if (activeDevice.getChannel() == channel) { - updateDevice(activeDevice.getDeviceId(), Device.STATUS_OFFLINE, null); - activeDevices.remove(activeDevice.getDeviceId()); - break; - } - } - } - - public ActiveDevice getActiveDevice(long deviceId) { - return activeDevices.get(deviceId); - } - - public void updateDevice(final long deviceId, String status, Date time) { - Device device = Context.getIdentityManager().getById(deviceId); - if (device == null) { - return; - } - - String oldStatus = device.getStatus(); - device.setStatus(status); - - if (!status.equals(oldStatus)) { - String eventType; - Map<Event, Position> events = new HashMap<>(); - switch (status) { - case Device.STATUS_ONLINE: - eventType = Event.TYPE_DEVICE_ONLINE; - break; - case Device.STATUS_UNKNOWN: - eventType = Event.TYPE_DEVICE_UNKNOWN; - if (updateDeviceState) { - events.putAll(updateDeviceState(deviceId)); - } - break; - default: - eventType = Event.TYPE_DEVICE_OFFLINE; - if (updateDeviceState) { - events.putAll(updateDeviceState(deviceId)); - } - break; - } - events.put(new Event(eventType, deviceId), null); - Context.getNotificationManager().updateEvents(events); - } - - Timeout timeout = timeouts.remove(deviceId); - if (timeout != null) { - timeout.cancel(); - } - - if (time != null) { - device.setLastUpdate(time); - } - - if (status.equals(Device.STATUS_ONLINE)) { - timeouts.put(deviceId, GlobalTimer.getTimer().newTimeout(timeout1 -> { - if (!timeout1.isCancelled()) { - updateDevice(deviceId, Device.STATUS_UNKNOWN, null); - } - }, deviceTimeout, TimeUnit.MILLISECONDS)); - } - - try { - Context.getDeviceManager().updateDeviceStatus(device); - } catch (SQLException error) { - LOGGER.warn("Update device status error", error); - } - - updateDevice(device); - } - - public Map<Event, Position> updateDeviceState(long deviceId) { - DeviceState deviceState = Context.getDeviceManager().getDeviceState(deviceId); - Map<Event, Position> result = new HashMap<>(); - - Map<Event, Position> event = Main.getInjector() - .getInstance(MotionEventHandler.class).updateMotionState(deviceState); - if (event != null) { - result.putAll(event); - } - - event = Main.getInjector().getInstance(OverspeedEventHandler.class) - .updateOverspeedState(deviceState, Context.getDeviceManager(). - lookupAttributeDouble(deviceId, OverspeedEventHandler.ATTRIBUTE_SPEED_LIMIT, 0, true, false)); - if (event != null) { - result.putAll(event); - } - - return result; - } - - public synchronized void sendKeepalive() { - for (Set<UpdateListener> userListeners : listeners.values()) { - for (UpdateListener listener : userListeners) { - listener.onKeepalive(); - } - } - } - - public synchronized void updateDevice(Device device) { - for (long userId : Context.getPermissionsManager().getDeviceUsers(device.getId())) { - if (listeners.containsKey(userId)) { - for (UpdateListener listener : listeners.get(userId)) { - listener.onUpdateDevice(device); - } - } - } - } - - public synchronized void updatePosition(Position position) { - long deviceId = position.getDeviceId(); - - for (long userId : Context.getPermissionsManager().getDeviceUsers(deviceId)) { - if (listeners.containsKey(userId)) { - for (UpdateListener listener : listeners.get(userId)) { - listener.onUpdatePosition(position); - } - } - } - } - - public synchronized void updateEvent(long userId, Event event) { - if (listeners.containsKey(userId)) { - for (UpdateListener listener : listeners.get(userId)) { - listener.onUpdateEvent(event); - } - } - } - - public interface UpdateListener { - void onKeepalive(); - void onUpdateDevice(Device device); - void onUpdatePosition(Position position); - void onUpdateEvent(Event event); - } - - public synchronized void addListener(long userId, UpdateListener listener) { - if (!listeners.containsKey(userId)) { - listeners.put(userId, new HashSet<>()); - } - listeners.get(userId).add(listener); - } - - public synchronized void removeListener(long userId, UpdateListener listener) { - if (!listeners.containsKey(userId)) { - listeners.put(userId, new HashSet<>()); - } - listeners.get(userId).remove(listener); - } - -} diff --git a/src/main/java/org/traccar/database/DataManager.java b/src/main/java/org/traccar/database/DataManager.java deleted file mode 100644 index 00c802fde..000000000 --- a/src/main/java/org/traccar/database/DataManager.java +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import liquibase.Contexts; -import liquibase.Liquibase; -import liquibase.database.Database; -import liquibase.database.DatabaseFactory; -import liquibase.exception.LiquibaseException; -import liquibase.resource.FileSystemResourceAccessor; -import liquibase.resource.ResourceAccessor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.config.Config; -import org.traccar.config.Keys; -import org.traccar.model.Attribute; -import org.traccar.model.BaseModel; -import org.traccar.model.Calendar; -import org.traccar.model.Command; -import org.traccar.model.Device; -import org.traccar.model.Driver; -import org.traccar.model.Event; -import org.traccar.model.Geofence; -import org.traccar.model.Group; -import org.traccar.model.Maintenance; -import org.traccar.model.ManagedUser; -import org.traccar.model.Notification; -import org.traccar.model.Order; -import org.traccar.model.Permission; -import org.traccar.model.Position; -import org.traccar.model.Server; -import org.traccar.model.Statistics; -import org.traccar.model.User; - -import javax.sql.DataSource; -import java.beans.Introspector; -import java.io.File; -import java.lang.reflect.Method; -import java.net.URL; -import java.sql.SQLException; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.Set; - -public class DataManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(DataManager.class); - - public static final String ACTION_SELECT_ALL = "selectAll"; - public static final String ACTION_SELECT = "select"; - public static final String ACTION_INSERT = "insert"; - public static final String ACTION_UPDATE = "update"; - public static final String ACTION_DELETE = "delete"; - - private final Config config; - - private DataSource dataSource; - - public DataSource getDataSource() { - return dataSource; - } - - private boolean generateQueries; - - private final boolean forceLdap; - - public DataManager(Config config) throws Exception { - this.config = config; - - forceLdap = config.getBoolean(Keys.LDAP_FORCE); - - initDatabase(); - initDatabaseSchema(); - } - - private void initDatabase() throws Exception { - - String driverFile = config.getString(Keys.DATABASE_DRIVER_FILE); - if (driverFile != null) { - ClassLoader classLoader = ClassLoader.getSystemClassLoader(); - try { - Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class); - method.setAccessible(true); - method.invoke(classLoader, new File(driverFile).toURI().toURL()); - } catch (NoSuchMethodException e) { - Method method = classLoader.getClass() - .getDeclaredMethod("appendToClassPathForInstrumentation", String.class); - method.setAccessible(true); - method.invoke(classLoader, driverFile); - } - } - - String driver = config.getString(Keys.DATABASE_DRIVER); - if (driver != null) { - Class.forName(driver); - } - - HikariConfig hikariConfig = new HikariConfig(); - hikariConfig.setDriverClassName(driver); - hikariConfig.setJdbcUrl(config.getString(Keys.DATABASE_URL)); - hikariConfig.setUsername(config.getString(Keys.DATABASE_USER)); - hikariConfig.setPassword(config.getString(Keys.DATABASE_PASSWORD)); - hikariConfig.setConnectionInitSql(config.getString(Keys.DATABASE_CHECK_CONNECTION)); - hikariConfig.setIdleTimeout(600000); - - int maxPoolSize = config.getInteger(Keys.DATABASE_MAX_POOL_SIZE); - if (maxPoolSize != 0) { - hikariConfig.setMaximumPoolSize(maxPoolSize); - } - - generateQueries = config.getBoolean(Keys.DATABASE_GENERATE_QUERIES); - - dataSource = new HikariDataSource(hikariConfig); - } - - public static String constructObjectQuery(String action, Class<?> clazz, boolean extended) { - switch (action) { - case ACTION_INSERT: - case ACTION_UPDATE: - StringBuilder result = new StringBuilder(); - StringBuilder fields = new StringBuilder(); - StringBuilder values = new StringBuilder(); - - Set<Method> methods = new HashSet<>(Arrays.asList(clazz.getMethods())); - methods.removeAll(Arrays.asList(Object.class.getMethods())); - methods.removeAll(Arrays.asList(BaseModel.class.getMethods())); - for (Method method : methods) { - boolean skip; - if (extended) { - skip = !method.isAnnotationPresent(QueryExtended.class); - } else { - skip = method.isAnnotationPresent(QueryIgnore.class) - || method.isAnnotationPresent(QueryExtended.class) && !action.equals(ACTION_INSERT); - } - if (!skip && method.getName().startsWith("get") && method.getParameterTypes().length == 0) { - String name = Introspector.decapitalize(method.getName().substring(3)); - if (action.equals(ACTION_INSERT)) { - fields.append(name).append(", "); - values.append(":").append(name).append(", "); - } else { - fields.append(name).append(" = :").append(name).append(", "); - } - } - } - fields.setLength(fields.length() - 2); - if (action.equals(ACTION_INSERT)) { - values.setLength(values.length() - 2); - result.append("INSERT INTO ").append(getObjectsTableName(clazz)).append(" ("); - result.append(fields).append(") "); - result.append("VALUES (").append(values).append(")"); - } else { - result.append("UPDATE ").append(getObjectsTableName(clazz)).append(" SET "); - result.append(fields); - result.append(" WHERE id = :id"); - } - return result.toString(); - case ACTION_SELECT_ALL: - return "SELECT * FROM " + getObjectsTableName(clazz); - case ACTION_SELECT: - return "SELECT * FROM " + getObjectsTableName(clazz) + " WHERE id = :id"; - case ACTION_DELETE: - return "DELETE FROM " + getObjectsTableName(clazz) + " WHERE id = :id"; - default: - throw new IllegalArgumentException("Unknown action"); - } - } - - public static String constructPermissionQuery(String action, Class<?> owner, Class<?> property) { - switch (action) { - case ACTION_SELECT_ALL: - return "SELECT " + makeNameId(owner) + ", " + makeNameId(property) + " FROM " - + getPermissionsTableName(owner, property); - case ACTION_INSERT: - return "INSERT INTO " + getPermissionsTableName(owner, property) - + " (" + makeNameId(owner) + ", " + makeNameId(property) + ") VALUES (:" - + makeNameId(owner) + ", :" + makeNameId(property) + ")"; - case ACTION_DELETE: - return "DELETE FROM " + getPermissionsTableName(owner, property) - + " WHERE " + makeNameId(owner) + " = :" + makeNameId(owner) - + " AND " + makeNameId(property) + " = :" + makeNameId(property); - default: - throw new IllegalArgumentException("Unknown action"); - } - } - - private String getQuery(String key) { - String query = config.getString(key); - if (query == null) { - LOGGER.info("Query not provided: " + key); - } - return query; - } - - public String getQuery(String action, Class<?> clazz) { - return getQuery(action, clazz, false); - } - - public String getQuery(String action, Class<?> clazz, boolean extended) { - String queryName; - if (action.equals(ACTION_SELECT_ALL)) { - queryName = "database.select" + clazz.getSimpleName() + "s"; - } else { - queryName = "database." + action.toLowerCase() + clazz.getSimpleName(); - if (extended) { - queryName += "Extended"; - } - } - String query = config.getString(queryName); - if (query == null) { - if (generateQueries) { - query = constructObjectQuery(action, clazz, extended); - } else { - LOGGER.info("Query not provided: " + queryName); - } - } - return query; - } - - public String getQuery(String action, Class<?> owner, Class<?> property) { - String queryName; - switch (action) { - case ACTION_SELECT_ALL: - queryName = "database.select" + owner.getSimpleName() + property.getSimpleName() + "s"; - break; - case ACTION_INSERT: - queryName = "database.link" + owner.getSimpleName() + property.getSimpleName(); - break; - default: - queryName = "database.unlink" + owner.getSimpleName() + property.getSimpleName(); - break; - } - String query = config.getString(queryName); - if (query == null) { - if (generateQueries) { - query = constructPermissionQuery( - action, owner, property.equals(User.class) ? ManagedUser.class : property); - } else { - LOGGER.info("Query not provided: " + queryName); - } - } - return query; - } - - private static String getPermissionsTableName(Class<?> owner, Class<?> property) { - String propertyName = property.getSimpleName(); - if (propertyName.equals("ManagedUser")) { - propertyName = "User"; - } - return "tc_" + Introspector.decapitalize(owner.getSimpleName()) - + "_" + Introspector.decapitalize(propertyName); - } - - private static String getObjectsTableName(Class<?> clazz) { - String result = "tc_" + Introspector.decapitalize(clazz.getSimpleName()); - // Add "s" ending if object name is not plural already - if (!result.endsWith("s")) { - result += "s"; - } - return result; - } - - private void initDatabaseSchema() throws LiquibaseException { - - if (config.hasKey(Keys.DATABASE_CHANGELOG)) { - - ResourceAccessor resourceAccessor = new FileSystemResourceAccessor(new File(".")); - - Database database = DatabaseFactory.getInstance().openDatabase( - config.getString(Keys.DATABASE_URL), - config.getString(Keys.DATABASE_USER), - config.getString(Keys.DATABASE_PASSWORD), - config.getString(Keys.DATABASE_DRIVER), - null, null, null, resourceAccessor); - - Liquibase liquibase = new Liquibase( - config.getString(Keys.DATABASE_CHANGELOG), resourceAccessor, database); - - liquibase.clearCheckSums(); - - liquibase.update(new Contexts()); - } - } - - public User login(String email, String password) throws SQLException { - User user = QueryBuilder.create(dataSource, getQuery("database.loginUser")) - .setString("email", email.trim()) - .executeQuerySingle(User.class); - LdapProvider ldapProvider = Context.getLdapProvider(); - if (user != null) { - if (ldapProvider != null && user.getLogin() != null && ldapProvider.login(user.getLogin(), password) - || !forceLdap && user.isPasswordValid(password)) { - return user; - } - } else { - if (ldapProvider != null && ldapProvider.login(email, password)) { - user = ldapProvider.getUser(email); - Context.getUsersManager().addItem(user); - return user; - } - } - return null; - } - - public void updateDeviceStatus(Device device) throws SQLException { - QueryBuilder.create(dataSource, getQuery(ACTION_UPDATE, Device.class, true)) - .setObject(device) - .executeUpdate(); - } - - public Collection<Position> getPositions(long deviceId, Date from, Date to) throws SQLException { - return QueryBuilder.create(dataSource, getQuery("database.selectPositions")) - .setLong("deviceId", deviceId) - .setDate("from", from) - .setDate("to", to) - .executeQuery(Position.class); - } - - public void updateLatestPosition(Position position) throws SQLException { - QueryBuilder.create(dataSource, getQuery("database.updateLatestPosition")) - .setDate("now", new Date()) - .setObject(position) - .executeUpdate(); - } - - public Collection<Position> getLatestPositions() throws SQLException { - return QueryBuilder.create(dataSource, getQuery("database.selectLatestPositions")) - .executeQuery(Position.class); - } - - public Server getServer() throws SQLException { - return QueryBuilder.create(dataSource, getQuery(ACTION_SELECT_ALL, Server.class)) - .executeQuerySingle(Server.class); - } - - public Collection<Event> getEvents(long deviceId, Date from, Date to) throws SQLException { - return QueryBuilder.create(dataSource, getQuery("database.selectEvents")) - .setLong("deviceId", deviceId) - .setDate("from", from) - .setDate("to", to) - .executeQuery(Event.class); - } - - public Collection<Statistics> getStatistics(Date from, Date to) throws SQLException { - return QueryBuilder.create(dataSource, getQuery("database.selectStatistics")) - .setDate("from", from) - .setDate("to", to) - .executeQuery(Statistics.class); - } - - public static Class<?> getClassByName(String name) throws ClassNotFoundException { - switch (name.toLowerCase().replace("id", "")) { - case "device": - return Device.class; - case "group": - return Group.class; - case "user": - return User.class; - case "manageduser": - return ManagedUser.class; - case "geofence": - return Geofence.class; - case "driver": - return Driver.class; - case "attribute": - return Attribute.class; - case "calendar": - return Calendar.class; - case "command": - return Command.class; - case "maintenance": - return Maintenance.class; - case "notification": - return Notification.class; - case "order": - return Order.class; - default: - throw new ClassNotFoundException(); - } - } - - private static String makeNameId(Class<?> clazz) { - String name = clazz.getSimpleName(); - return Introspector.decapitalize(name) + (!name.contains("Id") ? "Id" : ""); - } - - public Collection<Permission> getPermissions(Class<? extends BaseModel> owner, Class<? extends BaseModel> property) - throws SQLException, ClassNotFoundException { - return QueryBuilder.create(dataSource, getQuery(ACTION_SELECT_ALL, owner, property)) - .executePermissionsQuery(); - } - - public void linkObject(Class<?> owner, long ownerId, Class<?> property, long propertyId, boolean link) - throws SQLException { - QueryBuilder.create(dataSource, getQuery(link ? ACTION_INSERT : ACTION_DELETE, owner, property)) - .setLong(makeNameId(owner), ownerId) - .setLong(makeNameId(property), propertyId) - .executeUpdate(); - } - - public <T extends BaseModel> T getObject(Class<T> clazz, long entityId) throws SQLException { - return QueryBuilder.create(dataSource, getQuery(ACTION_SELECT, clazz)) - .setLong("id", entityId) - .executeQuerySingle(clazz); - } - - public <T extends BaseModel> Collection<T> getObjects(Class<T> clazz) throws SQLException { - return QueryBuilder.create(dataSource, getQuery(ACTION_SELECT_ALL, clazz)) - .executeQuery(clazz); - } - - public void addObject(BaseModel entity) throws SQLException { - entity.setId(QueryBuilder.create(dataSource, getQuery(ACTION_INSERT, entity.getClass()), true) - .setObject(entity) - .executeUpdate()); - } - - public void updateObject(BaseModel entity) throws SQLException { - QueryBuilder.create(dataSource, getQuery(ACTION_UPDATE, entity.getClass())) - .setObject(entity) - .executeUpdate(); - if (entity instanceof User && ((User) entity).getHashedPassword() != null) { - QueryBuilder.create(dataSource, getQuery(ACTION_UPDATE, User.class, true)) - .setObject(entity) - .executeUpdate(); - } - } - - public void removeObject(Class<? extends BaseModel> clazz, long entityId) throws SQLException { - QueryBuilder.create(dataSource, getQuery(ACTION_DELETE, clazz)) - .setLong("id", entityId) - .executeUpdate(); - } - -} diff --git a/src/main/java/org/traccar/database/DeviceLookupService.java b/src/main/java/org/traccar/database/DeviceLookupService.java new file mode 100644 index 000000000..583b2ae35 --- /dev/null +++ b/src/main/java/org/traccar/database/DeviceLookupService.java @@ -0,0 +1,141 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.model.Device; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +@Singleton +public class DeviceLookupService { + + private static final Logger LOGGER = LoggerFactory.getLogger(DeviceLookupService.class); + + private static final long INFO_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(60); + private static final long THROTTLE_MIN_MS = TimeUnit.MINUTES.toMillis(1); + private static final long THROTTLE_MAX_MS = TimeUnit.MINUTES.toMillis(30); + + private final Storage storage; + private final Timer timer; + + private final boolean throttlingEnabled; + + private static class IdentifierInfo { + private long lastQuery; + private long delay; + private Timeout timeout; + } + + private final class IdentifierTask implements TimerTask { + private final String uniqueId; + + private IdentifierTask(String uniqueId) { + this.uniqueId = uniqueId; + } + + @Override + public void run(Timeout timeout) { + LOGGER.debug("Device lookup expired {}", uniqueId); + synchronized (DeviceLookupService.this) { + identifierMap.remove(uniqueId); + } + } + } + + private final Map<String, IdentifierInfo> identifierMap = new ConcurrentHashMap<>(); + + @Inject + public DeviceLookupService(Config config, Storage storage, Timer timer) { + this.storage = storage; + this.timer = timer; + throttlingEnabled = config.getBoolean(Keys.DATABASE_THROTTLE_UNKNOWN); + } + + private synchronized boolean isThrottled(String uniqueId) { + if (throttlingEnabled) { + IdentifierInfo info = identifierMap.get(uniqueId); + return info != null && System.currentTimeMillis() < info.lastQuery + info.delay; + } else { + return false; + } + } + + private synchronized void lookupSucceeded(String uniqueId) { + if (throttlingEnabled) { + IdentifierInfo info = identifierMap.remove(uniqueId); + if (info != null) { + info.timeout.cancel(); + } + } + } + + private synchronized void lookupFailed(String uniqueId) { + if (throttlingEnabled) { + IdentifierInfo info = identifierMap.get(uniqueId); + if (info != null) { + info.timeout.cancel(); + info.delay = Math.min(info.delay * 2, THROTTLE_MAX_MS); + } else { + info = new IdentifierInfo(); + identifierMap.put(uniqueId, info); + info.delay = THROTTLE_MIN_MS; + } + info.lastQuery = System.currentTimeMillis(); + info.timeout = timer.newTimeout(new IdentifierTask(uniqueId), INFO_TIMEOUT_MS, TimeUnit.MILLISECONDS); + LOGGER.debug("Device lookup {} throttled for {} ms", uniqueId, info.delay); + } + } + + public Device lookup(String[] uniqueIds) { + Device device = null; + try { + for (String uniqueId : uniqueIds) { + if (!isThrottled(uniqueId)) { + device = storage.getObject(Device.class, new Request( + new Columns.All(), new Condition.Equals("uniqueId", uniqueId))); + if (device != null) { + lookupSucceeded(uniqueId); + break; + } else { + lookupFailed(uniqueId); + } + } else { + LOGGER.debug("Device lookup throttled {}", uniqueId); + } + } + } catch (StorageException e) { + LOGGER.warn("Find device error", e); + } + return device; + } + +} diff --git a/src/main/java/org/traccar/database/DeviceManager.java b/src/main/java/org/traccar/database/DeviceManager.java deleted file mode 100644 index c8a99274c..000000000 --- a/src/main/java/org/traccar/database/DeviceManager.java +++ /dev/null @@ -1,483 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import java.sql.SQLException; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.config.Config; -import org.traccar.Context; -import org.traccar.config.Keys; -import org.traccar.model.Command; -import org.traccar.model.Device; -import org.traccar.model.DeviceState; -import org.traccar.model.DeviceAccumulators; -import org.traccar.model.Group; -import org.traccar.model.Position; -import org.traccar.model.Server; - -public class DeviceManager extends BaseObjectManager<Device> implements IdentityManager, ManagableObjects { - - private static final Logger LOGGER = LoggerFactory.getLogger(DeviceManager.class); - - private final Config config; - private final long dataRefreshDelay; - - private Map<String, Device> devicesByUniqueId; - private Map<String, Device> devicesByPhone; - private final AtomicLong devicesLastUpdate = new AtomicLong(); - - private final Map<Long, Position> positions = new ConcurrentHashMap<>(); - - private final Map<Long, DeviceState> deviceStates = new ConcurrentHashMap<>(); - - public DeviceManager(DataManager dataManager) { - super(dataManager, Device.class); - this.config = Context.getConfig(); - try { - writeLock(); - if (devicesByPhone == null) { - devicesByPhone = new ConcurrentHashMap<>(); - } - if (devicesByUniqueId == null) { - devicesByUniqueId = new ConcurrentHashMap<>(); - } - } finally { - writeUnlock(); - } - dataRefreshDelay = config.getLong(Keys.DATABASE_REFRESH_DELAY) * 1000; - refreshLastPositions(); - } - - @Override - public long addUnknownDevice(String uniqueId) { - Device device = new Device(); - device.setName(uniqueId); - device.setUniqueId(uniqueId); - device.setCategory(Context.getConfig().getString(Keys.DATABASE_REGISTER_UNKNOWN_DEFAULT_CATEGORY)); - - long defaultGroupId = Context.getConfig().getLong(Keys.DATABASE_REGISTER_UNKNOWN_DEFAULT_GROUP_ID); - if (defaultGroupId != 0) { - device.setGroupId(defaultGroupId); - } - - try { - addItem(device); - - LOGGER.info("Automatically registered device " + uniqueId); - - if (defaultGroupId != 0) { - Context.getPermissionsManager().refreshDeviceAndGroupPermissions(); - Context.getPermissionsManager().refreshAllExtendedPermissions(); - } - - return device.getId(); - } catch (SQLException e) { - LOGGER.warn("Automatic device registration error", e); - return 0; - } - } - - public void updateDeviceCache(boolean force) throws SQLException { - long lastUpdate = devicesLastUpdate.get(); - if ((force || System.currentTimeMillis() - lastUpdate > dataRefreshDelay) - && devicesLastUpdate.compareAndSet(lastUpdate, System.currentTimeMillis())) { - refreshItems(); - } - } - - @Override - public Device getByUniqueId(String uniqueId) throws SQLException { - boolean forceUpdate; - try { - readLock(); - forceUpdate = !devicesByUniqueId.containsKey(uniqueId) && !config.getBoolean(Keys.DATABASE_IGNORE_UNKNOWN); - } finally { - readUnlock(); - } - updateDeviceCache(forceUpdate); - try { - readLock(); - return devicesByUniqueId.get(uniqueId); - } finally { - readUnlock(); - } - } - - @Override - public String getDevicePassword(long id, String protocol, String defaultPassword) { - - String password = lookupAttributeString(id, Command.KEY_DEVICE_PASSWORD, null, false, false); - if (password != null) { - return password; - } - - if (protocol != null) { - password = Context.getConfig().getString(Keys.PROTOCOL_DEVICE_PASSWORD.withPrefix(protocol)); - if (password != null) { - return password; - } - } - - return defaultPassword; - } - - public Device getDeviceByPhone(String phone) { - try { - readLock(); - return devicesByPhone.get(phone); - } finally { - readUnlock(); - } - } - - @Override - public Set<Long> getAllItems() { - Set<Long> result = super.getAllItems(); - if (result.isEmpty()) { - try { - updateDeviceCache(true); - } catch (SQLException e) { - LOGGER.warn("Update device cache error", e); - } - result = super.getAllItems(); - } - return result; - } - - public Collection<Device> getAllDevices() { - return getItems(getAllItems()); - } - - public Set<Long> getAllUserItems(long userId) { - return Context.getPermissionsManager().getDevicePermissions(userId); - } - - @Override - public Set<Long> getUserItems(long userId) { - if (Context.getPermissionsManager() != null) { - Set<Long> result = new HashSet<>(); - for (long deviceId : Context.getPermissionsManager().getDevicePermissions(userId)) { - Device device = getById(deviceId); - if (device != null && !device.getDisabled()) { - result.add(deviceId); - } - } - return result; - } else { - return new HashSet<>(); - } - } - - public Set<Long> getAllManagedItems(long userId) { - Set<Long> result = new HashSet<>(getAllUserItems(userId)); - for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { - result.addAll(getAllUserItems(managedUserId)); - } - return result; - } - - @Override - public Set<Long> getManagedItems(long userId) { - Set<Long> result = new HashSet<>(getUserItems(userId)); - for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { - result.addAll(getUserItems(managedUserId)); - } - return result; - } - - private void addByUniqueId(Device device) { - try { - writeLock(); - if (devicesByUniqueId == null) { - devicesByUniqueId = new ConcurrentHashMap<>(); - } - devicesByUniqueId.put(device.getUniqueId(), device); - } finally { - writeUnlock(); - } - } - - private void removeByUniqueId(String deviceUniqueId) { - try { - writeLock(); - if (devicesByUniqueId != null) { - devicesByUniqueId.remove(deviceUniqueId); - } - } finally { - writeUnlock(); - } - } - - private void addByPhone(Device device) { - try { - writeLock(); - if (devicesByPhone == null) { - devicesByPhone = new ConcurrentHashMap<>(); - } - devicesByPhone.put(device.getPhone(), device); - } finally { - writeUnlock(); - } - } - - private void removeByPhone(String phone) { - if (phone == null || phone.isEmpty()) { - return; - } - try { - writeLock(); - if (devicesByPhone != null) { - devicesByPhone.remove(phone); - } - } finally { - writeUnlock(); - } - } - - @Override - protected void addNewItem(Device device) { - super.addNewItem(device); - addByUniqueId(device); - if (device.getPhone() != null && !device.getPhone().isEmpty()) { - addByPhone(device); - } - if (Context.getGeofenceManager() != null) { - Position lastPosition = getLastPosition(device.getId()); - if (lastPosition != null) { - device.setGeofenceIds(Context.getGeofenceManager().getCurrentDeviceGeofences(lastPosition)); - } - } - } - - @Override - protected void updateCachedItem(Device device) { - Device cachedDevice = getById(device.getId()); - cachedDevice.setName(device.getName()); - cachedDevice.setGroupId(device.getGroupId()); - cachedDevice.setCategory(device.getCategory()); - cachedDevice.setContact(device.getContact()); - cachedDevice.setModel(device.getModel()); - cachedDevice.setDisabled(device.getDisabled()); - cachedDevice.setAttributes(device.getAttributes()); - if (!device.getUniqueId().equals(cachedDevice.getUniqueId())) { - removeByUniqueId(cachedDevice.getUniqueId()); - cachedDevice.setUniqueId(device.getUniqueId()); - addByUniqueId(cachedDevice); - } - if (device.getPhone() != null && !device.getPhone().isEmpty() - && !device.getPhone().equals(cachedDevice.getPhone())) { - String phone = cachedDevice.getPhone(); - removeByPhone(phone); - cachedDevice.setPhone(device.getPhone()); - addByPhone(cachedDevice); - } - } - - @Override - protected void removeCachedItem(long deviceId) { - Device cachedDevice = getById(deviceId); - if (cachedDevice != null) { - String deviceUniqueId = cachedDevice.getUniqueId(); - String phone = cachedDevice.getPhone(); - super.removeCachedItem(deviceId); - removeByUniqueId(deviceUniqueId); - removeByPhone(phone); - } - positions.remove(deviceId); - } - - public void updateDeviceStatus(Device device) throws SQLException { - getDataManager().updateDeviceStatus(device); - Device cachedDevice = getById(device.getId()); - if (cachedDevice != null) { - cachedDevice.setStatus(device.getStatus()); - } - } - - private void refreshLastPositions() { - if (getDataManager() != null) { - try { - for (Position position : getDataManager().getLatestPositions()) { - positions.put(position.getDeviceId(), position); - } - } catch (SQLException error) { - LOGGER.warn("Load latest positions error", error); - } - } - } - - public boolean isLatestPosition(Position position) { - Position lastPosition = getLastPosition(position.getDeviceId()); - return lastPosition == null || position.getFixTime().compareTo(lastPosition.getFixTime()) >= 0; - } - - public void updateLatestPosition(Position position) throws SQLException { - - if (isLatestPosition(position)) { - - getDataManager().updateLatestPosition(position); - - Device device = getById(position.getDeviceId()); - if (device != null) { - device.setPositionId(position.getId()); - } - - positions.put(position.getDeviceId(), position); - - if (Context.getConnectionManager() != null) { - Context.getConnectionManager().updatePosition(position); - } - } - } - - @Override - public Position getLastPosition(long deviceId) { - return positions.get(deviceId); - } - - public Collection<Position> getInitialState(long userId) { - - List<Position> result = new LinkedList<>(); - - if (Context.getPermissionsManager() != null) { - for (long deviceId : Context.getPermissionsManager().getUserAdmin(userId) - ? getAllUserItems(userId) : getUserItems(userId)) { - if (positions.containsKey(deviceId)) { - result.add(positions.get(deviceId)); - } - } - } - - return result; - } - - @Override - public boolean lookupAttributeBoolean( - long deviceId, String attributeName, boolean defaultValue, boolean lookupServer, boolean lookupConfig) { - Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); - if (result != null) { - return result instanceof String ? Boolean.parseBoolean((String) result) : (Boolean) result; - } - return defaultValue; - } - - @Override - public String lookupAttributeString( - long deviceId, String attributeName, String defaultValue, boolean lookupServer, boolean lookupConfig) { - Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); - return result != null ? (String) result : defaultValue; - } - - @Override - public int lookupAttributeInteger( - long deviceId, String attributeName, int defaultValue, boolean lookupServer, boolean lookupConfig) { - Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); - if (result != null) { - return result instanceof String ? Integer.parseInt((String) result) : ((Number) result).intValue(); - } - return defaultValue; - } - - @Override - public long lookupAttributeLong( - long deviceId, String attributeName, long defaultValue, boolean lookupServer, boolean lookupConfig) { - Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); - if (result != null) { - return result instanceof String ? Long.parseLong((String) result) : ((Number) result).longValue(); - } - return defaultValue; - } - - public double lookupAttributeDouble( - long deviceId, String attributeName, double defaultValue, boolean lookupServer, boolean lookupConfig) { - Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); - if (result != null) { - return result instanceof String ? Double.parseDouble((String) result) : ((Number) result).doubleValue(); - } - return defaultValue; - } - - private Object lookupAttribute(long deviceId, String attributeName, boolean lookupServer, boolean lookupConfig) { - Object result = null; - Device device = getById(deviceId); - if (device != null) { - result = device.getAttributes().get(attributeName); - if (result == null) { - long groupId = device.getGroupId(); - while (groupId != 0) { - Group group = Context.getGroupsManager().getById(groupId); - if (group != null) { - result = group.getAttributes().get(attributeName); - if (result != null) { - break; - } - groupId = group.getGroupId(); - } else { - groupId = 0; - } - } - } - if (result == null && lookupServer) { - Server server = Context.getPermissionsManager().getServer(); - result = server.getAttributes().get(attributeName); - } - if (result == null && lookupConfig) { - result = Context.getConfig().getString(attributeName); - } - } - return result; - } - - public void resetDeviceAccumulators(DeviceAccumulators deviceAccumulators) throws SQLException { - Position last = positions.get(deviceAccumulators.getDeviceId()); - if (last != null) { - if (deviceAccumulators.getTotalDistance() != null) { - last.getAttributes().put(Position.KEY_TOTAL_DISTANCE, deviceAccumulators.getTotalDistance()); - } - if (deviceAccumulators.getHours() != null) { - last.getAttributes().put(Position.KEY_HOURS, deviceAccumulators.getHours()); - } - getDataManager().addObject(last); - updateLatestPosition(last); - } else { - throw new IllegalArgumentException(); - } - } - - public DeviceState getDeviceState(long deviceId) { - DeviceState deviceState = deviceStates.get(deviceId); - if (deviceState == null) { - deviceState = new DeviceState(); - deviceStates.put(deviceId, deviceState); - } - return deviceState; - } - - public void setDeviceState(long deviceId, DeviceState deviceState) { - deviceStates.put(deviceId, deviceState); - } - -} diff --git a/src/main/java/org/traccar/database/DriversManager.java b/src/main/java/org/traccar/database/DriversManager.java deleted file mode 100644 index d111cd643..000000000 --- a/src/main/java/org/traccar/database/DriversManager.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.traccar.model.Driver; - -public class DriversManager extends ExtendedObjectManager<Driver> { - - private Map<String, Driver> driversByUniqueId; - - public DriversManager(DataManager dataManager) { - super(dataManager, Driver.class); - try { - writeLock(); - if (driversByUniqueId == null) { - driversByUniqueId = new ConcurrentHashMap<>(); - } - } finally { - writeUnlock(); - } - } - - private void addByUniqueId(Driver driver) { - try { - writeLock(); - if (driversByUniqueId == null) { - driversByUniqueId = new ConcurrentHashMap<>(); - } - driversByUniqueId.put(driver.getUniqueId(), driver); - } finally { - writeUnlock(); - } - } - - private void removeByUniqueId(String driverUniqueId) { - try { - writeLock(); - if (driversByUniqueId == null) { - driversByUniqueId = new ConcurrentHashMap<>(); - } - driversByUniqueId.remove(driverUniqueId); - } finally { - writeUnlock(); - } - } - - @Override - protected void addNewItem(Driver driver) { - super.addNewItem(driver); - addByUniqueId(driver); - } - - @Override - protected void updateCachedItem(Driver driver) { - Driver cachedDriver = getById(driver.getId()); - cachedDriver.setName(driver.getName()); - if (!driver.getUniqueId().equals(cachedDriver.getUniqueId())) { - removeByUniqueId(cachedDriver.getUniqueId()); - cachedDriver.setUniqueId(driver.getUniqueId()); - addByUniqueId(cachedDriver); - } - cachedDriver.setAttributes(driver.getAttributes()); - } - - @Override - protected void removeCachedItem(long driverId) { - Driver cachedDriver = getById(driverId); - if (cachedDriver != null) { - String driverUniqueId = cachedDriver.getUniqueId(); - super.removeCachedItem(driverId); - removeByUniqueId(driverUniqueId); - } - } - - public Driver getDriverByUniqueId(String uniqueId) { - try { - readLock(); - return driversByUniqueId.get(uniqueId); - } finally { - readUnlock(); - } - } -} diff --git a/src/main/java/org/traccar/database/ExtendedObjectManager.java b/src/main/java/org/traccar/database/ExtendedObjectManager.java deleted file mode 100644 index 93e5820fb..000000000 --- a/src/main/java/org/traccar/database/ExtendedObjectManager.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import java.sql.SQLException; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.model.Device; -import org.traccar.model.Group; -import org.traccar.model.Permission; -import org.traccar.model.BaseModel; - -public abstract class ExtendedObjectManager<T extends BaseModel> extends SimpleObjectManager<T> { - - private static final Logger LOGGER = LoggerFactory.getLogger(ExtendedObjectManager.class); - - private final Map<Long, Set<Long>> deviceItems = new ConcurrentHashMap<>(); - private final Map<Long, Set<Long>> deviceItemsWithGroups = new ConcurrentHashMap<>(); - private final Map<Long, Set<Long>> groupItems = new ConcurrentHashMap<>(); - - protected ExtendedObjectManager(DataManager dataManager, Class<T> baseClass) { - super(dataManager, baseClass); - refreshExtendedPermissions(); - } - - public final Set<Long> getGroupItems(long groupId) { - try { - readLock(); - Set<Long> result = groupItems.get(groupId); - if (result != null) { - return new HashSet<>(result); - } else { - return new HashSet<>(); - } - } finally { - readUnlock(); - } - } - - public final Set<Long> getDeviceItems(long deviceId) { - try { - readLock(); - Set<Long> result = deviceItems.get(deviceId); - if (result != null) { - return new HashSet<>(result); - } else { - return new HashSet<>(); - } - } finally { - readUnlock(); - } - } - - public Set<Long> getAllDeviceItems(long deviceId) { - try { - readLock(); - Set<Long> result = deviceItemsWithGroups.get(deviceId); - if (result != null) { - return new HashSet<>(result); - } else { - return new HashSet<>(); - } - } finally { - readUnlock(); - } - } - - @Override - public void removeItem(long itemId) throws SQLException { - super.removeItem(itemId); - refreshExtendedPermissions(); - } - - public void refreshExtendedPermissions() { - if (getDataManager() != null) { - try { - Collection<Permission> databaseGroupPermissions = - getDataManager().getPermissions(Group.class, getBaseClass()); - - Collection<Permission> databaseDevicePermissions = - getDataManager().getPermissions(Device.class, getBaseClass()); - - writeLock(); - - groupItems.clear(); - deviceItems.clear(); - deviceItemsWithGroups.clear(); - - for (Permission groupPermission : databaseGroupPermissions) { - groupItems - .computeIfAbsent(groupPermission.getOwnerId(), key -> new HashSet<>()) - .add(groupPermission.getPropertyId()); - } - - for (Permission devicePermission : databaseDevicePermissions) { - deviceItems - .computeIfAbsent(devicePermission.getOwnerId(), key -> new HashSet<>()) - .add(devicePermission.getPropertyId()); - deviceItemsWithGroups - .computeIfAbsent(devicePermission.getOwnerId(), key -> new HashSet<>()) - .add(devicePermission.getPropertyId()); - } - - for (Device device : Context.getDeviceManager().getAllDevices()) { - long groupId = device.getGroupId(); - while (groupId > 0) { - deviceItemsWithGroups - .computeIfAbsent(device.getId(), key -> new HashSet<>()) - .addAll(groupItems.getOrDefault(groupId, new HashSet<>())); - Group group = Context.getGroupsManager().getById(groupId); - groupId = group != null ? group.getGroupId() : 0; - } - } - - } catch (SQLException | ClassNotFoundException error) { - LOGGER.warn("Refresh permissions error", error); - } finally { - writeUnlock(); - } - } - } -} diff --git a/src/main/java/org/traccar/database/GeofenceManager.java b/src/main/java/org/traccar/database/GeofenceManager.java deleted file mode 100644 index a32847cf9..000000000 --- a/src/main/java/org/traccar/database/GeofenceManager.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import java.util.ArrayList; -import java.util.List; - -import org.traccar.Context; -import org.traccar.model.Device; -import org.traccar.model.Geofence; -import org.traccar.model.Position; - -public class GeofenceManager extends ExtendedObjectManager<Geofence> { - - public GeofenceManager(DataManager dataManager) { - super(dataManager, Geofence.class); - } - - @Override - public final void refreshExtendedPermissions() { - super.refreshExtendedPermissions(); - recalculateDevicesGeofences(); - } - - public List<Long> getCurrentDeviceGeofences(Position position) { - List<Long> result = new ArrayList<>(); - for (long geofenceId : getAllDeviceItems(position.getDeviceId())) { - Geofence geofence = getById(geofenceId); - if (geofence != null && geofence.getGeometry() - .containsPoint(position.getLatitude(), position.getLongitude())) { - result.add(geofenceId); - } - } - return result; - } - - public void recalculateDevicesGeofences() { - for (Device device : Context.getDeviceManager().getAllDevices()) { - List<Long> deviceGeofenceIds = device.getGeofenceIds(); - if (deviceGeofenceIds == null) { - deviceGeofenceIds = new ArrayList<>(); - } else { - deviceGeofenceIds.clear(); - } - Position lastPosition = Context.getIdentityManager().getLastPosition(device.getId()); - if (lastPosition != null && getAllDeviceItems(device.getId()) != null) { - deviceGeofenceIds.addAll(getCurrentDeviceGeofences(lastPosition)); - } - device.setGeofenceIds(deviceGeofenceIds); - } - } - -} diff --git a/src/main/java/org/traccar/database/GroupsManager.java b/src/main/java/org/traccar/database/GroupsManager.java deleted file mode 100644 index c35f35f93..000000000 --- a/src/main/java/org/traccar/database/GroupsManager.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import java.sql.SQLException; -import java.util.HashSet; -import java.util.Set; - -import org.traccar.Context; -import org.traccar.model.Group; - -public class GroupsManager extends BaseObjectManager<Group> implements ManagableObjects { - - public GroupsManager(DataManager dataManager) { - super(dataManager, Group.class); - } - - private void checkGroupCycles(Group group) { - Set<Long> groups = new HashSet<>(); - while (group != null) { - if (groups.contains(group.getId())) { - throw new IllegalArgumentException("Cycle in group hierarchy"); - } - groups.add(group.getId()); - group = getById(group.getGroupId()); - } - } - - @Override - public Set<Long> getAllItems() { - Set<Long> result = super.getAllItems(); - if (result.isEmpty()) { - refreshItems(); - result = super.getAllItems(); - } - return result; - } - - @Override - protected void addNewItem(Group group) { - checkGroupCycles(group); - super.addNewItem(group); - } - - @Override - public void updateItem(Group group) throws SQLException { - checkGroupCycles(group); - super.updateItem(group); - } - - @Override - public Set<Long> getUserItems(long userId) { - if (Context.getPermissionsManager() != null) { - return Context.getPermissionsManager().getGroupPermissions(userId); - } else { - return new HashSet<>(); - } - } - - @Override - public Set<Long> getManagedItems(long userId) { - Set<Long> result = getUserItems(userId); - for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { - result.addAll(getUserItems(managedUserId)); - } - return result; - } - -} diff --git a/src/main/java/org/traccar/database/IdentityManager.java b/src/main/java/org/traccar/database/IdentityManager.java deleted file mode 100644 index af6a6ce71..000000000 --- a/src/main/java/org/traccar/database/IdentityManager.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import org.traccar.model.Device; -import org.traccar.model.Position; - -public interface IdentityManager { - - long addUnknownDevice(String uniqueId); - - Device getById(long id); - - Device getByUniqueId(String uniqueId) throws Exception; - - String getDevicePassword(long id, String protocol, String defaultPassword); - - Position getLastPosition(long deviceId); - - boolean isLatestPosition(Position position); - - boolean lookupAttributeBoolean( - long deviceId, String attributeName, boolean defaultValue, boolean lookupServer, boolean lookupConfig); - - String lookupAttributeString( - long deviceId, String attributeName, String defaultValue, boolean lookupServer, boolean lookupConfig); - - int lookupAttributeInteger( - long deviceId, String attributeName, int defaultValue, boolean lookupServer, boolean lookupConfig); - - long lookupAttributeLong( - long deviceId, String attributeName, long defaultValue, boolean lookupServer, boolean lookupConfig); - - double lookupAttributeDouble( - long deviceId, String attributeName, double defaultValue, boolean lookupServer, boolean lookupConfig); - -} diff --git a/src/main/java/org/traccar/database/LdapProvider.java b/src/main/java/org/traccar/database/LdapProvider.java index d659a11a1..d517294b8 100644 --- a/src/main/java/org/traccar/database/LdapProvider.java +++ b/src/main/java/org/traccar/database/LdapProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/traccar/database/MailManager.java b/src/main/java/org/traccar/database/MailManager.java deleted file mode 100644 index d94f55cda..000000000 --- a/src/main/java/org/traccar/database/MailManager.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) - * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.Main; -import org.traccar.model.User; -import org.traccar.notification.PropertiesProvider; - -import javax.mail.BodyPart; -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.Multipart; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; -import java.util.Date; -import java.util.Properties; - -public final class MailManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(MailManager.class); - - private static Properties getProperties(PropertiesProvider provider) { - Properties properties = new Properties(); - String host = provider.getString("mail.smtp.host"); - if (host != null) { - properties.put("mail.transport.protocol", provider.getString("mail.transport.protocol", "smtp")); - properties.put("mail.smtp.host", host); - properties.put("mail.smtp.port", String.valueOf(provider.getInteger("mail.smtp.port", 25))); - - Boolean starttlsEnable = provider.getBoolean("mail.smtp.starttls.enable"); - if (starttlsEnable != null) { - properties.put("mail.smtp.starttls.enable", String.valueOf(starttlsEnable)); - } - Boolean starttlsRequired = provider.getBoolean("mail.smtp.starttls.required"); - if (starttlsRequired != null) { - properties.put("mail.smtp.starttls.required", String.valueOf(starttlsRequired)); - } - - Boolean sslEnable = provider.getBoolean("mail.smtp.ssl.enable"); - if (sslEnable != null) { - properties.put("mail.smtp.ssl.enable", String.valueOf(sslEnable)); - } - String sslTrust = provider.getString("mail.smtp.ssl.trust"); - if (sslTrust != null) { - properties.put("mail.smtp.ssl.trust", sslTrust); - } - - String sslProtocols = provider.getString("mail.smtp.ssl.protocols"); - if (sslProtocols != null) { - properties.put("mail.smtp.ssl.protocols", sslProtocols); - } - - String username = provider.getString("mail.smtp.username"); - if (username != null) { - properties.put("mail.smtp.username", username); - } - String password = provider.getString("mail.smtp.password"); - if (password != null) { - properties.put("mail.smtp.password", password); - } - String from = provider.getString("mail.smtp.from"); - if (from != null) { - properties.put("mail.smtp.from", from); - } - } - return properties; - } - - public boolean getEmailEnabled() { - return Context.getConfig().hasKey("mail.smtp.host"); - } - - public void sendMessage( - long userId, String subject, String body) throws MessagingException { - sendMessage(userId, subject, body, null); - } - - public void sendMessage( - long userId, String subject, String body, MimeBodyPart attachment) throws MessagingException { - User user = Context.getPermissionsManager().getUser(userId); - - Properties properties = null; - if (!Context.getConfig().getBoolean("mail.smtp.ignoreUserConfig")) { - properties = getProperties(new PropertiesProvider(user)); - } - if (properties == null || !properties.containsKey("mail.smtp.host")) { - properties = getProperties(new PropertiesProvider(Context.getConfig())); - } - if (!properties.containsKey("mail.smtp.host")) { - LOGGER.warn("No SMTP configuration found"); - return; - } - - Session session = Session.getInstance(properties); - - MimeMessage message = new MimeMessage(session); - - String from = properties.getProperty("mail.smtp.from"); - if (from != null) { - message.setFrom(new InternetAddress(from)); - } - - message.addRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmail())); - message.setSubject(subject); - message.setSentDate(new Date()); - - if (attachment != null) { - Multipart multipart = new MimeMultipart(); - - BodyPart messageBodyPart = new MimeBodyPart(); - messageBodyPart.setContent(body, "text/html; charset=utf-8"); - multipart.addBodyPart(messageBodyPart); - multipart.addBodyPart(attachment); - - message.setContent(multipart); - } else { - message.setContent(body, "text/html; charset=utf-8"); - } - - try (Transport transport = session.getTransport()) { - Main.getInjector().getInstance(StatisticsManager.class).registerMail(); - transport.connect( - properties.getProperty("mail.smtp.host"), - properties.getProperty("mail.smtp.username"), - properties.getProperty("mail.smtp.password")); - transport.sendMessage(message, message.getAllRecipients()); - } - } - -} diff --git a/src/main/java/org/traccar/database/MediaManager.java b/src/main/java/org/traccar/database/MediaManager.java index edade5766..c1ef810ee 100644 --- a/src/main/java/org/traccar/database/MediaManager.java +++ b/src/main/java/org/traccar/database/MediaManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,15 @@ package org.traccar.database; import io.netty.buffer.ByteBuf; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import javax.inject.Inject; +import javax.inject.Singleton; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Files; @@ -30,14 +35,16 @@ import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Date; +@Singleton public class MediaManager { private static final Logger LOGGER = LoggerFactory.getLogger(MediaManager.class); - private String path; + private final String path; - public MediaManager(String path) { - this.path = path; + @Inject + public MediaManager(Config config) { + this.path = config.getString(Keys.MEDIA_PATH); } private File createFile(String uniqueId, String name) throws IOException { @@ -49,6 +56,10 @@ public class MediaManager { return filePath.toFile(); } + public OutputStream createFileStream(String uniqueId, String name, String extension) throws IOException { + return new FileOutputStream(createFile(uniqueId, name + "." + extension)); + } + public String writeFile(String uniqueId, ByteBuf buf, String extension) { if (path != null) { int size = buf.readableBytes(); diff --git a/src/main/java/org/traccar/database/NotificationManager.java b/src/main/java/org/traccar/database/NotificationManager.java index 9f9a83cd2..cb971b082 100644 --- a/src/main/java/org/traccar/database/NotificationManager.java +++ b/src/main/java/org/traccar/database/NotificationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,128 +16,141 @@ */ package org.traccar.database; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.sql.SQLException; -import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; +import org.traccar.config.Config; import org.traccar.config.Keys; +import org.traccar.forward.EventData; +import org.traccar.forward.EventForwarder; +import org.traccar.geocoder.Geocoder; import org.traccar.model.Calendar; +import org.traccar.model.Device; import org.traccar.model.Event; +import org.traccar.model.Geofence; +import org.traccar.model.Maintenance; import org.traccar.model.Notification; import org.traccar.model.Position; -import org.traccar.model.Typed; +import org.traccar.notification.MessageException; +import org.traccar.notification.NotificatorManager; +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Request; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Arrays; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; -public class NotificationManager extends ExtendedObjectManager<Notification> { +@Singleton +public class NotificationManager { private static final Logger LOGGER = LoggerFactory.getLogger(NotificationManager.class); - private final boolean geocodeOnRequest; + private final Storage storage; + private final CacheManager cacheManager; + private final EventForwarder eventForwarder; + private final NotificatorManager notificatorManager; + private final Geocoder geocoder; - public NotificationManager(DataManager dataManager) { - super(dataManager, Notification.class); - geocodeOnRequest = Context.getConfig().getBoolean(Keys.GEOCODER_ON_REQUEST); - } + private final boolean geocodeOnRequest; - private Set<Long> getEffectiveNotifications(long userId, long deviceId, Date time) { - Set<Long> result = new HashSet<>(); - Set<Long> deviceNotifications = getAllDeviceItems(deviceId); - for (long itemId : getUserItems(userId)) { - if (getById(itemId).getAlways() || deviceNotifications.contains(itemId)) { - long calendarId = getById(itemId).getCalendarId(); - Calendar calendar = calendarId != 0 ? Context.getCalendarManager().getById(calendarId) : null; - if (calendar == null || calendar.checkMoment(time)) { - result.add(itemId); - } - } - } - return result; + @Inject + public NotificationManager( + Config config, Storage storage, CacheManager cacheManager, @Nullable EventForwarder eventForwarder, + NotificatorManager notificatorManager, @Nullable Geocoder geocoder) { + this.storage = storage; + this.cacheManager = cacheManager; + this.eventForwarder = eventForwarder; + this.notificatorManager = notificatorManager; + this.geocoder = geocoder; + geocodeOnRequest = config.getBoolean(Keys.GEOCODER_ON_REQUEST); } - public void updateEvent(Event event, Position position) { + private void updateEvent(Event event, Position position) { try { - getDataManager().addObject(event); - } catch (SQLException error) { + event.setId(storage.addObject(event, new Request(new Columns.Exclude("id")))); + } catch (StorageException error) { LOGGER.warn("Event save error", error); } - long deviceId = event.getDeviceId(); - Set<Long> users = Context.getPermissionsManager().getDeviceUsers(deviceId); - Set<Long> usersToForward = null; - if (Context.getEventForwarder() != null) { - usersToForward = new HashSet<>(); - } - for (long userId : users) { - if ((event.getGeofenceId() == 0 - || Context.getGeofenceManager().checkItemPermission(userId, event.getGeofenceId())) - && (event.getMaintenanceId() == 0 - || Context.getMaintenancesManager().checkItemPermission(userId, event.getMaintenanceId()))) { - if (usersToForward != null) { - usersToForward.add(userId); - } - final Set<String> notificators = new HashSet<>(); - for (long notificationId : getEffectiveNotifications(userId, deviceId, event.getEventTime())) { - Notification notification = getById(notificationId); - if (getById(notificationId).getType().equals(event.getType())) { - boolean filter = false; - if (event.getType().equals(Event.TYPE_ALARM)) { - String alarmsAttribute = notification.getString("alarms"); - if (alarmsAttribute == null) { - filter = true; - } else { - List<String> alarms = Arrays.asList(alarmsAttribute.split(",")); - filter = !alarms.contains(event.getString(Position.KEY_ALARM)); - } - } - if (!filter) { - notificators.addAll(notification.getNotificatorsTypes()); + var notifications = cacheManager.getDeviceObjects(event.getDeviceId(), Notification.class).stream() + .filter(notification -> notification.getType().equals(event.getType())) + .filter(notification -> { + if (event.getType().equals(Event.TYPE_ALARM)) { + String alarmsAttribute = notification.getString("alarms"); + if (alarmsAttribute != null) { + return Arrays.asList(alarmsAttribute.split(",")) + .contains(event.getString(Position.KEY_ALARM)); } + return false; } - } - - if (position != null && position.getAddress() == null - && geocodeOnRequest && Context.getGeocoder() != null) { - position.setAddress(Context.getGeocoder() - .getAddress(position.getLatitude(), position.getLongitude(), null)); - } + return true; + }) + .filter(notification -> { + long calendarId = notification.getCalendarId(); + Calendar calendar = calendarId != 0 ? cacheManager.getObject(Calendar.class, calendarId) : null; + return calendar == null || calendar.checkMoment(event.getEventTime()); + }) + .collect(Collectors.toUnmodifiableList()); - for (String notificator : notificators) { - Context.getNotificatorManager().getNotificator(notificator).sendAsync(userId, event, position); - } + if (!notifications.isEmpty()) { + if (position != null && position.getAddress() == null && geocodeOnRequest && geocoder != null) { + position.setAddress(geocoder.getAddress(position.getLatitude(), position.getLongitude(), null)); } + + notifications.forEach(notification -> { + cacheManager.getNotificationUsers(notification.getId(), event.getDeviceId()).forEach(user -> { + for (String notificator : notification.getNotificatorsTypes()) { + try { + notificatorManager.getNotificator(notificator).send(user, event, position); + } catch (MessageException | InterruptedException exception) { + LOGGER.warn("Notification failed", exception); + } + } + }); + }); } - if (Context.getEventForwarder() != null) { - Context.getEventForwarder().forwardEvent(event, position, usersToForward); - } + + forwardEvent(event, position); } - public void updateEvents(Map<Event, Position> events) { - for (Entry<Event, Position> event : events.entrySet()) { - updateEvent(event.getKey(), event.getValue()); + private void forwardEvent(Event event, Position position) { + if (eventForwarder != null) { + EventData eventData = new EventData(); + eventData.setEvent(event); + eventData.setPosition(position); + eventData.setDevice(cacheManager.getObject(Device.class, event.getDeviceId())); + if (event.getGeofenceId() != 0) { + eventData.setGeofence(cacheManager.getObject(Geofence.class, event.getGeofenceId())); + } + if (event.getMaintenanceId() != 0) { + eventData.setMaintenance(cacheManager.getObject(Maintenance.class, event.getMaintenanceId())); + } + eventForwarder.forward(eventData, (success, throwable) -> { + if (!success) { + LOGGER.warn("Event forwarding failed", throwable); + } + }); } } - public Set<Typed> getAllNotificationTypes() { - Set<Typed> types = new HashSet<>(); - Field[] fields = Event.class.getDeclaredFields(); - for (Field field : fields) { - if (Modifier.isStatic(field.getModifiers()) && field.getName().startsWith("TYPE_")) { - try { - types.add(new Typed(field.get(null).toString())); - } catch (IllegalArgumentException | IllegalAccessException error) { - LOGGER.warn("Get event types error", error); - } + public void updateEvents(Map<Event, Position> events) { + for (Entry<Event, Position> entry : events.entrySet()) { + Event event = entry.getKey(); + Position position = entry.getValue(); + try { + cacheManager.addDevice(event.getDeviceId()); + updateEvent(event, position); + } catch (StorageException e) { + throw new RuntimeException(e); + } finally { + cacheManager.removeDevice(event.getDeviceId()); } } - return types; } } diff --git a/src/main/java/org/traccar/database/PermissionsManager.java b/src/main/java/org/traccar/database/PermissionsManager.java deleted file mode 100644 index 32464cf90..000000000 --- a/src/main/java/org/traccar/database/PermissionsManager.java +++ /dev/null @@ -1,516 +0,0 @@ -/* - * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.model.Attribute; -import org.traccar.model.BaseModel; -import org.traccar.model.Calendar; -import org.traccar.model.Command; -import org.traccar.model.Device; -import org.traccar.model.Driver; -import org.traccar.model.Geofence; -import org.traccar.model.Group; -import org.traccar.model.Maintenance; -import org.traccar.model.ManagedUser; -import org.traccar.model.Notification; -import org.traccar.model.Order; -import org.traccar.model.Permission; -import org.traccar.model.Server; -import org.traccar.model.User; - -import java.sql.SQLException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -public class PermissionsManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(PermissionsManager.class); - - private final DataManager dataManager; - private final UsersManager usersManager; - - private volatile Server server; - - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - - private final Map<Long, Set<Long>> groupPermissions = new HashMap<>(); - private final Map<Long, Set<Long>> devicePermissions = new HashMap<>(); - private final Map<Long, Set<Long>> deviceUsers = new HashMap<>(); - private final Map<Long, Set<Long>> groupDevices = new HashMap<>(); - - public PermissionsManager(DataManager dataManager, UsersManager usersManager) { - this.dataManager = dataManager; - this.usersManager = usersManager; - refreshServer(); - refreshDeviceAndGroupPermissions(); - } - - protected final void readLock() { - lock.readLock().lock(); - } - - protected final void readUnlock() { - lock.readLock().unlock(); - } - - protected final void writeLock() { - lock.writeLock().lock(); - } - - protected final void writeUnlock() { - lock.writeLock().unlock(); - } - - public User getUser(long userId) { - readLock(); - try { - return usersManager.getById(userId); - } finally { - readUnlock(); - } - } - - public Set<Long> getGroupPermissions(long userId) { - readLock(); - try { - if (!groupPermissions.containsKey(userId)) { - groupPermissions.put(userId, new HashSet<>()); - } - return groupPermissions.get(userId); - } finally { - readUnlock(); - } - } - - public Set<Long> getDevicePermissions(long userId) { - readLock(); - try { - if (!devicePermissions.containsKey(userId)) { - devicePermissions.put(userId, new HashSet<>()); - } - return devicePermissions.get(userId); - } finally { - readUnlock(); - } - } - - private Set<Long> getAllDeviceUsers(long deviceId) { - readLock(); - try { - if (!deviceUsers.containsKey(deviceId)) { - deviceUsers.put(deviceId, new HashSet<>()); - } - return deviceUsers.get(deviceId); - } finally { - readUnlock(); - } - } - - public Set<Long> getDeviceUsers(long deviceId) { - Device device = Context.getIdentityManager().getById(deviceId); - if (device != null && !device.getDisabled()) { - return getAllDeviceUsers(deviceId); - } else { - Set<Long> result = new HashSet<>(); - for (long userId : getAllDeviceUsers(deviceId)) { - if (getUserAdmin(userId)) { - result.add(userId); - } - } - return result; - } - } - - public Set<Long> getGroupDevices(long groupId) { - readLock(); - try { - if (!groupDevices.containsKey(groupId)) { - groupDevices.put(groupId, new HashSet<>()); - } - return groupDevices.get(groupId); - } finally { - readUnlock(); - } - } - - public void refreshServer() { - try { - server = dataManager.getServer(); - } catch (SQLException error) { - LOGGER.warn("Refresh server config error", error); - } - } - - public final void refreshDeviceAndGroupPermissions() { - writeLock(); - try { - groupPermissions.clear(); - devicePermissions.clear(); - try { - GroupTree groupTree = new GroupTree(Context.getGroupsManager().getItems( - Context.getGroupsManager().getAllItems()), - Context.getDeviceManager().getAllDevices()); - for (Permission groupPermission : dataManager.getPermissions(User.class, Group.class)) { - Set<Long> userGroupPermissions = getGroupPermissions(groupPermission.getOwnerId()); - Set<Long> userDevicePermissions = getDevicePermissions(groupPermission.getOwnerId()); - userGroupPermissions.add(groupPermission.getPropertyId()); - for (Group group : groupTree.getGroups(groupPermission.getPropertyId())) { - userGroupPermissions.add(group.getId()); - } - for (Device device : groupTree.getDevices(groupPermission.getPropertyId())) { - userDevicePermissions.add(device.getId()); - } - } - - for (Permission devicePermission : dataManager.getPermissions(User.class, Device.class)) { - getDevicePermissions(devicePermission.getOwnerId()).add(devicePermission.getPropertyId()); - } - - groupDevices.clear(); - for (long groupId : Context.getGroupsManager().getAllItems()) { - for (Device device : groupTree.getDevices(groupId)) { - getGroupDevices(groupId).add(device.getId()); - } - } - - } catch (SQLException | ClassNotFoundException error) { - LOGGER.warn("Refresh device permissions error", error); - } - - deviceUsers.clear(); - for (Map.Entry<Long, Set<Long>> entry : devicePermissions.entrySet()) { - for (long deviceId : entry.getValue()) { - getAllDeviceUsers(deviceId).add(entry.getKey()); - } - } - } finally { - writeUnlock(); - } - } - - public boolean getUserAdmin(long userId) { - User user = getUser(userId); - return user != null && user.getAdministrator(); - } - - public void checkAdmin(long userId) throws SecurityException { - if (!getUserAdmin(userId)) { - throw new SecurityException("Admin access required"); - } - } - - public boolean getUserManager(long userId) { - User user = getUser(userId); - return user != null && user.getUserLimit() != 0; - } - - public void checkManager(long userId) throws SecurityException { - if (!getUserManager(userId)) { - throw new SecurityException("Manager access required"); - } - } - - public void checkManager(long userId, long managedUserId) throws SecurityException { - checkManager(userId); - if (!usersManager.getUserItems(userId).contains(managedUserId)) { - throw new SecurityException("User access denied"); - } - } - - public void checkUserLimit(long userId) throws SecurityException { - int userLimit = getUser(userId).getUserLimit(); - if (userLimit != -1 && usersManager.getUserItems(userId).size() >= userLimit) { - throw new SecurityException("Manager user limit reached"); - } - } - - public void checkDeviceLimit(long userId) throws SecurityException { - int deviceLimit = getUser(userId).getDeviceLimit(); - if (deviceLimit != -1) { - int deviceCount; - if (getUserManager(userId)) { - deviceCount = Context.getDeviceManager().getAllManagedItems(userId).size(); - } else { - deviceCount = Context.getDeviceManager().getAllUserItems(userId).size(); - } - if (deviceCount >= deviceLimit) { - throw new SecurityException("User device limit reached"); - } - } - } - - public boolean getUserReadonly(long userId) { - User user = getUser(userId); - return user != null && user.getReadonly(); - } - - public boolean getUserDeviceReadonly(long userId) { - User user = getUser(userId); - return user != null && user.getDeviceReadonly(); - } - - public boolean getUserLimitCommands(long userId) { - User user = getUser(userId); - return user != null && user.getLimitCommands(); - } - - public void checkReadonly(long userId) throws SecurityException { - if (!getUserAdmin(userId) && (server.getReadonly() || getUserReadonly(userId))) { - throw new SecurityException("Account is readonly"); - } - } - - public void checkDeviceReadonly(long userId) throws SecurityException { - if (!getUserAdmin(userId) && (server.getDeviceReadonly() || getUserDeviceReadonly(userId))) { - throw new SecurityException("Account is device readonly"); - } - } - - public void checkLimitCommands(long userId) throws SecurityException { - if (!getUserAdmin(userId) && (server.getLimitCommands() || getUserLimitCommands(userId))) { - throw new SecurityException("Account has limit sending commands"); - } - } - - public void checkUserDeviceCommand(long userId, long deviceId, long commandId) throws SecurityException { - if (!getUserAdmin(userId) && Context.getCommandsManager().checkDeviceCommand(deviceId, commandId)) { - throw new SecurityException("Command can not be sent to this device"); - } - } - - public void checkUserEnabled(long userId) throws SecurityException { - User user = getUser(userId); - if (user == null) { - throw new SecurityException("Unknown account"); - } - if (user.getDisabled()) { - throw new SecurityException("Account is disabled"); - } - if (user.getExpirationTime() != null && System.currentTimeMillis() > user.getExpirationTime().getTime()) { - throw new SecurityException("Account has expired"); - } - } - - public void checkUserUpdate(long userId, User before, User after) throws SecurityException { - if (before.getAdministrator() != after.getAdministrator() - || before.getDeviceLimit() != after.getDeviceLimit() - || before.getUserLimit() != after.getUserLimit()) { - checkAdmin(userId); - } - User user = getUser(userId); - if (user != null && user.getExpirationTime() != null - && (after.getExpirationTime() == null - || user.getExpirationTime().compareTo(after.getExpirationTime()) < 0)) { - checkAdmin(userId); - } - if (before.getReadonly() != after.getReadonly() - || before.getDeviceReadonly() != after.getDeviceReadonly() - || before.getDisabled() != after.getDisabled() - || before.getLimitCommands() != after.getLimitCommands()) { - if (userId == after.getId()) { - checkAdmin(userId); - } - if (!getUserAdmin(userId)) { - checkManager(userId); - } - } - } - - public void checkUser(long userId, long managedUserId) throws SecurityException { - if (userId != managedUserId && !getUserAdmin(userId)) { - checkManager(userId, managedUserId); - } - } - - public void checkGroup(long userId, long groupId) throws SecurityException { - if (!getGroupPermissions(userId).contains(groupId) && !getUserAdmin(userId)) { - checkManager(userId); - for (long managedUserId : usersManager.getUserItems(userId)) { - if (getGroupPermissions(managedUserId).contains(groupId)) { - return; - } - } - throw new SecurityException("Group access denied"); - } - } - - public void checkDevice(long userId, long deviceId) throws SecurityException { - if (!Context.getDeviceManager().getUserItems(userId).contains(deviceId) && !getUserAdmin(userId)) { - checkManager(userId); - for (long managedUserId : usersManager.getUserItems(userId)) { - if (Context.getDeviceManager().getUserItems(managedUserId).contains(deviceId)) { - return; - } - } - throw new SecurityException("Device access denied"); - } - } - - public void checkRegistration(long userId) { - if (!server.getRegistration() && !getUserAdmin(userId)) { - throw new SecurityException("Registration disabled"); - } - } - - public void checkPermission(Class<?> object, long userId, long objectId) - throws SecurityException { - SimpleObjectManager<? extends BaseModel> manager = null; - - if (object.equals(Device.class)) { - checkDevice(userId, objectId); - } else if (object.equals(Group.class)) { - checkGroup(userId, objectId); - } else if (object.equals(User.class) || object.equals(ManagedUser.class)) { - checkUser(userId, objectId); - } else if (object.equals(Geofence.class)) { - manager = Context.getGeofenceManager(); - } else if (object.equals(Attribute.class)) { - manager = Context.getAttributesManager(); - } else if (object.equals(Driver.class)) { - manager = Context.getDriversManager(); - } else if (object.equals(Calendar.class)) { - manager = Context.getCalendarManager(); - } else if (object.equals(Command.class)) { - manager = Context.getCommandsManager(); - } else if (object.equals(Maintenance.class)) { - manager = Context.getMaintenancesManager(); - } else if (object.equals(Notification.class)) { - manager = Context.getNotificationManager(); - } else if (object.equals(Order.class)) { - manager = Context.getOrderManager(); - } else { - throw new IllegalArgumentException("Unknown object type"); - } - - if (manager != null && !manager.checkItemPermission(userId, objectId) && !getUserAdmin(userId)) { - checkManager(userId); - for (long managedUserId : usersManager.getManagedItems(userId)) { - if (manager.checkItemPermission(managedUserId, objectId)) { - return; - } - } - throw new SecurityException("Type " + object + " access denied"); - } - } - - public void refreshAllUsersPermissions() { - if (Context.getGeofenceManager() != null) { - Context.getGeofenceManager().refreshUserItems(); - } - Context.getCalendarManager().refreshUserItems(); - Context.getDriversManager().refreshUserItems(); - Context.getAttributesManager().refreshUserItems(); - Context.getCommandsManager().refreshUserItems(); - Context.getMaintenancesManager().refreshUserItems(); - if (Context.getNotificationManager() != null) { - Context.getNotificationManager().refreshUserItems(); - } - } - - public void refreshAllExtendedPermissions() { - if (Context.getGeofenceManager() != null) { - Context.getGeofenceManager().refreshExtendedPermissions(); - } - Context.getDriversManager().refreshExtendedPermissions(); - Context.getAttributesManager().refreshExtendedPermissions(); - Context.getCommandsManager().refreshExtendedPermissions(); - Context.getMaintenancesManager().refreshExtendedPermissions(); - } - - public void refreshPermissions(Permission permission) { - if (permission.getOwnerClass().equals(User.class)) { - if (permission.getPropertyClass().equals(Device.class) - || permission.getPropertyClass().equals(Group.class)) { - refreshDeviceAndGroupPermissions(); - refreshAllExtendedPermissions(); - } else if (permission.getPropertyClass().equals(ManagedUser.class)) { - usersManager.refreshUserItems(); - } else if (permission.getPropertyClass().equals(Geofence.class) && Context.getGeofenceManager() != null) { - Context.getGeofenceManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Driver.class)) { - Context.getDriversManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Attribute.class)) { - Context.getAttributesManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Calendar.class)) { - Context.getCalendarManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Command.class)) { - Context.getCommandsManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Maintenance.class)) { - Context.getMaintenancesManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Order.class)) { - Context.getOrderManager().refreshUserItems(); - } else if (permission.getPropertyClass().equals(Notification.class) - && Context.getNotificationManager() != null) { - Context.getNotificationManager().refreshUserItems(); - } - } else if (permission.getOwnerClass().equals(Device.class) || permission.getOwnerClass().equals(Group.class)) { - if (permission.getPropertyClass().equals(Geofence.class) && Context.getGeofenceManager() != null) { - Context.getGeofenceManager().refreshExtendedPermissions(); - } else if (permission.getPropertyClass().equals(Driver.class)) { - Context.getDriversManager().refreshExtendedPermissions(); - } else if (permission.getPropertyClass().equals(Attribute.class)) { - Context.getAttributesManager().refreshExtendedPermissions(); - } else if (permission.getPropertyClass().equals(Command.class)) { - Context.getCommandsManager().refreshExtendedPermissions(); - } else if (permission.getPropertyClass().equals(Maintenance.class)) { - Context.getMaintenancesManager().refreshExtendedPermissions(); - } else if (permission.getPropertyClass().equals(Order.class)) { - Context.getOrderManager().refreshExtendedPermissions(); - } else if (permission.getPropertyClass().equals(Notification.class) - && Context.getNotificationManager() != null) { - Context.getNotificationManager().refreshExtendedPermissions(); - } - } - } - - public Server getServer() { - return server; - } - - public void updateServer(Server server) throws SQLException { - dataManager.updateObject(server); - this.server = server; - } - - public User login(String email, String password) throws SQLException { - User user = dataManager.login(email, password); - if (user != null) { - checkUserEnabled(user.getId()); - return getUser(user.getId()); - } - return null; - } - - public Object lookupAttribute(long userId, String key, Object defaultValue) { - Object preference; - Object serverPreference = server.getAttributes().get(key); - Object userPreference = getUser(userId).getAttributes().get(key); - if (server.getForceSettings()) { - preference = serverPreference != null ? serverPreference : userPreference; - } else { - preference = userPreference != null ? userPreference : serverPreference; - } - return preference != null ? preference : defaultValue; - } - -} diff --git a/src/main/java/org/traccar/database/SimpleObjectManager.java b/src/main/java/org/traccar/database/SimpleObjectManager.java deleted file mode 100644 index eb8284d4e..000000000 --- a/src/main/java/org/traccar/database/SimpleObjectManager.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import java.sql.SQLException; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.model.BaseModel; -import org.traccar.model.Permission; -import org.traccar.model.User; - -public abstract class SimpleObjectManager<T extends BaseModel> extends BaseObjectManager<T> - implements ManagableObjects { - - private static final Logger LOGGER = LoggerFactory.getLogger(SimpleObjectManager.class); - - private Map<Long, Set<Long>> userItems; - - protected SimpleObjectManager(DataManager dataManager, Class<T> baseClass) { - super(dataManager, baseClass); - } - - @Override - public final Set<Long> getUserItems(long userId) { - try { - readLock(); - Set<Long> result = userItems.get(userId); - if (result != null) { - return new HashSet<>(result); - } else { - return new HashSet<>(); - } - } finally { - readUnlock(); - } - } - - @Override - public Set<Long> getManagedItems(long userId) { - Set<Long> result = getUserItems(userId); - for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { - result.addAll(getUserItems(managedUserId)); - } - return result; - } - - public final boolean checkItemPermission(long userId, long itemId) { - return getUserItems(userId).contains(itemId); - } - - @Override - public void refreshItems() { - super.refreshItems(); - refreshUserItems(); - } - - public final void refreshUserItems() { - if (getDataManager() != null) { - try { - writeLock(); - userItems = new ConcurrentHashMap<>(); - for (Permission permission : getDataManager().getPermissions(User.class, getBaseClass())) { - Set<Long> items = userItems.computeIfAbsent(permission.getOwnerId(), key -> new HashSet<>()); - items.add(permission.getPropertyId()); - } - } catch (SQLException | ClassNotFoundException error) { - LOGGER.warn("Error getting permissions", error); - } finally { - writeUnlock(); - } - } - } - - @Override - public void removeItem(long itemId) throws SQLException { - super.removeItem(itemId); - refreshUserItems(); - } - -} diff --git a/src/main/java/org/traccar/database/StatisticsManager.java b/src/main/java/org/traccar/database/StatisticsManager.java index 4ad6d9d5c..e0995dabc 100644 --- a/src/main/java/org/traccar/database/StatisticsManager.java +++ b/src/main/java/org/traccar/database/StatisticsManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,16 @@ import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.helper.DateUtil; import org.traccar.model.Statistics; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Request; import javax.inject.Inject; +import javax.inject.Singleton; import javax.ws.rs.client.Client; import javax.ws.rs.client.Entity; import javax.ws.rs.core.Form; -import java.sql.SQLException; import java.util.Calendar; import java.util.Date; import java.util.HashMap; @@ -37,6 +41,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +@Singleton public class StatisticsManager { private static final Logger LOGGER = LoggerFactory.getLogger(StatisticsManager.class); @@ -44,7 +49,7 @@ public class StatisticsManager { private static final int SPLIT_MODE = Calendar.DAY_OF_MONTH; private final Config config; - private final DataManager dataManager; + private final Storage storage; private final Client client; private final ObjectMapper objectMapper; @@ -62,9 +67,9 @@ public class StatisticsManager { private int geolocationRequests; @Inject - public StatisticsManager(Config config, DataManager dataManager, Client client, ObjectMapper objectMapper) { + public StatisticsManager(Config config, Storage storage, Client client, ObjectMapper objectMapper) { this.config = config; - this.dataManager = dataManager; + this.storage = storage; this.client = client; this.objectMapper = objectMapper; } @@ -105,8 +110,8 @@ public class StatisticsManager { } try { - dataManager.addObject(statistics); - } catch (SQLException e) { + storage.addObject(statistics, new Request(new Columns.Exclude("id"))); + } catch (StorageException e) { LOGGER.warn("Error saving statistics", e); } diff --git a/src/main/java/org/traccar/database/UsersManager.java b/src/main/java/org/traccar/database/UsersManager.java deleted file mode 100644 index b741a85b6..000000000 --- a/src/main/java/org/traccar/database/UsersManager.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.database; - -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.traccar.model.User; - -public class UsersManager extends SimpleObjectManager<User> { - - private Map<String, User> usersTokens; - - public UsersManager(DataManager dataManager) { - super(dataManager, User.class); - if (usersTokens == null) { - usersTokens = new ConcurrentHashMap<>(); - } - } - - private void putToken(User user) { - if (usersTokens == null) { - usersTokens = new ConcurrentHashMap<>(); - } - if (user.getToken() != null) { - usersTokens.put(user.getToken(), user); - } - } - - @Override - protected void addNewItem(User user) { - super.addNewItem(user); - putToken(user); - } - - @Override - protected void updateCachedItem(User user) { - User cachedUser = getById(user.getId()); - super.updateCachedItem(user); - putToken(user); - if (cachedUser.getToken() != null && !cachedUser.getToken().equals(user.getToken())) { - usersTokens.remove(cachedUser.getToken()); - } - } - - @Override - protected void removeCachedItem(long userId) { - User cachedUser = getById(userId); - if (cachedUser != null) { - String userToken = cachedUser.getToken(); - super.removeCachedItem(userId); - if (userToken != null) { - usersTokens.remove(userToken); - } - } - } - - @Override - public Set<Long> getManagedItems(long userId) { - Set<Long> result = getUserItems(userId); - result.add(userId); - return result; - } - - public User getUserByToken(String token) { - return usersTokens.get(token); - } - -} diff --git a/src/main/java/org/traccar/forward/EventData.java b/src/main/java/org/traccar/forward/EventData.java new file mode 100644 index 000000000..4471b10b3 --- /dev/null +++ b/src/main/java/org/traccar/forward/EventData.java @@ -0,0 +1,78 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.forward; + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Geofence; +import org.traccar.model.Maintenance; +import org.traccar.model.Position; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class EventData { + + private Event event; + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + + private Position position; + + public Position getPosition() { + return position; + } + + public void setPosition(Position position) { + this.position = position; + } + + private Device device; + + public Device getDevice() { + return device; + } + + public void setDevice(Device device) { + this.device = device; + } + + private Geofence geofence; + + public Geofence getGeofence() { + return geofence; + } + + public void setGeofence(Geofence geofence) { + this.geofence = geofence; + } + + private Maintenance maintenance; + + public Maintenance getMaintenance() { + return maintenance; + } + + public void setMaintenance(Maintenance maintenance) { + this.maintenance = maintenance; + } + +} diff --git a/src/main/java/org/traccar/forward/EventForwarder.java b/src/main/java/org/traccar/forward/EventForwarder.java new file mode 100644 index 000000000..1f991c0b5 --- /dev/null +++ b/src/main/java/org/traccar/forward/EventForwarder.java @@ -0,0 +1,20 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.forward; + +public interface EventForwarder { + void forward(EventData eventData, ResultHandler resultHandler); +} diff --git a/src/main/java/org/traccar/forward/EventForwarderJson.java b/src/main/java/org/traccar/forward/EventForwarderJson.java new file mode 100644 index 000000000..7527d568a --- /dev/null +++ b/src/main/java/org/traccar/forward/EventForwarderJson.java @@ -0,0 +1,68 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.forward; + +import org.traccar.config.Config; +import org.traccar.config.Keys; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.InvocationCallback; +import javax.ws.rs.core.Response; + +public class EventForwarderJson implements EventForwarder { + + private final String url; + private final String header; + + private final Client client; + + public EventForwarderJson(Config config, Client client) { + this.client = client; + url = config.getString(Keys.EVENT_FORWARD_URL); + header = config.getString(Keys.EVENT_FORWARD_HEADERS); + } + + @Override + public void forward(EventData eventData, ResultHandler resultHandler) { + var requestBuilder = client.target(url).request(); + + if (header != null && !header.isEmpty()) { + for (String line: header.split("\\r?\\n")) { + String[] values = line.split(":", 2); + requestBuilder.header(values[0].trim(), values[1].trim()); + } + } + + requestBuilder.async().post(Entity.json(eventData), new InvocationCallback<Response>() { + @Override + public void completed(Response response) { + if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) { + resultHandler.onResult(true, null); + } else { + int code = response.getStatusInfo().getStatusCode(); + resultHandler.onResult(false, new RuntimeException("HTTP code " + code)); + } + } + + @Override + public void failed(Throwable throwable) { + resultHandler.onResult(false, throwable); + } + }); + } + +} diff --git a/src/main/java/org/traccar/forward/EventForwarderKafka.java b/src/main/java/org/traccar/forward/EventForwarderKafka.java new file mode 100644 index 000000000..e65c3a51b --- /dev/null +++ b/src/main/java/org/traccar/forward/EventForwarderKafka.java @@ -0,0 +1,58 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.forward; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.Producer; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.traccar.config.Config; +import org.traccar.config.Keys; + +import java.util.Properties; + +public class EventForwarderKafka implements EventForwarder { + + private final Producer<String, String> producer; + private final ObjectMapper objectMapper; + + private final String topic; + + public EventForwarderKafka(Config config, ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + Properties properties = new Properties(); + properties.put("bootstrap.servers", config.getString(Keys.EVENT_FORWARD_URL)); + properties.put("acks", "all"); + properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); + properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); + producer = new KafkaProducer<>(properties); + topic = config.getString(Keys.EVENT_FORWARD_TOPIC); + } + + @Override + public void forward(EventData eventData, ResultHandler resultHandler) { + try { + String key = Long.toString(eventData.getDevice().getId()); + String value = objectMapper.writeValueAsString(eventData); + producer.send(new ProducerRecord<>(topic, key, value)); + resultHandler.onResult(true, null); + } catch (JsonProcessingException e) { + resultHandler.onResult(false, e); + } + } + +} diff --git a/src/main/java/org/traccar/forward/EventForwarderMqtt.java b/src/main/java/org/traccar/forward/EventForwarderMqtt.java new file mode 100644 index 000000000..dc95cb4e2 --- /dev/null +++ b/src/main/java/org/traccar/forward/EventForwarderMqtt.java @@ -0,0 +1,103 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.forward; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hivemq.client.mqtt.datatypes.MqttQos; +import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient; +import com.hivemq.client.mqtt.mqtt5.Mqtt5Client; +import com.hivemq.client.mqtt.mqtt5.message.auth.Mqtt5SimpleAuth; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.UUID; + +public class EventForwarderMqtt implements EventForwarder { + + private static final Logger LOGGER = LoggerFactory.getLogger(EventForwarderMqtt.class); + private final Mqtt5AsyncClient client; + private final ObjectMapper objectMapper; + + private final String topic; + + public EventForwarderMqtt(Config config, ObjectMapper objectMapper) { + URI url; + try { + url = new URI(config.getString(Keys.EVENT_FORWARD_URL)); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + + String userInfo = url.getUserInfo(); + Mqtt5SimpleAuth simpleAuth = null; + if (userInfo != null) { + int delimiter = userInfo.indexOf(':'); + if (delimiter == -1) { + throw new IllegalArgumentException("Wrong credentials. Should be in format \"username:password\""); + } else { + simpleAuth = Mqtt5SimpleAuth.builder() + .username(userInfo.substring(0, delimiter++)) + .password(userInfo.substring(delimiter).getBytes()) + .build(); + } + } + + String host = url.getHost(); + int port = url.getPort(); + client = Mqtt5Client.builder() + .identifier("traccar-" + UUID.randomUUID().toString()) + .serverHost(host) + .serverPort(port) + .simpleAuth(simpleAuth) + .automaticReconnectWithDefaultConfig() + .buildAsync(); + + client.connectWith() + .send() + .whenComplete((message, e) -> { + if (e != null) { + throw new RuntimeException(e); + } + }); + + this.objectMapper = objectMapper; + topic = config.getString(Keys.EVENT_FORWARD_TOPIC); + } + + @Override + public void forward(EventData eventData, ResultHandler resultHandler) { + byte[] payload; + try { + payload = objectMapper.writeValueAsString(eventData).getBytes(); + } catch (JsonProcessingException e) { + resultHandler.onResult(false, e); + return; + } + + client.publishWith() + .topic(topic) + .qos(MqttQos.AT_LEAST_ONCE) + .payload(payload) + .send() + .whenComplete((message, e) -> resultHandler.onResult(e == null, e)); + } + +} diff --git a/src/main/java/org/traccar/forward/PositionData.java b/src/main/java/org/traccar/forward/PositionData.java new file mode 100644 index 000000000..784cf52f5 --- /dev/null +++ b/src/main/java/org/traccar/forward/PositionData.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.forward; + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.traccar.model.Device; +import org.traccar.model.Position; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class PositionData { + + private Position position; + + public Position getPosition() { + return position; + } + + public void setPosition(Position position) { + this.position = position; + } + + private Device device; + + public Device getDevice() { + return device; + } + + public void setDevice(Device device) { + this.device = device; + } + +} diff --git a/src/main/java/org/traccar/forward/PositionForwarder.java b/src/main/java/org/traccar/forward/PositionForwarder.java new file mode 100644 index 000000000..58bd1dcc7 --- /dev/null +++ b/src/main/java/org/traccar/forward/PositionForwarder.java @@ -0,0 +1,20 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.forward; + +public interface PositionForwarder { + void forward(PositionData positionData, ResultHandler resultHandler); +} diff --git a/src/main/java/org/traccar/forward/PositionForwarderJson.java b/src/main/java/org/traccar/forward/PositionForwarderJson.java new file mode 100644 index 000000000..27b96308e --- /dev/null +++ b/src/main/java/org/traccar/forward/PositionForwarderJson.java @@ -0,0 +1,86 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.forward; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.traccar.config.Config; +import org.traccar.config.Keys; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.InvocationCallback; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +public class PositionForwarderJson implements PositionForwarder { + + private final String url; + private final String header; + + private final Client client; + private final ObjectMapper objectMapper; + + public PositionForwarderJson(Config config, Client client, ObjectMapper objectMapper) { + this.client = client; + this.objectMapper = objectMapper; + this.url = config.getString(Keys.FORWARD_URL); + this.header = config.getString(Keys.FORWARD_HEADER); + } + + @Override + public void forward(PositionData positionData, ResultHandler resultHandler) { + var requestBuilder = client.target(url).request(); + + MediaType mediaType = MediaType.APPLICATION_JSON_TYPE; + if (header != null && !header.isEmpty()) { + for (String line: header.split("\\r?\\n")) { + String[] values = line.split(":", 2); + String headerName = values[0].trim(); + String headerValue = values[1].trim(); + if (headerName.equals(HttpHeaders.CONTENT_TYPE)) { + mediaType = MediaType.valueOf(headerValue); + } else { + requestBuilder.header(headerName, headerValue); + } + } + } + + try { + var entity = Entity.entity(objectMapper.writeValueAsString(positionData), mediaType); + requestBuilder.async().post(entity, new InvocationCallback<Response>() { + @Override + public void completed(Response response) { + if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) { + resultHandler.onResult(true, null); + } else { + int code = response.getStatusInfo().getStatusCode(); + resultHandler.onResult(false, new RuntimeException("HTTP code " + code)); + } + } + + @Override + public void failed(Throwable throwable) { + resultHandler.onResult(false, throwable); + } + }); + } catch (JsonProcessingException e) { + resultHandler.onResult(false, e); + } + } + +} diff --git a/src/main/java/org/traccar/forward/PositionForwarderKafka.java b/src/main/java/org/traccar/forward/PositionForwarderKafka.java new file mode 100644 index 000000000..7432e9364 --- /dev/null +++ b/src/main/java/org/traccar/forward/PositionForwarderKafka.java @@ -0,0 +1,58 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.forward; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.Producer; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.traccar.config.Config; +import org.traccar.config.Keys; + +import java.util.Properties; + +public class PositionForwarderKafka implements PositionForwarder { + + private final Producer<String, String> producer; + private final ObjectMapper objectMapper; + + private final String topic; + + public PositionForwarderKafka(Config config, ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + Properties properties = new Properties(); + properties.put("bootstrap.servers", config.getString(Keys.FORWARD_URL)); + properties.put("acks", "all"); + properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); + properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); + producer = new KafkaProducer<>(properties); + topic = config.getString(Keys.FORWARD_TOPIC); + } + + @Override + public void forward(PositionData positionData, ResultHandler resultHandler) { + try { + String key = Long.toString(positionData.getDevice().getId()); + String value = objectMapper.writeValueAsString(positionData); + producer.send(new ProducerRecord<>(topic, key, value)); + resultHandler.onResult(true, null); + } catch (JsonProcessingException e) { + resultHandler.onResult(false, e); + } + } + +} diff --git a/src/main/java/org/traccar/forward/PositionForwarderUrl.java b/src/main/java/org/traccar/forward/PositionForwarderUrl.java new file mode 100644 index 000000000..53cc7ad24 --- /dev/null +++ b/src/main/java/org/traccar/forward/PositionForwarderUrl.java @@ -0,0 +1,166 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.forward; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.Checksum; +import org.traccar.model.Device; +import org.traccar.model.Position; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.InvocationCallback; +import javax.ws.rs.core.Response; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Calendar; +import java.util.Formatter; +import java.util.Locale; +import java.util.TimeZone; + +public class PositionForwarderUrl implements PositionForwarder { + + private final String url; + private final String header; + + private final Client client; + private final ObjectMapper objectMapper; + + public PositionForwarderUrl(Config config, Client client, ObjectMapper objectMapper) { + this.client = client; + this.objectMapper = objectMapper; + this.url = config.getString(Keys.FORWARD_URL); + this.header = config.getString(Keys.FORWARD_HEADER); + } + + @Override + public void forward(PositionData positionData, ResultHandler resultHandler) { + try { + String url = formatRequest(positionData); + var requestBuilder = client.target(url).request(); + + if (header != null && !header.isEmpty()) { + for (String line: header.split("\\r?\\n")) { + String[] values = line.split(":", 2); + String headerName = values[0].trim(); + String headerValue = values[1].trim(); + requestBuilder.header(headerName, headerValue); + } + } + + requestBuilder.async().get(new InvocationCallback<Response>() { + @Override + public void completed(Response response) { + if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) { + resultHandler.onResult(true, null); + } else { + int code = response.getStatusInfo().getStatusCode(); + resultHandler.onResult(false, new RuntimeException("HTTP code " + code)); + } + } + + @Override + public void failed(Throwable throwable) { + resultHandler.onResult(false, throwable); + } + }); + } catch (UnsupportedEncodingException | JsonProcessingException e) { + resultHandler.onResult(false, e); + } + } + + public String formatRequest( + PositionData positionData) throws UnsupportedEncodingException, JsonProcessingException { + + Position position = positionData.getPosition(); + Device device = positionData.getDevice(); + + String request = url + .replace("{name}", URLEncoder.encode(device.getName(), StandardCharsets.UTF_8)) + .replace("{uniqueId}", device.getUniqueId()) + .replace("{status}", device.getStatus()) + .replace("{deviceId}", String.valueOf(position.getDeviceId())) + .replace("{protocol}", String.valueOf(position.getProtocol())) + .replace("{deviceTime}", String.valueOf(position.getDeviceTime().getTime())) + .replace("{fixTime}", String.valueOf(position.getFixTime().getTime())) + .replace("{valid}", String.valueOf(position.getValid())) + .replace("{latitude}", String.valueOf(position.getLatitude())) + .replace("{longitude}", String.valueOf(position.getLongitude())) + .replace("{altitude}", String.valueOf(position.getAltitude())) + .replace("{speed}", String.valueOf(position.getSpeed())) + .replace("{course}", String.valueOf(position.getCourse())) + .replace("{accuracy}", String.valueOf(position.getAccuracy())) + .replace("{statusCode}", calculateStatus(position)); + + if (position.getAddress() != null) { + request = request.replace( + "{address}", URLEncoder.encode(position.getAddress(), StandardCharsets.UTF_8)); + } + + if (request.contains("{attributes}")) { + String attributes = objectMapper.writeValueAsString(position.getAttributes()); + request = request.replace( + "{attributes}", URLEncoder.encode(attributes, StandardCharsets.UTF_8)); + } + + if (request.contains("{gprmc}")) { + request = request.replace("{gprmc}", formatSentence(position)); + } + + return request; + } + + private static String formatSentence(Position position) { + + StringBuilder s = new StringBuilder("$GPRMC,"); + + try (Formatter f = new Formatter(s, Locale.ENGLISH)) { + + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ENGLISH); + calendar.setTimeInMillis(position.getFixTime().getTime()); + + f.format("%1$tH%1$tM%1$tS.%1$tL,A,", calendar); + + double lat = position.getLatitude(); + double lon = position.getLongitude(); + + f.format("%02d%07.4f,%c,", (int) Math.abs(lat), Math.abs(lat) % 1 * 60, lat < 0 ? 'S' : 'N'); + f.format("%03d%07.4f,%c,", (int) Math.abs(lon), Math.abs(lon) % 1 * 60, lon < 0 ? 'W' : 'E'); + + f.format("%.2f,%.2f,", position.getSpeed(), position.getCourse()); + f.format("%1$td%1$tm%1$ty,,", calendar); + } + + s.append(Checksum.nmea(s.substring(1))); + + return s.toString(); + } + + // OpenGTS status code + private String calculateStatus(Position position) { + if (position.hasAttribute(Position.KEY_ALARM)) { + return "0xF841"; // STATUS_PANIC_ON + } else if (position.getSpeed() < 1.0) { + return "0xF020"; // STATUS_LOCATION + } else { + return "0xF11C"; // STATUS_MOTION_MOVING + } + } + +} diff --git a/src/main/java/org/traccar/forward/ResultHandler.java b/src/main/java/org/traccar/forward/ResultHandler.java new file mode 100644 index 000000000..009daf495 --- /dev/null +++ b/src/main/java/org/traccar/forward/ResultHandler.java @@ -0,0 +1,20 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.forward; + +public interface ResultHandler { + void onResult(boolean success, Throwable throwable); +} diff --git a/src/main/java/org/traccar/geocoder/BanGeocoder.java b/src/main/java/org/traccar/geocoder/BanGeocoder.java index b1f0900a4..f878a8bab 100644 --- a/src/main/java/org/traccar/geocoder/BanGeocoder.java +++ b/src/main/java/org/traccar/geocoder/BanGeocoder.java @@ -22,11 +22,12 @@ package org.traccar.geocoder; import javax.json.JsonArray; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class BanGeocoder extends JsonGeocoder { - public BanGeocoder(int cacheSize, AddressFormat addressFormat) { - super("https://api-adresse.data.gouv.fr/reverse/?lat=%f&lon=%f", cacheSize, addressFormat); + public BanGeocoder(Client client, int cacheSize, AddressFormat addressFormat) { + super(client, "https://api-adresse.data.gouv.fr/reverse/?lat=%f&lon=%f", cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/BingMapsGeocoder.java b/src/main/java/org/traccar/geocoder/BingMapsGeocoder.java index 32a26ee0c..01e33c2ea 100644 --- a/src/main/java/org/traccar/geocoder/BingMapsGeocoder.java +++ b/src/main/java/org/traccar/geocoder/BingMapsGeocoder.java @@ -18,11 +18,12 @@ package org.traccar.geocoder; import javax.json.JsonArray; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class BingMapsGeocoder extends JsonGeocoder { - public BingMapsGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { - super(url + "/Locations/%f,%f?key=" + key + "&include=ciso2", cacheSize, addressFormat); + public BingMapsGeocoder(Client client, String url, String key, int cacheSize, AddressFormat addressFormat) { + super(client, url + "/Locations/%f,%f?key=" + key + "&include=ciso2", cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/FactualGeocoder.java b/src/main/java/org/traccar/geocoder/FactualGeocoder.java index f540eb8fe..384f46b0e 100644 --- a/src/main/java/org/traccar/geocoder/FactualGeocoder.java +++ b/src/main/java/org/traccar/geocoder/FactualGeocoder.java @@ -17,6 +17,7 @@ package org.traccar.geocoder; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class FactualGeocoder extends JsonGeocoder { @@ -28,8 +29,8 @@ public class FactualGeocoder extends JsonGeocoder { return url; } - public FactualGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(url, key), cacheSize, addressFormat); + public FactualGeocoder(Client client, String url, String key, int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(url, key), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/GeoapifyGeocoder.java b/src/main/java/org/traccar/geocoder/GeoapifyGeocoder.java new file mode 100644 index 000000000..4748d6a2c --- /dev/null +++ b/src/main/java/org/traccar/geocoder/GeoapifyGeocoder.java @@ -0,0 +1,82 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.ws.rs.client.Client; + +public class GeoapifyGeocoder extends JsonGeocoder { + + private static String formatUrl(String key, String language) { + String url = "https://api.geoapify.com/v1/geocode/reverse?format=json&lat=%f&lon=%f"; + if (key != null) { + url += "&apiKey=" + key; + } + if (language != null) { + url += "&lang=" + language; + } + return url; + } + + public GeoapifyGeocoder(Client client, String key, String language, int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(key, language), cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + JsonArray results = json.getJsonArray("results"); + if (results.size() > 0) { + JsonObject result = results.getJsonObject(0); + + Address address = new Address(); + + if (json.containsKey("formatted")) { + address.setFormattedAddress(json.getString("formatted")); + } + + if (result.containsKey("housenumber")) { + address.setHouse(result.getString("housenumber")); + } + if (result.containsKey("street")) { + address.setStreet(result.getString("street")); + } + if (result.containsKey("suburb")) { + address.setSuburb(result.getString("suburb")); + } + if (result.containsKey("city")) { + address.setSettlement(result.getString("city")); + } + if (result.containsKey("district")) { + address.setDistrict(result.getString("district")); + } + if (result.containsKey("state")) { + address.setState(result.getString("state")); + } + if (result.containsKey("country_code")) { + address.setCountry(result.getString("country_code").toUpperCase()); + } + if (result.containsKey("postcode")) { + address.setPostcode(result.getString("postcode")); + } + + return address; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/geocoder/GeocodeFarmGeocoder.java b/src/main/java/org/traccar/geocoder/GeocodeFarmGeocoder.java index 39a3300a0..2af95910f 100644 --- a/src/main/java/org/traccar/geocoder/GeocodeFarmGeocoder.java +++ b/src/main/java/org/traccar/geocoder/GeocodeFarmGeocoder.java @@ -16,6 +16,7 @@ package org.traccar.geocoder; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class GeocodeFarmGeocoder extends JsonGeocoder { @@ -30,8 +31,9 @@ public class GeocodeFarmGeocoder extends JsonGeocoder { } return url; } - public GeocodeFarmGeocoder(String key, String language, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(key, language), cacheSize, addressFormat); + public GeocodeFarmGeocoder( + Client client, String key, String language, int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(key, language), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/GeocodeXyzGeocoder.java b/src/main/java/org/traccar/geocoder/GeocodeXyzGeocoder.java index aca360c3d..96491ece3 100644 --- a/src/main/java/org/traccar/geocoder/GeocodeXyzGeocoder.java +++ b/src/main/java/org/traccar/geocoder/GeocodeXyzGeocoder.java @@ -16,6 +16,7 @@ package org.traccar.geocoder; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class GeocodeXyzGeocoder extends JsonGeocoder { @@ -27,8 +28,8 @@ public class GeocodeXyzGeocoder extends JsonGeocoder { return url; } - public GeocodeXyzGeocoder(String key, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(key), cacheSize, addressFormat); + public GeocodeXyzGeocoder(Client client, String key, int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(key), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/Geocoder.java b/src/main/java/org/traccar/geocoder/Geocoder.java index 587a27520..f4abe871a 100644 --- a/src/main/java/org/traccar/geocoder/Geocoder.java +++ b/src/main/java/org/traccar/geocoder/Geocoder.java @@ -15,6 +15,8 @@ */ package org.traccar.geocoder; +import org.traccar.database.StatisticsManager; + public interface Geocoder { interface ReverseGeocoderCallback { @@ -27,4 +29,6 @@ public interface Geocoder { String getAddress(double latitude, double longitude, ReverseGeocoderCallback callback); + void setStatisticsManager(StatisticsManager statisticsManager); + } diff --git a/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java b/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java index b4881a006..0589eb000 100644 --- a/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java +++ b/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java @@ -16,6 +16,7 @@ package org.traccar.geocoder; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class GisgraphyGeocoder extends JsonGeocoder { @@ -27,8 +28,8 @@ public class GisgraphyGeocoder extends JsonGeocoder { return url; } - public GisgraphyGeocoder(String url, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(url), cacheSize, addressFormat); + public GisgraphyGeocoder(Client client, String url, int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(url), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/GoogleGeocoder.java b/src/main/java/org/traccar/geocoder/GoogleGeocoder.java index 9494cab45..4d9ec8f36 100644 --- a/src/main/java/org/traccar/geocoder/GoogleGeocoder.java +++ b/src/main/java/org/traccar/geocoder/GoogleGeocoder.java @@ -18,6 +18,7 @@ package org.traccar.geocoder; import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonString; +import javax.ws.rs.client.Client; public class GoogleGeocoder extends JsonGeocoder { @@ -32,8 +33,8 @@ public class GoogleGeocoder extends JsonGeocoder { return url; } - public GoogleGeocoder(String key, String language, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(key, language), cacheSize, addressFormat); + public GoogleGeocoder(Client client, String key, String language, int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(key, language), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/HereGeocoder.java b/src/main/java/org/traccar/geocoder/HereGeocoder.java index 40390e65b..eb639995e 100644 --- a/src/main/java/org/traccar/geocoder/HereGeocoder.java +++ b/src/main/java/org/traccar/geocoder/HereGeocoder.java @@ -16,6 +16,7 @@ package org.traccar.geocoder; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class HereGeocoder extends JsonGeocoder { @@ -35,8 +36,9 @@ public class HereGeocoder extends JsonGeocoder { } public HereGeocoder( - String url, String id, String key, String language, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(url, id, key, language), cacheSize, addressFormat); + Client client, String url, String id, String key, String language, + int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(url, id, key, language), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/JsonGeocoder.java b/src/main/java/org/traccar/geocoder/JsonGeocoder.java index f20aa79d6..6105e8cfd 100644 --- a/src/main/java/org/traccar/geocoder/JsonGeocoder.java +++ b/src/main/java/org/traccar/geocoder/JsonGeocoder.java @@ -17,13 +17,11 @@ package org.traccar.geocoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.Main; import org.traccar.database.StatisticsManager; import javax.json.JsonObject; import javax.ws.rs.WebApplicationException; -import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.Client; import javax.ws.rs.client.InvocationCallback; import java.util.AbstractMap; import java.util.Collections; @@ -34,16 +32,19 @@ public abstract class JsonGeocoder implements Geocoder { private static final Logger LOGGER = LoggerFactory.getLogger(JsonGeocoder.class); + private final Client client; private final String url; private final AddressFormat addressFormat; + private StatisticsManager statisticsManager; private Map<Map.Entry<Double, Double>, String> cache; - public JsonGeocoder(String url, final int cacheSize, AddressFormat addressFormat) { + public JsonGeocoder(Client client, String url, final int cacheSize, AddressFormat addressFormat) { + this.client = client; this.url = url; this.addressFormat = addressFormat; if (cacheSize > 0) { - this.cache = Collections.synchronizedMap(new LinkedHashMap<Map.Entry<Double, Double>, String>() { + this.cache = Collections.synchronizedMap(new LinkedHashMap<>() { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > cacheSize; @@ -52,6 +53,11 @@ public abstract class JsonGeocoder implements Geocoder { } } + @Override + public void setStatisticsManager(StatisticsManager statisticsManager) { + this.statisticsManager = statisticsManager; + } + protected String readValue(JsonObject object, String key) { if (object.containsKey(key) && !object.isNull(key)) { return object.getString(key); @@ -97,11 +103,11 @@ public abstract class JsonGeocoder implements Geocoder { } } - if (Main.getInjector() != null) { - Main.getInjector().getInstance(StatisticsManager.class).registerGeocoderRequest(); + if (statisticsManager != null) { + statisticsManager.registerGeocoderRequest(); } - Invocation.Builder request = Context.getClient().target(String.format(url, latitude, longitude)).request(); + var request = client.target(String.format(url, latitude, longitude)).request(); if (callback != null) { request.async().get(new InvocationCallback<JsonObject>() { diff --git a/src/main/java/org/traccar/geocoder/LocationIqGeocoder.java b/src/main/java/org/traccar/geocoder/LocationIqGeocoder.java new file mode 100644 index 000000000..f2ffe02d6 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/LocationIqGeocoder.java @@ -0,0 +1,29 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.ws.rs.client.Client; + +public class LocationIqGeocoder extends NominatimGeocoder { + + private static final String DEFAULT_URL = "https://us1.locationiq.com/v1/reverse.php"; + + public LocationIqGeocoder( + Client client, String url, String key, String language, int cacheSize, AddressFormat addressFormat) { + super(client, url != null ? url : DEFAULT_URL, key, language, cacheSize, addressFormat); + } + +} diff --git a/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java b/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java index 8dc3f76f0..3f2554c6e 100644 --- a/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java +++ b/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java @@ -18,6 +18,7 @@ package org.traccar.geocoder; import javax.json.JsonArray; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class MapQuestGeocoder extends JsonGeocoder { @@ -29,8 +30,8 @@ public class MapQuestGeocoder extends JsonGeocoder { return url; } - public MapQuestGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(url, key), cacheSize, addressFormat); + public MapQuestGeocoder(Client client, String url, String key, int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(url, key), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/MapTilerGeocoder.java b/src/main/java/org/traccar/geocoder/MapTilerGeocoder.java index 6b688a6e8..203f5f99b 100644 --- a/src/main/java/org/traccar/geocoder/MapTilerGeocoder.java +++ b/src/main/java/org/traccar/geocoder/MapTilerGeocoder.java @@ -17,11 +17,12 @@ package org.traccar.geocoder; import javax.json.JsonArray; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class MapTilerGeocoder extends JsonGeocoder { - public MapTilerGeocoder(String key, int cacheSize, AddressFormat addressFormat) { - super("https://api.maptiler.com/geocoding/%2$f,%1$f.json?key=" + key, cacheSize, addressFormat); + public MapTilerGeocoder(Client client, String key, int cacheSize, AddressFormat addressFormat) { + super(client, "https://api.maptiler.com/geocoding/%2$f,%1$f.json?key=" + key, cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/MapboxGeocoder.java b/src/main/java/org/traccar/geocoder/MapboxGeocoder.java index 9b987c9d8..72bfb53f5 100644 --- a/src/main/java/org/traccar/geocoder/MapboxGeocoder.java +++ b/src/main/java/org/traccar/geocoder/MapboxGeocoder.java @@ -18,6 +18,7 @@ package org.traccar.geocoder; import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonString; +import javax.ws.rs.client.Client; public class MapboxGeocoder extends JsonGeocoder { @@ -25,8 +26,8 @@ public class MapboxGeocoder extends JsonGeocoder { return "https://api.mapbox.com/geocoding/v5/mapbox.places/%2$f,%1$f.json?access_token=" + key; } - public MapboxGeocoder(String key, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(key), cacheSize, addressFormat); + public MapboxGeocoder(Client client, String key, int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(key), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/MapmyIndiaGeocoder.java b/src/main/java/org/traccar/geocoder/MapmyIndiaGeocoder.java index 2b70708a1..dea295cca 100644 --- a/src/main/java/org/traccar/geocoder/MapmyIndiaGeocoder.java +++ b/src/main/java/org/traccar/geocoder/MapmyIndiaGeocoder.java @@ -17,11 +17,12 @@ package org.traccar.geocoder; import javax.json.JsonArray; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class MapmyIndiaGeocoder extends JsonGeocoder { - public MapmyIndiaGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { - super(url + "/" + key + "/rev_geocode?lat=%f&lng=%f", cacheSize, addressFormat); + public MapmyIndiaGeocoder(Client client, String url, String key, int cacheSize, AddressFormat addressFormat) { + super(client, url + "/" + key + "/rev_geocode?lat=%f&lng=%f", cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/NominatimGeocoder.java b/src/main/java/org/traccar/geocoder/NominatimGeocoder.java index 8db25bf15..b731549f7 100644 --- a/src/main/java/org/traccar/geocoder/NominatimGeocoder.java +++ b/src/main/java/org/traccar/geocoder/NominatimGeocoder.java @@ -16,6 +16,7 @@ package org.traccar.geocoder; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class NominatimGeocoder extends JsonGeocoder { @@ -33,8 +34,9 @@ public class NominatimGeocoder extends JsonGeocoder { return url; } - public NominatimGeocoder(String url, String key, String language, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(url, key, language), cacheSize, addressFormat); + public NominatimGeocoder( + Client client, String url, String key, String language, int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(url, key, language), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java b/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java index 56161e52c..fb61440aa 100644 --- a/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java +++ b/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java @@ -18,19 +18,24 @@ package org.traccar.geocoder; import javax.json.JsonArray; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class OpenCageGeocoder extends JsonGeocoder { - private static String formatUrl(String url, String key) { + private static String formatUrl(String url, String key, String language) { if (url == null) { url = "https://api.opencagedata.com/geocode/v1"; } url += "/json?q=%f,%f&no_annotations=1&key=" + key; + if (language != null) { + url += "&language=" + language; + } return url; } - public OpenCageGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(url, key), cacheSize, addressFormat); + public OpenCageGeocoder( + Client client, String url, String key, String language, int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(url, key, language), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/PositionStackGeocoder.java b/src/main/java/org/traccar/geocoder/PositionStackGeocoder.java index 2674a68ca..9778d9eda 100644 --- a/src/main/java/org/traccar/geocoder/PositionStackGeocoder.java +++ b/src/main/java/org/traccar/geocoder/PositionStackGeocoder.java @@ -17,6 +17,7 @@ package org.traccar.geocoder; import javax.json.JsonArray; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class PositionStackGeocoder extends JsonGeocoder { @@ -24,8 +25,8 @@ public class PositionStackGeocoder extends JsonGeocoder { return "http://api.positionstack.com/v1/reverse?access_key=" + key + "&query=%f,%f"; } - public PositionStackGeocoder(String key, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(key), cacheSize, addressFormat); + public PositionStackGeocoder(Client client, String key, int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(key), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/TestGeocoder.java b/src/main/java/org/traccar/geocoder/TestGeocoder.java new file mode 100644 index 000000000..259f13c6c --- /dev/null +++ b/src/main/java/org/traccar/geocoder/TestGeocoder.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import org.traccar.database.StatisticsManager; + +public class TestGeocoder implements Geocoder { + + @Override + public void setStatisticsManager(StatisticsManager statisticsManager) { + } + + @Override + public String getAddress(double latitude, double longitude, ReverseGeocoderCallback callback) { + String address = String.format("Location %f, %f", latitude, longitude); + if (callback != null) { + callback.onSuccess(address); + return null; + } + return address; + } + +} diff --git a/src/main/java/org/traccar/geocoder/TomTomGeocoder.java b/src/main/java/org/traccar/geocoder/TomTomGeocoder.java index 232b24396..9bb36efc2 100644 --- a/src/main/java/org/traccar/geocoder/TomTomGeocoder.java +++ b/src/main/java/org/traccar/geocoder/TomTomGeocoder.java @@ -17,6 +17,7 @@ package org.traccar.geocoder; import javax.json.JsonArray; import javax.json.JsonObject; +import javax.ws.rs.client.Client; public class TomTomGeocoder extends JsonGeocoder { @@ -28,8 +29,8 @@ public class TomTomGeocoder extends JsonGeocoder { return url; } - public TomTomGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { - super(formatUrl(url, key), cacheSize, addressFormat); + public TomTomGeocoder(Client client, String url, String key, int cacheSize, AddressFormat addressFormat) { + super(client, formatUrl(url, key), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geofence/GeofenceCircle.java b/src/main/java/org/traccar/geofence/GeofenceCircle.java index 5d566f84e..59feb1730 100644 --- a/src/main/java/org/traccar/geofence/GeofenceCircle.java +++ b/src/main/java/org/traccar/geofence/GeofenceCircle.java @@ -18,7 +18,9 @@ package org.traccar.geofence; import java.text.DecimalFormat; import java.text.ParseException; +import org.traccar.config.Config; import org.traccar.helper.DistanceCalculator; +import org.traccar.model.Geofence; public class GeofenceCircle extends GeofenceGeometry { @@ -44,7 +46,7 @@ public class GeofenceCircle extends GeofenceGeometry { } @Override - public boolean containsPoint(double latitude, double longitude) { + public boolean containsPoint(Config config, Geofence geofence, double latitude, double longitude) { return distanceFromCenter(latitude, longitude) <= radius; } diff --git a/src/main/java/org/traccar/geofence/GeofenceGeometry.java b/src/main/java/org/traccar/geofence/GeofenceGeometry.java index 2c45c22af..fadabab1c 100644 --- a/src/main/java/org/traccar/geofence/GeofenceGeometry.java +++ b/src/main/java/org/traccar/geofence/GeofenceGeometry.java @@ -15,11 +15,14 @@ */ package org.traccar.geofence; +import org.traccar.config.Config; +import org.traccar.model.Geofence; + import java.text.ParseException; public abstract class GeofenceGeometry { - public abstract boolean containsPoint(double latitude, double longitude); + public abstract boolean containsPoint(Config config, Geofence geofence, double latitude, double longitude); public abstract double calculateArea(); diff --git a/src/main/java/org/traccar/geofence/GeofencePolygon.java b/src/main/java/org/traccar/geofence/GeofencePolygon.java index cd2cbf16a..13f6658ef 100644 --- a/src/main/java/org/traccar/geofence/GeofencePolygon.java +++ b/src/main/java/org/traccar/geofence/GeofencePolygon.java @@ -19,6 +19,8 @@ import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.shape.ShapeFactory; import org.locationtech.spatial4j.shape.jts.JtsShapeFactory; +import org.traccar.config.Config; +import org.traccar.model.Geofence; import java.text.ParseException; import java.util.ArrayList; @@ -95,7 +97,7 @@ public class GeofencePolygon extends GeofenceGeometry { } @Override - public boolean containsPoint(double latitude, double longitude) { + public boolean containsPoint(Config config, Geofence geofence, double latitude, double longitude) { int polyCorners = coordinates.size(); int i; diff --git a/src/main/java/org/traccar/geofence/GeofencePolyline.java b/src/main/java/org/traccar/geofence/GeofencePolyline.java index 370bf99bb..d9c280ae4 100644 --- a/src/main/java/org/traccar/geofence/GeofencePolyline.java +++ b/src/main/java/org/traccar/geofence/GeofencePolyline.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,23 +19,28 @@ package org.traccar.geofence; import java.text.ParseException; import java.util.ArrayList; +import org.traccar.config.Config; +import org.traccar.config.Keys; import org.traccar.helper.DistanceCalculator; +import org.traccar.model.Geofence; public class GeofencePolyline extends GeofenceGeometry { private ArrayList<Coordinate> coordinates; - private double distance; public GeofencePolyline() { } - public GeofencePolyline(String wkt, double distance) throws ParseException { + public GeofencePolyline(String wkt) throws ParseException { fromWkt(wkt); - this.distance = distance; } @Override - public boolean containsPoint(double latitude, double longitude) { + public boolean containsPoint(Config config, Geofence geofence, double latitude, double longitude) { + double distance = geofence.getDouble("polylineDistance"); + if (distance == 0) { + distance = config.getDouble(Keys.GEOFENCE_POLYLINE_DISTANCE); + } for (int i = 1; i < coordinates.size(); i++) { if (DistanceCalculator.distanceToLine( latitude, longitude, coordinates.get(i - 1).getLat(), coordinates.get(i - 1).getLon(), @@ -56,9 +61,9 @@ public class GeofencePolyline extends GeofenceGeometry { StringBuilder buf = new StringBuilder(); buf.append("LINESTRING ("); for (Coordinate coordinate : coordinates) { - buf.append(String.valueOf(coordinate.getLat())); + buf.append(coordinate.getLat()); buf.append(" "); - buf.append(String.valueOf(coordinate.getLon())); + buf.append(coordinate.getLon()); buf.append(", "); } return buf.substring(0, buf.length() - 2) + ")"; @@ -105,8 +110,4 @@ public class GeofencePolyline extends GeofenceGeometry { } - public void setDistance(double distance) { - this.distance = distance; - } - } diff --git a/src/main/java/org/traccar/geolocation/GoogleGeolocationProvider.java b/src/main/java/org/traccar/geolocation/GoogleGeolocationProvider.java index 5901b47cd..8f0f3b704 100644 --- a/src/main/java/org/traccar/geolocation/GoogleGeolocationProvider.java +++ b/src/main/java/org/traccar/geolocation/GoogleGeolocationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,14 @@ */ package org.traccar.geolocation; +import javax.ws.rs.client.Client; + public class GoogleGeolocationProvider extends UniversalGeolocationProvider { private static final String URL = "https://www.googleapis.com/geolocation/v1/geolocate"; - public GoogleGeolocationProvider(String key) { - super(URL, key); + public GoogleGeolocationProvider(Client client, String key) { + super(client, URL, key); } } diff --git a/src/main/java/org/traccar/geolocation/MozillaGeolocationProvider.java b/src/main/java/org/traccar/geolocation/MozillaGeolocationProvider.java index c6a73a52b..3b4ba4e1f 100644 --- a/src/main/java/org/traccar/geolocation/MozillaGeolocationProvider.java +++ b/src/main/java/org/traccar/geolocation/MozillaGeolocationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,14 @@ */ package org.traccar.geolocation; +import javax.ws.rs.client.Client; + public class MozillaGeolocationProvider extends UniversalGeolocationProvider { private static final String URL = "https://location.services.mozilla.com/v1/geolocate"; - public MozillaGeolocationProvider(String key) { - super(URL, key != null ? key : "test"); + public MozillaGeolocationProvider(Client client, String key) { + super(client, URL, key != null ? key : "test"); } } diff --git a/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java b/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java index 2535970d3..82fcf42ab 100644 --- a/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java +++ b/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,20 @@ */ package org.traccar.geolocation; -import org.traccar.Context; import org.traccar.model.CellTower; import org.traccar.model.Network; import javax.json.JsonObject; +import javax.ws.rs.client.Client; import javax.ws.rs.client.InvocationCallback; public class OpenCellIdGeolocationProvider implements GeolocationProvider { - private String url; + private final Client client; + private final String url; - public OpenCellIdGeolocationProvider(String url, String key) { + public OpenCellIdGeolocationProvider(Client client, String url, String key) { + this.client = client; if (url == null) { url = "http://opencellid.org/cell/get"; } @@ -41,7 +43,7 @@ public class OpenCellIdGeolocationProvider implements GeolocationProvider { String request = String.format(url, cellTower.getMobileCountryCode(), cellTower.getMobileNetworkCode(), cellTower.getLocationAreaCode(), cellTower.getCellId()); - Context.getClient().target(request).request().async().get(new InvocationCallback<JsonObject>() { + client.target(request).request().async().get(new InvocationCallback<JsonObject>() { @Override public void completed(JsonObject json) { if (json.containsKey("lat") && json.containsKey("lon")) { diff --git a/src/main/java/org/traccar/geolocation/UniversalGeolocationProvider.java b/src/main/java/org/traccar/geolocation/UniversalGeolocationProvider.java index f71620d8a..7a3f71ee1 100644 --- a/src/main/java/org/traccar/geolocation/UniversalGeolocationProvider.java +++ b/src/main/java/org/traccar/geolocation/UniversalGeolocationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,26 +15,26 @@ */ package org.traccar.geolocation; -import org.traccar.Context; import org.traccar.model.Network; import javax.json.JsonObject; -import javax.ws.rs.client.AsyncInvoker; +import javax.ws.rs.client.Client; import javax.ws.rs.client.Entity; import javax.ws.rs.client.InvocationCallback; public class UniversalGeolocationProvider implements GeolocationProvider { - private String url; + private final Client client; + private final String url; - public UniversalGeolocationProvider(String url, String key) { + public UniversalGeolocationProvider(Client client, String url, String key) { + this.client = client; this.url = url + "?key=" + key; } @Override public void getLocation(Network network, final LocationProviderCallback callback) { - AsyncInvoker invoker = Context.getClient().target(url).request().async(); - invoker.post(Entity.json(network), new InvocationCallback<JsonObject>() { + client.target(url).request().async().post(Entity.json(network), new InvocationCallback<JsonObject>() { @Override public void completed(JsonObject json) { if (json.containsKey("error")) { diff --git a/src/main/java/org/traccar/geolocation/UnwiredGeolocationProvider.java b/src/main/java/org/traccar/geolocation/UnwiredGeolocationProvider.java index 963bcb688..14893b6a3 100644 --- a/src/main/java/org/traccar/geolocation/UnwiredGeolocationProvider.java +++ b/src/main/java/org/traccar/geolocation/UnwiredGeolocationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,22 +19,23 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.traccar.Context; import org.traccar.model.CellTower; import org.traccar.model.Network; import org.traccar.model.WifiAccessPoint; import javax.json.JsonObject; +import javax.ws.rs.client.Client; import javax.ws.rs.client.Entity; import javax.ws.rs.client.InvocationCallback; import java.util.Collection; public class UnwiredGeolocationProvider implements GeolocationProvider { - private String url; - private String key; + private final Client client; + private final String url; + private final String key; - private ObjectMapper objectMapper; + private final ObjectMapper objectMapper; private abstract static class NetworkMixIn { @JsonProperty("mcc") @@ -73,7 +74,8 @@ public class UnwiredGeolocationProvider implements GeolocationProvider { abstract Integer getSignalStrength(); } - public UnwiredGeolocationProvider(String url, String key) { + public UnwiredGeolocationProvider(Client client, String url, String key) { + this.client = client; this.url = url; this.key = key; @@ -88,7 +90,7 @@ public class UnwiredGeolocationProvider implements GeolocationProvider { ObjectNode json = objectMapper.valueToTree(network); json.put("token", key); - Context.getClient().target(url).request().async().post(Entity.json(json), new InvocationCallback<JsonObject>() { + client.target(url).request().async().post(Entity.json(json), new InvocationCallback<JsonObject>() { @Override public void completed(JsonObject json) { if (json.getString("status").equals("error")) { diff --git a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java index 153da29b9..c9f1f63d7 100644 --- a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java +++ b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,28 +34,29 @@ import org.slf4j.LoggerFactory; import org.traccar.BaseDataHandler; import org.traccar.config.Config; import org.traccar.config.Keys; -import org.traccar.database.AttributesManager; -import org.traccar.database.IdentityManager; import org.traccar.model.Attribute; import org.traccar.model.Device; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton @ChannelHandler.Sharable public class ComputedAttributesHandler extends BaseDataHandler { private static final Logger LOGGER = LoggerFactory.getLogger(ComputedAttributesHandler.class); - private final IdentityManager identityManager; - private final AttributesManager attributesManager; + private final CacheManager cacheManager; private final JexlEngine engine; private final boolean includeDeviceAttributes; - public ComputedAttributesHandler( - Config config, IdentityManager identityManager, AttributesManager attributesManager) { - this.identityManager = identityManager; - this.attributesManager = attributesManager; + @Inject + public ComputedAttributesHandler(Config config, CacheManager cacheManager) { + this.cacheManager = cacheManager; engine = new JexlEngine(); engine.setStrict(true); engine.setFunctions(Collections.singletonMap("math", Math.class)); @@ -65,7 +66,7 @@ public class ComputedAttributesHandler extends BaseDataHandler { private MapContext prepareContext(Position position) { MapContext result = new MapContext(); if (includeDeviceAttributes) { - Device device = identityManager.getById(position.getDeviceId()); + Device device = cacheManager.getObject(Device.class, position.getDeviceId()); if (device != null) { for (Object key : device.getAttributes().keySet()) { result.set((String) key, device.getAttributes().get(key)); @@ -104,8 +105,7 @@ public class ComputedAttributesHandler extends BaseDataHandler { @Override protected Position handlePosition(Position position) { - Collection<Attribute> attributes = attributesManager.getItems( - attributesManager.getAllDeviceItems(position.getDeviceId())); + Collection<Attribute> attributes = cacheManager.getDeviceObjects(position.getDeviceId(), Attribute.class); for (Attribute attribute : attributes) { if (attribute.getAttribute() != null) { Object result = null; diff --git a/src/main/java/org/traccar/handler/CopyAttributesHandler.java b/src/main/java/org/traccar/handler/CopyAttributesHandler.java index f386116b0..e5c9bc29a 100644 --- a/src/main/java/org/traccar/handler/CopyAttributesHandler.java +++ b/src/main/java/org/traccar/handler/CopyAttributesHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,27 +18,39 @@ package org.traccar.handler; import io.netty.channel.ChannelHandler; import org.traccar.BaseDataHandler; -import org.traccar.database.IdentityManager; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton @ChannelHandler.Sharable public class CopyAttributesHandler extends BaseDataHandler { - private IdentityManager identityManager; + private final boolean enabled; + private final CacheManager cacheManager; - public CopyAttributesHandler(IdentityManager identityManager) { - this.identityManager = identityManager; + @Inject + public CopyAttributesHandler(Config config, CacheManager cacheManager) { + enabled = config.getBoolean(Keys.PROCESSING_COPY_ATTRIBUTES_ENABLE); + this.cacheManager = cacheManager; } @Override protected Position handlePosition(Position position) { - String attributesString = identityManager.lookupAttributeString( - position.getDeviceId(), "processing.copyAttributes", "", false, true); - Position last = identityManager.getLastPosition(position.getDeviceId()); - if (last != null) { - for (String attribute : attributesString.split("[ ,]")) { - if (last.getAttributes().containsKey(attribute) && !position.getAttributes().containsKey(attribute)) { - position.getAttributes().put(attribute, last.getAttributes().get(attribute)); + if (enabled) { + String attributesString = AttributeUtil.lookup( + cacheManager, Keys.PROCESSING_COPY_ATTRIBUTES, position.getDeviceId()); + Position last = cacheManager.getPosition(position.getDeviceId()); + if (last != null && attributesString != null) { + for (String attribute : attributesString.split("[ ,]")) { + if (last.hasAttribute(attribute) && !position.hasAttribute(attribute)) { + position.getAttributes().put(attribute, last.getAttributes().get(attribute)); + } } } } diff --git a/src/main/java/org/traccar/handler/DefaultDataHandler.java b/src/main/java/org/traccar/handler/DefaultDataHandler.java index 9d8ea044d..89255a5fe 100644 --- a/src/main/java/org/traccar/handler/DefaultDataHandler.java +++ b/src/main/java/org/traccar/handler/DefaultDataHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,25 +19,32 @@ import io.netty.channel.ChannelHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.BaseDataHandler; -import org.traccar.database.DataManager; import org.traccar.model.Position; +import org.traccar.storage.Storage; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Request; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton @ChannelHandler.Sharable public class DefaultDataHandler extends BaseDataHandler { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataHandler.class); - private final DataManager dataManager; + private final Storage storage; - public DefaultDataHandler(DataManager dataManager) { - this.dataManager = dataManager; + @Inject + public DefaultDataHandler(Storage storage) { + this.storage = storage; } @Override protected Position handlePosition(Position position) { try { - dataManager.addObject(position); + position.setId(storage.addObject(position, new Request(new Columns.Exclude("id")))); } catch (Exception error) { LOGGER.warn("Failed to store position", error); } diff --git a/src/main/java/org/traccar/handler/DistanceHandler.java b/src/main/java/org/traccar/handler/DistanceHandler.java index 1e7e444f6..30dc9ff2b 100644 --- a/src/main/java/org/traccar/handler/DistanceHandler.java +++ b/src/main/java/org/traccar/handler/DistanceHandler.java @@ -1,6 +1,6 @@ /* + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2015 Amila Silva - * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,24 +20,28 @@ import io.netty.channel.ChannelHandler; import org.traccar.BaseDataHandler; import org.traccar.config.Config; import org.traccar.config.Keys; -import org.traccar.database.IdentityManager; import org.traccar.helper.DistanceCalculator; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; +import javax.inject.Inject; +import javax.inject.Singleton; import java.math.BigDecimal; import java.math.RoundingMode; +@Singleton @ChannelHandler.Sharable public class DistanceHandler extends BaseDataHandler { - private final IdentityManager identityManager; + private final CacheManager cacheManager; private final boolean filter; private final int coordinatesMinError; private final int coordinatesMaxError; - public DistanceHandler(Config config, IdentityManager identityManager) { - this.identityManager = identityManager; + @Inject + public DistanceHandler(Config config, CacheManager cacheManager) { + this.cacheManager = cacheManager; this.filter = config.getBoolean(Keys.COORDINATES_FILTER); this.coordinatesMinError = config.getInteger(Keys.COORDINATES_MIN_ERROR); this.coordinatesMaxError = config.getInteger(Keys.COORDINATES_MAX_ERROR); @@ -47,15 +51,15 @@ public class DistanceHandler extends BaseDataHandler { protected Position handlePosition(Position position) { double distance = 0.0; - if (position.getAttributes().containsKey(Position.KEY_DISTANCE)) { + if (position.hasAttribute(Position.KEY_DISTANCE)) { distance = position.getDouble(Position.KEY_DISTANCE); } double totalDistance = 0.0; - Position last = identityManager != null ? identityManager.getLastPosition(position.getDeviceId()) : null; + Position last = cacheManager.getPosition(position.getDeviceId()); if (last != null) { totalDistance = last.getDouble(Position.KEY_TOTAL_DISTANCE); - if (!position.getAttributes().containsKey(Position.KEY_DISTANCE)) { + if (!position.hasAttribute(Position.KEY_DISTANCE)) { distance = DistanceCalculator.distance( position.getLatitude(), position.getLongitude(), last.getLatitude(), last.getLongitude()); diff --git a/src/main/java/org/traccar/handler/EngineHoursHandler.java b/src/main/java/org/traccar/handler/EngineHoursHandler.java index 92da84e6b..c10fe9064 100644 --- a/src/main/java/org/traccar/handler/EngineHoursHandler.java +++ b/src/main/java/org/traccar/handler/EngineHoursHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,22 +18,27 @@ package org.traccar.handler; import io.netty.channel.ChannelHandler; import org.traccar.BaseDataHandler; -import org.traccar.database.IdentityManager; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton @ChannelHandler.Sharable public class EngineHoursHandler extends BaseDataHandler { - private final IdentityManager identityManager; + private final CacheManager cacheManager; - public EngineHoursHandler(IdentityManager identityManager) { - this.identityManager = identityManager; + @Inject + public EngineHoursHandler(CacheManager cacheManager) { + this.cacheManager = cacheManager; } @Override protected Position handlePosition(Position position) { - if (!position.getAttributes().containsKey(Position.KEY_HOURS)) { - Position last = identityManager.getLastPosition(position.getDeviceId()); + if (!position.hasAttribute(Position.KEY_HOURS)) { + Position last = cacheManager.getPosition(position.getDeviceId()); if (last != null) { long hours = last.getLong(Position.KEY_HOURS); if (last.getBoolean(Position.KEY_IGNITION) && position.getBoolean(Position.KEY_IGNITION)) { diff --git a/src/main/java/org/traccar/handler/FilterHandler.java b/src/main/java/org/traccar/handler/FilterHandler.java index 7cd9153c1..994276bb6 100644 --- a/src/main/java/org/traccar/handler/FilterHandler.java +++ b/src/main/java/org/traccar/handler/FilterHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2014 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,49 +19,83 @@ import io.netty.channel.ChannelHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.BaseDataHandler; -import org.traccar.Context; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.helper.UnitsConverter; +import org.traccar.helper.model.AttributeUtil; +import org.traccar.model.Device; import org.traccar.model.Position; - +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Order; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Date; + +@Singleton @ChannelHandler.Sharable public class FilterHandler extends BaseDataHandler { private static final Logger LOGGER = LoggerFactory.getLogger(FilterHandler.class); - private boolean filterInvalid; - private boolean filterZero; - private boolean filterDuplicate; - private long filterFuture; - private boolean filterApproximate; - private int filterAccuracy; - private boolean filterStatic; - private int filterDistance; - private int filterMaxSpeed; - private long filterMinPeriod; - private long skipLimit; - private boolean skipAttributes; - - public FilterHandler(Config config) { + private final boolean enabled; + private final boolean filterInvalid; + private final boolean filterZero; + private final boolean filterDuplicate; + private final long filterFuture; + private final long filterPast; + private final boolean filterApproximate; + private final int filterAccuracy; + private final boolean filterStatic; + private final int filterDistance; + private final int filterMaxSpeed; + private final long filterMinPeriod; + private final boolean filterRelative; + private final long skipLimit; + private final boolean skipAttributes; + + private final CacheManager cacheManager; + private final Storage storage; + + @Inject + public FilterHandler(Config config, CacheManager cacheManager, Storage storage) { + enabled = config.getBoolean(Keys.FILTER_ENABLE); filterInvalid = config.getBoolean(Keys.FILTER_INVALID); filterZero = config.getBoolean(Keys.FILTER_ZERO); filterDuplicate = config.getBoolean(Keys.FILTER_DUPLICATE); filterFuture = config.getLong(Keys.FILTER_FUTURE) * 1000; + filterPast = config.getLong(Keys.FILTER_PAST) * 1000; filterAccuracy = config.getInteger(Keys.FILTER_ACCURACY); filterApproximate = config.getBoolean(Keys.FILTER_APPROXIMATE); filterStatic = config.getBoolean(Keys.FILTER_STATIC); filterDistance = config.getInteger(Keys.FILTER_DISTANCE); filterMaxSpeed = config.getInteger(Keys.FILTER_MAX_SPEED); - filterMinPeriod = config.getInteger(Keys.FILTER_MIN_PERIOD) * 1000; + filterMinPeriod = config.getInteger(Keys.FILTER_MIN_PERIOD) * 1000L; + filterRelative = config.getBoolean(Keys.FILTER_RELATIVE); skipLimit = config.getLong(Keys.FILTER_SKIP_LIMIT) * 1000; skipAttributes = config.getBoolean(Keys.FILTER_SKIP_ATTRIBUTES_ENABLE); + this.cacheManager = cacheManager; + this.storage = storage; + } + + private Position getPrecedingPosition(long deviceId, Date date) throws StorageException { + return storage.getObject(Position.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("deviceId", deviceId), + new Condition.Compare("fixTime", "<=", "time", date)), + new Order("fixTime", true, 1))); } private boolean filterInvalid(Position position) { return filterInvalid && (!position.getValid() - || position.getLatitude() > 90 || position.getLongitude() > 180 - || position.getLatitude() < -90 || position.getLongitude() < -180); + || position.getLatitude() > 90 || position.getLongitude() > 180 + || position.getLatitude() < -90 || position.getLongitude() < -180); } private boolean filterZero(Position position) { @@ -71,7 +105,7 @@ public class FilterHandler extends BaseDataHandler { private boolean filterDuplicate(Position position, Position last) { if (filterDuplicate && last != null && position.getFixTime().equals(last.getFixTime())) { for (String key : position.getAttributes().keySet()) { - if (!last.getAttributes().containsKey(key)) { + if (!last.hasAttribute(key)) { return false; } } @@ -84,6 +118,10 @@ public class FilterHandler extends BaseDataHandler { return filterFuture != 0 && position.getFixTime().getTime() > System.currentTimeMillis() + filterFuture; } + private boolean filterPast(Position position) { + return filterPast != 0 && position.getFixTime().getTime() < System.currentTimeMillis() - filterPast; + } + private boolean filterAccuracy(Position position) { return filterAccuracy != 0 && position.getAccuracy() > filterAccuracy; } @@ -129,10 +167,9 @@ public class FilterHandler extends BaseDataHandler { private boolean skipAttributes(Position position) { if (skipAttributes) { - String attributesString = Context.getIdentityManager().lookupAttributeString( - position.getDeviceId(), "filter.skipAttributes", "", false, true); - for (String attribute : attributesString.split("[ ,]")) { - if (position.getAttributes().containsKey(attribute)) { + String string = AttributeUtil.lookup(cacheManager, Keys.FILTER_SKIP_ATTRIBUTES, position.getDeviceId()); + for (String attribute : string.split("[ ,]")) { + if (position.hasAttribute(attribute)) { return true; } } @@ -144,51 +181,61 @@ public class FilterHandler extends BaseDataHandler { StringBuilder filterType = new StringBuilder(); - Position last = null; - if (Context.getIdentityManager() != null) { - last = Context.getIdentityManager().getLastPosition(position.getDeviceId()); - } - + // filter out invalid data if (filterInvalid(position)) { filterType.append("Invalid "); } if (filterZero(position)) { filterType.append("Zero "); } - if (filterDuplicate(position, last) && !skipLimit(position, last) && !skipAttributes(position)) { - filterType.append("Duplicate "); - } if (filterFuture(position)) { filterType.append("Future "); } + if (filterPast(position)) { + filterType.append("Past "); + } if (filterAccuracy(position)) { filterType.append("Accuracy "); } if (filterApproximate(position)) { filterType.append("Approximate "); } - if (filterStatic(position) && !skipLimit(position, last) && !skipAttributes(position)) { - filterType.append("Static "); - } - if (filterDistance(position, last) && !skipLimit(position, last) && !skipAttributes(position)) { - filterType.append("Distance "); - } - if (filterMaxSpeed(position, last)) { - filterType.append("MaxSpeed "); - } - if (filterMinPeriod(position, last)) { - filterType.append("MinPeriod "); + + // filter out excessive data + long deviceId = position.getDeviceId(); + if (filterDuplicate || filterStatic || filterDistance > 0 || filterMaxSpeed > 0 || filterMinPeriod > 0) { + Position preceding = null; + if (filterRelative) { + try { + Date newFixTime = position.getFixTime(); + preceding = getPrecedingPosition(deviceId, newFixTime); + } catch (StorageException e) { + LOGGER.warn("Error retrieving preceding position; fallbacking to last received position.", e); + preceding = cacheManager.getPosition(deviceId); + } + } else { + preceding = cacheManager.getPosition(deviceId); + } + if (filterDuplicate(position, preceding) && !skipLimit(position, preceding) && !skipAttributes(position)) { + filterType.append("Duplicate "); + } + if (filterStatic(position) && !skipLimit(position, preceding) && !skipAttributes(position)) { + filterType.append("Static "); + } + if (filterDistance(position, preceding) && !skipLimit(position, preceding) && !skipAttributes(position)) { + filterType.append("Distance "); + } + if (filterMaxSpeed(position, preceding)) { + filterType.append("MaxSpeed "); + } + if (filterMinPeriod(position, preceding)) { + filterType.append("MinPeriod "); + } } if (filterType.length() > 0) { - - StringBuilder message = new StringBuilder(); - message.append("Position filtered by "); - message.append(filterType.toString()); - message.append("filters from device: "); - message.append(Context.getIdentityManager().getById(position.getDeviceId()).getUniqueId()); - - LOGGER.info(message.toString()); + String uniqueId = cacheManager.getObject(Device.class, deviceId).getUniqueId(); + LOGGER.info("Position filtered by {}filters from device: {}", filterType, uniqueId); return true; } @@ -197,7 +244,7 @@ public class FilterHandler extends BaseDataHandler { @Override protected Position handlePosition(Position position) { - if (filter(position)) { + if (enabled && filter(position)) { return null; } return position; diff --git a/src/main/java/org/traccar/handler/GeocoderHandler.java b/src/main/java/org/traccar/handler/GeocoderHandler.java index 614cf97d6..e4f240a90 100644 --- a/src/main/java/org/traccar/handler/GeocoderHandler.java +++ b/src/main/java/org/traccar/handler/GeocoderHandler.java @@ -20,12 +20,11 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; import org.traccar.config.Config; import org.traccar.config.Keys; -import org.traccar.database.IdentityManager; import org.traccar.geocoder.Geocoder; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; @ChannelHandler.Sharable public class GeocoderHandler extends ChannelInboundHandlerAdapter { @@ -33,18 +32,17 @@ public class GeocoderHandler extends ChannelInboundHandlerAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(GeocoderHandler.class); private final Geocoder geocoder; - private final IdentityManager identityManager; + private final CacheManager cacheManager; private final boolean ignorePositions; private final boolean processInvalidPositions; - private final int geocoderReuseDistance; + private final int reuseDistance; - public GeocoderHandler( - Config config, Geocoder geocoder, IdentityManager identityManager) { + public GeocoderHandler(Config config, Geocoder geocoder, CacheManager cacheManager) { this.geocoder = geocoder; - this.identityManager = identityManager; - ignorePositions = Context.getConfig().getBoolean(Keys.GEOCODER_IGNORE_POSITIONS); + this.cacheManager = cacheManager; + ignorePositions = config.getBoolean(Keys.GEOCODER_IGNORE_POSITIONS); processInvalidPositions = config.getBoolean(Keys.GEOCODER_PROCESS_INVALID_POSITIONS); - geocoderReuseDistance = config.getInteger(Keys.GEOCODER_REUSE_DISTANCE, 0); + reuseDistance = config.getInteger(Keys.GEOCODER_REUSE_DISTANCE, 0); } @Override @@ -52,10 +50,10 @@ public class GeocoderHandler extends ChannelInboundHandlerAdapter { if (message instanceof Position && !ignorePositions) { final Position position = (Position) message; if (processInvalidPositions || position.getValid()) { - if (geocoderReuseDistance != 0) { - Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); + if (reuseDistance != 0) { + Position lastPosition = cacheManager.getPosition(position.getDeviceId()); if (lastPosition != null && lastPosition.getAddress() != null - && position.getDouble(Position.KEY_DISTANCE) <= geocoderReuseDistance) { + && position.getDouble(Position.KEY_DISTANCE) <= reuseDistance) { position.setAddress(lastPosition.getAddress()); ctx.fireChannelRead(position); return; diff --git a/src/main/java/org/traccar/handler/GeolocationHandler.java b/src/main/java/org/traccar/handler/GeolocationHandler.java index 0e78322c8..e7389f22d 100644 --- a/src/main/java/org/traccar/handler/GeolocationHandler.java +++ b/src/main/java/org/traccar/handler/GeolocationHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.traccar.config.Keys; import org.traccar.database.StatisticsManager; import org.traccar.geolocation.GeolocationProvider; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; @ChannelHandler.Sharable public class GeolocationHandler extends ChannelInboundHandlerAdapter { @@ -32,14 +33,19 @@ public class GeolocationHandler extends ChannelInboundHandlerAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(GeolocationHandler.class); private final GeolocationProvider geolocationProvider; + private final CacheManager cacheManager; private final StatisticsManager statisticsManager; private final boolean processInvalidPositions; + private final boolean reuse; public GeolocationHandler( - Config config, GeolocationProvider geolocationProvider, StatisticsManager statisticsManager) { + Config config, GeolocationProvider geolocationProvider, CacheManager cacheManager, + StatisticsManager statisticsManager) { this.geolocationProvider = geolocationProvider; + this.cacheManager = cacheManager; this.statisticsManager = statisticsManager; - this.processInvalidPositions = config.getBoolean(Keys.GEOLOCATION_PROCESS_INVALID_POSITIONS); + processInvalidPositions = config.getBoolean(Keys.GEOLOCATION_PROCESS_INVALID_POSITIONS); + reuse = config.getBoolean(Keys.GEOLOCATION_REUSE); } @Override @@ -48,6 +54,17 @@ public class GeolocationHandler extends ChannelInboundHandlerAdapter { final Position position = (Position) message; if ((position.getOutdated() || processInvalidPositions && !position.getValid()) && position.getNetwork() != null) { + if (reuse) { + Position lastPosition = cacheManager.getPosition(position.getDeviceId()); + if (lastPosition != null && position.getNetwork().equals(lastPosition.getNetwork())) { + updatePosition( + position, lastPosition.getLatitude(), lastPosition.getLongitude(), + lastPosition.getAccuracy()); + ctx.fireChannelRead(position); + return; + } + } + if (statisticsManager != null) { statisticsManager.registerGeolocationRequest(); } @@ -56,15 +73,7 @@ public class GeolocationHandler extends ChannelInboundHandlerAdapter { new GeolocationProvider.LocationProviderCallback() { @Override public void onSuccess(double latitude, double longitude, double accuracy) { - position.set(Position.KEY_APPROXIMATE, true); - position.setValid(true); - position.setFixTime(position.getDeviceTime()); - position.setLatitude(latitude); - position.setLongitude(longitude); - position.setAccuracy(accuracy); - position.setAltitude(0); - position.setSpeed(0); - position.setCourse(0); + updatePosition(position, latitude, longitude, accuracy); ctx.fireChannelRead(position); } @@ -82,4 +91,16 @@ public class GeolocationHandler extends ChannelInboundHandlerAdapter { } } + private void updatePosition(Position position, double latitude, double longitude, double accuracy) { + position.set(Position.KEY_APPROXIMATE, true); + position.setValid(true); + position.setFixTime(position.getDeviceTime()); + position.setLatitude(latitude); + position.setLongitude(longitude); + position.setAccuracy(accuracy); + position.setAltitude(0); + position.setSpeed(0); + position.setCourse(0); + } + } diff --git a/src/main/java/org/traccar/handler/HemisphereHandler.java b/src/main/java/org/traccar/handler/HemisphereHandler.java index aff3d8a64..ccbde9fe5 100644 --- a/src/main/java/org/traccar/handler/HemisphereHandler.java +++ b/src/main/java/org/traccar/handler/HemisphereHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +21,17 @@ import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.model.Position; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton @ChannelHandler.Sharable public class HemisphereHandler extends BaseDataHandler { private int latitudeFactor; private int longitudeFactor; + @Inject public HemisphereHandler(Config config) { String latitudeHemisphere = config.getString(Keys.LOCATION_LATITUDE_HEMISPHERE); if (latitudeHemisphere != null) { @@ -36,7 +41,7 @@ public class HemisphereHandler extends BaseDataHandler { latitudeFactor = -1; } } - String longitudeHemisphere = config.getString(Keys.LOCATION_LATITUDE_HEMISPHERE); + String longitudeHemisphere = config.getString(Keys.LOCATION_LONGITUDE_HEMISPHERE); if (longitudeHemisphere != null) { if (longitudeHemisphere.equalsIgnoreCase("E")) { longitudeFactor = 1; diff --git a/src/main/java/org/traccar/handler/MotionHandler.java b/src/main/java/org/traccar/handler/MotionHandler.java index e8051dd75..10312f9b3 100644 --- a/src/main/java/org/traccar/handler/MotionHandler.java +++ b/src/main/java/org/traccar/handler/MotionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,19 +19,25 @@ package org.traccar.handler; import io.netty.channel.ChannelHandler; import org.traccar.BaseDataHandler; import org.traccar.model.Position; +import org.traccar.reports.common.TripsConfig; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton @ChannelHandler.Sharable public class MotionHandler extends BaseDataHandler { - private double speedThreshold; + private final double speedThreshold; - public MotionHandler(double speedThreshold) { - this.speedThreshold = speedThreshold; + @Inject + public MotionHandler(TripsConfig tripsConfig) { + speedThreshold = tripsConfig.getSpeedThreshold(); } @Override protected Position handlePosition(Position position) { - if (!position.getAttributes().containsKey(Position.KEY_MOTION)) { + if (!position.hasAttribute(Position.KEY_MOTION)) { position.set(Position.KEY_MOTION, position.getSpeed() > speedThreshold); } return position; diff --git a/src/main/java/org/traccar/handler/OpenChannelHandler.java b/src/main/java/org/traccar/handler/OpenChannelHandler.java index d09d617ab..e416f35ae 100644 --- a/src/main/java/org/traccar/handler/OpenChannelHandler.java +++ b/src/main/java/org/traccar/handler/OpenChannelHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,26 +17,26 @@ package org.traccar.handler; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; -import org.traccar.TrackerServer; +import org.traccar.TrackerConnector; public class OpenChannelHandler extends ChannelDuplexHandler { - private final TrackerServer server; + private final TrackerConnector connector; - public OpenChannelHandler(TrackerServer server) { - this.server = server; + public OpenChannelHandler(TrackerConnector connector) { + this.connector = connector; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); - server.getChannelGroup().add(ctx.channel()); + connector.getChannelGroup().add(ctx.channel()); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); - server.getChannelGroup().remove(ctx.channel()); + connector.getChannelGroup().remove(ctx.channel()); } } diff --git a/src/main/java/org/traccar/handler/RemoteAddressHandler.java b/src/main/java/org/traccar/handler/RemoteAddressHandler.java index c09b8c39a..e18d34ef2 100644 --- a/src/main/java/org/traccar/handler/RemoteAddressHandler.java +++ b/src/main/java/org/traccar/handler/RemoteAddressHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,22 +18,36 @@ package org.traccar.handler; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import org.traccar.config.Config; +import org.traccar.config.Keys; import org.traccar.model.Position; +import javax.inject.Inject; +import javax.inject.Singleton; import java.net.InetSocketAddress; +@Singleton @ChannelHandler.Sharable public class RemoteAddressHandler extends ChannelInboundHandlerAdapter { + private final boolean enabled; + + @Inject + public RemoteAddressHandler(Config config) { + enabled = config.getBoolean(Keys.PROCESSING_REMOTE_ADDRESS_ENABLE); + } + @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { - InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress(); - String hostAddress = remoteAddress != null ? remoteAddress.getAddress().getHostAddress() : null; + if (enabled) { + InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress(); + String hostAddress = remoteAddress != null ? remoteAddress.getAddress().getHostAddress() : null; - if (msg instanceof Position) { - Position position = (Position) msg; - position.set(Position.KEY_IP, hostAddress); + if (msg instanceof Position) { + Position position = (Position) msg; + position.set(Position.KEY_IP, hostAddress); + } } ctx.fireChannelRead(msg); diff --git a/src/main/java/org/traccar/handler/SpeedLimitHandler.java b/src/main/java/org/traccar/handler/SpeedLimitHandler.java index 65f2c9cfe..0c6025999 100644 --- a/src/main/java/org/traccar/handler/SpeedLimitHandler.java +++ b/src/main/java/org/traccar/handler/SpeedLimitHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,10 @@ import org.slf4j.LoggerFactory; import org.traccar.model.Position; import org.traccar.speedlimit.SpeedLimitProvider; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton @ChannelHandler.Sharable public class SpeedLimitHandler extends ChannelInboundHandlerAdapter { @@ -30,6 +34,7 @@ public class SpeedLimitHandler extends ChannelInboundHandlerAdapter { private final SpeedLimitProvider speedLimitProvider; + @Inject public SpeedLimitHandler(SpeedLimitProvider speedLimitProvider) { this.speedLimitProvider = speedLimitProvider; } diff --git a/src/main/java/org/traccar/handler/StandardLoggingHandler.java b/src/main/java/org/traccar/handler/StandardLoggingHandler.java index 13c5f8281..84492e2a5 100644 --- a/src/main/java/org/traccar/handler/StandardLoggingHandler.java +++ b/src/main/java/org/traccar/handler/StandardLoggingHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import io.netty.channel.ChannelPromise; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.NetworkMessage; +import org.traccar.helper.NetworkUtil; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -63,7 +64,7 @@ public class StandardLoggingHandler extends ChannelDuplexHandler { public void log(ChannelHandlerContext ctx, boolean downstream, SocketAddress remoteAddress, ByteBuf buf) { StringBuilder message = new StringBuilder(); - message.append("[").append(ctx.channel().id().asShortText()).append(": "); + message.append("[").append(NetworkUtil.session(ctx.channel())).append(": "); message.append(protocol); if (downstream) { message.append(" > "); @@ -76,9 +77,8 @@ public class StandardLoggingHandler extends ChannelDuplexHandler { } else { message.append("unknown"); } - message.append("]"); + message.append("] "); - message.append(" HEX: "); message.append(ByteBufUtil.hexDump(buf)); LOGGER.info(message.toString()); diff --git a/src/main/java/org/traccar/handler/TimeHandler.java b/src/main/java/org/traccar/handler/TimeHandler.java index 822c22a0a..c98b0bd4c 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,24 +19,33 @@ import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.model.Position; +import javax.inject.Inject; +import javax.inject.Singleton; import java.util.Arrays; import java.util.HashSet; import java.util.Set; +@Singleton @ChannelHandler.Sharable public class TimeHandler extends ChannelInboundHandlerAdapter { + private final boolean enabled; private final boolean useServerTime; private final Set<String> protocols; + @Inject public TimeHandler(Config config) { - useServerTime = config.getString(Keys.TIME_OVERRIDE).equalsIgnoreCase("serverTime"); - String protocolList = Context.getConfig().getString(Keys.TIME_PROTOCOLS); + enabled = config.hasKey(Keys.TIME_OVERRIDE); + if (enabled) { + useServerTime = config.getString(Keys.TIME_OVERRIDE).equalsIgnoreCase("serverTime"); + } else { + useServerTime = false; + } + String protocolList = config.getString(Keys.TIME_PROTOCOLS); if (protocolList != null) { protocols = new HashSet<>(Arrays.asList(protocolList.split("[, ]"))); } else { @@ -47,7 +56,7 @@ public class TimeHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { - if (msg instanceof Position && (protocols == null + if (enabled && msg instanceof Position && (protocols == null || protocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName()))) { Position position = (Position) msg; diff --git a/src/main/java/org/traccar/handler/events/AlertEventHandler.java b/src/main/java/org/traccar/handler/events/AlertEventHandler.java index 05dbc516e..9f77df989 100644 --- a/src/main/java/org/traccar/handler/events/AlertEventHandler.java +++ b/src/main/java/org/traccar/handler/events/AlertEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,18 +21,23 @@ import java.util.Map; import io.netty.channel.ChannelHandler; import org.traccar.config.Config; import org.traccar.config.Keys; -import org.traccar.database.IdentityManager; import org.traccar.model.Event; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton @ChannelHandler.Sharable public class AlertEventHandler extends BaseEventHandler { - private final IdentityManager identityManager; + private final CacheManager cacheManager; private final boolean ignoreDuplicateAlerts; - public AlertEventHandler(Config config, IdentityManager identityManager) { - this.identityManager = identityManager; + @Inject + public AlertEventHandler(Config config, CacheManager cacheManager) { + this.cacheManager = cacheManager; ignoreDuplicateAlerts = config.getBoolean(Keys.EVENT_IGNORE_DUPLICATE_ALERTS); } @@ -42,7 +47,7 @@ public class AlertEventHandler extends BaseEventHandler { if (alarm != null) { boolean ignoreAlert = false; if (ignoreDuplicateAlerts) { - Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); + Position lastPosition = cacheManager.getPosition(position.getDeviceId()); if (lastPosition != null && alarm.equals(lastPosition.getAttributes().get(Position.KEY_ALARM))) { ignoreAlert = true; } diff --git a/src/main/java/org/traccar/handler/events/BaseEventHandler.java b/src/main/java/org/traccar/handler/events/BaseEventHandler.java index 41f677f6c..271aaa35d 100644 --- a/src/main/java/org/traccar/handler/events/BaseEventHandler.java +++ b/src/main/java/org/traccar/handler/events/BaseEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,17 +18,26 @@ package org.traccar.handler.events; import java.util.Map; import org.traccar.BaseDataHandler; -import org.traccar.Context; +import org.traccar.database.NotificationManager; import org.traccar.model.Event; import org.traccar.model.Position; +import javax.inject.Inject; + public abstract class BaseEventHandler extends BaseDataHandler { + private NotificationManager notificationManager; + + @Inject + public void setNotificationManager(NotificationManager notificationManager) { + this.notificationManager = notificationManager; + } + @Override protected Position handlePosition(Position position) { Map<Event, Position> events = analyzePosition(position); - if (events != null && Context.getNotificationManager() != null) { - Context.getNotificationManager().updateEvents(events); + if (events != null && !events.isEmpty()) { + notificationManager.updateEvents(events); } return position; } diff --git a/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java b/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java index 767cef3f6..51bbd82d6 100644 --- a/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java +++ b/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,32 +18,36 @@ package org.traccar.handler.events; import io.netty.channel.ChannelHandler; import org.traccar.config.Config; import org.traccar.config.Keys; -import org.traccar.database.IdentityManager; import org.traccar.helper.UnitsConverter; import org.traccar.model.Event; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; +import javax.inject.Inject; +import javax.inject.Singleton; import java.util.Collections; import java.util.Map; +@Singleton @ChannelHandler.Sharable public class BehaviorEventHandler extends BaseEventHandler { private final double accelerationThreshold; private final double brakingThreshold; - private final IdentityManager identityManager; + private final CacheManager cacheManager; - public BehaviorEventHandler(Config config, IdentityManager identityManager) { + @Inject + public BehaviorEventHandler(Config config, CacheManager cacheManager) { accelerationThreshold = config.getDouble(Keys.EVENT_BEHAVIOR_ACCELERATION_THRESHOLD); brakingThreshold = config.getDouble(Keys.EVENT_BEHAVIOR_BRAKING_THRESHOLD); - this.identityManager = identityManager; + this.cacheManager = cacheManager; } @Override protected Map<Event, Position> analyzePosition(Position position) { - Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); + Position lastPosition = cacheManager.getPosition(position.getDeviceId()); if (lastPosition != null && position.getFixTime().equals(lastPosition.getFixTime())) { double acceleration = UnitsConverter.mpsFromKnots(position.getSpeed() - lastPosition.getSpeed()) * 1000 / (position.getFixTime().getTime() - lastPosition.getFixTime().getTime()); diff --git a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java index 9b7ff554e..772176e9c 100644 --- a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java +++ b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,17 @@ import io.netty.channel.ChannelHandler; import org.traccar.model.Event; import org.traccar.model.Position; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton @ChannelHandler.Sharable public class CommandResultEventHandler extends BaseEventHandler { + @Inject + public CommandResultEventHandler() { + } + @Override protected Map<Event, Position> analyzePosition(Position position) { Object commandResult = position.getAttributes().get(Position.KEY_RESULT); diff --git a/src/main/java/org/traccar/handler/events/DriverEventHandler.java b/src/main/java/org/traccar/handler/events/DriverEventHandler.java index 6fdf4246b..51fdc0307 100644 --- a/src/main/java/org/traccar/handler/events/DriverEventHandler.java +++ b/src/main/java/org/traccar/handler/events/DriverEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,32 +16,37 @@ */ package org.traccar.handler.events; -import java.util.Collections; -import java.util.Map; - import io.netty.channel.ChannelHandler; -import org.traccar.database.IdentityManager; +import org.traccar.helper.model.PositionUtil; import org.traccar.model.Event; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Collections; +import java.util.Map; +@Singleton @ChannelHandler.Sharable public class DriverEventHandler extends BaseEventHandler { - private final IdentityManager identityManager; + private final CacheManager cacheManager; - public DriverEventHandler(IdentityManager identityManager) { - this.identityManager = identityManager; + @Inject + public DriverEventHandler(CacheManager cacheManager) { + this.cacheManager = cacheManager; } @Override protected Map<Event, Position> analyzePosition(Position position) { - if (!identityManager.isLatestPosition(position)) { + if (!PositionUtil.isLatest(cacheManager, position)) { return null; } String driverUniqueId = position.getString(Position.KEY_DRIVER_UNIQUE_ID); if (driverUniqueId != null) { String oldDriverUniqueId = null; - Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); + Position lastPosition = cacheManager.getPosition(position.getDeviceId()); if (lastPosition != null) { oldDriverUniqueId = lastPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); } diff --git a/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java b/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java deleted file mode 100644 index 343a17311..000000000 --- a/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2017 - 2019 Anton Tananaev (anton@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.events; - -import io.netty.channel.ChannelHandler; -import org.traccar.database.IdentityManager; -import org.traccar.model.Device; -import org.traccar.model.Event; -import org.traccar.model.Position; - -import java.util.Collections; -import java.util.Map; - -@ChannelHandler.Sharable -public class FuelDropEventHandler extends BaseEventHandler { - - public static final String ATTRIBUTE_FUEL_DROP_THRESHOLD = "fuelDropThreshold"; - - private final IdentityManager identityManager; - - public FuelDropEventHandler(IdentityManager identityManager) { - this.identityManager = identityManager; - } - - @Override - protected Map<Event, Position> analyzePosition(Position position) { - - Device device = identityManager.getById(position.getDeviceId()); - if (device == null) { - return null; - } - if (!identityManager.isLatestPosition(position)) { - return null; - } - - double fuelDropThreshold = identityManager - .lookupAttributeDouble(device.getId(), ATTRIBUTE_FUEL_DROP_THRESHOLD, 0, true, false); - - if (fuelDropThreshold > 0) { - Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); - if (position.getAttributes().containsKey(Position.KEY_FUEL_LEVEL) - && lastPosition != null && lastPosition.getAttributes().containsKey(Position.KEY_FUEL_LEVEL)) { - - double drop = lastPosition.getDouble(Position.KEY_FUEL_LEVEL) - - position.getDouble(Position.KEY_FUEL_LEVEL); - if (drop >= fuelDropThreshold) { - Event event = new Event(Event.TYPE_DEVICE_FUEL_DROP, position); - event.set(ATTRIBUTE_FUEL_DROP_THRESHOLD, fuelDropThreshold); - return Collections.singletonMap(event, position); - } - } - } - - return null; - } - -} diff --git a/src/main/java/org/traccar/handler/events/FuelEventHandler.java b/src/main/java/org/traccar/handler/events/FuelEventHandler.java new file mode 100644 index 000000000..462cc4223 --- /dev/null +++ b/src/main/java/org/traccar/handler/events/FuelEventHandler.java @@ -0,0 +1,79 @@ +/* + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler.events; + +import io.netty.channel.ChannelHandler; +import org.traccar.config.Keys; +import org.traccar.helper.model.AttributeUtil; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Map; + +@Singleton +@ChannelHandler.Sharable +public class FuelEventHandler extends BaseEventHandler { + + private final CacheManager cacheManager; + + @Inject + public FuelEventHandler(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + + @Override + protected Map<Event, Position> analyzePosition(Position position) { + + Device device = cacheManager.getObject(Device.class, position.getDeviceId()); + if (device == null) { + return null; + } + if (!PositionUtil.isLatest(cacheManager, position)) { + return null; + } + + if (position.hasAttribute(Position.KEY_FUEL_LEVEL)) { + Position lastPosition = cacheManager.getPosition(position.getDeviceId()); + if (lastPosition != null && lastPosition.hasAttribute(Position.KEY_FUEL_LEVEL)) { + double before = lastPosition.getDouble(Position.KEY_FUEL_LEVEL); + double after = position.getDouble(Position.KEY_FUEL_LEVEL); + double change = after - before; + + if (change > 0) { + double threshold = AttributeUtil.lookup( + cacheManager, Keys.EVENT_FUEL_INCREASE_THRESHOLD, position.getDeviceId()); + if (change >= threshold) { + return Map.of(new Event(Event.TYPE_DEVICE_FUEL_INCREASE, position), position); + } + } else if (change < 0) { + double threshold = AttributeUtil.lookup( + cacheManager, Keys.EVENT_FUEL_DROP_THRESHOLD, position.getDeviceId()); + if (Math.abs(change) >= threshold) { + return Map.of(new Event(Event.TYPE_DEVICE_FUEL_DROP, position), position); + } + } + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java index dae0c891f..9414f4b31 100644 --- a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java +++ b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,49 +15,59 @@ */ package org.traccar.handler.events; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import io.netty.channel.ChannelHandler; -import org.traccar.database.CalendarManager; -import org.traccar.database.ConnectionManager; -import org.traccar.database.GeofenceManager; -import org.traccar.database.IdentityManager; +import org.traccar.config.Config; +import org.traccar.helper.model.GeofenceUtil; +import org.traccar.helper.model.PositionUtil; import org.traccar.model.Calendar; import org.traccar.model.Device; import org.traccar.model.Event; +import org.traccar.model.Geofence; import org.traccar.model.Position; +import org.traccar.session.ConnectionManager; +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +@Singleton @ChannelHandler.Sharable public class GeofenceEventHandler extends BaseEventHandler { - private final IdentityManager identityManager; - private final GeofenceManager geofenceManager; - private final CalendarManager calendarManager; + private final Config config; + private final CacheManager cacheManager; private final ConnectionManager connectionManager; + private final Storage storage; + @Inject public GeofenceEventHandler( - IdentityManager identityManager, GeofenceManager geofenceManager, CalendarManager calendarManager, - ConnectionManager connectionManager) { - this.identityManager = identityManager; - this.geofenceManager = geofenceManager; - this.calendarManager = calendarManager; + Config config, CacheManager cacheManager, ConnectionManager connectionManager, Storage storage) { + this.config = config; + this.cacheManager = cacheManager; this.connectionManager = connectionManager; + this.storage = storage; } @Override protected Map<Event, Position> analyzePosition(Position position) { - Device device = identityManager.getById(position.getDeviceId()); + Device device = cacheManager.getObject(Device.class, position.getDeviceId()); if (device == null) { return null; } - if (!identityManager.isLatestPosition(position) || !position.getValid()) { + if (!PositionUtil.isLatest(cacheManager, position) || !position.getValid()) { return null; } - List<Long> currentGeofences = geofenceManager.getCurrentDeviceGeofences(position); + List<Long> currentGeofences = GeofenceUtil.getCurrentGeofences(config, cacheManager, position); List<Long> oldGeofences = new ArrayList<>(); if (device.getGeofenceIds() != null) { oldGeofences.addAll(device.getGeofenceIds()); @@ -66,15 +76,25 @@ public class GeofenceEventHandler extends BaseEventHandler { newGeofences.removeAll(oldGeofences); oldGeofences.removeAll(currentGeofences); - device.setGeofenceIds(currentGeofences); + if (!oldGeofences.isEmpty() || !newGeofences.isEmpty()) { - connectionManager.updateDevice(device); + device.setGeofenceIds(currentGeofences.isEmpty() ? null : currentGeofences); + + try { + storage.updateObject(device, new Request( + new Columns.Include("geofenceIds"), + new Condition.Equals("id", device.getId()))); + } catch (StorageException e) { + throw new RuntimeException("Update device geofences error", e); + } + + connectionManager.updateDevice(true, device); } Map<Event, Position> events = new HashMap<>(); for (long geofenceId : oldGeofences) { - long calendarId = geofenceManager.getById(geofenceId).getCalendarId(); - Calendar calendar = calendarId != 0 ? calendarManager.getById(calendarId) : null; + long calendarId = cacheManager.getObject(Geofence.class, geofenceId).getCalendarId(); + Calendar calendar = calendarId != 0 ? cacheManager.getObject(Calendar.class, calendarId) : null; if (calendar == null || calendar.checkMoment(position.getFixTime())) { Event event = new Event(Event.TYPE_GEOFENCE_EXIT, position); event.setGeofenceId(geofenceId); @@ -82,8 +102,8 @@ public class GeofenceEventHandler extends BaseEventHandler { } } for (long geofenceId : newGeofences) { - long calendarId = geofenceManager.getById(geofenceId).getCalendarId(); - Calendar calendar = calendarId != 0 ? calendarManager.getById(calendarId) : null; + long calendarId = cacheManager.getObject(Geofence.class, geofenceId).getCalendarId(); + Calendar calendar = calendarId != 0 ? cacheManager.getObject(Calendar.class, calendarId) : null; if (calendar == null || calendar.checkMoment(position.getFixTime())) { Event event = new Event(Event.TYPE_GEOFENCE_ENTER, position); event.setGeofenceId(geofenceId); diff --git a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java index 69df9a46b..b2e9a3325 100644 --- a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java +++ b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,34 +20,40 @@ import java.util.Collections; import java.util.Map; import io.netty.channel.ChannelHandler; -import org.traccar.database.IdentityManager; +import org.traccar.helper.model.PositionUtil; import org.traccar.model.Device; import org.traccar.model.Event; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton @ChannelHandler.Sharable public class IgnitionEventHandler extends BaseEventHandler { - private final IdentityManager identityManager; + private final CacheManager cacheManager; - public IgnitionEventHandler(IdentityManager identityManager) { - this.identityManager = identityManager; + @Inject + public IgnitionEventHandler(CacheManager cacheManager) { + this.cacheManager = cacheManager; } @Override protected Map<Event, Position> analyzePosition(Position position) { - Device device = identityManager.getById(position.getDeviceId()); - if (device == null || !identityManager.isLatestPosition(position)) { + Device device = cacheManager.getObject(Device.class, position.getDeviceId()); + if (device == null || !PositionUtil.isLatest(cacheManager, position)) { return null; } Map<Event, Position> result = null; - if (position.getAttributes().containsKey(Position.KEY_IGNITION)) { + if (position.hasAttribute(Position.KEY_IGNITION)) { boolean ignition = position.getBoolean(Position.KEY_IGNITION); - Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); - if (lastPosition != null && lastPosition.getAttributes().containsKey(Position.KEY_IGNITION)) { + Position lastPosition = cacheManager.getPosition(position.getDeviceId()); + if (lastPosition != null && lastPosition.hasAttribute(Position.KEY_IGNITION)) { boolean oldIgnition = lastPosition.getBoolean(Position.KEY_IGNITION); if (ignition && !oldIgnition) { diff --git a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java index 0f960ad1f..909950acf 100644 --- a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java +++ b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,48 +20,46 @@ import java.util.HashMap; import java.util.Map; import io.netty.channel.ChannelHandler; -import org.traccar.database.IdentityManager; -import org.traccar.database.MaintenancesManager; import org.traccar.model.Event; import org.traccar.model.Maintenance; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton @ChannelHandler.Sharable public class MaintenanceEventHandler extends BaseEventHandler { - private final IdentityManager identityManager; - private final MaintenancesManager maintenancesManager; + private final CacheManager cacheManager; - public MaintenanceEventHandler(IdentityManager identityManager, MaintenancesManager maintenancesManager) { - this.identityManager = identityManager; - this.maintenancesManager = maintenancesManager; + @Inject + public MaintenanceEventHandler(CacheManager cacheManager) { + this.cacheManager = cacheManager; } @Override protected Map<Event, Position> analyzePosition(Position position) { - if (identityManager.getById(position.getDeviceId()) == null - || !identityManager.isLatestPosition(position)) { - return null; - } - - Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); - if (lastPosition == null) { + Position lastPosition = cacheManager.getPosition(position.getDeviceId()); + if (lastPosition == null || position.getFixTime().compareTo(lastPosition.getFixTime()) < 0) { return null; } Map<Event, Position> events = new HashMap<>(); - for (long maintenanceId : maintenancesManager.getAllDeviceItems(position.getDeviceId())) { - Maintenance maintenance = maintenancesManager.getById(maintenanceId); + for (Maintenance maintenance : cacheManager.getDeviceObjects(position.getDeviceId(), Maintenance.class)) { if (maintenance.getPeriod() != 0) { double oldValue = lastPosition.getDouble(maintenance.getType()); double newValue = position.getDouble(maintenance.getType()); - if (oldValue != 0.0 && newValue != 0.0 - && (long) ((oldValue - maintenance.getStart()) / maintenance.getPeriod()) + if (oldValue != 0.0 && newValue != 0.0 && newValue >= maintenance.getStart()) { + if (oldValue < maintenance.getStart() + || (long) ((oldValue - maintenance.getStart()) / maintenance.getPeriod()) < (long) ((newValue - maintenance.getStart()) / maintenance.getPeriod())) { - Event event = new Event(Event.TYPE_MAINTENANCE, position); - event.setMaintenanceId(maintenanceId); - event.set(maintenance.getType(), newValue); - events.put(event, position); + Event event = new Event(Event.TYPE_MAINTENANCE, position); + event.setMaintenanceId(maintenance.getId()); + event.set(maintenance.getType(), newValue); + events.put(event, position); + } } } } diff --git a/src/main/java/org/traccar/handler/events/MediaEventHandler.java b/src/main/java/org/traccar/handler/events/MediaEventHandler.java new file mode 100644 index 000000000..a49e08e8d --- /dev/null +++ b/src/main/java/org/traccar/handler/events/MediaEventHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler.events; + +import io.netty.channel.ChannelHandler; +import org.traccar.model.Event; +import org.traccar.model.Position; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Singleton +@ChannelHandler.Sharable +public class MediaEventHandler extends BaseEventHandler { + + @Inject + public MediaEventHandler() { + } + + @Override + protected Map<Event, Position> analyzePosition(Position position) { + return Stream.of(Position.KEY_IMAGE, Position.KEY_VIDEO, Position.KEY_AUDIO) + .filter(position::hasAttribute) + .map(type -> { + Event event = new Event(Event.TYPE_MEDIA, position); + event.set("media", type); + event.set("file", position.getString(type)); + return event; + }) + .collect(Collectors.toMap(event -> event, event -> position)); + } + +} diff --git a/src/main/java/org/traccar/handler/events/MotionEventHandler.java b/src/main/java/org/traccar/handler/events/MotionEventHandler.java index db276f32b..c406bd935 100644 --- a/src/main/java/org/traccar/handler/events/MotionEventHandler.java +++ b/src/main/java/org/traccar/handler/events/MotionEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,120 +16,72 @@ */ package org.traccar.handler.events; -import java.util.Collections; -import java.util.Map; - import io.netty.channel.ChannelHandler; -import org.traccar.database.DeviceManager; -import org.traccar.database.IdentityManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.helper.model.PositionUtil; import org.traccar.model.Device; -import org.traccar.model.DeviceState; import org.traccar.model.Event; import org.traccar.model.Position; -import org.traccar.reports.ReportUtils; -import org.traccar.reports.model.TripsConfig; +import org.traccar.reports.common.TripsConfig; +import org.traccar.session.cache.CacheManager; +import org.traccar.session.state.MotionProcessor; +import org.traccar.session.state.MotionState; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Collections; +import java.util.Map; +@Singleton @ChannelHandler.Sharable public class MotionEventHandler extends BaseEventHandler { - private final IdentityManager identityManager; - private final DeviceManager deviceManager; + private static final Logger LOGGER = LoggerFactory.getLogger(MotionEventHandler.class); + + private final CacheManager cacheManager; + private final Storage storage; private final TripsConfig tripsConfig; - public MotionEventHandler(IdentityManager identityManager, DeviceManager deviceManager, TripsConfig tripsConfig) { - this.identityManager = identityManager; - this.deviceManager = deviceManager; + @Inject + public MotionEventHandler( + CacheManager cacheManager, Storage storage, TripsConfig tripsConfig) { + this.cacheManager = cacheManager; + this.storage = storage; this.tripsConfig = tripsConfig; } - private Map<Event, Position> newEvent(DeviceState deviceState, boolean newMotion) { - String eventType = newMotion ? Event.TYPE_DEVICE_MOVING : Event.TYPE_DEVICE_STOPPED; - Position position = deviceState.getMotionPosition(); - Event event = new Event(eventType, position); - deviceState.setMotionState(newMotion); - deviceState.setMotionPosition(null); - return Collections.singletonMap(event, position); - } - - public Map<Event, Position> updateMotionState(DeviceState deviceState) { - Map<Event, Position> result = null; - if (deviceState.getMotionState() != null && deviceState.getMotionPosition() != null) { - boolean newMotion = !deviceState.getMotionState(); - Position motionPosition = deviceState.getMotionPosition(); - long currentTime = System.currentTimeMillis(); - long motionTime = motionPosition.getFixTime().getTime() - + (newMotion ? tripsConfig.getMinimalTripDuration() : tripsConfig.getMinimalParkingDuration()); - if (motionTime <= currentTime) { - result = newEvent(deviceState, newMotion); - } - } - return result; - } - - public Map<Event, Position> updateMotionState(DeviceState deviceState, Position position) { - return updateMotionState(deviceState, position, position.getBoolean(Position.KEY_MOTION)); - } - - public Map<Event, Position> updateMotionState(DeviceState deviceState, Position position, boolean newMotion) { - Map<Event, Position> result = null; - Boolean oldMotion = deviceState.getMotionState(); - - long currentTime = position.getFixTime().getTime(); - if (newMotion != oldMotion) { - if (deviceState.getMotionPosition() == null) { - deviceState.setMotionPosition(position); - } - } else { - deviceState.setMotionPosition(null); - } - - Position motionPosition = deviceState.getMotionPosition(); - if (motionPosition != null) { - long motionTime = motionPosition.getFixTime().getTime(); - double distance = ReportUtils.calculateDistance(motionPosition, position, false); - Boolean ignition = null; - if (tripsConfig.getUseIgnition() - && position.getAttributes().containsKey(Position.KEY_IGNITION)) { - ignition = position.getBoolean(Position.KEY_IGNITION); - } - if (newMotion) { - if (motionTime + tripsConfig.getMinimalTripDuration() <= currentTime - || distance >= tripsConfig.getMinimalTripDistance()) { - result = newEvent(deviceState, newMotion); - } - } else { - if (motionTime + tripsConfig.getMinimalParkingDuration() <= currentTime - || ignition != null && !ignition) { - result = newEvent(deviceState, newMotion); - } - } - } - return result; - } - @Override protected Map<Event, Position> analyzePosition(Position position) { long deviceId = position.getDeviceId(); - Device device = identityManager.getById(deviceId); + Device device = cacheManager.getObject(Device.class, deviceId); if (device == null) { return null; } - if (!identityManager.isLatestPosition(position) + if (!PositionUtil.isLatest(cacheManager, position) || !tripsConfig.getProcessInvalidPositions() && !position.getValid()) { return null; } - Map<Event, Position> result = null; - DeviceState deviceState = deviceManager.getDeviceState(deviceId); - - if (deviceState.getMotionState() == null) { - deviceState.setMotionState(position.getBoolean(Position.KEY_MOTION)); - } else { - result = updateMotionState(deviceState, position); + MotionState state = MotionState.fromDevice(device); + MotionProcessor.updateState(state, position, position.getBoolean(Position.KEY_MOTION), tripsConfig); + if (state.isChanged()) { + state.toDevice(device); + try { + storage.updateObject(device, new Request( + new Columns.Include("motionStreak", "motionState", "motionTime", "motionDistance"), + new Condition.Equals("id", device.getId()))); + } catch (StorageException e) { + LOGGER.warn("Update device motion error", e); + } } - deviceManager.setDeviceState(deviceId, deviceState); - return result; + return state.getEvent() != null ? Collections.singletonMap(state.getEvent(), position) : null; } } diff --git a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java index 347ad9005..4d6aa8857 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,111 +16,65 @@ */ package org.traccar.handler.events; -import java.util.Collections; -import java.util.Map; - import io.netty.channel.ChannelHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.traccar.config.Config; import org.traccar.config.Keys; -import org.traccar.database.DeviceManager; -import org.traccar.database.GeofenceManager; +import org.traccar.helper.model.AttributeUtil; +import org.traccar.helper.model.PositionUtil; import org.traccar.model.Device; -import org.traccar.model.DeviceState; import org.traccar.model.Event; import org.traccar.model.Geofence; import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; +import org.traccar.session.state.OverspeedProcessor; +import org.traccar.session.state.OverspeedState; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Collections; +import java.util.Map; +@Singleton @ChannelHandler.Sharable public class OverspeedEventHandler extends BaseEventHandler { - public static final String ATTRIBUTE_SPEED = "speed"; - public static final String ATTRIBUTE_SPEED_LIMIT = "speedLimit"; + private static final Logger LOGGER = LoggerFactory.getLogger(OverspeedEventHandler.class); - private final DeviceManager deviceManager; - private final GeofenceManager geofenceManager; + private final CacheManager cacheManager; + private final Storage storage; - private final boolean notRepeat; private final long minimalDuration; private final boolean preferLowest; - public OverspeedEventHandler(Config config, DeviceManager deviceManager, GeofenceManager geofenceManager) { - this.deviceManager = deviceManager; - this.geofenceManager = geofenceManager; - notRepeat = config.getBoolean(Keys.EVENT_OVERSPEED_NOT_REPEAT); + @Inject + public OverspeedEventHandler( + Config config, CacheManager cacheManager, Storage storage) { + this.cacheManager = cacheManager; + this.storage = storage; minimalDuration = config.getLong(Keys.EVENT_OVERSPEED_MINIMAL_DURATION) * 1000; preferLowest = config.getBoolean(Keys.EVENT_OVERSPEED_PREFER_LOWEST); } - private Map<Event, Position> newEvent(DeviceState deviceState, double speedLimit) { - Position position = deviceState.getOverspeedPosition(); - Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, position); - event.set(ATTRIBUTE_SPEED, deviceState.getOverspeedPosition().getSpeed()); - event.set(ATTRIBUTE_SPEED_LIMIT, speedLimit); - event.setGeofenceId(deviceState.getOverspeedGeofenceId()); - deviceState.setOverspeedState(notRepeat); - deviceState.setOverspeedPosition(null); - deviceState.setOverspeedGeofenceId(0); - return Collections.singletonMap(event, position); - } - - public Map<Event, Position> updateOverspeedState(DeviceState deviceState, double speedLimit) { - Map<Event, Position> result = null; - if (deviceState.getOverspeedState() != null && !deviceState.getOverspeedState() - && deviceState.getOverspeedPosition() != null && speedLimit != 0) { - long currentTime = System.currentTimeMillis(); - Position overspeedPosition = deviceState.getOverspeedPosition(); - long overspeedTime = overspeedPosition.getFixTime().getTime(); - if (overspeedTime + minimalDuration <= currentTime) { - result = newEvent(deviceState, speedLimit); - } - } - return result; - } - - public Map<Event, Position> updateOverspeedState( - DeviceState deviceState, Position position, double speedLimit, long geofenceId) { - Map<Event, Position> result = null; - - Boolean oldOverspeed = deviceState.getOverspeedState(); - - long currentTime = position.getFixTime().getTime(); - boolean newOverspeed = position.getSpeed() > speedLimit; - if (newOverspeed && !oldOverspeed) { - if (deviceState.getOverspeedPosition() == null) { - deviceState.setOverspeedPosition(position); - deviceState.setOverspeedGeofenceId(geofenceId); - } - } else if (oldOverspeed && !newOverspeed) { - deviceState.setOverspeedState(false); - deviceState.setOverspeedPosition(null); - deviceState.setOverspeedGeofenceId(0); - } else { - deviceState.setOverspeedPosition(null); - deviceState.setOverspeedGeofenceId(0); - } - Position overspeedPosition = deviceState.getOverspeedPosition(); - if (overspeedPosition != null) { - long overspeedTime = overspeedPosition.getFixTime().getTime(); - if (newOverspeed && overspeedTime + minimalDuration <= currentTime) { - result = newEvent(deviceState, speedLimit); - } - } - return result; - } - @Override protected Map<Event, Position> analyzePosition(Position position) { long deviceId = position.getDeviceId(); - Device device = deviceManager.getById(deviceId); + Device device = cacheManager.getObject(Device.class, position.getDeviceId()); if (device == null) { return null; } - if (!deviceManager.isLatestPosition(position) || !position.getValid()) { + if (!PositionUtil.isLatest(cacheManager, position) || !position.getValid()) { return null; } - double speedLimit = deviceManager.lookupAttributeDouble(deviceId, ATTRIBUTE_SPEED_LIMIT, 0, true, false); + double speedLimit = AttributeUtil.lookup(cacheManager, Keys.EVENT_OVERSPEED_LIMIT, deviceId); double positionSpeedLimit = position.getDouble(Position.KEY_SPEED_LIMIT); if (positionSpeedLimit > 0) { @@ -130,11 +84,11 @@ public class OverspeedEventHandler extends BaseEventHandler { double geofenceSpeedLimit = 0; long overspeedGeofenceId = 0; - if (geofenceManager != null && device.getGeofenceIds() != null) { + if (device.getGeofenceIds() != null) { for (long geofenceId : device.getGeofenceIds()) { - Geofence geofence = geofenceManager.getById(geofenceId); + Geofence geofence = cacheManager.getObject(Geofence.class, geofenceId); if (geofence != null) { - double currentSpeedLimit = geofence.getDouble(ATTRIBUTE_SPEED_LIMIT); + double currentSpeedLimit = geofence.getDouble(Keys.EVENT_OVERSPEED_LIMIT.getKey()); if (currentSpeedLimit > 0 && geofenceSpeedLimit == 0 || preferLowest && currentSpeedLimit < geofenceSpeedLimit || !preferLowest && currentSpeedLimit > geofenceSpeedLimit) { @@ -152,18 +106,19 @@ public class OverspeedEventHandler extends BaseEventHandler { return null; } - Map<Event, Position> result = null; - DeviceState deviceState = deviceManager.getDeviceState(deviceId); - - if (deviceState.getOverspeedState() == null) { - deviceState.setOverspeedState(position.getSpeed() > speedLimit); - deviceState.setOverspeedGeofenceId(position.getSpeed() > speedLimit ? overspeedGeofenceId : 0); - } else { - result = updateOverspeedState(deviceState, position, speedLimit, overspeedGeofenceId); + OverspeedState state = OverspeedState.fromDevice(device); + OverspeedProcessor.updateState(state, position, speedLimit, minimalDuration, overspeedGeofenceId); + if (state.isChanged()) { + state.toDevice(device); + try { + storage.updateObject(device, new Request( + new Columns.Include("overspeedState", "overspeedTime", "overspeedGeofenceId"), + new Condition.Equals("id", device.getId()))); + } catch (StorageException e) { + LOGGER.warn("Update device overspeed error", e); + } } - - deviceManager.setDeviceState(deviceId, deviceState); - return result; + return state.getEvent() != null ? Collections.singletonMap(state.getEvent(), position) : null; } } diff --git a/src/main/java/org/traccar/helper/BitUtil.java b/src/main/java/org/traccar/helper/BitUtil.java index b6108edff..829ddebc9 100644 --- a/src/main/java/org/traccar/helper/BitUtil.java +++ b/src/main/java/org/traccar/helper/BitUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ public final class BitUtil { } public static boolean check(long number, int index) { - return (number & (1 << index)) != 0; + return (number & (1L << index)) != 0; } public static int between(int number, int from, int to) { diff --git a/src/main/java/org/traccar/helper/BufferUtil.java b/src/main/java/org/traccar/helper/BufferUtil.java index bbf12d738..d1025f548 100644 --- a/src/main/java/org/traccar/helper/BufferUtil.java +++ b/src/main/java/org/traccar/helper/BufferUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,6 +27,24 @@ public final class BufferUtil { private BufferUtil() { } + public static int readSignedMagnitudeInt(ByteBuf buffer) { + long value = buffer.readUnsignedInt(); + int result = (int) BitUtil.to(value, 31); + return BitUtil.check(value, 31) ? -result : result; + } + + public static int indexOf(ByteBuf buffer, int fromIndex, int toIndex, byte value, int count) { + int startIndex = fromIndex; + for (int i = 0; i < count; i++) { + int result = buffer.indexOf(startIndex, toIndex, value); + if (result < 0 || i == count - 1) { + return result; + } + startIndex = result + 1; + } + return -1; + } + public static int indexOf(String needle, ByteBuf haystack) { return indexOf(needle, haystack, haystack.readerIndex(), haystack.writerIndex()); } @@ -41,16 +59,16 @@ public final class BufferUtil { } public static int indexOf(ByteBuf needle, ByteBuf haystack, int startIndex, int endIndex) { - ByteBuf wrappedHaystack; - if (startIndex == haystack.readerIndex() && endIndex == haystack.writerIndex()) { - wrappedHaystack = haystack; - } else { - wrappedHaystack = Unpooled.wrappedBuffer(haystack); - wrappedHaystack.readerIndex(startIndex - haystack.readerIndex()); - wrappedHaystack.writerIndex(endIndex - haystack.readerIndex()); + int originalReaderIndex = haystack.readerIndex(); + int originalWriterIndex = haystack.writerIndex(); + try { + haystack.readerIndex(startIndex); + haystack.writerIndex(endIndex); + return ByteBufUtil.indexOf(needle, haystack); + } finally { + haystack.readerIndex(originalReaderIndex); + haystack.writerIndex(originalWriterIndex); } - int result = ByteBufUtil.indexOf(needle, wrappedHaystack); - return result < 0 ? result : haystack.readerIndex() + startIndex + result; } } diff --git a/src/main/java/org/traccar/helper/Checksum.java b/src/main/java/org/traccar/helper/Checksum.java index 8c3d0063a..db5817275 100644 --- a/src/main/java/org/traccar/helper/Checksum.java +++ b/src/main/java/org/traccar/helper/Checksum.java @@ -200,4 +200,19 @@ public final class Checksum { return (10 - (checksum % 10)) % 10; } + public static int ip(ByteBuffer data) { + int sum = 0; + while (data.remaining() > 0) { + sum += data.get() & 0xff; + if ((sum & 0x80000000) > 0) { + sum = (sum & 0xffff) + (sum >> 16); + } + } + while ((sum >> 16) > 0) { + sum = (sum & 0xffff) + sum >> 16; + } + sum = (sum == 0xffff) ? sum & 0xffff : (~sum) & 0xffff; + return sum; + } + } diff --git a/src/main/java/org/traccar/helper/ClassScanner.java b/src/main/java/org/traccar/helper/ClassScanner.java new file mode 100644 index 000000000..c928f6a12 --- /dev/null +++ b/src/main/java/org/traccar/helper/ClassScanner.java @@ -0,0 +1,80 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public final class ClassScanner { + + private ClassScanner() { + } + + public static List<Class<?>> findSubclasses( + Class<?> baseClass) throws IOException, URISyntaxException, ReflectiveOperationException { + return findSubclasses(baseClass, baseClass.getPackageName()); + } + + public static List<Class<?>> findSubclasses(Class<?> baseClass, String packageName) + throws IOException, URISyntaxException, ReflectiveOperationException { + + List<String> names = new LinkedList<>(); + String packagePath = packageName.replace('.', '/'); + URL packageUrl = baseClass.getClassLoader().getResource(packagePath); + + if (packageUrl.getProtocol().equals("jar")) { + String jarFileName = URLDecoder.decode(packageUrl.getFile(), StandardCharsets.UTF_8.name()); + try (JarFile jf = new JarFile(jarFileName.substring(5, jarFileName.indexOf("!")))) { + Enumeration<JarEntry> jarEntries = jf.entries(); + while (jarEntries.hasMoreElements()) { + String entryName = jarEntries.nextElement().getName(); + if (entryName.startsWith(packagePath) && entryName.length() > packagePath.length() + 5) { + names.add(entryName.substring(packagePath.length() + 1, entryName.lastIndexOf('.'))); + } + } + } + } else { + File folder = new File(new URI(packageUrl.toString())); + File[] files = folder.listFiles(); + if (files != null) { + for (File actual: files) { + String entryName = actual.getName(); + names.add(entryName.substring(0, entryName.lastIndexOf('.'))); + } + } + } + + var classes = new LinkedList<Class<?>>(); + for (String name : names) { + var clazz = Class.forName(packageName + '.' + name); + if (baseClass.isAssignableFrom(clazz)) { + classes.add(clazz); + } + } + return classes; + } + +} diff --git a/src/main/java/org/traccar/helper/Log.java b/src/main/java/org/traccar/helper/Log.java index 8c67f9ddc..e1b201f9f 100644 --- a/src/main/java/org/traccar/helper/Log.java +++ b/src/main/java/org/traccar/helper/Log.java @@ -28,6 +28,10 @@ import java.io.StringWriter; import java.io.Writer; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.nio.file.FileStore; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.ConsoleHandler; @@ -269,4 +273,18 @@ public final class Log { return s.toString(); } + public static long[] getStorageSpace() { + long usable = 0; + long total = 0; + for (Path root : FileSystems.getDefault().getRootDirectories()) { + try { + FileStore store = Files.getFileStore(root); + usable += store.getUsableSpace(); + total += store.getTotalSpace(); + } catch (IOException ignored) { + } + } + return new long[]{usable, total}; + } + } diff --git a/src/main/java/org/traccar/helper/LogAction.java b/src/main/java/org/traccar/helper/LogAction.java index d16b25483..b255b9206 100644 --- a/src/main/java/org/traccar/helper/LogAction.java +++ b/src/main/java/org/traccar/helper/LogAction.java @@ -47,7 +47,7 @@ public final class LogAction { private static final String PATTERN_OBJECT = "user: %d, action: %s, object: %s, id: %d"; private static final String PATTERN_LINK = "user: %d, action: %s, owner: %s, id: %d, property: %s, id: %d"; - private static final String PATTERN_LOGIN = "user: %d, action: %s"; + private static final String PATTERN_LOGIN = "user: %d, action: %s, from: %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"; @@ -72,12 +72,12 @@ public final class LogAction { logLinkAction(ACTION_UNLINK, userId, owner, ownerId, property, propertyId); } - public static void login(long userId) { - logLoginAction(ACTION_LOGIN, userId); + public static void login(long userId, String remoteAddress) { + logLoginAction(ACTION_LOGIN, userId, remoteAddress); } - public static void logout(long userId) { - logLoginAction(ACTION_LOGOUT, userId); + public static void logout(long userId, String remoteAddress) { + logLoginAction(ACTION_LOGOUT, userId, remoteAddress); } public static void failedLogin(String remoteAddress) { @@ -105,8 +105,11 @@ public final class LogAction { Introspector.decapitalize(property.getSimpleName()), propertyId)); } - private static void logLoginAction(String action, long userId) { - LOGGER.info(String.format(PATTERN_LOGIN, userId, action)); + private static void logLoginAction(String action, long userId, String remoteAddress) { + if (remoteAddress == null || remoteAddress.isEmpty()) { + remoteAddress = "unknown"; + } + LOGGER.info(String.format(PATTERN_LOGIN, userId, action, remoteAddress)); } public static void logReport( diff --git a/src/main/java/org/traccar/helper/NetworkUtil.java b/src/main/java/org/traccar/helper/NetworkUtil.java new file mode 100644 index 000000000..2fe3487da --- /dev/null +++ b/src/main/java/org/traccar/helper/NetworkUtil.java @@ -0,0 +1,31 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import io.netty.channel.Channel; +import io.netty.channel.socket.DatagramChannel; + +public final class NetworkUtil { + + private NetworkUtil() { + } + + public static String session(Channel channel) { + char transport = channel instanceof DatagramChannel ? 'U' : 'T'; + return transport + channel.id().asShortText(); + } + +} diff --git a/src/main/java/org/traccar/api/ObjectMapperProvider.java b/src/main/java/org/traccar/helper/ObjectMapperContextResolver.java index f81b20917..b40e30d76 100644 --- a/src/main/java/org/traccar/api/ObjectMapperProvider.java +++ b/src/main/java/org/traccar/helper/ObjectMapperContextResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.api; +package org.traccar.helper; import com.fasterxml.jackson.databind.ObjectMapper; -import org.traccar.Context; +import javax.inject.Inject; import javax.ws.rs.ext.ContextResolver; -import javax.ws.rs.ext.Provider; -@Provider -public class ObjectMapperProvider implements ContextResolver<ObjectMapper> { +// This does not work as a lambda +public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> { + + private final ObjectMapper objectMapper; + + @Inject + public ObjectMapperContextResolver(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } @Override - public ObjectMapper getContext(Class<?> type) { - return Context.getObjectMapper(); + public ObjectMapper getContext(Class<?> clazz) { + return objectMapper; } } diff --git a/src/main/java/org/traccar/helper/Parser.java b/src/main/java/org/traccar/helper/Parser.java index 75106e2ba..aa39e1ad7 100644 --- a/src/main/java/org/traccar/helper/Parser.java +++ b/src/main/java/org/traccar/helper/Parser.java @@ -48,13 +48,14 @@ public class Parser { } public boolean hasNext(int number) { - String value = matcher.group(position); - if (value != null && !value.isEmpty()) { - return true; - } else { - position += number; - return false; + for (int i = position; i < position + number; i++) { + String value = matcher.group(i); + if (value != null && !value.isEmpty()) { + return true; + } } + position += number; + return false; } public String next() { @@ -155,6 +156,7 @@ public class Parser { public enum CoordinateFormat { DEG_DEG, + DEG_DEG_HEM, DEG_HEM, DEG_MIN_MIN, DEG_MIN_HEM, @@ -173,6 +175,10 @@ public class Parser { case DEG_DEG: coordinate = Double.parseDouble(next() + '.' + next()); break; + case DEG_DEG_HEM: + coordinate = Double.parseDouble(next() + '.' + next()); + hemisphere = next(); + break; case DEG_HEM: coordinate = nextDouble(0); hemisphere = next(); diff --git a/src/main/java/org/traccar/helper/PatternUtil.java b/src/main/java/org/traccar/helper/PatternUtil.java index 74813e1d9..a46c7b7b4 100644 --- a/src/main/java/org/traccar/helper/PatternUtil.java +++ b/src/main/java/org/traccar/helper/PatternUtil.java @@ -63,7 +63,7 @@ public final class PatternUtil { for (int i = 0; i < pattern.length(); i++) { try { - Matcher matcher = Pattern.compile("(" + pattern.substring(0, i) + ").*").matcher(input); + Matcher matcher = Pattern.compile("(" + pattern.substring(0, i) + ")[\\s\\S]*").matcher(input); if (matcher.matches()) { result.patternMatch = pattern.substring(0, i); result.patternTail = pattern.substring(i); diff --git a/src/main/java/org/traccar/helper/model/AttributeUtil.java b/src/main/java/org/traccar/helper/model/AttributeUtil.java new file mode 100644 index 000000000..43558e8f7 --- /dev/null +++ b/src/main/java/org/traccar/helper/model/AttributeUtil.java @@ -0,0 +1,94 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper.model; + +import org.traccar.config.ConfigKey; +import org.traccar.config.KeyType; +import org.traccar.config.Keys; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.session.cache.CacheManager; + +public final class AttributeUtil { + + private AttributeUtil() { + } + + @SuppressWarnings({ "deprecation", "unchecked" }) + public static <T> T lookup(CacheManager cacheManager, ConfigKey<T> key, long deviceId) { + Device device = cacheManager.getObject(Device.class, deviceId); + Object result = device.getAttributes().get(key.getKey()); + long groupId = device.getGroupId(); + while (result == null && groupId > 0) { + Group group = cacheManager.getObject(Group.class, groupId); + if (group != null) { + result = group.getAttributes().get(key.getKey()); + groupId = group.getGroupId(); + } else { + groupId = 0; + } + } + if (result == null && key.hasType(KeyType.SERVER)) { + result = cacheManager.getServer().getAttributes().get(key.getKey()); + } + if (result == null && key.hasType(KeyType.CONFIG)) { + result = cacheManager.getConfig().getString(key.getKey()); + } + + if (result != null) { + Class<T> valueClass = key.getValueClass(); + if (valueClass.equals(Boolean.class)) { + return (T) (result instanceof String + ? Boolean.parseBoolean((String) result) + : result); + } else if (valueClass.equals(Integer.class)) { + return (T) (Object) (result instanceof String + ? Integer.parseInt((String) result) + : ((Number) result).intValue()); + } else if (valueClass.equals(Long.class)) { + return (T) (Object) (result instanceof String + ? Long.parseLong((String) result) + : ((Number) result).longValue()); + } else if (valueClass.equals(Double.class)) { + return (T) (Object) (result instanceof String + ? Double.parseDouble((String) result) + : ((Number) result).doubleValue()); + } else { + return (T) result; + } + } + return key.getDefaultValue(); + } + + public static String getDevicePassword( + CacheManager cacheManager, long deviceId, String protocol, String defaultPassword) { + + String password = lookup(cacheManager, Keys.DEVICE_PASSWORD, deviceId); + if (password != null) { + return password; + } + + if (protocol != null) { + password = cacheManager.getConfig().getString(Keys.PROTOCOL_DEVICE_PASSWORD.withPrefix(protocol)); + if (password != null) { + return password; + } + } + + return defaultPassword; + } + +} diff --git a/src/main/java/org/traccar/helper/model/DeviceUtil.java b/src/main/java/org/traccar/helper/model/DeviceUtil.java new file mode 100644 index 000000000..597078caf --- /dev/null +++ b/src/main/java/org/traccar/helper/model/DeviceUtil.java @@ -0,0 +1,33 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper.model; + +import org.traccar.model.Device; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Request; + +public final class DeviceUtil { + + private DeviceUtil() { + } + + public static void resetStatus(Storage storage) throws StorageException { + storage.updateObject(new Device(), new Request(new Columns.Include("status"))); + } + +} diff --git a/src/main/java/org/traccar/helper/model/GeofenceUtil.java b/src/main/java/org/traccar/helper/model/GeofenceUtil.java new file mode 100644 index 000000000..9f063a8b4 --- /dev/null +++ b/src/main/java/org/traccar/helper/model/GeofenceUtil.java @@ -0,0 +1,42 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper.model; + +import org.traccar.config.Config; +import org.traccar.model.Geofence; +import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; + +import java.util.ArrayList; +import java.util.List; + +public final class GeofenceUtil { + + private GeofenceUtil() { + } + + public static List<Long> getCurrentGeofences(Config config, CacheManager cacheManager, Position position) { + List<Long> result = new ArrayList<>(); + for (Geofence geofence : cacheManager.getDeviceObjects(position.getDeviceId(), Geofence.class)) { + if (geofence.getGeometry().containsPoint( + config, geofence, position.getLatitude(), position.getLongitude())) { + result.add(geofence.getId()); + } + } + return result; + } + +} diff --git a/src/main/java/org/traccar/helper/model/PositionUtil.java b/src/main/java/org/traccar/helper/model/PositionUtil.java new file mode 100644 index 000000000..6c380b81a --- /dev/null +++ b/src/main/java/org/traccar/helper/model/PositionUtil.java @@ -0,0 +1,80 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper.model; + +import org.traccar.model.BaseModel; +import org.traccar.model.Device; +import org.traccar.model.Position; +import org.traccar.model.User; +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Order; +import org.traccar.storage.query.Request; + +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +public final class PositionUtil { + + private PositionUtil() { + } + + public static boolean isLatest(CacheManager cacheManager, Position position) { + Position lastPosition = cacheManager.getPosition(position.getDeviceId()); + return lastPosition == null || position.getFixTime().compareTo(lastPosition.getFixTime()) >= 0; + } + + public static double calculateDistance(Position first, Position last, boolean useOdometer) { + double distance; + double firstOdometer = first.getDouble(Position.KEY_ODOMETER); + double lastOdometer = last.getDouble(Position.KEY_ODOMETER); + + if (useOdometer && firstOdometer != 0.0 && lastOdometer != 0.0) { + distance = lastOdometer - firstOdometer; + } else { + distance = last.getDouble(Position.KEY_TOTAL_DISTANCE) - first.getDouble(Position.KEY_TOTAL_DISTANCE); + } + return distance; + } + + public static List<Position> getPositions( + Storage storage, long deviceId, Date from, Date to) throws StorageException { + return storage.getObjects(Position.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("deviceId", deviceId), + new Condition.Between("fixTime", "from", from, "to", to)), + new Order("fixTime"))); + } + + public static List<Position> getLatestPositions(Storage storage, long userId) throws StorageException { + var devices = storage.getObjects(Device.class, new Request( + new Columns.Include("id"), + new Condition.Permission(User.class, userId, Device.class))); + var deviceIds = devices.stream().map(BaseModel::getId).collect(Collectors.toUnmodifiableSet()); + + var positions = storage.getObjects(Position.class, new Request( + new Columns.All(), new Condition.LatestPositions())); + return positions.stream() + .filter(position -> deviceIds.contains(position.getDeviceId())) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/org/traccar/helper/model/UserUtil.java b/src/main/java/org/traccar/helper/model/UserUtil.java new file mode 100644 index 000000000..9f93afeae --- /dev/null +++ b/src/main/java/org/traccar/helper/model/UserUtil.java @@ -0,0 +1,68 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper.model; + +import org.traccar.model.Server; +import org.traccar.model.User; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Order; +import org.traccar.storage.query.Request; + +import java.util.TimeZone; + +public final class UserUtil { + + private UserUtil() { + } + + public static boolean isEmpty(Storage storage) throws StorageException { + return storage.getObjects(User.class, new Request( + new Columns.Include("id"), + new Order("id", false, 1))).isEmpty(); + } + + public static String getDistanceUnit(Server server, User user) { + return lookupStringAttribute(server, user, "distanceUnit", "km"); + } + + public static String getSpeedUnit(Server server, User user) { + return lookupStringAttribute(server, user, "speedUnit", "kn"); + } + + public static String getVolumeUnit(Server server, User user) { + return lookupStringAttribute(server, user, "volumeUnit", "ltr"); + } + + public static TimeZone getTimezone(Server server, User user) { + String timezone = lookupStringAttribute(server, user, "timezone", null); + return timezone != null ? TimeZone.getTimeZone(timezone) : TimeZone.getDefault(); + } + + private static String lookupStringAttribute(Server server, User user, String key, String defaultValue) { + String preference; + String serverPreference = server.getString(key); + String userPreference = user.getString(key); + if (server.getForceSettings()) { + preference = serverPreference != null ? serverPreference : userPreference; + } else { + preference = userPreference != null ? userPreference : serverPreference; + } + return preference != null ? preference : defaultValue; + } + +} diff --git a/src/main/java/org/traccar/mail/LogMailManager.java b/src/main/java/org/traccar/mail/LogMailManager.java new file mode 100644 index 000000000..b6b912d6c --- /dev/null +++ b/src/main/java/org/traccar/mail/LogMailManager.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.mail; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.model.User; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; + +public class LogMailManager implements MailManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(LogMailManager.class); + + @Override + public boolean getEmailEnabled() { + return true; + } + + @Override + public void sendMessage(User user, String subject, String body) throws MessagingException { + sendMessage(user, subject, body, null); + } + + @Override + public void sendMessage(User user, String subject, String body, MimeBodyPart attachment) throws MessagingException { + LOGGER.info("\nTo: " + user.getEmail() + "\nSubject: " + subject + "\nBody:\n" + body); + } + +} diff --git a/src/main/java/org/traccar/mail/MailManager.java b/src/main/java/org/traccar/mail/MailManager.java new file mode 100644 index 000000000..69efbed32 --- /dev/null +++ b/src/main/java/org/traccar/mail/MailManager.java @@ -0,0 +1,31 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.mail; + +import org.traccar.model.User; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; + +public interface MailManager { + + boolean getEmailEnabled(); + + void sendMessage(User user, String subject, String body) throws MessagingException; + + void sendMessage(User user, String subject, String body, MimeBodyPart attachment) throws MessagingException; + +} diff --git a/src/main/java/org/traccar/mail/SmtpMailManager.java b/src/main/java/org/traccar/mail/SmtpMailManager.java new file mode 100644 index 000000000..4a0b7048f --- /dev/null +++ b/src/main/java/org/traccar/mail/SmtpMailManager.java @@ -0,0 +1,160 @@ +/* + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.mail; + +import org.traccar.config.Config; +import org.traccar.config.ConfigKey; +import org.traccar.config.Keys; +import org.traccar.database.StatisticsManager; +import org.traccar.model.User; +import org.traccar.notification.PropertiesProvider; + +import javax.mail.BodyPart; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import java.io.UnsupportedEncodingException; +import java.util.Date; +import java.util.Properties; + +public final class SmtpMailManager implements MailManager { + + private static final String CONTENT_TYPE = "text/html; charset=utf-8"; + + private final Config config; + private final StatisticsManager statisticsManager; + + public SmtpMailManager(Config config, StatisticsManager statisticsManager) { + this.config = config; + this.statisticsManager = statisticsManager; + } + + private static void copyBooleanProperty( + Properties properties, PropertiesProvider provider, ConfigKey<Boolean> key) { + Boolean value = provider.getBoolean(key); + if (value != null) { + properties.put(key.getKey(), String.valueOf(value)); + } + } + + private static void copyStringProperty( + Properties properties, PropertiesProvider provider, ConfigKey<String> key) { + String value = provider.getString(key); + if (value != null) { + properties.put(key.getKey(), value); + } + } + + private static Properties getProperties(PropertiesProvider provider) { + String host = provider.getString(Keys.MAIL_SMTP_HOST); + if (host != null) { + Properties properties = new Properties(); + + properties.put(Keys.MAIL_TRANSPORT_PROTOCOL.getKey(), provider.getString(Keys.MAIL_TRANSPORT_PROTOCOL)); + properties.put(Keys.MAIL_SMTP_HOST.getKey(), host); + properties.put(Keys.MAIL_SMTP_PORT.getKey(), String.valueOf(provider.getInteger(Keys.MAIL_SMTP_PORT))); + + copyBooleanProperty(properties, provider, Keys.MAIL_SMTP_STARTTLS_ENABLE); + copyBooleanProperty(properties, provider, Keys.MAIL_SMTP_STARTTLS_REQUIRED); + copyBooleanProperty(properties, provider, Keys.MAIL_SMTP_SSL_ENABLE); + copyStringProperty(properties, provider, Keys.MAIL_SMTP_SSL_TRUST); + copyStringProperty(properties, provider, Keys.MAIL_SMTP_SSL_PROTOCOLS); + copyStringProperty(properties, provider, Keys.MAIL_SMTP_USERNAME); + copyStringProperty(properties, provider, Keys.MAIL_SMTP_PASSWORD); + copyStringProperty(properties, provider, Keys.MAIL_SMTP_FROM); + copyStringProperty(properties, provider, Keys.MAIL_SMTP_FROM_NAME); + + return properties; + } + return null; + } + + public boolean getEmailEnabled() { + return config.hasKey(Keys.MAIL_SMTP_HOST); + } + + public void sendMessage( + User user, String subject, String body) throws MessagingException { + sendMessage(user, subject, body, null); + } + + public void sendMessage( + User user, String subject, String body, MimeBodyPart attachment) throws MessagingException { + + Properties properties = null; + if (!config.getBoolean(Keys.MAIL_SMTP_IGNORE_USER_CONFIG)) { + properties = getProperties(new PropertiesProvider(user)); + } + if (properties == null) { + properties = getProperties(new PropertiesProvider(config)); + } + if (properties == null) { + throw new MessagingException("No SMTP configuration found"); + } + + Session session = Session.getInstance(properties); + + MimeMessage message = new MimeMessage(session); + + String from = properties.getProperty(Keys.MAIL_SMTP_FROM.getKey()); + if (from != null) { + String fromName = properties.getProperty(Keys.MAIL_SMTP_FROM_NAME.getKey()); + if (fromName != null) { + try { + message.setFrom(new InternetAddress(from, fromName)); + } catch (UnsupportedEncodingException e) { + throw new MessagingException("Email address issue"); + } + } else { + message.setFrom(new InternetAddress(from)); + } + } + + message.addRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmail())); + message.setSubject(subject); + message.setSentDate(new Date()); + + if (attachment != null) { + Multipart multipart = new MimeMultipart(); + + BodyPart messageBodyPart = new MimeBodyPart(); + messageBodyPart.setContent(body, CONTENT_TYPE); + multipart.addBodyPart(messageBodyPart); + multipart.addBodyPart(attachment); + + message.setContent(multipart); + } else { + message.setContent(body, CONTENT_TYPE); + } + + try (Transport transport = session.getTransport()) { + statisticsManager.registerMail(); + transport.connect( + properties.getProperty(Keys.MAIL_SMTP_HOST.getKey()), + properties.getProperty(Keys.MAIL_SMTP_USERNAME.getKey()), + properties.getProperty(Keys.MAIL_SMTP_PASSWORD.getKey())); + transport.sendMessage(message, message.getAllRecipients()); + } + } + +} diff --git a/src/main/java/org/traccar/model/Attribute.java b/src/main/java/org/traccar/model/Attribute.java index 45d40b3ec..65f2e3881 100644 --- a/src/main/java/org/traccar/model/Attribute.java +++ b/src/main/java/org/traccar/model/Attribute.java @@ -16,6 +16,9 @@ */ package org.traccar.model; +import org.traccar.storage.StorageName; + +@StorageName("tc_attributes") public class Attribute extends BaseModel { private String description; diff --git a/src/main/java/org/traccar/database/CalendarManager.java b/src/main/java/org/traccar/model/BaseCommand.java index 44ced1082..f87b8ef65 100644 --- a/src/main/java/org/traccar/database/CalendarManager.java +++ b/src/main/java/org/traccar/model/BaseCommand.java @@ -1,6 +1,5 @@ /* - * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) - * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) + * Copyright 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.database; +package org.traccar.model; -import org.traccar.model.Calendar; +public class BaseCommand extends Message { -public class CalendarManager extends SimpleObjectManager<Calendar> { + private boolean textChannel; - public CalendarManager(DataManager dataManager) { - super(dataManager, Calendar.class); + public boolean getTextChannel() { + return textChannel; + } + + public void setTextChannel(boolean textChannel) { + this.textChannel = textChannel; } } diff --git a/src/main/java/org/traccar/model/BaseModel.java b/src/main/java/org/traccar/model/BaseModel.java index 8bdb916e8..acde0f83d 100644 --- a/src/main/java/org/traccar/model/BaseModel.java +++ b/src/main/java/org/traccar/model/BaseModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,11 +20,11 @@ public class BaseModel { private long id; - public final long getId() { + public long getId() { return id; } - public final void setId(long id) { + public void setId(long id) { this.id = id; } diff --git a/src/main/java/org/traccar/model/Calendar.java b/src/main/java/org/traccar/model/Calendar.java index 1010325b6..c1f98a957 100644 --- a/src/main/java/org/traccar/model/Calendar.java +++ b/src/main/java/org/traccar/model/Calendar.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,11 +20,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import net.fortuna.ical4j.data.CalendarBuilder; import net.fortuna.ical4j.data.ParserException; import net.fortuna.ical4j.filter.Filter; -import net.fortuna.ical4j.filter.PeriodRule; +import net.fortuna.ical4j.filter.predicate.PeriodRule; import net.fortuna.ical4j.model.DateTime; import net.fortuna.ical4j.model.Period; import net.fortuna.ical4j.model.component.CalendarComponent; -import org.traccar.database.QueryIgnore; +import org.traccar.storage.QueryIgnore; +import org.traccar.storage.StorageName; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -32,6 +33,7 @@ import java.time.Duration; import java.util.Collection; import java.util.Date; +@StorageName("tc_calendars") public class Calendar extends ExtendedModel { private String name; @@ -47,13 +49,13 @@ public class Calendar extends ExtendedModel { private byte[] data; public byte[] getData() { - return data.clone(); + return data; } public void setData(byte[] data) throws IOException, ParserException { CalendarBuilder builder = new CalendarBuilder(); calendar = builder.build(new ByteArrayInputStream(data)); - this.data = data.clone(); + this.data = data; } private net.fortuna.ical4j.model.Calendar calendar; diff --git a/src/main/java/org/traccar/model/CellTower.java b/src/main/java/org/traccar/model/CellTower.java index 254487471..355594c64 100644 --- a/src/main/java/org/traccar/model/CellTower.java +++ b/src/main/java/org/traccar/model/CellTower.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,11 @@ package org.traccar.model; import com.fasterxml.jackson.annotation.JsonInclude; -import org.traccar.Context; +import org.traccar.config.Config; import org.traccar.config.Keys; +import java.util.Objects; + @JsonInclude(JsonInclude.Include.NON_NULL) public class CellTower { @@ -37,14 +39,12 @@ public class CellTower { return cellTower; } - public static CellTower fromLacCid(int lac, long cid) { - return from( - Context.getConfig().getInteger(Keys.GEOLOCATION_MCC), - Context.getConfig().getInteger(Keys.GEOLOCATION_MCC), lac, cid); + public static CellTower fromLacCid(Config config, int lac, long cid) { + return from(config.getInteger(Keys.GEOLOCATION_MCC), config.getInteger(Keys.GEOLOCATION_MNC), lac, cid); } - public static CellTower fromCidLac(long cid, int lac) { - return fromLacCid(lac, cid); + public static CellTower fromCidLac(Config config, long cid, int lac) { + return fromLacCid(config, lac, cid); } private String radioType; @@ -113,4 +113,26 @@ public class CellTower { mobileNetworkCode = Integer.parseInt(operatorString.substring(3)); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CellTower cellTower = (CellTower) o; + return Objects.equals(radioType, cellTower.radioType) + && Objects.equals(cellId, cellTower.cellId) + && Objects.equals(locationAreaCode, cellTower.locationAreaCode) + && Objects.equals(mobileCountryCode, cellTower.mobileCountryCode) + && Objects.equals(mobileNetworkCode, cellTower.mobileNetworkCode) + && Objects.equals(signalStrength, cellTower.signalStrength); + } + + @Override + public int hashCode() { + return Objects.hash(radioType, cellId, locationAreaCode, mobileCountryCode, mobileNetworkCode, signalStrength); + } + } diff --git a/src/main/java/org/traccar/model/Command.java b/src/main/java/org/traccar/model/Command.java index 99930d1e6..99988dd82 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,14 @@ */ package org.traccar.model; -import org.traccar.database.QueryIgnore; +import org.traccar.storage.QueryIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.traccar.storage.StorageName; +@StorageName("tc_commands") @JsonIgnoreProperties(ignoreUnknown = true) -public class Command extends Message implements Cloneable { +public class Command extends BaseCommand { public static final String TYPE_CUSTOM = "custom"; public static final String TYPE_IDENTIFICATION = "deviceIdentification"; @@ -56,11 +58,10 @@ public class Command extends Message implements Cloneable { public static final String TYPE_GET_MODEM_STATUS = "getModemStatus"; public static final String TYPE_GET_DEVICE_STATUS = "getDeviceStatus"; public static final String TYPE_SET_SPEED_LIMIT = "setSpeedLimit"; - public static final String TYPE_MODE_POWER_SAVING = "modePowerSaving"; public static final String TYPE_MODE_DEEP_SLEEP = "modeDeepSleep"; - public static final String TYPE_ALARM_GEOFENCE = "movementAlarm"; + public static final String TYPE_ALARM_GEOFENCE = "alarmGeofence"; public static final String TYPE_ALARM_BATTERY = "alarmBattery"; public static final String TYPE_ALARM_SOS = "alarmSos"; public static final String TYPE_ALARM_REMOVE = "alarmRemove"; @@ -83,25 +84,16 @@ public class Command extends Message implements Cloneable { public static final String KEY_SERVER = "server"; public static final String KEY_PORT = "port"; + @QueryIgnore @Override - public Command clone() throws CloneNotSupportedException { - return (Command) super.clone(); - } - - private boolean textChannel; - - public boolean getTextChannel() { - return textChannel; - } - - public void setTextChannel(boolean textChannel) { - this.textChannel = textChannel; + public long getDeviceId() { + return super.getDeviceId(); } @QueryIgnore @Override - public long getDeviceId() { - return super.getDeviceId(); + public void setDeviceId(long deviceId) { + super.setDeviceId(deviceId); } private String description; diff --git a/src/main/java/org/traccar/model/Device.java b/src/main/java/org/traccar/model/Device.java index 0c9be932d..b8c87921d 100644 --- a/src/main/java/org/traccar/model/Device.java +++ b/src/main/java/org/traccar/model/Device.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,14 @@ package org.traccar.model; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; -import org.traccar.database.QueryExtended; -import org.traccar.database.QueryIgnore; +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.traccar.storage.QueryIgnore; +import org.traccar.storage.StorageName; -public class Device extends GroupedModel { +@StorageName("tc_devices") +public class Device extends GroupedModel implements Disableable { private String name; @@ -55,26 +58,18 @@ public class Device extends GroupedModel { } public void setStatus(String status) { - this.status = status; + this.status = status != null ? status.trim() : null; } private Date lastUpdate; - @QueryExtended + @QueryIgnore public Date getLastUpdate() { - if (lastUpdate != null) { - return new Date(lastUpdate.getTime()); - } else { - return null; - } + return this.lastUpdate; } public void setLastUpdate(Date lastUpdate) { - if (lastUpdate != null) { - this.lastUpdate = new Date(lastUpdate.getTime()); - } else { - this.lastUpdate = null; - } + this.lastUpdate = lastUpdate; } private long positionId; @@ -95,8 +90,12 @@ public class Device extends GroupedModel { return geofenceIds; } - public void setGeofenceIds(List<Long> geofenceIds) { - this.geofenceIds = geofenceIds; + public void setGeofenceIds(List<? extends Number> geofenceIds) { + if (geofenceIds != null) { + this.geofenceIds = geofenceIds.stream().map(Number::longValue).collect(Collectors.toList()); + } else { + this.geofenceIds = null; + } } private String phone; @@ -141,12 +140,117 @@ public class Device extends GroupedModel { private boolean disabled; + @Override public boolean getDisabled() { return disabled; } + @Override public void setDisabled(boolean disabled) { this.disabled = disabled; } + private Date expirationTime; + + @Override + public Date getExpirationTime() { + return expirationTime; + } + + @Override + public void setExpirationTime(Date expirationTime) { + this.expirationTime = expirationTime; + } + + private boolean motionStreak; + + @QueryIgnore + @JsonIgnore + public boolean getMotionStreak() { + return motionStreak; + } + + @JsonIgnore + public void setMotionStreak(boolean motionStreak) { + this.motionStreak = motionStreak; + } + + private boolean motionState; + + @QueryIgnore + @JsonIgnore + public boolean getMotionState() { + return motionState; + } + + @JsonIgnore + public void setMotionState(boolean motionState) { + this.motionState = motionState; + } + + private Date motionTime; + + @QueryIgnore + @JsonIgnore + public Date getMotionTime() { + return motionTime; + } + + @JsonIgnore + public void setMotionTime(Date motionTime) { + this.motionTime = motionTime; + } + + private double motionDistance; + + @QueryIgnore + @JsonIgnore + public double getMotionDistance() { + return motionDistance; + } + + @JsonIgnore + public void setMotionDistance(double motionDistance) { + this.motionDistance = motionDistance; + } + + private boolean overspeedState; + + @QueryIgnore + @JsonIgnore + public boolean getOverspeedState() { + return overspeedState; + } + + @JsonIgnore + public void setOverspeedState(boolean overspeedState) { + this.overspeedState = overspeedState; + } + + private Date overspeedTime; + + @QueryIgnore + @JsonIgnore + public Date getOverspeedTime() { + return overspeedTime; + } + + @JsonIgnore + public void setOverspeedTime(Date overspeedTime) { + this.overspeedTime = overspeedTime; + } + + private long overspeedGeofenceId; + + @QueryIgnore + @JsonIgnore + public long getOverspeedGeofenceId() { + return overspeedGeofenceId; + } + + @JsonIgnore + public void setOverspeedGeofenceId(long overspeedGeofenceId) { + this.overspeedGeofenceId = overspeedGeofenceId; + } + } diff --git a/src/main/java/org/traccar/model/DeviceState.java b/src/main/java/org/traccar/model/DeviceState.java deleted file mode 100644 index 75d6726ee..000000000 --- a/src/main/java/org/traccar/model/DeviceState.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2017 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.model; - -public class DeviceState { - - private Boolean motionState; - - public void setMotionState(boolean motionState) { - this.motionState = motionState; - } - - public Boolean getMotionState() { - return motionState; - } - - private Position motionPosition; - - public void setMotionPosition(Position motionPosition) { - this.motionPosition = motionPosition; - } - - public Position getMotionPosition() { - return motionPosition; - } - - private Boolean overspeedState; - - public void setOverspeedState(boolean overspeedState) { - this.overspeedState = overspeedState; - } - - public Boolean getOverspeedState() { - return overspeedState; - } - - private Position overspeedPosition; - - public void setOverspeedPosition(Position overspeedPosition) { - this.overspeedPosition = overspeedPosition; - } - - public Position getOverspeedPosition() { - return overspeedPosition; - } - - private long overspeedGeofenceId; - - public void setOverspeedGeofenceId(long overspeedGeofenceId) { - this.overspeedGeofenceId = overspeedGeofenceId; - } - - public long getOverspeedGeofenceId() { - return overspeedGeofenceId; - } - -} diff --git a/src/main/java/org/traccar/model/Disableable.java b/src/main/java/org/traccar/model/Disableable.java new file mode 100644 index 000000000..1145d6279 --- /dev/null +++ b/src/main/java/org/traccar/model/Disableable.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import java.util.Date; + +public interface Disableable { + + boolean getDisabled(); + + void setDisabled(boolean disabled); + + Date getExpirationTime(); + + void setExpirationTime(Date expirationTime); + + default void checkDisabled() throws SecurityException { + if (getDisabled()) { + throw new SecurityException(getClass().getSimpleName() + " is disabled"); + } + if (getExpirationTime() != null && System.currentTimeMillis() > getExpirationTime().getTime()) { + throw new SecurityException(getClass().getSimpleName() + " has expired"); + } + } + +} diff --git a/src/main/java/org/traccar/model/Driver.java b/src/main/java/org/traccar/model/Driver.java index 05f52fd4d..b9e023088 100644 --- a/src/main/java/org/traccar/model/Driver.java +++ b/src/main/java/org/traccar/model/Driver.java @@ -16,6 +16,9 @@ */ package org.traccar.model; +import org.traccar.storage.StorageName; + +@StorageName("tc_drivers") public class Driver extends ExtendedModel { private String name; @@ -37,4 +40,5 @@ public class Driver extends ExtendedModel { public void setUniqueId(String uniqueId) { this.uniqueId = uniqueId; } + } diff --git a/src/main/java/org/traccar/model/Event.java b/src/main/java/org/traccar/model/Event.java index a7a134ecf..0e851d748 100644 --- a/src/main/java/org/traccar/model/Event.java +++ b/src/main/java/org/traccar/model/Event.java @@ -15,8 +15,11 @@ */ package org.traccar.model; +import org.traccar.storage.StorageName; + import java.util.Date; +@StorageName("tc_events") public class Event extends Message { public Event(String type, Position position) { @@ -49,6 +52,7 @@ public class Event extends Message { public static final String TYPE_DEVICE_OVERSPEED = "deviceOverspeed"; public static final String TYPE_DEVICE_FUEL_DROP = "deviceFuelDrop"; + public static final String TYPE_DEVICE_FUEL_INCREASE = "deviceFuelIncrease"; public static final String TYPE_GEOFENCE_ENTER = "geofenceEnter"; public static final String TYPE_GEOFENCE_EXIT = "geofenceExit"; @@ -59,10 +63,9 @@ public class Event extends Message { public static final String TYPE_IGNITION_OFF = "ignitionOff"; public static final String TYPE_MAINTENANCE = "maintenance"; - public static final String TYPE_TEXT_MESSAGE = "textMessage"; - public static final String TYPE_DRIVER_CHANGED = "driverChanged"; + public static final String TYPE_MEDIA = "media"; private Date eventTime; diff --git a/src/main/java/org/traccar/model/ExtendedModel.java b/src/main/java/org/traccar/model/ExtendedModel.java index 8353d0e66..7a61eda8c 100644 --- a/src/main/java/org/traccar/model/ExtendedModel.java +++ b/src/main/java/org/traccar/model/ExtendedModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,17 +17,22 @@ package org.traccar.model; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; public class ExtendedModel extends BaseModel { private Map<String, Object> attributes = new LinkedHashMap<>(); + public boolean hasAttribute(String key) { + return attributes.containsKey(key); + } + public Map<String, Object> getAttributes() { return attributes; } public void setAttributes(Map<String, Object> attributes) { - this.attributes = attributes; + this.attributes = Objects.requireNonNullElseGet(attributes, LinkedHashMap::new); } public void set(String key, Boolean value) { @@ -86,7 +91,7 @@ public class ExtendedModel extends BaseModel { public String getString(String key) { if (attributes.containsKey(key)) { - return (String) attributes.get(key); + return attributes.get(key).toString(); } else { return null; } @@ -94,7 +99,12 @@ public class ExtendedModel extends BaseModel { public double getDouble(String key) { if (attributes.containsKey(key)) { - return ((Number) attributes.get(key)).doubleValue(); + Object value = attributes.get(key); + if (value instanceof Number) { + return ((Number) attributes.get(key)).doubleValue(); + } else { + return Double.parseDouble(value.toString()); + } } else { return 0.0; } @@ -102,7 +112,12 @@ public class ExtendedModel extends BaseModel { public boolean getBoolean(String key) { if (attributes.containsKey(key)) { - return (Boolean) attributes.get(key); + Object value = attributes.get(key); + if (value instanceof Boolean) { + return (Boolean) attributes.get(key); + } else { + return Boolean.parseBoolean(value.toString()); + } } else { return false; } @@ -110,7 +125,12 @@ public class ExtendedModel extends BaseModel { public int getInteger(String key) { if (attributes.containsKey(key)) { - return ((Number) attributes.get(key)).intValue(); + Object value = attributes.get(key); + if (value instanceof Number) { + return ((Number) attributes.get(key)).intValue(); + } else { + return Integer.parseInt(value.toString()); + } } else { return 0; } @@ -118,7 +138,12 @@ public class ExtendedModel extends BaseModel { public long getLong(String key) { if (attributes.containsKey(key)) { - return ((Number) attributes.get(key)).longValue(); + Object value = attributes.get(key); + if (value instanceof Number) { + return ((Number) attributes.get(key)).longValue(); + } else { + return Long.parseLong(value.toString()); + } } else { return 0; } diff --git a/src/main/java/org/traccar/model/Geofence.java b/src/main/java/org/traccar/model/Geofence.java index 85f392f99..9259028fb 100644 --- a/src/main/java/org/traccar/model/Geofence.java +++ b/src/main/java/org/traccar/model/Geofence.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,24 +15,19 @@ */ package org.traccar.model; -import java.text.ParseException; - -import org.traccar.Context; -import org.traccar.config.Keys; -import org.traccar.database.QueryIgnore; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.traccar.geofence.GeofenceCircle; import org.traccar.geofence.GeofenceGeometry; import org.traccar.geofence.GeofencePolygon; import org.traccar.geofence.GeofencePolyline; +import org.traccar.storage.QueryIgnore; +import org.traccar.storage.StorageName; -import com.fasterxml.jackson.annotation.JsonIgnore; +import java.text.ParseException; +@StorageName("tc_geofences") public class Geofence extends ScheduledModel { - public static final String TYPE_GEOFENCE_CILCLE = "geofenceCircle"; - public static final String TYPE_GEOFENCE_POLYGON = "geofencePolygon"; - public static final String TYPE_GEOFENCE_POLYLINE = "geofencePolyline"; - private String name; public String getName() { @@ -66,9 +61,7 @@ public class Geofence extends ScheduledModel { } else if (area.startsWith("POLYGON")) { geometry = new GeofencePolygon(area); } else if (area.startsWith("LINESTRING")) { - final double distance = getDouble("polylineDistance"); - geometry = new GeofencePolyline(area, distance > 0 ? distance - : Context.getConfig().getDouble(Keys.GEOFENCE_POLYLINE_DISTANCE)); + geometry = new GeofencePolyline(area); } else { throw new ParseException("Unknown geometry type", 0); } @@ -90,4 +83,5 @@ public class Geofence extends ScheduledModel { area = geometry.toWkt(); this.geometry = geometry; } + } diff --git a/src/main/java/org/traccar/model/Group.java b/src/main/java/org/traccar/model/Group.java index 91ea2319d..ff69f61fa 100644 --- a/src/main/java/org/traccar/model/Group.java +++ b/src/main/java/org/traccar/model/Group.java @@ -15,6 +15,9 @@ */ package org.traccar.model; +import org.traccar.storage.StorageName; + +@StorageName("tc_groups") public class Group extends GroupedModel { private String name; diff --git a/src/main/java/org/traccar/model/Maintenance.java b/src/main/java/org/traccar/model/Maintenance.java index 73f67ea96..cad100a3a 100644 --- a/src/main/java/org/traccar/model/Maintenance.java +++ b/src/main/java/org/traccar/model/Maintenance.java @@ -16,6 +16,9 @@ */ package org.traccar.model; +import org.traccar.storage.StorageName; + +@StorageName("tc_maintenances") public class Maintenance extends ExtendedModel { private String name; diff --git a/src/main/java/org/traccar/model/Network.java b/src/main/java/org/traccar/model/Network.java index 4d67fc5d8..b31c53c38 100644 --- a/src/main/java/org/traccar/model/Network.java +++ b/src/main/java/org/traccar/model/Network.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import java.util.ArrayList; import java.util.Collection; +import java.util.Objects; @JsonInclude(JsonInclude.Include.NON_NULL) public class Network { @@ -118,4 +119,27 @@ public class Network { wifiAccessPoints.add(wifiAccessPoint); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Network network = (Network) o; + return Objects.equals(homeMobileCountryCode, network.homeMobileCountryCode) + && Objects.equals(homeMobileNetworkCode, network.homeMobileNetworkCode) + && Objects.equals(radioType, network.radioType) + && Objects.equals(carrier, network.carrier) + && Objects.equals(cellTowers, network.cellTowers) + && Objects.equals(wifiAccessPoints, network.wifiAccessPoints); + } + + @Override + public int hashCode() { + return Objects.hash( + homeMobileCountryCode, homeMobileNetworkCode, radioType, carrier, cellTowers, wifiAccessPoints); + } + } diff --git a/src/main/java/org/traccar/model/Notification.java b/src/main/java/org/traccar/model/Notification.java index f1983a03a..95e446132 100644 --- a/src/main/java/org/traccar/model/Notification.java +++ b/src/main/java/org/traccar/model/Notification.java @@ -18,10 +18,12 @@ package org.traccar.model; import java.util.HashSet; import java.util.Set; -import org.traccar.database.QueryIgnore; +import org.traccar.storage.QueryIgnore; import com.fasterxml.jackson.annotation.JsonIgnore; +import org.traccar.storage.StorageName; +@StorageName("tc_notifications") public class Notification extends ScheduledModel { private boolean always; @@ -44,7 +46,6 @@ public class Notification extends ScheduledModel { this.type = type; } - private String notificators; public String getNotificators() { @@ -55,7 +56,6 @@ public class Notification extends ScheduledModel { this.notificators = transports; } - @JsonIgnore @QueryIgnore public Set<String> getNotificatorsTypes() { diff --git a/src/main/java/org/traccar/model/Order.java b/src/main/java/org/traccar/model/Order.java index fe6d926b8..7d09b0a47 100644 --- a/src/main/java/org/traccar/model/Order.java +++ b/src/main/java/org/traccar/model/Order.java @@ -15,6 +15,9 @@ */ package org.traccar.model; +import org.traccar.storage.StorageName; + +@StorageName("tc_orders") public class Order extends ExtendedModel { private String uniqueId; diff --git a/src/main/java/org/traccar/model/Pair.java b/src/main/java/org/traccar/model/Pair.java new file mode 100644 index 000000000..b679de57b --- /dev/null +++ b/src/main/java/org/traccar/model/Pair.java @@ -0,0 +1,43 @@ +package org.traccar.model; + +import java.util.Objects; + +public class Pair<K, V> { + + private final K first; + private final V second; + + public Pair(K first, V second) { + this.first = first; + this.second = second; + } + + public K getFirst() { + return first; + } + + public V getSecond() { + return second; + } + + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Pair pair = (Pair) o; + + return Objects.equals(first, pair.first) && Objects.equals(second, pair.second); + } + + @Override + public int hashCode() { + return Objects.hash(first, second); + } + +} diff --git a/src/main/java/org/traccar/model/Permission.java b/src/main/java/org/traccar/model/Permission.java index 6475a4582..0b2f0584f 100644 --- a/src/main/java/org/traccar/model/Permission.java +++ b/src/main/java/org/traccar/model/Permission.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,42 +16,121 @@ */ package org.traccar.model; -import java.util.Iterator; +import java.beans.Introspector; +import java.io.IOException; +import java.net.URISyntaxException; import java.util.LinkedHashMap; import java.util.Map; +import java.util.TreeMap; -import org.traccar.database.DataManager; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.traccar.helper.ClassScanner; +import org.traccar.storage.QueryIgnore; public class Permission { - private final Class<?> ownerClass; + private static final Map<String, Class<? extends BaseModel>> CLASSES = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + static { + try { + for (Class<?> clazz : ClassScanner.findSubclasses(BaseModel.class)) { + CLASSES.put(clazz.getSimpleName(), (Class<? extends BaseModel>) clazz); + } + } catch (IOException | ReflectiveOperationException | URISyntaxException e) { + throw new RuntimeException(e); + } + } + + private final LinkedHashMap<String, Long> data; + + private final Class<? extends BaseModel> ownerClass; private final long ownerId; - private final Class<?> propertyClass; + private final Class<? extends BaseModel> propertyClass; private final long propertyId; - public Permission(LinkedHashMap<String, Long> permissionMap) throws ClassNotFoundException { - Iterator<Map.Entry<String, Long>> iterator = permissionMap.entrySet().iterator(); - String owner = iterator.next().getKey(); - ownerClass = DataManager.getClassByName(owner); - String property = iterator.next().getKey(); - propertyClass = DataManager.getClassByName(property); - ownerId = permissionMap.get(owner); - propertyId = permissionMap.get(property); + public Permission(LinkedHashMap<String, Long> data) { + this.data = data; + var iterator = data.entrySet().iterator(); + var owner = iterator.next(); + ownerClass = getKeyClass(owner.getKey()); + ownerId = owner.getValue(); + var property = iterator.next(); + propertyClass = getKeyClass(property.getKey()); + propertyId = property.getValue(); + } + + public Permission( + Class<? extends BaseModel> ownerClass, long ownerId, + Class<? extends BaseModel> propertyClass, long propertyId) { + this.ownerClass = ownerClass; + this.ownerId = ownerId; + this.propertyClass = propertyClass; + this.propertyId = propertyId; + data = new LinkedHashMap<>(); + data.put(getKey(ownerClass), ownerId); + data.put(getKey(propertyClass), propertyId); + } + + public static Class<? extends BaseModel> getKeyClass(String key) { + return CLASSES.get(key.substring(0, key.length() - 2)); + } + + public static String getKey(Class<?> clazz) { + return Introspector.decapitalize(clazz.getSimpleName()) + "Id"; } - public Class<?> getOwnerClass() { + public static String getStorageName(Class<?> ownerClass, Class<?> propertyClass) { + String ownerName = ownerClass.getSimpleName(); + String propertyName = propertyClass.getSimpleName(); + String managedPrefix = "Managed"; + if (propertyName.startsWith(managedPrefix)) { + propertyName = propertyName.substring(managedPrefix.length()); + } + return "tc_" + Introspector.decapitalize(ownerName) + "_" + Introspector.decapitalize(propertyName); + } + + @QueryIgnore + @JsonIgnore + public String getStorageName() { + return getStorageName(ownerClass, propertyClass); + } + + @QueryIgnore + @JsonAnyGetter + public Map<String, Long> get() { + return data; + } + + @QueryIgnore + @JsonAnySetter + public void set(String key, Long value) { + data.put(key, value); + } + + @QueryIgnore + @JsonIgnore + public Class<? extends BaseModel> getOwnerClass() { return ownerClass; } + @QueryIgnore + @JsonIgnore public long getOwnerId() { return ownerId; } - public Class<?> getPropertyClass() { + @QueryIgnore + @JsonIgnore + public Class<? extends BaseModel> getPropertyClass() { return propertyClass; } + @QueryIgnore + @JsonIgnore public long getPropertyId() { return propertyId; } + } diff --git a/src/main/java/org/traccar/model/Position.java b/src/main/java/org/traccar/model/Position.java index 09d25e832..1286db5f2 100644 --- a/src/main/java/org/traccar/model/Position.java +++ b/src/main/java/org/traccar/model/Position.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,11 @@ package org.traccar.model; import java.util.Date; -import org.traccar.database.QueryIgnore; +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.traccar.storage.QueryIgnore; +import org.traccar.storage.StorageName; +@StorageName("tc_positions") public class Position extends Message { public static final String KEY_ORIGINAL = "raw"; @@ -147,7 +150,6 @@ public class Position extends Message { public Position(String protocol) { this.protocol = protocol; - this.serverTime = new Date(); } private String protocol; @@ -190,6 +192,7 @@ public class Position extends Message { this.fixTime = fixTime; } + @QueryIgnore public void setTime(Date time) { setDeviceTime(time); setFixTime(time); @@ -202,6 +205,7 @@ public class Position extends Message { return outdated; } + @QueryIgnore public void setOutdated(boolean outdated) { this.outdated = outdated; } @@ -223,6 +227,9 @@ public class Position extends Message { } public void setLatitude(double latitude) { + if (latitude < -90 || latitude > 90) { + throw new IllegalArgumentException("Latitude out of range"); + } this.latitude = latitude; } @@ -233,6 +240,9 @@ public class Position extends Message { } public void setLongitude(double longitude) { + if (longitude < -180 || longitude > 180) { + throw new IllegalArgumentException("Longitude out of range"); + } this.longitude = longitude; } @@ -296,10 +306,18 @@ public class Position extends Message { this.network = network; } - @Override + @JsonIgnore @QueryIgnore + @Override public String getType() { return super.getType(); } + @JsonIgnore + @QueryIgnore + @Override + public void setType(String type) { + super.setType(type); + } + } diff --git a/src/main/java/org/traccar/model/QueuedCommand.java b/src/main/java/org/traccar/model/QueuedCommand.java new file mode 100644 index 000000000..96a1eca4b --- /dev/null +++ b/src/main/java/org/traccar/model/QueuedCommand.java @@ -0,0 +1,46 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.traccar.storage.StorageName; + +import java.util.HashMap; + +@StorageName("tc_commands_queue") +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueuedCommand extends BaseCommand { + + public static QueuedCommand fromCommand(Command command) { + QueuedCommand queuedCommand = new QueuedCommand(); + queuedCommand.setDeviceId(command.getDeviceId()); + queuedCommand.setType(command.getType()); + queuedCommand.setTextChannel(command.getTextChannel()); + queuedCommand.setAttributes(new HashMap<>(command.getAttributes())); + return queuedCommand; + } + + public Command toCommand() { + Command command = new Command(); + command.setDeviceId(getDeviceId()); + command.setType(getType()); + command.setDescription(""); + command.setTextChannel(getTextChannel()); + command.setAttributes(new HashMap<>(getAttributes())); + return command; + } + +} diff --git a/src/main/java/org/traccar/model/Server.java b/src/main/java/org/traccar/model/Server.java index 7bdb53b22..73645721b 100644 --- a/src/main/java/org/traccar/model/Server.java +++ b/src/main/java/org/traccar/model/Server.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,12 @@ package org.traccar.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.traccar.Context; -import org.traccar.database.QueryIgnore; +import org.traccar.storage.QueryIgnore; +import org.traccar.storage.StorageName; +@StorageName("tc_servers") @JsonIgnoreProperties(ignoreUnknown = true) -public class Server extends ExtendedModel { +public class Server extends ExtendedModel implements UserRestrictions { private boolean registration; @@ -34,6 +35,7 @@ public class Server extends ExtendedModel { private boolean readonly; + @Override public boolean getReadonly() { return readonly; } @@ -44,6 +46,7 @@ public class Server extends ExtendedModel { private boolean deviceReadonly; + @Override public boolean getDeviceReadonly() { return deviceReadonly; } @@ -82,6 +85,16 @@ public class Server extends ExtendedModel { this.mapUrl = mapUrl; } + private String overlayUrl; + + public String getOverlayUrl() { + return overlayUrl; + } + + public void setOverlayUrl(String overlayUrl) { + this.overlayUrl = overlayUrl; + } + private double latitude; public double getLatitude() { @@ -144,6 +157,7 @@ public class Server extends ExtendedModel { private boolean limitCommands; + @Override public boolean getLimitCommands() { return limitCommands; } @@ -152,6 +166,28 @@ public class Server extends ExtendedModel { this.limitCommands = limitCommands; } + private boolean disableReports; + + @Override + public boolean getDisableReports() { + return disableReports; + } + + public void setDisableReports(boolean disableReports) { + this.disableReports = disableReports; + } + + private boolean fixedEmail; + + @Override + public boolean getFixedEmail() { + return fixedEmail; + } + + public void setFixedEmail(boolean fixedEmail) { + this.fixedEmail = fixedEmail; + } + private String poiLayer; public String getPoiLayer() { @@ -177,9 +213,52 @@ public class Server extends ExtendedModel { return getClass().getPackage().getImplementationVersion(); } + private boolean emailEnabled; + + @QueryIgnore + public void setEmailEnabled(boolean emailEnabled) { + this.emailEnabled = emailEnabled; + } + @QueryIgnore public Boolean getEmailEnabled() { - return Context.getMailManager().getEmailEnabled(); + return emailEnabled; + } + + private boolean geocoderEnabled; + + @QueryIgnore + public void setGeocoderEnabled(boolean geocoderEnabled) { + this.geocoderEnabled = geocoderEnabled; + } + + @QueryIgnore + public boolean getGeocoderEnabled() { + return geocoderEnabled; + } + + private long[] storageSpace; + + @QueryIgnore + public long[] getStorageSpace() { + return storageSpace; + } + + @QueryIgnore + public void setStorageSpace(long[] storageSpace) { + this.storageSpace = storageSpace; + } + + private boolean newServer; + + @QueryIgnore + public boolean getNewServer() { + return newServer; + } + + @QueryIgnore + public void setNewServer(boolean newServer) { + this.newServer = newServer; } } diff --git a/src/main/java/org/traccar/model/Statistics.java b/src/main/java/org/traccar/model/Statistics.java index a9a117aef..0dc1b98e8 100644 --- a/src/main/java/org/traccar/model/Statistics.java +++ b/src/main/java/org/traccar/model/Statistics.java @@ -15,9 +15,12 @@ */ package org.traccar.model; +import org.traccar.storage.StorageName; + import java.util.Date; import java.util.Map; +@StorageName("tc_statistics") public class Statistics extends ExtendedModel { private Date captureTime; diff --git a/src/main/java/org/traccar/model/User.java b/src/main/java/org/traccar/model/User.java index 976b6aac0..53594fe07 100644 --- a/src/main/java/org/traccar/model/User.java +++ b/src/main/java/org/traccar/model/User.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,14 @@ package org.traccar.model; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.traccar.database.QueryExtended; -import org.traccar.database.QueryIgnore; +import org.traccar.storage.QueryIgnore; import org.traccar.helper.Hashing; +import org.traccar.storage.StorageName; import java.util.Date; -public class User extends ExtendedModel { +@StorageName("tc_users") +public class User extends ExtendedModel implements UserRestrictions, Disableable { private String name; @@ -67,6 +68,7 @@ public class User extends ExtendedModel { private boolean readonly; + @Override public boolean getReadonly() { return readonly; } @@ -77,6 +79,12 @@ public class User extends ExtendedModel { private boolean administrator; + @QueryIgnore + @JsonIgnore + public boolean getManager() { + return userLimit != 0; + } + public boolean getAdministrator() { return administrator; } @@ -147,20 +155,24 @@ public class User extends ExtendedModel { private boolean disabled; + @Override public boolean getDisabled() { return disabled; } + @Override public void setDisabled(boolean disabled) { this.disabled = disabled; } private Date expirationTime; + @Override public Date getExpirationTime() { return expirationTime; } + @Override public void setExpirationTime(Date expirationTime) { this.expirationTime = expirationTime; } @@ -187,6 +199,7 @@ public class User extends ExtendedModel { private boolean deviceReadonly; + @Override public boolean getDeviceReadonly() { return deviceReadonly; } @@ -195,25 +208,9 @@ public class User extends ExtendedModel { this.deviceReadonly = deviceReadonly; } - private String token; - - public String getToken() { - return token; - } - - public void setToken(String token) { - if (token != null && !token.isEmpty()) { - if (!token.matches("^[a-zA-Z0-9-]{16,}$")) { - throw new IllegalArgumentException("Illegal token"); - } - this.token = token; - } else { - this.token = null; - } - } - private boolean limitCommands; + @Override public boolean getLimitCommands() { return limitCommands; } @@ -222,6 +219,28 @@ public class User extends ExtendedModel { this.limitCommands = limitCommands; } + private boolean disableReports; + + @Override + public boolean getDisableReports() { + return disableReports; + } + + public void setDisableReports(boolean disableReports) { + this.disableReports = disableReports; + } + + private boolean fixedEmail; + + @Override + public boolean getFixedEmail() { + return fixedEmail; + } + + public void setFixedEmail(boolean fixedEmail) { + this.fixedEmail = fixedEmail; + } + private String poiLayer; public String getPoiLayer() { @@ -237,6 +256,7 @@ public class User extends ExtendedModel { return null; } + @QueryIgnore public void setPassword(String password) { if (password != null && !password.isEmpty()) { Hashing.HashingResult hashingResult = Hashing.createHash(password); @@ -248,11 +268,12 @@ public class User extends ExtendedModel { private String hashedPassword; @JsonIgnore - @QueryExtended + @QueryIgnore public String getHashedPassword() { return hashedPassword; } + @QueryIgnore public void setHashedPassword(String hashedPassword) { this.hashedPassword = hashedPassword; } @@ -260,11 +281,12 @@ public class User extends ExtendedModel { private String salt; @JsonIgnore - @QueryExtended + @QueryIgnore public String getSalt() { return salt; } + @QueryIgnore public void setSalt(String salt) { this.salt = salt; } diff --git a/src/main/java/org/traccar/model/UserRestrictions.java b/src/main/java/org/traccar/model/UserRestrictions.java new file mode 100644 index 000000000..1fcc5682e --- /dev/null +++ b/src/main/java/org/traccar/model/UserRestrictions.java @@ -0,0 +1,24 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +public interface UserRestrictions { + boolean getReadonly(); + boolean getDeviceReadonly(); + boolean getLimitCommands(); + boolean getDisableReports(); + boolean getFixedEmail(); +} diff --git a/src/main/java/org/traccar/model/WifiAccessPoint.java b/src/main/java/org/traccar/model/WifiAccessPoint.java index 87a77f3c0..e28c1b935 100644 --- a/src/main/java/org/traccar/model/WifiAccessPoint.java +++ b/src/main/java/org/traccar/model/WifiAccessPoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ package org.traccar.model; import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.Objects; + @JsonInclude(JsonInclude.Include.NON_NULL) public class WifiAccessPoint { @@ -63,4 +65,23 @@ public class WifiAccessPoint { this.channel = channel; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + WifiAccessPoint that = (WifiAccessPoint) o; + return Objects.equals(macAddress, that.macAddress) + && Objects.equals(signalStrength, that.signalStrength) + && Objects.equals(channel, that.channel); + } + + @Override + public int hashCode() { + return Objects.hash(macAddress, signalStrength, channel); + } + } diff --git a/src/main/java/org/traccar/notification/EventForwarder.java b/src/main/java/org/traccar/notification/EventForwarder.java deleted file mode 100644 index c908fedbd..000000000 --- a/src/main/java/org/traccar/notification/EventForwarder.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.notification; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.config.Keys; -import org.traccar.model.Device; -import org.traccar.model.Event; -import org.traccar.model.Geofence; -import org.traccar.model.Maintenance; -import org.traccar.model.Position; - -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.Invocation; -import javax.ws.rs.client.InvocationCallback; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -public class EventForwarder { - - private static final Logger LOGGER = LoggerFactory.getLogger(EventForwarder.class); - - private final String url; - private final String header; - - public EventForwarder() { - url = Context.getConfig().getString(Keys.EVENT_FORWARD_URL); - header = Context.getConfig().getString(Keys.EVENT_FORWARD_HEADERS); - } - - private static final String KEY_POSITION = "position"; - private static final String KEY_EVENT = "event"; - private static final String KEY_GEOFENCE = "geofence"; - private static final String KEY_DEVICE = "device"; - private static final String KEY_MAINTENANCE = "maintenance"; - private static final String KEY_USERS = "users"; - - public final void forwardEvent(Event event, Position position, Set<Long> users) { - - Invocation.Builder requestBuilder = Context.getClient().target(url).request(); - - if (header != null && !header.isEmpty()) { - for (String line: header.split("\\r?\\n")) { - String[] values = line.split(":", 2); - requestBuilder.header(values[0].trim(), values[1].trim()); - } - } - - LOGGER.debug("Event forwarding initiated"); - requestBuilder.async().post( - Entity.json(preparePayload(event, position, users)), new InvocationCallback<Object>() { - @Override - public void completed(Object o) { - LOGGER.debug("Event forwarding succeeded"); - } - - @Override - public void failed(Throwable throwable) { - LOGGER.warn("Event forwarding failed", throwable); - } - }); - } - - protected Map<String, Object> preparePayload(Event event, Position position, Set<Long> users) { - Map<String, Object> data = new HashMap<>(); - data.put(KEY_EVENT, event); - if (position != null) { - data.put(KEY_POSITION, position); - } - Device device = Context.getIdentityManager().getById(event.getDeviceId()); - if (device != null) { - data.put(KEY_DEVICE, device); - } - if (event.getGeofenceId() != 0) { - Geofence geofence = Context.getGeofenceManager().getById(event.getGeofenceId()); - if (geofence != null) { - data.put(KEY_GEOFENCE, geofence); - } - } - if (event.getMaintenanceId() != 0) { - Maintenance maintenance = Context.getMaintenancesManager().getById(event.getMaintenanceId()); - if (maintenance != null) { - data.put(KEY_MAINTENANCE, maintenance); - } - } - data.put(KEY_USERS, Context.getUsersManager().getItems(users)); - return data; - } - -} diff --git a/src/main/java/org/traccar/notification/NotificationFormatter.java b/src/main/java/org/traccar/notification/NotificationFormatter.java index 9a6723a71..9ee3b97b6 100644 --- a/src/main/java/org/traccar/notification/NotificationFormatter.java +++ b/src/main/java/org/traccar/notification/NotificationFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,50 +17,59 @@ package org.traccar.notification; import org.apache.velocity.VelocityContext; -import org.traccar.Context; +import org.traccar.helper.model.UserUtil; import org.traccar.model.Device; import org.traccar.model.Event; +import org.traccar.model.Geofence; +import org.traccar.model.Maintenance; import org.traccar.model.Position; +import org.traccar.model.Server; import org.traccar.model.User; -import org.traccar.reports.ReportUtils; +import org.traccar.session.cache.CacheManager; -public final class NotificationFormatter { +import javax.inject.Inject; +import javax.inject.Singleton; - private NotificationFormatter() { +@Singleton +public class NotificationFormatter { + + private final CacheManager cacheManager; + private final TextTemplateFormatter textTemplateFormatter; + + @Inject + public NotificationFormatter( + CacheManager cacheManager, TextTemplateFormatter textTemplateFormatter) { + this.cacheManager = cacheManager; + this.textTemplateFormatter = textTemplateFormatter; } - public static VelocityContext prepareContext(long userId, Event event, Position position) { + public NotificationMessage formatMessage(User user, Event event, Position position, String templatePath) { - User user = Context.getPermissionsManager().getUser(userId); - Device device = Context.getIdentityManager().getById(event.getDeviceId()); + Server server = cacheManager.getServer(); + Device device = cacheManager.getObject(Device.class, event.getDeviceId()); - VelocityContext velocityContext = TextTemplateFormatter.prepareContext(user); + VelocityContext velocityContext = textTemplateFormatter.prepareContext(server, user); velocityContext.put("device", device); velocityContext.put("event", event); if (position != null) { velocityContext.put("position", position); - velocityContext.put("speedUnit", ReportUtils.getSpeedUnit(userId)); - velocityContext.put("distanceUnit", ReportUtils.getDistanceUnit(userId)); - velocityContext.put("volumeUnit", ReportUtils.getVolumeUnit(userId)); + velocityContext.put("speedUnit", UserUtil.getSpeedUnit(server, user)); + velocityContext.put("distanceUnit", UserUtil.getDistanceUnit(server, user)); + velocityContext.put("volumeUnit", UserUtil.getVolumeUnit(server, user)); } if (event.getGeofenceId() != 0) { - velocityContext.put("geofence", Context.getGeofenceManager().getById(event.getGeofenceId())); + velocityContext.put("geofence", cacheManager.getObject(Geofence.class, event.getGeofenceId())); } if (event.getMaintenanceId() != 0) { - velocityContext.put("maintenance", Context.getMaintenancesManager().getById(event.getMaintenanceId())); + velocityContext.put("maintenance", cacheManager.getObject(Maintenance.class, event.getMaintenanceId())); } String driverUniqueId = event.getString(Position.KEY_DRIVER_UNIQUE_ID); if (driverUniqueId != null) { - velocityContext.put("driver", Context.getDriversManager().getDriverByUniqueId(driverUniqueId)); + velocityContext.put("driver", cacheManager.findDriverByUniqueId(device.getId(), driverUniqueId)); } - return velocityContext; - } - - public static NotificationMessage formatMessage(long userId, Event event, Position position, String templatePath) { - VelocityContext velocityContext = prepareContext(userId, event, position); - return TextTemplateFormatter.formatMessage(velocityContext, event.getType(), templatePath); + return textTemplateFormatter.formatMessage(velocityContext, event.getType(), templatePath); } } diff --git a/src/main/java/org/traccar/notification/NotificatorManager.java b/src/main/java/org/traccar/notification/NotificatorManager.java index dfd8cd3d6..1d9f4f423 100644 --- a/src/main/java/org/traccar/notification/NotificatorManager.java +++ b/src/main/java/org/traccar/notification/NotificatorManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,87 +16,71 @@ */ package org.traccar.notification; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - +import com.google.inject.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; +import org.traccar.config.Config; +import org.traccar.config.Keys; import org.traccar.model.Typed; +import org.traccar.notificators.Notificator; import org.traccar.notificators.NotificatorFirebase; import org.traccar.notificators.NotificatorMail; import org.traccar.notificators.NotificatorNull; -import org.traccar.notificators.Notificator; +import org.traccar.notificators.NotificatorPushover; import org.traccar.notificators.NotificatorSms; +import org.traccar.notificators.NotificatorTelegram; import org.traccar.notificators.NotificatorTraccar; import org.traccar.notificators.NotificatorWeb; -import org.traccar.notificators.NotificatorTelegram; -import org.traccar.notificators.NotificatorPushover; -public final class NotificatorManager { +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Singleton +public class NotificatorManager { private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorManager.class); - private static final Notificator NULL_NOTIFICATOR = new NotificatorNull(); + private static final Map<String, Class<? extends Notificator>> NOTIFICATORS_ALL = Map.of( + "web", NotificatorWeb.class, + "mail", NotificatorMail.class, + "sms", NotificatorSms.class, + "firebase", NotificatorFirebase.class, + "traccar", NotificatorTraccar.class, + "telegram", NotificatorTelegram.class, + "pushover", NotificatorPushover.class); - private final Map<String, Notificator> notificators = new HashMap<>(); + private final Injector injector; - public NotificatorManager() { - final String[] types = Context.getConfig().getString("notificator.types", "").split(","); - for (String type : types) { - String defaultNotificator = ""; - switch (type) { - case "web": - defaultNotificator = NotificatorWeb.class.getCanonicalName(); - break; - case "mail": - defaultNotificator = NotificatorMail.class.getCanonicalName(); - break; - case "sms": - defaultNotificator = NotificatorSms.class.getCanonicalName(); - break; - case "firebase": - defaultNotificator = NotificatorFirebase.class.getCanonicalName(); - break; - case "traccar": - defaultNotificator = NotificatorTraccar.class.getCanonicalName(); - break; - case "telegram": - defaultNotificator = NotificatorTelegram.class.getCanonicalName(); - break; - case "pushover": - defaultNotificator = NotificatorPushover.class.getCanonicalName(); - break; - default: - break; - } - final String className = Context.getConfig() - .getString("notificator." + type + ".class", defaultNotificator); - try { - notificators.put(type, (Notificator) Class.forName(className).newInstance()); - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { - LOGGER.warn("Unable to load notificator class for " + type + " " + className + " " + e.getMessage()); - } + private final Set<String> types = new HashSet<>(); + + @Inject + public NotificatorManager(Injector injector, Config config) { + this.injector = injector; + String types = config.getString(Keys.NOTIFICATOR_TYPES); + if (types != null) { + this.types.addAll(Arrays.asList(types.split(","))); } } public Notificator getNotificator(String type) { - final Notificator notificator = notificators.get(type); - if (notificator == null) { - LOGGER.warn("No notificator configured for type : " + type); - return NULL_NOTIFICATOR; + var clazz = NOTIFICATORS_ALL.get(type); + if (clazz != null) { + var notificator = injector.getInstance(clazz); + if (notificator != null) { + return notificator; + } } - return notificator; + LOGGER.warn("Failed to get notificator {}", type); + return new NotificatorNull(); } public Set<Typed> getAllNotificatorTypes() { - Set<Typed> result = new HashSet<>(); - for (String notificator : notificators.keySet()) { - result.add(new Typed(notificator)); - } - return result; + return types.stream().map(Typed::new).collect(Collectors.toUnmodifiableSet()); } } diff --git a/src/main/java/org/traccar/notification/PropertiesProvider.java b/src/main/java/org/traccar/notification/PropertiesProvider.java index f0078feef..91887b5d4 100644 --- a/src/main/java/org/traccar/notification/PropertiesProvider.java +++ b/src/main/java/org/traccar/notification/PropertiesProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.traccar.notification; import org.traccar.config.Config; +import org.traccar.config.ConfigKey; import org.traccar.model.ExtendedModel; public class PropertiesProvider { @@ -32,36 +33,29 @@ public class PropertiesProvider { this.extendedModel = extendedModel; } - public String getString(String key) { + public String getString(ConfigKey<String> key) { if (config != null) { return config.getString(key); } else { - return extendedModel.getString(key); + String result = extendedModel.getString(key.getKey()); + return result != null ? result : key.getDefaultValue(); } } - public String getString(String key, String defaultValue) { - String value = getString(key); - if (value == null) { - value = defaultValue; - } - return value; - } - - public int getInteger(String key, int defaultValue) { + public int getInteger(ConfigKey<Integer> key) { if (config != null) { - return config.getInteger(key, defaultValue); + return config.getInteger(key); } else { - Object result = extendedModel.getAttributes().get(key); + Object result = extendedModel.getAttributes().get(key.getKey()); if (result != null) { return result instanceof String ? Integer.parseInt((String) result) : (Integer) result; } else { - return defaultValue; + return key.getDefaultValue(); } } } - public Boolean getBoolean(String key) { + public Boolean getBoolean(ConfigKey<Boolean> key) { if (config != null) { if (config.hasKey(key)) { return config.getBoolean(key); @@ -69,7 +63,7 @@ public class PropertiesProvider { return null; } } else { - Object result = extendedModel.getAttributes().get(key); + Object result = extendedModel.getAttributes().get(key.getKey()); if (result != null) { return result instanceof String ? Boolean.valueOf((String) result) : (Boolean) result; } else { diff --git a/src/main/java/org/traccar/notification/TextTemplateFormatter.java b/src/main/java/org/traccar/notification/TextTemplateFormatter.java index b7058c824..444f4a7c2 100644 --- a/src/main/java/org/traccar/notification/TextTemplateFormatter.java +++ b/src/main/java/org/traccar/notification/TextTemplateFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,37 +17,56 @@ package org.traccar.notification; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.tools.generic.DateTool; import org.apache.velocity.tools.generic.NumberTool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; +import org.traccar.api.signature.TokenManager; +import org.traccar.helper.model.UserUtil; +import org.traccar.model.Server; import org.traccar.model.User; -import org.traccar.reports.ReportUtils; +import org.traccar.storage.StorageException; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; +import java.security.GeneralSecurityException; import java.util.Locale; -public final class TextTemplateFormatter { +@Singleton +public class TextTemplateFormatter { private static final Logger LOGGER = LoggerFactory.getLogger(TextTemplateFormatter.class); - private TextTemplateFormatter() { + private final VelocityEngine velocityEngine; + private final TokenManager tokenManager; + + @Inject + public TextTemplateFormatter(VelocityEngine velocityEngine, TokenManager tokenManager) { + this.velocityEngine = velocityEngine; + this.tokenManager = tokenManager; } - public static VelocityContext prepareContext(User user) { + public VelocityContext prepareContext(Server server, User user) { VelocityContext velocityContext = new VelocityContext(); if (user != null) { velocityContext.put("user", user); - velocityContext.put("timezone", ReportUtils.getTimezone(user.getId())); + velocityContext.put("timezone", UserUtil.getTimezone(server, user)); + try { + velocityContext.put("token", tokenManager.generateToken(user.getId())); + } catch (IOException | GeneralSecurityException | StorageException e) { + LOGGER.warn("Token generation failed", e); + } } - velocityContext.put("webUrl", Context.getVelocityEngine().getProperty("web.url")); + velocityContext.put("webUrl", velocityEngine.getProperty("web.url")); velocityContext.put("dateTool", new DateTool()); velocityContext.put("numberTool", new NumberTool()); velocityContext.put("locale", Locale.getDefault()); @@ -55,23 +74,23 @@ public final class TextTemplateFormatter { return velocityContext; } - public static Template getTemplate(String name, String path) { + public Template getTemplate(String name, String path) { String templateFilePath; Template template; try { templateFilePath = Paths.get(path, name + ".vm").toString(); - template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); + template = velocityEngine.getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); } catch (ResourceNotFoundException error) { LOGGER.warn("Notification template error", error); templateFilePath = Paths.get(path, "unknown.vm").toString(); - template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); + template = velocityEngine.getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); } return template; } - public static NotificationMessage formatMessage(VelocityContext velocityContext, String name, String templatePath) { + public NotificationMessage formatMessage(VelocityContext velocityContext, String name, String templatePath) { StringWriter writer = new StringWriter(); getTemplate(name, templatePath).merge(velocityContext, writer); return new NotificationMessage((String) velocityContext.get("subject"), writer.toString()); diff --git a/src/main/java/org/traccar/notificators/Notificator.java b/src/main/java/org/traccar/notificators/Notificator.java index dd888bae9..052365c7a 100644 --- a/src/main/java/org/traccar/notificators/Notificator.java +++ b/src/main/java/org/traccar/notificators/Notificator.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,27 +16,13 @@ */ package org.traccar.notificators; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.traccar.model.Event; import org.traccar.model.Position; +import org.traccar.model.User; import org.traccar.notification.MessageException; -public abstract class Notificator { +public interface Notificator { - private static final Logger LOGGER = LoggerFactory.getLogger(Notificator.class); - - public void sendAsync(final long userId, final Event event, final Position position) { - new Thread(() -> { - try { - sendSync(userId, event, position); - } catch (MessageException | InterruptedException error) { - LOGGER.warn("Event send error", error); - } - }).start(); - } - - public abstract void sendSync(long userId, Event event, Position position) - throws MessageException, InterruptedException; + void send(User user, Event event, Position position) throws MessageException, InterruptedException; } diff --git a/src/main/java/org/traccar/notificators/NotificatorFirebase.java b/src/main/java/org/traccar/notificators/NotificatorFirebase.java index f91ec25a0..5ce2cbc0b 100644 --- a/src/main/java/org/traccar/notificators/NotificatorFirebase.java +++ b/src/main/java/org/traccar/notificators/NotificatorFirebase.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,88 +16,91 @@ */ package org.traccar.notificators; -import com.fasterxml.jackson.annotation.JsonProperty; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.messaging.AndroidConfig; +import com.google.firebase.messaging.AndroidNotification; +import com.google.firebase.messaging.ApnsConfig; +import com.google.firebase.messaging.Aps; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.google.firebase.messaging.MulticastMessage; +import com.google.firebase.messaging.Notification; +import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.model.User; -import org.traccar.notification.NotificationMessage; +import org.traccar.notification.MessageException; import org.traccar.notification.NotificationFormatter; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.InvocationCallback; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; -public class NotificatorFirebase extends Notificator { +@Singleton +public class NotificatorFirebase implements Notificator { - private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorFirebase.class); + private final NotificationFormatter notificationFormatter; - private final String url; - private final String key; + @Inject + public NotificatorFirebase(Config config, NotificationFormatter notificationFormatter) throws IOException { - public static class Notification { - @JsonProperty("title") - private String title; - @JsonProperty("body") - private String body; - @JsonProperty("sound") - private String sound; - } + this.notificationFormatter = notificationFormatter; - public static class Message { - @JsonProperty("registration_ids") - private String[] tokens; - @JsonProperty("notification") - private Notification notification; - } + InputStream serviceAccount = new ByteArrayInputStream( + config.getString(Keys.NOTIFICATOR_FIREBASE_SERVICE_ACCOUNT).getBytes()); - public NotificatorFirebase() { - this( - "https://fcm.googleapis.com/fcm/send", - Context.getConfig().getString(Keys.NOTIFICATOR_FIREBASE_KEY)); - } + FirebaseOptions options = FirebaseOptions.builder() + .setCredentials(GoogleCredentials.fromStream(serviceAccount)) + .build(); - protected NotificatorFirebase(String url, String key) { - this.url = url; - this.key = key; + FirebaseApp.initializeApp(options); } @Override - public void sendSync(long userId, Event event, Position position) { - final User user = Context.getPermissionsManager().getUser(userId); - if (user.getAttributes().containsKey("notificationTokens")) { - - NotificationMessage shortMessage = NotificationFormatter.formatMessage(userId, event, position, "short"); - - Notification notification = new Notification(); - notification.title = shortMessage.getSubject(); - notification.body = shortMessage.getBody(); - notification.sound = "default"; - - Message message = new Message(); - message.tokens = user.getString("notificationTokens").split("[, ]"); - message.notification = notification; - - Context.getClient().target(url).request() - .header("Authorization", "key=" + key) - .async().post(Entity.json(message), new InvocationCallback<Object>() { - @Override - public void completed(Object o) { - } - - @Override - public void failed(Throwable throwable) { - LOGGER.warn("Firebase notification error", throwable); + public void send(User user, Event event, Position position) throws MessageException { + if (user.hasAttribute("notificationTokens")) { + + var shortMessage = notificationFormatter.formatMessage(user, event, position, "short"); + + List<String> registrationTokens = Arrays.asList(user.getString("notificationTokens").split("[, ]")); + + MulticastMessage message = MulticastMessage.builder() + .setNotification(Notification.builder() + .setTitle(shortMessage.getSubject()) + .setBody(shortMessage.getBody()) + .build()) + .setAndroidConfig(AndroidConfig.builder() + .setNotification(AndroidNotification.builder() + .setSound("default") + .build()) + .build()) + .setApnsConfig(ApnsConfig.builder() + .setAps(Aps.builder() + .setSound("default") + .build()) + .build()) + .addAllTokens(registrationTokens) + .putData("eventId", String.valueOf(event.getId())) + .build(); + + try { + var result = FirebaseMessaging.getInstance().sendMulticast(message); + for (var response : result.getResponses()) { + if (!response.isSuccessful()) { + throw new MessageException(response.getException()); + } } - }); + } catch (FirebaseMessagingException e) { + throw new MessageException(e); + } } } - @Override - public void sendAsync(long userId, Event event, Position position) { - sendSync(userId, event, position); - } - } diff --git a/src/main/java/org/traccar/notificators/NotificatorMail.java b/src/main/java/org/traccar/notificators/NotificatorMail.java index 9b5637ed8..19fde6756 100644 --- a/src/main/java/org/traccar/notificators/NotificatorMail.java +++ b/src/main/java/org/traccar/notificators/NotificatorMail.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,22 +16,34 @@ */ package org.traccar.notificators; -import org.traccar.Context; +import org.traccar.mail.MailManager; import org.traccar.model.Event; import org.traccar.model.Position; -import org.traccar.notification.NotificationMessage; +import org.traccar.model.User; import org.traccar.notification.MessageException; import org.traccar.notification.NotificationFormatter; +import javax.inject.Inject; +import javax.inject.Singleton; import javax.mail.MessagingException; -public final class NotificatorMail extends Notificator { +@Singleton +public class NotificatorMail implements Notificator { + + private final MailManager mailManager; + private final NotificationFormatter notificationFormatter; + + @Inject + public NotificatorMail(MailManager mailManager, NotificationFormatter notificationFormatter) { + this.mailManager = mailManager; + this.notificationFormatter = notificationFormatter; + } @Override - public void sendSync(long userId, Event event, Position position) throws MessageException { + public void send(User user, Event event, Position position) throws MessageException { try { - NotificationMessage fullMessage = NotificationFormatter.formatMessage(userId, event, position, "full"); - Context.getMailManager().sendMessage(userId, fullMessage.getSubject(), fullMessage.getBody()); + var fullMessage = notificationFormatter.formatMessage(user, event, position, "full"); + mailManager.sendMessage(user, fullMessage.getSubject(), fullMessage.getBody()); } catch (MessagingException e) { throw new MessageException(e); } diff --git a/src/main/java/org/traccar/notificators/NotificatorNull.java b/src/main/java/org/traccar/notificators/NotificatorNull.java index 9364336be..ba9ade9c6 100644 --- a/src/main/java/org/traccar/notificators/NotificatorNull.java +++ b/src/main/java/org/traccar/notificators/NotificatorNull.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,18 +20,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.model.Event; import org.traccar.model.Position; +import org.traccar.model.User; -public final class NotificatorNull extends Notificator { +public class NotificatorNull implements Notificator { private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorNull.class); @Override - public void sendAsync(long userId, Event event, Position position) { - LOGGER.warn("You are using null notificatior, please check your configuration, notification not sent"); - } - - @Override - public void sendSync(long userId, Event event, Position position) { + public void send(User user, Event event, Position position) { LOGGER.warn("You are using null notificatior, please check your configuration, notification not sent"); } diff --git a/src/main/java/org/traccar/notificators/NotificatorPushover.java b/src/main/java/org/traccar/notificators/NotificatorPushover.java index 456c2fe4f..e00db0579 100644 --- a/src/main/java/org/traccar/notificators/NotificatorPushover.java +++ b/src/main/java/org/traccar/notificators/NotificatorPushover.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,22 +16,23 @@ package org.traccar.notificators; import com.fasterxml.jackson.annotation.JsonProperty; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; +import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.model.User; import org.traccar.notification.NotificationFormatter; -import org.traccar.notification.NotificationMessage; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.client.Client; import javax.ws.rs.client.Entity; -import javax.ws.rs.client.InvocationCallback; -public class NotificatorPushover extends Notificator { +@Singleton +public class NotificatorPushover implements Notificator { - private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorPushover.class); + private final NotificationFormatter notificationFormatter; + private final Client client; private final String url; private final String token; @@ -50,58 +51,35 @@ public class NotificatorPushover extends Notificator { private String message; } - public NotificatorPushover() { + @Inject + public NotificatorPushover(Config config, NotificationFormatter notificationFormatter, Client client) { + this.notificationFormatter = notificationFormatter; + this.client = client; url = "https://api.pushover.net/1/messages.json"; - token = Context.getConfig().getString(Keys.NOTIFICATOR_PUSHOVER_TOKEN); - user = Context.getConfig().getString(Keys.NOTIFICATOR_PUSHOVER_USER); + token = config.getString(Keys.NOTIFICATOR_PUSHOVER_TOKEN); + user = config.getString(Keys.NOTIFICATOR_PUSHOVER_USER); } @Override - public void sendSync(long userId, Event event, Position position) { + public void send(User user, Event event, Position position) { + var shortMessage = notificationFormatter.formatMessage(user, event, position, "short"); - final User user = Context.getPermissionsManager().getUser(userId); - - String device = ""; - - if (user.getAttributes().containsKey("notificator.pushover.device")) { - device = user.getString("notificator.pushover.device").replaceAll(" *, *", ","); - } + Message message = new Message(); + message.token = token; - if (token == null) { - LOGGER.warn("Pushover token not found"); - return; + message.user = user.getString("pushoverUserKey"); + if (message.user == null) { + message.user = this.user; } - if (this.user == null) { - LOGGER.warn("Pushover user not found"); - return; + if (user.hasAttribute("pushoverDeviceNames")) { + message.device = user.getString("pushoverDeviceNames").replaceAll(" *, *", ","); } - NotificationMessage shortMessage = NotificationFormatter.formatMessage(userId, event, position, "short"); - - Message message = new Message(); - message.token = token; - message.user = this.user; - message.device = device; message.title = shortMessage.getSubject(); message.message = shortMessage.getBody(); - Context.getClient().target(url).request() - .async().post(Entity.json(message), new InvocationCallback<Object>() { - @Override - public void completed(Object o) { - } - - @Override - public void failed(Throwable throwable) { - LOGGER.warn("Pushover API error", throwable); - } - }); - } - - @Override - public void sendAsync(long userId, Event event, Position position) { - sendSync(userId, event, position); + client.target(url).request().post(Entity.json(message)).close(); } } diff --git a/src/main/java/org/traccar/notificators/NotificatorSms.java b/src/main/java/org/traccar/notificators/NotificatorSms.java index fb817b112..e37d10888 100644 --- a/src/main/java/org/traccar/notificators/NotificatorSms.java +++ b/src/main/java/org/traccar/notificators/NotificatorSms.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,37 +16,38 @@ */ package org.traccar.notificators; -import org.traccar.Context; -import org.traccar.Main; import org.traccar.database.StatisticsManager; import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.model.User; import org.traccar.notification.MessageException; import org.traccar.notification.NotificationFormatter; -import org.traccar.notification.NotificationMessage; +import org.traccar.sms.SmsManager; -public final class NotificatorSms extends Notificator { +import javax.inject.Inject; +import javax.inject.Singleton; - @Override - public void sendAsync(long userId, Event event, Position position) { - final User user = Context.getPermissionsManager().getUser(userId); - if (user.getPhone() != null) { - NotificationMessage shortMessage = NotificationFormatter.formatMessage(userId, event, position, "short"); - Main.getInjector().getInstance(StatisticsManager.class).registerSms(); - Context.getSmsManager().sendMessageAsync(user.getPhone(), - shortMessage.getBody(), false); - } +@Singleton +public class NotificatorSms implements Notificator { + + private final SmsManager smsManager; + private final NotificationFormatter notificationFormatter; + private final StatisticsManager statisticsManager; + + @Inject + public NotificatorSms( + SmsManager smsManager, NotificationFormatter notificationFormatter, StatisticsManager statisticsManager) { + this.smsManager = smsManager; + this.notificationFormatter = notificationFormatter; + this.statisticsManager = statisticsManager; } @Override - public void sendSync(long userId, Event event, Position position) throws MessageException, InterruptedException { - final User user = Context.getPermissionsManager().getUser(userId); + public void send(User user, Event event, Position position) throws MessageException, InterruptedException { if (user.getPhone() != null) { - NotificationMessage shortMessage = NotificationFormatter.formatMessage(userId, event, position, "short"); - Main.getInjector().getInstance(StatisticsManager.class).registerSms(); - Context.getSmsManager().sendMessageSync(user.getPhone(), - shortMessage.getBody(), false); + var shortMessage = notificationFormatter.formatMessage(user, event, position, "short"); + statisticsManager.registerSms(); + smsManager.sendMessage(user.getPhone(), shortMessage.getBody(), false); } } diff --git a/src/main/java/org/traccar/notificators/NotificatorTelegram.java b/src/main/java/org/traccar/notificators/NotificatorTelegram.java index 70148110c..38e87c222 100644 --- a/src/main/java/org/traccar/notificators/NotificatorTelegram.java +++ b/src/main/java/org/traccar/notificators/NotificatorTelegram.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2021 Rafael Miquelino (rafaelmiquelino@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,22 +17,23 @@ package org.traccar.notificators; import com.fasterxml.jackson.annotation.JsonProperty; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.model.User; +import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.model.Event; import org.traccar.model.Position; +import org.traccar.model.User; import org.traccar.notification.NotificationFormatter; -import org.traccar.notification.NotificationMessage; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.client.Client; import javax.ws.rs.client.Entity; -import javax.ws.rs.client.InvocationCallback; -public class NotificatorTelegram extends Notificator { +@Singleton +public class NotificatorTelegram implements Notificator { - private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorTelegram.class); + private final NotificationFormatter notificationFormatter; + private final Client client; private final String urlSendText; private final String urlSendLocation; @@ -61,29 +62,16 @@ public class NotificatorTelegram extends Notificator { private int bearing; } - public NotificatorTelegram() { + @Inject + public NotificatorTelegram(Config config, NotificationFormatter notificationFormatter, Client client) { + this.notificationFormatter = notificationFormatter; + this.client = client; urlSendText = String.format( - "https://api.telegram.org/bot%s/sendMessage", - Context.getConfig().getString(Keys.NOTIFICATOR_TELEGRAM_KEY)); + "https://api.telegram.org/bot%s/sendMessage", config.getString(Keys.NOTIFICATOR_TELEGRAM_KEY)); urlSendLocation = String.format( - "https://api.telegram.org/bot%s/sendLocation", - Context.getConfig().getString(Keys.NOTIFICATOR_TELEGRAM_KEY)); - chatId = Context.getConfig().getString(Keys.NOTIFICATOR_TELEGRAM_CHAT_ID); - sendLocation = Context.getConfig().getBoolean(Keys.NOTIFICATOR_TELEGRAM_SEND_LOCATION); - } - - private void executeRequest(String url, Object message) { - Context.getClient().target(url).request() - .async().post(Entity.json(message), new InvocationCallback<Object>() { - @Override - public void completed(Object o) { - } - - @Override - public void failed(Throwable throwable) { - LOGGER.warn("Telegram API error", throwable); - } - }); + "https://api.telegram.org/bot%s/sendLocation", config.getString(Keys.NOTIFICATOR_TELEGRAM_KEY)); + chatId = config.getString(Keys.NOTIFICATOR_TELEGRAM_CHAT_ID); + sendLocation = config.getBoolean(Keys.NOTIFICATOR_TELEGRAM_SEND_LOCATION); } private LocationMessage createLocationMessage(String messageChatId, Position position) { @@ -97,9 +85,8 @@ public class NotificatorTelegram extends Notificator { } @Override - public void sendSync(long userId, Event event, Position position) { - User user = Context.getPermissionsManager().getUser(userId); - NotificationMessage shortMessage = NotificationFormatter.formatMessage(userId, event, position, "short"); + public void send(User user, Event event, Position position) { + var shortMessage = notificationFormatter.formatMessage(user, event, position, "short"); TextMessage message = new TextMessage(); message.chatId = user.getString("telegramChatId"); @@ -107,15 +94,11 @@ public class NotificatorTelegram extends Notificator { message.chatId = chatId; } message.text = shortMessage.getBody(); - executeRequest(urlSendText, message); + client.target(urlSendText).request().post(Entity.json(message)).close(); if (sendLocation && position != null) { - executeRequest(urlSendLocation, createLocationMessage(message.chatId, position)); + client.target(urlSendLocation).request().post( + Entity.json(createLocationMessage(message.chatId, position))).close(); } } - @Override - public void sendAsync(long userId, Event event, Position position) { - sendSync(userId, event, position); - } - } diff --git a/src/main/java/org/traccar/notificators/NotificatorTraccar.java b/src/main/java/org/traccar/notificators/NotificatorTraccar.java index 5bcd18b5e..9ae39f975 100644 --- a/src/main/java/org/traccar/notificators/NotificatorTraccar.java +++ b/src/main/java/org/traccar/notificators/NotificatorTraccar.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,69 @@ */ package org.traccar.notificators; -import org.traccar.Context; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.traccar.config.Config; import org.traccar.config.Keys; +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.model.User; +import org.traccar.notification.NotificationFormatter; -public class NotificatorTraccar extends NotificatorFirebase { +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Entity; - public NotificatorTraccar() { - super( - "https://www.traccar.org/push/", - Context.getConfig().getString(Keys.NOTIFICATOR_TRACCAR_KEY)); +@Singleton +public class NotificatorTraccar implements Notificator { + + private final NotificationFormatter notificationFormatter; + private final Client client; + + private final String url; + private final String key; + + public static class Notification { + @JsonProperty("title") + private String title; + @JsonProperty("body") + private String body; + @JsonProperty("sound") + private String sound; + } + + public static class Message { + @JsonProperty("registration_ids") + private String[] tokens; + @JsonProperty("notification") + private Notification notification; + } + + @Inject + public NotificatorTraccar(Config config, NotificationFormatter notificationFormatter, Client client) { + this.notificationFormatter = notificationFormatter; + this.client = client; + this.url = "https://www.traccar.org/push/"; + this.key = config.getString(Keys.NOTIFICATOR_TRACCAR_KEY); + } + + @Override + public void send(User user, Event event, Position position) { + if (user.hasAttribute("notificationTokens")) { + + var shortMessage = notificationFormatter.formatMessage(user, event, position, "short"); + + Notification notification = new Notification(); + notification.title = shortMessage.getSubject(); + notification.body = shortMessage.getBody(); + notification.sound = "default"; + + Message message = new Message(); + message.tokens = user.getString("notificationTokens").split("[, ]"); + message.notification = notification; + + client.target(url).request().header("Authorization", "key=" + key).post(Entity.json(message)).close(); + } } } diff --git a/src/main/java/org/traccar/notificators/NotificatorWeb.java b/src/main/java/org/traccar/notificators/NotificatorWeb.java index 1d11c0b46..deabeade1 100644 --- a/src/main/java/org/traccar/notificators/NotificatorWeb.java +++ b/src/main/java/org/traccar/notificators/NotificatorWeb.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,15 +16,45 @@ */ package org.traccar.notificators; -import org.traccar.Context; import org.traccar.model.Event; import org.traccar.model.Position; +import org.traccar.model.User; +import org.traccar.notification.NotificationFormatter; +import org.traccar.session.ConnectionManager; -public final class NotificatorWeb extends Notificator { +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +public final class NotificatorWeb implements Notificator { + + private final ConnectionManager connectionManager; + private final NotificationFormatter notificationFormatter; + + @Inject + public NotificatorWeb( + ConnectionManager connectionManager, NotificationFormatter notificationFormatter) { + this.connectionManager = connectionManager; + this.notificationFormatter = notificationFormatter; + } @Override - public void sendSync(long userId, Event event, Position position) { - Context.getConnectionManager().updateEvent(userId, event); + public void send(User user, Event event, Position position) { + + Event copy = new Event(); + copy.setId(event.getId()); + copy.setDeviceId(event.getDeviceId()); + copy.setType(event.getType()); + copy.setEventTime(event.getEventTime()); + copy.setPositionId(event.getPositionId()); + copy.setGeofenceId(event.getGeofenceId()); + copy.setMaintenanceId(event.getMaintenanceId()); + copy.getAttributes().putAll(event.getAttributes()); + + var message = notificationFormatter.formatMessage(user, event, position, "short"); + copy.set("message", message.getBody()); + + connectionManager.updateEvent(true, user.getId(), copy); } } diff --git a/src/main/java/org/traccar/protocol/AdmProtocol.java b/src/main/java/org/traccar/protocol/AdmProtocol.java index d1d81118c..bab1d2339 100644 --- a/src/main/java/org/traccar/protocol/AdmProtocol.java +++ b/src/main/java/org/traccar/protocol/AdmProtocol.java @@ -19,17 +19,21 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class AdmProtocol extends BaseProtocol { - public AdmProtocol() { + @Inject + public AdmProtocol(Config config) { setSupportedDataCommands( Command.TYPE_GET_DEVICE_STATUS, Command.TYPE_CUSTOM); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new AdmFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new AdmProtocolEncoder(AdmProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java b/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java index 7e3478704..1f940f7e2 100644 --- a/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.UnitsConverter; diff --git a/src/main/java/org/traccar/protocol/AisProtocol.java b/src/main/java/org/traccar/protocol/AisProtocol.java index 3b9cad7c8..bc975c277 100644 --- a/src/main/java/org/traccar/protocol/AisProtocol.java +++ b/src/main/java/org/traccar/protocol/AisProtocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.string.StringDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class AisProtocol extends BaseProtocol { - public AisProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public AisProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringDecoder()); pipeline.addLast(new AisProtocolDecoder(AisProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/AisProtocolDecoder.java b/src/main/java/org/traccar/protocol/AisProtocolDecoder.java index 8970f3d4a..a434e6e33 100644 --- a/src/main/java/org/traccar/protocol/AisProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AisProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitBuffer; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/AlematicsProtocol.java b/src/main/java/org/traccar/protocol/AlematicsProtocol.java index 8da2356b9..b85b44382 100644 --- a/src/main/java/org/traccar/protocol/AlematicsProtocol.java +++ b/src/main/java/org/traccar/protocol/AlematicsProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class AlematicsProtocol extends BaseProtocol { - public AlematicsProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public AlematicsProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new AlematicsFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/AlematicsProtocolDecoder.java b/src/main/java/org/traccar/protocol/AlematicsProtocolDecoder.java index 25ccf6856..981437191 100644 --- a/src/main/java/org/traccar/protocol/AlematicsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AlematicsProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/AnytrekProtocol.java b/src/main/java/org/traccar/protocol/AnytrekProtocol.java index 9bd0c9163..b0e974c69 100644 --- a/src/main/java/org/traccar/protocol/AnytrekProtocol.java +++ b/src/main/java/org/traccar/protocol/AnytrekProtocol.java @@ -19,15 +19,19 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class AnytrekProtocol extends BaseProtocol { - public AnytrekProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public AnytrekProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 2, 2, 0, true)); pipeline.addLast(new AnytrekProtocolDecoder(AnytrekProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/AnytrekProtocolDecoder.java b/src/main/java/org/traccar/protocol/AnytrekProtocolDecoder.java index c48f59c90..0f9c2b17a 100644 --- a/src/main/java/org/traccar/protocol/AnytrekProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AnytrekProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/ApelProtocol.java b/src/main/java/org/traccar/protocol/ApelProtocol.java index 382aa16af..f1d6e659c 100644 --- a/src/main/java/org/traccar/protocol/ApelProtocol.java +++ b/src/main/java/org/traccar/protocol/ApelProtocol.java @@ -19,14 +19,18 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class ApelProtocol extends BaseProtocol { - public ApelProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public ApelProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 2, 4, 0, true)); pipeline.addLast(new ApelProtocolDecoder(ApelProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/ApelProtocolDecoder.java b/src/main/java/org/traccar/protocol/ApelProtocolDecoder.java index c95a0366a..97ed7de96 100644 --- a/src/main/java/org/traccar/protocol/ApelProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ApelProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Checksum; diff --git a/src/main/java/org/traccar/protocol/AplicomProtocol.java b/src/main/java/org/traccar/protocol/AplicomProtocol.java index 2b9dbf97c..47bb780cb 100644 --- a/src/main/java/org/traccar/protocol/AplicomProtocol.java +++ b/src/main/java/org/traccar/protocol/AplicomProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class AplicomProtocol extends BaseProtocol { - public AplicomProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public AplicomProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new AplicomFrameDecoder()); pipeline.addLast(new AplicomProtocolDecoder(AplicomProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/AplicomProtocolDecoder.java b/src/main/java/org/traccar/protocol/AplicomProtocolDecoder.java index f11312428..0cd8ca37e 100644 --- a/src/main/java/org/traccar/protocol/AplicomProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AplicomProtocolDecoder.java @@ -21,8 +21,7 @@ import io.netty.channel.Channel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.config.Keys; import org.traccar.helper.Checksum; @@ -303,7 +302,7 @@ public class AplicomProtocolDecoder extends BaseProtocolDecoder { decodeEventData(position, buf, event); } - if (Context.getConfig().getBoolean(Keys.PROTOCOL_CAN.withPrefix(getProtocolName())) + if (getConfig().getBoolean(Keys.PROTOCOL_CAN.withPrefix(getProtocolName())) && buf.isReadable() && (selector & 0x1000) != 0 && event == EVENT_DATA) { decodeCanData(buf, position); } diff --git a/src/main/java/org/traccar/protocol/AppelloProtocol.java b/src/main/java/org/traccar/protocol/AppelloProtocol.java index 1ca4168e4..25b2bf3b8 100644 --- a/src/main/java/org/traccar/protocol/AppelloProtocol.java +++ b/src/main/java/org/traccar/protocol/AppelloProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class AppelloProtocol extends BaseProtocol { - public AppelloProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public AppelloProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/AppelloProtocolDecoder.java b/src/main/java/org/traccar/protocol/AppelloProtocolDecoder.java index 47e329234..8e182b9fb 100644 --- a/src/main/java/org/traccar/protocol/AppelloProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AppelloProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/AquilaProtocol.java b/src/main/java/org/traccar/protocol/AquilaProtocol.java index 5ca1ec091..6080df33d 100644 --- a/src/main/java/org/traccar/protocol/AquilaProtocol.java +++ b/src/main/java/org/traccar/protocol/AquilaProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class AquilaProtocol extends BaseProtocol { - public AquilaProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public AquilaProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/AquilaProtocolDecoder.java b/src/main/java/org/traccar/protocol/AquilaProtocolDecoder.java index 3c43ddf2a..50ff10469 100644 --- a/src/main/java/org/traccar/protocol/AquilaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AquilaProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/Ardi01Protocol.java b/src/main/java/org/traccar/protocol/Ardi01Protocol.java index f7826430f..b33c2817f 100644 --- a/src/main/java/org/traccar/protocol/Ardi01Protocol.java +++ b/src/main/java/org/traccar/protocol/Ardi01Protocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Ardi01Protocol extends BaseProtocol { - public Ardi01Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Ardi01Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/Ardi01ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Ardi01ProtocolDecoder.java index 85e9ecfde..07653623a 100644 --- a/src/main/java/org/traccar/protocol/Ardi01ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Ardi01ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/ArknavProtocol.java b/src/main/java/org/traccar/protocol/ArknavProtocol.java index 3b485e4a5..4f443aa3a 100644 --- a/src/main/java/org/traccar/protocol/ArknavProtocol.java +++ b/src/main/java/org/traccar/protocol/ArknavProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,19 +21,32 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class ArknavProtocol extends BaseProtocol { - public ArknavProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public ArknavProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '\r')); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new ArknavProtocolDecoder(ArknavProtocol.this)); } }); + addServer(new TrackerServer(config, getName(), true) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new ArknavProtocolDecoder(ArknavProtocol.this)); + } + }); + } } diff --git a/src/main/java/org/traccar/protocol/ArknavProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArknavProtocolDecoder.java index 4982e02fc..4def9c979 100644 --- a/src/main/java/org/traccar/protocol/ArknavProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ArknavProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/ArknavX8Protocol.java b/src/main/java/org/traccar/protocol/ArknavX8Protocol.java index a29bc1ad3..39c6e8009 100644 --- a/src/main/java/org/traccar/protocol/ArknavX8Protocol.java +++ b/src/main/java/org/traccar/protocol/ArknavX8Protocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class ArknavX8Protocol extends BaseProtocol { - public ArknavX8Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public ArknavX8Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ';')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/ArknavX8ProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArknavX8ProtocolDecoder.java index b570f5423..22c0344d6 100644 --- a/src/main/java/org/traccar/protocol/ArknavX8ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ArknavX8ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/ArmoliProtocol.java b/src/main/java/org/traccar/protocol/ArmoliProtocol.java new file mode 100644 index 000000000..32fba3b52 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArmoliProtocol.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.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; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class ArmoliProtocol extends BaseProtocol { + + @Inject + public ArmoliProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ";;", ";\r", ";")); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new ArmoliProtocolDecoder(ArmoliProtocol.this)); + pipeline.addLast(new ArmoliProtocolPoller(ArmoliProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ArmoliProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArmoliProtocolDecoder.java new file mode 100644 index 000000000..cbed64f76 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArmoliProtocolDecoder.java @@ -0,0 +1,151 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.session.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.ObdDecoder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class ArmoliProtocolDecoder extends BaseProtocolDecoder { + + public ArmoliProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("[M") // start + .number("(d{15})") // imei + .number("(dd)(dd)(dd)") // date (ddmmyy) + .number("(dd)(dd)(dd)") // time (hhmmss) + .number("([NS])(dd.d{6})") // latitude + .number("([EW])(ddd.d{6})") // longitude + .number("(d)") // valid + .number("(x)") // satellites + .number("(xx)") // speed + .number("(ddd)") // course + .number("(xxx)") // adc 1 + .number("(xxx)") // adc 2 + .number("(xx)") // status + .number("(xx)") // max speed + .number("(x{6})") // distance + .number("(dd)?") // hdop + .number("x{4}") // idle + .number(":(x+)").optional() // alarms + .number("G(x{6})").optional() // g-sensor + .number("H(x{3})").optional() // power + .number("E(x{3})").optional() // battery + .number("!(x+)").optional() // driver + .expression("@A([>0-9A-F]+)").optional() // obd + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + char type = sentence.charAt(1); + + Position position = new Position(getProtocolName()); + DeviceSession deviceSession; + + if (type != 'M') { + if (type == 'W') { + deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession != null) { + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, null); + position.set( + Position.KEY_RESULT, + sentence.substring(sentence.indexOf(',') + 1, sentence.length() - 1)); + return position; + } + } else if (channel != null && (type == 'Q' || type == 'L')) { + channel.writeAndFlush(new NetworkMessage("[TX,];;", remoteAddress)); + } + return null; + } + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setValid(parser.nextInt() > 0); + + position.set(Position.KEY_SATELLITES, parser.nextHexInt()); + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextHexInt())); + position.setCourse(parser.nextInt()); + + position.set(Position.PREFIX_ADC + 1, parser.nextHexInt() / 27.0 * 1000); + position.set(Position.PREFIX_ADC + 1, parser.nextHexInt() / 27.0 * 1000); + position.set(Position.KEY_STATUS, parser.nextHexInt()); + position.set("maxSpeed", parser.nextHexInt()); + position.set(Position.KEY_ODOMETER, parser.nextHexInt()); + + if (parser.hasNext()) { + position.set(Position.KEY_HDOP, parser.nextInt() * 0.1); + } + if (parser.hasNext()) { + position.set("alarms", parser.next()); + } + if (parser.hasNext()) { + position.set(Position.KEY_G_SENSOR, parser.next()); + } + if (parser.hasNext()) { + position.set(Position.KEY_POWER, parser.nextHexInt() * 0.01); + } + if (parser.hasNext()) { + position.set(Position.KEY_BATTERY, parser.nextHexInt() * 0.01); + } + if (parser.hasNext()) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + } + if (parser.hasNext()) { + String[] values = parser.next().split(">"); + for (int i = 1; i < values.length; i++) { + String value = values[i]; + position.add(ObdDecoder.decodeData( + Integer.parseInt(value.substring(4, 6), 16), + Long.parseLong(value.substring(6), 16), true)); + } + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/DeviceSession.java b/src/main/java/org/traccar/protocol/ArmoliProtocolPoller.java index 322381807..f7bb9f593 100644 --- a/src/main/java/org/traccar/DeviceSession.java +++ b/src/main/java/org/traccar/protocol/ArmoliProtocolPoller.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,30 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar; +package org.traccar.protocol; -import java.util.TimeZone; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolPoller; +import org.traccar.Protocol; -public class DeviceSession { +import java.net.SocketAddress; - private final long deviceId; +public class ArmoliProtocolPoller extends BaseProtocolPoller { - public DeviceSession(long deviceId) { - this.deviceId = deviceId; + public ArmoliProtocolPoller(Protocol protocol) { + super(180000); } - public long getDeviceId() { - return deviceId; - } - - private TimeZone timeZone; - - public void setTimeZone(TimeZone timeZone) { - this.timeZone = timeZone; - } - - public TimeZone getTimeZone() { - return timeZone; + @Override + protected void sendRequest(Channel channel, SocketAddress remoteAddress) { + channel.writeAndFlush("[TX,];;"); } } diff --git a/src/main/java/org/traccar/protocol/ArnaviBinaryProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArnaviBinaryProtocolDecoder.java index e957a6911..0f6b7a33f 100644 --- a/src/main/java/org/traccar/protocol/ArnaviBinaryProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ArnaviBinaryProtocolDecoder.java @@ -20,7 +20,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Checksum; diff --git a/src/main/java/org/traccar/protocol/ArnaviProtocol.java b/src/main/java/org/traccar/protocol/ArnaviProtocol.java index aecb42c8c..091d5c06f 100644 --- a/src/main/java/org/traccar/protocol/ArnaviProtocol.java +++ b/src/main/java/org/traccar/protocol/ArnaviProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class ArnaviProtocol extends BaseProtocol { - public ArnaviProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public ArnaviProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new ArnaviFrameDecoder()); pipeline.addLast(new ArnaviProtocolDecoder(ArnaviProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java index 68a70c944..361eeeef2 100644 --- a/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,13 @@ */ package org.traccar.protocol; +import com.google.inject.Injector; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; import org.traccar.Protocol; +import javax.inject.Inject; import java.net.SocketAddress; public class ArnaviProtocolDecoder extends BaseProtocolDecoder { @@ -33,6 +35,12 @@ public class ArnaviProtocolDecoder extends BaseProtocolDecoder { binaryProtocolDecoder = new ArnaviBinaryProtocolDecoder(protocol); } + @Inject + public void setInjector(Injector injector) { + injector.injectMembers(textProtocolDecoder); + injector.injectMembers(binaryProtocolDecoder); + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { diff --git a/src/main/java/org/traccar/protocol/ArnaviTextProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArnaviTextProtocolDecoder.java index b99869e6e..9d82c9ad5 100644 --- a/src/main/java/org/traccar/protocol/ArnaviTextProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ArnaviTextProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/AstraProtocol.java b/src/main/java/org/traccar/protocol/AstraProtocol.java index 12b0dfb68..021a81e07 100644 --- a/src/main/java/org/traccar/protocol/AstraProtocol.java +++ b/src/main/java/org/traccar/protocol/AstraProtocol.java @@ -19,20 +19,24 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class AstraProtocol extends BaseProtocol { - public AstraProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public AstraProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 1, 2, -3, 0)); pipeline.addLast(new AstraProtocolDecoder(AstraProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new AstraProtocolDecoder(AstraProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/AstraProtocolDecoder.java b/src/main/java/org/traccar/protocol/AstraProtocolDecoder.java index e6f546b9f..366bf9e8b 100644 --- a/src/main/java/org/traccar/protocol/AstraProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AstraProtocolDecoder.java @@ -21,7 +21,7 @@ import io.netty.channel.Channel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/At2000Protocol.java b/src/main/java/org/traccar/protocol/At2000Protocol.java index 5894f3eab..25e9be86f 100644 --- a/src/main/java/org/traccar/protocol/At2000Protocol.java +++ b/src/main/java/org/traccar/protocol/At2000Protocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class At2000Protocol extends BaseProtocol { - public At2000Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public At2000Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new At2000FrameDecoder()); pipeline.addLast(new At2000ProtocolDecoder(At2000Protocol.this)); } diff --git a/src/main/java/org/traccar/protocol/At2000ProtocolDecoder.java b/src/main/java/org/traccar/protocol/At2000ProtocolDecoder.java index 43798eb67..b81ba306d 100644 --- a/src/main/java/org/traccar/protocol/At2000ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/At2000ProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DataConverter; diff --git a/src/main/java/org/traccar/protocol/AtrackProtocol.java b/src/main/java/org/traccar/protocol/AtrackProtocol.java index 429708b26..21eb09696 100644 --- a/src/main/java/org/traccar/protocol/AtrackProtocol.java +++ b/src/main/java/org/traccar/protocol/AtrackProtocol.java @@ -18,24 +18,28 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class AtrackProtocol extends BaseProtocol { - public AtrackProtocol() { + @Inject + public AtrackProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new AtrackFrameDecoder()); pipeline.addLast(new AtrackProtocolEncoder(AtrackProtocol.this)); pipeline.addLast(new AtrackProtocolDecoder(AtrackProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new AtrackProtocolEncoder(AtrackProtocol.this)); pipeline.addLast(new AtrackProtocolDecoder(AtrackProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java index 186b81470..340641729 100644 --- a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java @@ -20,8 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.config.Keys; @@ -54,7 +53,7 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { private static final int MIN_DATA_LENGTH = 40; private boolean longDate; - private final boolean decimalFuel; + private boolean decimalFuel; private boolean custom; private String form; @@ -64,17 +63,20 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { public AtrackProtocolDecoder(Protocol protocol) { super(protocol); + } - longDate = Context.getConfig().getBoolean(Keys.PROTOCOL_LONG_DATE.withPrefix(getProtocolName())); - decimalFuel = Context.getConfig().getBoolean(Keys.PROTOCOL_DECIMAL_FUEL.withPrefix(getProtocolName())); + @Override + protected void init() { + longDate = getConfig().getBoolean(Keys.PROTOCOL_LONG_DATE.withPrefix(getProtocolName())); + decimalFuel = getConfig().getBoolean(Keys.PROTOCOL_DECIMAL_FUEL.withPrefix(getProtocolName())); - custom = Context.getConfig().getBoolean(Keys.PROTOCOL_CUSTOM.withPrefix(getProtocolName())); - form = Context.getConfig().getString(Keys.PROTOCOL_FORM.withPrefix(getProtocolName())); + custom = getConfig().getBoolean(Keys.PROTOCOL_CUSTOM.withPrefix(getProtocolName())); + form = getConfig().getString(Keys.PROTOCOL_FORM.withPrefix(getProtocolName())); if (form != null) { custom = true; } - String alarmMapString = Context.getConfig().getString(Keys.PROTOCOL_ALARM_MAP.withPrefix(getProtocolName())); + String alarmMapString = getConfig().getString(Keys.PROTOCOL_ALARM_MAP.withPrefix(getProtocolName())); if (alarmMapString != null) { for (String pair : alarmMapString.split(",")) { if (!pair.isEmpty()) { @@ -98,7 +100,7 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { this.form = form; } - private static void sendResponse(Channel channel, SocketAddress remoteAddress, long rawId, int index) { + private void sendResponse(Channel channel, SocketAddress remoteAddress, long rawId, int index) { if (channel != null) { ByteBuf response = Unpooled.buffer(12); response.writeShort(0xfe02); @@ -524,20 +526,24 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { private List<Position> decodeText(Channel channel, SocketAddress remoteAddress, String sentence) { - int startIndex = -1; - for (int i = 0; i < 4; i++) { - startIndex = sentence.indexOf(',', startIndex + 1); + int positionIndex = -1; + for (int i = 0; i < 5; i++) { + positionIndex = sentence.indexOf(',', positionIndex + 1); } - int endIndex = sentence.indexOf(',', startIndex + 1); - String imei = sentence.substring(startIndex + 1, endIndex); - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + String[] headers = sentence.substring(0, positionIndex).split(","); + long id = Long.parseLong(headers[2]); + int index = Integer.parseInt(headers[3]); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, headers[4]); if (deviceSession == null) { return null; } + sendResponse(channel, remoteAddress, id, index); + List<Position> positions = new LinkedList<>(); - String[] lines = sentence.substring(endIndex + 1).split("\r\n"); + String[] lines = sentence.substring(positionIndex + 1).split("\r\n"); for (String line : lines) { Position position = decodeTextLine(deviceSession, line); @@ -626,7 +632,7 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { getLastLocation(position, new Date(time * 1000)); - position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(String.valueOf(id), photo, "jpg")); + position.set(Position.KEY_IMAGE, writeMediaFile(String.valueOf(id), photo, "jpg")); photo.release(); photo = null; diff --git a/src/main/java/org/traccar/protocol/AuroProtocol.java b/src/main/java/org/traccar/protocol/AuroProtocol.java index b8ebdaa75..d37884c8b 100644 --- a/src/main/java/org/traccar/protocol/AuroProtocol.java +++ b/src/main/java/org/traccar/protocol/AuroProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class AuroProtocol extends BaseProtocol { - public AuroProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public AuroProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/AuroProtocolDecoder.java b/src/main/java/org/traccar/protocol/AuroProtocolDecoder.java index d7916147b..4489cf27e 100644 --- a/src/main/java/org/traccar/protocol/AuroProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AuroProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/AustinNbProtocol.java b/src/main/java/org/traccar/protocol/AustinNbProtocol.java index 32bfc0aae..6a68467e2 100644 --- a/src/main/java/org/traccar/protocol/AustinNbProtocol.java +++ b/src/main/java/org/traccar/protocol/AustinNbProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class AustinNbProtocol extends BaseProtocol { - public AustinNbProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public AustinNbProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new AustinNbProtocolDecoder(AustinNbProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/AustinNbProtocolDecoder.java b/src/main/java/org/traccar/protocol/AustinNbProtocolDecoder.java index dc6f3d280..b07b94e20 100644 --- a/src/main/java/org/traccar/protocol/AustinNbProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AustinNbProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,13 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; import org.traccar.model.Position; import java.net.SocketAddress; -import java.util.TimeZone; import java.util.regex.Pattern; public class AustinNbProtocolDecoder extends BaseProtocolDecoder { @@ -64,7 +63,7 @@ public class AustinNbProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - position.setTime(parser.nextDateTime(Parser.DateTimeFormat.YMD_HMS, TimeZone.getDefault().getID())); + position.setTime(parser.nextDateTime()); position.setValid(true); position.setLatitude(Double.parseDouble(parser.next().replace(',', '.'))); diff --git a/src/main/java/org/traccar/protocol/AutoFonProtocol.java b/src/main/java/org/traccar/protocol/AutoFonProtocol.java index 08b5edc7d..0566b1da6 100644 --- a/src/main/java/org/traccar/protocol/AutoFonProtocol.java +++ b/src/main/java/org/traccar/protocol/AutoFonProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class AutoFonProtocol extends BaseProtocol { - public AutoFonProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public AutoFonProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new AutoFonFrameDecoder()); pipeline.addLast(new AutoFonProtocolDecoder(AutoFonProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/AutoFonProtocolDecoder.java b/src/main/java/org/traccar/protocol/AutoFonProtocolDecoder.java index aa05ca2d7..dd6a0e33c 100644 --- a/src/main/java/org/traccar/protocol/AutoFonProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AutoFonProtocolDecoder.java @@ -21,7 +21,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/AutoGradeProtocol.java b/src/main/java/org/traccar/protocol/AutoGradeProtocol.java index c6dbb681e..bc80e473a 100644 --- a/src/main/java/org/traccar/protocol/AutoGradeProtocol.java +++ b/src/main/java/org/traccar/protocol/AutoGradeProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class AutoGradeProtocol extends BaseProtocol { - public AutoGradeProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public AutoGradeProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ')')); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/AutoGradeProtocolDecoder.java b/src/main/java/org/traccar/protocol/AutoGradeProtocolDecoder.java index 5052450b5..f52ac81c9 100644 --- a/src/main/java/org/traccar/protocol/AutoGradeProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AutoGradeProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/AutoTrackProtocol.java b/src/main/java/org/traccar/protocol/AutoTrackProtocol.java index 6aa7558bf..80255d3e9 100644 --- a/src/main/java/org/traccar/protocol/AutoTrackProtocol.java +++ b/src/main/java/org/traccar/protocol/AutoTrackProtocol.java @@ -19,14 +19,18 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class AutoTrackProtocol extends BaseProtocol { - public AutoTrackProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public AutoTrackProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 5, 2, 2, 0, true)); pipeline.addLast(new AutoTrackProtocolDecoder(AutoTrackProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/AutoTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/AutoTrackProtocolDecoder.java index da7f6b5a6..c072e55d0 100644 --- a/src/main/java/org/traccar/protocol/AutoTrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AutoTrackProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Checksum; diff --git a/src/main/java/org/traccar/protocol/AvemaProtocol.java b/src/main/java/org/traccar/protocol/AvemaProtocol.java index dbfab4dea..b35a447ff 100644 --- a/src/main/java/org/traccar/protocol/AvemaProtocol.java +++ b/src/main/java/org/traccar/protocol/AvemaProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class AvemaProtocol extends BaseProtocol { - public AvemaProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public AvemaProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/AvemaProtocolDecoder.java b/src/main/java/org/traccar/protocol/AvemaProtocolDecoder.java index 37836ad5f..0793975df 100644 --- a/src/main/java/org/traccar/protocol/AvemaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AvemaProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/Avl301Protocol.java b/src/main/java/org/traccar/protocol/Avl301Protocol.java index 71fc7cb26..c4a0affdc 100644 --- a/src/main/java/org/traccar/protocol/Avl301Protocol.java +++ b/src/main/java/org/traccar/protocol/Avl301Protocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Avl301Protocol extends BaseProtocol { - public Avl301Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Avl301Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(256, 2, 1, -3, 0)); pipeline.addLast(new Avl301ProtocolDecoder(Avl301Protocol.this)); } diff --git a/src/main/java/org/traccar/protocol/Avl301ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Avl301ProtocolDecoder.java index f6b7db2d6..8f036fc29 100644 --- a/src/main/java/org/traccar/protocol/Avl301ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Avl301ProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; @@ -125,7 +125,7 @@ public class Avl301ProtocolDecoder extends BaseProtocolDecoder { } position.setNetwork(new Network( - CellTower.fromLacCid(buf.readUnsignedShort(), buf.readUnsignedMedium()))); + CellTower.fromLacCid(getConfig(), buf.readUnsignedShort(), buf.readUnsignedMedium()))); position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); int flags = buf.readUnsignedByte(); diff --git a/src/main/java/org/traccar/protocol/B2316Protocol.java b/src/main/java/org/traccar/protocol/B2316Protocol.java index 7f08870ce..582be0b56 100644 --- a/src/main/java/org/traccar/protocol/B2316Protocol.java +++ b/src/main/java/org/traccar/protocol/B2316Protocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class B2316Protocol extends BaseProtocol { - public B2316Protocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public B2316Protocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new B2316ProtocolDecoder(B2316Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/B2316ProtocolDecoder.java b/src/main/java/org/traccar/protocol/B2316ProtocolDecoder.java index 854107a20..635806b2d 100644 --- a/src/main/java/org/traccar/protocol/B2316ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/B2316ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.model.CellTower; import org.traccar.model.Network; @@ -116,8 +116,9 @@ public class B2316ProtocolDecoder extends BaseProtocolDecoder { String[] points = item.getString("wi").split(";"); for (String point : points) { String[] values = point.split(","); + String mac = values[0].replaceAll("(..)", "$1:"); network.addWifiAccessPoint(WifiAccessPoint.from( - values[0].replaceAll("(..)", "$1:"), Integer.parseInt(values[1]))); + mac.substring(0, mac.length() - 1), Integer.parseInt(values[1]))); } } diff --git a/src/main/java/org/traccar/protocol/BceProtocol.java b/src/main/java/org/traccar/protocol/BceProtocol.java index c5e1dd04c..31fb1bd83 100644 --- a/src/main/java/org/traccar/protocol/BceProtocol.java +++ b/src/main/java/org/traccar/protocol/BceProtocol.java @@ -18,16 +18,20 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class BceProtocol extends BaseProtocol { - public BceProtocol() { + @Inject + public BceProtocol(Config config) { setSupportedDataCommands( Command.TYPE_OUTPUT_CONTROL); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new BceFrameDecoder()); pipeline.addLast(new BceProtocolEncoder(BceProtocol.this)); pipeline.addLast(new BceProtocolDecoder(BceProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/BceProtocolDecoder.java b/src/main/java/org/traccar/protocol/BceProtocolDecoder.java index c1a69981d..2c9459584 100644 --- a/src/main/java/org/traccar/protocol/BceProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/BceProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -90,11 +90,13 @@ public class BceProtocolDecoder extends BaseProtocolDecoder { } if (BitUtil.check(mask, 14)) { - position.setNetwork(new Network(CellTower.from( - buf.readUnsignedShortLE(), buf.readUnsignedByte(), - buf.readUnsignedShortLE(), buf.readUnsignedShortLE(), - buf.readUnsignedByte()))); - buf.readUnsignedByte(); + int mcc = buf.readUnsignedShortLE(); + int mnc = buf.readUnsignedByte(); + int lac = buf.readUnsignedShortLE(); + int cid = buf.readUnsignedShortLE(); + buf.readUnsignedByte(); // time advance + int rssi = -buf.readUnsignedByte(); + position.setNetwork(new Network(CellTower.from(mcc, mnc, lac, cid, rssi))); } } diff --git a/src/main/java/org/traccar/protocol/BlackKiteProtocol.java b/src/main/java/org/traccar/protocol/BlackKiteProtocol.java index 617a24d7a..3859a9273 100644 --- a/src/main/java/org/traccar/protocol/BlackKiteProtocol.java +++ b/src/main/java/org/traccar/protocol/BlackKiteProtocol.java @@ -19,13 +19,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class BlackKiteProtocol extends BaseProtocol { - public BlackKiteProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public BlackKiteProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new GalileoFrameDecoder()); pipeline.addLast(new BlackKiteProtocolDecoder(BlackKiteProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/BlackKiteProtocolDecoder.java b/src/main/java/org/traccar/protocol/BlackKiteProtocolDecoder.java index 474ceabdc..64fc439c4 100644 --- a/src/main/java/org/traccar/protocol/BlackKiteProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/BlackKiteProtocolDecoder.java @@ -20,7 +20,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/BlueProtocol.java b/src/main/java/org/traccar/protocol/BlueProtocol.java index d5dc5c421..da195f438 100644 --- a/src/main/java/org/traccar/protocol/BlueProtocol.java +++ b/src/main/java/org/traccar/protocol/BlueProtocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class BlueProtocol extends BaseProtocol { - public BlueProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public BlueProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 1, 2, -2, 0)); pipeline.addLast(new BlueProtocolDecoder(BlueProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/BlueProtocolDecoder.java b/src/main/java/org/traccar/protocol/BlueProtocolDecoder.java index f35ac6fbe..db59c564d 100644 --- a/src/main/java/org/traccar/protocol/BlueProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/BlueProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/BoxProtocol.java b/src/main/java/org/traccar/protocol/BoxProtocol.java index dfea15938..dc6852d50 100644 --- a/src/main/java/org/traccar/protocol/BoxProtocol.java +++ b/src/main/java/org/traccar/protocol/BoxProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class BoxProtocol extends BaseProtocol { - public BoxProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public BoxProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '\r')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/BoxProtocolDecoder.java b/src/main/java/org/traccar/protocol/BoxProtocolDecoder.java index 853fa8f81..8e92b69fb 100644 --- a/src/main/java/org/traccar/protocol/BoxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/BoxProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/BstplProtocol.java b/src/main/java/org/traccar/protocol/BstplProtocol.java new file mode 100644 index 000000000..dde14a2ca --- /dev/null +++ b/src/main/java/org/traccar/protocol/BstplProtocol.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.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; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class BstplProtocol extends BaseProtocol { + + @Inject + public BstplProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new BstplProtocolDecoder(BstplProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/BstplProtocolDecoder.java b/src/main/java/org/traccar/protocol/BstplProtocolDecoder.java new file mode 100644 index 000000000..15c114642 --- /dev/null +++ b/src/main/java/org/traccar/protocol/BstplProtocolDecoder.java @@ -0,0 +1,137 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; +import org.traccar.session.DeviceSession; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class BstplProtocolDecoder extends BaseProtocolDecoder { + + public BstplProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("BSTPL$") // header + .number("(d),") // type + .expression("([^,]+),") // device id + .expression("([AV]),") // validity + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d+.d+),([0NS]),") // latitude + .number("(d+.d+),([0EW]),") // longitude + .number("(d+),") // speed + .number("(d+),") // odometer + .number("(d+),") // course + .number("(d+),") // satellites + .number("([01]),") // box open + .number("(d+),") // rssi + .number("([01]),") // charge + .number("([01]),") // ignition + .number("([01]),") // engine + .number("([01]),") // locked + .number("(d+.d+),") // adc + .number("d+,") // reserved + .number("(d+.d+),") // battery + .expression("([^,]+),") // firmware + .number("([^,]+),") // iccid + .number("(d+.d+)") // power + .compile(); + + private String decodeAlarm(int value) { + switch (value) { + case 4: + return Position.ALARM_LOW_BATTERY; + case 5: + return Position.ALARM_ACCELERATION; + case 6: + return Position.ALARM_BRAKING; + case 7: + return Position.ALARM_OVERSPEED; + case 9: + return Position.ALARM_SOS; + default: + return null; + } + } + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + int type = parser.nextInt(); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_ALARM, decodeAlarm(type)); + + position.setValid(parser.next().equals("A")); + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + + position.set(Position.KEY_ODOMETER, parser.nextInt() * 1000L); + + position.setCourse(parser.nextInt()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + + boolean boxOpen = parser.nextInt() > 0; + if (type == 8 && boxOpen) { + position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING); + } + position.set("boxOpen", boxOpen); + + position.set(Position.KEY_RSSI, parser.nextInt()); + + boolean charge = parser.nextInt() > 0; + if (type == 3) { + position.set(Position.KEY_ALARM, charge ? Position.ALARM_POWER_RESTORED : Position.ALARM_POWER_CUT); + } + position.set(Position.KEY_CHARGE, charge); + + position.set(Position.KEY_IGNITION, parser.nextInt() > 0); + position.set("engine", parser.nextInt() > 0); + position.set(Position.KEY_BLOCKED, parser.nextInt() > 0); + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_ICCID, parser.next()); + position.set(Position.KEY_POWER, parser.nextDouble()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/C2stekProtocol.java b/src/main/java/org/traccar/protocol/C2stekProtocol.java index 804621fd3..5cd8ef4fd 100644 --- a/src/main/java/org/traccar/protocol/C2stekProtocol.java +++ b/src/main/java/org/traccar/protocol/C2stekProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class C2stekProtocol extends BaseProtocol { - public C2stekProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public C2stekProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, false, "$AP")); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/C2stekProtocolDecoder.java b/src/main/java/org/traccar/protocol/C2stekProtocolDecoder.java index 83e62ff86..aef158fc7 100644 --- a/src/main/java/org/traccar/protocol/C2stekProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/C2stekProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; @@ -49,10 +49,12 @@ public class C2stekProtocolDecoder extends BaseProtocolDecoder { .number("(-?d+.d+)#") // altitude .number("(d+)#") // battery .number("d+#") // geo area alarm - .number("(x+)#") // alarm - .number("([01])") // armed + .number("(x+)") // alarm + .groupBegin() + .number("#([01])?") // armed .number("([01])") // door .number("([01])#") // ignition + .groupEnd("?") .any() .text("$AP") .compile(); @@ -111,9 +113,13 @@ public class C2stekProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001); position.set(Position.KEY_ALARM, decodeAlarm(parser.nextHexInt())); - position.set(Position.KEY_ARMED, parser.nextInt() > 0); - position.set(Position.KEY_DOOR, parser.nextInt() > 0); - position.set(Position.KEY_IGNITION, parser.nextInt() > 0); + if (parser.hasNext()) { + position.set(Position.KEY_ARMED, parser.nextInt() > 0); + } + if (parser.hasNext(2)) { + position.set(Position.KEY_DOOR, parser.nextInt() > 0); + position.set(Position.KEY_IGNITION, parser.nextInt() > 0); + } return position; } diff --git a/src/main/java/org/traccar/protocol/CalAmpProtocol.java b/src/main/java/org/traccar/protocol/CalAmpProtocol.java index 232e72a8c..d67308cf2 100644 --- a/src/main/java/org/traccar/protocol/CalAmpProtocol.java +++ b/src/main/java/org/traccar/protocol/CalAmpProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class CalAmpProtocol extends BaseProtocol { - public CalAmpProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public CalAmpProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CalAmpProtocolDecoder(CalAmpProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/CalAmpProtocolDecoder.java b/src/main/java/org/traccar/protocol/CalAmpProtocolDecoder.java index 59b1fdf21..57f9c69ae 100644 --- a/src/main/java/org/traccar/protocol/CalAmpProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CalAmpProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/CarTrackProtocol.java b/src/main/java/org/traccar/protocol/CarTrackProtocol.java index e340fba25..0538aad72 100644 --- a/src/main/java/org/traccar/protocol/CarTrackProtocol.java +++ b/src/main/java/org/traccar/protocol/CarTrackProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class CarTrackProtocol extends BaseProtocol { - public CarTrackProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public CarTrackProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "##")); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/CarTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/CarTrackProtocolDecoder.java index ce3345826..3f5418549 100644 --- a/src/main/java/org/traccar/protocol/CarTrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CarTrackProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/CarcellProtocol.java b/src/main/java/org/traccar/protocol/CarcellProtocol.java index f08ab3bd9..832d9bb2d 100644 --- a/src/main/java/org/traccar/protocol/CarcellProtocol.java +++ b/src/main/java/org/traccar/protocol/CarcellProtocol.java @@ -21,17 +21,21 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class CarcellProtocol extends BaseProtocol { - public CarcellProtocol() { + @Inject + public CarcellProtocol(Config config) { setSupportedDataCommands( Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '\r')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/CarcellProtocolDecoder.java b/src/main/java/org/traccar/protocol/CarcellProtocolDecoder.java index ec640ba71..54ae068fb 100644 --- a/src/main/java/org/traccar/protocol/CarcellProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CarcellProtocolDecoder.java @@ -20,7 +20,7 @@ import java.util.regex.Pattern; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.Parser.CoordinateFormat; diff --git a/src/main/java/org/traccar/protocol/CarscopProtocol.java b/src/main/java/org/traccar/protocol/CarscopProtocol.java index 2c754a97f..a4413af28 100644 --- a/src/main/java/org/traccar/protocol/CarscopProtocol.java +++ b/src/main/java/org/traccar/protocol/CarscopProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class CarscopProtocol extends BaseProtocol { - public CarscopProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public CarscopProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '^')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/CarscopProtocolDecoder.java b/src/main/java/org/traccar/protocol/CarscopProtocolDecoder.java index 161666adc..f13a1d0eb 100644 --- a/src/main/java/org/traccar/protocol/CarscopProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CarscopProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/CastelProtocol.java b/src/main/java/org/traccar/protocol/CastelProtocol.java index 44c52d68f..9323b1503 100644 --- a/src/main/java/org/traccar/protocol/CastelProtocol.java +++ b/src/main/java/org/traccar/protocol/CastelProtocol.java @@ -19,26 +19,30 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; import java.nio.ByteOrder; +import javax.inject.Inject; + public class CastelProtocol extends BaseProtocol { - public CastelProtocol() { + @Inject + public CastelProtocol(Config config) { setSupportedDataCommands( Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 2, -4, 0, true)); pipeline.addLast(new CastelProtocolEncoder(CastelProtocol.this)); pipeline.addLast(new CastelProtocolDecoder(CastelProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CastelProtocolEncoder(CastelProtocol.this)); pipeline.addLast(new CastelProtocolDecoder(CastelProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java b/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java index 23401b5ee..4aa65245b 100644 --- a/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -160,6 +160,9 @@ public class CastelProtocolDecoder extends BaseProtocolDecoder { for (int i = 0; i < count; i++) { int value; + if (!PID_LENGTH_MAP.containsKey(pids[i])) { + throw new RuntimeException(String.format("Unknown PID 0x%02x", pids[i])); + } switch (PID_LENGTH_MAP.get(pids[i])) { case 1: value = buf.readUnsignedByte(); @@ -443,7 +446,7 @@ public class CastelProtocolDecoder extends BaseProtocolDecoder { decodeStat(position, buf); position.setNetwork(new Network( - CellTower.fromLacCid(buf.readUnsignedShortLE(), buf.readUnsignedShortLE()))); + CellTower.fromLacCid(getConfig(), buf.readUnsignedShortLE(), buf.readUnsignedShortLE()))); return position; @@ -499,7 +502,7 @@ public class CastelProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedByte(); // additional flags position.setNetwork(new Network( - CellTower.fromLacCid(buf.readUnsignedShortLE(), buf.readUnsignedShortLE()))); + CellTower.fromLacCid(getConfig(), buf.readUnsignedShortLE(), buf.readUnsignedShortLE()))); positions.add(position); } diff --git a/src/main/java/org/traccar/protocol/CastelProtocolEncoder.java b/src/main/java/org/traccar/protocol/CastelProtocolEncoder.java index dc694da28..61dde3e80 100644 --- a/src/main/java/org/traccar/protocol/CastelProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/CastelProtocolEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,9 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.traccar.BaseProtocolEncoder; -import org.traccar.Context; +import org.traccar.Protocol; import org.traccar.helper.Checksum; import org.traccar.model.Command; -import org.traccar.Protocol; import java.nio.charset.StandardCharsets; @@ -34,7 +33,7 @@ public class CastelProtocolEncoder extends BaseProtocolEncoder { private ByteBuf encodeContent(long deviceId, short type, ByteBuf content) { ByteBuf buf = Unpooled.buffer(0); - String uniqueId = Context.getIdentityManager().getById(deviceId).getUniqueId(); + String uniqueId = getUniqueId(deviceId); buf.writeByte('@'); buf.writeByte('@'); diff --git a/src/main/java/org/traccar/protocol/CautelaProtocol.java b/src/main/java/org/traccar/protocol/CautelaProtocol.java index 452bdf8d4..d0ca35ef1 100644 --- a/src/main/java/org/traccar/protocol/CautelaProtocol.java +++ b/src/main/java/org/traccar/protocol/CautelaProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class CautelaProtocol extends BaseProtocol { - public CautelaProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public CautelaProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/CautelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/CautelaProtocolDecoder.java index bddf19b41..37f733ac1 100644 --- a/src/main/java/org/traccar/protocol/CautelaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CautelaProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/CellocatorProtocol.java b/src/main/java/org/traccar/protocol/CellocatorProtocol.java index d910877cf..3287928c7 100644 --- a/src/main/java/org/traccar/protocol/CellocatorProtocol.java +++ b/src/main/java/org/traccar/protocol/CellocatorProtocol.java @@ -18,24 +18,28 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class CellocatorProtocol extends BaseProtocol { - public CellocatorProtocol() { + @Inject + public CellocatorProtocol(Config config) { setSupportedDataCommands( Command.TYPE_OUTPUT_CONTROL); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CellocatorFrameDecoder()); pipeline.addLast(new CellocatorProtocolEncoder(CellocatorProtocol.this)); pipeline.addLast(new CellocatorProtocolDecoder(CellocatorProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CellocatorProtocolEncoder(CellocatorProtocol.this)); pipeline.addLast(new CellocatorProtocolDecoder(CellocatorProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java b/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java index 09bd3572f..3573a95ca 100644 --- a/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -120,13 +120,15 @@ public class CellocatorProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedByte(); // operator / configuration flags buf.readUnsignedByte(); // reason data - position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + short event = buf.readUnsignedByte(); + position.set(Position.KEY_ALARM, decodeAlarm(event)); + position.set(Position.KEY_EVENT, event); position.set("mode", buf.readUnsignedByte()); - long input = buf.readUnsignedIntLE(); + long input = buf.readUnsignedInt(); + position.set(Position.KEY_IGNITION, BitUtil.check(input, 3 * 8 + 5)); position.set(Position.KEY_DOOR, BitUtil.check(input, 3 * 8)); - position.set(Position.KEY_IGNITION, BitUtil.check(input, 2 * 8 + 7)); position.set(Position.KEY_CHARGE, BitUtil.check(input, 7)); position.set(Position.KEY_INPUT, input); diff --git a/src/main/java/org/traccar/protocol/CguardProtocol.java b/src/main/java/org/traccar/protocol/CguardProtocol.java index 9157ca35c..caf0aad42 100644 --- a/src/main/java/org/traccar/protocol/CguardProtocol.java +++ b/src/main/java/org/traccar/protocol/CguardProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class CguardProtocol extends BaseProtocol { - public CguardProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public CguardProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/CguardProtocolDecoder.java b/src/main/java/org/traccar/protocol/CguardProtocolDecoder.java index d934921f1..90f8e0caf 100644 --- a/src/main/java/org/traccar/protocol/CguardProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CguardProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/CityeasyProtocol.java b/src/main/java/org/traccar/protocol/CityeasyProtocol.java index 8ab4ce93a..9656b284b 100644 --- a/src/main/java/org/traccar/protocol/CityeasyProtocol.java +++ b/src/main/java/org/traccar/protocol/CityeasyProtocol.java @@ -19,19 +19,23 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class CityeasyProtocol extends BaseProtocol { - public CityeasyProtocol() { + @Inject + public CityeasyProtocol(Config config) { setSupportedDataCommands( Command.TYPE_POSITION_SINGLE, Command.TYPE_POSITION_PERIODIC, Command.TYPE_POSITION_STOP, Command.TYPE_SET_TIMEZONE); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 2, -4, 0)); pipeline.addLast(new CityeasyProtocolEncoder(CityeasyProtocol.this)); pipeline.addLast(new CityeasyProtocolDecoder(CityeasyProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/CityeasyProtocolDecoder.java b/src/main/java/org/traccar/protocol/CityeasyProtocolDecoder.java index 9c4c7e11d..1b5eb55d4 100644 --- a/src/main/java/org/traccar/protocol/CityeasyProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CityeasyProtocolDecoder.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Checksum; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/ContinentalProtocol.java b/src/main/java/org/traccar/protocol/ContinentalProtocol.java index bc7928fba..06e93d79d 100644 --- a/src/main/java/org/traccar/protocol/ContinentalProtocol.java +++ b/src/main/java/org/traccar/protocol/ContinentalProtocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class ContinentalProtocol extends BaseProtocol { - public ContinentalProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public ContinentalProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 2, -4, 0)); pipeline.addLast(new ContinentalProtocolDecoder(ContinentalProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/ContinentalProtocolDecoder.java b/src/main/java/org/traccar/protocol/ContinentalProtocolDecoder.java index 471afa0d6..280871e1e 100644 --- a/src/main/java/org/traccar/protocol/ContinentalProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ContinentalProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.UnitsConverter; diff --git a/src/main/java/org/traccar/protocol/CradlepointProtocol.java b/src/main/java/org/traccar/protocol/CradlepointProtocol.java index 4a09e0311..7f201a31d 100644 --- a/src/main/java/org/traccar/protocol/CradlepointProtocol.java +++ b/src/main/java/org/traccar/protocol/CradlepointProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class CradlepointProtocol extends BaseProtocol { - public CradlepointProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public CradlepointProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/CradlepointProtocolDecoder.java b/src/main/java/org/traccar/protocol/CradlepointProtocolDecoder.java index a282131ce..924603291 100644 --- a/src/main/java/org/traccar/protocol/CradlepointProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/CradlepointProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/DingtekProtocol.java b/src/main/java/org/traccar/protocol/DingtekProtocol.java index cf2a6c0f5..e9466b7e8 100644 --- a/src/main/java/org/traccar/protocol/DingtekProtocol.java +++ b/src/main/java/org/traccar/protocol/DingtekProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class DingtekProtocol extends BaseProtocol { - public DingtekProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public DingtekProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new DingtekFrameDecoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/DingtekProtocolDecoder.java b/src/main/java/org/traccar/protocol/DingtekProtocolDecoder.java index 98fe4b7b3..580741ec9 100644 --- a/src/main/java/org/traccar/protocol/DingtekProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/DingtekProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DataConverter; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/DishaProtocol.java b/src/main/java/org/traccar/protocol/DishaProtocol.java index 38f49cc05..f83b8349a 100644 --- a/src/main/java/org/traccar/protocol/DishaProtocol.java +++ b/src/main/java/org/traccar/protocol/DishaProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class DishaProtocol extends BaseProtocol { - public DishaProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public DishaProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/DishaProtocolDecoder.java b/src/main/java/org/traccar/protocol/DishaProtocolDecoder.java index 3223988ab..1327e7a6c 100644 --- a/src/main/java/org/traccar/protocol/DishaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/DishaProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/DmtHttpProtocol.java b/src/main/java/org/traccar/protocol/DmtHttpProtocol.java index 34568128f..0dab26cda 100644 --- a/src/main/java/org/traccar/protocol/DmtHttpProtocol.java +++ b/src/main/java/org/traccar/protocol/DmtHttpProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class DmtHttpProtocol extends BaseProtocol { - public DmtHttpProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public DmtHttpProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(65535)); diff --git a/src/main/java/org/traccar/protocol/DmtHttpProtocolDecoder.java b/src/main/java/org/traccar/protocol/DmtHttpProtocolDecoder.java index 15cf84a5f..807850778 100644 --- a/src/main/java/org/traccar/protocol/DmtHttpProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/DmtHttpProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.UnitsConverter; @@ -174,21 +174,25 @@ public class DmtHttpProtocolDecoder extends BaseHttpProtocolDecoder { position.set(Position.KEY_INDEX, root.getInt("sqn")); position.set(Position.KEY_EVENT, root.getInt("reason")); - JsonArray analogues = root.getJsonArray("analogues"); - for (int i = 0; i < analogues.size(); i++) { - JsonObject adc = analogues.getJsonObject(i); - position.set(Position.PREFIX_ADC + adc.getInt("id"), adc.getInt("val")); + if (root.containsKey("analogues")) { + JsonArray analogues = root.getJsonArray("analogues"); + for (int i = 0; i < analogues.size(); i++) { + JsonObject adc = analogues.getJsonObject(i); + position.set(Position.PREFIX_ADC + adc.getInt("id"), adc.getInt("val")); + } } - int input = root.getInt("inputs"); - int output = root.getInt("outputs"); - int status = root.getInt("status"); - - position.set(Position.KEY_IGNITION, BitUtil.check(input, 0)); - - position.set(Position.KEY_INPUT, input); - position.set(Position.KEY_OUTPUT, output); - position.set(Position.KEY_STATUS, status); + if (root.containsKey("inputs")) { + int input = root.getInt("inputs"); + position.set(Position.KEY_IGNITION, BitUtil.check(input, 0)); + position.set(Position.KEY_INPUT, input); + } + if (root.containsKey("outputs")) { + position.set(Position.KEY_OUTPUT, root.getInt("outputs")); + } + if (root.containsKey("status")) { + position.set(Position.KEY_STATUS, root.getInt("status")); + } if (root.containsKey("counters")) { JsonArray counters = root.getJsonArray("counters"); diff --git a/src/main/java/org/traccar/protocol/DmtProtocol.java b/src/main/java/org/traccar/protocol/DmtProtocol.java index 78a5243c0..de56c9372 100644 --- a/src/main/java/org/traccar/protocol/DmtProtocol.java +++ b/src/main/java/org/traccar/protocol/DmtProtocol.java @@ -19,14 +19,18 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class DmtProtocol extends BaseProtocol { - public DmtProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public DmtProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 3, 2, 0, 0, true)); pipeline.addLast(new DmtProtocolDecoder(DmtProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java b/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java index 96b06557a..320aa1b60 100644 --- a/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -184,9 +184,9 @@ public class DmtProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_IGNITION, BitUtil.check(input, 0)); - if (!BitUtil.check(input, 1)) { + if (!BitUtil.check(status, 1)) { position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); - } else if (BitUtil.check(input, 6)) { + } else if (BitUtil.check(status, 6)) { position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING); } diff --git a/src/main/java/org/traccar/protocol/DolphinProtocol.java b/src/main/java/org/traccar/protocol/DolphinProtocol.java index 07c827e18..ed627be78 100644 --- a/src/main/java/org/traccar/protocol/DolphinProtocol.java +++ b/src/main/java/org/traccar/protocol/DolphinProtocol.java @@ -19,15 +19,19 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class DolphinProtocol extends BaseProtocol { - public DolphinProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public DolphinProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 4096, 20, 4, 4, 0, true)); pipeline.addLast(new DolphinProtocolDecoder(DolphinProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java b/src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java index d509b3ec0..b43635a52 100644 --- a/src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/DolphinProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.UnitsConverter; diff --git a/src/main/java/org/traccar/protocol/Dsf22FrameDecoder.java b/src/main/java/org/traccar/protocol/Dsf22FrameDecoder.java new file mode 100644 index 000000000..388c97f85 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Dsf22FrameDecoder.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class Dsf22FrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 21) { + return null; + } + + int count = buf.getUnsignedByte(buf.readerIndex() + 4); + + int length = 2 + 2 + 1 + count * (4 + 4 + 4 + 1 + 2 + 1); + + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } else { + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Dsf22Protocol.java b/src/main/java/org/traccar/protocol/Dsf22Protocol.java new file mode 100644 index 000000000..06c99b0f9 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Dsf22Protocol.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class Dsf22Protocol extends BaseProtocol { + + @Inject + public Dsf22Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new Dsf22FrameDecoder()); + pipeline.addLast(new Dsf22ProtocolDecoder(Dsf22Protocol.this)); + } + }); + addServer(new TrackerServer(config, getName(), true) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new Dsf22ProtocolDecoder(Dsf22Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Dsf22ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Dsf22ProtocolDecoder.java new file mode 100644 index 000000000..124bbfefa --- /dev/null +++ b/src/main/java/org/traccar/protocol/Dsf22ProtocolDecoder.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.session.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class Dsf22ProtocolDecoder extends BaseProtocolDecoder { + + public Dsf22ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + + String id = String.valueOf(buf.readUnsignedShortLE()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + int count = buf.readUnsignedByte(); + + for (int i = 0; i < count; i++) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + position.setLatitude(buf.readIntLE() / 10000000.0); + position.setLongitude(buf.readIntLE() / 10000000.0); + position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShortLE() * 0.001); + + int status = buf.readUnsignedByte(); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 0)); + position.set(Position.PREFIX_IN + 1, BitUtil.check(status, 1)); + position.set(Position.PREFIX_OUT + 1, BitUtil.check(status, 4)); + position.set(Position.KEY_ALARM, BitUtil.check(status, 6) ? Position.ALARM_JAMMING : null); + position.set(Position.KEY_STATUS, status); + + positions.add(position); + + } + + if (channel != null) { + byte[] response = {0x01}; + channel.writeAndFlush(new NetworkMessage(Unpooled.wrappedBuffer(response), remoteAddress)); + } + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/DualcamProtocol.java b/src/main/java/org/traccar/protocol/DualcamProtocol.java index 04c4f2bd1..363a2c5d9 100644 --- a/src/main/java/org/traccar/protocol/DualcamProtocol.java +++ b/src/main/java/org/traccar/protocol/DualcamProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class DualcamProtocol extends BaseProtocol { - public DualcamProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public DualcamProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new DualcamFrameDecoder()); pipeline.addLast(new DualcamProtocolDecoder(DualcamProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java b/src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java index c64b8171f..d03f7648d 100644 --- a/src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/DualcamProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -47,7 +46,8 @@ public class DualcamProtocolDecoder extends BaseProtocolDecoder { private String uniqueId; private int packetCount; private int currentPacket; - private ByteBuf photo; + private boolean video; + private ByteBuf media; @Override protected Object decode( @@ -65,13 +65,26 @@ public class DualcamProtocolDecoder extends BaseProtocolDecoder { long settings = buf.readUnsignedInt(); if (channel != null && deviceSession != null) { ByteBuf response = Unpooled.buffer(); - if (BitUtil.between(settings, 26, 28) > 0) { + if (BitUtil.between(settings, 26, 30) > 0) { response.writeShort(MSG_FILE_REQUEST); - String file = BitUtil.check(settings, 26) ? "%photof" : "%photor"; + String file; + if (BitUtil.check(settings, 26)) { + video = false; + file = "%photof"; + } else if (BitUtil.check(settings, 27)) { + video = false; + file = "%photor"; + } else if (BitUtil.check(settings, 28)) { + video = true; + file = "%videof"; + } else { + video = true; + file = "%videor"; + } response.writeShort(file.length()); response.writeCharSequence(file, StandardCharsets.US_ASCII); } else { - response.writeShort(MSG_COMPLETE); + response.writeShort(MSG_INIT); } channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } @@ -80,7 +93,7 @@ public class DualcamProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedShort(); // length packetCount = buf.readInt(); currentPacket = 1; - photo = Unpooled.buffer(); + media = Unpooled.buffer(); if (channel != null) { ByteBuf response = Unpooled.buffer(); response.writeShort(MSG_RESUME); @@ -91,17 +104,21 @@ public class DualcamProtocolDecoder extends BaseProtocolDecoder { break; case MSG_DATA: buf.readUnsignedShort(); // length - photo.writeBytes(buf, buf.readableBytes() - 2); + media.writeBytes(buf, buf.readableBytes() - 2); if (currentPacket == packetCount) { deviceSession = getDeviceSession(channel, remoteAddress); Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); getLastLocation(position, null); try { - position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(uniqueId, photo, "jpg")); + if (video) { + position.set(Position.KEY_VIDEO, writeMediaFile(uniqueId, media, "h265")); + } else { + position.set(Position.KEY_IMAGE, writeMediaFile(uniqueId, media, "jpg")); + } } finally { - photo.release(); - photo = null; + media.release(); + media = null; } if (channel != null) { ByteBuf response = Unpooled.buffer(); diff --git a/src/main/java/org/traccar/protocol/DwayProtocol.java b/src/main/java/org/traccar/protocol/DwayProtocol.java index 05fd8b6e7..1096c945c 100644 --- a/src/main/java/org/traccar/protocol/DwayProtocol.java +++ b/src/main/java/org/traccar/protocol/DwayProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class DwayProtocol extends BaseProtocol { - public DwayProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public DwayProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/DwayProtocolDecoder.java b/src/main/java/org/traccar/protocol/DwayProtocolDecoder.java index 9b02c898e..9cf40b011 100644 --- a/src/main/java/org/traccar/protocol/DwayProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/DwayProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/EasyTrackProtocol.java b/src/main/java/org/traccar/protocol/EasyTrackProtocol.java index 972b36077..39aa61580 100644 --- a/src/main/java/org/traccar/protocol/EasyTrackProtocol.java +++ b/src/main/java/org/traccar/protocol/EasyTrackProtocol.java @@ -21,19 +21,23 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class EasyTrackProtocol extends BaseProtocol { - public EasyTrackProtocol() { + @Inject + public EasyTrackProtocol(Config config) { setSupportedDataCommands( Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME, Command.TYPE_ALARM_ARM, Command.TYPE_ALARM_DISARM); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "#\r\n", "#", "\r\n")); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java index 4fcc48944..805cf1197 100644 --- a/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/EelinkProtocol.java b/src/main/java/org/traccar/protocol/EelinkProtocol.java index 8a055d643..35fd4fe65 100644 --- a/src/main/java/org/traccar/protocol/EelinkProtocol.java +++ b/src/main/java/org/traccar/protocol/EelinkProtocol.java @@ -19,28 +19,32 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class EelinkProtocol extends BaseProtocol { - public EelinkProtocol() { + @Inject + public EelinkProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_POSITION_SINGLE, Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME, Command.TYPE_REBOOT_DEVICE); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 2)); pipeline.addLast(new EelinkProtocolEncoder(EelinkProtocol.this, false)); pipeline.addLast(new EelinkProtocolDecoder(EelinkProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new EelinkProtocolEncoder(EelinkProtocol.this, true)); pipeline.addLast(new EelinkProtocolDecoder(EelinkProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java index 068b19cbc..941b10fef 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2014 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.socket.DatagramChannel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -31,6 +31,7 @@ import org.traccar.helper.UnitsConverter; import org.traccar.model.CellTower; import org.traccar.model.Network; import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; @@ -192,30 +193,67 @@ public class EelinkProtocolDecoder extends BaseProtocolDecoder { getLastLocation(position, position.getDeviceTime()); } + Network network = new Network(); + + int mcc = 0; + int mnc = 0; if (BitUtil.check(flags, 1)) { - position.setNetwork(new Network(CellTower.from( - buf.readUnsignedShort(), buf.readUnsignedShort(), - buf.readUnsignedShort(), buf.readUnsignedInt(), buf.readUnsignedByte()))); + mcc = buf.readUnsignedShort(); + mnc = buf.readUnsignedShort(); + network.addCellTower(CellTower.from( + mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedInt(), buf.readUnsignedByte())); } if (BitUtil.check(flags, 2)) { - buf.skipBytes(7); // bsid1 + network.addCellTower(CellTower.from( + mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedInt(), buf.readUnsignedByte())); } if (BitUtil.check(flags, 3)) { - buf.skipBytes(7); // bsid2 + network.addCellTower(CellTower.from( + mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedInt(), buf.readUnsignedByte())); } if (BitUtil.check(flags, 4)) { - buf.skipBytes(7); // bss0 + String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:"); + network.addWifiAccessPoint(WifiAccessPoint.from( + mac.substring(0, mac.length() - 1), buf.readUnsignedByte())); } if (BitUtil.check(flags, 5)) { - buf.skipBytes(7); // bss1 + String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:"); + network.addWifiAccessPoint(WifiAccessPoint.from( + mac.substring(0, mac.length() - 1), buf.readUnsignedByte())); } if (BitUtil.check(flags, 6)) { - buf.skipBytes(7); // bss2 + String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:"); + network.addWifiAccessPoint(WifiAccessPoint.from( + mac.substring(0, mac.length() - 1), buf.readUnsignedByte())); + } + + if (BitUtil.check(flags, 7)) { + buf.readUnsignedByte(); // radio access technology + int count = buf.readUnsignedByte(); + int lac = 0; + if (count > 0) { + mcc = buf.readUnsignedShort(); + mnc = buf.readUnsignedShort(); + lac = buf.readUnsignedShort(); // lac + buf.readUnsignedShort(); // tac + buf.readUnsignedInt(); // cid + buf.readUnsignedShort(); // ta + } + for (int i = 0; i < count; i++) { + int cid = buf.readUnsignedShort(); // physical cid + buf.readUnsignedShort(); // e-arfcn + int rssi = buf.readUnsignedByte(); + network.addCellTower(CellTower.from(mcc, mnc, lac, cid, rssi)); + } + } + + if (network.getCellTowers() != null || network.getWifiAccessPoints() != null) { + position.setNetwork(network); } if (type == MSG_WARNING) { diff --git a/src/main/java/org/traccar/protocol/EgtsProtocol.java b/src/main/java/org/traccar/protocol/EgtsProtocol.java index 5d4638f37..f257271d4 100644 --- a/src/main/java/org/traccar/protocol/EgtsProtocol.java +++ b/src/main/java/org/traccar/protocol/EgtsProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class EgtsProtocol extends BaseProtocol { - public EgtsProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public EgtsProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new EgtsFrameDecoder()); pipeline.addLast(new EgtsProtocolDecoder(EgtsProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/EgtsProtocolDecoder.java b/src/main/java/org/traccar/protocol/EgtsProtocolDecoder.java index e65ddb0ef..01d329580 100644 --- a/src/main/java/org/traccar/protocol/EgtsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/EgtsProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -291,7 +291,7 @@ public class EgtsProtocolDecoder extends BaseProtocolDecoder { if (serviceType == SERVICE_TELEDATA && position.getValid()) { if (useObjectIdAsDeviceId && objectId != 0L) { - deviceSession = getDeviceSession(channel, remoteAddress, true, String.valueOf(objectId)); + deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(objectId)); if (deviceSession != null) { position.setDeviceId(deviceSession.getDeviceId()); } diff --git a/src/main/java/org/traccar/protocol/EnforaProtocol.java b/src/main/java/org/traccar/protocol/EnforaProtocol.java index e462ab322..ebde56f70 100644 --- a/src/main/java/org/traccar/protocol/EnforaProtocol.java +++ b/src/main/java/org/traccar/protocol/EnforaProtocol.java @@ -19,26 +19,30 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class EnforaProtocol extends BaseProtocol { - public EnforaProtocol() { + @Inject + public EnforaProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, -2, 2)); pipeline.addLast(new EnforaProtocolEncoder(EnforaProtocol.this)); pipeline.addLast(new EnforaProtocolDecoder(EnforaProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new EnforaProtocolEncoder(EnforaProtocol.this)); pipeline.addLast(new EnforaProtocolDecoder(EnforaProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/EnforaProtocolDecoder.java b/src/main/java/org/traccar/protocol/EnforaProtocolDecoder.java index bfa7a116b..dd1c8017b 100644 --- a/src/main/java/org/traccar/protocol/EnforaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/EnforaProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BufferUtil; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/EnnfuProtocol.java b/src/main/java/org/traccar/protocol/EnnfuProtocol.java index 7ef94d83f..e326481fa 100644 --- a/src/main/java/org/traccar/protocol/EnnfuProtocol.java +++ b/src/main/java/org/traccar/protocol/EnnfuProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class EnnfuProtocol extends BaseProtocol { - public EnnfuProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public EnnfuProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '$')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/EnnfuProtocolDecoder.java b/src/main/java/org/traccar/protocol/EnnfuProtocolDecoder.java index 792ed1098..2198938e2 100644 --- a/src/main/java/org/traccar/protocol/EnnfuProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/EnnfuProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/EnvotechProtocol.java b/src/main/java/org/traccar/protocol/EnvotechProtocol.java new file mode 100644 index 000000000..dffa1c991 --- /dev/null +++ b/src/main/java/org/traccar/protocol/EnvotechProtocol.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.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; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class EnvotechProtocol extends BaseProtocol { + + @Inject + public EnvotechProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new EnvotechProtocolDecoder(EnvotechProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/EnvotechProtocolDecoder.java b/src/main/java/org/traccar/protocol/EnvotechProtocolDecoder.java new file mode 100644 index 000000000..750ff2bda --- /dev/null +++ b/src/main/java/org/traccar/protocol/EnvotechProtocolDecoder.java @@ -0,0 +1,116 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.session.DeviceSession; +import org.traccar.Protocol; +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 EnvotechProtocolDecoder extends BaseProtocolDecoder { + + public EnvotechProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$") + .number("dd") // mode + .expression("...,") // hardware + .number("(x+),") // event + .number("x+,") // group + .number("(x+),") // device id + .number("(dd)(dd)(dd)") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("xx") // connection status + .number("(dd)") // rssi + .number("d{5},") // mcc + .number("(ddd)") // power + .number("(ddd),") // battery + .number("(xx)") // inputs + .number("(xx),") // outputs + .number("(xxx)?") // fuel + .number("(xxx)?,") // weight + .number("(x{8}),") // status + .expression("[^']*'") + .number("(dd)(dd)(dd)") // date (ddmmyy) + .number("(dd)(dd)(dd)") // time (hhmmss) + .number("(d)") // fix + .number("(d+)(d{5})([NS])") // latitude + .number("(d+)(d{5})([EW])") // longitude + .number("(ddd)") // speed + .number("(ddd)") // course + .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; + } + + Position position = new Position(getProtocolName()); + + int event = parser.nextHexInt(); + switch (event) { + case 0x60: + position.set(Position.KEY_ALARM, Position.ALARM_LOCK); + break; + case 0x61: + position.set(Position.KEY_ALARM, Position.ALARM_UNLOCK); + break; + default: + break; + } + position.set(Position.KEY_EVENT, event); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setDeviceTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.KEY_POWER, parser.nextInt() * 0.01); + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.01); + position.set(Position.KEY_INPUT, parser.nextHexInt()); + position.set(Position.PREFIX_OUT, parser.nextHexInt()); + position.set(Position.KEY_FUEL_LEVEL, parser.nextHexInt()); + position.set("weight", parser.nextHexInt()); + position.set(Position.KEY_STATUS, parser.nextHexLong()); + + position.setFixTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setValid(parser.nextInt() > 0); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_DEG_HEM)); + position.setSpeed(parser.nextInt()); + position.setCourse(parser.nextInt()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/EsealProtocol.java b/src/main/java/org/traccar/protocol/EsealProtocol.java index fc1d342e1..0ed80dc6f 100644 --- a/src/main/java/org/traccar/protocol/EsealProtocol.java +++ b/src/main/java/org/traccar/protocol/EsealProtocol.java @@ -21,18 +21,22 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class EsealProtocol extends BaseProtocol { - public EsealProtocol() { + @Inject + public EsealProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_ALARM_ARM, Command.TYPE_ALARM_DISARM); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/EsealProtocolDecoder.java b/src/main/java/org/traccar/protocol/EsealProtocolDecoder.java index 0a12f781d..dd15c4276 100644 --- a/src/main/java/org/traccar/protocol/EsealProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/EsealProtocolDecoder.java @@ -17,8 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.config.Keys; @@ -32,11 +31,15 @@ import java.util.regex.Pattern; public class EsealProtocolDecoder extends BaseProtocolDecoder { - private final String config; + private String config; public EsealProtocolDecoder(Protocol protocol) { super(protocol); - config = Context.getConfig().getString(Keys.PROTOCOL_CONFIG.withPrefix(getProtocolName())); + } + + @Override + protected void init() { + config = getConfig().getString(Keys.PROTOCOL_CONFIG.withPrefix(getProtocolName())); } private static final Pattern PATTERN = new PatternBuilder() diff --git a/src/main/java/org/traccar/protocol/EskyProtocol.java b/src/main/java/org/traccar/protocol/EskyProtocol.java index fb047c207..cb2f59dc8 100644 --- a/src/main/java/org/traccar/protocol/EskyProtocol.java +++ b/src/main/java/org/traccar/protocol/EskyProtocol.java @@ -20,22 +20,26 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class EskyProtocol extends BaseProtocol { - public EskyProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public EskyProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new EskyFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new EskyProtocolDecoder(EskyProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new EskyProtocolDecoder(EskyProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/EskyProtocolDecoder.java b/src/main/java/org/traccar/protocol/EskyProtocolDecoder.java index 14b4376d5..4239022d0 100644 --- a/src/main/java/org/traccar/protocol/EskyProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/EskyProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import io.netty.channel.socket.DatagramChannel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/ExtremTracProtocol.java b/src/main/java/org/traccar/protocol/ExtremTracProtocol.java index 692fd4e99..ffc941b69 100644 --- a/src/main/java/org/traccar/protocol/ExtremTracProtocol.java +++ b/src/main/java/org/traccar/protocol/ExtremTracProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class ExtremTracProtocol extends BaseProtocol { - public ExtremTracProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public ExtremTracProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/ExtremTracProtocolDecoder.java b/src/main/java/org/traccar/protocol/ExtremTracProtocolDecoder.java index 9fde6f0a0..706c70825 100644 --- a/src/main/java/org/traccar/protocol/ExtremTracProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ExtremTracProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocol.java b/src/main/java/org/traccar/protocol/FifotrackProtocol.java index 4a0a12ed3..fd2beaabb 100644 --- a/src/main/java/org/traccar/protocol/FifotrackProtocol.java +++ b/src/main/java/org/traccar/protocol/FifotrackProtocol.java @@ -19,17 +19,21 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class FifotrackProtocol extends BaseProtocol { - public FifotrackProtocol() { + @Inject + public FifotrackProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_REQUEST_PHOTO); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new FifotrackFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new FifotrackProtocolEncoder(FifotrackProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java index 5f9326a61..a9d77b46e 100644 --- a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java @@ -19,8 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -35,6 +34,10 @@ import org.traccar.model.WifiAccessPoint; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; import java.util.regex.Pattern; public class FifotrackProtocolDecoder extends BaseProtocolDecoder { @@ -79,7 +82,7 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { .text("$$") .number("d+,") // length .number("(d+),") // imei - .number("x+,") // index + .number("(x+),") // index .text("A03,") // type .number("(d+)?,") // alarm .number("(dd)(dd)(dd)") // date (yymmdd) @@ -138,16 +141,20 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { .number("xx") .compile(); - private void requestPhoto(Channel channel, SocketAddress socketAddress, String imei, String file) { + private void sendResponse(Channel channel, SocketAddress remoteAddress, String imei, String content) { if (channel != null) { - String content = "1,D06," + file + "," + photo.writerIndex() + "," + Math.min(1024, photo.writableBytes()); int length = 1 + imei.length() + 1 + content.length(); String response = String.format("##%02d,%s,%s*", length, imei, content); response += Checksum.sum(response) + "\r\n"; - channel.writeAndFlush(new NetworkMessage(response, socketAddress)); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } } + private void requestPhoto(Channel channel, SocketAddress remoteAddress, String imei, String file) { + String content = "1,D06," + file + "," + photo.writerIndex() + "," + Math.min(1024, photo.writableBytes()); + sendResponse(channel, remoteAddress, imei, content); + } + private String decodeAlarm(Integer alarm) { if (alarm != null) { switch (alarm) { @@ -201,11 +208,14 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { return null; } - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + String imei = parser.next(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); if (deviceSession == null) { return null; } + String index = parser.next(); + Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); @@ -244,6 +254,11 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { position.setNetwork(network); + DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + String response = index + ",A03," + dateFormat.format(new Date()); + sendResponse(channel, remoteAddress, imei, response); + return position; } @@ -381,7 +396,7 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(getDeviceSession(channel, remoteAddress, imei).getDeviceId()); getLastLocation(position, null); - position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(imei, photo, "jpg")); + position.set(Position.KEY_IMAGE, writeMediaFile(imei, photo, "jpg")); photo.release(); photo = null; return position; diff --git a/src/main/java/org/traccar/protocol/FlespiProtocol.java b/src/main/java/org/traccar/protocol/FlespiProtocol.java index 05b105f93..374cf77e2 100644 --- a/src/main/java/org/traccar/protocol/FlespiProtocol.java +++ b/src/main/java/org/traccar/protocol/FlespiProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class FlespiProtocol extends BaseProtocol { - public FlespiProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public FlespiProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder(4096, 8192, 128 * 1024)); pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE)); diff --git a/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java index 83ca74ce5..6e6f9c700 100644 --- a/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; @@ -54,11 +54,11 @@ public class FlespiProtocolDecoder extends BaseHttpProtocolDecoder { List<Position> positions = new LinkedList<>(); for (int i = 0; i < result.size(); i++) { JsonObject message = result.getJsonObject(i); - JsonString ident = message.getJsonString("ident"); - if (ident == null) { + JsonString identifier = message.getJsonString("ident"); + if (identifier == null) { continue; } - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, ident.getString()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, identifier.getString()); if (deviceSession == null) { continue; } @@ -228,6 +228,15 @@ public class FlespiProtocolDecoder extends BaseHttpProtocolDecoder { position.set(Position.KEY_ALARM, Position.ALARM_BONNET); } return true; + case "custom.wln_accel_max": + position.set("maxAcceleration", ((JsonNumber) value).doubleValue()); + return true; + case "custom.wln_brk_max": + position.set("maxBraking", ((JsonNumber) value).doubleValue()); + return true; + case "custom.wln_crn_max": + position.set("maxCornering", ((JsonNumber) value).doubleValue()); + return true; default: return false; } diff --git a/src/main/java/org/traccar/protocol/FlexApiProtocol.java b/src/main/java/org/traccar/protocol/FlexApiProtocol.java new file mode 100644 index 000000000..088072d2d --- /dev/null +++ b/src/main/java/org/traccar/protocol/FlexApiProtocol.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import java.nio.charset.StandardCharsets; + +import javax.inject.Inject; + +public class FlexApiProtocol extends BaseProtocol { + + @Inject + public FlexApiProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new LineBasedFrameDecoder(5120)); + pipeline.addLast(new StringDecoder(StandardCharsets.US_ASCII)); + pipeline.addLast(new FlexApiProtocolDecoder(FlexApiProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java new file mode 100644 index 000000000..2dec44e64 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FlexApiProtocolDecoder.java @@ -0,0 +1,173 @@ +/* + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.session.DeviceSession; +import org.traccar.Protocol; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import javax.json.Json; +import javax.json.JsonObject; +import java.io.StringReader; +import java.net.SocketAddress; +import java.util.Date; + +public class FlexApiProtocolDecoder extends BaseProtocolDecoder { + + public FlexApiProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String message = (String) msg; + JsonObject root = Json.createReader(new StringReader(message.substring(1, message.length() - 2))).readObject(); + + String topic = root.getString("topic"); + String clientId = topic.substring(3, topic.indexOf('/', 3)); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, clientId); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + JsonObject payload = root.getJsonObject("payload"); + + if (topic.contains("/gnss/")) { + + position.setValid(true); + + if (payload.containsKey("time")) { + position.setTime(new Date(payload.getInt("time") * 1000L)); + position.setLatitude(payload.getJsonNumber("lat").doubleValue()); + position.setLongitude(payload.getJsonNumber("log").doubleValue()); + } else { + position.setTime(new Date(payload.getInt("gnss.ts") * 1000L)); + position.setLatitude(payload.getJsonNumber("gnss.latitude").doubleValue()); + position.setLongitude(payload.getJsonNumber("gnss.longitude").doubleValue()); + } + + position.setValid(payload.getInt("gnss.fix") > 0); + position.setAltitude(payload.getJsonNumber("gnss.altitude").doubleValue()); + position.setSpeed(payload.getJsonNumber("gnss.speed").doubleValue()); + position.setCourse(payload.getJsonNumber("gnss.heading").doubleValue()); + + position.set(Position.KEY_SATELLITES, payload.getInt("gnss.num_sv")); + position.set(Position.KEY_HDOP, payload.getJsonNumber("gnss.hdop").doubleValue()); + + } else if (topic.contains("/cellular1/")) { + + getLastLocation(position, new Date(payload.getInt("modem1.ts") * 1000L)); + + position.set("imei", payload.getString("modem1.imei")); + position.set("imsi", payload.getString("modem1.imsi")); + position.set(Position.KEY_ICCID, payload.getString("modem1.iccid")); + + String operator = payload.getString("modem1.operator"); + if (!operator.isEmpty()) { + CellTower cellTower = CellTower.from( + Integer.parseInt(operator.substring(0, 3)), + Integer.parseInt(operator.substring(3)), + Integer.parseInt(payload.getString("modem1.lac"), 16), + Integer.parseInt(payload.getString("modem1.cell_id"), 16), + payload.getInt("modem1.rssi")); + switch (payload.getInt("modem1.network")) { + case 1: + cellTower.setRadioType("gsm"); + break; + case 2: + cellTower.setRadioType("wcdma"); + break; + case 3: + cellTower.setRadioType("lte"); + break; + default: + break; + } + position.setNetwork(new Network(cellTower)); + } + + } else if (topic.contains("/obd/")) { + + getLastLocation(position, new Date(payload.getInt("obd.ts") * 1000L)); + + if (payload.containsKey("obd.speed")) { + position.set(Position.KEY_OBD_SPEED, payload.getJsonNumber("obd.speed").doubleValue()); + } + if (payload.containsKey("obd.odo")) { + position.set(Position.KEY_OBD_ODOMETER, payload.getInt("obd.odo")); + } + if (payload.containsKey("obd.rpm")) { + position.set(Position.KEY_RPM, payload.getInt("obd.rpm")); + } + if (payload.containsKey("obd.vin")) { + position.set(Position.KEY_VIN, payload.getString("obd.vin")); + } + + } else if (topic.contains("/motion/")) { + + getLastLocation(position, new Date(payload.getInt("motion.ts") * 1000L)); + + position.set("ax", payload.getJsonNumber("motion.ax").doubleValue()); + position.set("ay", payload.getJsonNumber("motion.ay").doubleValue()); + position.set("az", payload.getJsonNumber("motion.az").doubleValue()); + position.set("gx", payload.getJsonNumber("motion.gx").doubleValue()); + position.set("gy", payload.getJsonNumber("motion.gy").doubleValue()); + position.set("gz", payload.getJsonNumber("motion.gz").doubleValue()); + + } else if (topic.contains("/io/")) { + + getLastLocation(position, new Date(payload.getInt("io.ts") * 1000L)); + + if (payload.containsKey("io.IGT")) { + position.set(Position.KEY_IGNITION, payload.getInt("io.IGT") > 0); + } + + for (String key : payload.keySet()) { + if (key.startsWith("io.AI")) { + position.set(Position.PREFIX_ADC + key.substring(5), payload.getJsonNumber(key).doubleValue()); + } else if (key.startsWith("io.DI") && !key.endsWith("_pullup")) { + position.set(Position.PREFIX_IN + key.substring(5), payload.getInt(key) > 0); + } else if (key.startsWith("io.DO")) { + position.set(Position.PREFIX_OUT + key.substring(5), payload.getInt(key) > 0); + } + } + + } else if (topic.contains("/sysinfo/")) { + + getLastLocation(position, new Date(payload.getInt("sysinfo.ts") * 1000L)); + + position.set("serial", payload.getString("sysinfo.serial_number")); + position.set(Position.KEY_VERSION_FW, payload.getString("sysinfo.firmware_version")); + + } else { + + return null; + + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/FlexCommProtocol.java b/src/main/java/org/traccar/protocol/FlexCommProtocol.java index 9343ebeb8..5397156cb 100644 --- a/src/main/java/org/traccar/protocol/FlexCommProtocol.java +++ b/src/main/java/org/traccar/protocol/FlexCommProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class FlexCommProtocol extends BaseProtocol { - public FlexCommProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public FlexCommProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new FixedLengthFrameDecoder(2 + 2 + 101 + 5)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/FlexCommProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlexCommProtocolDecoder.java index 068c0a05c..0d8bd9373 100644 --- a/src/main/java/org/traccar/protocol/FlexCommProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FlexCommProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/FlexibleReportProtocol.java b/src/main/java/org/traccar/protocol/FlexibleReportProtocol.java index 0cd55343a..61e315af9 100644 --- a/src/main/java/org/traccar/protocol/FlexibleReportProtocol.java +++ b/src/main/java/org/traccar/protocol/FlexibleReportProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class FlexibleReportProtocol extends BaseProtocol { - public FlexibleReportProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public FlexibleReportProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new FlexibleReportProtocolDecoder(FlexibleReportProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/FlexibleReportProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlexibleReportProtocolDecoder.java index 759f2cd6f..9fcee1aeb 100644 --- a/src/main/java/org/traccar/protocol/FlexibleReportProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FlexibleReportProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/FlextrackProtocol.java b/src/main/java/org/traccar/protocol/FlextrackProtocol.java index ddd1d58f0..ebac8b4de 100644 --- a/src/main/java/org/traccar/protocol/FlextrackProtocol.java +++ b/src/main/java/org/traccar/protocol/FlextrackProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class FlextrackProtocol extends BaseProtocol { - public FlextrackProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public FlextrackProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "\r")); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/FlextrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlextrackProtocolDecoder.java index 9dce22ede..a0dac1c41 100644 --- a/src/main/java/org/traccar/protocol/FlextrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FlextrackProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/FoxProtocol.java b/src/main/java/org/traccar/protocol/FoxProtocol.java index 9bac773b5..fa45b3817 100644 --- a/src/main/java/org/traccar/protocol/FoxProtocol.java +++ b/src/main/java/org/traccar/protocol/FoxProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class FoxProtocol extends BaseProtocol { - public FoxProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public FoxProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "</fox>")); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/FoxProtocolDecoder.java b/src/main/java/org/traccar/protocol/FoxProtocolDecoder.java index 449f00022..6dd0b0e95 100644 --- a/src/main/java/org/traccar/protocol/FoxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FoxProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/FreedomProtocol.java b/src/main/java/org/traccar/protocol/FreedomProtocol.java index bc6b92d5f..dac117c04 100644 --- a/src/main/java/org/traccar/protocol/FreedomProtocol.java +++ b/src/main/java/org/traccar/protocol/FreedomProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class FreedomProtocol extends BaseProtocol { - public FreedomProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public FreedomProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/FreedomProtocolDecoder.java b/src/main/java/org/traccar/protocol/FreedomProtocolDecoder.java index 1d2dd3133..27dda1a6d 100644 --- a/src/main/java/org/traccar/protocol/FreedomProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FreedomProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/FreematicsProtocol.java b/src/main/java/org/traccar/protocol/FreematicsProtocol.java index 999b075a1..dce4994ab 100644 --- a/src/main/java/org/traccar/protocol/FreematicsProtocol.java +++ b/src/main/java/org/traccar/protocol/FreematicsProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class FreematicsProtocol extends BaseProtocol { - public FreematicsProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public FreematicsProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new FreematicsProtocolDecoder(FreematicsProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java b/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java index aded35823..4e5200f37 100644 --- a/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Checksum; diff --git a/src/main/java/org/traccar/protocol/FutureWayProtocol.java b/src/main/java/org/traccar/protocol/FutureWayProtocol.java index 73b53ee12..715dd3c9c 100644 --- a/src/main/java/org/traccar/protocol/FutureWayProtocol.java +++ b/src/main/java/org/traccar/protocol/FutureWayProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class FutureWayProtocol extends BaseProtocol { - public FutureWayProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public FutureWayProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new FutureWayFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/FutureWayProtocolDecoder.java b/src/main/java/org/traccar/protocol/FutureWayProtocolDecoder.java index c2f3781d9..57027b080 100644 --- a/src/main/java/org/traccar/protocol/FutureWayProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FutureWayProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DataConverter; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/G1rusFrameDecoder.java b/src/main/java/org/traccar/protocol/G1rusFrameDecoder.java new file mode 100644 index 000000000..8c67207ad --- /dev/null +++ b/src/main/java/org/traccar/protocol/G1rusFrameDecoder.java @@ -0,0 +1,49 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.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; + +public class G1rusFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + ByteBuf result = Unpooled.buffer(); + + while (buf.isReadable()) { + int b = buf.readUnsignedByte(); + if (b == 0x1B) { + int ext = buf.readUnsignedByte(); + if (ext == 0x00) { + result.writeByte(0x1B); + } else { + result.writeByte(0xF8); + } + } else { + result.writeByte(b); + } + } + + return result; + } + +} diff --git a/src/main/java/org/traccar/protocol/G1rusProtocol.java b/src/main/java/org/traccar/protocol/G1rusProtocol.java new file mode 100644 index 000000000..f1823762d --- /dev/null +++ b/src/main/java/org/traccar/protocol/G1rusProtocol.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class G1rusProtocol extends BaseProtocol { + + @Inject + public G1rusProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new G1rusFrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new G1rusProtocolDecoder(G1rusProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/G1rusProtocolDecoder.java b/src/main/java/org/traccar/protocol/G1rusProtocolDecoder.java new file mode 100644 index 000000000..17cfbc1eb --- /dev/null +++ b/src/main/java/org/traccar/protocol/G1rusProtocolDecoder.java @@ -0,0 +1,165 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.model.Position; +import org.traccar.session.DeviceSession; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class G1rusProtocolDecoder extends BaseProtocolDecoder { + public G1rusProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_HEARTBEAT = 0; + public static final int MSG_REGULAR = 1; + public static final int MSG_SMS_FORWARD = 2; + public static final int MSG_SERIAL = 3; + public static final int MSG_MIXED = 4; + + private String readString(ByteBuf buf) { + int length = buf.readUnsignedByte() & 0xF; + return buf.readCharSequence(length, StandardCharsets.US_ASCII).toString(); + } + + private Position decodeRegular(DeviceSession deviceSession, ByteBuf buf, int type) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.setTime(new Date((buf.readUnsignedIntLE() + 946684800) * 1000L)); + + if (BitUtil.check(type, 6)) { + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + } + + int dataMask = buf.readUnsignedShort(); + + if (BitUtil.check(dataMask, 0)) { + buf.readUnsignedByte(); // length + readString(buf); // device name + position.set(Position.KEY_VERSION_FW, readString(buf)); + position.set(Position.KEY_VERSION_HW, readString(buf)); + } + + if (BitUtil.check(dataMask, 1)) { + buf.readUnsignedByte(); // length + int locationMask = buf.readUnsignedShort(); + if (BitUtil.check(locationMask, 0)) { + int validity = buf.readUnsignedByte(); + position.set(Position.KEY_SATELLITES, BitUtil.to(validity, 5)); + position.setValid(BitUtil.between(validity, 5, 7) == 2); + } + if (BitUtil.check(locationMask, 1)) { + position.setLatitude(buf.readInt() / 1000000.0); + position.setLongitude(buf.readInt() / 1000000.0); + } + if (BitUtil.check(locationMask, 2)) { + position.setSpeed(buf.readUnsignedShort()); + } + if (BitUtil.check(locationMask, 3)) { + position.setCourse(buf.readUnsignedShort()); + } + if (BitUtil.check(locationMask, 4)) { + position.setAltitude(buf.readShort()); + } + if (BitUtil.check(locationMask, 5)) { + position.set(Position.KEY_HDOP, buf.readUnsignedShort()); + } + if (BitUtil.check(locationMask, 6)) { + position.set(Position.KEY_VDOP, buf.readUnsignedShort()); + } + } + + if (BitUtil.check(dataMask, 2)) { + buf.skipBytes(buf.readUnsignedByte()); + } + + if (BitUtil.check(dataMask, 3)) { + buf.skipBytes(buf.readUnsignedByte()); + } + + if (BitUtil.check(dataMask, 4)) { + buf.readUnsignedByte(); // length + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 110 / 4096 - 10); + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 110 / 4096 - 10); + position.set(Position.KEY_DEVICE_TEMP, buf.readUnsignedShort() * 110 / 4096 - 10); + } + + if (BitUtil.check(dataMask, 5)) { + buf.skipBytes(buf.readUnsignedByte()); + } + + if (BitUtil.check(dataMask, 7)) { + buf.skipBytes(buf.readUnsignedByte()); + } + + return position; + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // header + buf.readUnsignedByte(); // version + + int type = buf.readUnsignedByte(); + String imei = String.valueOf(buf.readLong()); + buf.readerIndex(buf.readerIndex() - 1); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + if (BitUtil.to(type, 6) == MSG_REGULAR) { + + return decodeRegular(deviceSession, buf, type); + + } else if (BitUtil.to(type, 6) == MSG_MIXED) { + + List<Position> positions = new LinkedList<>(); + while (buf.readableBytes() > 5) { + int length = buf.readUnsignedShort(); + int subtype = buf.readUnsignedByte(); + if (BitUtil.to(subtype, 6) == MSG_REGULAR) { + positions.add(decodeRegular(deviceSession, buf, subtype)); + } else { + buf.skipBytes(length - 1); + } + } + return positions.isEmpty() ? null : positions; + + } + + buf.readUnsignedShort(); // checksum + buf.readUnsignedByte(); // tail + + return null; + + } + +} diff --git a/src/main/java/org/traccar/protocol/GalileoProtocol.java b/src/main/java/org/traccar/protocol/GalileoProtocol.java index a1570c9b0..90e95574a 100644 --- a/src/main/java/org/traccar/protocol/GalileoProtocol.java +++ b/src/main/java/org/traccar/protocol/GalileoProtocol.java @@ -18,17 +18,21 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class GalileoProtocol extends BaseProtocol { - public GalileoProtocol() { + @Inject + public GalileoProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_OUTPUT_CONTROL); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new GalileoFrameDecoder()); pipeline.addLast(new GalileoProtocolEncoder(GalileoProtocol.this)); pipeline.addLast(new GalileoProtocolDecoder(GalileoProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/GalileoProtocolDecoder.java b/src/main/java/org/traccar/protocol/GalileoProtocolDecoder.java index f29fb9850..fc8a49cf5 100644 --- a/src/main/java/org/traccar/protocol/GalileoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GalileoProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,15 +20,16 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; +import org.traccar.helper.BitBuffer; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; +import org.traccar.session.DeviceSession; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; +import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -36,6 +37,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TimeZone; public class GalileoProtocolDecoder extends BaseProtocolDecoder { @@ -215,7 +217,6 @@ public class GalileoProtocolDecoder extends BaseProtocolDecoder { break; case 0xea: position.set("userDataArray", ByteBufUtil.hexDump(buf.readSlice(buf.readUnsignedByte()))); - position.set("userDataArray", ByteBufUtil.hexDump(buf.readSlice(buf.readUnsignedByte()))); break; default: buf.skipBytes(getTagLength(tag)); @@ -231,7 +232,11 @@ public class GalileoProtocolDecoder extends BaseProtocolDecoder { int header = buf.readUnsignedByte(); if (header == 0x01) { - return decodePositions(channel, remoteAddress, buf); + if (buf.getUnsignedMedium(buf.readerIndex() + 2) == 0x01001c) { + return decodeIridiumPosition(channel, remoteAddress, buf); + } else { + return decodePositions(channel, remoteAddress, buf); + } } else if (header == 0x07) { return decodePhoto(channel, remoteAddress, buf); } @@ -239,10 +244,58 @@ public class GalileoProtocolDecoder extends BaseProtocolDecoder { return null; } - private Object decodePositions( - Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws Exception { + private void decodeMinimalDataSet(Position position, ByteBuf buf) { + BitBuffer bits = new BitBuffer(buf.readSlice(10)); + bits.readUnsigned(1); + + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.set(Calendar.DAY_OF_YEAR, 1); + calendar.set(Calendar.HOUR_OF_DAY, calendar.getActualMinimum(Calendar.HOUR_OF_DAY)); + calendar.set(Calendar.MINUTE, calendar.getActualMinimum(Calendar.MINUTE)); + calendar.set(Calendar.SECOND, calendar.getActualMinimum(Calendar.SECOND)); + calendar.set(Calendar.MILLISECOND, calendar.getActualMinimum(Calendar.MILLISECOND)); + calendar.add(Calendar.SECOND, bits.readUnsigned(25)); + position.setTime(calendar.getTime()); + + position.setValid(bits.readUnsigned(1) == 0); + position.setLongitude(360 * bits.readUnsigned(22) / 4194304.0 - 180); + position.setLatitude(360 * bits.readUnsigned(21) / 2097152.0 - 90); + if (bits.readUnsigned(1) > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + } + + private Position decodeIridiumPosition(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + buf.readUnsignedShortLE(); // length + + buf.skipBytes(3); // identification header + buf.readUnsignedIntLE(); // index + + DeviceSession deviceSession = getDeviceSession( + channel, remoteAddress, buf.readSlice(15).toString(StandardCharsets.US_ASCII)); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedByte(); // session status + buf.skipBytes(4); // reserved + buf.readUnsignedIntLE(); // date and time + + buf.skipBytes(23); // coordinates block + + buf.skipBytes(3); // data tag header + decodeMinimalDataSet(position, buf); + + return position; + } + + private List<Position> decodePositions(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { - int length = (buf.readUnsignedShortLE() & 0x7fff) + 3; + int endIndex = (buf.readUnsignedShortLE() & 0x7fff) + buf.readerIndex(); List<Position> positions = new LinkedList<>(); Set<Integer> tags = new HashSet<>(); @@ -251,7 +304,7 @@ public class GalileoProtocolDecoder extends BaseProtocolDecoder { DeviceSession deviceSession = null; Position position = new Position(getProtocolName()); - while (buf.readerIndex() < length) { + while (buf.readerIndex() < endIndex) { int tag = buf.readUnsignedByte(); if (tags.contains(tag)) { @@ -287,7 +340,7 @@ public class GalileoProtocolDecoder extends BaseProtocolDecoder { if (hasLocation && position.getFixTime() != null) { positions.add(position); - } else if (position.getAttributes().containsKey(Position.KEY_RESULT)) { + } else if (position.hasAttribute(Position.KEY_RESULT)) { position.setDeviceId(deviceSession.getDeviceId()); getLastLocation(position, null); positions.add(position); @@ -322,14 +375,13 @@ public class GalileoProtocolDecoder extends BaseProtocolDecoder { } else { DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); - String uniqueId = Context.getIdentityManager().getById(deviceSession.getDeviceId()).getUniqueId(); position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); getLastLocation(position, null); - position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(uniqueId, photo, "jpg")); + position.set(Position.KEY_IMAGE, writeMediaFile(deviceSession.getUniqueId(), photo, "jpg")); photo.release(); photo = null; diff --git a/src/main/java/org/traccar/protocol/GatorProtocol.java b/src/main/java/org/traccar/protocol/GatorProtocol.java index ca81caefb..7341b69a3 100644 --- a/src/main/java/org/traccar/protocol/GatorProtocol.java +++ b/src/main/java/org/traccar/protocol/GatorProtocol.java @@ -19,20 +19,24 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class GatorProtocol extends BaseProtocol { - public GatorProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public GatorProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 2)); pipeline.addLast(new GatorProtocolDecoder(GatorProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new GatorProtocolDecoder(GatorProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java b/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java index 087861635..644caee81 100644 --- a/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; diff --git a/src/main/java/org/traccar/protocol/GenxProtocol.java b/src/main/java/org/traccar/protocol/GenxProtocol.java index c87ba946a..97d8633a0 100644 --- a/src/main/java/org/traccar/protocol/GenxProtocol.java +++ b/src/main/java/org/traccar/protocol/GenxProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class GenxProtocol extends BaseProtocol { - public GenxProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public GenxProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new GenxProtocolDecoder(GenxProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/GenxProtocolDecoder.java b/src/main/java/org/traccar/protocol/GenxProtocolDecoder.java index 2ae9de7a0..6448b6a5a 100644 --- a/src/main/java/org/traccar/protocol/GenxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GenxProtocolDecoder.java @@ -17,8 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; @@ -32,7 +31,11 @@ public class GenxProtocolDecoder extends BaseProtocolDecoder { public GenxProtocolDecoder(Protocol protocol) { super(protocol); - setReportColumns(Context.getConfig().getString(getProtocolName() + ".reportColumns", "1,2,3,4")); + } + + @Override + protected void init() { + setReportColumns(getConfig().getString(getProtocolName() + ".reportColumns", "1,2,3,4")); } public void setReportColumns(String format) { diff --git a/src/main/java/org/traccar/protocol/Gl100Protocol.java b/src/main/java/org/traccar/protocol/Gl100Protocol.java index 063e606db..e1748c9a0 100644 --- a/src/main/java/org/traccar/protocol/Gl100Protocol.java +++ b/src/main/java/org/traccar/protocol/Gl100Protocol.java @@ -21,22 +21,26 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Gl100Protocol extends BaseProtocol { - public Gl100Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Gl100Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '\0')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new Gl100ProtocolDecoder(Gl100Protocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new Gl100ProtocolDecoder(Gl100Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/Gl100ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl100ProtocolDecoder.java index ae0383e5c..789d87dad 100644 --- a/src/main/java/org/traccar/protocol/Gl100ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gl100ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/Gl200BinaryProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200BinaryProtocolDecoder.java index c3339bea5..ecd1f5bfa 100644 --- a/src/main/java/org/traccar/protocol/Gl200BinaryProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gl200BinaryProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitBuffer; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/Gl200Protocol.java b/src/main/java/org/traccar/protocol/Gl200Protocol.java index e2d0c6d2a..c7b6a8e7c 100644 --- a/src/main/java/org/traccar/protocol/Gl200Protocol.java +++ b/src/main/java/org/traccar/protocol/Gl200Protocol.java @@ -15,34 +15,37 @@ */ package org.traccar.protocol; +import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; -import io.netty.handler.codec.string.StringEncoder; +import javax.inject.Inject; public class Gl200Protocol extends BaseProtocol { - public Gl200Protocol() { + @Inject + public Gl200Protocol(Config config) { setSupportedDataCommands( Command.TYPE_POSITION_SINGLE, Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME, Command.TYPE_IDENTIFICATION, Command.TYPE_REBOOT_DEVICE); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new Gl200FrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new Gl200ProtocolEncoder(Gl200Protocol.this)); pipeline.addLast(new Gl200ProtocolDecoder(Gl200Protocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new Gl200ProtocolEncoder(Gl200Protocol.this)); pipeline.addLast(new Gl200ProtocolDecoder(Gl200Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/Gl200ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200ProtocolDecoder.java index ca1df7a13..a9736c9e7 100644 --- a/src/main/java/org/traccar/protocol/Gl200ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gl200ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,14 @@ */ package org.traccar.protocol; +import com.google.inject.Injector; import org.traccar.BaseProtocolDecoder; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.Protocol; +import javax.inject.Inject; import java.net.SocketAddress; public class Gl200ProtocolDecoder extends BaseProtocolDecoder { @@ -34,6 +36,12 @@ public class Gl200ProtocolDecoder extends BaseProtocolDecoder { binaryProtocolDecoder = new Gl200BinaryProtocolDecoder(protocol); } + @Inject + public void setInjector(Injector injector) { + injector.injectMembers(textProtocolDecoder); + injector.injectMembers(binaryProtocolDecoder); + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java index 683ba476e..517499f02 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 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,7 @@ package org.traccar.protocol; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.config.Keys; @@ -46,11 +45,15 @@ import java.util.regex.Pattern; public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { - private final boolean ignoreFixTime; + private boolean ignoreFixTime; public Gl200TextProtocolDecoder(Protocol protocol) { super(protocol); - ignoreFixTime = Context.getConfig().getBoolean(Keys.PROTOCOL_IGNORE_FIX_TIME.withPrefix(getProtocolName())); + } + + @Override + protected void init() { + ignoreFixTime = getConfig().getBoolean(Keys.PROTOCOL_IGNORE_FIX_TIME.withPrefix(getProtocolName())); } private static final Pattern PATTERN_ACK = new PatternBuilder() @@ -139,7 +142,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .number("(x+)?,") // lac .number("(x+)?,") // cid .groupEnd() - .number("(?:d+|(d+.d))?,") // odometer + .number("(?:d+|(d+.d))?,") // rssi / odometer .compile(); private static final Pattern PATTERN_OBD = new PatternBuilder() @@ -184,7 +187,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .expression("(?:([0-9A-Z]{17}),)?") // vin .expression("[^,]*,") // device name .number("(d+)?,") // power - .number("d{1,2},").optional() // report type + .number("(d{1,2}),").optional() // report type .number("d{1,2},").optional() // count .number("d*,").optional() // reserved .number("(d+),").optional() // battery @@ -208,11 +211,11 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .number("(?:d+.?d*|Inf|NaN)?,") // fuel consumption .number("(d+)?,") // fuel level .or() - .number("(d{1,7}.d)?,").optional() // odometer - .number("(d{1,3})?,") // battery - .or() .number("(-?d),") // rssi .number("(d{1,3}),") // battery + .or() + .number("(d{1,7}.d)?,").optional() // odometer + .number("(d{1,3})?,") // battery .groupEnd() .any() .number("(dddd)(dd)(dd)") // date (yyyymmdd) @@ -349,6 +352,22 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .text("$").optional() .compile(); + private static final Pattern PATTERN_DAR = new PatternBuilder() + .text("+RESP:GTDAR,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("(d),") // warning type + .number("(d{1,2}),,,") // fatigue degree + .expression(PATTERN_LOCATION.pattern()) + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private static final Pattern PATTERN = new PatternBuilder() .text("+").expression("(?:RESP|BUFF):GT...,") .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version @@ -357,15 +376,16 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .number("d*,") .number("(x{1,2}),") // report type .number("d{1,2},") // count + .number("d*,").optional() // reserved .expression(PATTERN_LOCATION.pattern()) .groupBegin() - .number("(d{1,7}.d)?,").optional() // odometer + .number("(?:(d{1,7}.d)|0)?,").optional() // odometer .number("(d{1,3})?,") // battery .or() .number("(d{1,7}.d)?,") // odometer .groupEnd() .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)") // time (hhmmss) + .number("(dd)(dd)(dd)") // time (hhmmss) .text(",") .number("(xxxx)") // count number .text("$").optional() @@ -377,6 +397,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .number("(?:[0-9A-Z]{2}xxxx)?,").optional() // protocol version .number("(d{15}|x{14}),") // imei .any() + .text(",") .number("(d{1,2})?,") // hdop .number("(d{1,3}.d)?,") // speed .number("(d{1,3})?,") // course @@ -835,6 +856,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { String vin = parser.next(); Integer power = parser.nextInt(); + Integer reportType = parser.nextInt(); Integer battery = parser.nextInt(); Parser itemParser = new Parser(PATTERN_LOCATION, parser.next()); @@ -877,12 +899,18 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_RPM, parser.nextInt()); position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); + if (parser.hasNext(2)) { + if (reportType != null) { + position.set(Position.KEY_MOTION, BitUtil.check(reportType, 0)); + position.set(Position.KEY_CHARGE, BitUtil.check(reportType, 1)); + } + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + } if (parser.hasNext()) { position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); } position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); - position.set(Position.KEY_RSSI, parser.nextInt()); - position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); decodeDeviceTime(position, parser); if (ignoreFixTime) { @@ -1114,6 +1142,29 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private Object decodeDar(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_DAR, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + int warningType = parser.nextInt(); + int fatigueDegree = parser.nextInt(); + if (warningType == 1) { + position.set(Position.KEY_ALARM, Position.ALARM_FATIGUE_DRIVING); + position.set("fatigueDegree", fatigueDegree); + } else { + position.set("warningType", warningType); + } + + decodeLocation(position, parser); + + decodeDeviceTime(position, parser); + + return position; + } + private Object decodeOther(Channel channel, SocketAddress remoteAddress, String sentence, String type) { Parser parser = new Parser(PATTERN, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1305,6 +1356,9 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { case "PFA": result = decodePna(channel, remoteAddress, sentence); break; + case "DAR": + result = decodeDar(channel, remoteAddress, sentence); + break; default: result = decodeOther(channel, remoteAddress, sentence, type); break; @@ -1325,7 +1379,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { } } - if (channel != null && Context.getConfig().getBoolean(Keys.PROTOCOL_ACK.withPrefix(getProtocolName()))) { + if (channel != null && getConfig().getBoolean(Keys.PROTOCOL_ACK.withPrefix(getProtocolName()))) { String checksum; if (sentence.endsWith("$")) { checksum = sentence.substring(sentence.length() - 1 - 4, sentence.length() - 1); diff --git a/src/main/java/org/traccar/protocol/GlobalSatProtocol.java b/src/main/java/org/traccar/protocol/GlobalSatProtocol.java index e86b5dc30..16b99f426 100644 --- a/src/main/java/org/traccar/protocol/GlobalSatProtocol.java +++ b/src/main/java/org/traccar/protocol/GlobalSatProtocol.java @@ -21,18 +21,22 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class GlobalSatProtocol extends BaseProtocol { - public GlobalSatProtocol() { + @Inject + public GlobalSatProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_ALARM_DISMISS, Command.TYPE_OUTPUT_CONTROL); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "!\r\n", "!")); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/GlobalSatProtocolDecoder.java b/src/main/java/org/traccar/protocol/GlobalSatProtocolDecoder.java index b48df4047..720b61695 100644 --- a/src/main/java/org/traccar/protocol/GlobalSatProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GlobalSatProtocolDecoder.java @@ -17,8 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -40,9 +39,12 @@ public class GlobalSatProtocolDecoder extends BaseProtocolDecoder { public GlobalSatProtocolDecoder(Protocol protocol) { super(protocol); + } - format0 = Context.getConfig().getString(getProtocolName() + ".format0", "TSPRXAB27GHKLMnaicz*U!"); - format1 = Context.getConfig().getString(getProtocolName() + ".format1", "SARY*U!"); + @Override + protected void init() { + format0 = getConfig().getString(getProtocolName() + ".format0", "TSPRXAB27GHKLMnaicz*U!"); + format1 = getConfig().getString(getProtocolName() + ".format1", "SARY*U!"); } public void setFormat0(String format) { diff --git a/src/main/java/org/traccar/protocol/GlobalstarProtocol.java b/src/main/java/org/traccar/protocol/GlobalstarProtocol.java index 84cd5565b..293f5fda5 100644 --- a/src/main/java/org/traccar/protocol/GlobalstarProtocol.java +++ b/src/main/java/org/traccar/protocol/GlobalstarProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class GlobalstarProtocol extends BaseProtocol { - public GlobalstarProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public GlobalstarProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(65535)); diff --git a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java index b742d0cac..0ddb95c14 100644 --- a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java @@ -27,7 +27,7 @@ import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import org.traccar.BaseHttpProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -151,15 +151,11 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder { position.setCourse(BitUtil.from(flags, 5) * 45); - position.setLatitude(buf.readUnsignedMedium() * 90.0 / (1 << 23)); - if (position.getLatitude() > 90) { - position.setLatitude(position.getLatitude() - 180); - } + double latitude = buf.readUnsignedMedium() * 90.0 / (1 << 23); + position.setLatitude(latitude > 90 ? latitude - 180 : latitude); - position.setLongitude(buf.readUnsignedMedium() * 180.0 / (1 << 23)); - if (position.getLongitude() > 180) { - position.setLongitude(position.getLongitude() - 360); - } + double longitude = buf.readUnsignedMedium() * 180.0 / (1 << 23); + position.setLongitude(longitude > 180 ? longitude - 360 : longitude); int speed = buf.readUnsignedByte(); position.setSpeed(UnitsConverter.knotsFromKph(speed)); diff --git a/src/main/java/org/traccar/protocol/GnxProtocol.java b/src/main/java/org/traccar/protocol/GnxProtocol.java index 3576bf805..32d642688 100644 --- a/src/main/java/org/traccar/protocol/GnxProtocol.java +++ b/src/main/java/org/traccar/protocol/GnxProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class GnxProtocol extends BaseProtocol { - public GnxProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public GnxProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "\n\r")); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/GnxProtocolDecoder.java b/src/main/java/org/traccar/protocol/GnxProtocolDecoder.java index c9c221a69..9c8b6879a 100644 --- a/src/main/java/org/traccar/protocol/GnxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GnxProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/GoSafeProtocol.java b/src/main/java/org/traccar/protocol/GoSafeProtocol.java index aaaffac97..607931500 100644 --- a/src/main/java/org/traccar/protocol/GoSafeProtocol.java +++ b/src/main/java/org/traccar/protocol/GoSafeProtocol.java @@ -21,22 +21,26 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class GoSafeProtocol extends BaseProtocol { - public GoSafeProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public GoSafeProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new GoSafeProtocolDecoder(GoSafeProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new GoSafeProtocolDecoder(GoSafeProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java b/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java index a86249224..77649a041 100644 --- a/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/GotopProtocol.java b/src/main/java/org/traccar/protocol/GotopProtocol.java index 07fe02248..53fcea0d0 100644 --- a/src/main/java/org/traccar/protocol/GotopProtocol.java +++ b/src/main/java/org/traccar/protocol/GotopProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class GotopProtocol extends BaseProtocol { - public GotopProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public GotopProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/GotopProtocolDecoder.java b/src/main/java/org/traccar/protocol/GotopProtocolDecoder.java index a867451aa..5c8d0bac2 100644 --- a/src/main/java/org/traccar/protocol/GotopProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GotopProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; @@ -35,7 +35,7 @@ public class GotopProtocolDecoder extends BaseProtocolDecoder { private static final Pattern PATTERN = new PatternBuilder() .number("(d+),") // imei - .expression("[^,]+,") // type + .expression("([^,]+),") // type .expression("([AV]),") // validity .number("DATE:(dd)(dd)(dd),") // date (yyddmm) .number("TIME:(dd)(dd)(dd),") // time (hhmmss) @@ -56,14 +56,25 @@ public class GotopProtocolDecoder 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()); + String type = parser.next(); + if (type.equals("CMD-KEY")) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } else if (type.startsWith("ALM-B")) { + if (Character.getNumericValue(type.charAt(5)) % 2 > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_ENTER); + } else { + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_EXIT); + } + } + position.setValid(parser.next().equals("A")); position.setTime(parser.nextDateTime()); diff --git a/src/main/java/org/traccar/protocol/Gps056Protocol.java b/src/main/java/org/traccar/protocol/Gps056Protocol.java index b6ab10a19..dbffbfdbb 100644 --- a/src/main/java/org/traccar/protocol/Gps056Protocol.java +++ b/src/main/java/org/traccar/protocol/Gps056Protocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Gps056Protocol extends BaseProtocol { - public Gps056Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Gps056Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new Gps056FrameDecoder()); pipeline.addLast(new Gps056ProtocolDecoder(Gps056Protocol.this)); } diff --git a/src/main/java/org/traccar/protocol/Gps056ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gps056ProtocolDecoder.java index 0ba79bb51..eea64364e 100644 --- a/src/main/java/org/traccar/protocol/Gps056ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gps056ProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/Gps103Protocol.java b/src/main/java/org/traccar/protocol/Gps103Protocol.java index 5356387ce..2725494c5 100644 --- a/src/main/java/org/traccar/protocol/Gps103Protocol.java +++ b/src/main/java/org/traccar/protocol/Gps103Protocol.java @@ -21,11 +21,15 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class Gps103Protocol extends BaseProtocol { - public Gps103Protocol() { + @Inject + public Gps103Protocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_POSITION_SINGLE, @@ -36,9 +40,9 @@ public class Gps103Protocol extends BaseProtocol { Command.TYPE_ALARM_ARM, Command.TYPE_ALARM_DISARM, Command.TYPE_REQUEST_PHOTO); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(2048, false, "\r\n", "\n", ";", "*")); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); @@ -46,9 +50,9 @@ public class Gps103Protocol extends BaseProtocol { pipeline.addLast(new Gps103ProtocolDecoder(Gps103Protocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new Gps103ProtocolEncoder(Gps103Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java index d74f19179..28efa3c30 100644 --- a/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java @@ -19,8 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DataConverter; @@ -57,9 +56,12 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder { .groupEnd() .expression("([^,]+)?,") // rfid .groupBegin() - .text("L,,,") + .text("L,") + .groupBegin() + .text(",,") .number("(x+),,") // lac .number("(x+),,,") // cid + .groupEnd("?") .or() .text("F,") .groupBegin() @@ -203,7 +205,7 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder { position.set(Position.PREFIX_TEMP + 1, Double.parseDouble(alarm.substring(2))); } else if (alarm.startsWith("oil ")) { position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(alarm.substring(4))); - } else if (!position.getAttributes().containsKey(Position.KEY_ALARM) && !alarm.equals("tracker")) { + } else if (!position.hasAttribute(Position.KEY_ALARM) && !alarm.equals("tracker")) { position.set(Position.KEY_EVENT, alarm); } @@ -219,12 +221,11 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder { } if (parser.hasNext(2)) { + position.setNetwork(new Network(CellTower.fromLacCid( + getConfig(), parser.nextHexInt(0), parser.nextHexInt(0)))); + } - getLastLocation(position, null); - - position.setNetwork(new Network(CellTower.fromLacCid(parser.nextHexInt(0), parser.nextHexInt(0)))); - - } else { + if (parser.hasNext(20)) { String utcHours = parser.next(); String utcMinutes = parser.next(); @@ -262,6 +263,10 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder { position.set("fuel2", parser.nextDouble()); position.set(Position.PREFIX_TEMP + 1, parser.nextInt()); + } else { + + getLastLocation(position, null); + } return position; @@ -361,7 +366,7 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder { getLastLocation(position, null); try { - position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(imei, photo, "jpg")); + position.set(Position.KEY_IMAGE, writeMediaFile(imei, photo, "jpg")); } finally { photoPackets = 0; photo.release(); diff --git a/src/main/java/org/traccar/protocol/Gps103ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Gps103ProtocolEncoder.java index e662e9b04..9a899eeeb 100644 --- a/src/main/java/org/traccar/protocol/Gps103ProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/Gps103ProtocolEncoder.java @@ -49,7 +49,7 @@ public class Gps103ProtocolEncoder extends StringProtocolEncoder implements Stri case Command.TYPE_CUSTOM: return formatCommand(command, "**,imei:%s,%s", Command.KEY_UNIQUE_ID, Command.KEY_DATA); case Command.TYPE_POSITION_STOP: - return formatCommand(command, "**,imei:%s,A", Command.KEY_UNIQUE_ID); + return formatCommand(command, "**,imei:%s,D", Command.KEY_UNIQUE_ID); case Command.TYPE_POSITION_SINGLE: return formatCommand(command, "**,imei:%s,B", Command.KEY_UNIQUE_ID); case Command.TYPE_POSITION_PERIODIC: diff --git a/src/main/java/org/traccar/protocol/GpsGateProtocol.java b/src/main/java/org/traccar/protocol/GpsGateProtocol.java index a131b6f48..a6a73ae6b 100644 --- a/src/main/java/org/traccar/protocol/GpsGateProtocol.java +++ b/src/main/java/org/traccar/protocol/GpsGateProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class GpsGateProtocol extends BaseProtocol { - public GpsGateProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public GpsGateProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "\0", "\n", "\r\n")); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/GpsGateProtocolDecoder.java b/src/main/java/org/traccar/protocol/GpsGateProtocolDecoder.java index c158d3212..82da58f1e 100644 --- a/src/main/java/org/traccar/protocol/GpsGateProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GpsGateProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Checksum; diff --git a/src/main/java/org/traccar/protocol/GpsMarkerProtocol.java b/src/main/java/org/traccar/protocol/GpsMarkerProtocol.java index ad23ece48..12b53342c 100644 --- a/src/main/java/org/traccar/protocol/GpsMarkerProtocol.java +++ b/src/main/java/org/traccar/protocol/GpsMarkerProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class GpsMarkerProtocol extends BaseProtocol { - public GpsMarkerProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public GpsMarkerProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "\r")); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/GpsMarkerProtocolDecoder.java b/src/main/java/org/traccar/protocol/GpsMarkerProtocolDecoder.java index bbb2c31e2..0fef4b7da 100644 --- a/src/main/java/org/traccar/protocol/GpsMarkerProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GpsMarkerProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/GpsmtaProtocol.java b/src/main/java/org/traccar/protocol/GpsmtaProtocol.java index ce6cc5929..a474b1e53 100644 --- a/src/main/java/org/traccar/protocol/GpsmtaProtocol.java +++ b/src/main/java/org/traccar/protocol/GpsmtaProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class GpsmtaProtocol extends BaseProtocol { - public GpsmtaProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public GpsmtaProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new GpsmtaProtocolDecoder(GpsmtaProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/GpsmtaProtocolDecoder.java b/src/main/java/org/traccar/protocol/GpsmtaProtocolDecoder.java index 31f9401b4..a9b85d255 100644 --- a/src/main/java/org/traccar/protocol/GpsmtaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GpsmtaProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/GranitProtocol.java b/src/main/java/org/traccar/protocol/GranitProtocol.java index 244c3977b..bb66501e2 100644 --- a/src/main/java/org/traccar/protocol/GranitProtocol.java +++ b/src/main/java/org/traccar/protocol/GranitProtocol.java @@ -19,11 +19,15 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class GranitProtocol extends BaseProtocol { - public GranitProtocol() { + @Inject + public GranitProtocol(Config config) { setSupportedDataCommands( Command.TYPE_IDENTIFICATION, Command.TYPE_REBOOT_DEVICE, @@ -32,9 +36,9 @@ public class GranitProtocol extends BaseProtocol { setSupportedTextCommands( Command.TYPE_REBOOT_DEVICE, Command.TYPE_POSITION_PERIODIC); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new GranitFrameDecoder()); pipeline.addLast(new GranitProtocolEncoder(GranitProtocol.this)); pipeline.addLast(new GranitProtocolDecoder(GranitProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/GranitProtocolDecoder.java b/src/main/java/org/traccar/protocol/GranitProtocolDecoder.java index 292e43a0e..dfc3c10f6 100644 --- a/src/main/java/org/traccar/protocol/GranitProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GranitProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/Gs100Protocol.java b/src/main/java/org/traccar/protocol/Gs100Protocol.java index a701815d0..425ca9330 100644 --- a/src/main/java/org/traccar/protocol/Gs100Protocol.java +++ b/src/main/java/org/traccar/protocol/Gs100Protocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Gs100Protocol extends BaseProtocol { - public Gs100Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Gs100Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new Gs100ProtocolDecoder(Gs100Protocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/Gs100ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gs100ProtocolDecoder.java index 2496aad48..352070107 100644 --- a/src/main/java/org/traccar/protocol/Gs100ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gs100ProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; diff --git a/src/main/java/org/traccar/protocol/Gt02Protocol.java b/src/main/java/org/traccar/protocol/Gt02Protocol.java index f412ee720..fa05761f6 100644 --- a/src/main/java/org/traccar/protocol/Gt02Protocol.java +++ b/src/main/java/org/traccar/protocol/Gt02Protocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Gt02Protocol extends BaseProtocol { - public Gt02Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Gt02Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(256, 2, 1, 2, 0)); pipeline.addLast(new Gt02ProtocolDecoder(Gt02Protocol.this)); } diff --git a/src/main/java/org/traccar/protocol/Gt02ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt02ProtocolDecoder.java index 78a3fd3ee..4ecb0b43b 100644 --- a/src/main/java/org/traccar/protocol/Gt02ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gt02ProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/Gt06Protocol.java b/src/main/java/org/traccar/protocol/Gt06Protocol.java index 9ec8de098..38278121c 100644 --- a/src/main/java/org/traccar/protocol/Gt06Protocol.java +++ b/src/main/java/org/traccar/protocol/Gt06Protocol.java @@ -18,18 +18,22 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class Gt06Protocol extends BaseProtocol { - public Gt06Protocol() { + @Inject + public Gt06Protocol(Config config) { setSupportedDataCommands( Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME, Command.TYPE_CUSTOM); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new Gt06FrameDecoder()); pipeline.addLast(new Gt06ProtocolEncoder(Gt06Protocol.this)); pipeline.addLast(new Gt06ProtocolDecoder(Gt06Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java index 0dcdab892..ef09677bf 100644 --- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; @@ -32,7 +31,6 @@ import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; import org.traccar.helper.UnitsConverter; import org.traccar.model.CellTower; -import org.traccar.model.Device; import org.traccar.model.Network; import org.traccar.model.Position; import org.traccar.model.WifiAccessPoint; @@ -70,19 +68,20 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_GPS_LBS_STATUS_3 = 0x27; public static final int MSG_LBS_MULTIPLE_1 = 0x28; public static final int MSG_LBS_MULTIPLE_2 = 0x2E; + public static final int MSG_LBS_MULTIPLE_3 = 0x24; public static final int MSG_LBS_WIFI = 0x2C; public static final int MSG_LBS_EXTEND = 0x18; public static final int MSG_LBS_STATUS = 0x19; public static final int MSG_GPS_PHONE = 0x1A; - public static final int MSG_GPS_LBS_EXTEND = 0x1E; - public static final int MSG_HEARTBEAT = 0x23; - public static final int MSG_ADDRESS_REQUEST = 0x2A; - public static final int MSG_ADDRESS_RESPONSE = 0x97; - public static final int MSG_GPS_LBS_5 = 0x31; - public static final int MSG_GPS_LBS_STATUS_4 = 0x32; - public static final int MSG_WIFI_5 = 0x33; - public static final int MSG_AZ735_GPS = 0x32; // only extended - public static final int MSG_AZ735_ALARM = 0x33; // only extended + public static final int MSG_GPS_LBS_EXTEND = 0x1E; // JI09 + public static final int MSG_HEARTBEAT = 0x23; // GK310 + public static final int MSG_ADDRESS_REQUEST = 0x2A; // GK310 + public static final int MSG_ADDRESS_RESPONSE = 0x97; // GK310 + public static final int MSG_GPS_LBS_5 = 0x31; // AZ735 + public static final int MSG_GPS_LBS_STATUS_4 = 0x32; // AZ735 + public static final int MSG_WIFI_5 = 0x33; // AZ735 + public static final int MSG_AZ735_GPS = 0x32; // AZ735 / only extended + public static final int MSG_AZ735_ALARM = 0x33; // AZ735 / only extended public static final int MSG_X1_GPS = 0x34; public static final int MSG_X1_PHOTO_INFO = 0x35; public static final int MSG_X1_PHOTO_DATA = 0x36; @@ -92,25 +91,63 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_COMMAND_0 = 0x80; public static final int MSG_COMMAND_1 = 0x81; public static final int MSG_COMMAND_2 = 0x82; - public static final int MSG_TIME_REQUEST = 0x8A; + public static final int MSG_TIME_REQUEST = 0x8A; // GK310 public static final int MSG_INFO = 0x94; public static final int MSG_SERIAL = 0x9B; public static final int MSG_STRING_INFO = 0x21; - public static final int MSG_GPS_2 = 0xA0; - public static final int MSG_LBS_2 = 0xA1; - public static final int MSG_WIFI_3 = 0xA2; - public static final int MSG_FENCE_SINGLE = 0xA3; - public static final int MSG_FENCE_MULTI = 0xA4; - public static final int MSG_LBS_ALARM = 0xA5; - public static final int MSG_LBS_ADDRESS = 0xA7; - public static final int MSG_OBD = 0x8C; - public static final int MSG_DTC = 0x65; - public static final int MSG_PID = 0x66; - public static final int MSG_BMS = 0x20; - public static final int MSG_MULTIMEDIA = 0x21; - public static final int MSG_BMS_2 = 0x40; - public static final int MSG_MULTIMEDIA_2 = 0x41; - public static final int MSG_ALARM = 0x95; + public static final int MSG_GPS_2 = 0xA0; // GK310 + public static final int MSG_LBS_2 = 0xA1; // GK310 + public static final int MSG_WIFI_3 = 0xA2; // GK310 + public static final int MSG_FENCE_SINGLE = 0xA3; // GK310 + public static final int MSG_FENCE_MULTI = 0xA4; // GK310 + public static final int MSG_LBS_ALARM = 0xA5; // GK310 & JM-LL301 + public static final int MSG_LBS_ADDRESS = 0xA7; // GK310 + public static final int MSG_OBD = 0x8C; // FM08ABC + public static final int MSG_DTC = 0x65; // FM08ABC + public static final int MSG_PID = 0x66; // FM08ABC + public static final int MSG_BMS = 0x40; // WD-209 + public static final int MSG_MULTIMEDIA = 0x41; // WD-209 + public static final int MSG_ALARM = 0x95; // JC100 + + private enum Variant { + VXT01, + WANWAY_S20, + SR411_MINI, + GT06E_CARD, + BENWAY, + S5, + SPACE10X, + STANDARD, + OBD6, + } + + private Variant variant; + + private static final Pattern PATTERN_FUEL = new PatternBuilder() + .text("!AIOIL,") + .number("d+,") // device address + .number("d+.d+,") // output value + .number("(d+.d+),") // temperature + .expression("[^,]+,") // version + .number("dd") // back wave + .number("d") // software status code + .number("d,") // hardware status code + .number("(d+.d+),") // measured value + .expression("[01],") // movement status + .number("d+,") // excited wave times + .number("xx") // checksum + .compile(); + + private static final Pattern PATTERN_LOCATION = new PatternBuilder() + .text("Current position!") + .number("Lat:([NS])(d+.d+),") // latitude + .number("Lon:([EW])(d+.d+),") // longitude + .text("Course:").number("(d+.d+),") // course + .text("Speed:").number("(d+.d+),") // speed + .text("DateTime:") + .number("(dddd)-(dd)-(dd) +") // date + .number("(dd):(dd):(dd)") // time + .compile(); private static boolean isSupported(int type) { return hasGps(type) || hasLbs(type) || hasStatus(type); @@ -172,6 +209,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { case MSG_GPS_LBS_STATUS_2: case MSG_GPS_LBS_STATUS_3: case MSG_GPS_LBS_STATUS_4: + case MSG_LBS_ALARM: return true; default: return false; @@ -297,9 +335,26 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { } int mcc = buf.readUnsignedShort(); - int mnc = BitUtil.check(mcc, 15) || type == MSG_GPS_LBS_6 ? buf.readUnsignedShort() : buf.readUnsignedByte(); - int lac = buf.readUnsignedShort(); - long cid = type == MSG_GPS_LBS_6 ? buf.readUnsignedInt() : buf.readUnsignedMedium(); + int mnc; + if (BitUtil.check(mcc, 15) || type == MSG_GPS_LBS_6) { + mnc = buf.readUnsignedShort(); + } else { + mnc = buf.readUnsignedByte(); + } + int lac; + if (type == MSG_LBS_ALARM) { + lac = buf.readInt(); + } else { + lac = buf.readUnsignedShort(); + } + long cid; + if (type == MSG_LBS_ALARM) { + cid = buf.readLong(); + } else if (type == MSG_GPS_LBS_6) { + cid = buf.readUnsignedInt(); + } else { + cid = buf.readUnsignedMedium(); + } position.setNetwork(new Network(CellTower.from(BitUtil.to(mcc, 15), mnc, lac, cid))); @@ -310,7 +365,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return true; } - private boolean decodeStatus(Position position, ByteBuf buf, boolean batteryLevel) { + private void decodeStatus(Position position, ByteBuf buf) { int status = buf.readUnsignedByte(); @@ -332,22 +387,19 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { case 4: position.set(Position.KEY_ALARM, Position.ALARM_SOS); break; + case 6: + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE); + break; case 7: - position.set(Position.KEY_ALARM, Position.ALARM_REMOVING); + if (variant == Variant.VXT01) { + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + } else { + position.set(Position.KEY_ALARM, Position.ALARM_REMOVING); + } break; default: break; } - - if (batteryLevel) { - position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte() * 100 / 6); - } else { - position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); - } - position.set(Position.KEY_RSSI, buf.readUnsignedByte()); - position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); - - return true; } private String decodeAlarm(short value) { @@ -367,13 +419,20 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return Position.ALARM_OVERSPEED; case 0x0E: case 0x0F: + case 0x19: return Position.ALARM_LOW_BATTERY; case 0x11: return Position.ALARM_POWER_OFF; + case 0x0C: case 0x13: + case 0x25: return Position.ALARM_TAMPERING; case 0x14: return Position.ALARM_DOOR; + case 0x18: + return Position.ALARM_REMOVING; + case 0x23: + return Position.ALARM_FALL_DOWN; case 0x29: return Position.ALARM_ACCELERATION; case 0x30: @@ -383,81 +442,27 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return Position.ALARM_CORNERING; case 0x2C: return Position.ALARM_ACCIDENT; - case 0x23: - return Position.ALARM_FALL_DOWN; default: return null; } } - private static final Pattern PATTERN_FUEL = new PatternBuilder() - .text("!AIOIL,") - .number("d+,") // device address - .number("d+.d+,") // output value - .number("(d+.d+),") // temperature - .expression("[^,]+,") // version - .number("dd") // back wave - .number("d") // software status code - .number("d,") // hardware status code - .number("(d+.d+),") // measured value - .expression("[01],") // movement status - .number("d+,") // excited wave times - .number("xx") // checksum - .compile(); - - private Position decodeFuelData(Position position, String sentence) { - Parser parser = new Parser(PATTERN_FUEL, sentence); - if (!parser.matches()) { - return null; - } - - position.set(Position.PREFIX_TEMP + 1, parser.nextDouble(0)); - position.set(Position.KEY_FUEL_LEVEL, parser.nextDouble(0)); - - return position; - } - - private static final Pattern PATTERN_LOCATION = new PatternBuilder() - .text("Current position!") - .number("Lat:([NS])(d+.d+),") // latitude - .number("Lon:([EW])(d+.d+),") // longitude - .text("Course:").number("(d+.d+),") // course - .text("Speed:").number("(d+.d+),") // speed - .text("DateTime:") - .number("(dddd)-(dd)-(dd) +") // date - .number("(dd):(dd):(dd)") // time - .compile(); - - private Position decodeLocationString(Position position, String sentence) { - Parser parser = new Parser(PATTERN_LOCATION, sentence); - if (!parser.matches()) { - return null; - } - - position.setValid(true); - position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); - position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); - position.setCourse(parser.nextDouble()); - position.setSpeed(parser.nextDouble()); - position.setTime(parser.nextDateTime(Parser.DateTimeFormat.YMD_HMS)); - - return position; - } - - private Object decodeBasic(Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws Exception { + private Object decodeBasic(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { int length = buf.readUnsignedByte(); int dataLength = length - 5; int type = buf.readUnsignedByte(); + Position position = new Position(getProtocolName()); DeviceSession deviceSession = null; if (type != MSG_LOGIN) { deviceSession = getDeviceSession(channel, remoteAddress); if (deviceSession == null) { return null; } - if (deviceSession.getTimeZone() == null) { - deviceSession.setTimeZone(getTimeZone(deviceSession.getDeviceId())); + position.setDeviceId(deviceSession.getDeviceId()); + if (!deviceSession.contains(DeviceSession.KEY_TIMEZONE)) { + deviceSession.set(DeviceSession.KEY_TIMEZONE, getTimeZone(deviceSession.getDeviceId())); } } @@ -467,8 +472,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedShort(); // type deviceSession = getDeviceSession(channel, remoteAddress, imei); - if (deviceSession != null && deviceSession.getTimeZone() == null) { - deviceSession.setTimeZone(getTimeZone(deviceSession.getDeviceId())); + if (deviceSession != null && !deviceSession.contains(DeviceSession.KEY_TIMEZONE)) { + deviceSession.set(DeviceSession.KEY_TIMEZONE, getTimeZone(deviceSession.getDeviceId())); } if (dataLength > 10) { @@ -480,23 +485,21 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { offset = -offset; } if (deviceSession != null) { - TimeZone timeZone = deviceSession.getTimeZone(); + TimeZone timeZone = deviceSession.get(DeviceSession.KEY_TIMEZONE); if (timeZone.getRawOffset() == 0) { timeZone.setRawOffset(offset * 1000); - deviceSession.setTimeZone(timeZone); + deviceSession.set(DeviceSession.KEY_TIMEZONE, timeZone); } } - } if (deviceSession != null) { sendResponse(channel, false, type, buf.getShort(buf.writerIndex() - 6), null); } - } else if (type == MSG_HEARTBEAT) { + return null; - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); + } else if (type == MSG_HEARTBEAT) { getLastLocation(position, null); @@ -525,6 +528,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { content.writeBytes(response.getBytes(StandardCharsets.US_ASCII)); sendResponse(channel, true, MSG_ADDRESS_RESPONSE, 0, content); + return null; + } else if (type == MSG_TIME_REQUEST) { Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); @@ -537,44 +542,13 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { content.writeByte(calendar.get(Calendar.SECOND)); sendResponse(channel, false, MSG_TIME_REQUEST, 0, content); - } else if (type == MSG_X1_GPS || type == MSG_X1_PHOTO_INFO) { - - return decodeX1(channel, buf, deviceSession, type); - - } else if (type == MSG_WIFI || type == MSG_WIFI_2 || type == MSG_WIFI_4) { - - return decodeWifi(channel, buf, deviceSession, type); - - } else if (type == MSG_INFO) { - - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); - - getLastLocation(position, null); - - position.set(Position.KEY_POWER, buf.readShort() * 0.01); - - return position; - - } else { - - return decodeBasicOther(channel, buf, deviceSession, type, dataLength); - - } - - return null; - } - - private Object decodeX1(Channel channel, ByteBuf buf, DeviceSession deviceSession, int type) { - - if (type == MSG_X1_GPS) { + return null; - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); + } else if (type == MSG_X1_GPS) { buf.readUnsignedInt(); // data and alarm - decodeGps(position, buf, false, deviceSession.getTimeZone()); + decodeGps(position, buf, false, deviceSession.get(DeviceSession.KEY_TIMEZONE)); buf.readUnsignedShort(); // terminal info @@ -618,103 +592,111 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { photos.put(pictureId, photo); sendPhotoRequest(channel, pictureId); - } - - return null; - } + return null; - private Object decodeWifi(Channel channel, ByteBuf buf, DeviceSession deviceSession, int type) { + } else if (type == MSG_WIFI || type == MSG_WIFI_2 || type == MSG_WIFI_4) { - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); + ByteBuf time = buf.readSlice(6); + DateBuilder dateBuilder = new DateBuilder() + .setYear(BcdUtil.readInteger(time, 2)) + .setMonth(BcdUtil.readInteger(time, 2)) + .setDay(BcdUtil.readInteger(time, 2)) + .setHour(BcdUtil.readInteger(time, 2)) + .setMinute(BcdUtil.readInteger(time, 2)) + .setSecond(BcdUtil.readInteger(time, 2)); + getLastLocation(position, dateBuilder.getDate()); - ByteBuf time = buf.readSlice(6); - DateBuilder dateBuilder = new DateBuilder() - .setYear(BcdUtil.readInteger(time, 2)) - .setMonth(BcdUtil.readInteger(time, 2)) - .setDay(BcdUtil.readInteger(time, 2)) - .setHour(BcdUtil.readInteger(time, 2)) - .setMinute(BcdUtil.readInteger(time, 2)) - .setSecond(BcdUtil.readInteger(time, 2)); - getLastLocation(position, dateBuilder.getDate()); - - Network network = new Network(); - - int wifiCount; - if (type == MSG_WIFI_4) { - wifiCount = buf.readUnsignedByte(); - } else { - wifiCount = buf.getUnsignedByte(2); - } + Network network = new Network(); - for (int i = 0; i < wifiCount; i++) { + int wifiCount; if (type == MSG_WIFI_4) { - buf.skipBytes(2); + wifiCount = buf.readUnsignedByte(); + } else { + wifiCount = buf.getUnsignedByte(2); } - WifiAccessPoint wifiAccessPoint = new WifiAccessPoint(); - wifiAccessPoint.setMacAddress(String.format("%02x:%02x:%02x:%02x:%02x:%02x", - buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte(), - buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte())); - if (type != MSG_WIFI_4) { - wifiAccessPoint.setSignalStrength((int) buf.readUnsignedByte()); + + for (int i = 0; i < wifiCount; i++) { + if (type == MSG_WIFI_4) { + buf.skipBytes(2); + } + WifiAccessPoint wifiAccessPoint = new WifiAccessPoint(); + wifiAccessPoint.setMacAddress(String.format("%02x:%02x:%02x:%02x:%02x:%02x", + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte(), + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte())); + if (type != MSG_WIFI_4) { + wifiAccessPoint.setSignalStrength((int) buf.readUnsignedByte()); + } + network.addWifiAccessPoint(wifiAccessPoint); } - network.addWifiAccessPoint(wifiAccessPoint); - } - if (type != MSG_WIFI_4) { + if (type != MSG_WIFI_4) { - int cellCount = buf.readUnsignedByte(); - int mcc = buf.readUnsignedShort(); - int mnc = buf.readUnsignedByte(); - for (int i = 0; i < cellCount; i++) { - network.addCellTower(CellTower.from( - mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedShort(), buf.readUnsignedByte())); - } + int cellCount = buf.readUnsignedByte(); + int mcc = buf.readUnsignedShort(); + int mnc = buf.readUnsignedByte(); + for (int i = 0; i < cellCount; i++) { + network.addCellTower(CellTower.from( + mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedShort(), buf.readUnsignedByte())); + } + + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeShort(0x7878); + response.writeByte(0); + response.writeByte(type); + response.writeBytes(time.resetReaderIndex()); + response.writeByte('\r'); + response.writeByte('\n'); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } - if (channel != null) { - ByteBuf response = Unpooled.buffer(); - response.writeShort(0x7878); - response.writeByte(0); - response.writeByte(type); - response.writeBytes(time.resetReaderIndex()); - response.writeByte('\r'); - response.writeByte('\n'); - channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); } - } + position.setNetwork(network); - position.setNetwork(network); + return position; - return position; - } + } else if (type == MSG_INFO) { - private Object decodeBasicOther( - Channel channel, ByteBuf buf, DeviceSession deviceSession, int type, int dataLength) { + getLastLocation(position, null); - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); + position.set(Position.KEY_POWER, buf.readShort() * 0.01); + + return position; + + } else if (type == MSG_LBS_MULTIPLE_3 && variant == Variant.SR411_MINI) { + + decodeGps(position, buf, false, deviceSession.get(DeviceSession.KEY_TIMEZONE)); - if (type == MSG_LBS_STATUS && dataLength >= 18) { + decodeLbs(position, buf, type, false); - return null; // space10x multi-lbs message + position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0); + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); + + return position; - } else if (type == MSG_LBS_MULTIPLE_1 || type == MSG_LBS_MULTIPLE_2 || type == MSG_LBS_EXTEND - || type == MSG_LBS_WIFI || type == MSG_LBS_2 || type == MSG_WIFI_3 || type == MSG_WIFI_5) { + } else if (type == MSG_LBS_MULTIPLE_1 || type == MSG_LBS_MULTIPLE_2 || type == MSG_LBS_MULTIPLE_3 + || type == MSG_LBS_EXTEND || type == MSG_LBS_WIFI || type == MSG_LBS_2 + || type == MSG_WIFI_3 || type == MSG_WIFI_5) { boolean longFormat = type == MSG_LBS_2 || type == MSG_WIFI_3 || type == MSG_WIFI_5; - DateBuilder dateBuilder = new DateBuilder(deviceSession.getTimeZone()) + DateBuilder dateBuilder = new DateBuilder((TimeZone) deviceSession.get(DeviceSession.KEY_TIMEZONE)) .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); getLastLocation(position, dateBuilder.getDate()); + if (variant == Variant.WANWAY_S20) { + buf.readUnsignedByte(); // ta + } + int mcc = buf.readUnsignedShort(); int mnc = BitUtil.check(mcc, 15) ? buf.readUnsignedShort() : buf.readUnsignedByte(); Network network = new Network(); - int cellCount = type == MSG_WIFI_5 ? 6 : 7; + int cellCount = variant == Variant.WANWAY_S20 ? buf.readUnsignedByte() : type == MSG_WIFI_5 ? 6 : 7; for (int i = 0; i < cellCount; i++) { int lac = longFormat ? buf.readInt() : buf.readUnsignedShort(); int cid = longFormat ? (int) buf.readLong() : buf.readUnsignedMedium(); @@ -724,9 +706,12 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { } } - buf.readUnsignedByte(); // time leads + if (variant != Variant.WANWAY_S20) { + buf.readUnsignedByte(); // ta + } - if (type != MSG_LBS_MULTIPLE_1 && type != MSG_LBS_MULTIPLE_2 && type != MSG_LBS_2) { + if (type != MSG_LBS_MULTIPLE_1 && type != MSG_LBS_MULTIPLE_2 && type != MSG_LBS_MULTIPLE_3 + && type != MSG_LBS_2) { int wifiCount = buf.readUnsignedByte(); for (int i = 0; i < wifiCount; i++) { String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:"); @@ -753,7 +738,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { } } - } else if (type == MSG_BMS || type == MSG_BMS_2) { + } else if (type == MSG_BMS) { buf.skipBytes(8); // serial number @@ -785,20 +770,144 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return position; + } else if (type == MSG_STATUS && buf.readableBytes() == 22) { + + getLastLocation(position, null); + + buf.readUnsignedByte(); // information content + buf.readUnsignedShort(); // satellites + buf.readUnsignedByte(); // alarm + buf.readUnsignedByte(); // language + + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + + buf.readUnsignedByte(); // working mode + buf.readUnsignedShort(); // working voltage + buf.readUnsignedByte(); // reserved + buf.readUnsignedShort(); // working times + buf.readUnsignedShort(); // working time + + int value = buf.readUnsignedShort(); + double temperature = BitUtil.to(value, 15) * 0.1; + position.set(Position.PREFIX_TEMP + 1, BitUtil.check(value, 15) ? temperature : -temperature); + } else if (isSupported(type)) { - if (type == MSG_STATUS && buf.readableBytes() == 22) { - decodeHeartbeat(buf, position); + if (type == MSG_LBS_STATUS && variant == Variant.SPACE10X) { + return null; // multi-lbs message + } + + if (hasGps(type)) { + decodeGps(position, buf, false, deviceSession.get(DeviceSession.KEY_TIMEZONE)); } else { - decodeBasicUniversal(buf, deviceSession, type, position); + getLastLocation(position, null); + } + + if (hasLbs(type) && buf.readableBytes() > 6) { + decodeLbs(position, buf, type, hasStatus(type) && type != MSG_LBS_ALARM); + } + + if (hasStatus(type)) { + decodeStatus(position, buf); + if (variant == Variant.OBD6) { + int signal = buf.readUnsignedShort(); + int satellites = BitUtil.between(signal, 10, 15) + BitUtil.between(signal, 5, 10); + position.set(Position.KEY_SATELLITES, satellites); + position.set(Position.KEY_RSSI, BitUtil.to(signal, 5)); + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + buf.readUnsignedByte(); // language + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + buf.readUnsignedByte(); // working mode + position.set(Position.KEY_POWER, buf.readUnsignedShort() / 100.0); + } else { + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte() * 100 / 6); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + } + } + + if (type == MSG_GPS_LBS_1) { + if (variant == Variant.GT06E_CARD) { + 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()); + } else if (variant == Variant.BENWAY) { + 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); + } + } + } else if (variant == Variant.VXT01) { + decodeStatus(position, buf); + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + buf.readUnsignedByte(); // alarm extension + } else if (variant == Variant.S5) { + decodeStatus(position, buf); + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + position.set("oil", buf.readUnsignedShort()); + int temperature = buf.readUnsignedByte(); + if (BitUtil.check(temperature, 7)) { + temperature = -BitUtil.to(temperature, 7); + } + position.set(Position.PREFIX_TEMP + 1, temperature); + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 10); + } + } + + if ((type == MSG_GPS_LBS_2 || type == MSG_GPS_LBS_3 || type == MSG_GPS_LBS_4) + && 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 subLength = 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(subLength); + break; + } + } + + if (buf.readableBytes() == 4 + 6) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); } } else if (type == MSG_ALARM) { + boolean extendedAlarm = dataLength > 7; if (extendedAlarm) { - decodeGps(position, buf, false, false, false, deviceSession.getTimeZone()); + decodeGps(position, buf, false, false, false, deviceSession.get(DeviceSession.KEY_TIMEZONE)); } else { - DateBuilder dateBuilder = new DateBuilder(deviceSession.getTimeZone()) + DateBuilder dateBuilder = new DateBuilder((TimeZone) deviceSession.get(DeviceSession.KEY_TIMEZONE)) .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); getLastLocation(position, dateBuilder.getDate()); @@ -830,6 +939,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); break; } + } else { if (dataLength > 0) { @@ -855,114 +965,6 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return position; } - private void decodeHeartbeat(ByteBuf buf, Position position) { - - getLastLocation(position, null); - - buf.readUnsignedByte(); // information content - buf.readUnsignedShort(); // satellites - buf.readUnsignedByte(); // alarm - buf.readUnsignedByte(); // language - - position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); - - buf.readUnsignedByte(); // working mode - buf.readUnsignedShort(); // working voltage - buf.readUnsignedByte(); // reserved - buf.readUnsignedShort(); // working times - buf.readUnsignedShort(); // working time - - int value = buf.readUnsignedShort(); - double temperature = BitUtil.to(value, 15) * 0.1; - position.set(Position.PREFIX_TEMP + 1, BitUtil.check(value, 15) ? temperature : -temperature); - - } - - 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, type, hasStatus(type)); - } - - if (hasStatus(type)) { - decodeStatus(position, buf, true); - } - - 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() == 18) { - decodeStatus(position, buf, false); - position.set("oil", buf.readUnsignedShort()); - int temperature = buf.readUnsignedByte(); - if (BitUtil.check(temperature, 7)) { - temperature = -BitUtil.to(temperature, 7); - } - position.set(Position.PREFIX_TEMP + 1, temperature); - position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 10); - } - - if (type == MSG_GPS_LBS_1 && buf.readableBytes() == 2 + 6) { - int mask = buf.readUnsignedShort(); - position.set(Position.KEY_IGNITION, BitUtil.check(mask, 8 + 7)); - 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 || type == MSG_GPS_LBS_4) && 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); @@ -970,8 +972,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return null; } - if (deviceSession.getTimeZone() == null) { - deviceSession.setTimeZone(getTimeZone(deviceSession.getDeviceId())); + if (!deviceSession.contains(DeviceSession.KEY_TIMEZONE)) { + deviceSession.set(DeviceSession.KEY_TIMEZONE, getTimeZone(deviceSession.getDeviceId())); } Position position = new Position(getProtocolName()); @@ -990,7 +992,16 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { data = buf.readSlice(buf.readableBytes() - 6).toString(StandardCharsets.UTF_16BE); } - if (decodeLocationString(position, data) == null) { + Parser parser = new Parser(PATTERN_LOCATION, data); + + if (parser.matches()) { + position.setValid(true); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setCourse(parser.nextDouble()); + position.setSpeed(parser.nextDouble()); + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.YMD_HMS)); + } else { getLastLocation(position, null); position.set(Position.KEY_RESULT, data); } @@ -1004,31 +1015,62 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { getLastLocation(position, null); if (subType == 0x00) { + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort() * 0.01); return position; + } else if (subType == 0x05) { + + if (buf.readableBytes() >= 6 + 1 + 6) { + DateBuilder dateBuilder = new DateBuilder((TimeZone) deviceSession.get(DeviceSession.KEY_TIMEZONE)) + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setDeviceTime(dateBuilder.getDate()); + } + int flags = buf.readUnsignedByte(); position.set(Position.KEY_DOOR, BitUtil.check(flags, 0)); position.set(Position.PREFIX_IO + 1, BitUtil.check(flags, 2)); return position; + } else if (subType == 0x0a) { + buf.skipBytes(8); // imei buf.skipBytes(8); // imsi position.set(Position.KEY_ICCID, ByteBufUtil.hexDump(buf.readSlice(10)).replaceAll("f", "")); return position; + } else if (subType == 0x0d) { + if (buf.getByte(buf.readerIndex()) != '!') { buf.skipBytes(6); } - return decodeFuelData(position, buf.toString( + + Parser parser = new Parser(PATTERN_FUEL, buf.toString( buf.readerIndex(), buf.readableBytes() - 4 - 2, StandardCharsets.US_ASCII)); + if (!parser.matches()) { + return null; + } + + position.set(Position.PREFIX_TEMP + 1, parser.nextDouble(0)); + position.set(Position.KEY_FUEL_LEVEL, parser.nextDouble(0)); + + return position; + } else if (subType == 0x1b) { - buf.readUnsignedByte(); // header - buf.readUnsignedByte(); // type - position.set(Position.KEY_DRIVER_UNIQUE_ID, ByteBufUtil.hexDump(buf.readSlice(4))); - buf.readUnsignedByte(); // checksum - buf.readUnsignedByte(); // footer + + if (Character.isLetter(buf.getUnsignedByte(buf.readerIndex()))) { + String data = buf.readCharSequence(buf.readableBytes() - 6, StandardCharsets.US_ASCII).toString(); + position.set("serial", data.trim()); + } else { + buf.readUnsignedByte(); // header + buf.readUnsignedByte(); // type + position.set(Position.KEY_DRIVER_UNIQUE_ID, ByteBufUtil.hexDump(buf.readSlice(4))); + buf.readUnsignedByte(); // checksum + buf.readUnsignedByte(); // footer + } return position; + } } else if (type == MSG_X1_PHOTO_DATA) { @@ -1043,15 +1085,13 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { if (photo.writableBytes() > 0) { sendPhotoRequest(channel, pictureId); } else { - Device device = Context.getDeviceManager().getById(deviceSession.getDeviceId()); - position.set( - Position.KEY_IMAGE, Context.getMediaManager().writeFile(device.getUniqueId(), photo, "jpg")); + position.set(Position.KEY_IMAGE, writeMediaFile(deviceSession.getUniqueId(), photo, "jpg")); photos.remove(pictureId).release(); } } else if (type == MSG_AZ735_GPS || type == MSG_AZ735_ALARM) { - if (!decodeGps(position, buf, true, deviceSession.getTimeZone())) { + if (!decodeGps(position, buf, true, deviceSession.get(DeviceSession.KEY_TIMEZONE))) { getLastLocation(position, position.getDeviceTime()); } @@ -1096,7 +1136,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { } else if (type == MSG_OBD) { - DateBuilder dateBuilder = new DateBuilder(deviceSession.getTimeZone()) + DateBuilder dateBuilder = new DateBuilder((TimeZone) deviceSession.get(DeviceSession.KEY_TIMEZONE)) .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); @@ -1143,132 +1183,111 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { } else if (type == MSG_GPS_MODULAR) { - return decodeExtendedModular(channel, buf, deviceSession); - - } else { - - return decodeExtendedOther(channel, buf, deviceSession, type); - - } - - return null; - } - - private Object decodeExtendedModular(Channel channel, ByteBuf buf, DeviceSession deviceSession) { - - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); - - while (buf.readableBytes() > 6) { - int moduleType = buf.readUnsignedShort(); - int moduleLength = buf.readUnsignedShort(); - - switch (moduleType) { - case 0x03: - position.set(Position.KEY_ICCID, ByteBufUtil.hexDump(buf.readSlice(10))); - break; - case 0x09: - position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); - break; - case 0x0a: - position.set(Position.KEY_SATELLITES_VISIBLE, buf.readUnsignedByte()); - break; - case 0x11: - CellTower cellTower = CellTower.from( - buf.readUnsignedShort(), - buf.readUnsignedShort(), - buf.readUnsignedShort(), - buf.readUnsignedMedium(), - buf.readUnsignedByte()); - if (cellTower.getCellId() > 0) { - position.setNetwork(new Network(cellTower)); - } - break; - case 0x18: - position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); - break; - case 0x28: - position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1); - break; - case 0x29: - position.set(Position.KEY_INDEX, buf.readUnsignedInt()); - break; - case 0x2a: - int input = buf.readUnsignedByte(); - position.set(Position.KEY_DOOR, BitUtil.to(input, 4) > 0); - position.set("tamper", BitUtil.from(input, 4) > 0); - break; - case 0x2b: - int event = buf.readUnsignedByte(); - switch (event) { - case 0x11: - position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); - break; - case 0x12: - position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); - break; - case 0x13: - position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); - break; - case 0x14: - position.set(Position.KEY_ALARM, Position.ALARM_REMOVING); - break; - default: - break; - } - position.set(Position.KEY_EVENT, event); - break; - case 0x2e: - position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); - break; - case 0x33: - position.setTime(new Date(buf.readUnsignedInt() * 1000)); - position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); - position.setAltitude(buf.readShort()); - - double latitude = buf.readUnsignedInt() / 60.0 / 30000.0; - double longitude = buf.readUnsignedInt() / 60.0 / 30000.0; - position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); - - int flags = buf.readUnsignedShort(); - position.setCourse(BitUtil.to(flags, 10)); - position.setValid(BitUtil.check(flags, 12)); + while (buf.readableBytes() > 6) { + int moduleType = buf.readUnsignedShort(); + int moduleLength = buf.readUnsignedShort(); - if (!BitUtil.check(flags, 10)) { - latitude = -latitude; - } - if (BitUtil.check(flags, 11)) { - longitude = -longitude; - } - - position.setLatitude(latitude); - position.setLongitude(longitude); - break; - case 0x34: - position.set(Position.KEY_EVENT, buf.readUnsignedByte()); - buf.readUnsignedIntLE(); // time - buf.skipBytes(buf.readUnsignedByte()); // content - break; - default: - buf.skipBytes(moduleLength); - break; + switch (moduleType) { + case 0x03: + position.set(Position.KEY_ICCID, ByteBufUtil.hexDump(buf.readSlice(10))); + break; + case 0x09: + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + break; + case 0x0a: + position.set(Position.KEY_SATELLITES_VISIBLE, buf.readUnsignedByte()); + break; + case 0x11: + CellTower cellTower = CellTower.from( + buf.readUnsignedShort(), + buf.readUnsignedShort(), + buf.readUnsignedShort(), + buf.readUnsignedMedium(), + buf.readUnsignedByte()); + if (cellTower.getCellId() > 0) { + position.setNetwork(new Network(cellTower)); + } + break; + case 0x18: + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); + break; + case 0x28: + position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1); + break; + case 0x29: + position.set(Position.KEY_INDEX, buf.readUnsignedInt()); + break; + case 0x2a: + int input = buf.readUnsignedByte(); + position.set(Position.KEY_DOOR, BitUtil.to(input, 4) > 0); + position.set("tamper", BitUtil.from(input, 4) > 0); + break; + case 0x2b: + int event = buf.readUnsignedByte(); + switch (event) { + case 0x11: + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + case 0x12: + position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); + break; + case 0x13: + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + break; + case 0x14: + position.set(Position.KEY_ALARM, Position.ALARM_REMOVING); + break; + default: + break; + } + position.set(Position.KEY_EVENT, event); + break; + case 0x2e: + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + break; + case 0x33: + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.setAltitude(buf.readShort()); + + double latitude = buf.readUnsignedInt() / 60.0 / 30000.0; + double longitude = buf.readUnsignedInt() / 60.0 / 30000.0; + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + + int flags = buf.readUnsignedShort(); + position.setCourse(BitUtil.to(flags, 10)); + position.setValid(BitUtil.check(flags, 12)); + + if (!BitUtil.check(flags, 10)) { + latitude = -latitude; + } + if (BitUtil.check(flags, 11)) { + longitude = -longitude; + } + + position.setLatitude(latitude); + position.setLongitude(longitude); + break; + case 0x34: + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + buf.readUnsignedIntLE(); // time + buf.skipBytes(buf.readUnsignedByte()); // content + break; + default: + buf.skipBytes(moduleLength); + break; + } } - } - - if (position.getFixTime() == null) { - getLastLocation(position, null); - } - - sendResponse(channel, false, MSG_GPS_MODULAR, buf.readUnsignedShort(), null); - return position; - } + if (position.getFixTime() == null) { + getLastLocation(position, null); + } - private Object decodeExtendedOther(Channel channel, ByteBuf buf, DeviceSession deviceSession, int type) { + sendResponse(channel, false, MSG_GPS_MODULAR, buf.readUnsignedShort(), null); - Position position = null; + return position; - if (type == MSG_MULTIMEDIA || type == MSG_MULTIMEDIA_2) { + } else if (type == MSG_MULTIMEDIA) { buf.skipBytes(8); // serial number long timestamp = buf.readUnsignedInt() * 1000; @@ -1301,9 +1320,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); getLastLocation(position, new Date(timestamp)); - Device device = Context.getDeviceManager().getById(deviceSession.getDeviceId()); - position.set(Position.KEY_IMAGE, - Context.getMediaManager().writeFile(device.getUniqueId(), photo, "jpg")); + position.set(Position.KEY_IMAGE, writeMediaFile(deviceSession.getUniqueId(), photo, "jpg")); photos.remove(mediaId).release(); } } @@ -1343,21 +1360,56 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return null; } + private void decodeVariant(ByteBuf buf) { + int header = buf.getUnsignedShort(buf.readerIndex()); + int length; + int type; + if (header == 0x7878) { + length = buf.getUnsignedByte(buf.readerIndex() + 2); + type = buf.getUnsignedByte(buf.readerIndex() + 2 + 1); + } else { + length = buf.getUnsignedShort(buf.readerIndex() + 2); + type = buf.getUnsignedByte(buf.readerIndex() + 2 + 2); + } + + if (header == 0x7878 && type == MSG_GPS_LBS_1 && length == 0x24) { + variant = Variant.VXT01; + } else if (header == 0x7878 && type == MSG_GPS_LBS_STATUS_1 && length == 0x24) { + variant = Variant.VXT01; + } else if (header == 0x7878 && type == MSG_LBS_MULTIPLE_3 && length == 0x31) { + variant = Variant.WANWAY_S20; + } else if (header == 0x7878 && type == MSG_LBS_MULTIPLE_3 && length == 0x2e) { + variant = Variant.SR411_MINI; + } else if (header == 0x7878 && type == MSG_GPS_LBS_1 && length >= 0x71) { + variant = Variant.GT06E_CARD; + } else if (header == 0x7878 && type == MSG_GPS_LBS_1 && length == 0x21) { + variant = Variant.BENWAY; + } else if (header == 0x7878 && type == MSG_GPS_LBS_1 && length == 0x2b) { + variant = Variant.S5; + } else if (header == 0x7878 && type == MSG_LBS_STATUS && length >= 0x17) { + variant = Variant.SPACE10X; + } else if (header == 0x7878 && type == MSG_STATUS && length == 0x13) { + variant = Variant.OBD6; + } else { + variant = Variant.STANDARD; + } + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; + decodeVariant(buf); + int header = buf.readShort(); if (header == 0x7878) { return decodeBasic(channel, remoteAddress, buf); - } else if (header == 0x7979) { + } else { return decodeExtended(channel, remoteAddress, buf); } - - return null; } } diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java index 9115ba10f..dc5dd446f 100644 --- a/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,12 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.traccar.BaseProtocolEncoder; -import org.traccar.Context; +import org.traccar.Protocol; +import org.traccar.config.Keys; import org.traccar.helper.Checksum; +import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Command; -import org.traccar.Protocol; +import org.traccar.model.Device; import java.nio.charset.StandardCharsets; @@ -33,8 +35,8 @@ public class Gt06ProtocolEncoder extends BaseProtocolEncoder { private ByteBuf encodeContent(long deviceId, String content) { - boolean language = Context.getIdentityManager() - .lookupAttributeBoolean(deviceId, getProtocolName() + ".language", false, false, true); + boolean language = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_LANGUAGE.withPrefix(getProtocolName()), deviceId); ByteBuf buf = Unpooled.buffer(); @@ -43,7 +45,7 @@ public class Gt06ProtocolEncoder extends BaseProtocolEncoder { buf.writeByte(1 + 1 + 4 + content.length() + 2 + 2 + (language ? 2 : 0)); // message length - buf.writeByte(0x80); // message type + buf.writeByte(Gt06ProtocolDecoder.MSG_COMMAND_0); buf.writeByte(4 + content.length()); // command length buf.writeInt(0); @@ -66,19 +68,31 @@ public class Gt06ProtocolEncoder extends BaseProtocolEncoder { @Override protected Object encodeCommand(Command command) { - boolean alternative = Context.getIdentityManager().lookupAttributeBoolean( - command.getDeviceId(), getProtocolName() + ".alternative", false, false, true); + boolean alternative = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_ALTERNATIVE.withPrefix(getProtocolName()), command.getDeviceId()); + + String password = AttributeUtil.getDevicePassword( + getCacheManager(), command.getDeviceId(), getProtocolName(), "123456"); - String password = Context.getIdentityManager() - .getDevicePassword(command.getDeviceId(), getProtocolName(), "123456"); + Device device = getCacheManager().getObject(Device.class, command.getDeviceId()); switch (command.getType()) { case Command.TYPE_ENGINE_STOP: - return encodeContent(command.getDeviceId(), - alternative ? "DYD," + password + "#" : "Relay,1#"); + if ("G109".equals(device.getModel())) { + return encodeContent(command.getDeviceId(), "DYD#"); + } else if (alternative) { + return encodeContent(command.getDeviceId(), "DYD," + password + "#"); + } else { + return encodeContent(command.getDeviceId(), "Relay,1#"); + } case Command.TYPE_ENGINE_RESUME: - return encodeContent(command.getDeviceId(), - alternative ? "HFYD," + password + "#" : "Relay,0#"); + if ("G109".equals(device.getModel())) { + return encodeContent(command.getDeviceId(), "HFYD#"); + } else if (alternative) { + return encodeContent(command.getDeviceId(), "HFYD," + password + "#"); + } else { + return encodeContent(command.getDeviceId(), "Relay,0#"); + } case Command.TYPE_CUSTOM: return encodeContent(command.getDeviceId(), command.getString(Command.KEY_DATA)); default: diff --git a/src/main/java/org/traccar/protocol/Gt30Protocol.java b/src/main/java/org/traccar/protocol/Gt30Protocol.java index aa4ad20b1..6b79ba58b 100644 --- a/src/main/java/org/traccar/protocol/Gt30Protocol.java +++ b/src/main/java/org/traccar/protocol/Gt30Protocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Gt30Protocol extends BaseProtocol { - public Gt30Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Gt30Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/Gt30ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt30ProtocolDecoder.java index abf208a46..fb3a2b8ae 100644 --- a/src/main/java/org/traccar/protocol/Gt30ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gt30ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/H02Protocol.java b/src/main/java/org/traccar/protocol/H02Protocol.java index a5246abc6..4e5f8c96a 100644 --- a/src/main/java/org/traccar/protocol/H02Protocol.java +++ b/src/main/java/org/traccar/protocol/H02Protocol.java @@ -17,15 +17,18 @@ package org.traccar.protocol; import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; -import org.traccar.Context; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.model.Command; +import javax.inject.Inject; + public class H02Protocol extends BaseProtocol { - public H02Protocol() { + @Inject + public H02Protocol(Config config) { setSupportedDataCommands( Command.TYPE_ALARM_ARM, Command.TYPE_ALARM_DISARM, @@ -33,19 +36,19 @@ public class H02Protocol extends BaseProtocol { Command.TYPE_ENGINE_RESUME, Command.TYPE_POSITION_PERIODIC ); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { - int messageLength = Context.getConfig().getInteger(Keys.PROTOCOL_MESSAGE_LENGTH.withPrefix(getName())); + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + int messageLength = config.getInteger(Keys.PROTOCOL_MESSAGE_LENGTH.withPrefix(getName())); pipeline.addLast(new H02FrameDecoder(messageLength)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new H02ProtocolEncoder(H02Protocol.this)); pipeline.addLast(new H02ProtocolDecoder(H02Protocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new H02ProtocolEncoder(H02Protocol.this)); pipeline.addLast(new H02ProtocolDecoder(H02Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java b/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java index dd7141a2c..2ad4f644b 100644 --- a/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.config.Keys; @@ -176,17 +175,19 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { .number("(d+),") // coding scheme .groupEnd() .groupBegin() - .number("-(d+)-(d+.d+),") // latitude + .number("-(d+)-(d+.d+),([NS]),") // latitude .or() - .number("(d+)(dd.d+),") // latitude + .number("(d+)(dd.d+),([NS]),") // latitude + .or() + .number("(d+)(dd)(d{4}),([NS]),") // latitude .groupEnd() - .expression("([NS]),") .groupBegin() - .number("-(d+)-(d+.d+),") // longitude + .number("-(d+)-(d+.d+),([EW]),") // longitude .or() - .number("(d+)(dd.d+),") // longitude + .number("(d+)(dd.d+),([EW]),") // longitude + .or() + .number("(d+)(dd)(d{4}),([EW]),") // longitude .groupEnd() - .expression("([EW]),") .number(" *(d+.?d*),") // speed .number("(d+.?d*)?,") // course .number("(?:d+,)?") // battery @@ -332,7 +333,7 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { if (parser.hasNext() && parser.next().equals("V1")) { sendResponse(channel, remoteAddress, id, "V1"); - } else if (Context.getConfig().getBoolean(Keys.PROTOCOL_ACK.withPrefix(getProtocolName()))) { + } else if (getConfig().getBoolean(Keys.PROTOCOL_ACK.withPrefix(getProtocolName()))) { sendResponse(channel, remoteAddress, id, "R12"); } @@ -349,19 +350,25 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { position.setValid(true); } - if (parser.hasNext(2)) { - position.setLatitude(-parser.nextCoordinate()); + if (parser.hasNext(3)) { + position.setLatitude(parser.nextCoordinate()); } - if (parser.hasNext(2)) { + if (parser.hasNext(3)) { position.setLatitude(parser.nextCoordinate()); } + if (parser.hasNext(4)) { + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN_HEM)); + } - if (parser.hasNext(2)) { - position.setLongitude(-parser.nextCoordinate()); + if (parser.hasNext(3)) { + position.setLongitude(parser.nextCoordinate()); } - if (parser.hasNext(2)) { + if (parser.hasNext(3)) { position.setLongitude(parser.nextCoordinate()); } + if (parser.hasNext(4)) { + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN_HEM)); + } position.setSpeed(parser.nextDouble(0)); position.setCourse(parser.nextDouble(0)); @@ -384,7 +391,8 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { position.setAltitude(parser.nextInt(0)); - position.setNetwork(new Network(CellTower.fromLacCid(parser.nextHexInt(0), parser.nextHexInt(0)))); + position.setNetwork(new Network(CellTower.fromLacCid( + getConfig(), parser.nextHexInt(0), parser.nextHexInt(0)))); } if (parser.hasNext()) { diff --git a/src/main/java/org/traccar/protocol/H02ProtocolEncoder.java b/src/main/java/org/traccar/protocol/H02ProtocolEncoder.java index 7a765332c..86b8c80d4 100644 --- a/src/main/java/org/traccar/protocol/H02ProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/H02ProtocolEncoder.java @@ -6,7 +6,7 @@ * 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 + * 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, @@ -16,10 +16,11 @@ */ package org.traccar.protocol; -import org.traccar.Context; +import org.traccar.Protocol; import org.traccar.StringProtocolEncoder; +import org.traccar.config.Keys; +import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Command; -import org.traccar.Protocol; import java.util.Date; @@ -59,8 +60,9 @@ public class H02ProtocolEncoder extends StringProtocolEncoder { return formatCommand(time, uniqueId, "S20", "1", "0"); case Command.TYPE_POSITION_PERIODIC: String frequency = command.getAttributes().get(Command.KEY_FREQUENCY).toString(); - if (Context.getIdentityManager().lookupAttributeBoolean( - command.getDeviceId(), getProtocolName() + ".alternative", false, false, true)) { + if (AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_ALTERNATIVE.withPrefix(getProtocolName()), + command.getDeviceId())) { return formatCommand(time, uniqueId, "D1", frequency); } else { return formatCommand(time, uniqueId, "S71", "22", frequency); diff --git a/src/main/java/org/traccar/protocol/HaicomProtocol.java b/src/main/java/org/traccar/protocol/HaicomProtocol.java index 6e5760bd4..f56c605f0 100644 --- a/src/main/java/org/traccar/protocol/HaicomProtocol.java +++ b/src/main/java/org/traccar/protocol/HaicomProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class HaicomProtocol extends BaseProtocol { - public HaicomProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public HaicomProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '*')); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/HaicomProtocolDecoder.java b/src/main/java/org/traccar/protocol/HaicomProtocolDecoder.java index dd20f2aeb..9903e7735 100644 --- a/src/main/java/org/traccar/protocol/HaicomProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HaicomProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/HomtecsProtocol.java b/src/main/java/org/traccar/protocol/HomtecsProtocol.java index 34dbf0f51..aa2d7d852 100644 --- a/src/main/java/org/traccar/protocol/HomtecsProtocol.java +++ b/src/main/java/org/traccar/protocol/HomtecsProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class HomtecsProtocol extends BaseProtocol { - public HomtecsProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public HomtecsProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new HomtecsProtocolDecoder(HomtecsProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/HomtecsProtocolDecoder.java b/src/main/java/org/traccar/protocol/HomtecsProtocolDecoder.java index a93572b5c..5541cb065 100644 --- a/src/main/java/org/traccar/protocol/HomtecsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HomtecsProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/HoopoProtocol.java b/src/main/java/org/traccar/protocol/HoopoProtocol.java index 387b967d3..02d8e5a8e 100644 --- a/src/main/java/org/traccar/protocol/HoopoProtocol.java +++ b/src/main/java/org/traccar/protocol/HoopoProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class HoopoProtocol extends BaseProtocol { - public HoopoProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public HoopoProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new JsonFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/HoopoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HoopoProtocolDecoder.java index 5db7f0bc0..708c74f2a 100644 --- a/src/main/java/org/traccar/protocol/HoopoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HoopoProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.model.Position; @@ -52,7 +52,7 @@ public class HoopoProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - Date time = new Date(OffsetDateTime.parse(eventData.getString("receiveTime")).toInstant().toEpochMilli()); + Date time = new Date(OffsetDateTime.parse(json.getString("eventTime")).toInstant().toEpochMilli()); position.setTime(time); position.setValid(true); @@ -62,6 +62,10 @@ public class HoopoProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_EVENT, eventData.getString("eventType")); position.set(Position.KEY_BATTERY_LEVEL, eventData.getInt("batteryLevel")); + if (json.containsKey("movement")) { + position.setSpeed(json.getJsonObject("movement").getInt("Speed")); + } + return position; } diff --git a/src/main/java/org/traccar/protocol/HuaShengProtocol.java b/src/main/java/org/traccar/protocol/HuaShengProtocol.java index 103f2d501..b1b61e977 100644 --- a/src/main/java/org/traccar/protocol/HuaShengProtocol.java +++ b/src/main/java/org/traccar/protocol/HuaShengProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class HuaShengProtocol extends BaseProtocol { - public HuaShengProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public HuaShengProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HuaShengFrameDecoder()); pipeline.addLast(new HuaShengProtocolDecoder(HuaShengProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java index 891046213..371691d82 100644 --- a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocol.java b/src/main/java/org/traccar/protocol/HuabaoProtocol.java index 791672b85..c37918b0e 100644 --- a/src/main/java/org/traccar/protocol/HuabaoProtocol.java +++ b/src/main/java/org/traccar/protocol/HuabaoProtocol.java @@ -18,17 +18,21 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class HuabaoProtocol extends BaseProtocol { - public HuabaoProtocol() { + @Inject + public HuabaoProtocol(Config config) { setSupportedDataCommands( Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HuabaoFrameDecoder()); pipeline.addLast(new HuabaoProtocolEncoder(HuabaoProtocol.this)); pipeline.addLast(new HuabaoProtocolDecoder(HuabaoProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java index 871410c44..d0bbeebb5 100644 --- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; @@ -34,7 +34,10 @@ import org.traccar.model.Position; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.TimeZone; @@ -48,11 +51,13 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_GENERAL_RESPONSE = 0x8001; public static final int MSG_GENERAL_RESPONSE_2 = 0x4401; public static final int MSG_HEARTBEAT = 0x0002; + public static final int MSG_HEARTBEAT_2 = 0x0506; public static final int MSG_TERMINAL_REGISTER = 0x0100; public static final int MSG_TERMINAL_REGISTER_RESPONSE = 0x8100; public static final int MSG_TERMINAL_CONTROL = 0x8105; public static final int MSG_TERMINAL_AUTH = 0x0102; public static final int MSG_LOCATION_REPORT = 0x0200; + public static final int MSG_LOCATION_BATCH_2 = 0x0210; public static final int MSG_ACCELERATION = 0x2070; public static final int MSG_LOCATION_REPORT_2 = 0x5501; public static final int MSG_LOCATION_REPORT_BLIND = 0x5502; @@ -61,6 +66,7 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_TIME_SYNC_REQUEST = 0x0109; public static final int MSG_TIME_SYNC_RESPONSE = 0x8109; public static final int MSG_PHOTO = 0x8888; + public static final int MSG_TRANSPARENT = 0x0900; public static final int RESULT_SUCCESS = 0; @@ -119,6 +125,9 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { || BitUtil.check(value, 10) || BitUtil.check(value, 11)) { return Position.ALARM_FAULT; } + if (BitUtil.check(value, 7) || BitUtil.check(value, 18)) { + return Position.ALARM_LOW_BATTERY; + } if (BitUtil.check(value, 8)) { return Position.ALARM_POWER_OFF; } @@ -142,6 +151,28 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { return BitUtil.check(value, 15) ? -BitUtil.to(value, 15) : BitUtil.to(value, 15); } + private Date readDate(ByteBuf buf, TimeZone timeZone) { + DateBuilder dateBuilder = new DateBuilder(timeZone) + .setYear(BcdUtil.readInteger(buf, 2)) + .setMonth(BcdUtil.readInteger(buf, 2)) + .setDay(BcdUtil.readInteger(buf, 2)) + .setHour(BcdUtil.readInteger(buf, 2)) + .setMinute(BcdUtil.readInteger(buf, 2)) + .setSecond(BcdUtil.readInteger(buf, 2)); + return dateBuilder.getDate(); + } + + private String decodeId(ByteBuf id) { + String serial = ByteBufUtil.hexDump(id); + if (serial.matches("[0-9]+")) { + return serial; + } else { + long imei = id.getUnsignedShort(0); + imei = (imei << 32) + id.getUnsignedInt(2); + return String.valueOf(imei); + } + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -149,7 +180,19 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { ByteBuf buf = (ByteBuf) msg; if (buf.getByte(buf.readerIndex()) == '(') { - return decodeResult(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII)); + String sentence = buf.toString(StandardCharsets.US_ASCII); + if (sentence.contains("BASE,2")) { + DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + String response = sentence.replace("TIME", dateFormat.format(new Date())); + if (channel != null) { + channel.writeAndFlush(new NetworkMessage( + Unpooled.copiedBuffer(response, StandardCharsets.US_ASCII), remoteAddress)); + } + return null; + } else { + return decodeResult(channel, remoteAddress, sentence); + } } buf.readUnsignedByte(); // start marker @@ -163,13 +206,13 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { index = buf.readUnsignedShort(); } - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, ByteBufUtil.hexDump(id)); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, decodeId(id)); if (deviceSession == null) { return null; } - if (deviceSession.getTimeZone() == null) { - deviceSession.setTimeZone(getTimeZone(deviceSession.getDeviceId(), "GMT+8")); + if (!deviceSession.contains(DeviceSession.KEY_TIMEZONE)) { + deviceSession.set(DeviceSession.KEY_TIMEZONE, getTimeZone(deviceSession.getDeviceId(), "GMT+8")); } if (type == MSG_TERMINAL_REGISTER) { @@ -178,12 +221,12 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { ByteBuf response = Unpooled.buffer(); response.writeShort(index); response.writeByte(RESULT_SUCCESS); - response.writeBytes(ByteBufUtil.hexDump(id).getBytes(StandardCharsets.US_ASCII)); + response.writeBytes(decodeId(id).getBytes(StandardCharsets.US_ASCII)); channel.writeAndFlush(new NetworkMessage( formatMessage(MSG_TERMINAL_REGISTER_RESPONSE, id, false, response), remoteAddress)); } - } else if (type == MSG_TERMINAL_AUTH || type == MSG_HEARTBEAT || type == MSG_PHOTO) { + } else if (type == MSG_TERMINAL_AUTH || type == MSG_HEARTBEAT || type == MSG_HEARTBEAT_2 || type == MSG_PHOTO) { sendGeneralResponse(channel, remoteAddress, id, type, index); @@ -201,11 +244,11 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { return decodeLocation2(deviceSession, buf, type); - } else if (type == MSG_LOCATION_BATCH) { + } else if (type == MSG_LOCATION_BATCH || type == MSG_LOCATION_BATCH_2) { sendGeneralResponse(channel, remoteAddress, id, type, index); - return decodeLocationBatch(deviceSession, buf); + return decodeLocationBatch(deviceSession, buf, type); } else if (type == MSG_TIME_SYNC_REQUEST) { @@ -249,6 +292,10 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { return position; + } else if (type == MSG_TRANSPARENT) { + + return decodeTransparent(deviceSession, buf); + } return null; @@ -266,17 +313,83 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { return null; } - private Position decodeLocation(DeviceSession deviceSession, ByteBuf buf) { - - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); + private void decodeExtension(Position position, ByteBuf buf, int endIndex) { + while (buf.readerIndex() < endIndex) { + int type = buf.readUnsignedByte(); + int length = buf.readUnsignedByte(); + switch (type) { + case 0x01: + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 100L); + break; + case 0x02: + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShort() * 0.1); + break; + case 0x03: + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedShort() * 0.1); + break; + case 0x80: + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedByte()); + break; + case 0x81: + position.set(Position.KEY_RPM, buf.readUnsignedShort()); + break; + case 0x82: + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1); + break; + case 0x83: + position.set(Position.KEY_ENGINE_LOAD, buf.readUnsignedByte()); + break; + case 0x84: + position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedByte() - 40); + break; + case 0x85: + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort()); + break; + case 0x86: + position.set("intakeTemp", buf.readUnsignedByte() - 40); + break; + case 0x87: + position.set("intakeFlow", buf.readUnsignedShort()); + break; + case 0x88: + position.set("intakePressure", buf.readUnsignedByte()); + break; + case 0x89: + position.set(Position.KEY_THROTTLE, buf.readUnsignedByte()); + break; + case 0x8B: + position.set(Position.KEY_VIN, buf.readCharSequence(17, StandardCharsets.US_ASCII).toString()); + break; + case 0x8C: + position.set(Position.KEY_OBD_ODOMETER, buf.readUnsignedInt() * 100L); + break; + case 0x8D: + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedShort() * 1000L); + break; + case 0x8E: + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte()); + break; + case 0xA0: + String codes = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString(); + position.set(Position.KEY_DTCS, codes.replace(',', ' ')); + break; + case 0xCC: + position.set(Position.KEY_ICCID, buf.readCharSequence(20, StandardCharsets.US_ASCII).toString()); + break; + default: + buf.skipBytes(length); + break; + } + } + } - position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedInt())); + private void decodeCoordinates(Position position, ByteBuf buf) { int status = buf.readInt(); position.set(Position.KEY_IGNITION, BitUtil.check(status, 0)); position.set(Position.KEY_BLOCKED, BitUtil.check(status, 10)); + position.set(Position.KEY_CHARGE, BitUtil.check(status, 26)); position.setValid(BitUtil.check(status, 1)); @@ -294,19 +407,28 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { } else { position.setLongitude(lon); } + } + + private double decodeCustomDouble(ByteBuf buf) { + int b1 = buf.readByte(); + int b2 = buf.readUnsignedByte(); + int sign = b1 != 0 ? b1 / Math.abs(b1) : 1; + return sign * (Math.abs(b1) + b2 / 255.0); + } + + private Position decodeLocation(DeviceSession deviceSession, ByteBuf buf) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedInt())); + + decodeCoordinates(position, buf); position.setAltitude(buf.readShort()); position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() * 0.1)); position.setCourse(buf.readUnsignedShort()); - - DateBuilder dateBuilder = new DateBuilder(deviceSession.getTimeZone()) - .setYear(BcdUtil.readInteger(buf, 2)) - .setMonth(BcdUtil.readInteger(buf, 2)) - .setDay(BcdUtil.readInteger(buf, 2)) - .setHour(BcdUtil.readInteger(buf, 2)) - .setMinute(BcdUtil.readInteger(buf, 2)) - .setSecond(BcdUtil.readInteger(buf, 2)); - position.setTime(dateBuilder.getDate()); + position.setTime(readDate(buf, deviceSession.get(DeviceSession.KEY_TIMEZONE))); if (buf.readableBytes() == 20) { @@ -333,6 +455,9 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { case 0x02: position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShort() * 0.1); break; + case 0x2b: + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedInt()); + break; case 0x30: position.set(Position.KEY_RSSI, buf.readUnsignedByte()); break; @@ -346,6 +471,11 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY, Integer.parseInt(lockStatus.substring(2, 5)) * 0.01); } break; + case 0x80: + buf.readUnsignedByte(); // content + endIndex = buf.writerIndex() - 2; + decodeExtension(position, buf, endIndex); + break; case 0x91: position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.1); position.set(Position.KEY_RPM, buf.readUnsignedShort()); @@ -383,11 +513,21 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1); break; case 0xD4: - case 0xFE: position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); break; case 0xD5: - position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); + if (length == 2) { + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); + } else { + int count = buf.readUnsignedByte(); + for (int i = 1; i <= count; i++) { + position.set("lock" + i + "Id", ByteBufUtil.hexDump(buf.readSlice(5))); + position.set("lock" + i + "Card", ByteBufUtil.hexDump(buf.readSlice(5))); + position.set("lock" + i + "Battery", buf.readUnsignedByte()); + int status = buf.readUnsignedShort(); + position.set("lock" + i + "Locked", !BitUtil.check(status, 5)); + } + } break; case 0xDA: buf.readUnsignedShort(); // string cut count @@ -396,6 +536,14 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_MOTION, BitUtil.check(deviceStatus, 2)); position.set("cover", BitUtil.check(deviceStatus, 3)); break; + case 0xE6: + while (buf.readerIndex() < endIndex) { + int sensorIndex = buf.readUnsignedByte(); + buf.skipBytes(6); // mac + position.set(Position.PREFIX_TEMP + sensorIndex, decodeCustomDouble(buf)); + position.set("humidity" + sensorIndex, decodeCustomDouble(buf)); + } + break; case 0xEB: if (buf.getUnsignedShort(buf.readerIndex()) > 200) { Network network = new Network(); @@ -423,6 +571,16 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { case 0x00CE: position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); break; + case 0x00D8: + Network network = new Network(); + network.addCellTower(CellTower.from( + buf.readUnsignedShort(), buf.readUnsignedByte(), + buf.readUnsignedShort(), buf.readUnsignedInt())); + position.setNetwork(network); + break; + case 0xE1: + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + break; default: buf.skipBytes(extendedLength - 2); break; @@ -440,6 +598,105 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.001); position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); break; + case 0xF3: + while (buf.readerIndex() < endIndex) { + int extendedType = buf.readUnsignedShort(); + int extendedLength = buf.readUnsignedByte(); + switch (extendedType) { + case 0x0002: + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedShort() * 0.1); + break; + case 0x0003: + position.set(Position.KEY_RPM, buf.readUnsignedShort()); + break; + case 0x0004: + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.001); + break; + case 0x0005: + position.set(Position.KEY_OBD_ODOMETER, buf.readUnsignedInt() * 100); + break; + case 0x0007: + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort() * 0.1); + break; + case 0x0008: + position.set(Position.KEY_ENGINE_LOAD, buf.readUnsignedShort() * 0.1); + break; + case 0x0009: + position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedShort() - 40); + break; + case 0x000B: + position.set("intakePressure", buf.readUnsignedShort()); + break; + case 0x000C: + position.set("intakeTemp", buf.readUnsignedShort() - 40); + break; + case 0x000D: + position.set("intakeFlow", buf.readUnsignedShort()); + break; + case 0x000E: + position.set(Position.KEY_THROTTLE, buf.readUnsignedShort() * 100 / 255); + break; + case 0x0050: + position.set(Position.KEY_VIN, buf.readSlice(17).toString(StandardCharsets.US_ASCII)); + break; + case 0x0100: + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedShort() * 0.1); + break; + case 0x0102: + position.set("tripFuel", buf.readUnsignedShort() * 0.1); + break; + case 0x0112: + position.set("hardAccelerationCount", buf.readUnsignedShort()); + break; + case 0x0113: + position.set("hardDecelerationCount", buf.readUnsignedShort()); + break; + case 0x0114: + position.set("hardCorneringCount", buf.readUnsignedShort()); + break; + default: + buf.skipBytes(extendedLength); + break; + } + } + break; + case 0xFE: + if (length == 1) { + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + } else { + int mark = buf.readUnsignedByte(); + if (mark == 0x7C) { + while (buf.readerIndex() < endIndex) { + int extendedType = buf.readUnsignedByte(); + int extendedLength = buf.readUnsignedByte(); + switch (extendedType) { + case 0x01: + long alarms = buf.readUnsignedInt(); + if (BitUtil.check(alarms, 0)) { + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + } + if (BitUtil.check(alarms, 1)) { + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + } + if (BitUtil.check(alarms, 2)) { + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + } + if (BitUtil.check(alarms, 3)) { + position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT); + } + if (BitUtil.check(alarms, 4)) { + position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING); + } + break; + default: + buf.skipBytes(extendedLength); + break; + } + } + } + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + } + break; default: break; } @@ -468,7 +725,8 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_CHARGE, true); } - position.setNetwork(new Network(CellTower.fromCidLac(buf.readUnsignedInt(), buf.readUnsignedShort()))); + position.setNetwork(new Network(CellTower.fromCidLac( + getConfig(), buf.readUnsignedInt(), buf.readUnsignedShort()))); int product = buf.readUnsignedByte(); int status = buf.readUnsignedShort(); @@ -496,24 +754,141 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { return position; } - private List<Position> decodeLocationBatch(DeviceSession deviceSession, ByteBuf buf) { + private List<Position> decodeLocationBatch(DeviceSession deviceSession, ByteBuf buf, int type) { List<Position> positions = new LinkedList<>(); - int count = buf.readUnsignedShort(); - int locationType = buf.readUnsignedByte(); + int locationType = 0; + if (type == MSG_LOCATION_BATCH) { + buf.readUnsignedShort(); // count + locationType = buf.readUnsignedByte(); + } - for (int i = 0; i < count; i++) { - int endIndex = buf.readUnsignedShort() + buf.readerIndex(); - Position position = decodeLocation(deviceSession, buf); - if (locationType == 0) { + while (buf.readableBytes() > 2) { + int length = type == MSG_LOCATION_BATCH_2 ? buf.readUnsignedByte() : buf.readUnsignedShort(); + ByteBuf fragment = buf.readSlice(length); + Position position = decodeLocation(deviceSession, fragment); + if (locationType > 0) { position.set(Position.KEY_ARCHIVE, true); } positions.add(position); - buf.readerIndex(endIndex); } return positions; } + private Position decodeTransparent(DeviceSession deviceSession, ByteBuf buf) { + + int type = buf.readUnsignedByte(); + + if (type == 0xF0) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + Date time = readDate(buf, deviceSession.get(DeviceSession.KEY_TIMEZONE)); + + if (buf.readUnsignedByte() > 0) { + position.set(Position.KEY_ARCHIVE, true); + } + + buf.readUnsignedByte(); // vehicle type + + int count; + int subtype = buf.readUnsignedByte(); + switch (subtype) { + case 0x01: + count = buf.readUnsignedByte(); + for (int i = 0; i < count; i++) { + int id = buf.readUnsignedShort(); + int length = buf.readUnsignedByte(); + switch (id) { + case 0x0102: + case 0x0528: + case 0x0546: + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 100); + break; + case 0x0103: + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedInt() * 0.01); + break; + case 0x052A: + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShort() * 0.01); + break; + case 0x0105: + case 0x052C: + position.set(Position.KEY_FUEL_USED, buf.readUnsignedInt() * 0.01); + break; + case 0x014A: + case 0x0537: + case 0x0538: + case 0x0539: + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort() * 0.01); + break; + default: + switch (length) { + case 1: + position.set(Position.PREFIX_IO + id, buf.readUnsignedByte()); + break; + case 2: + position.set(Position.PREFIX_IO + id, buf.readUnsignedShort()); + break; + case 4: + position.set(Position.PREFIX_IO + id, buf.readUnsignedInt()); + break; + default: + buf.skipBytes(length); + break; + } + break; + } + } + decodeCoordinates(position, buf); + position.setTime(time); + break; + case 0x03: + count = buf.readUnsignedByte(); + for (int i = 0; i < count; i++) { + int id = buf.readUnsignedShort(); + int length = buf.readUnsignedByte(); + switch (id) { + case 0x1A: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 0x1B: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 0x1C: + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + break; + case 0x1D: + case 0x1E: + case 0x1F: + position.set(Position.KEY_ALARM, Position.ALARM_LANE_CHANGE); + break; + case 0x23: + position.set(Position.KEY_ALARM, Position.ALARM_FATIGUE_DRIVING); + break; + default: + break; + } + buf.skipBytes(length); + } + decodeCoordinates(position, buf); + position.setTime(time); + break; + case 0x0B: + if (buf.readUnsignedByte() > 0) { + position.set(Position.KEY_VIN, buf.readCharSequence(17, StandardCharsets.US_ASCII).toString()); + } + getLastLocation(position, time); + break; + default: + return null; + } + + return position; + } + + return null; + } + } diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolEncoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolEncoder.java index 55c1e0c3b..ada7e3fba 100644 --- a/src/main/java/org/traccar/protocol/HuabaoProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/HuabaoProtocolEncoder.java @@ -18,10 +18,11 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.traccar.BaseProtocolEncoder; -import org.traccar.Context; +import org.traccar.Protocol; +import org.traccar.config.Keys; import org.traccar.helper.DataConverter; +import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Command; -import org.traccar.Protocol; import java.text.SimpleDateFormat; import java.util.Date; @@ -35,8 +36,8 @@ public class HuabaoProtocolEncoder extends BaseProtocolEncoder { @Override protected Object encodeCommand(Command command) { - boolean alternative = Context.getIdentityManager().lookupAttributeBoolean( - command.getDeviceId(), getProtocolName() + ".alternative", false, false, true); + boolean alternative = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_ALTERNATIVE.withPrefix(getProtocolName()), command.getDeviceId()); ByteBuf id = Unpooled.wrappedBuffer( DataConverter.parseHex(getUniqueId(command.getDeviceId()))); diff --git a/src/main/java/org/traccar/protocol/HunterProProtocol.java b/src/main/java/org/traccar/protocol/HunterProProtocol.java index 9f6424a57..ed4289d73 100644 --- a/src/main/java/org/traccar/protocol/HunterProProtocol.java +++ b/src/main/java/org/traccar/protocol/HunterProProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class HunterProProtocol extends BaseProtocol { - public HunterProProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public HunterProProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "\r")); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/HunterProProtocolDecoder.java b/src/main/java/org/traccar/protocol/HunterProProtocolDecoder.java index 06bc12d59..eada1fd9a 100644 --- a/src/main/java/org/traccar/protocol/HunterProProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HunterProProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/IdplProtocol.java b/src/main/java/org/traccar/protocol/IdplProtocol.java index 418178756..aa1f4ff5b 100644 --- a/src/main/java/org/traccar/protocol/IdplProtocol.java +++ b/src/main/java/org/traccar/protocol/IdplProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class IdplProtocol extends BaseProtocol { - public IdplProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public IdplProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/IdplProtocolDecoder.java b/src/main/java/org/traccar/protocol/IdplProtocolDecoder.java index cf3c03d7f..72409b168 100644 --- a/src/main/java/org/traccar/protocol/IdplProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/IdplProtocolDecoder.java @@ -20,7 +20,7 @@ import java.util.regex.Pattern; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.Parser.CoordinateFormat; diff --git a/src/main/java/org/traccar/protocol/IntellitracProtocol.java b/src/main/java/org/traccar/protocol/IntellitracProtocol.java index 3abf40da7..b1a91cca9 100644 --- a/src/main/java/org/traccar/protocol/IntellitracProtocol.java +++ b/src/main/java/org/traccar/protocol/IntellitracProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class IntellitracProtocol extends BaseProtocol { - public IntellitracProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public IntellitracProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new IntellitracFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/IntellitracProtocolDecoder.java b/src/main/java/org/traccar/protocol/IntellitracProtocolDecoder.java index 930d4f23b..b86584016 100644 --- a/src/main/java/org/traccar/protocol/IntellitracProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/IntellitracProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/IotmProtocol.java b/src/main/java/org/traccar/protocol/IotmProtocol.java index f202d9b9d..0d288f4bf 100644 --- a/src/main/java/org/traccar/protocol/IotmProtocol.java +++ b/src/main/java/org/traccar/protocol/IotmProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.mqtt.MqttEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class IotmProtocol extends BaseProtocol { - public IotmProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public IotmProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(MqttEncoder.INSTANCE); pipeline.addLast(new MqttDecoder()); pipeline.addLast(new IotmProtocolDecoder(IotmProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java b/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java index 9c94ffd4b..7bbe6c8de 100644 --- a/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/IotmProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import io.netty.handler.codec.mqtt.MqttMessageBuilders; import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttSubscribeMessage; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.UnitsConverter; @@ -260,7 +260,7 @@ public class IotmProtocolDecoder extends BaseProtocolDecoder { MqttSubscribeMessage message = (MqttSubscribeMessage) msg; MqttMessage response = MqttMessageBuilders.subAck() - .packetId((short) message.variableHeader().messageId()) + .packetId(message.variableHeader().messageId()) .build(); if (channel != null) { @@ -339,7 +339,7 @@ public class IotmProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedByte(); // checksum MqttMessage response = MqttMessageBuilders.pubAck() - .packetId((short) message.variableHeader().packetId()) + .packetId(message.variableHeader().packetId()) .build(); if (channel != null) { diff --git a/src/main/java/org/traccar/protocol/ItsProtocol.java b/src/main/java/org/traccar/protocol/ItsProtocol.java index 45df3da11..5148e8ab0 100644 --- a/src/main/java/org/traccar/protocol/ItsProtocol.java +++ b/src/main/java/org/traccar/protocol/ItsProtocol.java @@ -20,17 +20,21 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class ItsProtocol extends BaseProtocol { - public ItsProtocol() { + @Inject + public ItsProtocol(Config config) { setSupportedDataCommands( Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new ItsFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java index 9eed58347..8a8d734cf 100644 --- a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; @@ -205,7 +205,8 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { if (parser.hasNext()) { position.setValid(parser.nextInt() == 1); } - position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setTime(parser.nextDateTime( + Parser.DateTimeFormat.DMY_HMS, getTimeZone(deviceSession.getDeviceId()).getID())); if (parser.hasNext()) { position.setValid(parser.next().matches("[1A]")); } diff --git a/src/main/java/org/traccar/protocol/Ivt401Protocol.java b/src/main/java/org/traccar/protocol/Ivt401Protocol.java index fb44e4fe9..763457641 100644 --- a/src/main/java/org/traccar/protocol/Ivt401Protocol.java +++ b/src/main/java/org/traccar/protocol/Ivt401Protocol.java @@ -20,13 +20,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Ivt401Protocol extends BaseProtocol { - public Ivt401Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Ivt401Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ';')); pipeline.addLast(new StringDecoder()); pipeline.addLast(new Ivt401ProtocolDecoder(Ivt401Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/Ivt401ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Ivt401ProtocolDecoder.java index 63556e7a9..972f22ebe 100644 --- a/src/main/java/org/traccar/protocol/Ivt401ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Ivt401ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/JidoProtocol.java b/src/main/java/org/traccar/protocol/JidoProtocol.java new file mode 100644 index 000000000..78aa6c81c --- /dev/null +++ b/src/main/java/org/traccar/protocol/JidoProtocol.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.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; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class JidoProtocol extends BaseProtocol { + + @Inject + public JidoProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new JidoProtocolDecoder(JidoProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/JidoProtocolDecoder.java b/src/main/java/org/traccar/protocol/JidoProtocolDecoder.java new file mode 100644 index 000000000..98fb36e11 --- /dev/null +++ b/src/main/java/org/traccar/protocol/JidoProtocolDecoder.java @@ -0,0 +1,128 @@ +/* + * Copyright 2021 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.session.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class JidoProtocolDecoder extends BaseProtocolDecoder { + + public JidoProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("*") + .number("(d+),") // imei + .number("(d+),") // command + .expression("([AV]),").optional() // validity + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .expression("([EW]),") + .groupBegin() + .number("(d+),") // speed + .number("(d+),") // odometer + .number("(d+),") // course + .number("(-?d+),") // altitude + .number("(d+),") // satellites + .number("d+,") // gsm 1 + .number("d+,") // gsm 2 + .number("([01]),") // charging + .number("(d+),") // battery level + .expression("([YKN]),") // mode + .number("([01]),") // lock + .number("[^,]+,") // accelerometer x + .number("[^,]+,") // accelerometer y + .number("[^,]+,") // accelerometer z + .or() + .expression("[^,]*,") // data + .groupEnd() + .number("xx") // checksum + .compile(); + + private String decodeAlarm(int type) { + switch (type) { + case 3: + return Position.ALARM_LOW_BATTERY; + case 4: + return Position.ALARM_TAMPERING; + default: + return null; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_ALARM, decodeAlarm(parser.nextInt())); + + if (parser.hasNext()) { + position.setValid(parser.next().equals("A")); + } else { + position.setValid(true); + } + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + if (parser.hasNext(9)) { + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + + position.set(Position.KEY_ODOMETER, parser.nextInt()); + + position.setCourse(parser.nextInt()); + position.setAltitude(parser.nextInt()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_CHARGE, parser.nextInt() > 0); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + position.set("mode", parser.next()); + position.set(Position.KEY_BLOCKED, parser.nextInt() > 0); + + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/JpKorjarProtocol.java b/src/main/java/org/traccar/protocol/JpKorjarProtocol.java index fe5b2480d..30c8e9977 100644 --- a/src/main/java/org/traccar/protocol/JpKorjarProtocol.java +++ b/src/main/java/org/traccar/protocol/JpKorjarProtocol.java @@ -1,6 +1,6 @@ /* + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2016 Nyash (nyashh@gmail.com) - * Copyright 2018 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class JpKorjarProtocol extends BaseProtocol { - public JpKorjarProtocol() { - addServer(new TrackerServer(false, this.getName()) { + @Inject + public JpKorjarProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new JpKorjarFrameDecoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new JpKorjarProtocolDecoder(JpKorjarProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/JpKorjarProtocolDecoder.java b/src/main/java/org/traccar/protocol/JpKorjarProtocolDecoder.java index 33026918a..ffddcc568 100644 --- a/src/main/java/org/traccar/protocol/JpKorjarProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/JpKorjarProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/Jt600Protocol.java b/src/main/java/org/traccar/protocol/Jt600Protocol.java index 37c82f741..bf0b3379e 100644 --- a/src/main/java/org/traccar/protocol/Jt600Protocol.java +++ b/src/main/java/org/traccar/protocol/Jt600Protocol.java @@ -19,19 +19,23 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class Jt600Protocol extends BaseProtocol { - public Jt600Protocol() { + @Inject + public Jt600Protocol(Config config) { setSupportedDataCommands( Command.TYPE_ENGINE_RESUME, Command.TYPE_ENGINE_STOP, Command.TYPE_SET_TIMEZONE, Command.TYPE_REBOOT_DEVICE); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new Jt600FrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new Jt600ProtocolEncoder(Jt600Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java index 37c1674d4..9ed44f565 100644 --- a/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; @@ -141,7 +141,9 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder { int version = BitUtil.from(buf.readUnsignedByte(), 4); buf.readUnsignedShort(); // length - while (buf.readableBytes() > 1) { + boolean responseRequired = false; + + while (buf.readableBytes() >= 17) { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); @@ -160,6 +162,9 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ALARM, BitUtil.check(status, 2) ? Position.ALARM_GEOFENCE_EXIT : null); position.set(Position.KEY_ALARM, BitUtil.check(status, 3) ? Position.ALARM_POWER_CUT : null); position.set(Position.KEY_ALARM, BitUtil.check(status, 4) ? Position.ALARM_VIBRATION : null); + if (BitUtil.check(status, 5)) { + responseRequired = true; + } position.set(Position.KEY_BLOCKED, BitUtil.check(status, 7)); position.set(Position.KEY_ALARM, BitUtil.check(status, 8 + 3) ? Position.ALARM_LOW_BATTERY : null); position.set(Position.KEY_ALARM, BitUtil.check(status, 8 + 6) ? Position.ALARM_FAULT : null); @@ -172,7 +177,8 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY_LEVEL, battery); } - CellTower cellTower = CellTower.fromCidLac(buf.readUnsignedShort(), buf.readUnsignedShort()); + CellTower cellTower = CellTower.fromCidLac( + getConfig(), buf.readUnsignedShort(), buf.readUnsignedShort()); cellTower.setSignalStrength((int) buf.readUnsignedByte()); position.setNetwork(new Network(cellTower)); @@ -196,7 +202,7 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder { int rssi = buf.readUnsignedByte(); if (cid != 0 && lac != 0) { - CellTower cellTower = CellTower.fromCidLac(cid, lac); + CellTower cellTower = CellTower.fromCidLac(getConfig(), cid, lac); cellTower.setSignalStrength(rssi); position.setNetwork(new Network(cellTower)); } else { @@ -232,7 +238,15 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder { } - buf.readUnsignedByte(); // index + int index = buf.readUnsignedByte(); + + if (channel != null && responseRequired) { + if (protocolVersion < 0x19) { + channel.writeAndFlush(new NetworkMessage("(P35)", remoteAddress)); + } else { + channel.writeAndFlush(new NetworkMessage("(P69,0," + index + ")", remoteAddress)); + } + } return positions; } @@ -343,7 +357,7 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt(0)); position.set(Position.KEY_STATUS, parser.nextBinInt(0)); - CellTower cellTower = CellTower.fromCidLac(parser.nextInt(0), parser.nextInt(0)); + CellTower cellTower = CellTower.fromCidLac(getConfig(), parser.nextInt(0), parser.nextInt(0)); cellTower.setSignalStrength(parser.nextInt(0)); position.setNetwork(new Network(cellTower)); @@ -361,6 +375,64 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_P45 = new PatternBuilder() + .text("(") + .number("(d+),") // id + .text("P45,") // type + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d+.d+),([NS]),") // latitude + .number("(d+.d+),([EW]),") // longitude + .expression("([AV]),") // validity + .number("(d+),") // speed + .number("(d+),") // course + .number("d+,") // event source + .number("d+,") // unlock verification + .number("(d+),") // rfid + .number("d+,") // password verification + .number("d+,") // incorrect password count + .number("(d+),") // index + .any() + .compile(); + + private Position decodeP45(String sentence, Channel channel, SocketAddress remoteAddress) { + + Parser parser = new Parser(PATTERN_P45, 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.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setValid(parser.next().equals("A")); + + position.setSpeed(UnitsConverter.knotsFromMph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + + String rfid = parser.next(); + if (!rfid.equals("0000000000")) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, rfid); + } + + int index = parser.nextInt(); + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("(P69,0," + index + ")", remoteAddress)); + } + + return position; + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -374,6 +446,8 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder { String sentence = buf.toString(StandardCharsets.US_ASCII); if (sentence.contains("W01")) { return decodeW01(sentence, channel, remoteAddress); + } else if (sentence.contains("P45")) { + return decodeP45(sentence, channel, remoteAddress); } else { return decodeU01(sentence, channel, remoteAddress); } diff --git a/src/main/java/org/traccar/protocol/KenjiProtocol.java b/src/main/java/org/traccar/protocol/KenjiProtocol.java index 90c0c511c..8d78c8c56 100644 --- a/src/main/java/org/traccar/protocol/KenjiProtocol.java +++ b/src/main/java/org/traccar/protocol/KenjiProtocol.java @@ -22,13 +22,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class KenjiProtocol extends BaseProtocol { - public KenjiProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public KenjiProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/KenjiProtocolDecoder.java b/src/main/java/org/traccar/protocol/KenjiProtocolDecoder.java index 63812242a..fb989c72e 100644 --- a/src/main/java/org/traccar/protocol/KenjiProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/KenjiProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/KhdProtocol.java b/src/main/java/org/traccar/protocol/KhdProtocol.java index 60a2aea7f..521274de5 100644 --- a/src/main/java/org/traccar/protocol/KhdProtocol.java +++ b/src/main/java/org/traccar/protocol/KhdProtocol.java @@ -19,11 +19,15 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class KhdProtocol extends BaseProtocol { - public KhdProtocol() { + @Inject + public KhdProtocol(Config config) { setSupportedDataCommands( Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME, @@ -33,9 +37,9 @@ public class KhdProtocol extends BaseProtocol { Command.TYPE_SET_ODOMETER, Command.TYPE_POSITION_SINGLE); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(512, 3, 2)); pipeline.addLast(new KhdProtocolEncoder(KhdProtocol.this)); pipeline.addLast(new KhdProtocolDecoder(KhdProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java index a14f9b8a4..d7c236c4f 100644 --- a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; diff --git a/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java b/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java index 8aeb9660d..12353b415 100644 --- a/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java @@ -84,7 +84,7 @@ public class KhdProtocolEncoder extends BaseProtocolEncoder { return encodeCommand(MSG_FACTORY_RESET, uniqueId, null); case Command.TYPE_SET_SPEED_LIMIT: ByteBuf content = Unpooled.buffer(); - content.writeByte(Integer.parseInt(command.getString(Command.KEY_DATA))); + content.writeByte(command.getInteger(Command.KEY_DATA)); return encodeCommand(MSG_RESUME_OIL, uniqueId, content); case Command.TYPE_SET_ODOMETER: return encodeCommand(MSG_DELETE_MILEAGE, uniqueId, null); diff --git a/src/main/java/org/traccar/protocol/L100Protocol.java b/src/main/java/org/traccar/protocol/L100Protocol.java index 942029307..0edea6095 100644 --- a/src/main/java/org/traccar/protocol/L100Protocol.java +++ b/src/main/java/org/traccar/protocol/L100Protocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class L100Protocol extends BaseProtocol { - public L100Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public L100Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new L100FrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/L100ProtocolDecoder.java b/src/main/java/org/traccar/protocol/L100ProtocolDecoder.java index 5b5eb7d60..820de8f1c 100644 --- a/src/main/java/org/traccar/protocol/L100ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/L100ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Checksum; diff --git a/src/main/java/org/traccar/protocol/LacakProtocol.java b/src/main/java/org/traccar/protocol/LacakProtocol.java index 0a0499ad7..bbebd51ed 100644 --- a/src/main/java/org/traccar/protocol/LacakProtocol.java +++ b/src/main/java/org/traccar/protocol/LacakProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class LacakProtocol extends BaseProtocol { - public LacakProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public LacakProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(16384)); diff --git a/src/main/java/org/traccar/protocol/LacakProtocolDecoder.java b/src/main/java/org/traccar/protocol/LacakProtocolDecoder.java index 132087c8f..809fafc90 100644 --- a/src/main/java/org/traccar/protocol/LacakProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/LacakProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateUtil; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/LaipacProtocol.java b/src/main/java/org/traccar/protocol/LaipacProtocol.java index 1d561dbd2..249d3bcbe 100644 --- a/src/main/java/org/traccar/protocol/LaipacProtocol.java +++ b/src/main/java/org/traccar/protocol/LaipacProtocol.java @@ -18,23 +18,27 @@ 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.model.Command; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; +import org.traccar.model.Command; + +import javax.inject.Inject; public class LaipacProtocol extends BaseProtocol { - public LaipacProtocol() { + @Inject + public LaipacProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_POSITION_SINGLE, Command.TYPE_REBOOT_DEVICE); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java index 45890e9a2..e9570ee11 100644 --- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java @@ -17,8 +17,8 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.helper.model.AttributeUtil; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Checksum; @@ -253,8 +253,8 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder { sendAcknowledge(status, event, checksum, channel, remoteAddress); - String devicePassword = Context.getIdentityManager() - .getDevicePassword(deviceSession.getDeviceId(), getProtocolName(), DEFAULT_DEVICE_PASSWORD); + String devicePassword = AttributeUtil.getDevicePassword( + getCacheManager(), deviceSession.getDeviceId(), getProtocolName(), DEFAULT_DEVICE_PASSWORD); sendEventResponse(event, devicePassword, channel, remoteAddress); } diff --git a/src/main/java/org/traccar/protocol/LeafSpyProtocol.java b/src/main/java/org/traccar/protocol/LeafSpyProtocol.java index 05f63a2d7..7e13e23d0 100644 --- a/src/main/java/org/traccar/protocol/LeafSpyProtocol.java +++ b/src/main/java/org/traccar/protocol/LeafSpyProtocol.java @@ -22,13 +22,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class LeafSpyProtocol extends BaseProtocol { - public LeafSpyProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public LeafSpyProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(16384)); diff --git a/src/main/java/org/traccar/protocol/LeafSpyProtocolDecoder.java b/src/main/java/org/traccar/protocol/LeafSpyProtocolDecoder.java index ad0c9bd32..6affb85c5 100644 --- a/src/main/java/org/traccar/protocol/LeafSpyProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/LeafSpyProtocolDecoder.java @@ -22,7 +22,7 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.QueryStringDecoder; import org.traccar.BaseHttpProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/M2cProtocol.java b/src/main/java/org/traccar/protocol/M2cProtocol.java index 9de8526c3..a23ea0f57 100644 --- a/src/main/java/org/traccar/protocol/M2cProtocol.java +++ b/src/main/java/org/traccar/protocol/M2cProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class M2cProtocol extends BaseProtocol { - public M2cProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public M2cProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(32 * 1024, ']')); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/M2cProtocolDecoder.java b/src/main/java/org/traccar/protocol/M2cProtocolDecoder.java index 1460bb176..9415d0f07 100644 --- a/src/main/java/org/traccar/protocol/M2cProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/M2cProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/M2mProtocol.java b/src/main/java/org/traccar/protocol/M2mProtocol.java index dda328a59..6809d800c 100644 --- a/src/main/java/org/traccar/protocol/M2mProtocol.java +++ b/src/main/java/org/traccar/protocol/M2mProtocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.FixedLengthFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class M2mProtocol extends BaseProtocol { - public M2mProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public M2mProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new FixedLengthFrameDecoder(23)); pipeline.addLast(new M2mProtocolDecoder(M2mProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/M2mProtocolDecoder.java b/src/main/java/org/traccar/protocol/M2mProtocolDecoder.java index 21e4a2fd0..7eca93a59 100644 --- a/src/main/java/org/traccar/protocol/M2mProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/M2mProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/MaestroProtocol.java b/src/main/java/org/traccar/protocol/MaestroProtocol.java index 87453ce7d..38a67f9a4 100644 --- a/src/main/java/org/traccar/protocol/MaestroProtocol.java +++ b/src/main/java/org/traccar/protocol/MaestroProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class MaestroProtocol extends BaseProtocol { - public MaestroProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public MaestroProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new FixedLengthFrameDecoder(160)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/MaestroProtocolDecoder.java b/src/main/java/org/traccar/protocol/MaestroProtocolDecoder.java index 37b097414..78308658e 100644 --- a/src/main/java/org/traccar/protocol/MaestroProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MaestroProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/ManPowerProtocol.java b/src/main/java/org/traccar/protocol/ManPowerProtocol.java index 49d8b1e9f..492e86605 100644 --- a/src/main/java/org/traccar/protocol/ManPowerProtocol.java +++ b/src/main/java/org/traccar/protocol/ManPowerProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class ManPowerProtocol extends BaseProtocol { - public ManPowerProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public ManPowerProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ';')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/ManPowerProtocolDecoder.java b/src/main/java/org/traccar/protocol/ManPowerProtocolDecoder.java index 2c7b7eb40..8ac13b4d4 100644 --- a/src/main/java/org/traccar/protocol/ManPowerProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ManPowerProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/Mavlink2Protocol.java b/src/main/java/org/traccar/protocol/Mavlink2Protocol.java index d779648e4..cf65a2db3 100644 --- a/src/main/java/org/traccar/protocol/Mavlink2Protocol.java +++ b/src/main/java/org/traccar/protocol/Mavlink2Protocol.java @@ -15,18 +15,21 @@ */ package org.traccar.protocol; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; -import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import javax.inject.Inject; public class Mavlink2Protocol extends BaseProtocol { - public Mavlink2Protocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public Mavlink2Protocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 1, 1, 10, 0)); pipeline.addLast(new Mavlink2ProtocolDecoder(Mavlink2Protocol.this)); } diff --git a/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java index 431258388..fac930ba8 100644 --- a/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Mavlink2ProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/MegastekProtocol.java b/src/main/java/org/traccar/protocol/MegastekProtocol.java index e9f5f9fde..10215eb7c 100644 --- a/src/main/java/org/traccar/protocol/MegastekProtocol.java +++ b/src/main/java/org/traccar/protocol/MegastekProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class MegastekProtocol extends BaseProtocol { - public MegastekProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public MegastekProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new MegastekFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/MegastekProtocolDecoder.java b/src/main/java/org/traccar/protocol/MegastekProtocolDecoder.java index 7233280c2..06b6f0e76 100644 --- a/src/main/java/org/traccar/protocol/MegastekProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MegastekProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/MeiligaoProtocol.java b/src/main/java/org/traccar/protocol/MeiligaoProtocol.java index e8a66e49f..492094ce3 100644 --- a/src/main/java/org/traccar/protocol/MeiligaoProtocol.java +++ b/src/main/java/org/traccar/protocol/MeiligaoProtocol.java @@ -18,31 +18,36 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class MeiligaoProtocol extends BaseProtocol { - public MeiligaoProtocol() { + @Inject + public MeiligaoProtocol(Config config) { setSupportedDataCommands( Command.TYPE_POSITION_SINGLE, Command.TYPE_POSITION_PERIODIC, + Command.TYPE_OUTPUT_CONTROL, Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME, Command.TYPE_ALARM_GEOFENCE, Command.TYPE_SET_TIMEZONE, Command.TYPE_REQUEST_PHOTO, Command.TYPE_REBOOT_DEVICE); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new MeiligaoFrameDecoder()); pipeline.addLast(new MeiligaoProtocolEncoder(MeiligaoProtocol.this)); pipeline.addLast(new MeiligaoProtocolDecoder(MeiligaoProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new MeiligaoProtocolEncoder(MeiligaoProtocol.this)); pipeline.addLast(new MeiligaoProtocolDecoder(MeiligaoProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java index d38e5d1c3..f3b56973a 100644 --- a/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java @@ -19,8 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -40,7 +39,7 @@ import java.util.regex.Pattern; public class MeiligaoProtocolDecoder extends BaseProtocolDecoder { - private Map<Byte, ByteBuf> photos = new HashMap<>(); + private final Map<Byte, ByteBuf> photos = new HashMap<>(); public MeiligaoProtocolDecoder(Protocol protocol) { super(protocol); @@ -469,10 +468,9 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder { } else if (command == MSG_POSITION_IMAGE) { byte imageIndex = buf.readByte(); buf.readUnsignedByte(); // image upload type - String uniqueId = Context.getIdentityManager().getById(deviceSession.getDeviceId()).getUniqueId(); ByteBuf photo = photos.remove(imageIndex); try { - position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(uniqueId, photo, "jpg")); + position.set(Position.KEY_IMAGE, writeMediaFile(deviceSession.getUniqueId(), photo, "jpg")); } finally { photo.release(); } diff --git a/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java b/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java index e5b2cf4e7..5859d91ce 100644 --- a/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,16 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.traccar.BaseProtocolEncoder; -import org.traccar.Context; +import org.traccar.Protocol; +import org.traccar.config.Keys; import org.traccar.helper.Checksum; import org.traccar.helper.DataConverter; +import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Command; -import org.traccar.Protocol; +import org.traccar.model.Device; import java.nio.charset.StandardCharsets; +import java.util.Set; import java.util.TimeZone; public class MeiligaoProtocolEncoder extends BaseProtocolEncoder { @@ -56,15 +59,36 @@ public class MeiligaoProtocolEncoder extends BaseProtocolEncoder { return buf; } - @Override - protected Object encodeCommand(Command command) { + private ByteBuf encodeOutputCommand(long deviceId, int index, int value) { - boolean alternative = Context.getIdentityManager().lookupAttributeBoolean( - command.getDeviceId(), getProtocolName() + ".alternative", false, false, true); + int outputCount; + int outputType; - int outputControlMessageType = alternative - ? MeiligaoProtocolDecoder.MSG_OUTPUT_CONTROL_1 - : MeiligaoProtocolDecoder.MSG_OUTPUT_CONTROL_2; + String model = getCacheManager().getObject(Device.class, deviceId).getModel(); + + if (model != null && Set.of("TK510", "GT08", "TK208", "TK228", "MT05").contains(model)) { + outputCount = 5; + outputType = MeiligaoProtocolDecoder.MSG_OUTPUT_CONTROL_1; + } else { + outputCount = 1; + boolean alternative = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_ALTERNATIVE.withPrefix(getProtocolName()), deviceId); + outputType = alternative + ? MeiligaoProtocolDecoder.MSG_OUTPUT_CONTROL_1 + : MeiligaoProtocolDecoder.MSG_OUTPUT_CONTROL_2; + } + + ByteBuf content = Unpooled.buffer(); + + for (int i = 1; i <= outputCount; i++) { + content.writeByte(i == index ? value : 2); + } + + return encodeContent(deviceId, outputType, content); + } + + @Override + protected Object encodeCommand(Command command) { ByteBuf content = Unpooled.buffer(); @@ -74,12 +98,14 @@ public class MeiligaoProtocolEncoder extends BaseProtocolEncoder { case Command.TYPE_POSITION_PERIODIC: content.writeShort(command.getInteger(Command.KEY_FREQUENCY) / 10); return encodeContent(command.getDeviceId(), MeiligaoProtocolDecoder.MSG_TRACK_BY_INTERVAL, content); + case Command.TYPE_OUTPUT_CONTROL: + int index = command.getInteger(Command.KEY_INDEX) - 1; + int value = command.getInteger(Command.KEY_DATA); + return encodeOutputCommand(command.getDeviceId(), index, value); case Command.TYPE_ENGINE_STOP: - content.writeByte(0x01); - return encodeContent(command.getDeviceId(), outputControlMessageType, content); + return encodeOutputCommand(command.getDeviceId(), 1, 1); case Command.TYPE_ENGINE_RESUME: - content.writeByte(0x00); - return encodeContent(command.getDeviceId(), outputControlMessageType, content); + return encodeOutputCommand(command.getDeviceId(), 1, 0); case Command.TYPE_ALARM_GEOFENCE: content.writeShort(command.getInteger(Command.KEY_RADIUS)); return encodeContent(command.getDeviceId(), MeiligaoProtocolDecoder.MSG_MOVEMENT_ALARM, content); diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocol.java b/src/main/java/org/traccar/protocol/MeitrackProtocol.java index 7439ea611..c6eba8fe1 100644 --- a/src/main/java/org/traccar/protocol/MeitrackProtocol.java +++ b/src/main/java/org/traccar/protocol/MeitrackProtocol.java @@ -19,11 +19,15 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class MeitrackProtocol extends BaseProtocol { - public MeitrackProtocol() { + @Inject + public MeitrackProtocol(Config config) { setSupportedDataCommands( Command.TYPE_POSITION_SINGLE, Command.TYPE_ENGINE_STOP, @@ -32,18 +36,18 @@ public class MeitrackProtocol extends BaseProtocol { Command.TYPE_ALARM_DISARM, Command.TYPE_REQUEST_PHOTO, Command.TYPE_SEND_SMS); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new MeitrackFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new MeitrackProtocolEncoder(MeitrackProtocol.this)); pipeline.addLast(new MeitrackProtocolDecoder(MeitrackProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new MeitrackProtocolEncoder(MeitrackProtocol.this)); pipeline.addLast(new MeitrackProtocolDecoder(MeitrackProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java index 5eab10498..343141dca 100644 --- a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.model.Device; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Checksum; @@ -204,18 +204,18 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { position.set(Position.PREFIX_ADC + i, parser.nextHexInt()); } - String deviceModel = Context.getIdentityManager().getById(deviceSession.getDeviceId()).getModel(); - if (deviceModel == null) { - deviceModel = ""; + String model = getCacheManager().getObject(Device.class, deviceSession.getDeviceId()).getModel(); + if (model == null) { + model = ""; } - switch (deviceModel.toUpperCase()) { + switch (model.toUpperCase()) { case "MVT340": case "MVT380": - position.set(Position.KEY_BATTERY, parser.nextHexInt(0) * 3.0 * 2.0 / 1024.0); + position.set(Position.KEY_BATTERY, parser.nextHexInt() * 3.0 * 2.0 / 1024.0); position.set(Position.KEY_POWER, parser.nextHexInt(0) * 3.0 * 16.0 / 1024.0); break; case "MT90": - position.set(Position.KEY_BATTERY, parser.nextHexInt(0) * 3.3 * 2.0 / 4096.0); + position.set(Position.KEY_BATTERY, parser.nextHexInt() * 3.3 * 2.0 / 4096.0); position.set(Position.KEY_POWER, parser.nextHexInt(0)); break; case "T1": @@ -225,19 +225,18 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { case "MVT800": case "TC68": case "TC68S": - position.set(Position.KEY_BATTERY, parser.nextHexInt(0) * 3.3 * 2.0 / 4096.0); + position.set(Position.KEY_BATTERY, parser.nextHexInt() * 3.3 * 2.0 / 4096.0); position.set(Position.KEY_POWER, parser.nextHexInt(0) * 3.3 * 16.0 / 4096.0); break; case "T311": case "T322X": case "T333": case "T355": - position.set(Position.KEY_BATTERY, parser.nextHexInt(0) / 100.0); - position.set(Position.KEY_POWER, parser.nextHexInt(0) / 100.0); - break; + case "T366": + case "T366G": default: - position.set(Position.KEY_BATTERY, parser.nextHexInt(0)); - position.set(Position.KEY_POWER, parser.nextHexInt(0)); + position.set(Position.KEY_BATTERY, parser.nextHexInt() / 100.0); + position.set(Position.KEY_POWER, parser.nextHexInt(0) / 100.0); break; } @@ -368,13 +367,9 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { StringBuilder command = new StringBuilder("@@"); command.append(flag).append(27 + positions.size() / 10).append(","); command.append(imei).append(",CCC,").append(positions.size()).append("*"); - int checksum = 0; - for (int i = 0; i < command.length(); i += 1) { - checksum += command.charAt(i); - } - command.append(String.format("%02x", checksum & 0xff).toUpperCase()); + command.append(Checksum.sum(command.toString())); command.append("\r\n"); - channel.writeAndFlush(new NetworkMessage(command.toString(), remoteAddress)); // delete processed data + channel.writeAndFlush(new NetworkMessage(command.toString(), remoteAddress)); } return positions; @@ -404,7 +399,8 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { int paramCount = buf.readUnsignedByte(); for (int j = 0; j < paramCount; j++) { - int id = buf.readUnsignedByte(); + boolean extension = buf.getUnsignedByte(buf.readerIndex()) == 0xFE; + int id = extension ? buf.readUnsignedShort() : buf.readUnsignedByte(); switch (id) { case 0x01: position.set(Position.KEY_EVENT, buf.readUnsignedByte()); @@ -418,12 +414,21 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { case 0x07: position.set(Position.KEY_RSSI, buf.readUnsignedByte()); break; + case 0x14: + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + break; + case 0x15: + position.set(Position.KEY_INPUT, buf.readUnsignedByte()); + break; case 0x97: position.set(Position.KEY_THROTTLE, buf.readUnsignedByte()); break; case 0x9D: position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte()); break; + case 0xFE69: + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + break; default: buf.readUnsignedByte(); break; @@ -432,7 +437,8 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { paramCount = buf.readUnsignedByte(); for (int j = 0; j < paramCount; j++) { - int id = buf.readUnsignedByte(); + boolean extension = buf.getUnsignedByte(buf.readerIndex()) == 0xFE; + int id = extension ? buf.readUnsignedShort() : buf.readUnsignedByte(); switch (id) { case 0x08: position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE())); @@ -446,6 +452,9 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { case 0x0B: position.setAltitude(buf.readShortLE()); break; + case 0x16: + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShortLE() * 0.01); + break; case 0x19: position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.01); break; @@ -482,7 +491,8 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { paramCount = buf.readUnsignedByte(); for (int j = 0; j < paramCount; j++) { - int id = buf.readUnsignedByte(); + boolean extension = buf.getUnsignedByte(buf.readerIndex()) == 0xFE; + int id = extension ? buf.readUnsignedShort() : buf.readUnsignedByte(); switch (id) { case 0x02: position.setLatitude(buf.readIntLE() * 0.000001); @@ -514,8 +524,30 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { paramCount = buf.readUnsignedByte(); for (int j = 0; j < paramCount; j++) { - buf.readUnsignedByte(); // id - buf.skipBytes(buf.readUnsignedByte()); // value + boolean extension = buf.getUnsignedByte(buf.readerIndex()) == 0xFE; + int id = extension ? buf.readUnsignedShort() : buf.readUnsignedByte(); + int length = buf.readUnsignedByte(); + switch (id) { + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + buf.readUnsignedByte(); // label + position.set(Position.PREFIX_TEMP + (id - 0x2A), buf.readShortLE() * 0.01); + break; + case 0xFE31: + buf.readUnsignedByte(); // alarm protocol + buf.readUnsignedByte(); // alarm type + buf.skipBytes(length - 2); + break; + default: + buf.skipBytes(length); + break; + } } positions.add(position); @@ -524,13 +556,13 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { return positions; } - private void requestPhotoPacket(Channel channel, SocketAddress socketAddress, String imei, String file, int index) { + private void requestPhotoPacket(Channel channel, SocketAddress remoteAddress, String imei, String file, int index) { if (channel != null) { String content = "D00," + file + "," + index; int length = 1 + imei.length() + 1 + content.length() + 5; String response = String.format("@@O%02d,%s,%s*", length, imei, content); response += Checksum.sum(response) + "\r\n"; - channel.writeAndFlush(new NetworkMessage(response, socketAddress)); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } } @@ -546,6 +578,13 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { String type = buf.toString(index + 1, 3, StandardCharsets.US_ASCII); switch (type) { + case "AAC": + if (channel != null) { + String response = String.format("@@z27,%s,AAC,1*", imei); + response += Checksum.sum(response) + "\r\n"; + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + return null; case "D00": if (photo == null) { photo = Unpooled.buffer(); @@ -570,7 +609,7 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { getLastLocation(position, null); - position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(imei, photo, "jpg")); + position.set(Position.KEY_IMAGE, writeMediaFile(imei, photo, "jpg")); photo.release(); photo = null; diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocolEncoder.java b/src/main/java/org/traccar/protocol/MeitrackProtocolEncoder.java index 354e81434..365dbb35a 100644 --- a/src/main/java/org/traccar/protocol/MeitrackProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/MeitrackProtocolEncoder.java @@ -15,11 +15,12 @@ */ package org.traccar.protocol; -import org.traccar.Context; +import org.traccar.Protocol; import org.traccar.StringProtocolEncoder; +import org.traccar.config.Keys; import org.traccar.helper.Checksum; +import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Command; -import org.traccar.Protocol; import java.util.Map; @@ -42,8 +43,8 @@ public class MeitrackProtocolEncoder extends StringProtocolEncoder { Map<String, Object> attributes = command.getAttributes(); - boolean alternative = Context.getIdentityManager().lookupAttributeBoolean( - command.getDeviceId(), getProtocolName() + ".alternative", false, false, true); + boolean alternative = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_ALTERNATIVE.withPrefix(getProtocolName()), command.getDeviceId()); switch (command.getType()) { case Command.TYPE_POSITION_SINGLE: diff --git a/src/main/java/org/traccar/protocol/MictrackProtocol.java b/src/main/java/org/traccar/protocol/MictrackProtocol.java index 9fd9666e4..ccbc4db4c 100644 --- a/src/main/java/org/traccar/protocol/MictrackProtocol.java +++ b/src/main/java/org/traccar/protocol/MictrackProtocol.java @@ -20,21 +20,25 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class MictrackProtocol extends BaseProtocol { - public MictrackProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public MictrackProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new MictrackProtocolDecoder(MictrackProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { 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 652ba3f6a..84ba75e7c 100644 --- a/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2020 Roeland Boeters (roeland@geodelta.com) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; @@ -114,14 +114,18 @@ public class MictrackProtocolDecoder extends BaseProtocolDecoder { } } - private void decodeWifi(Network network, String data) { + private void decodeWifi(Network network, String data, boolean hasSsid) { String[] values = data.split(","); - for (int i = 0; i < values.length / 2; i++) { - network.addWifiAccessPoint(WifiAccessPoint.from(values[i * 2], Integer.parseInt(values[i * 2 + 1]))); + int step = hasSsid ? 3 : 2; + int offset = hasSsid ? 1 : 0; + for (int i = 0; i < values.length / step; i++) { + network.addWifiAccessPoint(WifiAccessPoint.from( + values[i * step + offset], Integer.parseInt(values[i * step + offset + 1]))); } } - private void decodeNetwork(Position position, String data, boolean hasWifi, boolean hasCell) throws ParseException { + private void decodeNetwork( + Position position, String data, boolean hasWifi, boolean hasSsid, boolean hasCell) throws ParseException { int index = 0; String[] values = data.split("\\+"); @@ -130,7 +134,7 @@ public class MictrackProtocolDecoder extends BaseProtocolDecoder { Network network = new Network(); if (hasWifi) { - decodeWifi(network, values[index++]); + decodeWifi(network, values[index++], hasSsid); } if (hasCell) { @@ -231,19 +235,22 @@ public class MictrackProtocolDecoder extends BaseProtocolDecoder { decodeLocation(position, fragments[4]); break; case "R1": - decodeNetwork(position, fragments[4], true, false); + decodeNetwork(position, fragments[4], true, false, false); break; case "R2": case "R3": - decodeNetwork(position, fragments[4], false, true); + decodeNetwork(position, fragments[4], false, false, true); break; case "R12": case "R13": - decodeNetwork(position, fragments[4], true, true); + decodeNetwork(position, fragments[4], true, false, true); break; case "RH": decodeStatus(position, fragments[4]); break; + case "Y1": + decodeNetwork(position, fragments[4], true, true, false); + break; default: return null; } diff --git a/src/main/java/org/traccar/protocol/MilesmateProtocol.java b/src/main/java/org/traccar/protocol/MilesmateProtocol.java index 822711603..59212e791 100644 --- a/src/main/java/org/traccar/protocol/MilesmateProtocol.java +++ b/src/main/java/org/traccar/protocol/MilesmateProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class MilesmateProtocol extends BaseProtocol { - public MilesmateProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public MilesmateProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/MilesmateProtocolDecoder.java b/src/main/java/org/traccar/protocol/MilesmateProtocolDecoder.java index 901ceb8f7..21c629411 100644 --- a/src/main/java/org/traccar/protocol/MilesmateProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MilesmateProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/MiniFinderProtocol.java b/src/main/java/org/traccar/protocol/MiniFinderProtocol.java index 82534ecd8..44599accc 100644 --- a/src/main/java/org/traccar/protocol/MiniFinderProtocol.java +++ b/src/main/java/org/traccar/protocol/MiniFinderProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,15 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class MiniFinderProtocol extends BaseProtocol { - public MiniFinderProtocol() { + @Inject + public MiniFinderProtocol(Config config) { setSupportedDataCommands( Command.TYPE_SET_TIMEZONE, Command.TYPE_VOICE_MONITORING, @@ -38,10 +42,10 @@ public class MiniFinderProtocol extends BaseProtocol { Command.TYPE_MODE_DEEP_SLEEP, Command.TYPE_SOS_NUMBER, Command.TYPE_SET_INDICATOR); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { - pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ';')); + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ";\0", ";")); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new MiniFinderProtocolEncoder(MiniFinderProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java b/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java index 2b7a960c4..f2e5eb905 100644 --- a/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2014 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.Parser; @@ -143,7 +143,7 @@ public class MiniFinderProtocolDecoder extends BaseProtocolDecoder { } DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); - if (deviceSession == null || !sentence.matches("![3A-D],.*")) { + if (deviceSession == null || !sentence.matches("![35A-D],.*")) { return null; } @@ -161,6 +161,19 @@ public class MiniFinderProtocolDecoder extends BaseProtocolDecoder { return position; + } else if (type.equals("5")) { + + String[] values = sentence.split(","); + + getLastLocation(position, null); + + position.set(Position.KEY_RSSI, Integer.parseInt(values[1])); + if (values.length >= 4) { + position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(values[3])); + } + + return position; + } else if (type.equals("B") || type.equals("D")) { Parser parser = new Parser(PATTERN_BD, sentence); diff --git a/src/main/java/org/traccar/protocol/Minifinder2Protocol.java b/src/main/java/org/traccar/protocol/Minifinder2Protocol.java index f8801db74..5499a274e 100644 --- a/src/main/java/org/traccar/protocol/Minifinder2Protocol.java +++ b/src/main/java/org/traccar/protocol/Minifinder2Protocol.java @@ -19,15 +19,19 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class Minifinder2Protocol extends BaseProtocol { - public Minifinder2Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Minifinder2Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1200, 2, 2, 4, 0, true)); pipeline.addLast(new Minifinder2ProtocolDecoder(Minifinder2Protocol.this)); } diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java index 641a45864..0b08badb8 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 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -50,7 +50,7 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_SERVICES = 0x03; public static final int MSG_RESPONSE = 0x7F; - private String decodeAlarm(int code) { + private String decodeAlarm(long code) { if (BitUtil.check(code, 0)) { return Position.ALARM_LOW_BATTERY; } @@ -181,7 +181,11 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { position.setDeviceId(deviceSession.getDeviceId()); break; case 0x02: - position.set(Position.KEY_ALARM, decodeAlarm(buf.readIntLE())); + long alarm = buf.readUnsignedIntLE(); + position.set(Position.KEY_ALARM, decodeAlarm(alarm)); + if (BitUtil.check(alarm, 31)) { + position.set("bark", true); + } break; case 0x14: position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); @@ -194,7 +198,9 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE())); position.setCourse(buf.readUnsignedShortLE()); position.setAltitude(buf.readShortLE()); - position.setValid(buf.readUnsignedShortLE() > 0); + int hdop = buf.readUnsignedShortLE(); + position.setValid(hdop > 0); + position.set(Position.KEY_HDOP, hdop * 0.1); position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); break; @@ -260,8 +266,16 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { hasLocation = true; break; case 0x30: - buf.readUnsignedInt(); // timestamp - position.set(Position.KEY_STEPS, buf.readUnsignedInt()); + buf.readUnsignedIntLE(); // timestamp + position.set(Position.KEY_STEPS, buf.readUnsignedIntLE()); + break; + case 0x31: + int i = 1; + while (buf.readerIndex() < endIndex) { + position.set("activity" + i + "Time", buf.readUnsignedIntLE()); + position.set("activity" + i, buf.readUnsignedIntLE()); + i += 1; + } break; case 0x40: buf.readUnsignedIntLE(); // timestamp diff --git a/src/main/java/org/traccar/protocol/MobilogixProtocol.java b/src/main/java/org/traccar/protocol/MobilogixProtocol.java index 28380a2af..1b06c2249 100644 --- a/src/main/java/org/traccar/protocol/MobilogixProtocol.java +++ b/src/main/java/org/traccar/protocol/MobilogixProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class MobilogixProtocol extends BaseProtocol { - public MobilogixProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public MobilogixProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ']')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/MobilogixProtocolDecoder.java b/src/main/java/org/traccar/protocol/MobilogixProtocolDecoder.java index f3b70e40c..d7600ecbb 100644 --- a/src/main/java/org/traccar/protocol/MobilogixProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MobilogixProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -47,9 +47,9 @@ public class MobilogixProtocolDecoder extends BaseProtocolDecoder { .number("(d+.d+)") // battery .groupBegin() .text(",") - .number("(d)") // valid + .number("(d)") // satellites .number("(d)") // rssi - .number("(d),") // satellites + .number("(d),") // valid .number("(-?d+.d+),") // latitude .number("(-?d+.d+),") // longitude .number("(d+.?d*),") // speed @@ -127,12 +127,12 @@ public class MobilogixProtocolDecoder extends BaseProtocolDecoder { if (parser.hasNext(7)) { + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_RSSI, 6 * parser.nextInt() - 111); + position.setValid(parser.nextInt() > 0); position.setFixTime(position.getDeviceTime()); - position.set(Position.KEY_RSSI, parser.nextInt()); - position.set(Position.KEY_SATELLITES, parser.nextInt()); - position.setLatitude(parser.nextDouble()); position.setLongitude(parser.nextDouble()); position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); diff --git a/src/main/java/org/traccar/protocol/MoovboxProtocol.java b/src/main/java/org/traccar/protocol/MoovboxProtocol.java index 7b554266f..16438e122 100644 --- a/src/main/java/org/traccar/protocol/MoovboxProtocol.java +++ b/src/main/java/org/traccar/protocol/MoovboxProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class MoovboxProtocol extends BaseProtocol { - public MoovboxProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public MoovboxProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(65535)); diff --git a/src/main/java/org/traccar/protocol/MoovboxProtocolDecoder.java b/src/main/java/org/traccar/protocol/MoovboxProtocolDecoder.java index 3116d073c..8e6679b05 100644 --- a/src/main/java/org/traccar/protocol/MoovboxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MoovboxProtocolDecoder.java @@ -20,7 +20,7 @@ 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.session.DeviceSession; import org.traccar.Protocol; import org.traccar.model.Position; import org.w3c.dom.Document; diff --git a/src/main/java/org/traccar/protocol/MotorProtocol.java b/src/main/java/org/traccar/protocol/MotorProtocol.java index 680687e15..3101c9b75 100644 --- a/src/main/java/org/traccar/protocol/MotorProtocol.java +++ b/src/main/java/org/traccar/protocol/MotorProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class MotorProtocol extends BaseProtocol { - public MotorProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public MotorProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new MotorProtocolDecoder(MotorProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/MotorProtocolDecoder.java b/src/main/java/org/traccar/protocol/MotorProtocolDecoder.java index 8ce4fe8b1..9bca4d9bc 100644 --- a/src/main/java/org/traccar/protocol/MotorProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MotorProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/Mta6Protocol.java b/src/main/java/org/traccar/protocol/Mta6Protocol.java index 14a66ce5c..019fe4fa9 100644 --- a/src/main/java/org/traccar/protocol/Mta6Protocol.java +++ b/src/main/java/org/traccar/protocol/Mta6Protocol.java @@ -19,22 +19,25 @@ 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.Context; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.config.Keys; +import javax.inject.Inject; + public class Mta6Protocol extends BaseProtocol { - public Mta6Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Mta6Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(65535)); pipeline.addLast(new Mta6ProtocolDecoder( - Mta6Protocol.this, !Context.getConfig().getBoolean(Keys.PROTOCOL_CAN.withPrefix(getName())))); + Mta6Protocol.this, !config.getBoolean(Keys.PROTOCOL_CAN.withPrefix(getName())))); } }); } diff --git a/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java index 88419b871..896c7a2d2 100644 --- a/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java @@ -26,7 +26,7 @@ import io.netty.handler.codec.http.HttpVersion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/MtxProtocol.java b/src/main/java/org/traccar/protocol/MtxProtocol.java index 44372ce83..e085b6221 100644 --- a/src/main/java/org/traccar/protocol/MtxProtocol.java +++ b/src/main/java/org/traccar/protocol/MtxProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class MtxProtocol extends BaseProtocol { - public MtxProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public MtxProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/MtxProtocolDecoder.java b/src/main/java/org/traccar/protocol/MtxProtocolDecoder.java index d1207bedf..e94d12b36 100644 --- a/src/main/java/org/traccar/protocol/MtxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MtxProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/MxtProtocol.java b/src/main/java/org/traccar/protocol/MxtProtocol.java index dbe43fe45..1190bf527 100644 --- a/src/main/java/org/traccar/protocol/MxtProtocol.java +++ b/src/main/java/org/traccar/protocol/MxtProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class MxtProtocol extends BaseProtocol { - public MxtProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public MxtProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new MxtFrameDecoder()); pipeline.addLast(new MxtProtocolDecoder(MxtProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/MxtProtocolDecoder.java b/src/main/java/org/traccar/protocol/MxtProtocolDecoder.java index 379b610e1..b3e2295e8 100644 --- a/src/main/java/org/traccar/protocol/MxtProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MxtProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/NavigilProtocol.java b/src/main/java/org/traccar/protocol/NavigilProtocol.java index 2c946c39f..46a6c33a5 100644 --- a/src/main/java/org/traccar/protocol/NavigilProtocol.java +++ b/src/main/java/org/traccar/protocol/NavigilProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class NavigilProtocol extends BaseProtocol { - public NavigilProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public NavigilProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new NavigilFrameDecoder()); pipeline.addLast(new NavigilProtocolDecoder(NavigilProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/NavigilProtocolDecoder.java b/src/main/java/org/traccar/protocol/NavigilProtocolDecoder.java index db5521201..6dadbc559 100644 --- a/src/main/java/org/traccar/protocol/NavigilProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/NavigilProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Checksum; diff --git a/src/main/java/org/traccar/protocol/NavisProtocol.java b/src/main/java/org/traccar/protocol/NavisProtocol.java index d5af6838d..640a77803 100644 --- a/src/main/java/org/traccar/protocol/NavisProtocol.java +++ b/src/main/java/org/traccar/protocol/NavisProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class NavisProtocol extends BaseProtocol { - public NavisProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public NavisProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new NavisFrameDecoder()); pipeline.addLast(new NavisProtocolDecoder(NavisProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/NavisProtocolDecoder.java b/src/main/java/org/traccar/protocol/NavisProtocolDecoder.java index 7ba474ae0..77158b315 100644 --- a/src/main/java/org/traccar/protocol/NavisProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/NavisProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,11 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.Checksum; -import org.traccar.helper.Checksum.Algorithm; import org.traccar.helper.DateBuilder; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; @@ -591,10 +590,8 @@ public class NavisProtocolDecoder extends BaseProtocolDecoder { private void sendFlexReply(Channel channel, ByteBuf data) { if (channel != null) { - ByteBuf cs = Unpooled.buffer(1); - cs.writeByte(Checksum.crc8(new Algorithm(8, 0x31, 0xFF, false, false, 0x00), data.nioBuffer())); - - channel.writeAndFlush(new NetworkMessage(Unpooled.wrappedBuffer(data, cs), channel.remoteAddress())); + data.writeByte(Checksum.crc8(Checksum.CRC8_EGTS, data.nioBuffer())); + channel.writeAndFlush(new NetworkMessage(data, channel.remoteAddress())); } } diff --git a/src/main/java/org/traccar/protocol/NavisetProtocol.java b/src/main/java/org/traccar/protocol/NavisetProtocol.java index 78755ea4d..388f141f8 100644 --- a/src/main/java/org/traccar/protocol/NavisetProtocol.java +++ b/src/main/java/org/traccar/protocol/NavisetProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class NavisetProtocol extends BaseProtocol { - public NavisetProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public NavisetProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new NavisetFrameDecoder()); pipeline.addLast(new NavisetProtocolDecoder(NavisetProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/NavisetProtocolDecoder.java b/src/main/java/org/traccar/protocol/NavisetProtocolDecoder.java index 10d71d76c..47d10b310 100644 --- a/src/main/java/org/traccar/protocol/NavisetProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/NavisetProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/NavtelecomFrameDecoder.java b/src/main/java/org/traccar/protocol/NavtelecomFrameDecoder.java index 0fb82528b..2ab7d11a9 100644 --- a/src/main/java/org/traccar/protocol/NavtelecomFrameDecoder.java +++ b/src/main/java/org/traccar/protocol/NavtelecomFrameDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,12 @@ public class NavtelecomFrameDecoder extends BaseFrameDecoder { protected Object decode( ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { - if (buf.getByte(buf.readerIndex()) == '@') { + if (buf.getByte(buf.readerIndex()) == 0x7f) { + + buf.skipBytes(1); + return null; + + } else if (buf.getByte(buf.readerIndex()) == '@') { int length = buf.getUnsignedShortLE(12) + 12 + 2 + 2; if (buf.readableBytes() >= length) { diff --git a/src/main/java/org/traccar/protocol/NavtelecomProtocol.java b/src/main/java/org/traccar/protocol/NavtelecomProtocol.java index 29ce8c41e..50013d1a4 100644 --- a/src/main/java/org/traccar/protocol/NavtelecomProtocol.java +++ b/src/main/java/org/traccar/protocol/NavtelecomProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class NavtelecomProtocol extends BaseProtocol { - public NavtelecomProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public NavtelecomProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new NavtelecomFrameDecoder()); pipeline.addLast(new NavtelecomProtocolDecoder(NavtelecomProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java b/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java index bdcc12c4c..08b1a8d0f 100644 --- a/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/NavtelecomProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -195,6 +195,9 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder { for (int j = 0; j < bits.length(); j++) { if (bits.get(j)) { + + int value; + switch (j + 1) { case 1: position.set(Position.KEY_INDEX, buf.readUnsignedIntLE()); @@ -205,8 +208,12 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder { case 3: position.setDeviceTime(new Date(buf.readUnsignedIntLE() * 1000)); break; + case 8: + value = buf.readUnsignedByte(); + position.setValid(BitUtil.check(value, 1)); + position.set(Position.KEY_SATELLITES, BitUtil.from(value, 2)); + break; case 9: - position.setValid(true); position.setFixTime(new Date(buf.readUnsignedIntLE() * 1000)); break; case 10: @@ -221,6 +228,83 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder { case 13: position.setSpeed(UnitsConverter.knotsFromKph(buf.readFloatLE())); break; + case 14: + position.setCourse(buf.readUnsignedShortLE()); + break; + case 15: + position.set(Position.KEY_ODOMETER, buf.readFloatLE()); + break; + case 19: + position.set(Position.KEY_POWER, buf.readShortLE() * 0.001); + break; + case 20: + position.set(Position.KEY_BATTERY, buf.readShortLE() * 0.001); + break; + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + position.set(Position.PREFIX_ADC + (j + 2 - 21), buf.readUnsignedShortLE() * 0.001); + break; + case 29: + value = buf.readUnsignedByte(); + for (int k = 0; k <= 7; k++) { + position.set(Position.PREFIX_IN + (k + 1), BitUtil.check(value, k)); + } + break; + case 31: + value = buf.readUnsignedByte(); + for (int k = 0; k <= 3; k++) { + position.set(Position.PREFIX_OUT + (k + 1), BitUtil.check(value, k)); + } + break; + case 33: + case 34: + position.set(Position.PREFIX_COUNT + (j + 2 - 33), buf.readUnsignedIntLE()); + break; + case 35: + case 36: + position.set("freq" + (j + 2 - 35), buf.readUnsignedShortLE()); + break; + case 37: + position.set(Position.KEY_HOURS, buf.readUnsignedIntLE()); + break; + case 38: + case 39: + case 40: + case 41: + case 42: + case 43: + value = buf.readUnsignedShortLE(); + position.set("fuel" + (j + 2 - 38), (value < 65500) ? value : null); + break; + case 44: + value = buf.readUnsignedShortLE(); + position.set(Position.KEY_FUEL_LEVEL, (value < 65500) ? value : null); + break; + case 45: + case 46: + case 47: + case 48: + case 49: + case 50: + case 51: + case 52: + value = buf.readByte(); + position.set( + Position.PREFIX_TEMP + (j + 2 - 45), + (value != (byte) 0x80) ? value : null); + break; + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + position.set("fuelTemp" + (j + 2 - 78), (int) buf.readByte()); + break; default: buf.skipBytes(getItemLength(j + 1)); break; @@ -235,12 +319,11 @@ public class NavtelecomProtocolDecoder extends BaseProtocolDecoder { positions.add(position); } - int checksum = buf.readUnsignedByte(); if (channel != null) { ByteBuf response = Unpooled.buffer(); response.writeCharSequence(type, StandardCharsets.US_ASCII); response.writeByte(count); - response.writeByte(checksum); + response.writeByte(Checksum.crc8(Checksum.CRC8_EGTS, response.nioBuffer())); channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } diff --git a/src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java b/src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java new file mode 100644 index 000000000..5b610013a --- /dev/null +++ b/src/main/java/org/traccar/protocol/NdtpV6FrameDecoder.java @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class NdtpV6FrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + return buf; + } + +} diff --git a/src/main/java/org/traccar/protocol/NdtpV6Protocol.java b/src/main/java/org/traccar/protocol/NdtpV6Protocol.java new file mode 100644 index 000000000..ce0dbbef2 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NdtpV6Protocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class NdtpV6Protocol extends BaseProtocol { + + @Inject + public NdtpV6Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new NdtpV6ProtocolDecoder(NdtpV6Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/NdtpV6ProtocolDecoder.java b/src/main/java/org/traccar/protocol/NdtpV6ProtocolDecoder.java new file mode 100644 index 000000000..35cb0bae8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NdtpV6ProtocolDecoder.java @@ -0,0 +1,203 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; +import org.traccar.session.DeviceSession; + +import java.net.SocketAddress; +import java.util.Date; + +public class NdtpV6ProtocolDecoder extends BaseProtocolDecoder { + + public NdtpV6ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final byte[] SIGNATURE = {0x7E, 0x7E}; + + private static final int NPL_FLAG_CRC = 2; + private static final int NPH_RESULT_OK = 0x00000000; + private static final int NPL_TYPE_NPH = 2; + private static final int NPL_ADDRESS_SERVER = 0; + + private static final int NPH_RESULT = 0; + + private static final int NPH_SRV_GENERIC_CONTROLS = 0; + private static final int NPH_SRV_NAVDATA = 1; + + private static final int NPH_SGC_RESULT = NPH_RESULT; + private static final int NPH_SGC_CONN_REQUEST = 100; + + private static final int NPH_SND_RESULT = NPH_RESULT; + + private void sendResponse( + Channel channel, int serviceId, long requestId) { + + ByteBuf content = Unpooled.buffer(); + content.writeShortLE(serviceId); + content.writeIntLE(NPH_SND_RESULT); + content.writeIntLE((int) requestId); + content.writeIntLE(NPH_RESULT_OK); + + ByteBuf response = Unpooled.buffer(); + response.writeBytes(SIGNATURE); + response.writeShortLE(content.readableBytes()); + response.writeShortLE(NPL_FLAG_CRC); // flags + response.writeShort(Checksum.crc16(Checksum.CRC16_MODBUS, content.nioBuffer())); + response.writeByte(NPL_TYPE_NPH); // type + response.writeIntLE(NPL_ADDRESS_SERVER); // peer address + response.writeShortLE(0); // request id + response.writeBytes(content); + content.release(); + + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + + private static final short MAIN_NAV_DATA = 0; + private static final short ADDITIONAL_NAV_DATA = 2; + + private void decodeData(ByteBuf buf, Position position) { + + short itemType; + short itemIndex; + + itemType = buf.readUnsignedByte(); + itemIndex = buf.readUnsignedByte(); + if (itemType == MAIN_NAV_DATA && (itemIndex == 0 || itemIndex == 1)) { + + position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); + position.setLongitude(buf.readIntLE() / 10000000.0); + position.setLatitude(buf.readIntLE() / 10000000.0); + + short flags = buf.readUnsignedByte(); + position.setValid(BitUtil.check(flags, 7)); + if (BitUtil.check(flags, 1)) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + + position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 20); + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedShortLE()); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE())); + position.setCourse(buf.readUnsignedShortLE()); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedShortLE()); + position.setAltitude(buf.readShortLE()); + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_PDOP, buf.readUnsignedByte()); + } + + itemType = buf.readUnsignedByte(); + itemIndex = buf.readUnsignedByte(); + if (itemType == ADDITIONAL_NAV_DATA && itemIndex == 0) { + + position.set(Position.KEY_BATTERY_LEVEL, Math.max((buf.readUnsignedShortLE() - 3600) / 6, 100)); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShortLE()); + position.set(Position.PREFIX_ADC + 3, buf.readUnsignedShortLE()); + position.set(Position.PREFIX_ADC + 4, buf.readUnsignedShortLE()); + + buf.readUnsignedByte(); // inputs + buf.readUnsignedByte(); // outputs + buf.readUnsignedShortLE(); // in1 count + buf.readUnsignedShortLE(); // in2 count + buf.readUnsignedShortLE(); // in3 count + buf.readUnsignedShortLE(); // in4 count + buf.readUnsignedIntLE(); // track length + + position.set(Position.KEY_ANTENNA, buf.readUnsignedByte()); + position.set(Position.KEY_GPS, buf.readUnsignedByte()); + position.set(Position.KEY_ACCELERATION, buf.readUnsignedByte()); + position.set(Position.KEY_POWER, buf.readUnsignedByte() * 200); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + + buf.skipBytes(2); // signature + buf.readUnsignedShortLE(); // length + buf.readUnsignedShortLE(); // connection flags + buf.readUnsignedShortLE(); // checksum + buf.readUnsignedByte(); // type + buf.readUnsignedIntLE(); // address + buf.readUnsignedShortLE(); // identification + + int serviceId = buf.readUnsignedShortLE(); + int serviceType = buf.readUnsignedShortLE(); + buf.readUnsignedShortLE(); // request flags + long requestId = buf.readUnsignedIntLE(); + + if (deviceSession == null && serviceId == NPH_SRV_GENERIC_CONTROLS && serviceType == NPH_SGC_CONN_REQUEST) { + + buf.readUnsignedShortLE(); // version major + buf.readUnsignedShortLE(); // version minor + buf.readUnsignedShortLE(); // connection flags + + int deviceId = buf.readUnsignedShortLE(); + Position position = new Position(getProtocolName()); + deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(deviceId)); + position.setDeviceId(deviceSession.getDeviceId()); + + if (channel != null) { + sendResponse(channel, serviceId, requestId); + } + + position.set(Position.KEY_RESULT, String.valueOf(NPH_SGC_RESULT)); + position.setTime(new Date()); + getLastLocation(position, new Date()); + position.setValid(false); + + return position; + + } + + if (serviceId == NPH_SRV_NAVDATA) { + + deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (channel != null) { + sendResponse(channel, serviceId, requestId); + } + + decodeData(buf, position); + + return position; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/NdtpV6ProtocolEncoder.java b/src/main/java/org/traccar/protocol/NdtpV6ProtocolEncoder.java new file mode 100644 index 000000000..7aac8658a --- /dev/null +++ b/src/main/java/org/traccar/protocol/NdtpV6ProtocolEncoder.java @@ -0,0 +1,42 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocolEncoder; +import org.traccar.Protocol; +import org.traccar.model.Command; + +public class NdtpV6ProtocolEncoder extends BaseProtocolEncoder { + + public NdtpV6ProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + switch (command.getType()) { + case Command.TYPE_IDENTIFICATION: + return "BB+IDNT"; + case Command.TYPE_REBOOT_DEVICE: + return "BB+RESET"; + case Command.TYPE_POSITION_SINGLE: + return "BB+RRCD"; + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/NeosProtocol.java b/src/main/java/org/traccar/protocol/NeosProtocol.java index e545a9969..0787b6562 100644 --- a/src/main/java/org/traccar/protocol/NeosProtocol.java +++ b/src/main/java/org/traccar/protocol/NeosProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class NeosProtocol extends BaseProtocol { - public NeosProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public NeosProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new NeosProtocolDecoder(NeosProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/NeosProtocolDecoder.java b/src/main/java/org/traccar/protocol/NeosProtocolDecoder.java index 6b5596dba..18ebc49da 100644 --- a/src/main/java/org/traccar/protocol/NeosProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/NeosProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/NetProtocol.java b/src/main/java/org/traccar/protocol/NetProtocol.java index c114d19fc..f27e4afb8 100644 --- a/src/main/java/org/traccar/protocol/NetProtocol.java +++ b/src/main/java/org/traccar/protocol/NetProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class NetProtocol extends BaseProtocol { - public NetProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public NetProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '!')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/NetProtocolDecoder.java b/src/main/java/org/traccar/protocol/NetProtocolDecoder.java index c71a792a2..ebffb06f1 100644 --- a/src/main/java/org/traccar/protocol/NetProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/NetProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/NiotProtocol.java b/src/main/java/org/traccar/protocol/NiotProtocol.java index b57b18a3a..0fbe0c689 100644 --- a/src/main/java/org/traccar/protocol/NiotProtocol.java +++ b/src/main/java/org/traccar/protocol/NiotProtocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class NiotProtocol extends BaseProtocol { - public NiotProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public NiotProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 2)); pipeline.addLast(new NiotProtocolDecoder(NiotProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/NiotProtocolDecoder.java b/src/main/java/org/traccar/protocol/NiotProtocolDecoder.java index 47c6e2ffd..35614ccca 100644 --- a/src/main/java/org/traccar/protocol/NiotProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/NiotProtocolDecoder.java @@ -20,7 +20,8 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.helper.BufferUtil; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; @@ -57,12 +58,6 @@ public class NiotProtocolDecoder extends BaseProtocolDecoder { } } - private double readCoordinate(ByteBuf buf) { - long value = buf.readUnsignedInt(); - double result = BitUtil.to(value, 31) / 1800000.0; - return BitUtil.check(value, 31) ? -result : result; - } - @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -96,8 +91,8 @@ public class NiotProtocolDecoder extends BaseProtocolDecoder { .setSecond(BcdUtil.readInteger(buf, 2)); position.setTime(dateBuilder.getDate()); - position.setLatitude(readCoordinate(buf)); - position.setLongitude(readCoordinate(buf)); + position.setLatitude(BufferUtil.readSignedMagnitudeInt(buf) / 1800000.0); + position.setLongitude(BufferUtil.readSignedMagnitudeInt(buf) / 1800000.0); BcdUtil.readInteger(buf, 4); // reserved position.setCourse(BcdUtil.readInteger(buf, 4)); diff --git a/src/main/java/org/traccar/protocol/NoranProtocol.java b/src/main/java/org/traccar/protocol/NoranProtocol.java index 3df364c30..626991029 100644 --- a/src/main/java/org/traccar/protocol/NoranProtocol.java +++ b/src/main/java/org/traccar/protocol/NoranProtocol.java @@ -18,20 +18,24 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class NoranProtocol extends BaseProtocol { - public NoranProtocol() { + @Inject + public NoranProtocol(Config config) { setSupportedDataCommands( Command.TYPE_POSITION_SINGLE, Command.TYPE_POSITION_PERIODIC, Command.TYPE_POSITION_STOP, Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new NoranProtocolEncoder(NoranProtocol.this)); pipeline.addLast(new NoranProtocolDecoder(NoranProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/NoranProtocolDecoder.java b/src/main/java/org/traccar/protocol/NoranProtocolDecoder.java index 53dae7fd6..53b58f9b6 100644 --- a/src/main/java/org/traccar/protocol/NoranProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/NoranProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/NvsProtocol.java b/src/main/java/org/traccar/protocol/NvsProtocol.java index d319b22f3..7ed488e38 100644 --- a/src/main/java/org/traccar/protocol/NvsProtocol.java +++ b/src/main/java/org/traccar/protocol/NvsProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class NvsProtocol extends BaseProtocol { - public NvsProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public NvsProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new NvsFrameDecoder()); pipeline.addLast(new NvsProtocolDecoder(NvsProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/NvsProtocolDecoder.java b/src/main/java/org/traccar/protocol/NvsProtocolDecoder.java index 5d1159f7d..f826c4121 100644 --- a/src/main/java/org/traccar/protocol/NvsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/NvsProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.UnitsConverter; diff --git a/src/main/java/org/traccar/protocol/NyitechProtocol.java b/src/main/java/org/traccar/protocol/NyitechProtocol.java index 58974be5c..e7ef10945 100644 --- a/src/main/java/org/traccar/protocol/NyitechProtocol.java +++ b/src/main/java/org/traccar/protocol/NyitechProtocol.java @@ -19,15 +19,19 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class NyitechProtocol extends BaseProtocol { - public NyitechProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public NyitechProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 2, -4, 0, true)); pipeline.addLast(new NyitechProtocolDecoder(NyitechProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/NyitechProtocolDecoder.java b/src/main/java/org/traccar/protocol/NyitechProtocolDecoder.java index 62b41a2ea..49bc5b824 100644 --- a/src/main/java/org/traccar/protocol/NyitechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/NyitechProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/ObdDongleProtocol.java b/src/main/java/org/traccar/protocol/ObdDongleProtocol.java index 10a55759b..94f450426 100644 --- a/src/main/java/org/traccar/protocol/ObdDongleProtocol.java +++ b/src/main/java/org/traccar/protocol/ObdDongleProtocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class ObdDongleProtocol extends BaseProtocol { - public ObdDongleProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public ObdDongleProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1099, 20, 2, 3, 0)); pipeline.addLast(new ObdDongleProtocolDecoder(ObdDongleProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/ObdDongleProtocolDecoder.java b/src/main/java/org/traccar/protocol/ObdDongleProtocolDecoder.java index 1c9771ce9..bf0ba6f82 100644 --- a/src/main/java/org/traccar/protocol/ObdDongleProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ObdDongleProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/OigoProtocol.java b/src/main/java/org/traccar/protocol/OigoProtocol.java index 5056f68aa..0539bada6 100644 --- a/src/main/java/org/traccar/protocol/OigoProtocol.java +++ b/src/main/java/org/traccar/protocol/OigoProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class OigoProtocol extends BaseProtocol { - public OigoProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public OigoProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new OigoProtocolDecoder(OigoProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/OigoProtocolDecoder.java b/src/main/java/org/traccar/protocol/OigoProtocolDecoder.java index b9cc71e8c..b0c7c3bc6 100644 --- a/src/main/java/org/traccar/protocol/OigoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/OigoProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/OkoProtocol.java b/src/main/java/org/traccar/protocol/OkoProtocol.java index 9571ccc48..29c8bc1b9 100644 --- a/src/main/java/org/traccar/protocol/OkoProtocol.java +++ b/src/main/java/org/traccar/protocol/OkoProtocol.java @@ -20,13 +20,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class OkoProtocol extends BaseProtocol { - public OkoProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public OkoProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '}')); pipeline.addLast(new StringDecoder()); pipeline.addLast(new OkoProtocolDecoder(OkoProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/OkoProtocolDecoder.java b/src/main/java/org/traccar/protocol/OkoProtocolDecoder.java index fa35ab455..3bb62acb9 100644 --- a/src/main/java/org/traccar/protocol/OkoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/OkoProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/OmnicommProtocol.java b/src/main/java/org/traccar/protocol/OmnicommProtocol.java index 96660cb59..dd400c779 100644 --- a/src/main/java/org/traccar/protocol/OmnicommProtocol.java +++ b/src/main/java/org/traccar/protocol/OmnicommProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class OmnicommProtocol extends BaseProtocol { - public OmnicommProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public OmnicommProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new OmnicommFrameDecoder()); pipeline.addLast(new OmnicommProtocolDecoder(OmnicommProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java b/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java index f90d1f2b3..9d747032b 100644 --- a/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java @@ -21,7 +21,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/OpenGtsProtocol.java b/src/main/java/org/traccar/protocol/OpenGtsProtocol.java index 5ef3260c6..5443b4ffc 100644 --- a/src/main/java/org/traccar/protocol/OpenGtsProtocol.java +++ b/src/main/java/org/traccar/protocol/OpenGtsProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class OpenGtsProtocol extends BaseProtocol { - public OpenGtsProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public OpenGtsProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(16384)); diff --git a/src/main/java/org/traccar/protocol/OpenGtsProtocolDecoder.java b/src/main/java/org/traccar/protocol/OpenGtsProtocolDecoder.java index b76cbfa85..255a81ae6 100644 --- a/src/main/java/org/traccar/protocol/OpenGtsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/OpenGtsProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.QueryStringDecoder; import org.traccar.BaseHttpProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/OrbcommProtocol.java b/src/main/java/org/traccar/protocol/OrbcommProtocol.java new file mode 100644 index 000000000..fb09f0abb --- /dev/null +++ b/src/main/java/org/traccar/protocol/OrbcommProtocol.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestEncoder; +import io.netty.handler.codec.http.HttpResponseDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerClient; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class OrbcommProtocol extends BaseProtocol { + + @Inject + public OrbcommProtocol(Config config) { + addClient(new TrackerClient(config, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new HttpRequestEncoder()); + pipeline.addLast(new HttpResponseDecoder()); + pipeline.addLast(new HttpObjectAggregator(65535)); + pipeline.addLast(new OrbcommProtocolDecoder(OrbcommProtocol.this)); + pipeline.addLast(new OrbcommProtocolPoller(OrbcommProtocol.this, config)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java b/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java new file mode 100644 index 000000000..1164d72a1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OrbcommProtocolDecoder.java @@ -0,0 +1,124 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpResponse; +import org.traccar.BasePipelineFactory; +import org.traccar.BaseProtocolDecoder; +import org.traccar.session.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonValue; +import java.io.StringReader; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.TimeZone; + +public class OrbcommProtocolDecoder extends BaseProtocolDecoder { + + public OrbcommProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpResponse response = (FullHttpResponse) msg; + String content = response.content().toString(StandardCharsets.UTF_8); + JsonObject json = Json.createReader(new StringReader(content)).readObject(); + + if (channel != null && !json.getString("NextStartUTC").isEmpty()) { + OrbcommProtocolPoller poller = + BasePipelineFactory.getHandler(channel.pipeline(), OrbcommProtocolPoller.class); + if (poller != null) { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + poller.setStartTime(dateFormat.parse(json.getString("NextStartUTC"))); + } + } + + if (json.get("Messages").getValueType() == JsonValue.ValueType.NULL) { + return null; + } + + LinkedList<Position> positions = new LinkedList<>(); + + JsonArray messages = json.getJsonArray("Messages"); + for (int i = 0; i < messages.size(); i++) { + JsonObject message = messages.getJsonObject(i); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, message.getString("MobileID")); + if (deviceSession != null) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + position.setDeviceTime(dateFormat.parse(message.getString("MessageUTC"))); + + JsonArray fields = message.getJsonObject("Payload").getJsonArray("Fields"); + for (int j = 0; j < fields.size(); j++) { + JsonObject field = fields.getJsonObject(j); + String value = field.getString("Value"); + switch (field.getString("Name").toLowerCase()) { + case "eventtime": + position.setDeviceTime(new Date(Long.parseLong(value) * 1000)); + break; + case "latitude": + position.setLatitude(Integer.parseInt(value) / 60000.0); + break; + case "longitude": + position.setLongitude(Integer.parseInt(value) / 60000.0); + break; + case "speed": + position.setSpeed(UnitsConverter.knotsFromKph(Integer.parseInt(value))); + break; + case "heading": + int heading = Integer.parseInt(value); + position.setCourse(heading <= 360 ? heading : 0); + break; + default: + break; + } + } + + if (position.getLatitude() != 0 && position.getLongitude() != 0) { + position.setValid(true); + position.setFixTime(position.getDeviceTime()); + } else { + getLastLocation(position, position.getDeviceTime()); + } + + positions.add(position); + + } + } + + return positions.isEmpty() ? null : positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/OrbcommProtocolPoller.java b/src/main/java/org/traccar/protocol/OrbcommProtocolPoller.java new file mode 100644 index 000000000..0f57bfb49 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OrbcommProtocolPoller.java @@ -0,0 +1,74 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.QueryStringEncoder; +import org.traccar.BaseProtocolPoller; +import org.traccar.Protocol; +import org.traccar.config.Config; +import org.traccar.config.Keys; + +import java.net.SocketAddress; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +public class OrbcommProtocolPoller extends BaseProtocolPoller { + + private final String accessId; + private final String password; + private final String host; + + private Date startTime = new Date(); + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public OrbcommProtocolPoller(Protocol protocol, Config config) { + super(config.getLong(Keys.PROTOCOL_INTERVAL.withPrefix(protocol.getName()))); + accessId = config.getString(Keys.ORBCOMM_ACCESS_ID); + password = config.getString(Keys.ORBCOMM_PASSWORD); + host = config.getString(Keys.PROTOCOL_ADDRESS.withPrefix(protocol.getName())); + } + + @Override + protected void sendRequest(Channel channel, SocketAddress remoteAddress) { + + QueryStringEncoder encoder = new QueryStringEncoder("/GLGW/2/RestMessages.svc/JSON/get_return_messages/"); + encoder.addParam("access_id", accessId); + encoder.addParam("password", password); + + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + encoder.addParam("start_utc", dateFormat.format(startTime)); + + HttpRequest request = new DefaultFullHttpRequest( + HttpVersion.HTTP_1_1, HttpMethod.GET, encoder.toString(), Unpooled.buffer()); + request.headers().add(HttpHeaderNames.HOST, host); + request.headers().add(HttpHeaderNames.CONTENT_LENGTH, 0); + channel.writeAndFlush(request); + } + +} diff --git a/src/main/java/org/traccar/protocol/OrionProtocol.java b/src/main/java/org/traccar/protocol/OrionProtocol.java index 8485ae638..2dec7cd06 100644 --- a/src/main/java/org/traccar/protocol/OrionProtocol.java +++ b/src/main/java/org/traccar/protocol/OrionProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class OrionProtocol extends BaseProtocol { - public OrionProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public OrionProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new OrionFrameDecoder()); pipeline.addLast(new OrionProtocolDecoder(OrionProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/OrionProtocolDecoder.java b/src/main/java/org/traccar/protocol/OrionProtocolDecoder.java index af819989e..681891edb 100644 --- a/src/main/java/org/traccar/protocol/OrionProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/OrionProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/OsmAndProtocol.java b/src/main/java/org/traccar/protocol/OsmAndProtocol.java index d3aa2fd6f..a86bc70d7 100644 --- a/src/main/java/org/traccar/protocol/OsmAndProtocol.java +++ b/src/main/java/org/traccar/protocol/OsmAndProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class OsmAndProtocol extends BaseProtocol { - public OsmAndProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public OsmAndProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(16384)); diff --git a/src/main/java/org/traccar/protocol/OsmAndProtocolDecoder.java b/src/main/java/org/traccar/protocol/OsmAndProtocolDecoder.java index ec9bbc240..c8968023a 100644 --- a/src/main/java/org/traccar/protocol/OsmAndProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/OsmAndProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,8 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.QueryStringDecoder; import org.traccar.BaseHttpProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; -import org.traccar.database.CommandsManager; import org.traccar.helper.DateUtil; import org.traccar.model.CellTower; import org.traccar.model.Command; @@ -61,6 +59,8 @@ public class OsmAndProtocolDecoder extends BaseHttpProtocolDecoder { position.setValid(true); Network network = new Network(); + Double latitude = null; + Double longitude = null; for (Map.Entry<String, List<String>> entry : params.entrySet()) { for (String value : entry.getValue()) { @@ -94,15 +94,15 @@ public class OsmAndProtocolDecoder extends BaseHttpProtocolDecoder { } break; case "lat": - position.setLatitude(Double.parseDouble(value)); + latitude = Double.parseDouble(value); break; case "lon": - position.setLongitude(Double.parseDouble(value)); + longitude = Double.parseDouble(value); break; case "location": String[] location = value.split(","); - position.setLatitude(Double.parseDouble(location[0])); - position.setLongitude(Double.parseDouble(location[1])); + latitude = Double.parseDouble(location[0]); + longitude = Double.parseDouble(location[1]); break; case "cell": String[] cell = value.split(","); @@ -143,6 +143,9 @@ public class OsmAndProtocolDecoder extends BaseHttpProtocolDecoder { case "driverUniqueId": position.set(Position.KEY_DRIVER_UNIQUE_ID, value); break; + case "charge": + position.set(Position.KEY_CHARGE, Boolean.parseBoolean(value)); + break; default: try { position.set(entry.getKey(), Double.parseDouble(value)); @@ -172,17 +175,17 @@ public class OsmAndProtocolDecoder extends BaseHttpProtocolDecoder { position.setNetwork(network); } - if (position.getLatitude() == 0 && position.getLongitude() == 0) { + if (latitude != null && longitude != null) { + position.setLatitude(latitude); + position.setLongitude(longitude); + } else { getLastLocation(position, position.getDeviceTime()); } if (position.getDeviceId() != 0) { String response = null; - CommandsManager commandsManager = Context.getCommandsManager(); - if (commandsManager != null) { - for (Command command : commandsManager.readQueuedCommands(position.getDeviceId(), 1)) { - response = command.getString(Command.KEY_DATA); - } + for (Command command : getCommandsManager().readQueuedCommands(position.getDeviceId(), 1)) { + response = command.getString(Command.KEY_DATA); } if (response != null) { sendResponse(channel, HttpResponseStatus.OK, Unpooled.copiedBuffer(response, StandardCharsets.UTF_8)); diff --git a/src/main/java/org/traccar/protocol/OutsafeProtocol.java b/src/main/java/org/traccar/protocol/OutsafeProtocol.java index c728a404d..0099be456 100644 --- a/src/main/java/org/traccar/protocol/OutsafeProtocol.java +++ b/src/main/java/org/traccar/protocol/OutsafeProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class OutsafeProtocol extends BaseProtocol { - public OutsafeProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public OutsafeProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(65535)); diff --git a/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java b/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java index 9de77d241..62b873be7 100644 --- a/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.Protocol; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/OwnTracksProtocol.java b/src/main/java/org/traccar/protocol/OwnTracksProtocol.java index 0086371d8..9ad337f19 100644 --- a/src/main/java/org/traccar/protocol/OwnTracksProtocol.java +++ b/src/main/java/org/traccar/protocol/OwnTracksProtocol.java @@ -22,13 +22,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class OwnTracksProtocol extends BaseProtocol { - public OwnTracksProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public OwnTracksProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(16384)); diff --git a/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java b/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java index 509d14ae4..71ac87168 100644 --- a/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java @@ -20,7 +20,7 @@ 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.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/PacificTrackProtocol.java b/src/main/java/org/traccar/protocol/PacificTrackProtocol.java index 08991ab64..709729ef1 100644 --- a/src/main/java/org/traccar/protocol/PacificTrackProtocol.java +++ b/src/main/java/org/traccar/protocol/PacificTrackProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class PacificTrackProtocol extends BaseProtocol { - public PacificTrackProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public PacificTrackProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new PacificTrackProtocolDecoder(PacificTrackProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java index b5d34a029..7079745be 100644 --- a/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/PathAwayProtocol.java b/src/main/java/org/traccar/protocol/PathAwayProtocol.java index 6b5d75c5e..1d13eea95 100644 --- a/src/main/java/org/traccar/protocol/PathAwayProtocol.java +++ b/src/main/java/org/traccar/protocol/PathAwayProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class PathAwayProtocol extends BaseProtocol { - public PathAwayProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public PathAwayProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(16384)); diff --git a/src/main/java/org/traccar/protocol/PathAwayProtocolDecoder.java b/src/main/java/org/traccar/protocol/PathAwayProtocolDecoder.java index 02a15e34a..3e7fa9a5b 100644 --- a/src/main/java/org/traccar/protocol/PathAwayProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PathAwayProtocolDecoder.java @@ -24,7 +24,7 @@ import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.QueryStringDecoder; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/PiligrimProtocol.java b/src/main/java/org/traccar/protocol/PiligrimProtocol.java index d88c1ab72..aa45a0def 100644 --- a/src/main/java/org/traccar/protocol/PiligrimProtocol.java +++ b/src/main/java/org/traccar/protocol/PiligrimProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class PiligrimProtocol extends BaseProtocol { - public PiligrimProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public PiligrimProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(16384)); diff --git a/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java b/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java index 26ce2fe53..34c879cb8 100644 --- a/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2014 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,16 +22,19 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.QueryStringDecoder; import org.traccar.BaseHttpProtocolDecoder; -import org.traccar.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; import org.traccar.model.Position; +import org.traccar.session.DeviceSession; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.LinkedList; import java.util.List; +import java.util.regex.Pattern; public class PiligrimProtocolDecoder extends BaseHttpProtocolDecoder { @@ -47,6 +50,21 @@ public class PiligrimProtocolDecoder extends BaseHttpProtocolDecoder { public static final int MSG_GPS_SENSORS = 0xF2; public static final int MSG_EVENTS = 0xF3; + private static final Pattern PATTERN = new PatternBuilder() + .expression("[^$]+") + .text("$GPRMC,") + .number("(dd)(dd)(dd).d+,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d{2,3})(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.d+),") // speed + .number("(d+.d+),") // course + .number("(dd)(dd)(dd),") // date (ddmmyy) + .any() + .compile(); + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -150,6 +168,42 @@ public class PiligrimProtocolDecoder extends BaseHttpProtocolDecoder { } return positions; + + } else if (uri.startsWith("/push.do")) { + + sendResponse(channel, "PUSH.DO: OK"); + + String sentence = request.content().toString(StandardCharsets.US_ASCII); + + String[] parts = sentence.split("&"); + String phone = parts[1].substring(16); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, phone); + if (deviceSession == null) { + return null; + } + + Parser parser = new Parser(PATTERN, parts[2]); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble()); + position.setCourse(parser.nextDouble()); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + return position; + } return null; diff --git a/src/main/java/org/traccar/protocol/PluginProtocol.java b/src/main/java/org/traccar/protocol/PluginProtocol.java index d5f28da9d..b2101b18d 100644 --- a/src/main/java/org/traccar/protocol/PluginProtocol.java +++ b/src/main/java/org/traccar/protocol/PluginProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class PluginProtocol extends BaseProtocol { - public PluginProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public PluginProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java b/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java index 65de211ac..6ee95d18a 100644 --- a/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/PolteProtocol.java b/src/main/java/org/traccar/protocol/PolteProtocol.java index a3e548716..69666cc0e 100644 --- a/src/main/java/org/traccar/protocol/PolteProtocol.java +++ b/src/main/java/org/traccar/protocol/PolteProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class PolteProtocol extends BaseProtocol { - public PolteProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public PolteProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(65535)); diff --git a/src/main/java/org/traccar/protocol/PolteProtocolDecoder.java b/src/main/java/org/traccar/protocol/PolteProtocolDecoder.java index ce45abef6..028de5424 100644 --- a/src/main/java/org/traccar/protocol/PolteProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PolteProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.Protocol; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/PortmanProtocol.java b/src/main/java/org/traccar/protocol/PortmanProtocol.java index b7faae08a..de78013fa 100644 --- a/src/main/java/org/traccar/protocol/PortmanProtocol.java +++ b/src/main/java/org/traccar/protocol/PortmanProtocol.java @@ -21,17 +21,21 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class PortmanProtocol extends BaseProtocol { - public PortmanProtocol() { + @Inject + public PortmanProtocol(Config config) { setSupportedDataCommands( Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/PortmanProtocolDecoder.java b/src/main/java/org/traccar/protocol/PortmanProtocolDecoder.java index e1847a2b2..da9403313 100644 --- a/src/main/java/org/traccar/protocol/PortmanProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PortmanProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/PretraceProtocol.java b/src/main/java/org/traccar/protocol/PretraceProtocol.java index 9d35c1c2f..b77dd97bf 100644 --- a/src/main/java/org/traccar/protocol/PretraceProtocol.java +++ b/src/main/java/org/traccar/protocol/PretraceProtocol.java @@ -21,17 +21,21 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class PretraceProtocol extends BaseProtocol { - public PretraceProtocol() { + @Inject + public PretraceProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_POSITION_PERIODIC); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ')')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/PretraceProtocolDecoder.java b/src/main/java/org/traccar/protocol/PretraceProtocolDecoder.java index a19384e62..ff6ad763a 100644 --- a/src/main/java/org/traccar/protocol/PretraceProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PretraceProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/PretraceProtocolEncoder.java b/src/main/java/org/traccar/protocol/PretraceProtocolEncoder.java index 1083a252e..a109e7a07 100644 --- a/src/main/java/org/traccar/protocol/PretraceProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/PretraceProtocolEncoder.java @@ -16,10 +16,9 @@ package org.traccar.protocol; import org.traccar.BaseProtocolEncoder; -import org.traccar.Context; +import org.traccar.Protocol; import org.traccar.helper.Checksum; import org.traccar.model.Command; -import org.traccar.Protocol; public class PretraceProtocolEncoder extends BaseProtocolEncoder { @@ -35,7 +34,7 @@ public class PretraceProtocolEncoder extends BaseProtocolEncoder { @Override protected Object encodeCommand(Command command) { - String uniqueId = Context.getIdentityManager().getById(command.getDeviceId()).getUniqueId(); + String uniqueId = getUniqueId(command.getDeviceId()); switch (command.getType()) { case Command.TYPE_CUSTOM: diff --git a/src/main/java/org/traccar/protocol/PricolProtocol.java b/src/main/java/org/traccar/protocol/PricolProtocol.java index 6821cd949..f5e904541 100644 --- a/src/main/java/org/traccar/protocol/PricolProtocol.java +++ b/src/main/java/org/traccar/protocol/PricolProtocol.java @@ -19,20 +19,24 @@ import io.netty.handler.codec.FixedLengthFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class PricolProtocol extends BaseProtocol { - public PricolProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public PricolProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new FixedLengthFrameDecoder(64)); pipeline.addLast(new PricolProtocolDecoder(PricolProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new PricolProtocolDecoder(PricolProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/PricolProtocolDecoder.java b/src/main/java/org/traccar/protocol/PricolProtocolDecoder.java index 190c68258..5f6805f09 100644 --- a/src/main/java/org/traccar/protocol/PricolProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PricolProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/ProgressProtocol.java b/src/main/java/org/traccar/protocol/ProgressProtocol.java index aac84205d..49eb6847f 100644 --- a/src/main/java/org/traccar/protocol/ProgressProtocol.java +++ b/src/main/java/org/traccar/protocol/ProgressProtocol.java @@ -19,14 +19,18 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class ProgressProtocol extends BaseProtocol { - public ProgressProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public ProgressProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 2, 4, 0, true)); pipeline.addLast(new ProgressProtocolDecoder(ProgressProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/ProgressProtocolDecoder.java b/src/main/java/org/traccar/protocol/ProgressProtocolDecoder.java index 0025cd9e7..e3a5881da 100644 --- a/src/main/java/org/traccar/protocol/ProgressProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ProgressProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/PstProtocol.java b/src/main/java/org/traccar/protocol/PstProtocol.java index d8c7008cb..73f978cbd 100644 --- a/src/main/java/org/traccar/protocol/PstProtocol.java +++ b/src/main/java/org/traccar/protocol/PstProtocol.java @@ -18,24 +18,28 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class PstProtocol extends BaseProtocol { - public PstProtocol() { + @Inject + public PstProtocol(Config config) { setSupportedDataCommands( Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new PstProtocolEncoder(PstProtocol.this)); pipeline.addLast(new PstProtocolDecoder(PstProtocol.this)); } }); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new PstFrameEncoder()); pipeline.addLast(new PstFrameDecoder()); pipeline.addLast(new PstProtocolEncoder(PstProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/PstProtocolDecoder.java b/src/main/java/org/traccar/protocol/PstProtocolDecoder.java index e3fe1af62..872e77a3a 100644 --- a/src/main/java/org/traccar/protocol/PstProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PstProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/Pt215Protocol.java b/src/main/java/org/traccar/protocol/Pt215Protocol.java index 09bd9b121..b272582a4 100644 --- a/src/main/java/org/traccar/protocol/Pt215Protocol.java +++ b/src/main/java/org/traccar/protocol/Pt215Protocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Pt215Protocol extends BaseProtocol { - public Pt215Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Pt215Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 1, -1, 0)); pipeline.addLast(new Pt215ProtocolDecoder(Pt215Protocol.this)); } diff --git a/src/main/java/org/traccar/protocol/Pt215ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Pt215ProtocolDecoder.java index 48ce7dede..f669c5ffd 100644 --- a/src/main/java/org/traccar/protocol/Pt215ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Pt215ProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/Pt3000Protocol.java b/src/main/java/org/traccar/protocol/Pt3000Protocol.java index 1ad0026a3..d72774f47 100644 --- a/src/main/java/org/traccar/protocol/Pt3000Protocol.java +++ b/src/main/java/org/traccar/protocol/Pt3000Protocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Pt3000Protocol extends BaseProtocol { - public Pt3000Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Pt3000Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, 'd')); // probably wrong pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/Pt3000ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Pt3000ProtocolDecoder.java index e7f9e062a..c33660f51 100644 --- a/src/main/java/org/traccar/protocol/Pt3000ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Pt3000ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/Pt502Protocol.java b/src/main/java/org/traccar/protocol/Pt502Protocol.java index 56444fb42..d5d30e8e8 100644 --- a/src/main/java/org/traccar/protocol/Pt502Protocol.java +++ b/src/main/java/org/traccar/protocol/Pt502Protocol.java @@ -19,20 +19,24 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class Pt502Protocol extends BaseProtocol { - public Pt502Protocol() { + @Inject + public Pt502Protocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_SET_TIMEZONE, Command.TYPE_ALARM_SPEED, Command.TYPE_OUTPUT_CONTROL, Command.TYPE_REQUEST_PHOTO); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new Pt502FrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new Pt502ProtocolEncoder(Pt502Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/Pt502ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Pt502ProtocolDecoder.java index ff92b51f1..2a6a81a65 100644 --- a/src/main/java/org/traccar/protocol/Pt502ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Pt502ProtocolDecoder.java @@ -20,8 +20,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; @@ -175,14 +174,13 @@ public class Pt502ProtocolDecoder extends BaseProtocolDecoder { } else { DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); - String uniqueId = Context.getIdentityManager().getById(deviceSession.getDeviceId()).getUniqueId(); Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); getLastLocation(position, null); - position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(uniqueId, photo, "jpg")); + position.set(Position.KEY_IMAGE, writeMediaFile(deviceSession.getUniqueId(), photo, "jpg")); photo.release(); photo = null; diff --git a/src/main/java/org/traccar/protocol/Pt60Protocol.java b/src/main/java/org/traccar/protocol/Pt60Protocol.java index c502426c5..58345f025 100644 --- a/src/main/java/org/traccar/protocol/Pt60Protocol.java +++ b/src/main/java/org/traccar/protocol/Pt60Protocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Pt60Protocol extends BaseProtocol { - public Pt60Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Pt60Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "@R#@", "@E#@")); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/Pt60ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Pt60ProtocolDecoder.java index 6a3fe2734..94b549fe6 100644 --- a/src/main/java/org/traccar/protocol/Pt60ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Pt60ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/R12wProtocol.java b/src/main/java/org/traccar/protocol/R12wProtocol.java index 3726233b4..a406f6306 100644 --- a/src/main/java/org/traccar/protocol/R12wProtocol.java +++ b/src/main/java/org/traccar/protocol/R12wProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class R12wProtocol extends BaseProtocol { - public R12wProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public R12wProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/R12wProtocolDecoder.java b/src/main/java/org/traccar/protocol/R12wProtocolDecoder.java index d60318447..3be784911 100644 --- a/src/main/java/org/traccar/protocol/R12wProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/R12wProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Checksum; diff --git a/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java b/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java index c9db10610..63ca3476c 100644 --- a/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java +++ b/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class RaceDynamicsProtocol extends BaseProtocol { - public RaceDynamicsProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public RaceDynamicsProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1500)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/RaceDynamicsProtocolDecoder.java b/src/main/java/org/traccar/protocol/RaceDynamicsProtocolDecoder.java index f441bf8ed..89639ad30 100644 --- a/src/main/java/org/traccar/protocol/RaceDynamicsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RaceDynamicsProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/RadarProtocol.java b/src/main/java/org/traccar/protocol/RadarProtocol.java index 9783778f0..9d88c6d72 100644 --- a/src/main/java/org/traccar/protocol/RadarProtocol.java +++ b/src/main/java/org/traccar/protocol/RadarProtocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class RadarProtocol extends BaseProtocol { - public RadarProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public RadarProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 12, 2, -14, 0)); pipeline.addLast(new RadarProtocolDecoder(RadarProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/RadarProtocolDecoder.java b/src/main/java/org/traccar/protocol/RadarProtocolDecoder.java index d87f77b84..818e97f8b 100644 --- a/src/main/java/org/traccar/protocol/RadarProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RadarProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.UnitsConverter; diff --git a/src/main/java/org/traccar/protocol/RaveonProtocol.java b/src/main/java/org/traccar/protocol/RaveonProtocol.java index 44faadb3b..db70396ee 100644 --- a/src/main/java/org/traccar/protocol/RaveonProtocol.java +++ b/src/main/java/org/traccar/protocol/RaveonProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class RaveonProtocol extends BaseProtocol { - public RaveonProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public RaveonProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/RaveonProtocolDecoder.java b/src/main/java/org/traccar/protocol/RaveonProtocolDecoder.java index 50acd20a1..dfc21bf69 100644 --- a/src/main/java/org/traccar/protocol/RaveonProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RaveonProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/RecodaProtocol.java b/src/main/java/org/traccar/protocol/RecodaProtocol.java index 0bc9870bc..0d50db01e 100644 --- a/src/main/java/org/traccar/protocol/RecodaProtocol.java +++ b/src/main/java/org/traccar/protocol/RecodaProtocol.java @@ -19,14 +19,18 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class RecodaProtocol extends BaseProtocol { - public RecodaProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public RecodaProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 4, 4, -8, 0, true)); pipeline.addLast(new RecodaProtocolDecoder(RecodaProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/RecodaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RecodaProtocolDecoder.java index 04098225f..0c417a62f 100644 --- a/src/main/java/org/traccar/protocol/RecodaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RecodaProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.UnitsConverter; diff --git a/src/main/java/org/traccar/protocol/RetranslatorProtocol.java b/src/main/java/org/traccar/protocol/RetranslatorProtocol.java index fae81f7d2..1d4b419bb 100644 --- a/src/main/java/org/traccar/protocol/RetranslatorProtocol.java +++ b/src/main/java/org/traccar/protocol/RetranslatorProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class RetranslatorProtocol extends BaseProtocol { - public RetranslatorProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public RetranslatorProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new RetranslatorFrameDecoder()); pipeline.addLast(new RetranslatorProtocolDecoder(RetranslatorProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/RetranslatorProtocolDecoder.java b/src/main/java/org/traccar/protocol/RetranslatorProtocolDecoder.java index 5bf6cef50..afbf7e511 100644 --- a/src/main/java/org/traccar/protocol/RetranslatorProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RetranslatorProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/RfTrackProtocol.java b/src/main/java/org/traccar/protocol/RfTrackProtocol.java new file mode 100644 index 000000000..d3b41e93e --- /dev/null +++ b/src/main/java/org/traccar/protocol/RfTrackProtocol.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class RfTrackProtocol extends BaseProtocol { + + @Inject + public RfTrackProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(16384)); + pipeline.addLast(new RfTrackProtocolDecoder(RfTrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/RfTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/RfTrackProtocolDecoder.java new file mode 100644 index 000000000..28a3ac29c --- /dev/null +++ b/src/main/java/org/traccar/protocol/RfTrackProtocolDecoder.java @@ -0,0 +1,163 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.QueryStringDecoder; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.Protocol; +import org.traccar.model.CellTower; +import org.traccar.model.Command; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; +import org.traccar.session.DeviceSession; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import java.io.StringReader; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class RfTrackProtocolDecoder extends BaseHttpProtocolDecoder { + + public RfTrackProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + QueryStringDecoder decoder = new QueryStringDecoder( + request.content().toString(StandardCharsets.US_ASCII), false); + Map<String, List<String>> params = decoder.parameters(); + + Position position = new Position(getProtocolName()); + Network network = new Network(); + + for (Map.Entry<String, List<String>> entry : params.entrySet()) { + for (String value : entry.getValue()) { + switch (entry.getKey()) { + case "i": + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, value); + if (deviceSession == null) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + break; + case "v": + position.set(Position.KEY_VERSION_FW, value); + break; + case "t": + position.setDeviceTime(new Date(Long.parseLong(value))); + break; + case "bat": + int battery = Integer.parseInt(value); + position.set(Position.KEY_BATTERY_LEVEL, battery & 0xff); + position.set("plugStatus", (battery >> 8) & 0x0f); + position.set(Position.KEY_CHARGE, ((battery >> 12) & 0x0f) == 1); + break; + case "id": + position.set("braceletId", value); + break; + case "rc": + int braceletCode = Integer.parseInt(value); + position.set("braceletCode", braceletCode & 0xffff); + position.set("braceletStatus", braceletCode >> 16); + break; + case "idt": + long braceletTime = Long.parseLong(value); + position.set("lastHeartbeat", (braceletTime >> 45) * 10); + position.set("lastPaired", ((braceletTime >> 30) & 0xffff) * 10); + position.set("lastUnpaired", ((braceletTime >> 15) & 0xffff) * 10); + break; + case "mt": + int vibrationTime = Integer.parseInt(value); + position.set("vibrationDevice", (vibrationTime & 0x7fff) * 10); + position.set("vibrationBracelet", (vibrationTime >> 15) * 10); + break; + case "gps": + JsonObject location = Json.createReader(new StringReader(value)).readObject(); + position.setValid(true); + position.setAccuracy(location.getJsonNumber("a").doubleValue()); + position.setLongitude(location.getJsonNumber("x").doubleValue()); + position.setLatitude(location.getJsonNumber("y").doubleValue()); + position.setAltitude(location.getJsonNumber("z").doubleValue()); + position.setFixTime(new Date(location.getJsonNumber("t").longValue())); + break; + case "gsm": + JsonObject cellInfo = Json.createReader(new StringReader(value)).readObject(); + int mcc = cellInfo.getInt("c"); + int mnc = cellInfo.getInt("n"); + JsonArray cells = cellInfo.getJsonArray("b"); + for (int i = 0; i < cells.size(); i++) { + JsonObject cell = cells.getJsonObject(i); + network.addCellTower(CellTower.from( + mcc, mnc, cell.getInt("l"), cell.getInt("c"), cell.getInt("b"))); + } + break; + case "dbm": + position.set(Position.KEY_RSSI, Integer.parseInt(value)); + break; + case "bar": + position.set("pressure", Double.parseDouble(value)); + break; + case "cob": + position.set("pressureChanges", value); + break; + case "wifi": + JsonArray wifiInfo = Json.createReader(new StringReader(value)).readArray(); + for (int i = 0; i < wifiInfo.size(); i++) { + JsonObject wifi = wifiInfo.getJsonObject(i); + network.addWifiAccessPoint(WifiAccessPoint.from( + wifi.getString("m").replace('-', ':'), wifi.getInt("l"))); + } + break; + case "u_ids": + position.set("unpairedIds", value); + break; + default: + break; + } + } + } + + if (position.getFixTime() == null) { + getLastLocation(position, position.getDeviceTime()); + } + + if (network.getCellTowers() != null || network.getWifiAccessPoints() != null) { + position.setNetwork(network); + } + + String response = "{}"; + for (Command command : getCommandsManager().readQueuedCommands(position.getDeviceId(), 1)) { + response = command.getString(Command.KEY_DATA); + } + sendResponse(channel, HttpResponseStatus.OK, Unpooled.copiedBuffer(response, StandardCharsets.UTF_8)); + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/RitiProtocol.java b/src/main/java/org/traccar/protocol/RitiProtocol.java index de1026672..9b9c00cb2 100644 --- a/src/main/java/org/traccar/protocol/RitiProtocol.java +++ b/src/main/java/org/traccar/protocol/RitiProtocol.java @@ -19,14 +19,18 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class RitiProtocol extends BaseProtocol { - public RitiProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public RitiProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 105, 2, 3, 0, true)); pipeline.addLast(new RitiProtocolDecoder(RitiProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/RitiProtocolDecoder.java b/src/main/java/org/traccar/protocol/RitiProtocolDecoder.java index 46267ca90..501d5faa7 100644 --- a/src/main/java/org/traccar/protocol/RitiProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RitiProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/RoboTrackProtocol.java b/src/main/java/org/traccar/protocol/RoboTrackProtocol.java index c2c531293..ab2bc5842 100644 --- a/src/main/java/org/traccar/protocol/RoboTrackProtocol.java +++ b/src/main/java/org/traccar/protocol/RoboTrackProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class RoboTrackProtocol extends BaseProtocol { - public RoboTrackProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public RoboTrackProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new RoboTrackFrameDecoder()); pipeline.addLast(new RoboTrackProtocolDecoder(RoboTrackProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/RoboTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/RoboTrackProtocolDecoder.java index b613f31d7..ffe16bd7b 100644 --- a/src/main/java/org/traccar/protocol/RoboTrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RoboTrackProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/RstProtocol.java b/src/main/java/org/traccar/protocol/RstProtocol.java index 10d11d493..109d91b16 100644 --- a/src/main/java/org/traccar/protocol/RstProtocol.java +++ b/src/main/java/org/traccar/protocol/RstProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class RstProtocol extends BaseProtocol { - public RstProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public RstProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new RstProtocolDecoder(RstProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java index 9e3261a04..fcc96fbf1 100644 --- a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocol.java b/src/main/java/org/traccar/protocol/RuptelaProtocol.java index 5d1f86553..99a9686f6 100644 --- a/src/main/java/org/traccar/protocol/RuptelaProtocol.java +++ b/src/main/java/org/traccar/protocol/RuptelaProtocol.java @@ -19,11 +19,15 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class RuptelaProtocol extends BaseProtocol { - public RuptelaProtocol() { + @Inject + public RuptelaProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_ENGINE_STOP, @@ -35,9 +39,9 @@ public class RuptelaProtocol extends BaseProtocol { Command.TYPE_OUTPUT_CONTROL, Command.TYPE_SET_CONNECTION, Command.TYPE_SET_ODOMETER); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 2, 0)); pipeline.addLast(new RuptelaProtocolEncoder(RuptelaProtocol.this)); pipeline.addLast(new RuptelaProtocolDecoder(RuptelaProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java index 5c2885a8b..77df0deb7 100644 --- a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DataConverter; @@ -50,6 +49,7 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_SMS_VIA_GPRS_RESPONSE = 7; public static final int MSG_SMS_VIA_GPRS = 8; public static final int MSG_DTCS = 9; + public static final int MSG_IDENTIFICATION = 15; public static final int MSG_SET_IO = 17; public static final int MSG_FILES = 37; public static final int MSG_EXTENDED_RECORDS = 68; @@ -102,6 +102,12 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { case 5: position.set(Position.KEY_IGNITION, readValue(buf, length, false) == 1); break; + case 29: + position.set(Position.KEY_POWER, readValue(buf, length, false)); + break; + case 30: + position.set(Position.KEY_BATTERY, readValue(buf, length, false) * 0.001); + break; case 74: position.set(Position.PREFIX_TEMP + 3, readValue(buf, length, true) * 0.1); break; @@ -110,22 +116,19 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { case 80: position.set(Position.PREFIX_TEMP + (id - 78), readValue(buf, length, true) * 0.1); break; - case 198: - if (readValue(buf, length, false) > 0) { - position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); - } - break; - case 199: - case 200: + case 134: if (readValue(buf, length, false) > 0) { position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); } break; - case 201: + case 136: if (readValue(buf, length, false) > 0) { position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); } break; + case 197: + position.set(Position.KEY_RPM, readValue(buf, length, false) * 0.125); + break; default: position.set(Position.PREFIX_IO + id, readValue(buf, length, false)); break; @@ -294,7 +297,7 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); getLastLocation(position, null); - position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(imei, photo, "jpg")); + position.set(Position.KEY_IMAGE, writeMediaFile(imei, photo, "jpg")); photo.release(); photo = null; return position; @@ -303,6 +306,18 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { return null; + } else if (type == MSG_IDENTIFICATION) { + + ByteBuf content = Unpooled.buffer(); + content.writeByte(1); + ByteBuf response = RuptelaProtocolEncoder.encodeContent(type, content); + content.release(); + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + return null; + } else { return decodeCommandResponse(deviceSession, type, buf); diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java index 442961b19..5ec971a98 100644 --- a/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java @@ -80,14 +80,14 @@ public class RuptelaProtocolEncoder extends BaseProtocolEncoder { return encodeContent(RuptelaProtocolDecoder.MSG_FIRMWARE_UPDATE, content); case Command.TYPE_OUTPUT_CONTROL: content.writeInt(command.getInteger(Command.KEY_INDEX)); - content.writeInt(Integer.parseInt(command.getString(Command.KEY_DATA))); + content.writeInt(command.getInteger(Command.KEY_DATA)); return encodeContent(RuptelaProtocolDecoder.MSG_SET_IO, content); case Command.TYPE_SET_CONNECTION: String c = command.getString(Command.KEY_SERVER) + "," + command.getInteger(Command.KEY_PORT) + ",TCP"; content.writeBytes(c.getBytes(StandardCharsets.US_ASCII)); return encodeContent(RuptelaProtocolDecoder.MSG_SET_CONNECTION, content); case Command.TYPE_SET_ODOMETER: - content.writeInt(Integer.parseInt(command.getString(Command.KEY_DATA))); + content.writeInt(command.getInteger(Command.KEY_DATA)); return encodeContent(RuptelaProtocolDecoder.MSG_SET_ODOMETER, content); default: return null; diff --git a/src/main/java/org/traccar/protocol/S168Protocol.java b/src/main/java/org/traccar/protocol/S168Protocol.java index e78664c40..f904ed9ff 100644 --- a/src/main/java/org/traccar/protocol/S168Protocol.java +++ b/src/main/java/org/traccar/protocol/S168Protocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class S168Protocol extends BaseProtocol { - public S168Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public S168Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '$')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java b/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java index 71aff1a65..cf665c6ba 100644 --- a/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,13 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; import java.net.SocketAddress; import java.text.DateFormat; @@ -48,9 +51,14 @@ public class S168ProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); + Network network = new Network(); + String content = values[4]; String[] fragments = content.split(";"); for (String fragment : fragments) { + if (fragment.isEmpty()) { + continue; + } int dataIndex = fragment.indexOf(':'); String type = fragment.substring(0, dataIndex); @@ -70,12 +78,44 @@ public class S168ProtocolDecoder extends BaseProtocolDecoder { position.setCourse(Integer.parseInt(values[index++])); position.setAltitude(Integer.parseInt(values[index++])); break; + case "CELL": + int cellCount = Integer.parseInt(values[index++]); + int mcc = Integer.parseInt(values[index++], 16); + int mnc = Integer.parseInt(values[index++], 16); + for (int i = 0; i < cellCount; i++) { + network.addCellTower(CellTower.from( + mcc, mnc, Integer.parseInt(values[index++], 16), Integer.parseInt(values[index++], 16), + Integer.parseInt(values[index++], 16))); + } + break; + case "WIFI": + int wifiCount = Integer.parseInt(values[index++]); + for (int i = 0; i < wifiCount; i++) { + network.addWifiAccessPoint(WifiAccessPoint.from( + values[index++].replace('-', ':'), Integer.parseInt(values[index++]))); + } + break; + case "STATUS": + position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(values[index++])); + position.set(Position.KEY_RSSI, Integer.parseInt(values[index++])); + break; default: break; } } - return position.getAttributes().containsKey(Position.KEY_SATELLITES) ? position : null; + if (network.getCellTowers() != null || network.getWifiAccessPoints() != null) { + position.setNetwork(network); + } + if (!position.hasAttribute(Position.KEY_SATELLITES)) { + getLastLocation(position, null); + } + + if (position.getNetwork() != null || !position.getAttributes().isEmpty()) { + return position; + } else { + return null; + } } } diff --git a/src/main/java/org/traccar/protocol/SabertekProtocol.java b/src/main/java/org/traccar/protocol/SabertekProtocol.java index 0ec847b60..403243cdc 100644 --- a/src/main/java/org/traccar/protocol/SabertekProtocol.java +++ b/src/main/java/org/traccar/protocol/SabertekProtocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.string.StringDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class SabertekProtocol extends BaseProtocol { - public SabertekProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public SabertekProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new SabertekFrameDecoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new SabertekProtocolDecoder(SabertekProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/SabertekProtocolDecoder.java b/src/main/java/org/traccar/protocol/SabertekProtocolDecoder.java index 3033aa2cc..71279812c 100644 --- a/src/main/java/org/traccar/protocol/SabertekProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SabertekProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/SanavProtocol.java b/src/main/java/org/traccar/protocol/SanavProtocol.java index 6799c57e6..1a0e7b0e9 100644 --- a/src/main/java/org/traccar/protocol/SanavProtocol.java +++ b/src/main/java/org/traccar/protocol/SanavProtocol.java @@ -20,21 +20,25 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class SanavProtocol extends BaseProtocol { - public SanavProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public SanavProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new SanavProtocolDecoder(SanavProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new SanavProtocolDecoder(SanavProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/SanavProtocolDecoder.java b/src/main/java/org/traccar/protocol/SanavProtocolDecoder.java index 7e1c158e6..6741cb67c 100644 --- a/src/main/java/org/traccar/protocol/SanavProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SanavProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/SanulProtocol.java b/src/main/java/org/traccar/protocol/SanulProtocol.java index 3104e9366..ea44bf868 100644 --- a/src/main/java/org/traccar/protocol/SanulProtocol.java +++ b/src/main/java/org/traccar/protocol/SanulProtocol.java @@ -19,15 +19,19 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class SanulProtocol extends BaseProtocol { - public SanulProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public SanulProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 3, 2, 0, 0, true)); pipeline.addLast(new SanulProtocolDecoder(SanulProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/SanulProtocolDecoder.java b/src/main/java/org/traccar/protocol/SanulProtocolDecoder.java index 036d1ee51..9568cd6d3 100644 --- a/src/main/java/org/traccar/protocol/SanulProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SanulProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/SatsolProtocol.java b/src/main/java/org/traccar/protocol/SatsolProtocol.java index b69fdd1fe..d90033e38 100644 --- a/src/main/java/org/traccar/protocol/SatsolProtocol.java +++ b/src/main/java/org/traccar/protocol/SatsolProtocol.java @@ -19,15 +19,19 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class SatsolProtocol extends BaseProtocol { - public SatsolProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public SatsolProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1400, 8, 2, 0, 0, true)); pipeline.addLast(new SatsolProtocolDecoder(SatsolProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/SatsolProtocolDecoder.java b/src/main/java/org/traccar/protocol/SatsolProtocolDecoder.java index c457d5620..37a84be04 100644 --- a/src/main/java/org/traccar/protocol/SatsolProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SatsolProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/SigfoxProtocol.java b/src/main/java/org/traccar/protocol/SigfoxProtocol.java index e2f2cbe1f..9a268af62 100644 --- a/src/main/java/org/traccar/protocol/SigfoxProtocol.java +++ b/src/main/java/org/traccar/protocol/SigfoxProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class SigfoxProtocol extends BaseProtocol { - public SigfoxProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public SigfoxProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(65535)); diff --git a/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java index ce577f392..4ed2bb51d 100644 --- a/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java @@ -22,7 +22,8 @@ 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.helper.BufferUtil; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DataConverter; @@ -159,18 +160,8 @@ public class SigfoxProtocolDecoder extends BaseHttpProtocolDecoder { if (event == 0x0f || event == 0x1f) { position.setValid(event >> 4 > 0); - - long value; - value = buf.readUnsignedInt(); - position.setLatitude(BitUtil.to(value, 31) * 0.000001); - if (BitUtil.check(value, 31)) { - position.setLatitude(-position.getLatitude()); - } - value = buf.readUnsignedInt(); - position.setLongitude(BitUtil.to(value, 31) * 0.000001); - if (BitUtil.check(value, 31)) { - position.setLongitude(-position.getLongitude()); - } + position.setLatitude(BufferUtil.readSignedMagnitudeInt(buf) * 0.000001); + position.setLongitude(BufferUtil.readSignedMagnitudeInt(buf) * 0.000001); position.set(Position.KEY_BATTERY, (int) buf.readUnsignedByte()); diff --git a/src/main/java/org/traccar/protocol/SiwiProtocol.java b/src/main/java/org/traccar/protocol/SiwiProtocol.java index 8963721c8..f12958a50 100644 --- a/src/main/java/org/traccar/protocol/SiwiProtocol.java +++ b/src/main/java/org/traccar/protocol/SiwiProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class SiwiProtocol extends BaseProtocol { - public SiwiProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public SiwiProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new SiwiProtocolDecoder(SiwiProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java b/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java index bf8bfab77..7ba501834 100644 --- a/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/SkypatrolProtocol.java b/src/main/java/org/traccar/protocol/SkypatrolProtocol.java index 7c6203d86..7ae26f634 100644 --- a/src/main/java/org/traccar/protocol/SkypatrolProtocol.java +++ b/src/main/java/org/traccar/protocol/SkypatrolProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class SkypatrolProtocol extends BaseProtocol { - public SkypatrolProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public SkypatrolProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new SkypatrolProtocolDecoder(SkypatrolProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/SkypatrolProtocolDecoder.java b/src/main/java/org/traccar/protocol/SkypatrolProtocolDecoder.java index 8aae310bb..6ffcbbe44 100644 --- a/src/main/java/org/traccar/protocol/SkypatrolProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SkypatrolProtocolDecoder.java @@ -20,8 +20,7 @@ import io.netty.channel.Channel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.config.Keys; import org.traccar.helper.BitUtil; @@ -35,11 +34,15 @@ public class SkypatrolProtocolDecoder extends BaseProtocolDecoder { private static final Logger LOGGER = LoggerFactory.getLogger(SkypatrolProtocolDecoder.class); - private final long defaultMask; + private long defaultMask; public SkypatrolProtocolDecoder(Protocol protocol) { super(protocol); - defaultMask = Context.getConfig().getInteger(Keys.PROTOCOL_MASK.withPrefix(getProtocolName())); + } + + @Override + protected void init() { + defaultMask = getConfig().getInteger(Keys.PROTOCOL_MASK.withPrefix(getProtocolName())); } private static double convertCoordinate(long coordinate) { diff --git a/src/main/java/org/traccar/protocol/SmartSoleProtocol.java b/src/main/java/org/traccar/protocol/SmartSoleProtocol.java index bcf43f68b..cb7efb7ee 100644 --- a/src/main/java/org/traccar/protocol/SmartSoleProtocol.java +++ b/src/main/java/org/traccar/protocol/SmartSoleProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class SmartSoleProtocol extends BaseProtocol { - public SmartSoleProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public SmartSoleProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '$')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/SmartSoleProtocolDecoder.java b/src/main/java/org/traccar/protocol/SmartSoleProtocolDecoder.java index 04920c969..7fc38f061 100644 --- a/src/main/java/org/traccar/protocol/SmartSoleProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SmartSoleProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/SmokeyProtocol.java b/src/main/java/org/traccar/protocol/SmokeyProtocol.java index 482c8347c..22b343537 100644 --- a/src/main/java/org/traccar/protocol/SmokeyProtocol.java +++ b/src/main/java/org/traccar/protocol/SmokeyProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class SmokeyProtocol extends BaseProtocol { - public SmokeyProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public SmokeyProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new SmokeyProtocolDecoder(SmokeyProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/SmokeyProtocolDecoder.java b/src/main/java/org/traccar/protocol/SmokeyProtocolDecoder.java index 9da52e97a..2244ad289 100644 --- a/src/main/java/org/traccar/protocol/SmokeyProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SmokeyProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java b/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java index 53a948cdc..0676aa629 100644 --- a/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java +++ b/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class SolarPoweredProtocol extends BaseProtocol { - public SolarPoweredProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public SolarPoweredProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HuabaoFrameDecoder()); pipeline.addLast(new SolarPoweredProtocolDecoder(SolarPoweredProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java b/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java index 9d5dc072f..0432fbd03 100644 --- a/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/SpotProtocol.java b/src/main/java/org/traccar/protocol/SpotProtocol.java index bbf0e8d8a..6bd802fed 100644 --- a/src/main/java/org/traccar/protocol/SpotProtocol.java +++ b/src/main/java/org/traccar/protocol/SpotProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.http.HttpResponseEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class SpotProtocol extends BaseProtocol { - public SpotProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public SpotProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpObjectAggregator(65535)); diff --git a/src/main/java/org/traccar/protocol/SpotProtocolDecoder.java b/src/main/java/org/traccar/protocol/SpotProtocolDecoder.java index 34417d95f..d493b748d 100644 --- a/src/main/java/org/traccar/protocol/SpotProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SpotProtocolDecoder.java @@ -20,7 +20,7 @@ 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.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateUtil; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/StarLinkProtocol.java b/src/main/java/org/traccar/protocol/StarLinkProtocol.java index 5630722ee..d578fa705 100644 --- a/src/main/java/org/traccar/protocol/StarLinkProtocol.java +++ b/src/main/java/org/traccar/protocol/StarLinkProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class StarLinkProtocol extends BaseProtocol { - public StarLinkProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public StarLinkProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java index 7a6b6f4fe..aa23bfac5 100644 --- a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,9 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.config.Keys; +import org.traccar.helper.model.AttributeUtil; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DataConverter; import org.traccar.helper.Parser; @@ -55,17 +56,21 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { public StarLinkProtocolDecoder(Protocol protocol) { super(protocol); + } - setFormat(Context.getConfig().getString( + @Override + protected void init() { + setFormat(getConfig().getString( getProtocolName() + ".format", "#EDT#,#EID#,#PDT#,#LAT#,#LONG#,#SPD#,#HEAD#,#ODO#," + "#IN1#,#IN2#,#IN3#,#IN4#,#OUT1#,#OUT2#,#OUT3#,#OUT4#,#LAC#,#CID#,#VIN#,#VBAT#,#DEST#,#IGN#,#ENG#")); - setDateFormat(Context.getConfig().getString(getProtocolName() + ".dateFormat", "yyMMddHHmmss")); + setDateFormat(getConfig().getString(getProtocolName() + ".dateFormat", "yyMMddHHmmss")); } public String[] getFormat(long deviceId) { - return Context.getIdentityManager().lookupAttributeString( - deviceId, getProtocolName() + ".format", format, false, false).split(","); + String value = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_FORMAT.withPrefix(getProtocolName()), deviceId); + return (value != null ? value : format).split(","); } public void setFormat(String format) { @@ -73,8 +78,9 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { } public DateFormat getDateFormat(long deviceId) { - DateFormat dateFormat = new SimpleDateFormat(Context.getIdentityManager().lookupAttributeString( - deviceId, getProtocolName() + ".dateFormat", this.dateFormat, false, false)); + String value = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_DATE_FORMAT.withPrefix(getProtocolName()), deviceId); + DateFormat dateFormat = new SimpleDateFormat(value != null ? value : this.dateFormat); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); return dateFormat; } @@ -313,7 +319,7 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { } if (lac != null && cid != null) { - position.setNetwork(new Network(CellTower.fromLacCid(lac, cid))); + position.setNetwork(new Network(CellTower.fromLacCid(getConfig(), lac, cid))); } if (event == 20) { diff --git a/src/main/java/org/traccar/protocol/StarcomProtocol.java b/src/main/java/org/traccar/protocol/StarcomProtocol.java index 63a6143a6..33c3a4776 100644 --- a/src/main/java/org/traccar/protocol/StarcomProtocol.java +++ b/src/main/java/org/traccar/protocol/StarcomProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class StarcomProtocol extends BaseProtocol { - public StarcomProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public StarcomProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StarcomProtocolDecoder(StarcomProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java index 5ffddb318..56ab733c8 100644 --- a/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/StartekProtocol.java b/src/main/java/org/traccar/protocol/StartekProtocol.java index 32f1c5a29..d010df858 100644 --- a/src/main/java/org/traccar/protocol/StartekProtocol.java +++ b/src/main/java/org/traccar/protocol/StartekProtocol.java @@ -21,19 +21,23 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class StartekProtocol extends BaseProtocol { - public StartekProtocol() { + @Inject + public StartekProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_OUTPUT_CONTROL, Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java index 042518cb2..8e3624cb5 100644 --- a/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/StartekProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.Parser; @@ -70,17 +70,30 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder { .number("(x+),") // outputs .number("(x+)|") // power .number("(x+)") // battery - .groupBegin() - .text("|") - .expression("([^,]+)").optional() // adc + .expression("([^,]+)?") // adc .groupBegin() .text(",") .number("d,") // extended - .expression("([^,]+)?,") // fuel - .expression("([^,]+)?,?") // temperature + .expression("([^,]+)?") // fuel + .groupBegin() + .text(",") + .expression("([^,]+)?") // temperature + .groupBegin() + .text(",") + .groupBegin() + .number("(d+)?|") // rpm + .number("(d+)?|") // engine load + .number("(d+)?|") // maf flow + .number("(d+)?|") // intake pressure + .number("(d+)?|") // intake temperature + .number("(d+)?|") // throttle + .number("(d+)?|") // coolant temperature + .number("(d+)?|") // instant fuel + .number("(d+)[%L]").optional() // fuel level + .groupEnd("?") + .groupEnd("?") .groupEnd("?") .groupEnd("?") - .any() .compile(); private String decodeAlarm(int value) { @@ -181,7 +194,7 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder { if (parser.hasNext()) { String[] adc = parser.next().split("\\|"); - for (int i = 0; i < adc.length; i++) { + for (int i = 1; i < adc.length; i++) { position.set(Position.PREFIX_ADC + (i + 1), Integer.parseInt(adc[i], 16) * 0.01); } } @@ -208,6 +221,24 @@ public class StartekProtocolDecoder extends BaseProtocolDecoder { } } + if (parser.hasNext(6)) { + position.set(Position.KEY_RPM, parser.nextInt()); + position.set(Position.KEY_ENGINE_LOAD, parser.nextInt()); + position.set("airFlow", parser.nextInt()); + position.set("airPressure", parser.nextInt()); + if (parser.hasNext()) { + position.set("airTemp", parser.nextInt() - 40); + } + position.set(Position.KEY_THROTTLE, parser.nextInt()); + if (parser.hasNext()) { + position.set(Position.KEY_COOLANT_TEMP, parser.nextInt() - 40); + } + if (parser.hasNext()) { + position.set(Position.KEY_FUEL_CONSUMPTION, parser.nextInt() * 0.1); + } + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); + } + return position; } diff --git a/src/main/java/org/traccar/protocol/StbProtocol.java b/src/main/java/org/traccar/protocol/StbProtocol.java index 002ed86c7..af4e0d2c4 100644 --- a/src/main/java/org/traccar/protocol/StbProtocol.java +++ b/src/main/java/org/traccar/protocol/StbProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class StbProtocol extends BaseProtocol { - public StbProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public StbProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new JsonFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/StbProtocolDecoder.java b/src/main/java/org/traccar/protocol/StbProtocolDecoder.java index cc985d605..641359bfd 100644 --- a/src/main/java/org/traccar/protocol/StbProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/StbProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,12 @@ */ package org.traccar.protocol; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.model.Position; +import org.traccar.session.DeviceSession; import javax.json.Json; import javax.json.JsonObject; @@ -42,29 +39,13 @@ public class StbProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_PROPERTY = 310; public static final int MSG_ALARM = 410; - public static class Response { - @JsonProperty("msgType") - private int type; - @JsonProperty("devId") - private String deviceId; - @JsonProperty("result") - private int result; - @JsonProperty("txnNo") - private String transaction; - } - private void sendResponse( - Channel channel, SocketAddress remoteAddress, int type, String deviceId, JsonObject root) - throws JsonProcessingException { - - Response response = new Response(); - response.type = type + 1; - response.deviceId = deviceId; - response.result = 1; - response.transaction = root.getString("txnNo"); + Channel channel, SocketAddress remoteAddress, int type, String deviceId, JsonObject root) { + String response = String.format( + "{ \"msgType\": %d, \"devId\": \"%s\", \"result\": 1, \"txnNo\": \"%s\" }", + type + 1, deviceId, root.getString("txnNo")); if (channel != null) { - channel.writeAndFlush(new NetworkMessage( - Context.getObjectMapper().writeValueAsString(response), remoteAddress)); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } } diff --git a/src/main/java/org/traccar/protocol/Stl060Protocol.java b/src/main/java/org/traccar/protocol/Stl060Protocol.java index 2711e936b..83b5db3bb 100644 --- a/src/main/java/org/traccar/protocol/Stl060Protocol.java +++ b/src/main/java/org/traccar/protocol/Stl060Protocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Stl060Protocol extends BaseProtocol { - public Stl060Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Stl060Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new Stl060FrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/Stl060ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Stl060ProtocolDecoder.java index 7b0055aa1..dc1fa3ba3 100644 --- a/src/main/java/org/traccar/protocol/Stl060ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Stl060ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/SuntechProtocol.java b/src/main/java/org/traccar/protocol/SuntechProtocol.java index 199885537..4253b761b 100644 --- a/src/main/java/org/traccar/protocol/SuntechProtocol.java +++ b/src/main/java/org/traccar/protocol/SuntechProtocol.java @@ -19,11 +19,15 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class SuntechProtocol extends BaseProtocol { - public SuntechProtocol() { + @Inject + public SuntechProtocol(Config config) { setSupportedDataCommands( Command.TYPE_OUTPUT_CONTROL, Command.TYPE_REBOOT_DEVICE, @@ -32,9 +36,9 @@ public class SuntechProtocol extends BaseProtocol { Command.TYPE_ENGINE_RESUME, Command.TYPE_ALARM_ARM, Command.TYPE_ALARM_DISARM); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new SuntechFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new SuntechProtocolEncoder(SuntechProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java index 2d00ea81e..047a1822a 100644 --- a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,13 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.config.Keys; +import org.traccar.helper.BufferUtil; +import org.traccar.helper.model.AttributeUtil; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; @@ -34,10 +37,17 @@ import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; import java.util.TimeZone; +import java.util.stream.Collectors; public class SuntechProtocolDecoder extends BaseProtocolDecoder { + private boolean universal; private String prefix; private int protocolType; @@ -46,10 +56,16 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { private boolean includeRpm; private boolean includeTemp; + private ByteBuf crash; + public SuntechProtocolDecoder(Protocol protocol) { super(protocol); } + public boolean getUniversal() { + return universal; + } + public String getPrefix() { return prefix; } @@ -59,8 +75,8 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } public int getProtocolType(long deviceId) { - return Context.getIdentityManager().lookupAttributeInteger( - deviceId, getProtocolName() + ".protocolType", protocolType, false, true); + Integer value = AttributeUtil.lookup(getCacheManager(), Keys.PROTOCOL_TYPE, deviceId); + return value != null ? value : protocolType; } public void setHbm(boolean hbm) { @@ -68,8 +84,8 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } public boolean isHbm(long deviceId) { - return Context.getIdentityManager().lookupAttributeBoolean( - deviceId, getProtocolName() + ".hbm", hbm, false, true); + Boolean value = AttributeUtil.lookup(getCacheManager(), Keys.PROTOCOL_HBM, deviceId); + return value != null ? value : hbm; } public void setIncludeAdc(boolean includeAdc) { @@ -77,8 +93,9 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } public boolean isIncludeAdc(long deviceId) { - return Context.getIdentityManager().lookupAttributeBoolean( - deviceId, getProtocolName() + ".includeAdc", includeAdc, false, true); + Boolean value = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_INCLUDE_ADC.withPrefix(getProtocolName()), deviceId); + return value != null ? value : includeAdc; } public void setIncludeRpm(boolean includeRpm) { @@ -86,8 +103,9 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } public boolean isIncludeRpm(long deviceId) { - return Context.getIdentityManager().lookupAttributeBoolean( - deviceId, getProtocolName() + ".includeRpm", includeRpm, false, true); + Boolean value = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_INCLUDE_RPM.withPrefix(getProtocolName()), deviceId); + return value != null ? value : includeRpm; } public void setIncludeTemp(boolean includeTemp) { @@ -95,8 +113,9 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } public boolean isIncludeTemp(long deviceId) { - return Context.getIdentityManager().lookupAttributeBoolean( - deviceId, getProtocolName() + ".includeTemp", includeTemp, false, true); + Boolean value = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_INCLUDE_TEMPERATURE.withPrefix(getProtocolName()), deviceId); + return value != null ? value : includeTemp; } private Position decode9( @@ -185,12 +204,16 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { return Position.ALARM_POWER_RESTORED; case 41: return Position.ALARM_POWER_CUT; + case 42: + return Position.ALARM_SOS; case 46: return Position.ALARM_ACCELERATION; case 47: return Position.ALARM_BRAKING; case 50: return Position.ALARM_JAMMING; + case 132: + return Position.ALARM_DOOR; default: return null; } @@ -358,7 +381,7 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } } else if (attribute.startsWith("GTSL")) { position.set(Position.KEY_DRIVER_UNIQUE_ID, attribute.split("\\|")[4]); - } else { + } else if (attribute.contains("=")) { String[] pair = attribute.split("="); if (pair.length >= 2) { String value = pair[1].trim(); @@ -381,6 +404,8 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { break; } } + } else { + position.set("serial", attribute.trim()); } remaining -= attribute.length() + 1; } @@ -448,7 +473,7 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { String type = values[index++]; - if (!type.equals("STT") && !type.equals("ALT") && !type.equals("BLE")) { + if (!type.equals("STT") && !type.equals("ALT") && !type.equals("BLE") && !type.equals("RES")) { return null; } @@ -461,6 +486,14 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { position.setDeviceId(deviceSession.getDeviceId()); position.set(Position.KEY_TYPE, type); + if (type.equals("RES")) { + getLastLocation(position, null); + position.set( + Position.KEY_RESULT, + Arrays.stream(values, index, values.length).collect(Collectors.joining(";"))); + return position; + } + int mask; if (type.equals("BLE")) { mask = 0b1100000110110; @@ -550,7 +583,9 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } if (BitUtil.check(mask, 17)) { - position.set(Position.KEY_INPUT, Integer.parseInt(values[index++])); + int input = Integer.parseInt(values[index++]); + position.set(Position.KEY_IGNITION, BitUtil.check(input, 0)); + position.set(Position.KEY_INPUT, input); } if (BitUtil.check(mask, 18)) { @@ -559,7 +594,8 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { if (type.equals("ALT")) { if (BitUtil.check(mask, 19)) { - position.set("alertId", values[index++]); + int alertId = Integer.parseInt(values[index++]); + position.set(Position.KEY_ALARM, decodeAlert(alertId)); } if (BitUtil.check(mask, 20)) { position.set("alertModifier", values[index++]); @@ -654,19 +690,11 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { } if (BitUtil.check(mask, 11)) { - long value = buf.readUnsignedInt(); - if (BitUtil.check(value, 31)) { - value = -BitUtil.to(value, 31); - } - position.setLatitude(value / 1000000.0); + position.setLatitude(BufferUtil.readSignedMagnitudeInt(buf) / 1000000.0); } if (BitUtil.check(mask, 12)) { - long value = buf.readUnsignedInt(); - if (BitUtil.check(value, 31)) { - value = -BitUtil.to(value, 31); - } - position.setLongitude(value / 1000000.0); + position.setLongitude(BufferUtil.readSignedMagnitudeInt(buf) / 1000000.0); } if (BitUtil.check(mask, 13)) { @@ -732,6 +760,89 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { return position; } + private Collection<Position> decodeCrashReport(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + if (buf.getByte(buf.readerIndex() + 3) != ';') { + return null; + } + + String[] values = buf.readCharSequence(23, StandardCharsets.US_ASCII).toString().split(";"); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[1]); + if (deviceSession == null) { + return null; + } + + int currentIndex = Integer.parseInt(values[2]); + int totalIndex = Integer.parseInt(values[3]); + + if (crash == null) { + crash = Unpooled.buffer(); + } + + crash.writeBytes(buf.readSlice(buf.readableBytes() - 3)); + + if (currentIndex == totalIndex) { + + LinkedList<Position> positions = new LinkedList<>(); + + Date crashTime = new DateBuilder() + .setDate(crash.readUnsignedByte(), crash.readUnsignedByte(), crash.readUnsignedByte()) + .setTime(crash.readUnsignedByte(), crash.readUnsignedByte(), crash.readUnsignedByte()) + .getDate(); + + List<Date> times = Arrays.asList( + new Date(crashTime.getTime() - 3000), + new Date(crashTime.getTime() - 2000), + new Date(crashTime.getTime() - 1000), + new Date(crashTime.getTime() + 1000)); + + for (Date time : times) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + position.setTime(time); + position.setLatitude(crash.readIntLE() * 0.0000001); + position.setLongitude(crash.readIntLE() * 0.0000001); + position.setSpeed(UnitsConverter.knotsFromKph(crash.readUnsignedShort() * 0.01)); + position.setCourse(crash.readUnsignedShort() * 0.01); + + StringBuilder value = new StringBuilder("["); + for (int i = 0; i < 100; i++) { + if (value.length() > 1) { + value.append(","); + } + value.append("["); + value.append(crash.readShortLE()); + value.append(","); + value.append(crash.readShortLE()); + value.append(","); + value.append(crash.readShortLE()); + value.append("]"); + } + value.append("]"); + + position.set(Position.KEY_G_SENSOR, value.toString()); + + positions.add(position); + + } + + crash.release(); + crash = null; + + return positions; + + } else { + + return null; + + } + + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -740,14 +851,18 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { if (buf.getByte(buf.readerIndex() + 1) == 0) { + universal = true; return decodeBinary(channel, remoteAddress, buf); } else { - String[] values = buf.toString(StandardCharsets.US_ASCII).split(";"); + String[] values = buf.toString(StandardCharsets.US_ASCII).split(";", -1); prefix = values[0]; - if (prefix.length() < 5) { + if (prefix.equals("CRR")) { + return decodeCrashReport(channel, remoteAddress, buf); + } else if (prefix.length() < 5) { + universal = true; return decodeUniversal(channel, remoteAddress, values); } else if (prefix.endsWith("HTE")) { return decodeTravelReport(channel, remoteAddress, values); diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java index 3b4995110..a4faacf13 100644 --- a/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,49 +27,95 @@ public class SuntechProtocolEncoder extends StringProtocolEncoder { super(protocol); } - private String getPrefix(Channel channel) { - String prefix = "SA200CMD"; + @Override + protected Object encodeCommand(Channel channel, Command command) { + + boolean universal = false; + String prefix = "SA200"; if (channel != null) { SuntechProtocolDecoder protocolDecoder = BasePipelineFactory.getHandler(channel.pipeline(), SuntechProtocolDecoder.class); if (protocolDecoder != null) { + universal = protocolDecoder.getUniversal(); String decoderPrefix = protocolDecoder.getPrefix(); if (decoderPrefix != null && decoderPrefix.length() > 5) { - prefix = decoderPrefix.substring(0, decoderPrefix.length() - 3) + "CMD"; + prefix = decoderPrefix.substring(0, decoderPrefix.length() - 3); } } } - return prefix; - } - - @Override - protected Object encodeCommand(Channel channel, Command command) { - String prefix = getPrefix(channel); + if (universal) { + return encodeUniversalCommand(command); + } else { + return encodeLegacyCommand(prefix, command); + } + } + protected Object encodeUniversalCommand(Command command) { switch (command.getType()) { case Command.TYPE_REBOOT_DEVICE: - return formatCommand(command, prefix + ";%s;02;Reboot\r", Command.KEY_UNIQUE_ID); + return formatCommand(command, "CMD;%s;03;03\r", Command.KEY_UNIQUE_ID); case Command.TYPE_POSITION_SINGLE: - return formatCommand(command, prefix + ";%s;02;\r", Command.KEY_UNIQUE_ID); + return formatCommand(command, "CMD;%s;03;01\r", Command.KEY_UNIQUE_ID); case Command.TYPE_OUTPUT_CONTROL: - if (command.getAttributes().containsKey(Command.KEY_DATA)) { - if (command.getAttributes().get(Command.KEY_DATA).equals("1")) { - return formatCommand(command, prefix + ";%s;02;Enable%s\r", - Command.KEY_UNIQUE_ID, Command.KEY_INDEX); - } else { - return formatCommand(command, prefix + ";%s;02;Disable%s\r", - Command.KEY_UNIQUE_ID, Command.KEY_INDEX); + if (command.getAttributes().get(Command.KEY_DATA).equals("1")) { + switch (command.getInteger(Command.KEY_INDEX)) { + case 1: + return formatCommand(command, "CMD;%s;04;01\r", Command.KEY_UNIQUE_ID); + case 2: + return formatCommand(command, "CMD;%s;04;03\r", Command.KEY_UNIQUE_ID); + case 3: + return formatCommand(command, "CMD;%s;04;09\r", Command.KEY_UNIQUE_ID); + default: + return null; } + } else { + switch (command.getInteger(Command.KEY_INDEX)) { + case 1: + return formatCommand(command, "CMD;%s;04;02\r", Command.KEY_UNIQUE_ID); + case 2: + return formatCommand(command, "CMD;%s;04;04\r", Command.KEY_UNIQUE_ID); + case 3: + return formatCommand(command, "CMD;%s;04;10\r", Command.KEY_UNIQUE_ID); + default: + return null; + } + } + case Command.TYPE_ENGINE_STOP: + return formatCommand(command, "CMD;%s;04;01\r", Command.KEY_UNIQUE_ID); + case Command.TYPE_ENGINE_RESUME: + return formatCommand(command, "CMD;%s;04;02\r", Command.KEY_UNIQUE_ID); + case Command.TYPE_ALARM_ARM: + return formatCommand(command, "CMD;%s;04;03\r", Command.KEY_UNIQUE_ID); + case Command.TYPE_ALARM_DISARM: + return formatCommand(command, "CMD;%s;04;04\r", Command.KEY_UNIQUE_ID); + default: + return null; + } + } + + protected Object encodeLegacyCommand(String prefix, Command command) { + switch (command.getType()) { + case Command.TYPE_REBOOT_DEVICE: + return formatCommand(command, prefix + "CMD;%s;02;Reboot\r", Command.KEY_UNIQUE_ID); + case Command.TYPE_POSITION_SINGLE: + return formatCommand(command, prefix + "CMD;%s;02;StatusReq\r", Command.KEY_UNIQUE_ID); + case Command.TYPE_OUTPUT_CONTROL: + if (command.getAttributes().get(Command.KEY_DATA).equals("1")) { + return formatCommand(command, prefix + "CMD;%s;02;Enable%s\r", + Command.KEY_UNIQUE_ID, Command.KEY_INDEX); + } else { + return formatCommand(command, prefix + "CMD;%s;02;Disable%s\r", + Command.KEY_UNIQUE_ID, Command.KEY_INDEX); } case Command.TYPE_ENGINE_STOP: - return formatCommand(command, prefix + ";%s;02;Enable1\r", Command.KEY_UNIQUE_ID); + return formatCommand(command, prefix + "CMD;%s;02;Enable1\r", Command.KEY_UNIQUE_ID); case Command.TYPE_ENGINE_RESUME: - return formatCommand(command, prefix + ";%s;02;Disable1\r", Command.KEY_UNIQUE_ID); + return formatCommand(command, prefix + "CMD;%s;02;Disable1\r", Command.KEY_UNIQUE_ID); case Command.TYPE_ALARM_ARM: - return formatCommand(command, prefix + ";%s;02;Enable2\r", Command.KEY_UNIQUE_ID); + return formatCommand(command, prefix + "CMD;%s;02;Enable2\r", Command.KEY_UNIQUE_ID); case Command.TYPE_ALARM_DISARM: - return formatCommand(command, prefix + ";%s;02;Disable2\r", Command.KEY_UNIQUE_ID); + return formatCommand(command, prefix + "CMD;%s;02;Disable2\r", Command.KEY_UNIQUE_ID); default: return null; } diff --git a/src/main/java/org/traccar/protocol/SupermateProtocol.java b/src/main/java/org/traccar/protocol/SupermateProtocol.java index 46625ddc7..4290b7126 100644 --- a/src/main/java/org/traccar/protocol/SupermateProtocol.java +++ b/src/main/java/org/traccar/protocol/SupermateProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class SupermateProtocol extends BaseProtocol { - public SupermateProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public SupermateProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "#")); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/SupermateProtocolDecoder.java b/src/main/java/org/traccar/protocol/SupermateProtocolDecoder.java index 40a25bb91..f53f0f598 100644 --- a/src/main/java/org/traccar/protocol/SupermateProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SupermateProtocolDecoder.java @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/SviasProtocol.java b/src/main/java/org/traccar/protocol/SviasProtocol.java index accfa173f..7c6624f7c 100644 --- a/src/main/java/org/traccar/protocol/SviasProtocol.java +++ b/src/main/java/org/traccar/protocol/SviasProtocol.java @@ -21,12 +21,15 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; - +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class SviasProtocol extends BaseProtocol { - public SviasProtocol() { + @Inject + public SviasProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_POSITION_SINGLE, @@ -36,9 +39,9 @@ public class SviasProtocol extends BaseProtocol { Command.TYPE_ALARM_ARM, Command.TYPE_ALARM_DISARM, Command.TYPE_ALARM_REMOVE); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "]")); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/SviasProtocolDecoder.java b/src/main/java/org/traccar/protocol/SviasProtocolDecoder.java index 7e783f6cd..d7b126167 100644 --- a/src/main/java/org/traccar/protocol/SviasProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SviasProtocolDecoder.java @@ -24,7 +24,7 @@ import org.traccar.helper.PatternBuilder; import java.net.SocketAddress; import java.util.regex.Pattern; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.helper.Parser; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/SwiftechProtocol.java b/src/main/java/org/traccar/protocol/SwiftechProtocol.java index 5e2597b93..68cf40d84 100644 --- a/src/main/java/org/traccar/protocol/SwiftechProtocol.java +++ b/src/main/java/org/traccar/protocol/SwiftechProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class SwiftechProtocol extends BaseProtocol { - public SwiftechProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public SwiftechProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/SwiftechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SwiftechProtocolDecoder.java index 8d0b31c8f..b1cff8b64 100644 --- a/src/main/java/org/traccar/protocol/SwiftechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SwiftechProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/T55Protocol.java b/src/main/java/org/traccar/protocol/T55Protocol.java index f5ec19094..cedac275f 100644 --- a/src/main/java/org/traccar/protocol/T55Protocol.java +++ b/src/main/java/org/traccar/protocol/T55Protocol.java @@ -21,22 +21,26 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class T55Protocol extends BaseProtocol { - public T55Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public T55Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new T55ProtocolDecoder(T55Protocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new T55ProtocolDecoder(T55Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java b/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java index 230d29216..3be161fb8 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 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,9 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.config.Keys; +import org.traccar.helper.model.AttributeUtil; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; @@ -27,6 +28,7 @@ import org.traccar.helper.PatternBuilder; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; +import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.DatagramChannel; import java.util.Date; @@ -124,15 +126,41 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { .expression("([01])") // ignition .compile(); + private static final Pattern PATTERN_PUBX = new PatternBuilder() + .text("$PUBX,") + .number("(d+),") // index + .number("(dd)(dd)(dd).d+,") // time (hhmmss) + .number("(dd)(dd.d+),([NS]),") // latitude + .number("(ddd)(dd.d+),([EW]),") // longitude + .number("(-?d+.d+),") // altitude + .expression("(..),") // status + .number("(d+.d+),") // horizontal accuracy + .number("d+.d+,") // vertical accuracy + .number("(d+.d+),") // speed + .number("(d+.d+),") // course + .number("-?d+.d+,") // vertical velocity + .expression("[^,]*,") // corrections age + .number("(d+.d+),") // hdop + .number("(d+.d+),") // vdop + .number("d+.d+,") // tdop + .number("(d+),") // satellites + .number("(d+),") // device id + .number("d+") + .text("*") + .number("xx") // checksum + .compile(); + private Position position = null; private Position decodeGprmc( DeviceSession deviceSession, String sentence, SocketAddress remoteAddress, Channel channel) { - if (deviceSession != null && channel != null && !(channel instanceof DatagramChannel) - && Context.getIdentityManager().lookupAttributeBoolean( - deviceSession.getDeviceId(), getProtocolName() + ".ack", false, false, true)) { - channel.writeAndFlush(new NetworkMessage("OK1\r\n", remoteAddress)); + if (deviceSession != null && channel != null && !(channel instanceof DatagramChannel)) { + boolean ack = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_ACK.withPrefix(getProtocolName()), deviceSession.getDeviceId()); + if (ack) { + channel.writeAndFlush(new NetworkMessage("OK1\r\n", remoteAddress)); + } } Parser parser = new Parser(PATTERN_GPRMC, sentence); @@ -300,11 +328,44 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { return position; } + private Position decodePubx(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_PUBX, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + position.set(Position.KEY_INDEX, parser.nextInt()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS)); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setAltitude(parser.nextDouble()); + position.setValid(!parser.next().equals("NF")); + position.setAccuracy(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + + position.set(Position.KEY_HDOP, parser.nextDouble()); + position.set(Position.KEY_VDOP, parser.nextDouble()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession != null) { + position.setDeviceId(deviceSession.getDeviceId()); + return position; + } + + return null; + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { - String sentence = (String) msg; + String sentence = ((String) msg).trim(); DeviceSession deviceSession; @@ -320,6 +381,10 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { sentence = sentence.substring(index); } else { deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null && remoteAddress instanceof InetSocketAddress) { + String host = ((InetSocketAddress) remoteAddress).getHostString(); + deviceSession = getDeviceSession(channel, remoteAddress, host); + } } if (sentence.startsWith("$PGID")) { @@ -354,6 +419,8 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { return decodeGpiop(deviceSession, sentence); } else if (sentence.startsWith("QZE")) { return decodeQze(channel, remoteAddress, sentence); + } else if (sentence.startsWith("$PUBX")) { + return decodePubx(channel, remoteAddress, sentence); } return null; diff --git a/src/main/java/org/traccar/protocol/T57Protocol.java b/src/main/java/org/traccar/protocol/T57Protocol.java index f67f82318..4bafe8c6d 100644 --- a/src/main/java/org/traccar/protocol/T57Protocol.java +++ b/src/main/java/org/traccar/protocol/T57Protocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class T57Protocol extends BaseProtocol { - public T57Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public T57Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new T57FrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/T57ProtocolDecoder.java b/src/main/java/org/traccar/protocol/T57ProtocolDecoder.java index 2a3cca3e4..d9fd1c8cf 100644 --- a/src/main/java/org/traccar/protocol/T57ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/T57ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/T800xProtocol.java b/src/main/java/org/traccar/protocol/T800xProtocol.java index 8b91265cb..253c3cb73 100644 --- a/src/main/java/org/traccar/protocol/T800xProtocol.java +++ b/src/main/java/org/traccar/protocol/T800xProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,21 +19,32 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class T800xProtocol extends BaseProtocol { - public T800xProtocol() { + @Inject + public T800xProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 2, -5, 0)); pipeline.addLast(new T800xProtocolEncoder(T800xProtocol.this)); pipeline.addLast(new T800xProtocolDecoder(T800xProtocol.this)); } }); + addServer(new TrackerServer(config, getName(), true) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new T800xProtocolEncoder(T800xProtocol.this)); + pipeline.addLast(new T800xProtocolDecoder(T800xProtocol.this)); + } + }); } } diff --git a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java index 7f7873e50..6e09e6e3b 100644 --- a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; @@ -58,6 +58,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_DRIVER_BEHAVIOR_1 = 0x05; // 0x2626 public static final int MSG_DRIVER_BEHAVIOR_2 = 0x06; // 0x2626 public static final int MSG_BLE = 0x10; + public static final int MSG_NETWORK_2 = 0x11; public static final int MSG_GPS_2 = 0x13; public static final int MSG_ALARM_2 = 0x14; public static final int MSG_COMMAND = 0x81; @@ -77,7 +78,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { } } - private String decodeAlarm(int value) { + private String decodeAlarm1(int value) { switch (value) { case 1: return Position.ALARM_POWER_CUT; @@ -107,6 +108,28 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { } } + private String decodeAlarm2(int value) { + switch (value) { + case 1: + case 4: + return Position.ALARM_REMOVING; + case 2: + return Position.ALARM_TAMPERING; + case 3: + return Position.ALARM_SOS; + case 5: + return Position.ALARM_FALL_DOWN; + case 6: + return Position.ALARM_LOW_BATTERY; + case 14: + return Position.ALARM_GEOFENCE_ENTER; + case 15: + return Position.ALARM_GEOFENCE_EXIT; + default: + return null; + } + } + private Date readDate(ByteBuf buf) { return new DateBuilder() .setYear(BcdUtil.readInteger(buf, 2)) @@ -145,7 +168,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { return decodePosition(channel, deviceSession, buf, type, index, imei); - } else if (type == MSG_NETWORK && header == 0x2727) { + } else if (type == MSG_NETWORK && header == 0x2727 || type == MSG_NETWORK_2) { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); @@ -232,6 +255,11 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { return null; } + private double decodeBleTemp(ByteBuf buf) { + int value = buf.readUnsignedShort(); + return (BitUtil.check(value, 15) ? -BitUtil.to(value, 15) : BitUtil.to(value, 15)) * 0.01; + } + private Position decodeBle( Channel channel, DeviceSession deviceSession, ByteBuf buf, int type, int index, ByteBuf imei) { @@ -281,7 +309,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { position.set("tag" + i + "Id", ByteBufUtil.hexDump(buf.readSlice(6))); position.set("tag" + i + "Battery", buf.readUnsignedByte() * 0.01 + 2); buf.readUnsignedByte(); // battery level - position.set("tag" + i + "Temp", buf.readUnsignedShort() * 0.01); + position.set("tag" + i + "Temp", decodeBleTemp(buf)); position.set("tag" + i + "Humidity", buf.readUnsignedShort() * 0.01); position.set("tag" + i + "LightSensor", buf.readUnsignedShort()); position.set("tag" + i + "Rssi", buf.readUnsignedByte() - 128); @@ -290,7 +318,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { position.set("tag" + i + "Id", ByteBufUtil.hexDump(buf.readSlice(6))); position.set("tag" + i + "Battery", buf.readUnsignedByte() * 0.01 + 2); buf.readUnsignedByte(); // battery level - position.set("tag" + i + "Temp", buf.readUnsignedShort() * 0.01); + position.set("tag" + i + "Temp", decodeBleTemp(buf)); position.set("tag" + i + "Door", buf.readUnsignedByte() > 0); position.set("tag" + i + "Rssi", buf.readUnsignedByte() - 128); break; @@ -369,7 +397,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { } int alarm = buf.readUnsignedByte(); - position.set(Position.KEY_ALARM, decodeAlarm(alarm)); + position.set(Position.KEY_ALARM, header != 0x2727 ? decodeAlarm1(alarm) : decodeAlarm2(alarm)); if (header != 0x2727) { @@ -384,7 +412,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { if (BitUtil.check(status, 6)) { - position.setValid(!BitUtil.check(status, 7)); + position.setValid(true); position.setTime(readDate(buf)); position.setAltitude(buf.readFloatLE()); position.setLongitude(buf.readFloatLE()); diff --git a/src/main/java/org/traccar/protocol/TaipPrefixEncoder.java b/src/main/java/org/traccar/protocol/TaipPrefixEncoder.java index 02c111b01..48419af2a 100644 --- a/src/main/java/org/traccar/protocol/TaipPrefixEncoder.java +++ b/src/main/java/org/traccar/protocol/TaipPrefixEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,14 @@ */ package org.traccar.protocol; +import com.google.inject.Inject; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageEncoder; -import org.traccar.Context; import org.traccar.Protocol; +import org.traccar.config.Config; import org.traccar.config.Keys; import java.util.List; @@ -30,14 +31,20 @@ import java.util.List; public class TaipPrefixEncoder extends MessageToMessageEncoder<ByteBuf> { private final Protocol protocol; + private Config config; public TaipPrefixEncoder(Protocol protocol) { this.protocol = protocol; } + @Inject + public void setConfig(Config config) { + this.config = config; + } + @Override - protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception { - if (Context.getConfig().getBoolean(Keys.PROTOCOL_PREFIX.withPrefix(protocol.getName()))) { + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) { + if (config.getBoolean(Keys.PROTOCOL_PREFIX.withPrefix(protocol.getName()))) { out.add(Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(new byte[] {0x20, 0x20, 0x06, 0x00}), msg.retain())); } else { out.add(msg.retain()); diff --git a/src/main/java/org/traccar/protocol/TaipProtocol.java b/src/main/java/org/traccar/protocol/TaipProtocol.java index 0966cfd7c..943ec98c5 100644 --- a/src/main/java/org/traccar/protocol/TaipProtocol.java +++ b/src/main/java/org/traccar/protocol/TaipProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TaipProtocol extends BaseProtocol { - public TaipProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TaipProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '<')); pipeline.addLast(new TaipPrefixEncoder(TaipProtocol.this)); pipeline.addLast(new StringDecoder()); @@ -35,9 +39,9 @@ public class TaipProtocol extends BaseProtocol { pipeline.addLast(new TaipProtocolDecoder(TaipProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new TaipPrefixEncoder(TaipProtocol.this)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java b/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java index ec0ce1931..e5e84b7c4 100644 --- a/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/TechTltProtocol.java b/src/main/java/org/traccar/protocol/TechTltProtocol.java index 0cffb452d..191dd9ccc 100644 --- a/src/main/java/org/traccar/protocol/TechTltProtocol.java +++ b/src/main/java/org/traccar/protocol/TechTltProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TechTltProtocol extends BaseProtocol { - public TechTltProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public TechTltProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new TechTltProtocolDecoder(TechTltProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/TechTltProtocolDecoder.java b/src/main/java/org/traccar/protocol/TechTltProtocolDecoder.java index 17f5c80fa..94efacc63 100644 --- a/src/main/java/org/traccar/protocol/TechTltProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TechTltProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; @@ -110,7 +110,7 @@ public class TechTltProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_SATELLITES, parser.nextInt()); - position.setNetwork(new Network(CellTower.fromLacCid(parser.nextInt(), parser.nextInt()))); + position.setNetwork(new Network(CellTower.fromLacCid(getConfig(), parser.nextInt(), parser.nextInt()))); return position; } diff --git a/src/main/java/org/traccar/protocol/TechtoCruzFrameDecoder.java b/src/main/java/org/traccar/protocol/TechtoCruzFrameDecoder.java new file mode 100644 index 000000000..8b9152e8b --- /dev/null +++ b/src/main/java/org/traccar/protocol/TechtoCruzFrameDecoder.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +import java.nio.charset.StandardCharsets; + +public class TechtoCruzFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + int lengthStart = buf.readerIndex() + 3; + int lengthEnd = buf.indexOf(lengthStart, buf.writerIndex(), (byte) ','); + if (lengthEnd > 0) { + int length = lengthStart + + Integer.parseInt(buf.toString(lengthStart, lengthEnd - lengthStart, StandardCharsets.US_ASCII)); + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TechtoCruzProtocol.java b/src/main/java/org/traccar/protocol/TechtoCruzProtocol.java index 890a8abb1..265a3eb64 100644 --- a/src/main/java/org/traccar/protocol/TechtoCruzProtocol.java +++ b/src/main/java/org/traccar/protocol/TechtoCruzProtocol.java @@ -15,20 +15,23 @@ */ package org.traccar.protocol; -import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TechtoCruzProtocol extends BaseProtocol { - public TechtoCruzProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TechtoCruzProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { - pipeline.addLast(new LineBasedFrameDecoder(1024)); + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new TechtoCruzFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new TechtoCruzProtocolDecoder(TechtoCruzProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/TechtoCruzProtocolDecoder.java b/src/main/java/org/traccar/protocol/TechtoCruzProtocolDecoder.java index 6b9f0edb6..09efcb7d4 100644 --- a/src/main/java/org/traccar/protocol/TechtoCruzProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TechtoCruzProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/TekProtocol.java b/src/main/java/org/traccar/protocol/TekProtocol.java index c1d78e6f5..54e860d79 100644 --- a/src/main/java/org/traccar/protocol/TekProtocol.java +++ b/src/main/java/org/traccar/protocol/TekProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TekProtocol extends BaseProtocol { - public TekProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TekProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new TekFrameDecoder()); pipeline.addLast(new TekProtocolDecoder(TekProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/TekProtocolDecoder.java b/src/main/java/org/traccar/protocol/TekProtocolDecoder.java index 33ff51d2d..819c7e819 100644 --- a/src/main/java/org/traccar/protocol/TekProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TekProtocolDecoder.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/TelemaxProtocol.java b/src/main/java/org/traccar/protocol/TelemaxProtocol.java index 838da9df1..9e9cbb50e 100644 --- a/src/main/java/org/traccar/protocol/TelemaxProtocol.java +++ b/src/main/java/org/traccar/protocol/TelemaxProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TelemaxProtocol extends BaseProtocol { - public TelemaxProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TelemaxProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/TelemaxProtocolDecoder.java b/src/main/java/org/traccar/protocol/TelemaxProtocolDecoder.java index 9369ab101..f6f6f5379 100644 --- a/src/main/java/org/traccar/protocol/TelemaxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TelemaxProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/protocol/TelicProtocol.java b/src/main/java/org/traccar/protocol/TelicProtocol.java index 991befa19..9ef7864ca 100644 --- a/src/main/java/org/traccar/protocol/TelicProtocol.java +++ b/src/main/java/org/traccar/protocol/TelicProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TelicProtocol extends BaseProtocol { - public TelicProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TelicProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new TelicFrameDecoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/TelicProtocolDecoder.java b/src/main/java/org/traccar/protocol/TelicProtocolDecoder.java index a4f9e2989..9681dc565 100644 --- a/src/main/java/org/traccar/protocol/TelicProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TelicProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java index c30fee6e3..3a0962584 100644 --- a/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java +++ b/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ public class TeltonikaFrameDecoder extends BaseFrameDecoder { ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { while (buf.isReadable() && buf.getByte(buf.readerIndex()) == (byte) 0xff) { - buf.skipBytes(1); + return buf.readRetainedSlice(1); } if (buf.readableBytes() < MESSAGE_MINIMUM_LENGTH) { diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocol.java b/src/main/java/org/traccar/protocol/TeltonikaProtocol.java index 5817b86be..38283cb64 100644 --- a/src/main/java/org/traccar/protocol/TeltonikaProtocol.java +++ b/src/main/java/org/traccar/protocol/TeltonikaProtocol.java @@ -18,24 +18,28 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class TeltonikaProtocol extends BaseProtocol { - public TeltonikaProtocol() { + @Inject + public TeltonikaProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new TeltonikaFrameDecoder()); pipeline.addLast(new TeltonikaProtocolEncoder(TeltonikaProtocol.this)); pipeline.addLast(new TeltonikaProtocolDecoder(TeltonikaProtocol.this, false)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new TeltonikaProtocolEncoder(TeltonikaProtocol.this)); pipeline.addLast(new TeltonikaProtocolDecoder(TeltonikaProtocol.this, true)); } diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java index 89ae48b3a..929eca8aa 100644 --- a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,8 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.model.Device; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.config.Keys; @@ -39,11 +39,15 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { private static final int IMAGE_PACKET_MAX = 2048; + private static final Map<Integer, Map<Set<String>, BiConsumer<Position, ByteBuf>>> PARAMETERS = new HashMap<>(); + private final boolean connectionless; private boolean extended; private final Map<Long, ByteBuf> photos = new HashMap<>(); @@ -55,7 +59,11 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { public TeltonikaProtocolDecoder(Protocol protocol, boolean connectionless) { super(protocol); this.connectionless = connectionless; - this.extended = Context.getConfig().getBoolean(Keys.PROTOCOL_EXTENDED.withPrefix(getProtocolName())); + } + + @Override + protected void init() { + this.extended = getConfig().getBoolean(Keys.PROTOCOL_EXTENDED.withPrefix(getProtocolName())); } private void parseIdentification(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { @@ -116,7 +124,8 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { return printable; } - private void decodeSerial(Channel channel, SocketAddress remoteAddress, Position position, ByteBuf buf) { + private void decodeSerial( + Channel channel, SocketAddress remoteAddress, DeviceSession deviceSession, Position position, ByteBuf buf) { getLastLocation(position, null); @@ -145,10 +154,9 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { channel, remoteAddress, photoId, photo.writerIndex(), Math.min(IMAGE_PACKET_MAX, photo.writableBytes())); } else { - String uniqueId = Context.getIdentityManager().getById(position.getDeviceId()).getUniqueId(); photos.remove(photoId); try { - position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(uniqueId, photo, "jpg")); + position.set(Position.KEY_IMAGE, writeMediaFile(deviceSession.getUniqueId(), photo, "jpg")); } finally { photo.release(); } @@ -181,191 +189,193 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { } } - private long readValue(ByteBuf buf, int length, boolean signed) { + private long readValue(ByteBuf buf, int length) { switch (length) { case 1: - return signed ? buf.readByte() : buf.readUnsignedByte(); + return buf.readUnsignedByte(); case 2: - return signed ? buf.readShort() : buf.readUnsignedShort(); + return buf.readUnsignedShort(); case 4: - return signed ? buf.readInt() : buf.readUnsignedInt(); + return buf.readUnsignedInt(); default: return buf.readLong(); } } - private void decodeOtherParameter(Position position, int id, ByteBuf buf, int length) { - switch (id) { - case 1: - case 2: - case 3: - case 4: - position.set("di" + id, readValue(buf, length, false)); - break; - case 9: - position.set(Position.PREFIX_ADC + 1, readValue(buf, length, false)); - break; - case 10: - position.set(Position.PREFIX_ADC + 2, readValue(buf, length, false)); - break; - case 16: - position.set(Position.KEY_ODOMETER, readValue(buf, length, false)); - break; - case 17: - position.set("axisX", readValue(buf, length, true)); - break; - case 18: - position.set("axisY", readValue(buf, length, true)); - break; - case 19: - position.set("axisZ", readValue(buf, length, true)); - break; - case 21: - position.set(Position.KEY_RSSI, readValue(buf, length, false)); - break; - case 25: - case 26: - case 27: - case 28: - position.set(Position.PREFIX_TEMP + (id - 24 + 4), readValue(buf, length, true) * 0.1); - break; - case 66: - position.set(Position.KEY_POWER, readValue(buf, length, false) * 0.001); - break; - case 67: - position.set(Position.KEY_BATTERY, readValue(buf, length, false) * 0.001); - break; - case 72: - case 73: - case 74: - position.set(Position.PREFIX_TEMP + (id - 71), readValue(buf, length, true) * 0.1); - break; - case 78: - long driverUniqueId = readValue(buf, length, false); - if (driverUniqueId != 0) { - position.set(Position.KEY_DRIVER_UNIQUE_ID, String.format("%016X", driverUniqueId)); - } - break; - case 80: - position.set("workMode", readValue(buf, length, false)); - break; - case 90: - position.set(Position.KEY_DOOR, readValue(buf, length, false)); - break; - case 115: - position.set(Position.KEY_COOLANT_TEMP, readValue(buf, length, true) * 0.1); - break; - case 179: - position.set(Position.PREFIX_OUT + 1, readValue(buf, length, false) == 1); - break; - case 180: - position.set(Position.PREFIX_OUT + 2, readValue(buf, length, false) == 1); - break; - case 181: - position.set(Position.KEY_PDOP, readValue(buf, length, false) * 0.1); - break; - case 182: - position.set(Position.KEY_HDOP, readValue(buf, length, false) * 0.1); - break; - case 199: - position.set(Position.KEY_ODOMETER_TRIP, readValue(buf, length, false)); - break; - case 236: - if (readValue(buf, length, false) == 1) { - position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); - } - break; - case 239: - position.set(Position.KEY_IGNITION, readValue(buf, length, false) == 1); - break; - case 240: - position.set(Position.KEY_MOTION, readValue(buf, length, false) == 1); - break; - case 241: - position.set(Position.KEY_OPERATOR, readValue(buf, length, false)); - break; - case 253: - switch ((int) readValue(buf, length, false)) { - case 1: - position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); - break; - case 2: - position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); - break; - case 3: - position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); - break; - default: - break; - } - break; - default: - position.set(Position.PREFIX_IO + id, readValue(buf, length, false)); - break; - } + private static void register(int id, Set<String> models, BiConsumer<Position, ByteBuf> handler) { + PARAMETERS.computeIfAbsent(id, key -> new HashMap<>()).put(models, handler); + } + + static { + var fmbXXX = Set.of( + "FMB001", "FMB010", "FMB002", "FMB020", "FMB003", "FMB110", "FMB120", "FMB122", "FMB125", "FMB130", + "FMB140", "FMU125", "FMB900", "FMB920", "FMB962", "FMB964", "FM3001", "FMB202", "FMB204", "FMB206", + "FMT100", "MTB100", "FMP100", "MSP500"); + + register(1, null, (p, b) -> p.set(Position.PREFIX_IN + 1, b.readUnsignedByte() > 0)); + register(2, null, (p, b) -> p.set(Position.PREFIX_IN + 2, b.readUnsignedByte() > 0)); + register(3, null, (p, b) -> p.set(Position.PREFIX_IN + 3, b.readUnsignedByte() > 0)); + register(4, null, (p, b) -> p.set(Position.PREFIX_IN + 4, b.readUnsignedByte() > 0)); + register(9, fmbXXX, (p, b) -> p.set(Position.PREFIX_ADC + 1, b.readUnsignedShort() * 0.001)); + register(10, fmbXXX, (p, b) -> p.set(Position.PREFIX_ADC + 2, b.readUnsignedShort() * 0.001)); + register(11, fmbXXX, (p, b) -> p.set(Position.KEY_ICCID, String.valueOf(b.readLong()))); + register(12, fmbXXX, (p, b) -> p.set(Position.KEY_FUEL_USED, b.readUnsignedInt() * 0.001)); + register(13, fmbXXX, (p, b) -> p.set(Position.KEY_FUEL_CONSUMPTION, b.readUnsignedShort() * 0.01)); + register(16, null, (p, b) -> p.set(Position.KEY_ODOMETER, b.readUnsignedInt())); + register(17, null, (p, b) -> p.set("axisX", b.readShort())); + register(18, null, (p, b) -> p.set("axisY", b.readShort())); + register(19, null, (p, b) -> p.set("axisZ", b.readShort())); + register(21, null, (p, b) -> p.set(Position.KEY_RSSI, b.readUnsignedByte())); + register(24, fmbXXX, (p, b) -> p.setSpeed(UnitsConverter.knotsFromKph(b.readUnsignedShort()))); + register(25, null, (p, b) -> p.set("bleTemp1", b.readShort() * 0.01)); + register(26, null, (p, b) -> p.set("bleTemp2", b.readShort() * 0.01)); + register(27, null, (p, b) -> p.set("bleTemp3", b.readShort() * 0.01)); + register(28, null, (p, b) -> p.set("bleTemp4", b.readShort() * 0.01)); + register(66, null, (p, b) -> p.set(Position.KEY_POWER, b.readUnsignedShort() * 0.001)); + register(67, null, (p, b) -> p.set(Position.KEY_BATTERY, b.readUnsignedShort() * 0.001)); + register(68, fmbXXX, (p, b) -> p.set("batteryCurrent", b.readUnsignedShort() * 0.001)); + register(72, fmbXXX, (p, b) -> p.set(Position.PREFIX_TEMP + 1, b.readInt() * 0.1)); + register(73, fmbXXX, (p, b) -> p.set(Position.PREFIX_TEMP + 2, b.readInt() * 0.1)); + register(74, fmbXXX, (p, b) -> p.set(Position.PREFIX_TEMP + 3, b.readInt() * 0.1)); + register(75, fmbXXX, (p, b) -> p.set(Position.PREFIX_TEMP + 4, b.readInt() * 0.1)); + register(78, null, (p, b) -> { + long driverUniqueId = b.readLong(); + if (driverUniqueId > 0) { + p.set(Position.KEY_DRIVER_UNIQUE_ID, String.format("%016X", driverUniqueId)); + } + }); + register(80, fmbXXX, (p, b) -> p.set("dataMode", b.readUnsignedByte())); + register(90, null, (p, b) -> p.set(Position.KEY_DOOR, b.readUnsignedShort())); + register(115, fmbXXX, (p, b) -> p.set(Position.KEY_COOLANT_TEMP, b.readShort() * 0.1)); + register(179, null, (p, b) -> p.set(Position.PREFIX_OUT + 1, b.readUnsignedByte() > 0)); + register(180, null, (p, b) -> p.set(Position.PREFIX_OUT + 2, b.readUnsignedByte() > 0)); + register(181, null, (p, b) -> p.set(Position.KEY_PDOP, b.readUnsignedShort() * 0.1)); + register(182, null, (p, b) -> p.set(Position.KEY_HDOP, b.readUnsignedShort() * 0.1)); + register(199, null, (p, b) -> p.set(Position.KEY_ODOMETER_TRIP, b.readUnsignedInt())); + register(200, fmbXXX, (p, b) -> p.set("sleepMode", b.readUnsignedByte())); + register(205, fmbXXX, (p, b) -> p.set("cid", b.readUnsignedShort())); + register(206, fmbXXX, (p, b) -> p.set("lac", b.readUnsignedShort())); + register(236, null, (p, b) -> { + p.set(Position.KEY_ALARM, b.readUnsignedByte() > 0 ? Position.ALARM_GENERAL : null); + }); + register(239, null, (p, b) -> p.set(Position.KEY_IGNITION, b.readUnsignedByte() > 0)); + register(240, null, (p, b) -> p.set(Position.KEY_MOTION, b.readUnsignedByte() > 0)); + register(241, null, (p, b) -> p.set(Position.KEY_OPERATOR, b.readUnsignedInt())); + register(253, null, (p, b) -> { + switch (b.readUnsignedByte()) { + case 1: + p.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 2: + p.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 3: + p.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + break; + default: + break; + } + }); } private void decodeGh3000Parameter(Position position, int id, ByteBuf buf, int length) { switch (id) { case 1: - position.set(Position.KEY_BATTERY_LEVEL, readValue(buf, length, false)); + position.set(Position.KEY_BATTERY_LEVEL, readValue(buf, length)); break; case 2: - position.set("usbConnected", readValue(buf, length, false) == 1); + position.set("usbConnected", readValue(buf, length) == 1); break; case 5: - position.set("uptime", readValue(buf, length, false)); + position.set("uptime", readValue(buf, length)); break; case 20: - position.set(Position.KEY_HDOP, readValue(buf, length, false) * 0.1); + position.set(Position.KEY_HDOP, readValue(buf, length) * 0.1); break; case 21: - position.set(Position.KEY_VDOP, readValue(buf, length, false) * 0.1); + position.set(Position.KEY_VDOP, readValue(buf, length) * 0.1); break; case 22: - position.set(Position.KEY_PDOP, readValue(buf, length, false) * 0.1); + position.set(Position.KEY_PDOP, readValue(buf, length) * 0.1); break; case 67: - position.set(Position.KEY_BATTERY, readValue(buf, length, false) * 0.001); + position.set(Position.KEY_BATTERY, readValue(buf, length) * 0.001); break; case 221: - position.set("button", readValue(buf, length, false)); + position.set("button", readValue(buf, length)); break; case 222: - if (readValue(buf, length, false) == 1) { + if (readValue(buf, length) == 1) { position.set(Position.KEY_ALARM, Position.ALARM_SOS); } break; case 240: - position.set(Position.KEY_MOTION, readValue(buf, length, false) == 1); + position.set(Position.KEY_MOTION, readValue(buf, length) == 1); break; case 244: - position.set(Position.KEY_ROAMING, readValue(buf, length, false) == 1); + position.set(Position.KEY_ROAMING, readValue(buf, length) == 1); break; default: - position.set(Position.PREFIX_IO + id, readValue(buf, length, false)); + position.set(Position.PREFIX_IO + id, readValue(buf, length)); break; } } - private void decodeParameter(Position position, int id, ByteBuf buf, int length, int codec) { + private void decodeParameter(Position position, int id, ByteBuf buf, int length, int codec, String model) { if (codec == CODEC_GH3000) { decodeGh3000Parameter(position, id, buf, length); } else { - decodeOtherParameter(position, id, buf, length); + int index = buf.readerIndex(); + boolean decoded = false; + for (var entry : PARAMETERS.getOrDefault(id, new HashMap<>()).entrySet()) { + if (entry.getKey() == null || model != null && entry.getKey().contains(model)) { + entry.getValue().accept(position, buf); + decoded = true; + break; + } + } + if (decoded) { + buf.readerIndex(index + length); + } else { + position.set(Position.PREFIX_IO + id, readValue(buf, length)); + } + } + } + + private void decodeCell( + Position position, Network network, String mncKey, String lacKey, String cidKey, String rssiKey) { + if (position.hasAttribute(mncKey) && position.hasAttribute(lacKey) && position.hasAttribute(cidKey)) { + CellTower cellTower = CellTower.from( + getConfig().getInteger(Keys.GEOLOCATION_MCC), + ((Number) position.getAttributes().remove(mncKey)).intValue(), + ((Number) position.getAttributes().remove(lacKey)).intValue(), + ((Number) position.getAttributes().remove(cidKey)).longValue()); + cellTower.setSignalStrength(((Number) position.getAttributes().remove(rssiKey)).intValue()); + network.addCellTower(cellTower); } } - private void decodeNetwork(Position position) { - long cid = position.getLong(Position.PREFIX_IO + 205); - int lac = position.getInteger(Position.PREFIX_IO + 206); - if (cid != 0 && lac != 0) { - CellTower cellTower = CellTower.fromLacCid(lac, cid); - long operator = position.getInteger(Position.KEY_OPERATOR); - if (operator != 0) { - cellTower.setOperator(operator); + private void decodeNetwork(Position position, String model) { + if ("TAT100".equals(model)) { + Network network = new Network(); + decodeCell(position, network, "io1200", "io287", "io288", "io289"); + decodeCell(position, network, "io1201", "io290", "io291", "io292"); + decodeCell(position, network, "io1202", "io293", "io294", "io295"); + decodeCell(position, network, "io1203", "io296", "io297", "io298"); + if (network.getCellTowers() != null) { + position.setNetwork(network); + } + } else { + Integer cid = (Integer) position.getAttributes().remove("cid"); + Integer lac = (Integer) position.getAttributes().remove("lac"); + if (cid != null && lac != null) { + CellTower cellTower = CellTower.fromLacCid(getConfig(), lac, cid); + long operator = position.getInteger(Position.KEY_OPERATOR); + if (operator >= 1000) { + cellTower.setOperator(operator); + } + position.setNetwork(new Network(cellTower)); } - position.setNetwork(new Network(cellTower)); } } @@ -384,7 +394,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { } } - private void decodeLocation(Position position, ByteBuf buf, int codec) { + private void decodeLocation(Position position, ByteBuf buf, int codec, String model) { int globalMask = 0x0f; @@ -422,7 +432,8 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { } if (BitUtil.check(locationMask, 5)) { - CellTower cellTower = CellTower.fromLacCid(buf.readUnsignedShort(), buf.readUnsignedShort()); + CellTower cellTower = CellTower.fromLacCid( + getConfig(), buf.readUnsignedShort(), buf.readUnsignedShort()); if (BitUtil.check(locationMask, 6)) { cellTower.setSignalStrength((int) buf.readUnsignedByte()); @@ -480,7 +491,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { if (BitUtil.check(globalMask, 1)) { int cnt = readExtByte(buf, codec, CODEC_8_EXT); for (int j = 0; j < cnt; j++) { - decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 1, codec); + decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 1, codec, model); } } @@ -488,7 +499,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { if (BitUtil.check(globalMask, 2)) { int cnt = readExtByte(buf, codec, CODEC_8_EXT); for (int j = 0; j < cnt; j++) { - decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 2, codec); + decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 2, codec, model); } } @@ -496,7 +507,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { if (BitUtil.check(globalMask, 3)) { int cnt = readExtByte(buf, codec, CODEC_8_EXT); for (int j = 0; j < cnt; j++) { - decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 4, codec); + decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 4, codec, model); } } @@ -504,7 +515,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { if (codec == CODEC_8 || codec == CODEC_8_EXT || codec == CODEC_16) { int cnt = readExtByte(buf, codec, CODEC_8_EXT); for (int j = 0; j < cnt; j++) { - decodeOtherParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 8); + decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 8, codec, model); } } @@ -558,7 +569,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { } } - decodeNetwork(position); + decodeNetwork(position, model); } @@ -574,10 +585,10 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { int count = buf.readUnsignedByte(); DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); - if (deviceSession == null) { return null; } + String model = getCacheManager().getObject(Device.class, deviceSession.getDeviceId()).getModel(); for (int i = 0; i < count; i++) { Position position = new Position(getProtocolName()); @@ -597,9 +608,9 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { ByteBufUtil.hexDump(buf.readSlice(length))); } } else if (codec == CODEC_12) { - decodeSerial(channel, remoteAddress, position, buf); + decodeSerial(channel, remoteAddress, deviceSession, position, buf); } else { - decodeLocation(position, buf, codec); + decodeLocation(position, buf, codec, model); } if (!position.getOutdated() || !position.getAttributes().isEmpty()) { @@ -636,9 +647,11 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { } } - private Object decodeTcp(Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws Exception { + private Object decodeTcp(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { - if (buf.getUnsignedShort(0) > 0) { + if (buf.readableBytes() == 1 && buf.readUnsignedByte() == 0xff) { + return null; + } else if (buf.getUnsignedShort(0) > 0) { parseIdentification(channel, remoteAddress, buf); } else { buf.skipBytes(4); @@ -648,7 +661,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { return null; } - private Object decodeUdp(Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws Exception { + private Object decodeUdp(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { buf.readUnsignedShort(); // length buf.readUnsignedShort(); // packet id diff --git a/src/main/java/org/traccar/protocol/TeraTrackProtocol.java b/src/main/java/org/traccar/protocol/TeraTrackProtocol.java new file mode 100644 index 000000000..73219cc5e --- /dev/null +++ b/src/main/java/org/traccar/protocol/TeraTrackProtocol.java @@ -0,0 +1,42 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.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; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class TeraTrackProtocol extends BaseProtocol { + + @Inject + public TeraTrackProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new JsonFrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new TeraTrackProtocolDecoder(TeraTrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TeraTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeraTrackProtocolDecoder.java new file mode 100644 index 000000000..313210f63 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TeraTrackProtocolDecoder.java @@ -0,0 +1,79 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.session.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import javax.json.Json; +import javax.json.JsonObject; +import java.io.StringReader; +import java.net.SocketAddress; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +public class TeraTrackProtocolDecoder extends BaseProtocolDecoder { + + public TeraTrackProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + JsonObject json = Json.createReader(new StringReader((String) msg)).readObject(); + + String deviceId = json.getString("MDeviceID"); + String imei = json.getString("IMEI"); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, deviceId, imei); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + position.setTime(dateFormat.parse(json.getString("DateTime"))); + + position.setValid(true); + position.setLatitude(Double.parseDouble(json.getString("Latitude"))); + position.setLongitude(Double.parseDouble(json.getString("Longitude"))); + position.setSpeed(UnitsConverter.knotsFromKph(Integer.parseInt(json.getString("Speed")))); + + position.set(Position.KEY_ODOMETER, Integer.parseInt(json.getString("Mileage"))); + position.set(Position.KEY_BLOCKED, json.getString("LockOpen").equals("0")); + position.set(Position.KEY_DRIVER_UNIQUE_ID, json.getString("CardNo")); + position.set(Position.KEY_ALARM, json.getString("LowPower").equals("1") ? Position.ALARM_LOW_POWER : null); + position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(json.getString("Power"))); + position.set(Position.KEY_RSSI, Integer.parseInt(json.getString("GSM"))); + + if (channel != null && json.getString("MessageAck").equals("1")) { + channel.writeAndFlush(new NetworkMessage("{01}", remoteAddress)); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/ThinkPowerProtocol.java b/src/main/java/org/traccar/protocol/ThinkPowerProtocol.java index ece1b0544..38bf078aa 100644 --- a/src/main/java/org/traccar/protocol/ThinkPowerProtocol.java +++ b/src/main/java/org/traccar/protocol/ThinkPowerProtocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class ThinkPowerProtocol extends BaseProtocol { - public ThinkPowerProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public ThinkPowerProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 2, 2, 0)); pipeline.addLast(new ThinkPowerProtocolDecoder(ThinkPowerProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/ThinkPowerProtocolDecoder.java b/src/main/java/org/traccar/protocol/ThinkPowerProtocolDecoder.java index b3f943078..e7ab23e5b 100644 --- a/src/main/java/org/traccar/protocol/ThinkPowerProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ThinkPowerProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,8 @@ 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.helper.BufferUtil; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Checksum; @@ -64,8 +65,8 @@ public class ThinkPowerProtocolDecoder extends BaseProtocolDecoder { switch (type) { case 0x01: position.setValid(true); - position.setLatitude(buf.readInt() * 0.0000001); - position.setLongitude(buf.readInt() * 0.0000001); + position.setLatitude(BufferUtil.readSignedMagnitudeInt(buf) * 0.0000001); + position.setLongitude(BufferUtil.readSignedMagnitudeInt(buf) * 0.0000001); position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() * 0.1)); position.setCourse(buf.readUnsignedShort() * 0.01); break; diff --git a/src/main/java/org/traccar/protocol/ThinkRaceProtocol.java b/src/main/java/org/traccar/protocol/ThinkRaceProtocol.java index ca1237cef..782b0a352 100644 --- a/src/main/java/org/traccar/protocol/ThinkRaceProtocol.java +++ b/src/main/java/org/traccar/protocol/ThinkRaceProtocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class ThinkRaceProtocol extends BaseProtocol { - public ThinkRaceProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public ThinkRaceProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2 + 12 + 1 + 1, 2, 2, 0)); pipeline.addLast(new ThinkRaceProtocolDecoder(ThinkRaceProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/ThinkRaceProtocolDecoder.java b/src/main/java/org/traccar/protocol/ThinkRaceProtocolDecoder.java index 0928b25e0..796b726ea 100644 --- a/src/main/java/org/traccar/protocol/ThinkRaceProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ThinkRaceProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -104,7 +104,7 @@ public class ThinkRaceProtocolDecoder extends BaseProtocolDecoder { position.setCourse(buf.readUnsignedByte()); position.setNetwork(new Network( - CellTower.fromLacCid(buf.readUnsignedShort(), buf.readUnsignedShort()))); + CellTower.fromLacCid(getConfig(), buf.readUnsignedShort(), buf.readUnsignedShort()))); return position; diff --git a/src/main/java/org/traccar/protocol/ThurayaProtocol.java b/src/main/java/org/traccar/protocol/ThurayaProtocol.java new file mode 100644 index 000000000..f709a1183 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ThurayaProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; + +public class ThurayaProtocol extends BaseProtocol { + + @Inject + public ThurayaProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 2, -4, 0)); + pipeline.addLast(new ThurayaProtocolDecoder(ThurayaProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ThurayaProtocolDecoder.java b/src/main/java/org/traccar/protocol/ThurayaProtocolDecoder.java new file mode 100644 index 000000000..a287ece34 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ThurayaProtocolDecoder.java @@ -0,0 +1,195 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.model.Position; +import org.traccar.session.DeviceSession; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; + +public class ThurayaProtocolDecoder extends BaseProtocolDecoder { + + public ThurayaProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_EVENT = 0x5101; + public static final int MSG_PERIODIC_REPORT = 0x7101; + public static final int MSG_SETTING_RESPONSE = 0x8115; + public static final int MSG_ACK = 0x9901; + + private static int checksum(ByteBuffer buf) { + int crc = 0; + while (buf.hasRemaining()) { + crc += buf.get(); + } + crc = ~crc; + crc += 1; + return crc; + } + + private void sendResponse(Channel channel, SocketAddress remoteAddress, long id, int type) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeCharSequence("#T", StandardCharsets.US_ASCII); + response.writeShort(15); // length + response.writeShort(MSG_ACK); + response.writeInt((int) id); + response.writeShort(type); + response.writeShort(1); // server ok + response.writeShort(checksum(response.nioBuffer())); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + private void decodeLocation(ByteBuf buf, Position position) { + + position.setValid(true); + + DateBuilder dateBuilder = new DateBuilder(); + + int date = buf.readInt(); + dateBuilder.setDay(date % 100); + date /= 100; + dateBuilder.setMonth(date % 100); + date /= 100; + dateBuilder.setYear(date); + + int time = buf.readInt(); + dateBuilder.setSecond(time % 100); + time /= 100; + dateBuilder.setMinute(time % 100); + time /= 100; + dateBuilder.setHour(time); + + position.setTime(dateBuilder.getDate()); + + position.setLongitude(buf.readInt() / 1000000.0); + position.setLatitude(buf.readInt() / 1000000.0); + + int data = buf.readUnsignedShort(); + + int ignition = BitUtil.from(data, 12); + if (ignition == 1) { + position.set(Position.KEY_IGNITION, true); + } else if (ignition == 2) { + position.set(Position.KEY_IGNITION, false); + } + + position.setCourse(BitUtil.to(data, 12)); + position.setSpeed(buf.readShort()); + + position.set(Position.KEY_RPM, buf.readShort()); + + position.set("data", readString(buf)); + } + + private String decodeAlarm(int event) { + switch (event) { + case 10: + return Position.ALARM_VIBRATION; + case 11: + return Position.ALARM_OVERSPEED; + case 12: + return Position.ALARM_POWER_CUT; + case 13: + return Position.ALARM_LOW_BATTERY; + case 18: + return Position.ALARM_GPS_ANTENNA_CUT; + case 20: + return Position.ALARM_ACCELERATION; + case 21: + return Position.ALARM_BRAKING; + default: + return null; + } + } + + private String readString(ByteBuf buf) { + int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0); + CharSequence value = buf.readCharSequence(endIndex - buf.readerIndex(), StandardCharsets.US_ASCII); + buf.readUnsignedByte(); // delimiter + return value.toString(); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // service + buf.readUnsignedShort(); // length + int type = buf.readUnsignedShort(); + long id = buf.readUnsignedInt(); + + sendResponse(channel, remoteAddress, id, type); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(id)); + if (deviceSession == null) { + return null; + } + + if (type == MSG_EVENT) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + decodeLocation(buf, position); + + int event = buf.readUnsignedByte(); + position.set(Position.KEY_ALARM, decodeAlarm(event)); + position.set(Position.KEY_EVENT, event); + position.set("eventData", readString(buf)); + + return position; + + } else if (type == MSG_PERIODIC_REPORT) { + + List<Position> positions = new LinkedList<>(); + + int count = buf.readUnsignedByte(); + for (int i = 0; i < count; i++) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + decodeLocation(buf, position); + + positions.add(position); + + } + + return positions; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Tk102Protocol.java b/src/main/java/org/traccar/protocol/Tk102Protocol.java index 9f2463cd6..150e83ab3 100644 --- a/src/main/java/org/traccar/protocol/Tk102Protocol.java +++ b/src/main/java/org/traccar/protocol/Tk102Protocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Tk102Protocol extends BaseProtocol { - public Tk102Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Tk102Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 1 + 1 + 10, 1, 1, 0)); pipeline.addLast(new Tk102ProtocolDecoder(Tk102Protocol.this)); } diff --git a/src/main/java/org/traccar/protocol/Tk102ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tk102ProtocolDecoder.java index da0c6928b..af29fbc21 100644 --- a/src/main/java/org/traccar/protocol/Tk102ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Tk102ProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/Tk103Protocol.java b/src/main/java/org/traccar/protocol/Tk103Protocol.java index ff0bedfb7..cf09886f5 100644 --- a/src/main/java/org/traccar/protocol/Tk103Protocol.java +++ b/src/main/java/org/traccar/protocol/Tk103Protocol.java @@ -21,11 +21,15 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class Tk103Protocol extends BaseProtocol { - public Tk103Protocol() { + @Inject + public Tk103Protocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_GET_DEVICE_STATUS, @@ -45,9 +49,9 @@ public class Tk103Protocol extends BaseProtocol { Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME, Command.TYPE_OUTPUT_CONTROL); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new Tk103FrameDecoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); @@ -55,9 +59,9 @@ public class Tk103Protocol extends BaseProtocol { pipeline.addLast(new Tk103ProtocolDecoder(Tk103Protocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new Tk103ProtocolEncoder(Tk103Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java index ff33cb103..b343c3b33 100644 --- a/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.config.Keys; @@ -36,11 +35,15 @@ import java.util.regex.Pattern; public class Tk103ProtocolDecoder extends BaseProtocolDecoder { - private final boolean decodeLow; + private boolean decodeLow; public Tk103ProtocolDecoder(Protocol protocol) { super(protocol); - decodeLow = Context.getConfig().getBoolean(Keys.PROTOCOL_DECODE_LOW.withPrefix(getProtocolName())); + } + + @Override + protected void init() { + decodeLow = getConfig().getBoolean(Keys.PROTOCOL_DECODE_LOW.withPrefix(getProtocolName())); } private static final Pattern PATTERN = new PatternBuilder() @@ -96,6 +99,16 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder { .any() .compile(); + private static final Pattern PATTERN_CELL = new PatternBuilder() + .text("(") + .number("(d{12})") // device id + .expression(".{4}") // type + .number("(?:d{15})?,") // imei + .expression("(.+),") // cell + .number("(d{8})") // odometer + .text(")") + .compile(); + private static final Pattern PATTERN_NETWORK = new PatternBuilder() .text("(").optional() .number("(d{12})") // device id @@ -294,6 +307,39 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder { return position; } + private Position decodeCell(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_CELL, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + Network network = new Network(); + + String[] cells = parser.next().split("\n"); + for (String cell : cells) { + String[] values = cell.substring(1, cell.length() - 1).split(","); + network.addCellTower(CellTower.from( + Integer.parseInt(values[0]), Integer.parseInt(values[1]), + Integer.parseInt(values[2]), Integer.parseInt(values[3]))); + } + + position.setNetwork(network); + + position.set(Position.KEY_ODOMETER, parser.nextLong(16, 0)); + + return position; + } + private Position decodeNetwork(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_NETWORK, sentence); if (!parser.matches()) { @@ -419,7 +465,9 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder { } } - if (sentence.contains("ZC20")) { + if (sentence.indexOf('{') > 0 && sentence.indexOf('}') > 0) { + return decodeCell(channel, remoteAddress, sentence); + } else if (sentence.contains("ZC20")) { return decodeBattery(channel, remoteAddress, sentence); } else if (sentence.contains("BZ00")) { return decodeNetwork(channel, remoteAddress, sentence); diff --git a/src/main/java/org/traccar/protocol/Tk103ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Tk103ProtocolEncoder.java index 5d7e63920..e3e1ae961 100644 --- a/src/main/java/org/traccar/protocol/Tk103ProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/Tk103ProtocolEncoder.java @@ -16,10 +16,11 @@ */ package org.traccar.protocol; -import org.traccar.Context; +import org.traccar.Protocol; import org.traccar.StringProtocolEncoder; +import org.traccar.config.Keys; +import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Command; -import org.traccar.Protocol; public class Tk103ProtocolEncoder extends StringProtocolEncoder { @@ -42,12 +43,12 @@ public class Tk103ProtocolEncoder extends StringProtocolEncoder { @Override protected Object encodeCommand(Command command) { - boolean alternative = forceAlternative || Context.getIdentityManager().lookupAttributeBoolean( - command.getDeviceId(), getProtocolName() + ".alternative", false, false, true); + boolean alternative = AttributeUtil.lookup( + getCacheManager(), Keys.PROTOCOL_ALTERNATIVE.withPrefix(getProtocolName()), command.getDeviceId()); initDevicePassword(command, "123456"); - if (alternative) { + if (alternative || forceAlternative) { switch (command.getType()) { case Command.TYPE_CUSTOM: return formatAlt(command, "%s", Command.KEY_DATA); diff --git a/src/main/java/org/traccar/protocol/Tlt2hProtocol.java b/src/main/java/org/traccar/protocol/Tlt2hProtocol.java index 12fd92afa..b10271f7d 100644 --- a/src/main/java/org/traccar/protocol/Tlt2hProtocol.java +++ b/src/main/java/org/traccar/protocol/Tlt2hProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Tlt2hProtocol extends BaseProtocol { - public Tlt2hProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Tlt2hProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(32 * 1024, "##\r\n")); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java index ad7dfa886..e85bdf9b3 100644 --- a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,8 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.model.CellTower; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; @@ -55,6 +56,12 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder { private static final Pattern PATTERN_POSITION = new PatternBuilder() .text("#") .number("(?:(dd)|x*)") // cell or voltage + .groupBegin() + .number("#(d+),") // mcc + .number("(d+),") // mnc + .number("(x+),") // lac + .number("(x+)") // cell id + .groupEnd("?") .text("$GPRMC,") .number("(dd)(dd)(dd).d+,") // time (hhmmss.sss) .expression("([AVL]),") // validity @@ -71,6 +78,12 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder { private static final Pattern PATTERN_WIFI = new PatternBuilder() .text("#") .number("(?:(dd)|x+)") // cell or voltage + .groupBegin() + .number("#(d+),") // mcc + .number("(d+),") // mnc + .number("(x+),") // lac + .number("(x+)") // cell id + .groupEnd("?") .text("$WIFI,") .number("(dd)(dd)(dd).d+,") // time (hhmmss.sss) .expression("[AVL],") // validity @@ -168,6 +181,13 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY, parser.nextInt() * 0.1); } + if (parser.hasNext(4)) { + Network network = new Network(); + network.addCellTower(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt())); + position.setNetwork(network); + } + DateBuilder dateBuilder = new DateBuilder() .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); @@ -191,11 +211,16 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY, parser.nextInt() * 0.1); + Network network = new Network(); + if (parser.hasNext(4)) { + network.addCellTower(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt())); + } + DateBuilder dateBuilder = new DateBuilder() .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); String[] values = parser.next().split(","); - Network network = new Network(); for (int i = 0; i < values.length / 2; i++) { String mac = values[i * 2 + 1].replaceAll("(..)", "$1:").substring(0, 17); network.addWifiAccessPoint(WifiAccessPoint.from(mac, Integer.parseInt(values[i * 2]))); diff --git a/src/main/java/org/traccar/protocol/TlvProtocol.java b/src/main/java/org/traccar/protocol/TlvProtocol.java index 94f5da94f..9d83388c9 100644 --- a/src/main/java/org/traccar/protocol/TlvProtocol.java +++ b/src/main/java/org/traccar/protocol/TlvProtocol.java @@ -19,13 +19,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TlvProtocol extends BaseProtocol { - public TlvProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TlvProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '\0')); pipeline.addLast(new TlvProtocolDecoder(TlvProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/TlvProtocolDecoder.java b/src/main/java/org/traccar/protocol/TlvProtocolDecoder.java index 36cf7859f..7870c778a 100644 --- a/src/main/java/org/traccar/protocol/TlvProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TlvProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.UnitsConverter; diff --git a/src/main/java/org/traccar/protocol/TmgProtocol.java b/src/main/java/org/traccar/protocol/TmgProtocol.java index 020332ce7..e078c425b 100644 --- a/src/main/java/org/traccar/protocol/TmgProtocol.java +++ b/src/main/java/org/traccar/protocol/TmgProtocol.java @@ -20,13 +20,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TmgProtocol extends BaseProtocol { - public TmgProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TmgProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new TmgFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/TmgProtocolDecoder.java b/src/main/java/org/traccar/protocol/TmgProtocolDecoder.java index d27849f8c..00dc2a09b 100644 --- a/src/main/java/org/traccar/protocol/TmgProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TmgProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/TopflytechProtocol.java b/src/main/java/org/traccar/protocol/TopflytechProtocol.java index 303072bdb..339d2fc8d 100644 --- a/src/main/java/org/traccar/protocol/TopflytechProtocol.java +++ b/src/main/java/org/traccar/protocol/TopflytechProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TopflytechProtocol extends BaseProtocol { - public TopflytechProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TopflytechProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ')')); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/TopflytechProtocolDecoder.java b/src/main/java/org/traccar/protocol/TopflytechProtocolDecoder.java index 6de053c32..92a7b5c9d 100644 --- a/src/main/java/org/traccar/protocol/TopflytechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TopflytechProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/TopinProtocol.java b/src/main/java/org/traccar/protocol/TopinProtocol.java index d28afbf94..b15373d71 100644 --- a/src/main/java/org/traccar/protocol/TopinProtocol.java +++ b/src/main/java/org/traccar/protocol/TopinProtocol.java @@ -18,16 +18,20 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class TopinProtocol extends BaseProtocol { - public TopinProtocol() { + @Inject + public TopinProtocol(Config config) { setSupportedDataCommands( Command.TYPE_SOS_NUMBER); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new TopinProtocolEncoder(TopinProtocol.this)); pipeline.addLast(new TopinProtocolDecoder(TopinProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java index 267bce562..a1d5481db 100644 --- a/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; @@ -93,6 +93,19 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { return negative ? -result : result; } + private String decodeAlarm(int alarms) { + if (BitUtil.check(alarms, 0)) { + return Position.ALARM_VIBRATION; + } + if (BitUtil.check(alarms, 1)) { + return Position.ALARM_OVERSPEED; + } + if (BitUtil.check(alarms, 4)) { + return Position.ALARM_LOW_POWER; + } + return null; + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -158,17 +171,7 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { if (buf.readableBytes() >= 5) { position.setAltitude(buf.readShort()); - - int alarms = buf.readUnsignedByte(); - if (BitUtil.check(alarms, 0)) { - position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION); - } - if (BitUtil.check(alarms, 1)) { - position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); - } - if (BitUtil.check(alarms, 4)) { - position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); - } + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); } ByteBuf content = Unpooled.buffer(); @@ -190,10 +193,12 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { getLastLocation(position, null); + ByteBuf content = buf.retainedSlice(buf.readerIndex(), buf.readableBytes() - 2); + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); position.set(Position.KEY_VERSION_FW, buf.readUnsignedByte()); buf.readUnsignedByte(); // timezone - int interval = buf.readUnsignedByte(); + buf.readUnsignedByte(); // interval if (buf.readableBytes() >= 1 + 2) { position.set(Position.KEY_RSSI, buf.readUnsignedByte()); } @@ -207,8 +212,6 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_HEART_RATE, buf.readUnsignedByte()); } - ByteBuf content = Unpooled.buffer(); - content.writeByte(interval); sendResponse(channel, length, type, content); return position; @@ -246,6 +249,10 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedShort(), buf.readUnsignedByte())); } + if (buf.readableBytes() > 2) { + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + } + position.setNetwork(network); ByteBuf content = Unpooled.buffer(); diff --git a/src/main/java/org/traccar/protocol/TotemProtocol.java b/src/main/java/org/traccar/protocol/TotemProtocol.java index f8cda8358..9ab36fd0b 100644 --- a/src/main/java/org/traccar/protocol/TotemProtocol.java +++ b/src/main/java/org/traccar/protocol/TotemProtocol.java @@ -20,18 +20,39 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class TotemProtocol extends BaseProtocol { - public TotemProtocol() { + @Inject + public TotemProtocol(Config config) { setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_REBOOT_DEVICE, + Command.TYPE_FACTORY_RESET, + Command.TYPE_GET_VERSION, + Command.TYPE_POSITION_SINGLE, + Command.TYPE_ENGINE_RESUME, + Command.TYPE_ENGINE_STOP + ); + + setTextCommandEncoder(new TotemProtocolSmsEncoder(this)); + setSupportedTextCommands( + Command.TYPE_CUSTOM, + Command.TYPE_REBOOT_DEVICE, + Command.TYPE_FACTORY_RESET, + Command.TYPE_GET_VERSION, + Command.TYPE_POSITION_SINGLE, Command.TYPE_ENGINE_RESUME, Command.TYPE_ENGINE_STOP ); - addServer(new TrackerServer(false, getName()) { + + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new TotemFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java b/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java index 58c66031e..9d0d794f8 100644 --- a/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -140,8 +140,12 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder { .number("(x{8})") // status .number("(dd)(dd)(dd)") // date (yymmdd) .number("(dd)(dd)(dd)") // time (hhmmss) + .groupBegin() .number("(dd)") // battery .number("(dd)") // external power + .or() + .number("(ddd)") // battery + .groupEnd() .number("(dddd)") // adc 1 .groupBegin() .groupBegin() @@ -166,6 +170,7 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder { .number("(d{7})") // odometer .number("(dd)(dd.dddd)([NS])") // latitude .number("(ddd)(dd.dddd)([EW])") // longitude + .number("dddd").optional() // temperature .number("dddd") // serial number .number("xx") // checksum .any() @@ -320,7 +325,7 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder { int lac = parser.nextHexInt(0); int cid = parser.nextHexInt(0); if (lac != 0 && cid != 0) { - position.setNetwork(new Network(CellTower.fromLacCid(lac, cid))); + position.setNetwork(new Network(CellTower.fromLacCid(getConfig(), lac, cid))); } position.set(Position.PREFIX_TEMP + 1, parser.next()); @@ -346,7 +351,7 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder { position.set(Position.PREFIX_TEMP + 2, parser.next()); position.setNetwork(new Network( - CellTower.fromLacCid(parser.nextHexInt(0), parser.nextHexInt(0)))); + CellTower.fromLacCid(getConfig(), parser.nextHexInt(0), parser.nextHexInt(0)))); position.setValid(parser.next().equals("A")); position.set(Position.KEY_SATELLITES, parser.nextInt()); @@ -379,8 +384,13 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder { position.setTime(parser.nextDateTime()); - position.set(Position.KEY_BATTERY, parser.nextDouble() * 0.1); - position.set(Position.KEY_POWER, parser.nextDouble()); + if (parser.hasNext(2)) { + position.set(Position.KEY_BATTERY, parser.nextDouble() * 0.1); + position.set(Position.KEY_POWER, parser.nextDouble()); + } + if (parser.hasNext()) { + position.set(Position.KEY_BATTERY, parser.nextDouble() * 0.01); + } position.set(Position.PREFIX_ADC + 1, parser.next()); position.set(Position.PREFIX_ADC + 2, parser.next()); @@ -403,7 +413,7 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder { int mcc = parser.nextInt(); cellTower = CellTower.from(mcc, mnc, lac, cid); } else { - cellTower = CellTower.fromLacCid(lac, cid); + cellTower = CellTower.fromLacCid(getConfig(), lac, cid); } position.set(Position.KEY_SATELLITES, parser.nextInt()); cellTower.setSignalStrength(parser.nextInt()); diff --git a/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java b/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java index a96dd1ee3..4b22ade03 100644 --- a/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java @@ -6,7 +6,7 @@ * 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 + * 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, @@ -19,6 +19,7 @@ package org.traccar.protocol; import org.traccar.StringProtocolEncoder; import org.traccar.model.Command; import org.traccar.Protocol; +import org.traccar.helper.Checksum; public class TotemProtocolEncoder extends StringProtocolEncoder { @@ -26,20 +27,41 @@ public class TotemProtocolEncoder extends StringProtocolEncoder { super(protocol); } - @Override - protected Object encodeCommand(Command command) { - - initDevicePassword(command, "000000"); - + public static String formatContent(Command command) { switch (command.getType()) { + case Command.TYPE_CUSTOM: + return String.format("%s,%s", + command.getAttributes().get(Command.KEY_DEVICE_PASSWORD), + command.getAttributes().get(Command.KEY_DATA) + ); + case Command.TYPE_REBOOT_DEVICE: + return String.format("%s,006", command.getAttributes().get(Command.KEY_DEVICE_PASSWORD)); + case Command.TYPE_FACTORY_RESET: + return String.format("%s,007", command.getAttributes().get(Command.KEY_DEVICE_PASSWORD)); + case Command.TYPE_GET_VERSION: + return String.format("%s,056", command.getAttributes().get(Command.KEY_DEVICE_PASSWORD)); + case Command.TYPE_POSITION_SINGLE: + return String.format("%s,012", command.getAttributes().get(Command.KEY_DEVICE_PASSWORD)); // Assuming PIN 8 (Output C) is the power wire, like manual says but it can be PIN 5,7,8 case Command.TYPE_ENGINE_STOP: - return formatCommand(command, "*%s,025,C,1#", Command.KEY_DEVICE_PASSWORD); + return String.format("%s,025,C,1", command.getAttributes().get(Command.KEY_DEVICE_PASSWORD)); case Command.TYPE_ENGINE_RESUME: - return formatCommand(command, "*%s,025,C,0#", Command.KEY_DEVICE_PASSWORD); + return String.format("%s,025,C,0", command.getAttributes().get(Command.KEY_DEVICE_PASSWORD)); default: return null; } } + @Override + protected Object encodeCommand(Command command) { + + initDevicePassword(command, "000000"); + + String commandString = formatContent(command); + String builtCommand = String.format("$$%04dCF%s", 10 + commandString.getBytes().length, commandString); + + return String.format("%s%02X", builtCommand, Checksum.xor(builtCommand)); + + } + } diff --git a/src/main/java/org/traccar/protocol/TotemProtocolSmsEncoder.java b/src/main/java/org/traccar/protocol/TotemProtocolSmsEncoder.java new file mode 100644 index 000000000..8656f8a5c --- /dev/null +++ b/src/main/java/org/traccar/protocol/TotemProtocolSmsEncoder.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Irving Gonzalez + * Copyright 2015 - 2019 Anton Tananaev (anton@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.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class TotemProtocolSmsEncoder extends StringProtocolEncoder { + + public TotemProtocolSmsEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + initDevicePassword(command, "000000"); + + return String.format("*%s#", TotemProtocolEncoder.formatContent(command)); + } + +} diff --git a/src/main/java/org/traccar/protocol/Tr20Protocol.java b/src/main/java/org/traccar/protocol/Tr20Protocol.java index 1b71db03f..615fdab28 100644 --- a/src/main/java/org/traccar/protocol/Tr20Protocol.java +++ b/src/main/java/org/traccar/protocol/Tr20Protocol.java @@ -21,22 +21,26 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Tr20Protocol extends BaseProtocol { - public Tr20Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Tr20Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new Tr20ProtocolDecoder(Tr20Protocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new Tr20ProtocolDecoder(Tr20Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/Tr20ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tr20ProtocolDecoder.java index 2f11bd152..0e1c7568b 100644 --- a/src/main/java/org/traccar/protocol/Tr20ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Tr20ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; @@ -52,7 +52,7 @@ public class Tr20ProtocolDecoder extends BaseProtocolDecoder { .number("(ddd)(dd.d+),") // longitude .number("(d+),") // speed .number("(d+),") // course - .number("(?:NA|[FC]?(-?d+)[^,]*),") // temperature + .number("(?:NA|[BFC]?(-?d+)[^,]*),") // temperature .number("(x{8}),") // status .number("(d+)") // event .any() diff --git a/src/main/java/org/traccar/protocol/Tr900Protocol.java b/src/main/java/org/traccar/protocol/Tr900Protocol.java index b70521b35..162cbe651 100644 --- a/src/main/java/org/traccar/protocol/Tr900Protocol.java +++ b/src/main/java/org/traccar/protocol/Tr900Protocol.java @@ -21,22 +21,26 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Tr900Protocol extends BaseProtocol { - public Tr900Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Tr900Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new Tr900ProtocolDecoder(Tr900Protocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new Tr900ProtocolDecoder(Tr900Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/Tr900ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tr900ProtocolDecoder.java index 319194c21..da0e8d292 100644 --- a/src/main/java/org/traccar/protocol/Tr900ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Tr900ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/TrackboxProtocol.java b/src/main/java/org/traccar/protocol/TrackboxProtocol.java index 5da5abd64..4236144a3 100644 --- a/src/main/java/org/traccar/protocol/TrackboxProtocol.java +++ b/src/main/java/org/traccar/protocol/TrackboxProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TrackboxProtocol extends BaseProtocol { - public TrackboxProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TrackboxProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/TrackboxProtocolDecoder.java b/src/main/java/org/traccar/protocol/TrackboxProtocolDecoder.java index db8022738..10483d445 100644 --- a/src/main/java/org/traccar/protocol/TrackboxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TrackboxProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/TrakMateProtocol.java b/src/main/java/org/traccar/protocol/TrakMateProtocol.java index bda5df10f..b7637e6f3 100644 --- a/src/main/java/org/traccar/protocol/TrakMateProtocol.java +++ b/src/main/java/org/traccar/protocol/TrakMateProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TrakMateProtocol extends BaseProtocol { - public TrakMateProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TrakMateProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/TrakMateProtocolDecoder.java b/src/main/java/org/traccar/protocol/TrakMateProtocolDecoder.java index 4d5cb18f5..b1f50dc10 100644 --- a/src/main/java/org/traccar/protocol/TrakMateProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TrakMateProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/TramigoProtocol.java b/src/main/java/org/traccar/protocol/TramigoProtocol.java index f683ccc5d..79a59abd3 100644 --- a/src/main/java/org/traccar/protocol/TramigoProtocol.java +++ b/src/main/java/org/traccar/protocol/TramigoProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TramigoProtocol extends BaseProtocol { - public TramigoProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TramigoProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new TramigoFrameDecoder()); pipeline.addLast(new TramigoProtocolDecoder(TramigoProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/TramigoProtocolDecoder.java b/src/main/java/org/traccar/protocol/TramigoProtocolDecoder.java index e42e2f670..21dd78da3 100644 --- a/src/main/java/org/traccar/protocol/TramigoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TramigoProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateUtil; diff --git a/src/main/java/org/traccar/protocol/TrvProtocol.java b/src/main/java/org/traccar/protocol/TrvProtocol.java index 99a164cf1..e67afbda2 100644 --- a/src/main/java/org/traccar/protocol/TrvProtocol.java +++ b/src/main/java/org/traccar/protocol/TrvProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TrvProtocol extends BaseProtocol { - public TrvProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TrvProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java b/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java index 05312b820..9df29ae1b 100644 --- a/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; @@ -59,7 +59,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder { .number("(d)") // acc .number("(dd)") // arm status .number("(dd)") // working mode - .number("(?:[0-2]{3})?,") + .number("(?:d{3,5})?,") .number("(d+),") // mcc .number("(d+),") // mnc .number("(d+),") // lac @@ -183,7 +183,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder { return position; - } else if (type.equals("AP01") || type.equals("AP10") || type.equals("YP03")) { + } else if (type.equals("AP01") || type.equals("AP10") || type.equals("YP03") || type.equals("YP14")) { Parser parser = new Parser(PATTERN, sentence); if (!parser.matches()) { diff --git a/src/main/java/org/traccar/protocol/Tt8850Protocol.java b/src/main/java/org/traccar/protocol/Tt8850Protocol.java index 66a13da9e..ab109e274 100644 --- a/src/main/java/org/traccar/protocol/Tt8850Protocol.java +++ b/src/main/java/org/traccar/protocol/Tt8850Protocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Tt8850Protocol extends BaseProtocol { - public Tt8850Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Tt8850Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "$")); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/Tt8850ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tt8850ProtocolDecoder.java index 1010528c4..cbc983000 100644 --- a/src/main/java/org/traccar/protocol/Tt8850ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Tt8850ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/TytanProtocol.java b/src/main/java/org/traccar/protocol/TytanProtocol.java index 32e9acae1..cc3bc9b52 100644 --- a/src/main/java/org/traccar/protocol/TytanProtocol.java +++ b/src/main/java/org/traccar/protocol/TytanProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class TytanProtocol extends BaseProtocol { - public TytanProtocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public TytanProtocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new TytanProtocolDecoder(TytanProtocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/TytanProtocolDecoder.java b/src/main/java/org/traccar/protocol/TytanProtocolDecoder.java index 93d3a63d2..6169e0545 100644 --- a/src/main/java/org/traccar/protocol/TytanProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TytanProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/TzoneProtocol.java b/src/main/java/org/traccar/protocol/TzoneProtocol.java index 6e855d138..d25757b63 100644 --- a/src/main/java/org/traccar/protocol/TzoneProtocol.java +++ b/src/main/java/org/traccar/protocol/TzoneProtocol.java @@ -15,18 +15,21 @@ */ package org.traccar.protocol; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; -import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import javax.inject.Inject; public class TzoneProtocol extends BaseProtocol { - public TzoneProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public TzoneProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(256, 2, 2, 2, 0)); pipeline.addLast(new TzoneProtocolDecoder(TzoneProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java b/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java index 249915b39..8e84a6781 100644 --- a/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java @@ -17,10 +17,13 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; +import org.traccar.NetworkMessage; import org.traccar.Protocol; +import org.traccar.config.Keys; import org.traccar.helper.BcdUtil; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; @@ -30,6 +33,11 @@ import org.traccar.model.Network; import org.traccar.model.Position; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; public class TzoneProtocolDecoder extends BaseProtocolDecoder { @@ -37,6 +45,20 @@ public class TzoneProtocolDecoder extends BaseProtocolDecoder { super(protocol); } + private void sendResponse(Channel channel, SocketAddress remoteAddress, int index) { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + + String ack = String.format("@ACK,%d#", index); + String time = String.format("@UTC time:%s", dateFormat.format(new Date())); + + ByteBuf response = Unpooled.copiedBuffer(ack + time, StandardCharsets.US_ASCII); + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + private String decodeAlarm(Short value) { switch (value) { case 0x01: @@ -261,7 +283,7 @@ public class TzoneProtocolDecoder extends BaseProtocolDecoder { if (hardware == 0x10A || hardware == 0x10B || hardware == 0x406) { position.setNetwork(new Network( - CellTower.fromLacCid(buf.readUnsignedShort(), buf.readUnsignedShort()))); + CellTower.fromLacCid(getConfig(), buf.readUnsignedShort(), buf.readUnsignedShort()))); } else if (hardware == 0x407) { @@ -348,6 +370,10 @@ public class TzoneProtocolDecoder extends BaseProtocolDecoder { } + if (getConfig().getBoolean(Keys.PROTOCOL_ACK.withPrefix(getProtocolName()))) { + sendResponse(channel, remoteAddress, buf.getUnsignedShort(buf.writerIndex() - 6)); + } + return position; } diff --git a/src/main/java/org/traccar/protocol/UlbotechProtocol.java b/src/main/java/org/traccar/protocol/UlbotechProtocol.java index dfe5235f0..57fc47644 100644 --- a/src/main/java/org/traccar/protocol/UlbotechProtocol.java +++ b/src/main/java/org/traccar/protocol/UlbotechProtocol.java @@ -18,16 +18,20 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class UlbotechProtocol extends BaseProtocol { - public UlbotechProtocol() { + @Inject + public UlbotechProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new UlbotechFrameDecoder()); pipeline.addLast(new UlbotechProtocolEncoder(UlbotechProtocol.this)); pipeline.addLast(new UlbotechProtocolDecoder(UlbotechProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/UlbotechProtocolDecoder.java b/src/main/java/org/traccar/protocol/UlbotechProtocolDecoder.java index 7fec0bf8b..c9b35158e 100644 --- a/src/main/java/org/traccar/protocol/UlbotechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/UlbotechProtocolDecoder.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -37,6 +37,7 @@ import org.traccar.model.Position; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.Date; +import java.util.TimeZone; import java.util.regex.Pattern; public class UlbotechProtocolDecoder extends BaseProtocolDecoder { @@ -214,16 +215,17 @@ public class UlbotechProtocolDecoder extends BaseProtocolDecoder { return null; } - if (deviceSession.getTimeZone() == null) { - deviceSession.setTimeZone(getTimeZone(deviceSession.getDeviceId())); + if (!deviceSession.contains(DeviceSession.KEY_TIMEZONE)) { + deviceSession.set(DeviceSession.KEY_TIMEZONE, getTimeZone(deviceSession.getDeviceId())); } Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); + TimeZone timeZone = deviceSession.get(DeviceSession.KEY_TIMEZONE); long seconds = buf.readUnsignedInt() & 0x7fffffffL; seconds += 946684800L; // 2000-01-01 00:00 - seconds -= deviceSession.getTimeZone().getRawOffset() / 1000; + seconds -= timeZone.getRawOffset() / 1000; Date time = new Date(seconds * 1000); boolean hasLocation = false; diff --git a/src/main/java/org/traccar/protocol/UproProtocol.java b/src/main/java/org/traccar/protocol/UproProtocol.java index 4e60ffeb6..e27088594 100644 --- a/src/main/java/org/traccar/protocol/UproProtocol.java +++ b/src/main/java/org/traccar/protocol/UproProtocol.java @@ -20,13 +20,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class UproProtocol extends BaseProtocol { - public UproProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public UproProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); pipeline.addLast(new StringEncoder()); pipeline.addLast(new UproProtocolDecoder(UproProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java index 9f236a7e5..ed714e464 100644 --- a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; @@ -67,12 +67,11 @@ public class UproProtocolDecoder extends BaseProtocolDecoder { DateBuilder dateBuilder = new DateBuilder() .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); - position.setValid(true); position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN)); position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN)); int flags = parser.nextInt(0); - position.setValid(BitUtil.check(flags, 0)); + position.setValid(!BitUtil.check(flags, 0)); if (!BitUtil.check(flags, 1)) { position.setLatitude(-position.getLatitude()); } diff --git a/src/main/java/org/traccar/protocol/UuxProtocol.java b/src/main/java/org/traccar/protocol/UuxProtocol.java index 41b59d829..3de4a4732 100644 --- a/src/main/java/org/traccar/protocol/UuxProtocol.java +++ b/src/main/java/org/traccar/protocol/UuxProtocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class UuxProtocol extends BaseProtocol { - public UuxProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public UuxProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 1)); pipeline.addLast(new UuxProtocolDecoder(UuxProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/UuxProtocolDecoder.java b/src/main/java/org/traccar/protocol/UuxProtocolDecoder.java index 1081fa811..b9065e7f3 100644 --- a/src/main/java/org/traccar/protocol/UuxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/UuxProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/V680Protocol.java b/src/main/java/org/traccar/protocol/V680Protocol.java index dc0922cd4..53bca849c 100644 --- a/src/main/java/org/traccar/protocol/V680Protocol.java +++ b/src/main/java/org/traccar/protocol/V680Protocol.java @@ -21,22 +21,26 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class V680Protocol extends BaseProtocol { - public V680Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public V680Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "##")); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new V680ProtocolDecoder(V680Protocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new V680ProtocolDecoder(V680Protocol.this)); diff --git a/src/main/java/org/traccar/protocol/V680ProtocolDecoder.java b/src/main/java/org/traccar/protocol/V680ProtocolDecoder.java index 40267022b..237aea39b 100644 --- a/src/main/java/org/traccar/protocol/V680ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/V680ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/VisiontekProtocol.java b/src/main/java/org/traccar/protocol/VisiontekProtocol.java index 2c6af45a8..5296402b4 100644 --- a/src/main/java/org/traccar/protocol/VisiontekProtocol.java +++ b/src/main/java/org/traccar/protocol/VisiontekProtocol.java @@ -21,13 +21,17 @@ import org.traccar.BaseProtocol; import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class VisiontekProtocol extends BaseProtocol { - public VisiontekProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public VisiontekProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/VisiontekProtocolDecoder.java b/src/main/java/org/traccar/protocol/VisiontekProtocolDecoder.java index c4787bda2..9ab871bfe 100644 --- a/src/main/java/org/traccar/protocol/VisiontekProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/VisiontekProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/VnetProtocol.java b/src/main/java/org/traccar/protocol/VnetProtocol.java index 0fed30e13..dd739f0d9 100644 --- a/src/main/java/org/traccar/protocol/VnetProtocol.java +++ b/src/main/java/org/traccar/protocol/VnetProtocol.java @@ -19,15 +19,19 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import java.nio.ByteOrder; +import javax.inject.Inject; + public class VnetProtocol extends BaseProtocol { - public VnetProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public VnetProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1500, 4, 2, 12, 0, true)); pipeline.addLast(new VnetProtocolDecoder(VnetProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/VnetProtocolDecoder.java b/src/main/java/org/traccar/protocol/VnetProtocolDecoder.java index 1ee00bd3f..048c89e1e 100644 --- a/src/main/java/org/traccar/protocol/VnetProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/VnetProtocolDecoder.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; diff --git a/src/main/java/org/traccar/protocol/Vt200Protocol.java b/src/main/java/org/traccar/protocol/Vt200Protocol.java index 2a9ef6ab5..efb5fe2fd 100644 --- a/src/main/java/org/traccar/protocol/Vt200Protocol.java +++ b/src/main/java/org/traccar/protocol/Vt200Protocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Vt200Protocol extends BaseProtocol { - public Vt200Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Vt200Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new Vt200FrameDecoder()); pipeline.addLast(new Vt200ProtocolDecoder(Vt200Protocol.this)); } diff --git a/src/main/java/org/traccar/protocol/Vt200ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Vt200ProtocolDecoder.java index 84ad09caa..a8fc801e7 100644 --- a/src/main/java/org/traccar/protocol/Vt200ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Vt200ProtocolDecoder.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; import org.traccar.helper.BitUtil; diff --git a/src/main/java/org/traccar/protocol/VtfmsProtocol.java b/src/main/java/org/traccar/protocol/VtfmsProtocol.java index 2826a86e6..482ab4a37 100644 --- a/src/main/java/org/traccar/protocol/VtfmsProtocol.java +++ b/src/main/java/org/traccar/protocol/VtfmsProtocol.java @@ -15,26 +15,29 @@ */ package org.traccar.protocol; +import io.netty.handler.codec.string.StringDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; -import io.netty.handler.codec.string.StringDecoder; +import javax.inject.Inject; public class VtfmsProtocol extends BaseProtocol { - public VtfmsProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public VtfmsProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new VtfmsFrameDecoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new VtfmsProtocolDecoder(VtfmsProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringDecoder()); pipeline.addLast(new VtfmsProtocolDecoder(VtfmsProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/VtfmsProtocolDecoder.java b/src/main/java/org/traccar/protocol/VtfmsProtocolDecoder.java index 17fac4311..bf0cdcb51 100644 --- a/src/main/java/org/traccar/protocol/VtfmsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/VtfmsProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/WatchProtocol.java b/src/main/java/org/traccar/protocol/WatchProtocol.java index 6dc3bf9fb..600f81328 100644 --- a/src/main/java/org/traccar/protocol/WatchProtocol.java +++ b/src/main/java/org/traccar/protocol/WatchProtocol.java @@ -18,11 +18,15 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; +import javax.inject.Inject; + public class WatchProtocol extends BaseProtocol { - public WatchProtocol() { + @Inject + public WatchProtocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_POSITION_SINGLE, @@ -40,9 +44,9 @@ public class WatchProtocol extends BaseProtocol { Command.TYPE_VOICE_MESSAGE, Command.TYPE_SET_TIMEZONE, Command.TYPE_SET_INDICATOR); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new WatchFrameDecoder()); pipeline.addLast(new WatchProtocolEncoder(WatchProtocol.this)); pipeline.addLast(new WatchProtocolDecoder(WatchProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java b/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java index 4990cfd65..142d1b64f 100644 --- a/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,14 +18,12 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; +import org.traccar.helper.BufferUtil; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; import org.traccar.helper.UnitsConverter; @@ -41,7 +39,7 @@ import java.util.regex.Pattern; public class WatchProtocolDecoder extends BaseProtocolDecoder { - private static final Logger LOGGER = LoggerFactory.getLogger(WatchProtocolDecoder.class); + private ByteBuf audio; public WatchProtocolDecoder(Protocol protocol) { super(protocol); @@ -89,6 +87,8 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder { return Position.ALARM_GEOFENCE_EXIT; } else if (BitUtil.check(status, 2)) { return Position.ALARM_GEOFENCE_ENTER; + } else if (BitUtil.check(status, 14)) { + return Position.ALARM_POWER_CUT; } else if (BitUtil.check(status, 16)) { return Position.ALARM_SOS; } else if (BitUtil.check(status, 17)) { @@ -142,14 +142,21 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder { Network network = new Network(); int cellCount = Integer.parseInt(values[index++]); - index += 1; // timing advance - int mcc = !values[index].isEmpty() ? Integer.parseInt(values[index++]) : 0; - int mnc = !values[index].isEmpty() ? Integer.parseInt(values[index++]) : 0; - - for (int i = 0; i < cellCount; i++) { - network.addCellTower(CellTower.from(mcc, mnc, - Integer.parseInt(values[index++]), Integer.parseInt(values[index++]), - Integer.parseInt(values[index++]))); + if (cellCount > 0) { + index += 1; // timing advance + int mcc = !values[index].isEmpty() ? Integer.parseInt(values[index++]) : 0; + int mnc = !values[index].isEmpty() ? Integer.parseInt(values[index++]) : 0; + + for (int i = 0; i < cellCount; i++) { + int lac = Integer.parseInt(values[index++]); + int cid = Integer.parseInt(values[index++]); + String rssi = values[index++]; + if (!rssi.isEmpty()) { + network.addCellTower(CellTower.from(mcc, mnc, lac, cid, Integer.parseInt(rssi))); + } else { + network.addCellTower(CellTower.from(mcc, mnc, lac, cid)); + } + } } if (index < values.length && !values[index].isEmpty()) { @@ -157,8 +164,11 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder { for (int i = 0; i < wifiCount; i++) { index += 1; // wifi name - network.addWifiAccessPoint(WifiAccessPoint.from( - values[index++], Integer.parseInt(values[index++]))); + String macAddress = values[index++]; + String rssi = values[index++]; + if (!macAddress.isEmpty() && !macAddress.equals("0") && !rssi.isEmpty()) { + network.addWifiAccessPoint(WifiAccessPoint.from(macAddress, Integer.parseInt(rssi))); + } } } @@ -253,6 +263,9 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder { Position position = decodePosition(deviceSession, buf.toString(StandardCharsets.US_ASCII)); if (type.startsWith("AL")) { + if (position != null) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } sendResponse(channel, id, index, "AL"); } @@ -305,17 +318,41 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder { int timeIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ','); buf.readerIndex(timeIndex + 12 + 2); - position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(id, buf, "jpg")); + position.set(Position.KEY_IMAGE, writeMediaFile(id, buf, "jpg")); return position; + } else if (type.equals("JXTK")) { + + int dataIndex = BufferUtil.indexOf(buf, buf.readerIndex(), buf.writerIndex(), (byte) ',', 4) + 1; + String[] values = buf.readCharSequence( + dataIndex - buf.readerIndex(), StandardCharsets.US_ASCII).toString().split(","); + + int current = Integer.parseInt(values[2]); + int total = Integer.parseInt(values[3]); + + if (audio == null) { + audio = Unpooled.buffer(); + } + audio.writeBytes(buf); + + sendResponse(channel, id, index, "JXTKR,1"); + + if (current < total) { + return null; + } else { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, null); + position.set(Position.KEY_AUDIO, writeMediaFile(id, audio, "amr")); + audio.release(); + audio = null; + return position; + } + } else if (type.equals("TK")) { if (buf.readableBytes() == 1) { - byte result = buf.readByte(); - if (result != '1') { - LOGGER.warn(type + "," + result); - } return null; } @@ -324,7 +361,7 @@ public class WatchProtocolDecoder extends BaseProtocolDecoder { getLastLocation(position, null); - position.set(Position.KEY_AUDIO, Context.getMediaManager().writeFile(id, buf, "amr")); + position.set(Position.KEY_AUDIO, writeMediaFile(id, buf, "amr")); return position; diff --git a/src/main/java/org/traccar/protocol/WatchProtocolEncoder.java b/src/main/java/org/traccar/protocol/WatchProtocolEncoder.java index f1904ea4d..14ebe2852 100644 --- a/src/main/java/org/traccar/protocol/WatchProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/WatchProtocolEncoder.java @@ -67,6 +67,9 @@ public class WatchProtocolEncoder extends StringProtocolEncoder implements Strin if (decoder != null) { hasIndex = decoder.getHasIndex(); manufacturer = decoder.getManufacturer(); + if (manufacturer.equals("3G")) { + manufacturer = "SG"; + } } } diff --git a/src/main/java/org/traccar/protocol/WialonProtocol.java b/src/main/java/org/traccar/protocol/WialonProtocol.java index cb6ea5319..a744349cd 100644 --- a/src/main/java/org/traccar/protocol/WialonProtocol.java +++ b/src/main/java/org/traccar/protocol/WialonProtocol.java @@ -15,32 +15,34 @@ */ package org.traccar.protocol; +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; -import org.traccar.Context; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.model.Command; -import io.netty.handler.codec.LineBasedFrameDecoder; -import io.netty.handler.codec.string.StringDecoder; -import io.netty.handler.codec.string.StringEncoder; - import java.nio.charset.StandardCharsets; +import javax.inject.Inject; + public class WialonProtocol extends BaseProtocol { - public WialonProtocol() { + @Inject + public WialonProtocol(Config config) { setSupportedDataCommands( Command.TYPE_REBOOT_DEVICE, Command.TYPE_SEND_USSD, Command.TYPE_IDENTIFICATION, Command.TYPE_OUTPUT_CONTROL); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(4 * 1024)); - boolean utf8 = Context.getConfig().getBoolean(Keys.PROTOCOL_UTF8.withPrefix(getName())); + boolean utf8 = config.getBoolean(Keys.PROTOCOL_UTF8.withPrefix(getName())); if (utf8) { pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8)); pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8)); @@ -52,11 +54,11 @@ public class WialonProtocol extends BaseProtocol { pipeline.addLast(new WialonProtocolDecoder(WialonProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(4 * 1024)); - boolean utf8 = Context.getConfig().getBoolean(Keys.PROTOCOL_UTF8.withPrefix(getName())); + boolean utf8 = config.getBoolean(Keys.PROTOCOL_UTF8.withPrefix(getName())); if (utf8) { pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8)); pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8)); diff --git a/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java b/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java index 80299ff08..3d57525b7 100644 --- a/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; @@ -39,7 +39,7 @@ public class WialonProtocolDecoder extends BaseProtocolDecoder { } private static final Pattern PATTERN_ANY = new PatternBuilder() - .expression("([^#]*)?") // imei + .expression("([^#]+)?") // imei .text("#") // start byte .expression("([^#]+)") // type .text("#") // separator @@ -47,13 +47,13 @@ public class WialonProtocolDecoder extends BaseProtocolDecoder { .compile(); private static final Pattern PATTERN = new PatternBuilder() - .number("(dd)(dd)(dd);") // date (ddmmyy) - .number("(dd)(dd)(dd);") // time (hhmmss) + .number("(?:NA|(dd)(dd)(dd));") // date (ddmmyy) + .number("(?:NA|(dd)(dd)(dd));") // time (hhmmss) .number("(?:NA|(dd)(dd.d+));") // latitude .expression("(?:NA|([NS]));") .number("(?:NA|(ddd)(dd.d+));") // longitude .expression("(?:NA|([EW]));") - .number("(d+.?d*)?;") // speed + .number("(?:NA|(d+.?d*))?;") // speed .number("(?:NA|(d+.?d*))?;") // course .number("(?:NA|(-?d+.?d*));") // altitude .number("(?:NA|(d+))") // satellites @@ -95,7 +95,11 @@ public class WialonProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + if (parser.hasNext(6)) { + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + } else { + position.setTime(new Date()); + } if (parser.hasNext(9)) { position.setLatitude(parser.nextCoordinate()); diff --git a/src/main/java/org/traccar/protocol/WliProtocol.java b/src/main/java/org/traccar/protocol/WliProtocol.java index c10ebf505..f7084e55b 100644 --- a/src/main/java/org/traccar/protocol/WliProtocol.java +++ b/src/main/java/org/traccar/protocol/WliProtocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class WliProtocol extends BaseProtocol { - public WliProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public WliProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new WliFrameDecoder()); pipeline.addLast(new WliProtocolDecoder(WliProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/WliProtocolDecoder.java b/src/main/java/org/traccar/protocol/WliProtocolDecoder.java index 0e2a0a65e..ec1c4d17a 100644 --- a/src/main/java/org/traccar/protocol/WliProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/WliProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,12 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; import org.traccar.model.Position; import java.net.SocketAddress; @@ -41,9 +43,9 @@ public class WliProtocolDecoder extends BaseProtocolDecoder { ByteBuf buf = (ByteBuf) msg; buf.readUnsignedByte(); // header - int type = buf.readUnsignedByte(); + int clazz = buf.readUnsignedByte(); - if (type == '1') { + if (clazz == '1') { DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); if (deviceSession == null) { @@ -53,11 +55,13 @@ public class WliProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); + CellTower cellTower = new CellTower(); + position.set(Position.KEY_INDEX, buf.readUnsignedShort()); buf.readUnsignedShort(); // length buf.readUnsignedShort(); // checksum - buf.readUnsignedByte(); // application message type + int type = buf.readUnsignedByte(); buf.readUnsignedByte(); // delimiter while (buf.readableBytes() > 1) { @@ -95,18 +99,56 @@ public class WliProtocolDecoder extends BaseProtocolDecoder { String value = buf.readCharSequence( endIndex - buf.readerIndex(), StandardCharsets.US_ASCII).toString(); - switch (fieldNumber) { - case 246: - String[] values = value.split(","); - position.set(Position.KEY_POWER, Integer.parseInt(values[2]) * 0.01); - position.set(Position.KEY_BATTERY, Integer.parseInt(values[3]) * 0.01); + int networkFieldsOffset; + switch (type) { + case 0xE4: + networkFieldsOffset = 10; + break; + case 0xCB: + networkFieldsOffset = 80; break; - case 255: - position.setDeviceTime(new Date(Long.parseLong(value) * 1000)); + case 0x1E: + networkFieldsOffset = 182; break; + case 0xC9: default: + networkFieldsOffset = 35; break; } + if (fieldNumber - networkFieldsOffset >= 0 && fieldNumber - networkFieldsOffset < 10) { + switch (fieldNumber - networkFieldsOffset) { + case 0: + cellTower.setMobileCountryCode(Integer.parseInt(value)); + break; + case 1: + cellTower.setMobileNetworkCode(Integer.parseInt(value)); + break; + case 2: + cellTower.setLocationAreaCode(Integer.parseInt(value)); + break; + case 3: + cellTower.setCellId(Long.parseLong(value)); + break; + case 4: + cellTower.setSignalStrength(Integer.parseInt(value)); + break; + default: + break; + } + } else { + switch (fieldNumber) { + case 246: + String[] values = value.split(","); + position.set(Position.KEY_POWER, Integer.parseInt(values[2]) * 0.01); + position.set(Position.KEY_BATTERY, Integer.parseInt(values[3]) * 0.01); + break; + case 255: + position.setDeviceTime(new Date(Long.parseLong(value) * 1000)); + break; + default: + break; + } + } } @@ -114,13 +156,21 @@ public class WliProtocolDecoder extends BaseProtocolDecoder { } + if (type == 0xE4) { + getLastLocation(position, position.getDeviceTime()); + } + + if (cellTower.getCellId() != null) { + position.setNetwork(new Network(cellTower)); + } + if (!position.getValid()) { getLastLocation(position, position.getDeviceTime()); } return position; - } else if (type == '2') { + } else if (clazz == '2') { String id = buf.toString(buf.readerIndex(), buf.readableBytes() - 1, StandardCharsets.US_ASCII); getDeviceSession(channel, remoteAddress, id.substring("wli:".length())); diff --git a/src/main/java/org/traccar/protocol/WondexProtocol.java b/src/main/java/org/traccar/protocol/WondexProtocol.java index 6401fde85..5a0401df4 100644 --- a/src/main/java/org/traccar/protocol/WondexProtocol.java +++ b/src/main/java/org/traccar/protocol/WondexProtocol.java @@ -15,16 +15,19 @@ */ package org.traccar.protocol; +import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; -import io.netty.handler.codec.string.StringEncoder; +import javax.inject.Inject; public class WondexProtocol extends BaseProtocol { - public WondexProtocol() { + @Inject + public WondexProtocol(Config config) { setSupportedDataCommands( Command.TYPE_GET_DEVICE_STATUS, Command.TYPE_GET_MODEM_STATUS, @@ -40,18 +43,18 @@ public class WondexProtocol extends BaseProtocol { Command.TYPE_POSITION_SINGLE, Command.TYPE_GET_VERSION, Command.TYPE_IDENTIFICATION); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new WondexFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new WondexProtocolEncoder(WondexProtocol.this)); pipeline.addLast(new WondexProtocolDecoder(WondexProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new WondexProtocolEncoder(WondexProtocol.this)); pipeline.addLast(new WondexProtocolDecoder(WondexProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/WondexProtocolDecoder.java b/src/main/java/org/traccar/protocol/WondexProtocolDecoder.java index b85ae2656..46aa65a5d 100644 --- a/src/main/java/org/traccar/protocol/WondexProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/WondexProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; @@ -74,6 +74,9 @@ public class WondexProtocolDecoder extends BaseProtocolDecoder { || buf.toString(StandardCharsets.US_ASCII).startsWith("$MSG:")) { DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); @@ -89,12 +92,12 @@ public class WondexProtocolDecoder 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()); diff --git a/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java b/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java index 21f1ee321..fb213dc40 100644 --- a/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/src/main/java/org/traccar/protocol/WristbandProtocol.java b/src/main/java/org/traccar/protocol/WristbandProtocol.java index 1e5ef2c01..c5d8d4050 100644 --- a/src/main/java/org/traccar/protocol/WristbandProtocol.java +++ b/src/main/java/org/traccar/protocol/WristbandProtocol.java @@ -19,13 +19,17 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class WristbandProtocol extends BaseProtocol { - public WristbandProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public WristbandProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 2, 3, 0)); pipeline.addLast(new WristbandProtocolDecoder(WristbandProtocol.this)); } diff --git a/src/main/java/org/traccar/protocol/WristbandProtocolDecoder.java b/src/main/java/org/traccar/protocol/WristbandProtocolDecoder.java index 58b5784d0..323992ddd 100644 --- a/src/main/java/org/traccar/protocol/WristbandProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/WristbandProtocolDecoder.java @@ -19,7 +19,7 @@ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/Xexun2FrameEncoder.java b/src/main/java/org/traccar/protocol/Xexun2FrameEncoder.java new file mode 100644 index 000000000..52d43c36c --- /dev/null +++ b/src/main/java/org/traccar/protocol/Xexun2FrameEncoder.java @@ -0,0 +1,48 @@ +/* + * Copyright 2022 Stefan Clark (stefan@stefanclark.co.uk) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +public class Xexun2FrameEncoder extends MessageToByteEncoder<ByteBuf> { + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) { + out.writeBytes(msg.readBytes(2)); + + while (msg.readableBytes() > 2) { + int b = msg.readUnsignedByte(); + if (b == 0xfa && msg.isReadable() && msg.getUnsignedByte(msg.readerIndex()) == 0xaf) { + msg.readUnsignedByte(); + out.writeByte(0xfb); + out.writeByte(0xbf); + out.writeByte(0x01); + } else if (b == 0xfb && msg.isReadable() && msg.getUnsignedByte(msg.readerIndex()) == 0xbf) { + msg.readUnsignedByte(); + out.writeByte(0xfb); + out.writeByte(0xbf); + out.writeByte(0x02); + } else { + out.writeByte(b); + } + } + + out.writeBytes(msg.readBytes(2)); + + } +} diff --git a/src/main/java/org/traccar/protocol/Xexun2Protocol.java b/src/main/java/org/traccar/protocol/Xexun2Protocol.java index 265841c77..52cf731f0 100644 --- a/src/main/java/org/traccar/protocol/Xexun2Protocol.java +++ b/src/main/java/org/traccar/protocol/Xexun2Protocol.java @@ -18,15 +18,27 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; +import org.traccar.model.Command; + +import javax.inject.Inject; public class Xexun2Protocol extends BaseProtocol { - public Xexun2Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Xexun2Protocol(Config config) { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_POSITION_PERIODIC, + Command.TYPE_POWER_OFF, + Command.TYPE_REBOOT_DEVICE); + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new Xexun2FrameEncoder()); pipeline.addLast(new Xexun2FrameDecoder()); pipeline.addLast(new Xexun2ProtocolDecoder(Xexun2Protocol.this)); + pipeline.addLast(new Xexun2ProtocolEncoder(Xexun2Protocol.this)); } }); } diff --git a/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java index 766a3f05b..913dfaf28 100644 --- a/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Xexun2ProtocolDecoder.java @@ -20,10 +20,11 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; import org.traccar.helper.UnitsConverter; import org.traccar.model.CellTower; import org.traccar.model.Network; @@ -41,13 +42,14 @@ public class Xexun2ProtocolDecoder extends BaseProtocolDecoder { super(protocol); } + public static final int FLAG = 0xfaaf; + public static final int MSG_COMMAND = 0x07; public static final int MSG_POSITION = 0x14; private void sendResponse(Channel channel, int type, int index, ByteBuf imei) { if (channel != null) { ByteBuf response = Unpooled.buffer(); - response.writeByte(0xfa); - response.writeByte(0xaf); + response.writeShort(FLAG); response.writeShort(type); response.writeShort(index); @@ -56,8 +58,7 @@ public class Xexun2ProtocolDecoder extends BaseProtocolDecoder { response.writeShort(0xfffe); // checksum response.writeByte(1); // response - response.writeByte(0xfa); - response.writeByte(0xaf); + response.writeShort(FLAG); channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); } @@ -67,6 +68,9 @@ public class Xexun2ProtocolDecoder extends BaseProtocolDecoder { if (BitUtil.check(value, 0)) { return Position.ALARM_SOS; } + if (BitUtil.check(value, 1)) { + return Position.ALARM_REMOVING; + } if (BitUtil.check(value, 15)) { return Position.ALARM_FALL_DOWN; } @@ -96,10 +100,16 @@ public class Xexun2ProtocolDecoder extends BaseProtocolDecoder { return null; } - sendResponse(channel, type, index, imei); + int payloadSize = buf.readUnsignedShort() & 0x03ff; + int checksum = buf.readUnsignedShort(); + + if (checksum != Checksum.ip(buf.nioBuffer(buf.readerIndex(), payloadSize))) { + return null; + } - buf.readUnsignedShort(); // attributes - buf.readUnsignedShort(); // checksum + if (type != MSG_COMMAND) { + sendResponse(channel, type, index, imei); + } if (type == MSG_POSITION) { List<Integer> lengths = new ArrayList<>(); @@ -173,7 +183,25 @@ public class Xexun2ProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); position.setLongitude(convertCoordinate(buf.readDouble())); position.setLatitude(convertCoordinate(buf.readDouble())); - + } + if (BitUtil.check(positionMask, 7)) { + int dataLength = buf.readUnsignedShort(); + if (dataLength > 0) { + int dataType = buf.readUnsignedByte(); + int dataEndIndex = buf.readerIndex() + buf.readUnsignedShort(); + if (dataType == 'G') { + position.setFixTime(position.getDeviceTime()); + position.setLongitude(convertCoordinate(buf.readDouble())); + position.setLatitude(convertCoordinate(buf.readDouble())); + position.setValid(buf.readUnsignedByte() > 0); + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + buf.readUnsignedByte(); // satellite signal-to-noise ratio + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() * 0.1)); + position.setCourse(buf.readUnsignedShort() * 0.1); + position.setAltitude(buf.readFloat()); + } + buf.readerIndex(dataEndIndex); + } } } if (BitUtil.check(mask, 3)) { diff --git a/src/main/java/org/traccar/protocol/Xexun2ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Xexun2ProtocolEncoder.java new file mode 100644 index 000000000..8f3fa5672 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Xexun2ProtocolEncoder.java @@ -0,0 +1,70 @@ +/* + * Copyright 2022 Stefan Clark (stefan@stefanclark.co.uk) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.helper.Checksum; +import org.traccar.helper.DataConverter; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.nio.charset.StandardCharsets; + +public class Xexun2ProtocolEncoder extends BaseProtocolEncoder { + + public Xexun2ProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private static ByteBuf encodeContent(String uniqueId, String content) { + ByteBuf buf = Unpooled.buffer(); + + ByteBuf message = Unpooled.copiedBuffer(content.getBytes(StandardCharsets.US_ASCII)); + + buf.writeShort(Xexun2ProtocolDecoder.FLAG); + buf.writeShort(Xexun2ProtocolDecoder.MSG_COMMAND); + buf.writeShort(1); // index + buf.writeBytes(DataConverter.parseHex(uniqueId + "0")); + buf.writeShort(message.readableBytes()); + buf.writeShort(Checksum.ip(message.nioBuffer())); + buf.writeBytes(message); + buf.writeShort(Xexun2ProtocolDecoder.FLAG); + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + String uniqueId = getUniqueId(command.getDeviceId()); + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return encodeContent(uniqueId, command.getString(Command.KEY_DATA)); + case Command.TYPE_POSITION_PERIODIC: + return encodeContent(uniqueId, + String.format("tracking_send=%1$d,%1$d", command.getInteger(Command.KEY_FREQUENCY))); + case Command.TYPE_POWER_OFF: + return encodeContent(uniqueId, "of=1"); + case Command.TYPE_REBOOT_DEVICE: + return encodeContent(uniqueId, "reset"); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/XexunProtocol.java b/src/main/java/org/traccar/protocol/XexunProtocol.java index b83c4e445..5c7329603 100644 --- a/src/main/java/org/traccar/protocol/XexunProtocol.java +++ b/src/main/java/org/traccar/protocol/XexunProtocol.java @@ -15,27 +15,29 @@ */ package org.traccar.protocol; +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; -import org.traccar.Context; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.model.Command; -import io.netty.handler.codec.LineBasedFrameDecoder; -import io.netty.handler.codec.string.StringDecoder; -import io.netty.handler.codec.string.StringEncoder; +import javax.inject.Inject; public class XexunProtocol extends BaseProtocol { - public XexunProtocol() { + @Inject + public XexunProtocol(Config config) { setSupportedDataCommands( Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { - boolean full = Context.getConfig().getBoolean(Keys.PROTOCOL_EXTENDED.withPrefix(getName())); + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + boolean full = config.getBoolean(Keys.PROTOCOL_EXTENDED.withPrefix(getName())); if (full) { pipeline.addLast(new LineBasedFrameDecoder(1024)); // tracker bug \n\r } else { diff --git a/src/main/java/org/traccar/protocol/XexunProtocolDecoder.java b/src/main/java/org/traccar/protocol/XexunProtocolDecoder.java index 73d386477..e41d467d5 100644 --- a/src/main/java/org/traccar/protocol/XexunProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/XexunProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/protocol/XirgoProtocol.java b/src/main/java/org/traccar/protocol/XirgoProtocol.java index 1be5b6c4b..0841d86d5 100644 --- a/src/main/java/org/traccar/protocol/XirgoProtocol.java +++ b/src/main/java/org/traccar/protocol/XirgoProtocol.java @@ -15,23 +15,26 @@ */ 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; +import org.traccar.config.Config; import org.traccar.model.Command; -import io.netty.handler.codec.string.StringDecoder; -import io.netty.handler.codec.string.StringEncoder; +import javax.inject.Inject; public class XirgoProtocol extends BaseProtocol { - public XirgoProtocol() { + @Inject + public XirgoProtocol(Config config) { setSupportedDataCommands( Command.TYPE_OUTPUT_CONTROL); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "##")); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); @@ -39,9 +42,9 @@ public class XirgoProtocol extends BaseProtocol { pipeline.addLast(new XirgoProtocolDecoder(XirgoProtocol.this)); } }); - addServer(new TrackerServer(true, getName()) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new XirgoProtocolEncoder(XirgoProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/XirgoProtocolDecoder.java b/src/main/java/org/traccar/protocol/XirgoProtocolDecoder.java index 630fe5aed..220c28054 100644 --- a/src/main/java/org/traccar/protocol/XirgoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/XirgoProtocolDecoder.java @@ -18,8 +18,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import io.netty.channel.socket.nio.NioDatagramChannel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.config.Keys; @@ -40,7 +39,11 @@ public class XirgoProtocolDecoder extends BaseProtocolDecoder { public XirgoProtocolDecoder(Protocol protocol) { super(protocol); - form = Context.getConfig().getString(Keys.PROTOCOL_FORM.withPrefix(getProtocolName())); + } + + @Override + protected void init() { + form = getConfig().getString(Keys.PROTOCOL_FORM.withPrefix(getProtocolName())); } public void setForm(String form) { diff --git a/src/main/java/org/traccar/protocol/Xrb28Protocol.java b/src/main/java/org/traccar/protocol/Xrb28Protocol.java index 5d8af418b..65c2a1230 100644 --- a/src/main/java/org/traccar/protocol/Xrb28Protocol.java +++ b/src/main/java/org/traccar/protocol/Xrb28Protocol.java @@ -21,22 +21,26 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; import org.traccar.model.Command; import java.nio.charset.StandardCharsets; +import javax.inject.Inject; + public class Xrb28Protocol extends BaseProtocol { - public Xrb28Protocol() { + @Inject + public Xrb28Protocol(Config config) { setSupportedDataCommands( Command.TYPE_CUSTOM, Command.TYPE_POSITION_SINGLE, Command.TYPE_POSITION_PERIODIC, Command.TYPE_ALARM_ARM, Command.TYPE_ALARM_DISARM); - addServer(new TrackerServer(false, getName()) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder(StandardCharsets.ISO_8859_1)); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java index 69e5b7372..88f2d07e5 100644 --- a/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; diff --git a/src/main/java/org/traccar/protocol/Xt013Protocol.java b/src/main/java/org/traccar/protocol/Xt013Protocol.java index ebb3c123f..9e9087609 100644 --- a/src/main/java/org/traccar/protocol/Xt013Protocol.java +++ b/src/main/java/org/traccar/protocol/Xt013Protocol.java @@ -15,20 +15,23 @@ */ package org.traccar.protocol; +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; -import io.netty.handler.codec.LineBasedFrameDecoder; -import io.netty.handler.codec.string.StringDecoder; -import io.netty.handler.codec.string.StringEncoder; +import javax.inject.Inject; public class Xt013Protocol extends BaseProtocol { - public Xt013Protocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public Xt013Protocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StringEncoder()); diff --git a/src/main/java/org/traccar/protocol/Xt013ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xt013ProtocolDecoder.java index f49fb9563..ab0b2cdaa 100644 --- a/src/main/java/org/traccar/protocol/Xt013ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Xt013ProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; diff --git a/src/main/java/org/traccar/protocol/Xt2400Protocol.java b/src/main/java/org/traccar/protocol/Xt2400Protocol.java index 9427876c8..e200adb9f 100644 --- a/src/main/java/org/traccar/protocol/Xt2400Protocol.java +++ b/src/main/java/org/traccar/protocol/Xt2400Protocol.java @@ -18,13 +18,17 @@ package org.traccar.protocol; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class Xt2400Protocol extends BaseProtocol { - public Xt2400Protocol() { - addServer(new TrackerServer(true, getName()) { + @Inject + public Xt2400Protocol(Config config) { + addServer(new TrackerServer(config, getName(), true) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new Xt2400ProtocolDecoder(Xt2400Protocol.this)); } }); diff --git a/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java index 85e8e140f..edcb3f535 100644 --- a/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java @@ -18,8 +18,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.Context; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.Protocol; import org.traccar.config.Keys; import org.traccar.helper.DataConverter; @@ -38,8 +37,11 @@ public class Xt2400ProtocolDecoder extends BaseProtocolDecoder { public Xt2400ProtocolDecoder(Protocol protocol) { super(protocol); + } - String config = Context.getConfig().getString(Keys.PROTOCOL_CONFIG.withPrefix(getProtocolName())); + @Override + protected void init() { + String config = getConfig().getString(Keys.PROTOCOL_CONFIG.withPrefix(getProtocolName())); if (config != null) { setConfig(config); } diff --git a/src/main/java/org/traccar/protocol/YwtProtocol.java b/src/main/java/org/traccar/protocol/YwtProtocol.java index c525b75cf..fb44e2360 100644 --- a/src/main/java/org/traccar/protocol/YwtProtocol.java +++ b/src/main/java/org/traccar/protocol/YwtProtocol.java @@ -21,13 +21,17 @@ import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; +import org.traccar.config.Config; + +import javax.inject.Inject; public class YwtProtocol extends BaseProtocol { - public YwtProtocol() { - addServer(new TrackerServer(false, getName()) { + @Inject + public YwtProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { @Override - protected void addProtocolHandlers(PipelineBuilder pipeline) { + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); diff --git a/src/main/java/org/traccar/protocol/YwtProtocolDecoder.java b/src/main/java/org/traccar/protocol/YwtProtocolDecoder.java index bf5a23fa7..fd050bee9 100644 --- a/src/main/java/org/traccar/protocol/YwtProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/YwtProtocolDecoder.java @@ -17,7 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; +import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.Parser; diff --git a/src/main/java/org/traccar/reports/CsvExportProvider.java b/src/main/java/org/traccar/reports/CsvExportProvider.java new file mode 100644 index 000000000..df55c470e --- /dev/null +++ b/src/main/java/org/traccar/reports/CsvExportProvider.java @@ -0,0 +1,76 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports; + +import org.traccar.helper.DateUtil; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Position; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; + +import javax.inject.Inject; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class CsvExportProvider { + + private final Storage storage; + + @Inject + public CsvExportProvider(Storage storage) { + this.storage = storage; + } + + public void generate( + OutputStream outputStream, long deviceId, Date from, Date to) throws StorageException { + + var positions = PositionUtil.getPositions(storage, deviceId, from, to); + + var attributes = positions.stream() + .flatMap((position -> position.getAttributes().keySet().stream())) + .collect(Collectors.toUnmodifiableSet()); + + var properties = new LinkedHashMap<String, Function<Position, Object>>(); + properties.put("id", Position::getId); + properties.put("deviceId", Position::getDeviceId); + properties.put("protocol", Position::getProtocol); + properties.put("serverTime", position -> DateUtil.formatDate(position.getServerTime())); + properties.put("deviceTime", position -> DateUtil.formatDate(position.getDeviceTime())); + properties.put("fixTime", position -> DateUtil.formatDate(position.getFixTime())); + properties.put("valid", Position::getValid); + properties.put("latitude", Position::getLatitude); + properties.put("longitude", Position::getLongitude); + properties.put("altitude", Position::getAltitude); + properties.put("speed", Position::getSpeed); + properties.put("course", Position::getCourse); + properties.put("address", Position::getAddress); + properties.put("accuracy", Position::getAccuracy); + attributes.forEach(key -> properties.put(key, position -> position.getAttributes().get(key))); + + try (PrintWriter writer = new PrintWriter(outputStream)) { + writer.println(String.join(",", properties.keySet())); + positions.forEach(position -> writer.println(properties.values().stream() + .map(f -> Objects.toString(f.apply(position), "")) + .collect(Collectors.joining(",")))); + } + } + +} diff --git a/src/main/java/org/traccar/reports/Events.java b/src/main/java/org/traccar/reports/Events.java deleted file mode 100644 index 66d9e708d..000000000 --- a/src/main/java/org/traccar/reports/Events.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) - * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.reports; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; - -import org.apache.poi.ss.util.WorkbookUtil; -import org.traccar.Context; -import org.traccar.model.Device; -import org.traccar.model.Event; -import org.traccar.model.Geofence; -import org.traccar.model.Group; -import org.traccar.model.Maintenance; -import org.traccar.reports.model.DeviceReport; - -public final class Events { - - private Events() { - } - - public static Collection<Event> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Collection<String> types, Date from, Date to) throws SQLException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<Event> result = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - Collection<Event> events = Context.getDataManager().getEvents(deviceId, from, to); - boolean all = types.isEmpty() || types.contains(Event.ALL_EVENTS); - for (Event event : events) { - if (all || types.contains(event.getType())) { - long geofenceId = event.getGeofenceId(); - long maintenanceId = event.getMaintenanceId(); - if ((geofenceId == 0 || Context.getGeofenceManager().checkItemPermission(userId, geofenceId)) - && (maintenanceId == 0 - || Context.getMaintenancesManager().checkItemPermission(userId, maintenanceId))) { - result.add(event); - } - } - } - } - return result; - } - - public static void getExcel(OutputStream outputStream, - long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Collection<String> types, Date from, Date to) throws SQLException, IOException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<DeviceReport> devicesEvents = new ArrayList<>(); - ArrayList<String> sheetNames = new ArrayList<>(); - HashMap<Long, String> geofenceNames = new HashMap<>(); - HashMap<Long, String> maintenanceNames = new HashMap<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - Collection<Event> events = Context.getDataManager().getEvents(deviceId, from, to); - boolean all = types.isEmpty() || types.contains(Event.ALL_EVENTS); - for (Iterator<Event> iterator = events.iterator(); iterator.hasNext();) { - Event event = iterator.next(); - if (all || types.contains(event.getType())) { - long geofenceId = event.getGeofenceId(); - long maintenanceId = event.getMaintenanceId(); - if (geofenceId != 0) { - if (Context.getGeofenceManager().checkItemPermission(userId, geofenceId)) { - Geofence geofence = Context.getGeofenceManager().getById(geofenceId); - if (geofence != null) { - geofenceNames.put(geofenceId, geofence.getName()); - } - } else { - iterator.remove(); - } - } else if (maintenanceId != 0) { - if (Context.getMaintenancesManager().checkItemPermission(userId, maintenanceId)) { - Maintenance maintenance = Context.getMaintenancesManager().getById(maintenanceId); - if (maintenance != null) { - maintenanceNames.put(maintenanceId, maintenance.getName()); - } - } else { - iterator.remove(); - } - } - } else { - iterator.remove(); - } - } - DeviceReport deviceEvents = new DeviceReport(); - Device device = Context.getIdentityManager().getById(deviceId); - deviceEvents.setDeviceName(device.getName()); - sheetNames.add(WorkbookUtil.createSafeSheetName(deviceEvents.getDeviceName())); - if (device.getGroupId() != 0) { - Group group = Context.getGroupsManager().getById(device.getGroupId()); - if (group != null) { - deviceEvents.setGroupName(group.getName()); - } - } - deviceEvents.setObjects(events); - devicesEvents.add(deviceEvents); - } - String templatePath = Context.getConfig().getString("report.templatesPath", - "templates/export/"); - try (InputStream inputStream = new FileInputStream(templatePath + "/events.xlsx")) { - org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); - jxlsContext.putVar("devices", devicesEvents); - jxlsContext.putVar("sheetNames", sheetNames); - jxlsContext.putVar("geofenceNames", geofenceNames); - jxlsContext.putVar("maintenanceNames", maintenanceNames); - jxlsContext.putVar("from", from); - jxlsContext.putVar("to", to); - ReportUtils.processTemplateWithSheets(inputStream, outputStream, jxlsContext); - } - } -} diff --git a/src/main/java/org/traccar/reports/EventsReportProvider.java b/src/main/java/org/traccar/reports/EventsReportProvider.java new file mode 100644 index 000000000..30f55ba80 --- /dev/null +++ b/src/main/java/org/traccar/reports/EventsReportProvider.java @@ -0,0 +1,169 @@ +/* + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports; + +import org.apache.poi.ss.util.WorkbookUtil; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Geofence; +import org.traccar.model.Group; +import org.traccar.model.Maintenance; +import org.traccar.model.Position; +import org.traccar.reports.common.ReportUtils; +import org.traccar.reports.model.DeviceReportSection; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Order; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +public class EventsReportProvider { + + private final Config config; + private final ReportUtils reportUtils; + private final Storage storage; + + @Inject + public EventsReportProvider(Config config, ReportUtils reportUtils, Storage storage) { + this.config = config; + this.reportUtils = reportUtils; + this.storage = storage; + } + + private List<Event> getEvents(long deviceId, Date from, Date to) throws StorageException { + return storage.getObjects(Event.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("deviceId", deviceId), + new Condition.Between("eventTime", "from", from, "to", to)), + new Order("eventTime"))); + } + + public Collection<Event> getObjects( + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Collection<String> types, Date from, Date to) throws StorageException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<Event> result = new ArrayList<>(); + for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) { + Collection<Event> events = getEvents(device.getId(), from, to); + boolean all = types.isEmpty() || types.contains(Event.ALL_EVENTS); + for (Event event : events) { + if (all || types.contains(event.getType())) { + long geofenceId = event.getGeofenceId(); + long maintenanceId = event.getMaintenanceId(); + if ((geofenceId == 0 || reportUtils.getObject(userId, Geofence.class, geofenceId) != null) + && (maintenanceId == 0 + || reportUtils.getObject(userId, Maintenance.class, maintenanceId) != null)) { + result.add(event); + } + } + } + } + return result; + } + + public void getExcel( + OutputStream outputStream, long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Collection<String> types, Date from, Date to) throws StorageException, IOException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<DeviceReportSection> devicesEvents = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + HashMap<Long, String> geofenceNames = new HashMap<>(); + HashMap<Long, String> maintenanceNames = new HashMap<>(); + HashMap<Long, Position> positions = new HashMap<>(); + for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) { + Collection<Event> events = getEvents(device.getId(), from, to); + boolean all = types.isEmpty() || types.contains(Event.ALL_EVENTS); + for (Iterator<Event> iterator = events.iterator(); iterator.hasNext();) { + Event event = iterator.next(); + if (all || types.contains(event.getType())) { + long geofenceId = event.getGeofenceId(); + long maintenanceId = event.getMaintenanceId(); + if (geofenceId != 0) { + Geofence geofence = reportUtils.getObject(userId, Geofence.class, geofenceId); + if (geofence != null) { + geofenceNames.put(geofenceId, geofence.getName()); + } else { + iterator.remove(); + } + } else if (maintenanceId != 0) { + Maintenance maintenance = reportUtils.getObject(userId, Maintenance.class, maintenanceId); + if (maintenance != null) { + maintenanceNames.put(maintenanceId, maintenance.getName()); + } else { + iterator.remove(); + } + } + } else { + iterator.remove(); + } + } + for (Event event : events) { + long positionId = event.getPositionId(); + if (positionId > 0) { + Position position = storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.Equals("id", positionId))); + positions.put(positionId, position); + } + } + DeviceReportSection deviceEvents = new DeviceReportSection(); + deviceEvents.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(deviceEvents.getDeviceName())); + if (device.getGroupId() > 0) { + Group group = storage.getObject(Group.class, new Request( + new Columns.All(), new Condition.Equals("id", device.getGroupId()))); + if (group != null) { + deviceEvents.setGroupName(group.getName()); + } + } + deviceEvents.setObjects(events); + devicesEvents.add(deviceEvents); + } + + File file = Paths.get(config.getString(Keys.TEMPLATES_ROOT), "export", "events.xlsx").toFile(); + try (InputStream inputStream = new FileInputStream(file)) { + var context = reportUtils.initializeContext(userId); + context.putVar("devices", devicesEvents); + context.putVar("sheetNames", sheetNames); + context.putVar("geofenceNames", geofenceNames); + context.putVar("maintenanceNames", maintenanceNames); + context.putVar("positions", positions); + context.putVar("from", from); + context.putVar("to", to); + reportUtils.processTemplateWithSheets(inputStream, outputStream, context); + } + } +} diff --git a/src/main/java/org/traccar/reports/GpxExportProvider.java b/src/main/java/org/traccar/reports/GpxExportProvider.java new file mode 100644 index 000000000..ccbd97fc3 --- /dev/null +++ b/src/main/java/org/traccar/reports/GpxExportProvider.java @@ -0,0 +1,76 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports; + +import org.traccar.helper.DateUtil; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.Date; + +public class GpxExportProvider { + + private final Storage storage; + + @Inject + public GpxExportProvider(Storage storage) { + this.storage = storage; + } + + public void generate( + OutputStream outputStream, long deviceId, Date from, Date to) throws StorageException { + + var device = storage.getObject(Device.class, new Request( + new Columns.All(), new Condition.Equals("id", deviceId))); + var positions = PositionUtil.getPositions(storage, deviceId, from, to); + + try (PrintWriter writer = new PrintWriter(outputStream)) { + writer.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + writer.print("<gpx version=\"1.0\">"); + writer.print("<trk>"); + writer.print("<name>"); + writer.print(device.getName()); + writer.print("</name>"); + writer.print("<trkseg>"); + positions.forEach(position -> { + writer.print("<trkpt lat=\""); + writer.print(position.getLatitude()); + writer.print("\" lon=\""); + writer.print(position.getLongitude()); + writer.print("\">"); + writer.print("<ele>"); + writer.print(position.getAltitude()); + writer.print("</ele>"); + writer.print("<time>"); + writer.print(DateUtil.formatDate(position.getFixTime())); + writer.print("</time>"); + writer.print("</trkpt>"); + }); + writer.print("</trkseg>"); + writer.print("</trk>"); + writer.print("</gpx>"); + } + } + +} diff --git a/src/main/java/org/traccar/reports/KmlExportProvider.java b/src/main/java/org/traccar/reports/KmlExportProvider.java new file mode 100644 index 000000000..24fcfb8ab --- /dev/null +++ b/src/main/java/org/traccar/reports/KmlExportProvider.java @@ -0,0 +1,80 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports; + +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.stream.Collectors; + +public class KmlExportProvider { + + private final Storage storage; + + @Inject + public KmlExportProvider(Storage storage) { + this.storage = storage; + } + + public void generate( + OutputStream outputStream, long deviceId, Date from, Date to) throws StorageException { + + var device = storage.getObject(Device.class, new Request( + new Columns.All(), new Condition.Equals("id", deviceId))); + var positions = PositionUtil.getPositions(storage, deviceId, from, to); + + var dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + try (PrintWriter writer = new PrintWriter(outputStream)) { + writer.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + writer.print("<kml xmlns=\"http://www.opengis.net/kml/2.2\">"); + writer.print("<Document>"); + writer.print("<name>"); + writer.print(device.getName()); + writer.print("</name>"); + writer.print("<Placemark>"); + writer.print("<name>"); + writer.print(dateFormat.format(from)); + writer.print(" - "); + writer.print(dateFormat.format(to)); + writer.print("</name>"); + writer.print("<LineString>"); + writer.print("<extrude>1</extrude>"); + writer.print("<tessellate>1</tessellate>"); + writer.print("<altitudeMode>absolute</altitudeMode>"); + writer.print("<coordinates>"); + writer.print(positions.stream() + .map((p -> String.format("%f,%f,%f", p.getLongitude(), p.getLatitude(), p.getAltitude()))) + .collect(Collectors.joining(" "))); + writer.print("</coordinates>"); + writer.print("</LineString>"); + writer.print("</Placemark>"); + writer.print("</Document>"); + writer.print("</kml>"); + } + } + +} diff --git a/src/main/java/org/traccar/reports/ReportUtils.java b/src/main/java/org/traccar/reports/ReportUtils.java deleted file mode 100644 index 58674beae..000000000 --- a/src/main/java/org/traccar/reports/ReportUtils.java +++ /dev/null @@ -1,368 +0,0 @@ -/* - * 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"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.reports; - -import org.apache.velocity.tools.generic.DateTool; -import org.apache.velocity.tools.generic.NumberTool; -import org.jxls.area.Area; -import org.jxls.builder.xls.XlsCommentAreaBuilder; -import org.jxls.common.CellRef; -import org.jxls.formula.StandardFormulaProcessor; -import org.jxls.transform.Transformer; -import org.jxls.transform.poi.PoiTransformer; -import org.jxls.util.TransformerFactory; -import org.traccar.Context; -import org.traccar.config.Keys; -import org.traccar.database.DeviceManager; -import org.traccar.database.IdentityManager; -import org.traccar.handler.events.MotionEventHandler; -import org.traccar.helper.UnitsConverter; -import org.traccar.model.DeviceState; -import org.traccar.model.Driver; -import org.traccar.model.Event; -import org.traccar.model.Position; -import org.traccar.reports.model.BaseReport; -import org.traccar.reports.model.StopReport; -import org.traccar.reports.model.TripReport; -import org.traccar.reports.model.TripsConfig; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; - -public final class ReportUtils { - - private ReportUtils() { - } - - public static void checkPeriodLimit(Date from, Date to) { - long limit = Context.getConfig().getLong(Keys.REPORT_PERIOD_LIMIT) * 1000; - if (limit > 0 && to.getTime() - from.getTime() > limit) { - throw new IllegalArgumentException("Time period exceeds the limit"); - } - } - - public static String getDistanceUnit(long userId) { - return (String) Context.getPermissionsManager().lookupAttribute(userId, "distanceUnit", "km"); - } - - public static String getSpeedUnit(long userId) { - return (String) Context.getPermissionsManager().lookupAttribute(userId, "speedUnit", "kn"); - } - - public static String getVolumeUnit(long userId) { - return (String) Context.getPermissionsManager().lookupAttribute(userId, "volumeUnit", "ltr"); - } - - public static TimeZone getTimezone(long userId) { - String timezone = (String) Context.getPermissionsManager().lookupAttribute(userId, "timezone", null); - return timezone != null ? TimeZone.getTimeZone(timezone) : TimeZone.getDefault(); - } - - public static Collection<Long> getDeviceList(Collection<Long> deviceIds, Collection<Long> groupIds) { - Collection<Long> result = new ArrayList<>(deviceIds); - for (long groupId : groupIds) { - result.addAll(Context.getPermissionsManager().getGroupDevices(groupId)); - } - return result; - } - - public static double calculateDistance(Position firstPosition, Position lastPosition) { - return calculateDistance(firstPosition, lastPosition, true); - } - - public static double calculateDistance(Position firstPosition, Position lastPosition, boolean useOdometer) { - double distance = 0.0; - double firstOdometer = firstPosition.getDouble(Position.KEY_ODOMETER); - double lastOdometer = lastPosition.getDouble(Position.KEY_ODOMETER); - - 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)) { - distance = lastPosition.getDouble(Position.KEY_TOTAL_DISTANCE) - - firstPosition.getDouble(Position.KEY_TOTAL_DISTANCE); - } - return distance; - } - - public static double calculateFuel(Position firstPosition, Position lastPosition) { - - if (firstPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null - && lastPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null) { - - BigDecimal value = BigDecimal.valueOf(firstPosition.getDouble(Position.KEY_FUEL_LEVEL) - - lastPosition.getDouble(Position.KEY_FUEL_LEVEL)); - return value.setScale(1, RoundingMode.HALF_EVEN).doubleValue(); - } - return 0; - } - - public static String findDriver(Position firstPosition, Position lastPosition) { - if (firstPosition.getAttributes().containsKey(Position.KEY_DRIVER_UNIQUE_ID)) { - return firstPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); - } else if (lastPosition.getAttributes().containsKey(Position.KEY_DRIVER_UNIQUE_ID)) { - return lastPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); - } - return null; - } - - public static String findDriverName(String driverUniqueId) { - if (driverUniqueId != null && Context.getDriversManager() != null) { - Driver driver = Context.getDriversManager().getDriverByUniqueId(driverUniqueId); - if (driver != null) { - return driver.getName(); - } - } - return null; - } - - public static org.jxls.common.Context initializeContext(long userId) { - org.jxls.common.Context jxlsContext = PoiTransformer.createInitialContext(); - jxlsContext.putVar("distanceUnit", getDistanceUnit(userId)); - jxlsContext.putVar("speedUnit", getSpeedUnit(userId)); - jxlsContext.putVar("volumeUnit", getVolumeUnit(userId)); - jxlsContext.putVar("webUrl", Context.getVelocityEngine().getProperty("web.url")); - jxlsContext.putVar("dateTool", new DateTool()); - jxlsContext.putVar("numberTool", new NumberTool()); - jxlsContext.putVar("timezone", getTimezone(userId)); - jxlsContext.putVar("locale", Locale.getDefault()); - jxlsContext.putVar("bracketsRegex", "[\\{\\}\"]"); - return jxlsContext; - } - - public static void processTemplateWithSheets( - InputStream templateStream, OutputStream targetStream, - org.jxls.common.Context jxlsContext) throws IOException { - - Transformer transformer = TransformerFactory.createTransformer(templateStream, targetStream); - List<Area> xlsAreas = new XlsCommentAreaBuilder(transformer).build(); - for (Area xlsArea : xlsAreas) { - xlsArea.applyAt(new CellRef(xlsArea.getStartCellRef().getCellName()), jxlsContext); - xlsArea.setFormulaProcessor(new StandardFormulaProcessor()); - xlsArea.processFormulas(); - } - transformer.deleteSheet(xlsAreas.get(0).getStartCellRef().getSheetName()); - transformer.write(); - } - - private static TripReport calculateTrip( - ArrayList<Position> positions, int startIndex, int endIndex, boolean ignoreOdometer) { - - Position startTrip = positions.get(startIndex); - Position endTrip = positions.get(endIndex); - - double speedMax = 0; - for (int i = startIndex; i <= endIndex; i++) { - double speed = positions.get(i).getSpeed(); - if (speed > speedMax) { - speedMax = speed; - } - } - - TripReport trip = new TripReport(); - - long tripDuration = endTrip.getFixTime().getTime() - startTrip.getFixTime().getTime(); - long deviceId = startTrip.getDeviceId(); - trip.setDeviceId(deviceId); - trip.setDeviceName(Context.getIdentityManager().getById(deviceId).getName()); - - trip.setStartPositionId(startTrip.getId()); - trip.setStartLat(startTrip.getLatitude()); - trip.setStartLon(startTrip.getLongitude()); - trip.setStartTime(startTrip.getFixTime()); - String startAddress = startTrip.getAddress(); - if (startAddress == null && Context.getGeocoder() != null - && Context.getConfig().getBoolean(Keys.GEOCODER_ON_REQUEST)) { - startAddress = Context.getGeocoder().getAddress(startTrip.getLatitude(), startTrip.getLongitude(), null); - } - trip.setStartAddress(startAddress); - - trip.setEndPositionId(endTrip.getId()); - trip.setEndLat(endTrip.getLatitude()); - trip.setEndLon(endTrip.getLongitude()); - trip.setEndTime(endTrip.getFixTime()); - String endAddress = endTrip.getAddress(); - if (endAddress == null && Context.getGeocoder() != null - && Context.getConfig().getBoolean(Keys.GEOCODER_ON_REQUEST)) { - endAddress = Context.getGeocoder().getAddress(endTrip.getLatitude(), endTrip.getLongitude(), null); - } - trip.setEndAddress(endAddress); - - trip.setDistance(calculateDistance(startTrip, endTrip, !ignoreOdometer)); - trip.setDuration(tripDuration); - if (tripDuration > 0) { - trip.setAverageSpeed(UnitsConverter.knotsFromMps(trip.getDistance() * 1000 / tripDuration)); - } - trip.setMaxSpeed(speedMax); - trip.setSpentFuel(calculateFuel(startTrip, endTrip)); - - trip.setDriverUniqueId(findDriver(startTrip, endTrip)); - trip.setDriverName(findDriverName(trip.getDriverUniqueId())); - - if (!ignoreOdometer - && startTrip.getDouble(Position.KEY_ODOMETER) != 0 - && endTrip.getDouble(Position.KEY_ODOMETER) != 0) { - trip.setStartOdometer(startTrip.getDouble(Position.KEY_ODOMETER)); - trip.setEndOdometer(endTrip.getDouble(Position.KEY_ODOMETER)); - } else { - trip.setStartOdometer(startTrip.getDouble(Position.KEY_TOTAL_DISTANCE)); - trip.setEndOdometer(endTrip.getDouble(Position.KEY_TOTAL_DISTANCE)); - } - - return trip; - } - - private static StopReport calculateStop( - ArrayList<Position> positions, int startIndex, int endIndex, boolean ignoreOdometer) { - - Position startStop = positions.get(startIndex); - Position endStop = positions.get(endIndex); - - StopReport stop = new StopReport(); - - long deviceId = startStop.getDeviceId(); - stop.setDeviceId(deviceId); - stop.setDeviceName(Context.getIdentityManager().getById(deviceId).getName()); - - stop.setPositionId(startStop.getId()); - stop.setLatitude(startStop.getLatitude()); - stop.setLongitude(startStop.getLongitude()); - stop.setStartTime(startStop.getFixTime()); - String address = startStop.getAddress(); - if (address == null && Context.getGeocoder() != null - && Context.getConfig().getBoolean(Keys.GEOCODER_ON_REQUEST)) { - address = Context.getGeocoder().getAddress(stop.getLatitude(), stop.getLongitude(), null); - } - stop.setAddress(address); - - stop.setEndTime(endStop.getFixTime()); - - long stopDuration = endStop.getFixTime().getTime() - startStop.getFixTime().getTime(); - stop.setDuration(stopDuration); - stop.setSpentFuel(calculateFuel(startStop, endStop)); - - if (startStop.getAttributes().containsKey(Position.KEY_HOURS) - && endStop.getAttributes().containsKey(Position.KEY_HOURS)) { - stop.setEngineHours(endStop.getLong(Position.KEY_HOURS) - startStop.getLong(Position.KEY_HOURS)); - } - - if (!ignoreOdometer - && startStop.getDouble(Position.KEY_ODOMETER) != 0 - && endStop.getDouble(Position.KEY_ODOMETER) != 0) { - stop.setStartOdometer(startStop.getDouble(Position.KEY_ODOMETER)); - stop.setEndOdometer(endStop.getDouble(Position.KEY_ODOMETER)); - } else { - stop.setStartOdometer(startStop.getDouble(Position.KEY_TOTAL_DISTANCE)); - stop.setEndOdometer(endStop.getDouble(Position.KEY_TOTAL_DISTANCE)); - } - - return stop; - - } - - private static <T extends BaseReport> T calculateTripOrStop( - ArrayList<Position> positions, int startIndex, int endIndex, boolean ignoreOdometer, Class<T> reportClass) { - - if (reportClass.equals(TripReport.class)) { - return (T) calculateTrip(positions, startIndex, endIndex, ignoreOdometer); - } else { - return (T) calculateStop(positions, startIndex, endIndex, ignoreOdometer); - } - } - - private static boolean isMoving(ArrayList<Position> positions, int index, TripsConfig tripsConfig) { - if (tripsConfig.getMinimalNoDataDuration() > 0) { - boolean beforeGap = index < positions.size() - 1 - && positions.get(index + 1).getFixTime().getTime() - positions.get(index).getFixTime().getTime() - >= tripsConfig.getMinimalNoDataDuration(); - boolean afterGap = index > 0 - && positions.get(index).getFixTime().getTime() - positions.get(index - 1).getFixTime().getTime() - >= tripsConfig.getMinimalNoDataDuration(); - if (beforeGap || afterGap) { - return false; - } - } - if (positions.get(index).getAttributes().containsKey(Position.KEY_MOTION) - && positions.get(index).getAttributes().get(Position.KEY_MOTION) instanceof Boolean) { - return positions.get(index).getBoolean(Position.KEY_MOTION); - } else { - return positions.get(index).getSpeed() > tripsConfig.getSpeedThreshold(); - } - } - - public static <T extends BaseReport> Collection<T> detectTripsAndStops( - IdentityManager identityManager, DeviceManager deviceManager, - Collection<Position> positionCollection, - TripsConfig tripsConfig, boolean ignoreOdometer, Class<T> reportClass) { - - Collection<T> result = new ArrayList<>(); - - ArrayList<Position> positions = new ArrayList<>(positionCollection); - if (!positions.isEmpty()) { - boolean trips = reportClass.equals(TripReport.class); - MotionEventHandler motionHandler = new MotionEventHandler(identityManager, deviceManager, tripsConfig); - DeviceState deviceState = new DeviceState(); - deviceState.setMotionState(isMoving(positions, 0, tripsConfig)); - int startEventIndex = trips == deviceState.getMotionState() ? 0 : -1; - int startNoEventIndex = -1; - for (int i = 0; i < positions.size(); i++) { - Map<Event, Position> event = motionHandler.updateMotionState(deviceState, positions.get(i), - isMoving(positions, i, tripsConfig)); - if (startEventIndex == -1 - && (trips != deviceState.getMotionState() && deviceState.getMotionPosition() != null - || trips == deviceState.getMotionState() && event != null)) { - startEventIndex = i; - startNoEventIndex = -1; - } else if (trips != deviceState.getMotionState() && startEventIndex != -1 - && deviceState.getMotionPosition() == null && event == null) { - startEventIndex = -1; - } - if (startNoEventIndex == -1 - && (trips == deviceState.getMotionState() && deviceState.getMotionPosition() != null - || trips != deviceState.getMotionState() && event != null)) { - startNoEventIndex = i; - } else if (startNoEventIndex != -1 && deviceState.getMotionPosition() == null && event == null) { - startNoEventIndex = -1; - } - if (startEventIndex != -1 && startNoEventIndex != -1 && event != null - && trips != deviceState.getMotionState()) { - result.add(calculateTripOrStop(positions, startEventIndex, startNoEventIndex, - ignoreOdometer, reportClass)); - startEventIndex = -1; - } - } - if (startEventIndex != -1 && (startNoEventIndex != -1 || !trips)) { - result.add(calculateTripOrStop(positions, startEventIndex, - startNoEventIndex != -1 ? startNoEventIndex : positions.size() - 1, - ignoreOdometer, reportClass)); - } - } - - return result; - } - -} diff --git a/src/main/java/org/traccar/reports/Route.java b/src/main/java/org/traccar/reports/Route.java deleted file mode 100644 index 6adb00aae..000000000 --- a/src/main/java/org/traccar/reports/Route.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2016 Anton Tananaev (anton@traccar.org) - * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.reports; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; - -import org.apache.poi.ss.util.WorkbookUtil; -import org.traccar.Context; -import org.traccar.model.Device; -import org.traccar.model.Group; -import org.traccar.model.Position; -import org.traccar.reports.model.DeviceReport; - -public final class Route { - - private Route() { - } - - public static Collection<Position> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to) throws SQLException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<Position> result = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - result.addAll(Context.getDataManager().getPositions(deviceId, from, to)); - } - return result; - } - - public static void getExcel(OutputStream outputStream, - long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to) throws SQLException, IOException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<DeviceReport> devicesRoutes = new ArrayList<>(); - ArrayList<String> sheetNames = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - Collection<Position> positions = Context.getDataManager() - .getPositions(deviceId, from, to); - DeviceReport deviceRoutes = new DeviceReport(); - Device device = Context.getIdentityManager().getById(deviceId); - deviceRoutes.setDeviceName(device.getName()); - sheetNames.add(WorkbookUtil.createSafeSheetName(deviceRoutes.getDeviceName())); - if (device.getGroupId() != 0) { - Group group = Context.getGroupsManager().getById(device.getGroupId()); - if (group != null) { - deviceRoutes.setGroupName(group.getName()); - } - } - deviceRoutes.setObjects(positions); - devicesRoutes.add(deviceRoutes); - } - String templatePath = Context.getConfig().getString("report.templatesPath", - "templates/export/"); - try (InputStream inputStream = new FileInputStream(templatePath + "/route.xlsx")) { - org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); - jxlsContext.putVar("devices", devicesRoutes); - jxlsContext.putVar("sheetNames", sheetNames); - jxlsContext.putVar("from", from); - jxlsContext.putVar("to", to); - ReportUtils.processTemplateWithSheets(inputStream, outputStream, jxlsContext); - } - } -} diff --git a/src/main/java/org/traccar/reports/RouteReportProvider.java b/src/main/java/org/traccar/reports/RouteReportProvider.java new file mode 100644 index 000000000..3ee651619 --- /dev/null +++ b/src/main/java/org/traccar/reports/RouteReportProvider.java @@ -0,0 +1,102 @@ +/* + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports; + +import org.apache.poi.ss.util.WorkbookUtil; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.model.Position; +import org.traccar.reports.common.ReportUtils; +import org.traccar.reports.model.DeviceReportSection; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +public class RouteReportProvider { + + private final Config config; + private final ReportUtils reportUtils; + private final Storage storage; + + @Inject + public RouteReportProvider(Config config, ReportUtils reportUtils, Storage storage) { + this.config = config; + this.reportUtils = reportUtils; + this.storage = storage; + } + + public Collection<Position> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<Position> result = new ArrayList<>(); + for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) { + result.addAll(PositionUtil.getPositions(storage, device.getId(), from, to)); + } + return result; + } + + public void getExcel(OutputStream outputStream, + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException, IOException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<DeviceReportSection> devicesRoutes = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) { + var positions = PositionUtil.getPositions(storage, device.getId(), from, to); + DeviceReportSection deviceRoutes = new DeviceReportSection(); + deviceRoutes.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(deviceRoutes.getDeviceName())); + if (device.getGroupId() > 0) { + Group group = storage.getObject(Group.class, new Request( + new Columns.All(), new Condition.Equals("id", device.getGroupId()))); + if (group != null) { + deviceRoutes.setGroupName(group.getName()); + } + } + deviceRoutes.setObjects(positions); + devicesRoutes.add(deviceRoutes); + } + + File file = Paths.get(config.getString(Keys.TEMPLATES_ROOT), "export", "route.xlsx").toFile(); + try (InputStream inputStream = new FileInputStream(file)) { + var context = reportUtils.initializeContext(userId); + context.putVar("devices", devicesRoutes); + context.putVar("sheetNames", sheetNames); + context.putVar("from", from); + context.putVar("to", to); + reportUtils.processTemplateWithSheets(inputStream, outputStream, context); + } + } +} diff --git a/src/main/java/org/traccar/reports/Stops.java b/src/main/java/org/traccar/reports/Stops.java deleted file mode 100644 index 2036b0641..000000000 --- a/src/main/java/org/traccar/reports/Stops.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2017 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.traccar.reports; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; - -import org.apache.poi.ss.util.WorkbookUtil; -import org.traccar.Context; -import org.traccar.Main; -import org.traccar.database.DeviceManager; -import org.traccar.database.IdentityManager; -import org.traccar.model.Device; -import org.traccar.model.Group; -import org.traccar.reports.model.DeviceReport; -import org.traccar.reports.model.StopReport; - -public final class Stops { - - private Stops() { - } - - private static Collection<StopReport> detectStops(long deviceId, Date from, Date to) throws SQLException { - boolean ignoreOdometer = Context.getDeviceManager() - .lookupAttributeBoolean(deviceId, "report.ignoreOdometer", false, false, true); - - IdentityManager identityManager = Main.getInjector().getInstance(IdentityManager.class); - DeviceManager deviceManager = Main.getInjector().getInstance(DeviceManager.class); - - return ReportUtils.detectTripsAndStops( - identityManager, deviceManager, Context.getDataManager().getPositions(deviceId, from, to), - Context.getTripsConfig(), ignoreOdometer, StopReport.class); - } - - public static Collection<StopReport> getObjects( - long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to) throws SQLException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<StopReport> result = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - result.addAll(detectStops(deviceId, from, to)); - } - return result; - } - - public static void getExcel( - OutputStream outputStream, long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to) throws SQLException, IOException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<DeviceReport> devicesStops = new ArrayList<>(); - ArrayList<String> sheetNames = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - Collection<StopReport> stops = detectStops(deviceId, from, to); - DeviceReport deviceStops = new DeviceReport(); - Device device = Context.getIdentityManager().getById(deviceId); - deviceStops.setDeviceName(device.getName()); - sheetNames.add(WorkbookUtil.createSafeSheetName(deviceStops.getDeviceName())); - if (device.getGroupId() != 0) { - Group group = Context.getGroupsManager().getById(device.getGroupId()); - if (group != null) { - deviceStops.setGroupName(group.getName()); - } - } - deviceStops.setObjects(stops); - devicesStops.add(deviceStops); - } - String templatePath = Context.getConfig().getString("report.templatesPath", - "templates/export/"); - try (InputStream inputStream = new FileInputStream(templatePath + "/stops.xlsx")) { - org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); - jxlsContext.putVar("devices", devicesStops); - jxlsContext.putVar("sheetNames", sheetNames); - jxlsContext.putVar("from", from); - jxlsContext.putVar("to", to); - ReportUtils.processTemplateWithSheets(inputStream, outputStream, jxlsContext); - } - } - -} diff --git a/src/main/java/org/traccar/reports/StopsReportProvider.java b/src/main/java/org/traccar/reports/StopsReportProvider.java new file mode 100644 index 000000000..ec3fd2215 --- /dev/null +++ b/src/main/java/org/traccar/reports/StopsReportProvider.java @@ -0,0 +1,110 @@ +/* + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports; + +import org.apache.poi.ss.util.WorkbookUtil; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.reports.common.ReportUtils; +import org.traccar.reports.model.DeviceReportSection; +import org.traccar.reports.model.StopReportItem; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +public class StopsReportProvider { + + private final Config config; + private final ReportUtils reportUtils; + private final Storage storage; + + @Inject + public StopsReportProvider(Config config, ReportUtils reportUtils, Storage storage) { + this.config = config; + this.reportUtils = reportUtils; + this.storage = storage; + } + + private Collection<StopReportItem> detectStops(Device device, Date from, Date to) throws StorageException { + boolean ignoreOdometer = config.getBoolean(Keys.REPORT_IGNORE_ODOMETER); + var positions = PositionUtil.getPositions(storage, device.getId(), from, to); + return reportUtils.detectTripsAndStops(device, positions, ignoreOdometer, StopReportItem.class); + } + + public Collection<StopReportItem> getObjects( + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<StopReportItem> result = new ArrayList<>(); + for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) { + result.addAll(detectStops(device, from, to)); + } + return result; + } + + public void getExcel( + OutputStream outputStream, long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException, IOException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<DeviceReportSection> devicesStops = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) { + Collection<StopReportItem> stops = detectStops(device, from, to); + DeviceReportSection deviceStops = new DeviceReportSection(); + deviceStops.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(deviceStops.getDeviceName())); + if (device.getGroupId() > 0) { + Group group = storage.getObject(Group.class, new Request( + new Columns.All(), new Condition.Equals("id", device.getGroupId()))); + if (group != null) { + deviceStops.setGroupName(group.getName()); + } + } + deviceStops.setObjects(stops); + devicesStops.add(deviceStops); + } + + File file = Paths.get(config.getString(Keys.TEMPLATES_ROOT), "export", "stops.xlsx").toFile(); + try (InputStream inputStream = new FileInputStream(file)) { + var context = reportUtils.initializeContext(userId); + context.putVar("devices", devicesStops); + context.putVar("sheetNames", sheetNames); + context.putVar("from", from); + context.putVar("to", to); + reportUtils.processTemplateWithSheets(inputStream, outputStream, context); + } + } + +} diff --git a/src/main/java/org/traccar/reports/Summary.java b/src/main/java/org/traccar/reports/SummaryReportProvider.java index d576ac1e6..9415f3e81 100644 --- a/src/main/java/org/traccar/reports/Summary.java +++ b/src/main/java/org/traccar/reports/SummaryReportProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,31 +16,52 @@ */ package org.traccar.reports; +import org.jxls.util.JxlsHelper; +import org.traccar.api.security.PermissionsService; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.UnitsConverter; +import org.traccar.helper.model.PositionUtil; +import org.traccar.helper.model.UserUtil; +import org.traccar.model.Device; +import org.traccar.model.Position; +import org.traccar.reports.common.ReportUtils; +import org.traccar.reports.model.SummaryReportItem; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; + +import javax.inject.Inject; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.sql.SQLException; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; -import org.jxls.util.JxlsHelper; -import org.traccar.Context; -import org.traccar.helper.UnitsConverter; -import org.traccar.model.Position; -import org.traccar.reports.model.SummaryReport; +public class SummaryReportProvider { -public final class Summary { + private final Config config; + private final ReportUtils reportUtils; + private final PermissionsService permissionsService; + private final Storage storage; - private Summary() { + @Inject + public SummaryReportProvider( + Config config, ReportUtils reportUtils, PermissionsService permissionsService, Storage storage) { + this.config = config; + this.reportUtils = reportUtils; + this.permissionsService = permissionsService; + this.storage = storage; } - private static SummaryReport calculateSummaryResult(long deviceId, Collection<Position> positions) { - SummaryReport result = new SummaryReport(); - result.setDeviceId(deviceId); - result.setDeviceName(Context.getIdentityManager().getById(deviceId).getName()); + private SummaryReportItem calculateSummaryResult(Device device, Collection<Position> positions) { + SummaryReportItem result = new SummaryReportItem(); + result.setDeviceId(device.getId()); + result.setDeviceName(device.getName()); if (positions != null && !positions.isEmpty()) { Position firstPosition = null; Position previousPosition = null; @@ -53,14 +74,12 @@ public final class Summary { result.setMaxSpeed(position.getSpeed()); } } - boolean ignoreOdometer = Context.getDeviceManager() - .lookupAttributeBoolean(deviceId, "report.ignoreOdometer", false, false, true); - result.setDistance(ReportUtils.calculateDistance(firstPosition, previousPosition, !ignoreOdometer)); - result.setSpentFuel(ReportUtils.calculateFuel(firstPosition, previousPosition)); + boolean ignoreOdometer = config.getBoolean(Keys.REPORT_IGNORE_ODOMETER); + result.setDistance(PositionUtil.calculateDistance(firstPosition, previousPosition, !ignoreOdometer)); + result.setSpentFuel(reportUtils.calculateFuel(firstPosition, previousPosition)); long durationMilliseconds; - if (firstPosition.getAttributes().containsKey(Position.KEY_HOURS) - && previousPosition.getAttributes().containsKey(Position.KEY_HOURS)) { + if (firstPosition.hasAttribute(Position.KEY_HOURS) && previousPosition.hasAttribute(Position.KEY_HOURS)) { durationMilliseconds = previousPosition.getLong(Position.KEY_HOURS) - firstPosition.getLong(Position.KEY_HOURS); result.setEngineHours(durationMilliseconds); @@ -90,62 +109,67 @@ public final class Summary { return result; } - private static int getDay(long userId, Date date) { - Calendar calendar = Calendar.getInstance(ReportUtils.getTimezone(userId)); + private int getDay(long userId, Date date) throws StorageException { + Calendar calendar = Calendar.getInstance(UserUtil.getTimezone( + permissionsService.getServer(), permissionsService.getUser(userId))); calendar.setTime(date); return calendar.get(Calendar.DAY_OF_MONTH); } - 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)); + private Collection<SummaryReportItem> calculateSummaryResults( + long userId, Device device, Date from, Date to, boolean daily) throws StorageException { - ArrayList<SummaryReport> results = new ArrayList<>(); + var positions = PositionUtil.getPositions(storage, device.getId(), from, to); + var results = new ArrayList<SummaryReportItem>(); if (daily && !positions.isEmpty()) { int startIndex = 0; int startDay = getDay(userId, positions.iterator().next().getFixTime()); for (int i = 0; i < positions.size(); i++) { int currentDay = getDay(userId, positions.get(i).getFixTime()); if (currentDay != startDay) { - results.add(calculateSummaryResult(deviceId, positions.subList(startIndex, i))); + results.add(calculateSummaryResult(device, positions.subList(startIndex, i))); startIndex = i; startDay = currentDay; } } - results.add(calculateSummaryResult(deviceId, positions.subList(startIndex, positions.size()))); + results.add(calculateSummaryResult(device, positions.subList(startIndex, positions.size()))); } else { - results.add(calculateSummaryResult(deviceId, positions)); + results.add(calculateSummaryResult(device, positions)); } return results; } - public static Collection<SummaryReport> getObjects(long userId, Collection<Long> deviceIds, - 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.addAll(calculateSummaryResults(userId, deviceId, from, to, daily)); + public Collection<SummaryReportItem> getObjects( + long userId, Collection<Long> deviceIds, + Collection<Long> groupIds, Date from, Date to, boolean daily) throws StorageException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<SummaryReportItem> result = new ArrayList<>(); + for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) { + Collection<SummaryReportItem> deviceResults = calculateSummaryResults(userId, device, from, to, daily); + for (SummaryReportItem summaryReport : deviceResults) { + if (summaryReport.getStartTime() != null && summaryReport.getEndTime() != null) { + result.add(summaryReport); + } + } } return result; } - public static void getExcel(OutputStream outputStream, + public void getExcel(OutputStream outputStream, long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to, boolean daily) throws SQLException, IOException { - ReportUtils.checkPeriodLimit(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")) { - org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); - jxlsContext.putVar("summaries", summaries); - jxlsContext.putVar("from", from); - jxlsContext.putVar("to", to); + Date from, Date to, boolean daily) throws StorageException, IOException { + Collection<SummaryReportItem> summaries = getObjects(userId, deviceIds, groupIds, from, to, daily); + + File file = Paths.get(config.getString(Keys.TEMPLATES_ROOT), "export", "summary.xlsx").toFile(); + try (InputStream inputStream = new FileInputStream(file)) { + var context = reportUtils.initializeContext(userId); + context.putVar("summaries", summaries); + context.putVar("from", from); + context.putVar("to", to); JxlsHelper.getInstance().setUseFastFormulaProcessor(false) - .processTemplate(inputStream, outputStream, jxlsContext); + .processTemplate(inputStream, outputStream, context); } } } diff --git a/src/main/java/org/traccar/reports/Trips.java b/src/main/java/org/traccar/reports/Trips.java deleted file mode 100644 index 7c0cd6921..000000000 --- a/src/main/java/org/traccar/reports/Trips.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2016 Anton Tananaev (anton@traccar.org) - * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.reports; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; - -import org.apache.poi.ss.util.WorkbookUtil; -import org.traccar.Context; -import org.traccar.Main; -import org.traccar.database.DeviceManager; -import org.traccar.database.IdentityManager; -import org.traccar.model.Device; -import org.traccar.model.Group; -import org.traccar.reports.model.DeviceReport; -import org.traccar.reports.model.TripReport; - -public final class Trips { - - private Trips() { - } - - private static Collection<TripReport> detectTrips(long deviceId, Date from, Date to) throws SQLException { - boolean ignoreOdometer = Context.getDeviceManager() - .lookupAttributeBoolean(deviceId, "report.ignoreOdometer", false, false, true); - - IdentityManager identityManager = Main.getInjector().getInstance(IdentityManager.class); - DeviceManager deviceManager = Main.getInjector().getInstance(DeviceManager.class); - - return ReportUtils.detectTripsAndStops( - identityManager, deviceManager, Context.getDataManager().getPositions(deviceId, from, to), - Context.getTripsConfig(), ignoreOdometer, TripReport.class); - } - - public static Collection<TripReport> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to) throws SQLException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<TripReport> result = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - result.addAll(detectTrips(deviceId, from, to)); - } - return result; - } - - public static void getExcel(OutputStream outputStream, - long userId, Collection<Long> deviceIds, Collection<Long> groupIds, - Date from, Date to) throws SQLException, IOException { - ReportUtils.checkPeriodLimit(from, to); - ArrayList<DeviceReport> devicesTrips = new ArrayList<>(); - ArrayList<String> sheetNames = new ArrayList<>(); - for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { - Context.getPermissionsManager().checkDevice(userId, deviceId); - Collection<TripReport> trips = detectTrips(deviceId, from, to); - DeviceReport deviceTrips = new DeviceReport(); - Device device = Context.getIdentityManager().getById(deviceId); - deviceTrips.setDeviceName(device.getName()); - sheetNames.add(WorkbookUtil.createSafeSheetName(deviceTrips.getDeviceName())); - if (device.getGroupId() != 0) { - Group group = Context.getGroupsManager().getById(device.getGroupId()); - if (group != null) { - deviceTrips.setGroupName(group.getName()); - } - } - deviceTrips.setObjects(trips); - devicesTrips.add(deviceTrips); - } - String templatePath = Context.getConfig().getString("report.templatesPath", - "templates/export/"); - try (InputStream inputStream = new FileInputStream(templatePath + "/trips.xlsx")) { - org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); - jxlsContext.putVar("devices", devicesTrips); - jxlsContext.putVar("sheetNames", sheetNames); - jxlsContext.putVar("from", from); - jxlsContext.putVar("to", to); - ReportUtils.processTemplateWithSheets(inputStream, outputStream, jxlsContext); - } - } - -} diff --git a/src/main/java/org/traccar/reports/TripsReportProvider.java b/src/main/java/org/traccar/reports/TripsReportProvider.java new file mode 100644 index 000000000..265811354 --- /dev/null +++ b/src/main/java/org/traccar/reports/TripsReportProvider.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports; + +import org.apache.poi.ss.util.WorkbookUtil; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.reports.common.ReportUtils; +import org.traccar.reports.model.DeviceReportSection; +import org.traccar.reports.model.TripReportItem; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +public class TripsReportProvider { + + private final Config config; + private final ReportUtils reportUtils; + private final Storage storage; + + @Inject + public TripsReportProvider(Config config, ReportUtils reportUtils, Storage storage) { + this.config = config; + this.reportUtils = reportUtils; + this.storage = storage; + } + + private Collection<TripReportItem> detectTrips(Device device, Date from, Date to) throws StorageException { + boolean ignoreOdometer = config.getBoolean(Keys.REPORT_IGNORE_ODOMETER); + var positions = PositionUtil.getPositions(storage, device.getId(), from, to); + return reportUtils.detectTripsAndStops(device, positions, ignoreOdometer, TripReportItem.class); + } + + public Collection<TripReportItem> getObjects( + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<TripReportItem> result = new ArrayList<>(); + for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) { + result.addAll(detectTrips(device, from, to)); + } + return result; + } + + public void getExcel(OutputStream outputStream, + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws StorageException, IOException { + reportUtils.checkPeriodLimit(from, to); + + ArrayList<DeviceReportSection> devicesTrips = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + for (Device device: reportUtils.getAccessibleDevices(userId, deviceIds, groupIds)) { + Collection<TripReportItem> trips = detectTrips(device, from, to); + DeviceReportSection deviceTrips = new DeviceReportSection(); + deviceTrips.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(deviceTrips.getDeviceName())); + if (device.getGroupId() > 0) { + Group group = storage.getObject(Group.class, new Request( + new Columns.All(), new Condition.Equals("id", device.getGroupId()))); + if (group != null) { + deviceTrips.setGroupName(group.getName()); + } + } + deviceTrips.setObjects(trips); + devicesTrips.add(deviceTrips); + } + + File file = Paths.get(config.getString(Keys.TEMPLATES_ROOT), "export", "trips.xlsx").toFile(); + try (InputStream inputStream = new FileInputStream(file)) { + var context = reportUtils.initializeContext(userId); + context.putVar("devices", devicesTrips); + context.putVar("sheetNames", sheetNames); + context.putVar("from", from); + context.putVar("to", to); + reportUtils.processTemplateWithSheets(inputStream, outputStream, context); + } + } + +} diff --git a/src/main/java/org/traccar/reports/common/ReportUtils.java b/src/main/java/org/traccar/reports/common/ReportUtils.java new file mode 100644 index 000000000..a7c420095 --- /dev/null +++ b/src/main/java/org/traccar/reports/common/ReportUtils.java @@ -0,0 +1,399 @@ +/* + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports.common; + +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.tools.generic.DateTool; +import org.apache.velocity.tools.generic.NumberTool; +import org.jxls.area.Area; +import org.jxls.builder.xls.XlsCommentAreaBuilder; +import org.jxls.common.CellRef; +import org.jxls.formula.StandardFormulaProcessor; +import org.jxls.transform.Transformer; +import org.jxls.transform.poi.PoiTransformer; +import org.jxls.util.TransformerFactory; +import org.traccar.api.security.PermissionsService; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.geocoder.Geocoder; +import org.traccar.helper.UnitsConverter; +import org.traccar.helper.model.PositionUtil; +import org.traccar.helper.model.UserUtil; +import org.traccar.model.BaseModel; +import org.traccar.model.Device; +import org.traccar.model.Driver; +import org.traccar.model.Group; +import org.traccar.model.Position; +import org.traccar.model.User; +import org.traccar.reports.model.BaseReportItem; +import org.traccar.reports.model.StopReportItem; +import org.traccar.reports.model.TripReportItem; +import org.traccar.session.state.MotionProcessor; +import org.traccar.session.state.MotionState; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.stream.Collectors; + +@Singleton +public class ReportUtils { + + private final Config config; + private final Storage storage; + private final PermissionsService permissionsService; + private final TripsConfig tripsConfig; + private final VelocityEngine velocityEngine; + private final Geocoder geocoder; + + @Inject + public ReportUtils( + Config config, Storage storage, PermissionsService permissionsService, + TripsConfig tripsConfig, VelocityEngine velocityEngine, @Nullable Geocoder geocoder) { + this.config = config; + this.storage = storage; + this.permissionsService = permissionsService; + this.tripsConfig = tripsConfig; + this.velocityEngine = velocityEngine; + this.geocoder = geocoder; + } + + public <T extends BaseModel> T getObject( + long userId, Class<T> clazz, long objectId) throws StorageException, SecurityException { + return storage.getObject(clazz, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("id", objectId), + new Condition.Permission(User.class, userId, clazz)))); + } + + public void checkPeriodLimit(Date from, Date to) { + long limit = config.getLong(Keys.REPORT_PERIOD_LIMIT) * 1000; + if (limit > 0 && to.getTime() - from.getTime() > limit) { + throw new IllegalArgumentException("Time period exceeds the limit"); + } + } + + public Collection<Device> getAccessibleDevices( + long userId, Collection<Long> deviceIds, Collection<Long> groupIds) throws StorageException { + + var devices = storage.getObjects(Device.class, new Request( + new Columns.All(), + new Condition.Permission(User.class, userId, Device.class))); + var deviceById = devices.stream() + .collect(Collectors.toUnmodifiableMap(Device::getId, x -> x)); + var devicesByGroup = devices.stream() + .filter(x -> x.getGroupId() > 0) + .collect(Collectors.groupingBy(Device::getGroupId)); + + var groups = storage.getObjects(Group.class, new Request( + new Columns.All(), + new Condition.Permission(User.class, userId, Group.class))); + var groupsByGroup = groups.stream() + .filter(x -> x.getGroupId() > 0) + .collect(Collectors.groupingBy(Group::getGroupId)); + + var results = deviceIds.stream() + .map(deviceById::get) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + var groupQueue = new LinkedList<>(groupIds); + while (!groupQueue.isEmpty()) { + long groupId = groupQueue.pop(); + results.addAll(devicesByGroup.getOrDefault(groupId, Collections.emptyList())); + groupQueue.addAll(groupsByGroup.getOrDefault(groupId, Collections.emptyList()) + .stream().map(Group::getId).collect(Collectors.toUnmodifiableList())); + } + + return results; + } + + public double calculateFuel(Position firstPosition, Position lastPosition) { + + if (firstPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null + && lastPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null) { + + BigDecimal value = BigDecimal.valueOf(firstPosition.getDouble(Position.KEY_FUEL_LEVEL) + - lastPosition.getDouble(Position.KEY_FUEL_LEVEL)); + return value.setScale(1, RoundingMode.HALF_EVEN).doubleValue(); + } + return 0; + } + + public String findDriver(Position firstPosition, Position lastPosition) { + if (firstPosition.hasAttribute(Position.KEY_DRIVER_UNIQUE_ID)) { + return firstPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); + } else if (lastPosition.hasAttribute(Position.KEY_DRIVER_UNIQUE_ID)) { + return lastPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); + } + return null; + } + + public String findDriverName(String driverUniqueId) throws StorageException { + if (driverUniqueId != null) { + Driver driver = storage.getObject(Driver.class, new Request( + new Columns.All(), + new Condition.Equals("uniqueId", driverUniqueId))); + if (driver != null) { + return driver.getName(); + } + } + return null; + } + + public org.jxls.common.Context initializeContext(long userId) throws StorageException { + var server = permissionsService.getServer(); + var user = permissionsService.getUser(userId); + var context = PoiTransformer.createInitialContext(); + context.putVar("distanceUnit", UserUtil.getDistanceUnit(server, user)); + context.putVar("speedUnit", UserUtil.getSpeedUnit(server, user)); + context.putVar("volumeUnit", UserUtil.getVolumeUnit(server, user)); + context.putVar("webUrl", velocityEngine.getProperty("web.url")); + context.putVar("dateTool", new DateTool()); + context.putVar("numberTool", new NumberTool()); + context.putVar("timezone", UserUtil.getTimezone(server, user)); + context.putVar("locale", Locale.getDefault()); + context.putVar("bracketsRegex", "[\\{\\}\"]"); + return context; + } + + public void processTemplateWithSheets( + InputStream templateStream, OutputStream targetStream, org.jxls.common.Context context) throws IOException { + + Transformer transformer = TransformerFactory.createTransformer(templateStream, targetStream); + List<Area> xlsAreas = new XlsCommentAreaBuilder(transformer).build(); + for (Area xlsArea : xlsAreas) { + xlsArea.applyAt(new CellRef(xlsArea.getStartCellRef().getCellName()), context); + xlsArea.setFormulaProcessor(new StandardFormulaProcessor()); + xlsArea.processFormulas(); + } + transformer.deleteSheet(xlsAreas.get(0).getStartCellRef().getSheetName()); + transformer.write(); + } + + private TripReportItem calculateTrip( + Device device, ArrayList<Position> positions, int startIndex, int endIndex, + boolean ignoreOdometer) throws StorageException { + + Position startTrip = positions.get(startIndex); + Position endTrip = positions.get(endIndex); + + double speedMax = 0; + for (int i = startIndex; i <= endIndex; i++) { + double speed = positions.get(i).getSpeed(); + if (speed > speedMax) { + speedMax = speed; + } + } + + TripReportItem trip = new TripReportItem(); + + long tripDuration = endTrip.getFixTime().getTime() - startTrip.getFixTime().getTime(); + long deviceId = startTrip.getDeviceId(); + trip.setDeviceId(deviceId); + trip.setDeviceName(device.getName()); + + trip.setStartPositionId(startTrip.getId()); + trip.setStartLat(startTrip.getLatitude()); + trip.setStartLon(startTrip.getLongitude()); + trip.setStartTime(startTrip.getFixTime()); + String startAddress = startTrip.getAddress(); + if (startAddress == null && geocoder != null && config.getBoolean(Keys.GEOCODER_ON_REQUEST)) { + startAddress = geocoder.getAddress(startTrip.getLatitude(), startTrip.getLongitude(), null); + } + trip.setStartAddress(startAddress); + + trip.setEndPositionId(endTrip.getId()); + trip.setEndLat(endTrip.getLatitude()); + trip.setEndLon(endTrip.getLongitude()); + trip.setEndTime(endTrip.getFixTime()); + String endAddress = endTrip.getAddress(); + if (endAddress == null && geocoder != null && config.getBoolean(Keys.GEOCODER_ON_REQUEST)) { + endAddress = geocoder.getAddress(endTrip.getLatitude(), endTrip.getLongitude(), null); + } + trip.setEndAddress(endAddress); + + trip.setDistance(PositionUtil.calculateDistance(startTrip, endTrip, !ignoreOdometer)); + trip.setDuration(tripDuration); + if (tripDuration > 0) { + trip.setAverageSpeed(UnitsConverter.knotsFromMps(trip.getDistance() * 1000 / tripDuration)); + } + trip.setMaxSpeed(speedMax); + trip.setSpentFuel(calculateFuel(startTrip, endTrip)); + + trip.setDriverUniqueId(findDriver(startTrip, endTrip)); + trip.setDriverName(findDriverName(trip.getDriverUniqueId())); + + if (!ignoreOdometer + && startTrip.getDouble(Position.KEY_ODOMETER) != 0 + && endTrip.getDouble(Position.KEY_ODOMETER) != 0) { + trip.setStartOdometer(startTrip.getDouble(Position.KEY_ODOMETER)); + trip.setEndOdometer(endTrip.getDouble(Position.KEY_ODOMETER)); + } else { + trip.setStartOdometer(startTrip.getDouble(Position.KEY_TOTAL_DISTANCE)); + trip.setEndOdometer(endTrip.getDouble(Position.KEY_TOTAL_DISTANCE)); + } + + return trip; + } + + private StopReportItem calculateStop( + Device device, ArrayList<Position> positions, int startIndex, int endIndex, boolean ignoreOdometer) { + + Position startStop = positions.get(startIndex); + Position endStop = positions.get(endIndex); + + StopReportItem stop = new StopReportItem(); + + long deviceId = startStop.getDeviceId(); + stop.setDeviceId(deviceId); + stop.setDeviceName(device.getName()); + + stop.setPositionId(startStop.getId()); + stop.setLatitude(startStop.getLatitude()); + stop.setLongitude(startStop.getLongitude()); + stop.setStartTime(startStop.getFixTime()); + String address = startStop.getAddress(); + if (address == null && geocoder != null && config.getBoolean(Keys.GEOCODER_ON_REQUEST)) { + address = geocoder.getAddress(stop.getLatitude(), stop.getLongitude(), null); + } + stop.setAddress(address); + + stop.setEndTime(endStop.getFixTime()); + + long stopDuration = endStop.getFixTime().getTime() - startStop.getFixTime().getTime(); + stop.setDuration(stopDuration); + stop.setSpentFuel(calculateFuel(startStop, endStop)); + + if (startStop.hasAttribute(Position.KEY_HOURS) && endStop.hasAttribute(Position.KEY_HOURS)) { + stop.setEngineHours(endStop.getLong(Position.KEY_HOURS) - startStop.getLong(Position.KEY_HOURS)); + } + + if (!ignoreOdometer + && startStop.getDouble(Position.KEY_ODOMETER) != 0 + && endStop.getDouble(Position.KEY_ODOMETER) != 0) { + stop.setStartOdometer(startStop.getDouble(Position.KEY_ODOMETER)); + stop.setEndOdometer(endStop.getDouble(Position.KEY_ODOMETER)); + } else { + stop.setStartOdometer(startStop.getDouble(Position.KEY_TOTAL_DISTANCE)); + stop.setEndOdometer(endStop.getDouble(Position.KEY_TOTAL_DISTANCE)); + } + + return stop; + + } + + @SuppressWarnings("unchecked") + private <T extends BaseReportItem> T calculateTripOrStop( + Device device, ArrayList<Position> positions, int startIndex, int endIndex, + boolean ignoreOdometer, Class<T> reportClass) throws StorageException { + + if (reportClass.equals(TripReportItem.class)) { + return (T) calculateTrip(device, positions, startIndex, endIndex, ignoreOdometer); + } else { + return (T) calculateStop(device, positions, startIndex, endIndex, ignoreOdometer); + } + } + + private boolean isMoving(ArrayList<Position> positions, int index, TripsConfig tripsConfig) { + if (tripsConfig.getMinimalNoDataDuration() > 0) { + boolean beforeGap = index < positions.size() - 1 + && positions.get(index + 1).getFixTime().getTime() - positions.get(index).getFixTime().getTime() + >= tripsConfig.getMinimalNoDataDuration(); + boolean afterGap = index > 0 + && positions.get(index).getFixTime().getTime() - positions.get(index - 1).getFixTime().getTime() + >= tripsConfig.getMinimalNoDataDuration(); + if (beforeGap || afterGap) { + return false; + } + } + return positions.get(index).getBoolean(Position.KEY_MOTION); + } + + public <T extends BaseReportItem> Collection<T> detectTripsAndStops( + Device device, Collection<Position> positionCollection, boolean ignoreOdometer, + Class<T> reportClass) throws StorageException { + + Collection<T> result = new ArrayList<>(); + + ArrayList<Position> positions = new ArrayList<>(positionCollection); + if (!positions.isEmpty()) { + boolean trips = reportClass.equals(TripReportItem.class); + + MotionState motionState = new MotionState(); + boolean initialValue = isMoving(positions, 0, tripsConfig); + motionState.setMotionStreak(initialValue); + motionState.setMotionState(initialValue); + + boolean detected = trips == motionState.getMotionState(); + int startEventIndex = detected ? 0 : -1; + int startNoEventIndex = -1; + for (int i = 0; i < positions.size(); i++) { + boolean motion = isMoving(positions, i, tripsConfig); + if (motionState.getMotionState() != motion) { + if (motion == trips) { + startEventIndex = detected ? startEventIndex : i; + startNoEventIndex = -1; + } else { + startNoEventIndex = i; + } + } + + MotionProcessor.updateState(motionState, positions.get(i), motion, tripsConfig); + if (motionState.getEvent() != null) { + if (motion == trips) { + detected = true; + startNoEventIndex = -1; + } else if (startEventIndex >= 0 && startNoEventIndex >= 0) { + result.add(calculateTripOrStop( + device, positions, startEventIndex, startNoEventIndex, ignoreOdometer, reportClass)); + detected = false; + startEventIndex = -1; + startNoEventIndex = -1; + } + } + } + if (startEventIndex >= 0 && startEventIndex < positions.size() - 1) { + int endIndex = startNoEventIndex >= 0 ? startNoEventIndex : positions.size() - 1; + result.add(calculateTripOrStop( + device, positions, startEventIndex, endIndex, ignoreOdometer, reportClass)); + } + } + + return result; + } + +} diff --git a/src/main/java/org/traccar/reports/model/TripsConfig.java b/src/main/java/org/traccar/reports/common/TripsConfig.java index 0f0c615d3..52db97b74 100644 --- a/src/main/java/org/traccar/reports/model/TripsConfig.java +++ b/src/main/java/org/traccar/reports/common/TripsConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,14 +14,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.reports.model; +package org.traccar.reports.common; +import org.traccar.config.Config; +import org.traccar.config.Keys; + +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton public class TripsConfig { - public TripsConfig() { + @Inject + public TripsConfig(Config config) { + this( + config.getLong(Keys.REPORT_TRIP_MINIMAL_TRIP_DISTANCE), + config.getLong(Keys.REPORT_TRIP_MINIMAL_TRIP_DURATION) * 1000, + config.getLong(Keys.REPORT_TRIP_MINIMAL_PARKING_DURATION) * 1000, + config.getLong(Keys.REPORT_TRIP_MINIMAL_NO_DATA_DURATION) * 1000, + config.getBoolean(Keys.REPORT_TRIP_USE_IGNITION), + config.getBoolean(Keys.EVENT_MOTION_PROCESS_INVALID_POSITIONS), + config.getDouble(Keys.EVENT_MOTION_SPEED_THRESHOLD)); } - public TripsConfig(double minimalTripDistance, long minimalTripDuration, long minimalParkingDuration, + public TripsConfig( + double minimalTripDistance, long minimalTripDuration, long minimalParkingDuration, long minimalNoDataDuration, boolean useIgnition, boolean processInvalidPositions, double speedThreshold) { this.minimalTripDistance = minimalTripDistance; this.minimalTripDuration = minimalTripDuration; @@ -32,74 +49,46 @@ public class TripsConfig { this.speedThreshold = speedThreshold; } - private double minimalTripDistance; + private final double minimalTripDistance; public double getMinimalTripDistance() { return minimalTripDistance; } - public void setMinimalTripDistance(double minimalTripDistance) { - this.minimalTripDistance = minimalTripDistance; - } - - private long minimalTripDuration; + private final long minimalTripDuration; public long getMinimalTripDuration() { return minimalTripDuration; } - public void setMinimalTripDuration(long minimalTripDuration) { - this.minimalTripDuration = minimalTripDuration; - } - - private long minimalParkingDuration; + private final long minimalParkingDuration; public long getMinimalParkingDuration() { return minimalParkingDuration; } - public void setMinimalParkingDuration(long minimalParkingDuration) { - this.minimalParkingDuration = minimalParkingDuration; - } - - private long minimalNoDataDuration; + private final long minimalNoDataDuration; public long getMinimalNoDataDuration() { return minimalNoDataDuration; } - public void setMinimalNoDataDuration(long minimalNoDataDuration) { - this.minimalNoDataDuration = minimalNoDataDuration; - } - - private boolean useIgnition; + private final boolean useIgnition; public boolean getUseIgnition() { return useIgnition; } - public void setUseIgnition(boolean useIgnition) { - this.useIgnition = useIgnition; - } - - private boolean processInvalidPositions; + private final boolean processInvalidPositions; public boolean getProcessInvalidPositions() { return processInvalidPositions; } - public void setProcessInvalidPositions(boolean processInvalidPositions) { - this.processInvalidPositions = processInvalidPositions; - } - - private double speedThreshold; + private final double speedThreshold; public double getSpeedThreshold() { return speedThreshold; } - public void setSpeedThreshold(double speedThreshold) { - this.speedThreshold = speedThreshold; - } - } diff --git a/src/main/java/org/traccar/reports/model/BaseReport.java b/src/main/java/org/traccar/reports/model/BaseReportItem.java index 928c0557d..6e270dfe3 100644 --- a/src/main/java/org/traccar/reports/model/BaseReport.java +++ b/src/main/java/org/traccar/reports/model/BaseReportItem.java @@ -18,7 +18,7 @@ package org.traccar.reports.model; import java.util.Date; -public class BaseReport { +public class BaseReportItem { private long deviceId; diff --git a/src/main/java/org/traccar/reports/model/DeviceReport.java b/src/main/java/org/traccar/reports/model/DeviceReportSection.java index 932753d15..ffc4d774f 100644 --- a/src/main/java/org/traccar/reports/model/DeviceReport.java +++ b/src/main/java/org/traccar/reports/model/DeviceReportSection.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -public class DeviceReport { +public class DeviceReportSection { private String deviceName; diff --git a/src/main/java/org/traccar/reports/model/StopReport.java b/src/main/java/org/traccar/reports/model/StopReportItem.java index e20f2c503..3c35bdc21 100644 --- a/src/main/java/org/traccar/reports/model/StopReport.java +++ b/src/main/java/org/traccar/reports/model/StopReportItem.java @@ -16,7 +16,7 @@ */ package org.traccar.reports.model; -public class StopReport extends BaseReport { +public class StopReportItem extends BaseReportItem { private long positionId; diff --git a/src/main/java/org/traccar/reports/model/SummaryReport.java b/src/main/java/org/traccar/reports/model/SummaryReportItem.java index 886f8b9e2..44a15cf1d 100644 --- a/src/main/java/org/traccar/reports/model/SummaryReport.java +++ b/src/main/java/org/traccar/reports/model/SummaryReportItem.java @@ -16,7 +16,7 @@ */ package org.traccar.reports.model; -public class SummaryReport extends BaseReport { +public class SummaryReportItem extends BaseReportItem { private long engineHours; // milliseconds diff --git a/src/main/java/org/traccar/reports/model/TripReport.java b/src/main/java/org/traccar/reports/model/TripReportItem.java index 151c34bd5..332a34cca 100644 --- a/src/main/java/org/traccar/reports/model/TripReport.java +++ b/src/main/java/org/traccar/reports/model/TripReportItem.java @@ -16,7 +16,7 @@ */ package org.traccar.reports.model; -public class TripReport extends BaseReport { +public class TripReportItem extends BaseReportItem { private long startPositionId; diff --git a/src/main/java/org/traccar/schedule/ScheduleManager.java b/src/main/java/org/traccar/schedule/ScheduleManager.java index 5d5054100..6412a186a 100644 --- a/src/main/java/org/traccar/schedule/ScheduleManager.java +++ b/src/main/java/org/traccar/schedule/ScheduleManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,29 +15,39 @@ */ package org.traccar.schedule; +import com.google.inject.Injector; +import org.traccar.LifecycleObject; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -public class ScheduleManager { +@Singleton +public class ScheduleManager implements LifecycleObject { + private final Injector injector; private ScheduledExecutorService executor; - public void start() { + @Inject + public ScheduleManager(Injector injector) { + this.injector = injector; + } + @Override + public void start() { executor = Executors.newSingleThreadScheduledExecutor(); - - new TaskDeviceInactivityCheck().schedule(executor); - new TaskWebSocketKeepalive().schedule(executor); - + List.of(TaskDeviceInactivityCheck.class, TaskWebSocketKeepalive.class, TaskHealthCheck.class) + .forEach(task -> injector.getInstance(task).schedule(executor)); } + @Override public void stop() { - if (executor != null) { executor.shutdown(); executor = null; } - } } diff --git a/src/main/java/org/traccar/schedule/ScheduleTask.java b/src/main/java/org/traccar/schedule/ScheduleTask.java new file mode 100644 index 000000000..1b537213b --- /dev/null +++ b/src/main/java/org/traccar/schedule/ScheduleTask.java @@ -0,0 +1,22 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.schedule; + +import java.util.concurrent.ScheduledExecutorService; + +public interface ScheduleTask extends Runnable { + void schedule(ScheduledExecutorService executor); +} diff --git a/src/main/java/org/traccar/schedule/TaskDeviceInactivityCheck.java b/src/main/java/org/traccar/schedule/TaskDeviceInactivityCheck.java index 80641d7d4..57c64dc5b 100644 --- a/src/main/java/org/traccar/schedule/TaskDeviceInactivityCheck.java +++ b/src/main/java/org/traccar/schedule/TaskDeviceInactivityCheck.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,26 @@ */ package org.traccar.schedule; -import org.traccar.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.database.NotificationManager; import org.traccar.model.Device; import org.traccar.model.Event; import org.traccar.model.Position; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Request; +import javax.inject.Inject; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -public class TaskDeviceInactivityCheck implements Runnable { +public class TaskDeviceInactivityCheck implements ScheduleTask { + + private static final Logger LOGGER = LoggerFactory.getLogger(TaskDeviceInactivityCheck.class); public static final String ATTRIBUTE_DEVICE_INACTIVITY_START = "deviceInactivityStart"; public static final String ATTRIBUTE_DEVICE_INACTIVITY_PERIOD = "deviceInactivityPeriod"; @@ -33,6 +42,16 @@ public class TaskDeviceInactivityCheck implements Runnable { private static final long CHECK_PERIOD_MINUTES = 15; + private final Storage storage; + private final NotificationManager notificationManager; + + @Inject + public TaskDeviceInactivityCheck(Storage storage, NotificationManager notificationManager) { + this.storage = storage; + this.notificationManager = notificationManager; + } + + @Override public void schedule(ScheduledExecutorService executor) { executor.scheduleAtFixedRate(this, CHECK_PERIOD_MINUTES, CHECK_PERIOD_MINUTES, TimeUnit.MINUTES); } @@ -43,15 +62,20 @@ public class TaskDeviceInactivityCheck implements Runnable { 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); + + try { + for (Device device : storage.getObjects(Device.class, new Request(new Columns.All()))) { + 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); + } } + } catch (StorageException e) { + LOGGER.warn("Get devices error", e); } - Context.getNotificationManager().updateEvents(events); + notificationManager.updateEvents(events); } private boolean checkDevice(Device device, long currentTime, long checkPeriod) { diff --git a/src/main/java/org/traccar/api/HealthCheckService.java b/src/main/java/org/traccar/schedule/TaskHealthCheck.java index 0182cc358..a8c9873ce 100644 --- a/src/main/java/org/traccar/api/HealthCheckService.java +++ b/src/main/java/org/traccar/schedule/TaskHealthCheck.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,28 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.api; +package org.traccar.schedule; import com.sun.jna.Library; import com.sun.jna.Native; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; +import org.traccar.config.Config; import org.traccar.config.Keys; -import java.util.TimerTask; +import javax.inject.Inject; +import javax.ws.rs.client.Client; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; -public class HealthCheckService { +public class TaskHealthCheck implements ScheduleTask { - private static final Logger LOGGER = LoggerFactory.getLogger(HealthCheckService.class); + private static final Logger LOGGER = LoggerFactory.getLogger(TaskHealthCheck.class); + + private final Config config; + private final Client client; private SystemD systemD; private boolean enabled; private long period; - public HealthCheckService() { - if (!Context.getConfig().getBoolean(Keys.WEB_DISABLE_HEALTH_CHECK) + @Inject + public TaskHealthCheck(Config config, Client client) { + this.config = config; + this.client = client; + if (!config.getBoolean(Keys.WEB_DISABLE_HEALTH_CHECK) && System.getProperty("os.name").toLowerCase().startsWith("linux")) { try { systemD = Native.load("systemd", SystemD.class); @@ -52,36 +61,31 @@ public class HealthCheckService { } } - public boolean isEnabled() { - return enabled; - } - - public long getPeriod() { - return period; - } - private String getUrl() { - String address = Context.getConfig().getString(Keys.WEB_ADDRESS, "localhost"); - int port = Context.getConfig().getInteger(Keys.WEB_PORT); + String address = config.getString(Keys.WEB_ADDRESS, "localhost"); + int port = config.getInteger(Keys.WEB_PORT); return "http://" + address + ":" + port + "/api/server"; } - public TimerTask createTask() { - return new TimerTask() { - @Override - public void run() { - LOGGER.debug("Health check running"); - int status = Context.getClient().target(getUrl()).request().get().getStatus(); - if (status == 200) { - int result = systemD.sd_notify(0, "WATCHDOG=1"); - if (result < 0) { - LOGGER.warn("Health check notify error {}", result); - } - } else { - LOGGER.warn("Health check failed with status {}", status); - } + @Override + public void schedule(ScheduledExecutorService executor) { + if (enabled) { + executor.scheduleAtFixedRate(this, period, period, TimeUnit.MILLISECONDS); + } + } + + @Override + public void run() { + LOGGER.debug("Health check running"); + int status = client.target(getUrl()).request().get().getStatus(); + if (status == 200) { + int result = systemD.sd_notify(0, "WATCHDOG=1"); + if (result < 0) { + LOGGER.warn("Health check notify error {}", result); } - }; + } else { + LOGGER.warn("Health check failed with status {}", status); + } } interface SystemD extends Library { diff --git a/src/main/java/org/traccar/schedule/TaskWebSocketKeepalive.java b/src/main/java/org/traccar/schedule/TaskWebSocketKeepalive.java index 953b0efea..e6c2e8b6d 100644 --- a/src/main/java/org/traccar/schedule/TaskWebSocketKeepalive.java +++ b/src/main/java/org/traccar/schedule/TaskWebSocketKeepalive.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,22 +15,31 @@ */ package org.traccar.schedule; -import org.traccar.Context; +import org.traccar.session.ConnectionManager; +import javax.inject.Inject; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -public class TaskWebSocketKeepalive implements Runnable { +public class TaskWebSocketKeepalive implements ScheduleTask { private static final long PERIOD_SECONDS = 55; + private final ConnectionManager connectionManager; + + @Inject + public TaskWebSocketKeepalive(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + @Override public void schedule(ScheduledExecutorService executor) { executor.scheduleAtFixedRate(this, PERIOD_SECONDS, PERIOD_SECONDS, TimeUnit.SECONDS); } @Override public void run() { - Context.getConnectionManager().sendKeepalive(); + connectionManager.sendKeepalive(); } } diff --git a/src/main/java/org/traccar/session/ConnectionManager.java b/src/main/java/org/traccar/session/ConnectionManager.java new file mode 100644 index 000000000..37a42d827 --- /dev/null +++ b/src/main/java/org/traccar/session/ConnectionManager.java @@ -0,0 +1,374 @@ +/* + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.session; + +import io.netty.channel.Channel; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Protocol; +import org.traccar.broadcast.BroadcastInterface; +import org.traccar.broadcast.BroadcastService; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.database.DeviceLookupService; +import org.traccar.database.NotificationManager; +import org.traccar.model.BaseModel; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.model.User; +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Singleton +public class ConnectionManager implements BroadcastInterface { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class); + + private final long deviceTimeout; + + private final Map<Long, DeviceSession> sessionsByDeviceId = new ConcurrentHashMap<>(); + private final Map<Endpoint, Map<String, DeviceSession>> sessionsByEndpoint = new ConcurrentHashMap<>(); + + private final Config config; + private final CacheManager cacheManager; + private final Storage storage; + private final NotificationManager notificationManager; + private final Timer timer; + private final BroadcastService broadcastService; + private final DeviceLookupService deviceLookupService; + + private final Map<Long, Set<UpdateListener>> listeners = new HashMap<>(); + private final Map<Long, Set<Long>> userDevices = new HashMap<>(); + private final Map<Long, Set<Long>> deviceUsers = new HashMap<>(); + + private final Map<Long, Timeout> timeouts = new ConcurrentHashMap<>(); + + @Inject + public ConnectionManager( + Config config, CacheManager cacheManager, Storage storage, + NotificationManager notificationManager, Timer timer, BroadcastService broadcastService, + DeviceLookupService deviceLookupService) { + this.config = config; + this.cacheManager = cacheManager; + this.storage = storage; + this.notificationManager = notificationManager; + this.timer = timer; + this.broadcastService = broadcastService; + this.deviceLookupService = deviceLookupService; + deviceTimeout = config.getLong(Keys.STATUS_TIMEOUT); + broadcastService.registerListener(this); + } + + public DeviceSession getDeviceSession(long deviceId) { + return sessionsByDeviceId.get(deviceId); + } + + public DeviceSession getDeviceSession( + Protocol protocol, Channel channel, SocketAddress remoteAddress, + String... uniqueIds) throws StorageException { + + Endpoint endpoint = new Endpoint(channel, remoteAddress); + Map<String, DeviceSession> endpointSessions = sessionsByEndpoint.getOrDefault( + endpoint, new ConcurrentHashMap<>()); + + uniqueIds = Arrays.stream(uniqueIds).filter(Objects::nonNull).toArray(String[]::new); + if (uniqueIds.length > 0) { + for (String uniqueId : uniqueIds) { + DeviceSession deviceSession = endpointSessions.get(uniqueId); + if (deviceSession != null) { + return deviceSession; + } + } + } else { + return endpointSessions.values().stream().findAny().orElse(null); + } + + Device device = deviceLookupService.lookup(uniqueIds); + + if (device == null && config.getBoolean(Keys.DATABASE_REGISTER_UNKNOWN)) { + device = addUnknownDevice(uniqueIds[0]); + } + + if (device != null) { + device.checkDisabled(); + + DeviceSession oldSession = sessionsByDeviceId.remove(device.getId()); + if (oldSession != null) { + Endpoint oldEndpoint = new Endpoint(oldSession.getChannel(), oldSession.getRemoteAddress()); + Map<String, DeviceSession> oldEndpointSessions = sessionsByEndpoint.get(oldEndpoint); + if (oldEndpointSessions != null && oldEndpointSessions.size() > 1) { + oldEndpointSessions.remove(device.getUniqueId()); + } else { + sessionsByEndpoint.remove(oldEndpoint); + } + } + + DeviceSession deviceSession = new DeviceSession( + device.getId(), device.getUniqueId(), protocol, channel, remoteAddress); + endpointSessions.put(device.getUniqueId(), deviceSession); + sessionsByEndpoint.put(endpoint, endpointSessions); + sessionsByDeviceId.put(device.getId(), deviceSession); + + if (oldSession == null) { + cacheManager.addDevice(device.getId()); + } + + return deviceSession; + } else { + LOGGER.warn("Unknown device - " + String.join(" ", uniqueIds) + + " (" + ((InetSocketAddress) remoteAddress).getHostString() + ")"); + return null; + } + } + + private Device addUnknownDevice(String uniqueId) { + Device device = new Device(); + device.setName(uniqueId); + device.setUniqueId(uniqueId); + device.setCategory(config.getString(Keys.DATABASE_REGISTER_UNKNOWN_DEFAULT_CATEGORY)); + + long defaultGroupId = config.getLong(Keys.DATABASE_REGISTER_UNKNOWN_DEFAULT_GROUP_ID); + if (defaultGroupId != 0) { + device.setGroupId(defaultGroupId); + } + + try { + device.setId(storage.addObject(device, new Request(new Columns.Exclude("id")))); + LOGGER.info("Automatically registered " + uniqueId); + return device; + } catch (StorageException e) { + LOGGER.warn("Automatic registration failed", e); + return null; + } + } + + public void deviceDisconnected(Channel channel, boolean supportsOffline) { + Endpoint endpoint = new Endpoint(channel, channel.remoteAddress()); + Map<String, DeviceSession> endpointSessions = sessionsByEndpoint.remove(endpoint); + if (endpointSessions != null) { + for (DeviceSession deviceSession : endpointSessions.values()) { + if (supportsOffline) { + updateDevice(deviceSession.getDeviceId(), Device.STATUS_OFFLINE, null); + } + sessionsByDeviceId.remove(deviceSession.getDeviceId()); + cacheManager.removeDevice(deviceSession.getDeviceId()); + } + } + } + + public void deviceUnknown(long deviceId) { + updateDevice(deviceId, Device.STATUS_UNKNOWN, null); + removeDeviceSession(deviceId); + } + + private void removeDeviceSession(long deviceId) { + DeviceSession deviceSession = sessionsByDeviceId.remove(deviceId); + if (deviceSession != null) { + cacheManager.removeDevice(deviceId); + Endpoint endpoint = new Endpoint(deviceSession.getChannel(), deviceSession.getRemoteAddress()); + sessionsByEndpoint.computeIfPresent(endpoint, (e, sessions) -> { + sessions.remove(deviceSession.getUniqueId()); + return sessions.isEmpty() ? null : sessions; + }); + } + } + + public void updateDevice(long deviceId, String status, Date time) { + Device device = cacheManager.getObject(Device.class, deviceId); + if (device == null) { + try { + device = storage.getObject(Device.class, new Request( + new Columns.All(), new Condition.Equals("id", deviceId))); + } catch (StorageException e) { + LOGGER.warn("Failed to get device", e); + } + if (device == null) { + return; + } + } + + String oldStatus = device.getStatus(); + device.setStatus(status); + + if (!status.equals(oldStatus)) { + String eventType; + Map<Event, Position> events = new HashMap<>(); + switch (status) { + case Device.STATUS_ONLINE: + eventType = Event.TYPE_DEVICE_ONLINE; + break; + case Device.STATUS_UNKNOWN: + eventType = Event.TYPE_DEVICE_UNKNOWN; + break; + default: + eventType = Event.TYPE_DEVICE_OFFLINE; + break; + } + events.put(new Event(eventType, deviceId), null); + notificationManager.updateEvents(events); + } + + if (time != null) { + device.setLastUpdate(time); + } + + Timeout timeout = timeouts.remove(deviceId); + if (timeout != null) { + timeout.cancel(); + } + + if (status.equals(Device.STATUS_ONLINE)) { + timeouts.put(deviceId, timer.newTimeout(timeout1 -> { + if (!timeout1.isCancelled()) { + deviceUnknown(deviceId); + } + }, deviceTimeout, TimeUnit.SECONDS)); + } + + try { + storage.updateObject(device, new Request( + new Columns.Include("status", "lastUpdate"), + new Condition.Equals("id", deviceId))); + } catch (StorageException e) { + LOGGER.warn("Update device status error", e); + } + + updateDevice(true, device); + } + + public synchronized void sendKeepalive() { + for (Set<UpdateListener> userListeners : listeners.values()) { + for (UpdateListener listener : userListeners) { + listener.onKeepalive(); + } + } + } + + @Override + public synchronized void updateDevice(boolean local, Device device) { + if (local) { + broadcastService.updateDevice(true, device); + } else if (Device.STATUS_ONLINE.equals(device.getStatus())) { + timeouts.remove(device.getId()); + removeDeviceSession(device.getId()); + } + for (long userId : deviceUsers.getOrDefault(device.getId(), Collections.emptySet())) { + if (listeners.containsKey(userId)) { + for (UpdateListener listener : listeners.get(userId)) { + listener.onUpdateDevice(device); + } + } + } + } + + @Override + public synchronized void updatePosition(boolean local, Position position) { + if (local) { + broadcastService.updatePosition(true, position); + } + for (long userId : deviceUsers.getOrDefault(position.getDeviceId(), Collections.emptySet())) { + if (listeners.containsKey(userId)) { + for (UpdateListener listener : listeners.get(userId)) { + listener.onUpdatePosition(position); + } + } + } + } + + @Override + public synchronized void updateEvent(boolean local, long userId, Event event) { + if (local) { + broadcastService.updateEvent(true, userId, event); + } + if (listeners.containsKey(userId)) { + for (UpdateListener listener : listeners.get(userId)) { + listener.onUpdateEvent(event); + } + } + } + + @Override + public synchronized void invalidatePermission( + boolean local, + Class<? extends BaseModel> clazz1, long id1, + Class<? extends BaseModel> clazz2, long id2) { + if (clazz1.equals(User.class) && clazz2.equals(Device.class)) { + if (listeners.containsKey(id1)) { + userDevices.get(id1).add(id2); + deviceUsers.put(id2, new HashSet<>(List.of(id1))); + } + } + } + + public interface UpdateListener { + void onKeepalive(); + void onUpdateDevice(Device device); + void onUpdatePosition(Position position); + void onUpdateEvent(Event event); + } + + public synchronized void addListener(long userId, UpdateListener listener) throws StorageException { + var set = listeners.get(userId); + if (set == null) { + set = new HashSet<>(); + listeners.put(userId, set); + + var devices = storage.getObjects(Device.class, new Request( + new Columns.Include("id"), new Condition.Permission(User.class, userId, Device.class))); + userDevices.put(userId, devices.stream().map(BaseModel::getId).collect(Collectors.toSet())); + devices.forEach(device -> deviceUsers.computeIfAbsent(device.getId(), id -> new HashSet<>()).add(userId)); + } + set.add(listener); + } + + public synchronized void removeListener(long userId, UpdateListener listener) { + var set = listeners.get(userId); + set.remove(listener); + if (set.isEmpty()) { + listeners.remove(userId); + + userDevices.remove(userId).forEach(deviceId -> deviceUsers.computeIfPresent(deviceId, (x, userIds) -> { + userIds.remove(userId); + return userIds.isEmpty() ? null : userIds; + })); + } + } + +} diff --git a/src/main/java/org/traccar/database/ActiveDevice.java b/src/main/java/org/traccar/session/DeviceSession.java index c05d56ad2..009f90f5a 100644 --- a/src/main/java/org/traccar/database/ActiveDevice.java +++ b/src/main/java/org/traccar/session/DeviceSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.database; +package org.traccar.session; import io.netty.channel.Channel; import io.netty.handler.codec.http.HttpRequestDecoder; @@ -22,37 +22,69 @@ import org.traccar.Protocol; import org.traccar.model.Command; import java.net.SocketAddress; +import java.util.HashMap; +import java.util.Map; -public class ActiveDevice { +public class DeviceSession { private final long deviceId; + private final String uniqueId; private final Protocol protocol; private final Channel channel; private final SocketAddress remoteAddress; - private final boolean supportsLiveCommands; - public ActiveDevice(long deviceId, Protocol protocol, Channel channel, SocketAddress remoteAddress) { + public DeviceSession( + long deviceId, String uniqueId, Protocol protocol, Channel channel, SocketAddress remoteAddress) { this.deviceId = deviceId; + this.uniqueId = uniqueId; this.protocol = protocol; this.channel = channel; this.remoteAddress = remoteAddress; - supportsLiveCommands = BasePipelineFactory.getHandler(channel.pipeline(), HttpRequestDecoder.class) == null; + } + + public long getDeviceId() { + return deviceId; + } + + public String getUniqueId() { + return uniqueId; } public Channel getChannel() { return channel; } - public long getDeviceId() { - return deviceId; + public SocketAddress getRemoteAddress() { + return remoteAddress; } public boolean supportsLiveCommands() { - return supportsLiveCommands; + return BasePipelineFactory.getHandler(channel.pipeline(), HttpRequestDecoder.class) == null; } public void sendCommand(Command command) { protocol.sendDataCommand(channel, remoteAddress, command); } + public static final String KEY_TIMEZONE = "timezone"; + + private final Map<String, Object> locals = new HashMap<>(); + + public boolean contains(String key) { + return locals.containsKey(key); + } + + public void set(String key, Object value) { + if (value != null) { + locals.put(key, value); + } else { + locals.remove(key); + } + } + + @SuppressWarnings("unchecked") + public <T> T get(String key) { + return (T) locals.get(key); + } + } diff --git a/src/main/java/org/traccar/session/Endpoint.java b/src/main/java/org/traccar/session/Endpoint.java new file mode 100644 index 000000000..76aac3444 --- /dev/null +++ b/src/main/java/org/traccar/session/Endpoint.java @@ -0,0 +1,58 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.session; + +import io.netty.channel.Channel; + +import java.net.SocketAddress; +import java.util.Objects; + +public class Endpoint { + + private final Channel channel; + private final SocketAddress remoteAddress; + + public Endpoint(Channel channel, SocketAddress remoteAddress) { + this.channel = channel; + this.remoteAddress = remoteAddress; + } + + public Channel getChannel() { + return channel; + } + + public SocketAddress getRemoteAddress() { + return remoteAddress; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Endpoint endpoint = (Endpoint) o; + return channel.equals(endpoint.channel) && remoteAddress.equals(endpoint.remoteAddress); + } + + @Override + public int hashCode() { + return Objects.hash(channel, remoteAddress); + } + +} diff --git a/src/main/java/org/traccar/session/cache/CacheKey.java b/src/main/java/org/traccar/session/cache/CacheKey.java new file mode 100644 index 000000000..23145e34b --- /dev/null +++ b/src/main/java/org/traccar/session/cache/CacheKey.java @@ -0,0 +1,57 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.session.cache; + +import org.traccar.model.BaseModel; + +import java.util.Objects; + +class CacheKey { + + private final Class<? extends BaseModel> clazz; + private final long id; + + CacheKey(BaseModel object) { + this(object.getClass(), object.getId()); + } + + CacheKey(Class<? extends BaseModel> clazz, long id) { + this.clazz = clazz; + this.id = id; + } + + public boolean classIs(Class<? extends BaseModel> clazz) { + return clazz.equals(this.clazz); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CacheKey cacheKey = (CacheKey) o; + return id == cacheKey.id && Objects.equals(clazz, cacheKey.clazz); + } + + @Override + public int hashCode() { + return Objects.hash(clazz, id); + } + +} diff --git a/src/main/java/org/traccar/session/cache/CacheManager.java b/src/main/java/org/traccar/session/cache/CacheManager.java new file mode 100644 index 000000000..9d2350012 --- /dev/null +++ b/src/main/java/org/traccar/session/cache/CacheManager.java @@ -0,0 +1,425 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.session.cache; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.broadcast.BroadcastInterface; +import org.traccar.broadcast.BroadcastService; +import org.traccar.config.Config; +import org.traccar.model.Attribute; +import org.traccar.model.BaseModel; +import org.traccar.model.Calendar; +import org.traccar.model.Device; +import org.traccar.model.Driver; +import org.traccar.model.Geofence; +import org.traccar.model.Group; +import org.traccar.model.GroupedModel; +import org.traccar.model.Maintenance; +import org.traccar.model.Notification; +import org.traccar.model.Position; +import org.traccar.model.ScheduledModel; +import org.traccar.model.Server; +import org.traccar.model.User; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; + +@Singleton +public class CacheManager implements BroadcastInterface { + + private static final Logger LOGGER = LoggerFactory.getLogger(CacheManager.class); + private static final int GROUP_DEPTH_LIMIT = 3; + private static final Collection<Class<? extends BaseModel>> CLASSES = Arrays.asList( + Attribute.class, Driver.class, Geofence.class, Maintenance.class, Notification.class); + + private final Config config; + private final Storage storage; + private final BroadcastService broadcastService; + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + private final Map<CacheKey, CacheValue> deviceCache = new HashMap<>(); + private final Map<Long, Integer> deviceReferences = new HashMap<>(); + private final Map<Long, Map<Class<? extends BaseModel>, Set<Long>>> deviceLinks = new HashMap<>(); + private final Map<Long, Position> devicePositions = new HashMap<>(); + + private Server server; + private final Map<Long, List<User>> notificationUsers = new HashMap<>(); + + @Inject + public CacheManager(Config config, Storage storage, BroadcastService broadcastService) throws StorageException { + this.config = config; + this.storage = storage; + this.broadcastService = broadcastService; + invalidateServer(); + invalidateUsers(); + broadcastService.registerListener(this); + } + + public Config getConfig() { + return config; + } + + public <T extends BaseModel> T getObject(Class<T> clazz, long id) { + try { + lock.readLock().lock(); + var cacheValue = deviceCache.get(new CacheKey(clazz, id)); + return cacheValue != null ? cacheValue.getValue() : null; + } finally { + lock.readLock().unlock(); + } + } + + public <T extends BaseModel> List<T> getDeviceObjects(long deviceId, Class<T> clazz) { + try { + lock.readLock().lock(); + var links = deviceLinks.get(deviceId); + if (links != null) { + return links.getOrDefault(clazz, new LinkedHashSet<>()).stream() + .map(id -> { + var cacheValue = deviceCache.get(new CacheKey(clazz, id)); + return cacheValue != null ? cacheValue.<T>getValue() : null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } else { + LOGGER.warn("Device {} cache missing", deviceId); + return Collections.emptyList(); + } + } finally { + lock.readLock().unlock(); + } + } + + public Position getPosition(long deviceId) { + try { + lock.readLock().lock(); + return devicePositions.get(deviceId); + } finally { + lock.readLock().unlock(); + } + } + + public Server getServer() { + try { + lock.readLock().lock(); + return server; + } finally { + lock.readLock().unlock(); + } + } + + public List<User> getNotificationUsers(long notificationId, long deviceId) { + try { + lock.readLock().lock(); + var users = deviceLinks.get(deviceId).get(User.class).stream() + .collect(Collectors.toUnmodifiableSet()); + return notificationUsers.getOrDefault(notificationId, new LinkedList<>()).stream() + .filter(user -> users.contains(user.getId())) + .collect(Collectors.toUnmodifiableList()); + } finally { + lock.readLock().unlock(); + } + } + + public Driver findDriverByUniqueId(long deviceId, String driverUniqueId) { + return getDeviceObjects(deviceId, Driver.class).stream() + .filter(driver -> driver.getUniqueId().equals(driverUniqueId)) + .findFirst() + .orElse(null); + } + + public void addDevice(long deviceId) throws StorageException { + try { + lock.writeLock().lock(); + Integer references = deviceReferences.get(deviceId); + if (references != null) { + references += 1; + } else { + unsafeAddDevice(deviceId); + references = 1; + } + deviceReferences.put(deviceId, references); + } finally { + lock.writeLock().unlock(); + } + } + + public void removeDevice(long deviceId) { + try { + lock.writeLock().lock(); + Integer references = deviceReferences.get(deviceId); + if (references != null) { + references -= 1; + if (references <= 0) { + unsafeRemoveDevice(deviceId); + deviceReferences.remove(deviceId); + } else { + deviceReferences.put(deviceId, references); + } + } + } finally { + lock.writeLock().unlock(); + } + } + + public void updatePosition(Position position) { + try { + lock.writeLock().lock(); + if (deviceLinks.containsKey(position.getDeviceId())) { + devicePositions.put(position.getDeviceId(), position); + } + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void invalidateObject(boolean local, Class<? extends BaseModel> clazz, long id) { + try { + var object = storage.getObject(clazz, new Request( + new Columns.All(), new Condition.Equals("id", id))); + if (object != null) { + updateOrInvalidate(local, object); + } else { + invalidate(clazz, id); + } + } catch (StorageException e) { + throw new RuntimeException(e); + } + } + + public <T extends BaseModel> void updateOrInvalidate(boolean local, T object) throws StorageException { + if (local) { + broadcastService.invalidateObject(true, object.getClass(), object.getId()); + } + + if (object instanceof Server) { + invalidateServer(); + return; + } + if (object instanceof User) { + invalidateUsers(); + return; + } + + boolean invalidate = false; + var before = getObject(object.getClass(), object.getId()); + if (before == null) { + return; + } else if (object instanceof GroupedModel) { + if (((GroupedModel) before).getGroupId() != ((GroupedModel) object).getGroupId()) { + invalidate = true; + } + } else if (object instanceof ScheduledModel) { + if (((ScheduledModel) before).getCalendarId() != ((ScheduledModel) object).getCalendarId()) { + invalidate = true; + } + } + if (invalidate) { + invalidate(object.getClass(), object.getId()); + } else { + try { + lock.writeLock().lock(); + deviceCache.get(new CacheKey(object.getClass(), object.getId())).setValue(object); + } finally { + lock.writeLock().unlock(); + } + } + } + + public <T extends BaseModel> void invalidate(Class<T> clazz, long id) throws StorageException { + invalidate(new CacheKey(clazz, id)); + } + + @Override + public void invalidatePermission( + boolean local, + Class<? extends BaseModel> clazz1, long id1, + Class<? extends BaseModel> clazz2, long id2) { + if (local) { + broadcastService.invalidatePermission(true, clazz1, id1, clazz2, id2); + } + + try { + invalidate(new CacheKey(clazz1, id1), new CacheKey(clazz2, id2)); + } catch (StorageException e) { + throw new RuntimeException(e); + } + } + + private void invalidateServer() throws StorageException { + server = storage.getObject(Server.class, new Request(new Columns.All())); + } + + private void invalidateUsers() throws StorageException { + notificationUsers.clear(); + Map<Long, User> users = new HashMap<>(); + storage.getObjects(User.class, new Request(new Columns.All())) + .forEach(user -> users.put(user.getId(), user)); + storage.getPermissions(User.class, Notification.class).forEach(permission -> { + long notificationId = permission.getPropertyId(); + var user = users.get(permission.getOwnerId()); + notificationUsers.computeIfAbsent(notificationId, k -> new LinkedList<>()).add(user); + }); + } + + private void addObject(long deviceId, BaseModel object) { + deviceCache.computeIfAbsent(new CacheKey(object), k -> new CacheValue(object)).retain(deviceId); + } + + private void unsafeAddDevice(long deviceId) throws StorageException { + Map<Class<? extends BaseModel>, Set<Long>> links = new HashMap<>(); + + Device device = storage.getObject(Device.class, new Request( + new Columns.All(), new Condition.Equals("id", deviceId))); + if (device != null) { + addObject(deviceId, device); + + int groupDepth = 0; + long groupId = device.getGroupId(); + while (groupDepth < GROUP_DEPTH_LIMIT && groupId > 0) { + Group group = storage.getObject(Group.class, new Request( + new Columns.All(), new Condition.Equals("id", groupId))); + links.computeIfAbsent(Group.class, k -> new LinkedHashSet<>()).add(group.getId()); + addObject(deviceId, group); + groupId = group.getGroupId(); + groupDepth += 1; + } + + for (Class<? extends BaseModel> clazz : CLASSES) { + var objects = storage.getObjects(clazz, new Request( + new Columns.All(), new Condition.Permission(Device.class, deviceId, clazz))); + links.put(clazz, objects.stream().map(BaseModel::getId).collect(Collectors.toSet())); + for (var object : objects) { + addObject(deviceId, object); + if (object instanceof ScheduledModel) { + var scheduled = (ScheduledModel) object; + if (scheduled.getCalendarId() > 0) { + var calendar = storage.getObject(Calendar.class, new Request( + new Columns.All(), new Condition.Equals("id", scheduled.getCalendarId()))); + links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>()) + .add(calendar.getId()); + addObject(deviceId, calendar); + } + } + } + } + + var users = storage.getObjects(User.class, new Request( + new Columns.All(), new Condition.Permission(User.class, Device.class, deviceId))); + links.put(User.class, users.stream().map(BaseModel::getId).collect(Collectors.toSet())); + for (var user : users) { + addObject(deviceId, user); + var notifications = storage.getObjects(Notification.class, new Request( + new Columns.All(), + new Condition.Permission(User.class, user.getId(), Notification.class))).stream() + .filter(Notification::getAlways) + .collect(Collectors.toList()); + for (var notification : notifications) { + links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>()) + .add(notification.getId()); + addObject(deviceId, notification); + if (notification.getCalendarId() > 0) { + var calendar = storage.getObject(Calendar.class, new Request( + new Columns.All(), new Condition.Equals("id", notification.getCalendarId()))); + links.computeIfAbsent(Notification.class, k -> new LinkedHashSet<>()) + .add(calendar.getId()); + addObject(deviceId, calendar); + } + } + } + + deviceLinks.put(deviceId, links); + + if (device.getPositionId() > 0) { + devicePositions.put(deviceId, storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.Equals("id", device.getPositionId())))); + } + } + } + + private void unsafeRemoveDevice(long deviceId) { + deviceCache.remove(new CacheKey(Device.class, deviceId)); + deviceLinks.remove(deviceId).forEach((clazz, ids) -> ids.forEach(id -> { + var key = new CacheKey(clazz, id); + deviceCache.computeIfPresent(key, (k, value) -> { + value.release(deviceId); + return value.getReferences().size() > 0 ? value : null; + }); + })); + devicePositions.remove(deviceId); + } + + private void invalidate(CacheKey... keys) throws StorageException { + try { + lock.writeLock().lock(); + unsafeInvalidate(keys); + } finally { + lock.writeLock().unlock(); + } + } + + private void unsafeInvalidate(CacheKey[] keys) throws StorageException { + boolean invalidateServer = false; + boolean invalidateUsers = false; + Set<Long> linkedDevices = new HashSet<>(); + for (var key : keys) { + if (key.classIs(Server.class)) { + invalidateServer = true; + } else { + if (key.classIs(User.class) || key.classIs(Notification.class)) { + invalidateUsers = true; + } + deviceCache.computeIfPresent(key, (k, value) -> { + linkedDevices.addAll(value.getReferences()); + return value; + }); + } + } + for (long deviceId : linkedDevices) { + unsafeRemoveDevice(deviceId); + unsafeAddDevice(deviceId); + } + if (invalidateServer) { + invalidateServer(); + } + if (invalidateUsers) { + invalidateUsers(); + } + } + +} diff --git a/src/main/java/org/traccar/session/cache/CacheValue.java b/src/main/java/org/traccar/session/cache/CacheValue.java new file mode 100644 index 000000000..1f0383ce5 --- /dev/null +++ b/src/main/java/org/traccar/session/cache/CacheValue.java @@ -0,0 +1,53 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.session.cache; + +import org.traccar.model.BaseModel; + +import java.util.HashSet; +import java.util.Set; + +class CacheValue { + + private BaseModel value; + private final Set<Long> references = new HashSet<>(); + + CacheValue(BaseModel value) { + this.value = value; + } + + public void retain(long deviceId) { + references.add(deviceId); + } + + public void release(long deviceId) { + references.remove(deviceId); + } + + @SuppressWarnings("unchecked") + public <T extends BaseModel> T getValue() { + return (T) value; + } + + public void setValue(BaseModel value) { + this.value = value; + } + + public Set<Long> getReferences() { + return references; + } + +} diff --git a/src/main/java/org/traccar/session/state/MotionProcessor.java b/src/main/java/org/traccar/session/state/MotionProcessor.java new file mode 100644 index 000000000..a1737a739 --- /dev/null +++ b/src/main/java/org/traccar/session/state/MotionProcessor.java @@ -0,0 +1,81 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.session.state; + +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.reports.common.TripsConfig; + +public final class MotionProcessor { + + private MotionProcessor() { + } + + public static void updateState( + MotionState state, Position position, boolean newState, TripsConfig tripsConfig) { + + state.setEvent(null); + + boolean oldState = state.getMotionState(); + if (oldState == newState) { + if (state.getMotionTime() != null) { + long oldTime = state.getMotionTime().getTime(); + long newTime = position.getFixTime().getTime(); + + double distance = position.getDouble(Position.KEY_TOTAL_DISTANCE) - state.getMotionDistance(); + Boolean ignition = null; + if (tripsConfig.getUseIgnition() && position.hasAttribute(Position.KEY_IGNITION)) { + ignition = position.getBoolean(Position.KEY_IGNITION); + } + + boolean generateEvent = false; + if (newState) { + if (newTime - oldTime >= tripsConfig.getMinimalTripDuration() + || distance >= tripsConfig.getMinimalTripDistance()) { + generateEvent = true; + } + } else { + if (newTime - oldTime >= tripsConfig.getMinimalParkingDuration() + || ignition != null && !ignition) { + generateEvent = true; + } + } + + if (generateEvent) { + + String eventType = newState ? Event.TYPE_DEVICE_MOVING : Event.TYPE_DEVICE_STOPPED; + Event event = new Event(eventType, position); + + state.setMotionStreak(newState); + state.setMotionTime(null); + state.setMotionDistance(0); + state.setEvent(event); + + } + } + } else { + state.setMotionState(newState); + if (state.getMotionStreak() == newState) { + state.setMotionTime(null); + state.setMotionDistance(0); + } else { + state.setMotionTime(position.getFixTime()); + state.setMotionDistance(position.getDouble(Position.KEY_TOTAL_DISTANCE)); + } + } + } + +} diff --git a/src/main/java/org/traccar/session/state/MotionState.java b/src/main/java/org/traccar/session/state/MotionState.java new file mode 100644 index 000000000..6c917ad16 --- /dev/null +++ b/src/main/java/org/traccar/session/state/MotionState.java @@ -0,0 +1,101 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.session.state; + +import org.traccar.model.Device; +import org.traccar.model.Event; + +import java.util.Date; + +public class MotionState { + + public static MotionState fromDevice(Device device) { + MotionState state = new MotionState(); + state.motionStreak = device.getMotionStreak(); + state.motionState = device.getMotionState(); + state.motionTime = device.getMotionTime(); + state.motionDistance = device.getMotionDistance(); + return state; + } + + public void toDevice(Device device) { + device.setMotionStreak(motionStreak); + device.setMotionState(motionState); + device.setMotionTime(motionTime); + device.setMotionDistance(motionDistance); + } + + private boolean changed; + + public boolean isChanged() { + return changed; + } + + private boolean motionStreak; + + public boolean getMotionStreak() { + return motionStreak; + } + + public void setMotionStreak(boolean motionStreak) { + this.motionStreak = motionStreak; + changed = true; + } + + private boolean motionState; + + public boolean getMotionState() { + return motionState; + } + + public void setMotionState(boolean motionState) { + this.motionState = motionState; + changed = true; + } + + private Date motionTime; + + public Date getMotionTime() { + return motionTime; + } + + public void setMotionTime(Date motionTime) { + this.motionTime = motionTime; + changed = true; + } + + private double motionDistance; + + public double getMotionDistance() { + return motionDistance; + } + + public void setMotionDistance(double motionDistance) { + this.motionDistance = motionDistance; + changed = true; + } + + private Event event; + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + +} diff --git a/src/main/java/org/traccar/session/state/OverspeedProcessor.java b/src/main/java/org/traccar/session/state/OverspeedProcessor.java new file mode 100644 index 000000000..62f6a3de2 --- /dev/null +++ b/src/main/java/org/traccar/session/state/OverspeedProcessor.java @@ -0,0 +1,65 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.session.state; + +import org.traccar.model.Event; +import org.traccar.model.Position; + +public final class OverspeedProcessor { + + public static final String ATTRIBUTE_SPEED = "speed"; + + private OverspeedProcessor() { + } + + public static void updateState( + OverspeedState state, Position position, double speedLimit, long minimalDuration, long geofenceId) { + + state.setEvent(null); + + boolean oldState = state.getOverspeedState(); + if (oldState) { + boolean newState = position.getSpeed() > speedLimit; + if (newState) { + if (state.getOverspeedTime() != null) { + long oldTime = state.getOverspeedTime().getTime(); + long newTime = position.getFixTime().getTime(); + if (newTime - oldTime > minimalDuration) { + + Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, position); + event.set(ATTRIBUTE_SPEED, position.getSpeed()); + event.set(Position.KEY_SPEED_LIMIT, speedLimit); + event.setGeofenceId(state.getOverspeedGeofenceId()); + + state.setOverspeedTime(null); + state.setOverspeedGeofenceId(0); + state.setEvent(event); + + } + } + } else { + state.setOverspeedState(false); + state.setOverspeedTime(null); + state.setOverspeedGeofenceId(0); + } + } else if (position != null && position.getSpeed() > speedLimit) { + state.setOverspeedState(true); + state.setOverspeedTime(position.getFixTime()); + state.setOverspeedGeofenceId(geofenceId); + } + } + +} diff --git a/src/main/java/org/traccar/session/state/OverspeedState.java b/src/main/java/org/traccar/session/state/OverspeedState.java new file mode 100644 index 000000000..340ede6d7 --- /dev/null +++ b/src/main/java/org/traccar/session/state/OverspeedState.java @@ -0,0 +1,88 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.session.state; + +import org.traccar.model.Device; +import org.traccar.model.Event; + +import java.util.Date; + +public class OverspeedState { + + public static OverspeedState fromDevice(Device device) { + OverspeedState state = new OverspeedState(); + state.overspeedState = device.getOverspeedState(); + state.overspeedTime = device.getOverspeedTime(); + state.overspeedGeofenceId = device.getOverspeedGeofenceId(); + return state; + } + + public void toDevice(Device device) { + device.setOverspeedState(overspeedState); + device.setOverspeedTime(overspeedTime); + device.setOverspeedGeofenceId(overspeedGeofenceId); + } + + private boolean changed; + + public boolean isChanged() { + return changed; + } + + private boolean overspeedState; + + public boolean getOverspeedState() { + return overspeedState; + } + + public void setOverspeedState(boolean overspeedState) { + this.overspeedState = overspeedState; + changed = true; + } + + private Date overspeedTime; + + public Date getOverspeedTime() { + return overspeedTime; + } + + public void setOverspeedTime(Date overspeedTime) { + this.overspeedTime = overspeedTime; + changed = true; + } + + private long overspeedGeofenceId; + + public long getOverspeedGeofenceId() { + return overspeedGeofenceId; + } + + public void setOverspeedGeofenceId(long overspeedGeofenceId) { + this.overspeedGeofenceId = overspeedGeofenceId; + changed = true; + } + + private Event event; + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + +} diff --git a/src/main/java/org/traccar/sms/HttpSmsClient.java b/src/main/java/org/traccar/sms/HttpSmsClient.java index 6234eabb8..51b161594 100644 --- a/src/main/java/org/traccar/sms/HttpSmsClient.java +++ b/src/main/java/org/traccar/sms/HttpSmsClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,16 +16,14 @@ */ package org.traccar.sms; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; +import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.helper.DataConverter; import org.traccar.notification.MessageException; +import javax.ws.rs.client.Client; import javax.ws.rs.client.Entity; import javax.ws.rs.client.Invocation; -import javax.ws.rs.client.InvocationCallback; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.UnsupportedEncodingException; @@ -34,8 +32,7 @@ import java.nio.charset.StandardCharsets; public class HttpSmsClient implements SmsManager { - private static final Logger LOGGER = LoggerFactory.getLogger(HttpSmsClient.class); - + private final Client client; private final String url; private final String authorizationHeader; private final String authorization; @@ -43,14 +40,15 @@ public class HttpSmsClient implements SmsManager { private final boolean encode; private final MediaType mediaType; - public HttpSmsClient() { - url = Context.getConfig().getString(Keys.SMS_HTTP_URL); - authorizationHeader = Context.getConfig().getString(Keys.SMS_HTTP_AUTHORIZATION_HEADER); - if (Context.getConfig().hasKey(Keys.SMS_HTTP_AUTHORIZATION)) { - authorization = Context.getConfig().getString(Keys.SMS_HTTP_AUTHORIZATION); + public HttpSmsClient(Config config, Client client) { + this.client = client; + url = config.getString(Keys.SMS_HTTP_URL); + authorizationHeader = config.getString(Keys.SMS_HTTP_AUTHORIZATION_HEADER); + if (config.hasKey(Keys.SMS_HTTP_AUTHORIZATION)) { + authorization = config.getString(Keys.SMS_HTTP_AUTHORIZATION); } else { - String user = Context.getConfig().getString(Keys.SMS_HTTP_USER); - String password = Context.getConfig().getString(Keys.SMS_HTTP_PASSWORD); + String user = config.getString(Keys.SMS_HTTP_USER); + String password = config.getString(Keys.SMS_HTTP_PASSWORD); if (user != null && password != null) { authorization = "Basic " + DataConverter.printBase64((user + ":" + password).getBytes(StandardCharsets.UTF_8)); @@ -58,7 +56,7 @@ public class HttpSmsClient implements SmsManager { authorization = null; } } - template = Context.getConfig().getString(Keys.SMS_HTTP_TEMPLATE).trim(); + template = config.getString(Keys.SMS_HTTP_TEMPLATE).trim(); if (template.charAt(0) == '{' || template.charAt(0) == '[') { encode = false; mediaType = MediaType.APPLICATION_JSON_TYPE; @@ -83,7 +81,7 @@ public class HttpSmsClient implements SmsManager { } private Invocation.Builder getRequestBuilder() { - Invocation.Builder builder = Context.getClient().target(url).request(); + Invocation.Builder builder = client.target(url).request(); if (authorization != null) { builder = builder.header(authorizationHeader, authorization); } @@ -91,26 +89,13 @@ public class HttpSmsClient implements SmsManager { } @Override - public void sendMessageSync(String destAddress, String message, boolean command) throws MessageException { - Response response = getRequestBuilder().post(Entity.entity(preparePayload(destAddress, message), mediaType)); - if (response.getStatus() / 100 != 2) { - throw new MessageException(response.readEntity(String.class)); - } - } - - @Override - public void sendMessageAsync(final String destAddress, final String message, final boolean command) { - getRequestBuilder().async().post( - Entity.entity(preparePayload(destAddress, message), mediaType), new InvocationCallback<String>() { - @Override - public void completed(String s) { - } - - @Override - public void failed(Throwable throwable) { - LOGGER.warn("SMS send failed", throwable); + public void sendMessage(String destAddress, String message, boolean command) throws MessageException { + try (Response response = getRequestBuilder() + .post(Entity.entity(preparePayload(destAddress, message), mediaType))) { + if (response.getStatus() / 100 != 2) { + throw new MessageException(response.readEntity(String.class)); } - }); + } } } diff --git a/src/main/java/org/traccar/sms/SmsManager.java b/src/main/java/org/traccar/sms/SmsManager.java index 3b0cbda7f..9ab25d9cb 100644 --- a/src/main/java/org/traccar/sms/SmsManager.java +++ b/src/main/java/org/traccar/sms/SmsManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,10 +20,7 @@ import org.traccar.notification.MessageException; public interface SmsManager { - void sendMessageSync( + void sendMessage( String destAddress, String message, boolean command) throws InterruptedException, MessageException; - void sendMessageAsync( - String destAddress, String message, boolean command); - } diff --git a/src/main/java/org/traccar/sms/SnsSmsClient.java b/src/main/java/org/traccar/sms/SnsSmsClient.java index bdd4104f5..ed5a325cc 100644 --- a/src/main/java/org/traccar/sms/SnsSmsClient.java +++ b/src/main/java/org/traccar/sms/SnsSmsClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2021 Subodh Ranadive (subodhranadive3103@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,7 @@ import com.amazonaws.services.sns.model.PublishRequest; import com.amazonaws.services.sns.model.PublishResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; +import org.traccar.config.Config; import org.traccar.config.Keys; import java.util.HashMap; @@ -38,23 +38,16 @@ public class SnsSmsClient implements SmsManager { private final AmazonSNSAsync snsClient; - public SnsSmsClient() { - if (Context.getConfig().hasKey(Keys.SMS_AWS_REGION) - && Context.getConfig().hasKey(Keys.SMS_AWS_ACCESS) - && Context.getConfig().hasKey(Keys.SMS_AWS_SECRET)) { - BasicAWSCredentials awsCredentials = - new BasicAWSCredentials(Context.getConfig().getString(Keys.SMS_AWS_ACCESS), - Context.getConfig().getString(Keys.SMS_AWS_SECRET)); - snsClient = AmazonSNSAsyncClientBuilder.standard() - .withRegion(Context.getConfig().getString(Keys.SMS_AWS_REGION)) - .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)).build(); - } else { - throw new RuntimeException("SNS Not Configured Properly. Please provide valid config."); - } + public SnsSmsClient(Config config) { + BasicAWSCredentials awsCredentials = new BasicAWSCredentials( + config.getString(Keys.SMS_AWS_ACCESS), config.getString(Keys.SMS_AWS_SECRET)); + snsClient = AmazonSNSAsyncClientBuilder.standard() + .withRegion(config.getString(Keys.SMS_AWS_REGION)) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)).build(); } @Override - public void sendMessageSync(String destAddress, String message, boolean command) { + public void sendMessage(String destAddress, String message, boolean command) { Map<String, MessageAttributeValue> smsAttributes = new HashMap<>(); smsAttributes.put("AWS.SNS.SMS.SenderID", new MessageAttributeValue().withStringValue("SNS").withDataType("String")); @@ -64,19 +57,15 @@ public class SnsSmsClient implements SmsManager { PublishRequest publishRequest = new PublishRequest().withMessage(message) .withPhoneNumber(destAddress).withMessageAttributes(smsAttributes); - snsClient.publishAsync(publishRequest, new AsyncHandler<PublishRequest, PublishResult>() { + snsClient.publishAsync(publishRequest, new AsyncHandler<>() { @Override public void onError(Exception exception) { LOGGER.error("SMS send failed", exception); } + @Override public void onSuccess(PublishRequest request, PublishResult result) { } }); } - - @Override - public void sendMessageAsync(String destAddress, String message, boolean command) { - sendMessageSync(destAddress, message, command); - } } diff --git a/src/main/java/org/traccar/speedlimit/OverpassSpeedLimitProvider.java b/src/main/java/org/traccar/speedlimit/OverpassSpeedLimitProvider.java index 429a47c76..edf089f37 100644 --- a/src/main/java/org/traccar/speedlimit/OverpassSpeedLimitProvider.java +++ b/src/main/java/org/traccar/speedlimit/OverpassSpeedLimitProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,19 +15,21 @@ */ 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.Client; import javax.ws.rs.client.InvocationCallback; public class OverpassSpeedLimitProvider implements SpeedLimitProvider { + private final Client client; private final String url; - public OverpassSpeedLimitProvider(String url) { + public OverpassSpeedLimitProvider(Client client, String url) { + this.client = client; this.url = url + "?data=[out:json];way[maxspeed](around:100.0,%f,%f);out%%20tags;"; } @@ -46,7 +48,7 @@ public class OverpassSpeedLimitProvider implements SpeedLimitProvider { @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(); + AsyncInvoker invoker = client.target(formattedUrl).request().async(); invoker.get(new InvocationCallback<JsonObject>() { @Override public void completed(JsonObject json) { diff --git a/src/main/java/org/traccar/storage/DatabaseModule.java b/src/main/java/org/traccar/storage/DatabaseModule.java new file mode 100644 index 000000000..3e3483818 --- /dev/null +++ b/src/main/java/org/traccar/storage/DatabaseModule.java @@ -0,0 +1,103 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.storage; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import liquibase.Contexts; +import liquibase.Liquibase; +import liquibase.database.Database; +import liquibase.database.DatabaseFactory; +import liquibase.exception.LiquibaseException; +import liquibase.resource.DirectoryResourceAccessor; +import liquibase.resource.ResourceAccessor; +import org.traccar.config.Config; +import org.traccar.config.Keys; + +import javax.inject.Singleton; +import javax.sql.DataSource; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URL; + +public class DatabaseModule extends AbstractModule { + + @Singleton + @Provides + public static DataSource provideDataSource( + Config config) throws ReflectiveOperationException, IOException, LiquibaseException { + + String driverFile = config.getString(Keys.DATABASE_DRIVER_FILE); + if (driverFile != null) { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + try { + Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class); + method.setAccessible(true); + method.invoke(classLoader, new File(driverFile).toURI().toURL()); + } catch (NoSuchMethodException e) { + Method method = classLoader.getClass() + .getDeclaredMethod("appendToClassPathForInstrumentation", String.class); + method.setAccessible(true); + method.invoke(classLoader, driverFile); + } + } + + String driver = config.getString(Keys.DATABASE_DRIVER); + if (driver != null) { + Class.forName(driver); + } + + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setDriverClassName(driver); + hikariConfig.setJdbcUrl(config.getString(Keys.DATABASE_URL)); + hikariConfig.setUsername(config.getString(Keys.DATABASE_USER)); + hikariConfig.setPassword(config.getString(Keys.DATABASE_PASSWORD)); + hikariConfig.setConnectionInitSql(config.getString(Keys.DATABASE_CHECK_CONNECTION)); + hikariConfig.setIdleTimeout(600000); + + int maxPoolSize = config.getInteger(Keys.DATABASE_MAX_POOL_SIZE); + if (maxPoolSize != 0) { + hikariConfig.setMaximumPoolSize(maxPoolSize); + } + + DataSource dataSource = new HikariDataSource(hikariConfig); + + if (config.hasKey(Keys.DATABASE_CHANGELOG)) { + + ResourceAccessor resourceAccessor = new DirectoryResourceAccessor(new File(".")); + + Database database = DatabaseFactory.getInstance().openDatabase( + config.getString(Keys.DATABASE_URL), + config.getString(Keys.DATABASE_USER), + config.getString(Keys.DATABASE_PASSWORD), + config.getString(Keys.DATABASE_DRIVER), + null, null, null, resourceAccessor); + + String changelog = config.getString(Keys.DATABASE_CHANGELOG); + + try (Liquibase liquibase = new Liquibase(changelog, resourceAccessor, database)) { + liquibase.clearCheckSums(); + liquibase.update(new Contexts()); + } + } + + return dataSource; + } + +} diff --git a/src/main/java/org/traccar/storage/DatabaseStorage.java b/src/main/java/org/traccar/storage/DatabaseStorage.java new file mode 100644 index 000000000..a049a641c --- /dev/null +++ b/src/main/java/org/traccar/storage/DatabaseStorage.java @@ -0,0 +1,410 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.storage; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.traccar.config.Config; +import org.traccar.model.BaseModel; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.model.GroupedModel; +import org.traccar.model.Permission; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Order; +import org.traccar.storage.query.Request; + +import javax.inject.Inject; +import javax.sql.DataSource; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class DatabaseStorage extends Storage { + + private final Config config; + private final DataSource dataSource; + private final ObjectMapper objectMapper; + private final String databaseType; + + @Inject + public DatabaseStorage(Config config, DataSource dataSource, ObjectMapper objectMapper) { + this.config = config; + this.dataSource = dataSource; + this.objectMapper = objectMapper; + + try { + databaseType = dataSource.getConnection().getMetaData().getDatabaseProductName(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Override + public <T> List<T> getObjects(Class<T> clazz, Request request) throws StorageException { + StringBuilder query = new StringBuilder("SELECT "); + if (request.getColumns() instanceof Columns.All) { + query.append('*'); + } else { + query.append(formatColumns(request.getColumns().getColumns(clazz, "set"), c -> c)); + } + query.append(" FROM ").append(getStorageName(clazz)); + query.append(formatCondition(request.getCondition())); + query.append(formatOrder(request.getOrder())); + try { + QueryBuilder builder = QueryBuilder.create(config, dataSource, objectMapper, query.toString()); + for (Map.Entry<String, Object> variable : getConditionVariables(request.getCondition()).entrySet()) { + builder.setValue(variable.getKey(), variable.getValue()); + } + return builder.executeQuery(clazz); + } catch (SQLException e) { + throw new StorageException(e); + } + } + + @Override + public <T> long addObject(T entity, Request request) throws StorageException { + List<String> columns = request.getColumns().getColumns(entity.getClass(), "get"); + StringBuilder query = new StringBuilder("INSERT INTO "); + query.append(getStorageName(entity.getClass())); + query.append("("); + query.append(formatColumns(columns, c -> c)); + query.append(") VALUES ("); + query.append(formatColumns(columns, c -> ':' + c)); + query.append(")"); + try { + QueryBuilder builder = QueryBuilder.create(config, dataSource, objectMapper, query.toString(), true); + builder.setObject(entity, columns); + return builder.executeUpdate(); + } catch (SQLException e) { + throw new StorageException(e); + } + } + + @Override + public <T> void updateObject(T entity, Request request) throws StorageException { + List<String> columns = request.getColumns().getColumns(entity.getClass(), "get"); + StringBuilder query = new StringBuilder("UPDATE "); + query.append(getStorageName(entity.getClass())); + query.append(" SET "); + query.append(formatColumns(columns, c -> c + " = :" + c)); + query.append(formatCondition(request.getCondition())); + try { + QueryBuilder builder = QueryBuilder.create(config, dataSource, objectMapper, query.toString()); + builder.setObject(entity, columns); + for (Map.Entry<String, Object> variable : getConditionVariables(request.getCondition()).entrySet()) { + builder.setValue(variable.getKey(), variable.getValue()); + } + builder.executeUpdate(); + } catch (SQLException e) { + throw new StorageException(e); + } + } + + @Override + public void removeObject(Class<?> clazz, Request request) throws StorageException { + StringBuilder query = new StringBuilder("DELETE FROM "); + query.append(getStorageName(clazz)); + query.append(formatCondition(request.getCondition())); + try { + QueryBuilder builder = QueryBuilder.create(config, dataSource, objectMapper, query.toString()); + for (Map.Entry<String, Object> variable : getConditionVariables(request.getCondition()).entrySet()) { + builder.setValue(variable.getKey(), variable.getValue()); + } + builder.executeUpdate(); + } catch (SQLException e) { + throw new StorageException(e); + } + } + + @Override + public List<Permission> getPermissions( + Class<? extends BaseModel> ownerClass, long ownerId, + Class<? extends BaseModel> propertyClass, long propertyId) throws StorageException { + StringBuilder query = new StringBuilder("SELECT * FROM "); + query.append(Permission.getStorageName(ownerClass, propertyClass)); + var conditions = new LinkedList<Condition>(); + if (ownerId > 0) { + conditions.add(new Condition.Equals(Permission.getKey(ownerClass), ownerId)); + } + if (propertyId > 0) { + conditions.add(new Condition.Equals(Permission.getKey(propertyClass), propertyId)); + } + Condition combinedCondition = Condition.merge(conditions); + query.append(formatCondition(combinedCondition)); + try { + QueryBuilder builder = QueryBuilder.create(config, dataSource, objectMapper, query.toString()); + for (Map.Entry<String, Object> variable : getConditionVariables(combinedCondition).entrySet()) { + builder.setValue(variable.getKey(), variable.getValue()); + } + return builder.executePermissionsQuery(); + } catch (SQLException e) { + throw new StorageException(e); + } + } + + @Override + public void addPermission(Permission permission) throws StorageException { + StringBuilder query = new StringBuilder("INSERT INTO "); + query.append(permission.getStorageName()); + query.append(" VALUES ("); + query.append(permission.get().keySet().stream().map(key -> ':' + key).collect(Collectors.joining(", "))); + query.append(")"); + try { + QueryBuilder builder = QueryBuilder.create(config, dataSource, objectMapper, query.toString(), true); + for (var entry : permission.get().entrySet()) { + builder.setLong(entry.getKey(), entry.getValue()); + } + builder.executeUpdate(); + } catch (SQLException e) { + throw new StorageException(e); + } + } + + @Override + public void removePermission(Permission permission) throws StorageException { + StringBuilder query = new StringBuilder("DELETE FROM "); + query.append(permission.getStorageName()); + query.append(" WHERE "); + query.append(permission + .get().keySet().stream().map(key -> key + " = :" + key).collect(Collectors.joining(" AND "))); + try { + QueryBuilder builder = QueryBuilder.create(config, dataSource, objectMapper, query.toString(), true); + for (var entry : permission.get().entrySet()) { + builder.setLong(entry.getKey(), entry.getValue()); + } + builder.executeUpdate(); + } catch (SQLException e) { + throw new StorageException(e); + } + } + + private String getStorageName(Class<?> clazz) throws StorageException { + StorageName storageName = clazz.getAnnotation(StorageName.class); + if (storageName == null) { + throw new StorageException("StorageName annotation is missing"); + } + return storageName.value(); + } + + private Map<String, Object> getConditionVariables(Condition genericCondition) { + Map<String, Object> results = new HashMap<>(); + if (genericCondition instanceof Condition.Compare) { + var condition = (Condition.Compare) genericCondition; + if (condition.getValue() != null) { + results.put(condition.getVariable(), condition.getValue()); + } + } else if (genericCondition instanceof Condition.Between) { + var condition = (Condition.Between) genericCondition; + results.put(condition.getFromVariable(), condition.getFromValue()); + results.put(condition.getToVariable(), condition.getToValue()); + } else if (genericCondition instanceof Condition.Binary) { + var condition = (Condition.Binary) genericCondition; + results.putAll(getConditionVariables(condition.getFirst())); + results.putAll(getConditionVariables(condition.getSecond())); + } else if (genericCondition instanceof Condition.Permission) { + var condition = (Condition.Permission) genericCondition; + if (condition.getOwnerId() > 0) { + results.put(Permission.getKey(condition.getOwnerClass()), condition.getOwnerId()); + } else { + results.put(Permission.getKey(condition.getPropertyClass()), condition.getPropertyId()); + } + } else if (genericCondition instanceof Condition.LatestPositions) { + var condition = (Condition.LatestPositions) genericCondition; + if (condition.getDeviceId() > 0) { + results.put("deviceId", condition.getDeviceId()); + } + } + return results; + } + + private String formatColumns(List<String> columns, Function<String, String> mapper) { + return columns.stream().map(mapper).collect(Collectors.joining(", ")); + } + + private String formatCondition(Condition genericCondition) throws StorageException { + return formatCondition(genericCondition, true); + } + + private String formatCondition(Condition genericCondition, boolean appendWhere) throws StorageException { + StringBuilder result = new StringBuilder(); + if (genericCondition != null) { + if (appendWhere) { + result.append(" WHERE "); + } + if (genericCondition instanceof Condition.Compare) { + + var condition = (Condition.Compare) genericCondition; + result.append(condition.getColumn()); + result.append(" "); + result.append(condition.getOperator()); + result.append(" :"); + result.append(condition.getVariable()); + + } else if (genericCondition instanceof Condition.Between) { + + var condition = (Condition.Between) genericCondition; + result.append(condition.getColumn()); + result.append(" BETWEEN :"); + result.append(condition.getFromVariable()); + result.append(" AND :"); + result.append(condition.getToVariable()); + + } else if (genericCondition instanceof Condition.Binary) { + + var condition = (Condition.Binary) genericCondition; + result.append(formatCondition(condition.getFirst(), false)); + result.append(" "); + result.append(condition.getOperator()); + result.append(" "); + result.append(formatCondition(condition.getSecond(), false)); + + } else if (genericCondition instanceof Condition.Permission) { + + var condition = (Condition.Permission) genericCondition; + result.append("id IN ("); + result.append(formatPermissionQuery(condition)); + result.append(")"); + + } else if (genericCondition instanceof Condition.LatestPositions) { + + var condition = (Condition.LatestPositions) genericCondition; + result.append("id IN ("); + result.append("SELECT positionId FROM "); + result.append(getStorageName(Device.class)); + if (condition.getDeviceId() > 0) { + result.append(" WHERE id = :deviceId"); + } + result.append(")"); + + } + } + return result.toString(); + } + + private String formatOrder(Order order) { + StringBuilder result = new StringBuilder(); + if (order != null) { + result.append(" ORDER BY "); + result.append(order.getColumn()); + if (order.getDescending()) { + result.append(" DESC"); + } + if (order.getLimit() > 0) { + if (databaseType.equals("Microsoft SQL Server")) { + result.append(" OFFSET 0 ROWS FETCH FIRST "); + result.append(order.getLimit()); + result.append(" ROWS ONLY"); + } else { + result.append(" LIMIT "); + result.append(order.getLimit()); + } + } + } + return result.toString(); + } + + private String formatPermissionQuery(Condition.Permission condition) throws StorageException { + StringBuilder result = new StringBuilder(); + + String outputKey; + String conditionKey; + if (condition.getOwnerId() > 0) { + outputKey = Permission.getKey(condition.getPropertyClass()); + conditionKey = Permission.getKey(condition.getOwnerClass()); + } else { + outputKey = Permission.getKey(condition.getOwnerClass()); + conditionKey = Permission.getKey(condition.getPropertyClass()); + } + + String storageName = Permission.getStorageName(condition.getOwnerClass(), condition.getPropertyClass()); + result.append("SELECT "); + result.append(storageName).append('.').append(outputKey); + result.append(" FROM "); + result.append(storageName); + result.append(" WHERE "); + result.append(conditionKey); + result.append(" = :"); + result.append(conditionKey); + + if (condition.getIncludeGroups()) { + + boolean expandDevices; + String groupStorageName; + if (GroupedModel.class.isAssignableFrom(condition.getOwnerClass())) { + expandDevices = Device.class.isAssignableFrom(condition.getOwnerClass()); + groupStorageName = Permission.getStorageName(Group.class, condition.getPropertyClass()); + } else { + expandDevices = Device.class.isAssignableFrom(condition.getPropertyClass()); + groupStorageName = Permission.getStorageName(condition.getOwnerClass(), Group.class); + } + + result.append(" UNION "); + + result.append("SELECT DISTINCT "); + if (!expandDevices) { + if (outputKey.equals("groupId")) { + result.append("all_groups."); + } else { + result.append(groupStorageName).append('.'); + } + } + result.append(outputKey); + result.append(" FROM "); + result.append(groupStorageName); + + result.append(" INNER JOIN ("); + result.append("SELECT id as parentId, id as groupId FROM "); + result.append(getStorageName(Group.class)); + result.append(" UNION "); + result.append("SELECT groupId as parentId, id as groupId FROM "); + result.append(getStorageName(Group.class)); + result.append(" WHERE groupId IS NOT NULL"); + result.append(" UNION "); + result.append("SELECT g2.groupId as parentId, g1.id as groupId FROM "); + result.append(getStorageName(Group.class)); + result.append(" AS g2"); + result.append(" INNER JOIN "); + result.append(getStorageName(Group.class)); + result.append(" AS g1 ON g2.id = g1.groupId"); + result.append(" WHERE g2.groupId IS NOT NULL"); + result.append(") AS all_groups ON "); + result.append(groupStorageName); + result.append(".groupId = all_groups.parentId"); + + if (expandDevices) { + result.append(" INNER JOIN ("); + result.append("SELECT groupId as parentId, id as deviceId FROM "); + result.append(getStorageName(Device.class)); + result.append(" WHERE groupId IS NOT NULL"); + result.append(") AS devices ON all_groups.groupId = devices.parentId"); + } + + result.append(" WHERE "); + result.append(conditionKey); + result.append(" = :"); + result.append(conditionKey); + + } + + return result.toString(); + } + +} diff --git a/src/main/java/org/traccar/storage/MemoryStorage.java b/src/main/java/org/traccar/storage/MemoryStorage.java new file mode 100644 index 000000000..f19897ff8 --- /dev/null +++ b/src/main/java/org/traccar/storage/MemoryStorage.java @@ -0,0 +1,79 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.storage; + +import org.traccar.model.BaseModel; +import org.traccar.model.Pair; +import org.traccar.model.Permission; +import org.traccar.storage.query.Request; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class MemoryStorage extends Storage { + + private final Map<Pair<Class<?>, Class<?>>, Set<Pair<Long, Long>>> permissions = new HashMap<>(); + + @Override + public <T> List<T> getObjects(Class<T> clazz, Request request) { + return null; + } + + @Override + public <T> long addObject(T entity, Request request) { + return 0; + } + + @Override + public <T> void updateObject(T entity, Request request) { + } + + @Override + public void removeObject(Class<?> clazz, Request request) { + } + + private Set<Pair<Long, Long>> getPermissionsSet(Class<?> ownerClass, Class<?> propertyClass) { + return permissions.computeIfAbsent(new Pair<>(ownerClass, propertyClass), k -> new HashSet<>()); + } + + @Override + public List<Permission> getPermissions( + Class<? extends BaseModel> ownerClass, long ownerId, + Class<? extends BaseModel> propertyClass, long propertyId) { + return getPermissionsSet(ownerClass, propertyClass).stream() + .filter(pair -> ownerId == 0 || pair.getFirst().equals(ownerId)) + .filter(pair -> propertyId == 0 || pair.getSecond().equals(propertyId)) + .map(pair -> new Permission(ownerClass, pair.getFirst(), propertyClass, pair.getSecond())) + .collect(Collectors.toList()); + } + + @Override + public void addPermission(Permission permission) { + getPermissionsSet(permission.getOwnerClass(), permission.getPropertyClass()) + .add(new Pair<>(permission.getOwnerId(), permission.getPropertyId())); + } + + @Override + public void removePermission(Permission permission) { + getPermissionsSet(permission.getOwnerClass(), permission.getPropertyClass()) + .remove(new Pair<>(permission.getOwnerId(), permission.getPropertyId())); + } + +} diff --git a/src/main/java/org/traccar/database/QueryBuilder.java b/src/main/java/org/traccar/storage/QueryBuilder.java index 396cbf562..2f4c07406 100644 --- a/src/main/java/org/traccar/database/QueryBuilder.java +++ b/src/main/java/org/traccar/storage/QueryBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.database; +package org.traccar.storage; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; +import org.traccar.config.Config; +import org.traccar.config.Keys; import org.traccar.model.Permission; import javax.sql.DataSource; @@ -33,7 +35,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.sql.Types; -import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; @@ -41,17 +42,25 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +@SuppressWarnings("UnusedReturnValue") public final class QueryBuilder { private static final Logger LOGGER = LoggerFactory.getLogger(QueryBuilder.class); + private final Config config; + private final ObjectMapper objectMapper; + private final Map<String, List<Integer>> indexMap = new HashMap<>(); private Connection connection; private PreparedStatement statement; private final String query; private final boolean returnGeneratedKeys; - private QueryBuilder(DataSource dataSource, String query, boolean returnGeneratedKeys) throws SQLException { + private QueryBuilder( + Config config, DataSource dataSource, ObjectMapper objectMapper, + String query, boolean returnGeneratedKeys) throws SQLException { + this.config = config; + this.objectMapper = objectMapper; this.query = query; this.returnGeneratedKeys = returnGeneratedKeys; if (query != null) { @@ -126,13 +135,15 @@ public final class QueryBuilder { return parsedQuery.toString(); } - public static QueryBuilder create(DataSource dataSource, String query) throws SQLException { - return new QueryBuilder(dataSource, query, false); + public static QueryBuilder create( + Config config, DataSource dataSource, ObjectMapper objectMapper, String query) throws SQLException { + return new QueryBuilder(config, dataSource, objectMapper, query, false); } public static QueryBuilder create( - DataSource dataSource, String query, boolean returnGeneratedKeys) throws SQLException { - return new QueryBuilder(dataSource, query, returnGeneratedKeys); + Config config, DataSource dataSource, ObjectMapper objectMapper, String query, + boolean returnGeneratedKeys) throws SQLException { + return new QueryBuilder(config, dataSource, objectMapper, query, returnGeneratedKeys); } private List<Integer> indexes(String name) { @@ -255,36 +266,49 @@ public final class QueryBuilder { return this; } - public QueryBuilder setObject(Object object) throws SQLException { - - Method[] methods = object.getClass().getMethods(); + public QueryBuilder setValue(String name, Object value) throws SQLException { + if (value instanceof Boolean) { + setBoolean(name, (Boolean) value); + } else if (value instanceof Integer) { + setInteger(name, (Integer) value); + } else if (value instanceof Long) { + setLong(name, (Long) value); + } else if (value instanceof Double) { + setDouble(name, (Double) value); + } else if (value instanceof String) { + setString(name, (String) value); + } else if (value instanceof Date) { + setDate(name, (Date) value); + } + return this; + } - for (Method method : methods) { - if (method.getName().startsWith("get") && method.getParameterTypes().length == 0 - && !method.isAnnotationPresent(QueryIgnore.class)) { - String name = method.getName().substring(3); - try { - if (method.getReturnType().equals(boolean.class)) { - setBoolean(name, (Boolean) method.invoke(object)); - } else if (method.getReturnType().equals(int.class)) { - setInteger(name, (Integer) method.invoke(object)); - } else if (method.getReturnType().equals(long.class)) { - setLong(name, (Long) method.invoke(object), name.endsWith("Id")); - } else if (method.getReturnType().equals(double.class)) { - setDouble(name, (Double) method.invoke(object)); - } else if (method.getReturnType().equals(String.class)) { - setString(name, (String) method.invoke(object)); - } else if (method.getReturnType().equals(Date.class)) { - setDate(name, (Date) method.invoke(object)); - } else if (method.getReturnType().equals(byte[].class)) { - setBlob(name, (byte[]) method.invoke(object)); - } else { - setString(name, Context.getObjectMapper().writeValueAsString(method.invoke(object))); - } - } catch (IllegalAccessException | InvocationTargetException | JsonProcessingException error) { - LOGGER.warn("Get property error", error); + public QueryBuilder setObject(Object object, List<String> columns) throws SQLException { + + try { + for (String column : columns) { + Method method = object.getClass().getMethod( + "get" + Character.toUpperCase(column.charAt(0)) + column.substring(1)); + if (method.getReturnType().equals(boolean.class)) { + setBoolean(column, (Boolean) method.invoke(object)); + } else if (method.getReturnType().equals(int.class)) { + setInteger(column, (Integer) method.invoke(object)); + } else if (method.getReturnType().equals(long.class)) { + setLong(column, (Long) method.invoke(object), column.endsWith("Id")); + } else if (method.getReturnType().equals(double.class)) { + setDouble(column, (Double) method.invoke(object)); + } else if (method.getReturnType().equals(String.class)) { + setString(column, (String) method.invoke(object)); + } else if (method.getReturnType().equals(Date.class)) { + setDate(column, (Date) method.invoke(object)); + } else if (method.getReturnType().equals(byte[].class)) { + setBlob(column, (byte[]) method.invoke(object)); + } else { + setString(column, objectMapper.writeValueAsString(method.invoke(object))); } } + } catch (ReflectiveOperationException | JsonProcessingException e) { + LOGGER.warn("Set object error", e); } return this; @@ -294,15 +318,6 @@ public final class QueryBuilder { void process(T object, ResultSet resultSet) throws SQLException; } - public <T> T executeQuerySingle(Class<T> clazz) throws SQLException { - Collection<T> result = executeQuery(clazz); - if (!result.isEmpty()) { - return result.iterator().next(); - } else { - return null; - } - } - private <T> void addProcessors( List<ResultSetProcessor<T>> processors, final Class<?> parameterType, final Method method, final String name) { @@ -371,7 +386,7 @@ public final class QueryBuilder { String value = resultSet.getString(name); if (value != null && !value.isEmpty()) { try { - method.invoke(object, Context.getObjectMapper().readValue(value, parameterType)); + method.invoke(object, objectMapper.readValue(value, parameterType)); } catch (InvocationTargetException | IllegalAccessException | IOException error) { LOGGER.warn("Set property error", error); } @@ -380,13 +395,21 @@ public final class QueryBuilder { } } - public <T> Collection<T> executeQuery(Class<T> clazz) throws SQLException { + private void logQuery() { + if (config.getBoolean(Keys.LOGGER_QUERIES)) { + LOGGER.info(query); + } + } + + public <T> List<T> executeQuery(Class<T> clazz) throws SQLException { List<T> result = new LinkedList<>(); if (query != null) { try { + logQuery(); + try (ResultSet resultSet = statement.executeQuery()) { ResultSetMetaData resultMetaData = resultSet.getMetaData(); @@ -396,8 +419,7 @@ public final class QueryBuilder { Method[] methods = clazz.getMethods(); for (final Method method : methods) { - if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 - && !method.isAnnotationPresent(QueryIgnore.class)) { + if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) { final String name = method.getName().substring(3); @@ -443,6 +465,7 @@ public final class QueryBuilder { if (query != null) { try { + logQuery(); statement.execute(); if (returnGeneratedKeys) { ResultSet resultSet = statement.getGeneratedKeys(); @@ -458,10 +481,11 @@ public final class QueryBuilder { return 0; } - public Collection<Permission> executePermissionsQuery() throws SQLException, ClassNotFoundException { + public List<Permission> executePermissionsQuery() throws SQLException { List<Permission> result = new LinkedList<>(); if (query != null) { try { + logQuery(); try (ResultSet resultSet = statement.executeQuery()) { ResultSetMetaData resultMetaData = resultSet.getMetaData(); while (resultSet.next()) { diff --git a/src/main/java/org/traccar/database/QueryIgnore.java b/src/main/java/org/traccar/storage/QueryIgnore.java index ac835cf2f..553d4b9fc 100644 --- a/src/main/java/org/traccar/database/QueryIgnore.java +++ b/src/main/java/org/traccar/storage/QueryIgnore.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.database; +package org.traccar.storage; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/org/traccar/storage/Storage.java b/src/main/java/org/traccar/storage/Storage.java new file mode 100644 index 000000000..55f5c22c0 --- /dev/null +++ b/src/main/java/org/traccar/storage/Storage.java @@ -0,0 +1,53 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.storage; + +import org.traccar.model.BaseModel; +import org.traccar.model.Permission; +import org.traccar.storage.query.Request; + +import java.util.List; + +public abstract class Storage { + + public abstract <T> List<T> getObjects(Class<T> clazz, Request request) throws StorageException; + + public abstract <T> long addObject(T entity, Request request) throws StorageException; + + public abstract <T> void updateObject(T entity, Request request) throws StorageException; + + public abstract void removeObject(Class<?> clazz, Request request) throws StorageException; + + public abstract List<Permission> getPermissions( + Class<? extends BaseModel> ownerClass, long ownerId, + Class<? extends BaseModel> propertyClass, long propertyId) throws StorageException; + + public abstract void addPermission(Permission permission) throws StorageException; + + public abstract void removePermission(Permission permission) throws StorageException; + + public List<Permission> getPermissions( + Class<? extends BaseModel> ownerClass, + Class<? extends BaseModel> propertyClass) throws StorageException { + return getPermissions(ownerClass, 0, propertyClass, 0); + } + + public <T> T getObject(Class<T> clazz, Request request) throws StorageException { + var objects = getObjects(clazz, request); + return objects.isEmpty() ? null : objects.get(0); + } + +} diff --git a/src/main/java/org/traccar/storage/StorageException.java b/src/main/java/org/traccar/storage/StorageException.java new file mode 100644 index 000000000..3f066cae6 --- /dev/null +++ b/src/main/java/org/traccar/storage/StorageException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.storage; + +public class StorageException extends Exception { + + public StorageException(String message) { + super(message); + } + + public StorageException(Throwable cause) { + super(cause); + } + + public StorageException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/org/traccar/database/QueryExtended.java b/src/main/java/org/traccar/storage/StorageName.java index 07bc2c211..bf824c333 100644 --- a/src/main/java/org/traccar/database/QueryExtended.java +++ b/src/main/java/org/traccar/storage/StorageName.java @@ -1,6 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) - * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * Copyright 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.database; +package org.traccar.storage; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.METHOD) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -public @interface QueryExtended { +public @interface StorageName { + String value(); } diff --git a/src/main/java/org/traccar/storage/query/Columns.java b/src/main/java/org/traccar/storage/query/Columns.java new file mode 100644 index 000000000..a00400b36 --- /dev/null +++ b/src/main/java/org/traccar/storage/query/Columns.java @@ -0,0 +1,81 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.storage.query; + +import org.traccar.storage.QueryIgnore; + +import java.beans.Introspector; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public abstract class Columns { + + public abstract List<String> getColumns(Class<?> clazz, String type); + + protected List<String> getAllColumns(Class<?> clazz, String type) { + List<String> columns = new LinkedList<>(); + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + int parameterCount = type.equals("set") ? 1 : 0; + if (method.getName().startsWith(type) && method.getParameterTypes().length == parameterCount + && !method.isAnnotationPresent(QueryIgnore.class) + && !method.getName().equals("getClass")) { + columns.add(Introspector.decapitalize(method.getName().substring(3))); + } + } + return columns; + } + + public static class All extends Columns { + @Override + public List<String> getColumns(Class<?> clazz, String type) { + return getAllColumns(clazz, type); + } + } + + public static class Include extends Columns { + private final List<String> columns; + + public Include(String... columns) { + this.columns = Arrays.stream(columns).collect(Collectors.toList()); + } + + @Override + public List<String> getColumns(Class<?> clazz, String type) { + return columns; + } + } + + public static class Exclude extends Columns { + private final Set<String> columns; + + public Exclude(String... columns) { + this.columns = Arrays.stream(columns).collect(Collectors.toSet()); + } + + @Override + public List<String> getColumns(Class<?> clazz, String type) { + return getAllColumns(clazz, type).stream() + .filter(column -> !columns.contains(column)) + .collect(Collectors.toList()); + } + } + +} diff --git a/src/main/java/org/traccar/storage/query/Condition.java b/src/main/java/org/traccar/storage/query/Condition.java new file mode 100644 index 000000000..08b199052 --- /dev/null +++ b/src/main/java/org/traccar/storage/query/Condition.java @@ -0,0 +1,211 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.storage.query; + +import org.traccar.model.GroupedModel; + +import java.util.List; + +public interface Condition { + + static Condition merge(List<Condition> conditions) { + Condition result = null; + var iterator = conditions.iterator(); + if (iterator.hasNext()) { + result = iterator.next(); + while (iterator.hasNext()) { + result = new Condition.And(result, iterator.next()); + } + } + return result; + } + + class Equals extends Compare { + public Equals(String column, Object value) { + super(column, "=", column, value); + } + } + + class Compare implements Condition { + private final String column; + private final String operator; + private final String variable; + private final Object value; + + public Compare(String column, String operator, String variable, Object value) { + this.column = column; + this.operator = operator; + this.variable = variable; + this.value = value; + } + + public String getColumn() { + return column; + } + + public String getOperator() { + return operator; + } + + public String getVariable() { + return variable; + } + + public Object getValue() { + return value; + } + } + + class Between implements Condition { + private final String column; + private final String fromVariable; + private final Object fromValue; + private final String toVariable; + private final Object toValue; + + public Between(String column, String fromVariable, Object fromValue, String toVariable, Object toValue) { + this.column = column; + this.fromVariable = fromVariable; + this.fromValue = fromValue; + this.toVariable = toVariable; + this.toValue = toValue; + } + + public String getColumn() { + return column; + } + + public String getFromVariable() { + return fromVariable; + } + + public Object getFromValue() { + return fromValue; + } + + public String getToVariable() { + return toVariable; + } + + public Object getToValue() { + return toValue; + } + } + + class Or extends Binary { + public Or(Condition first, Condition second) { + super(first, second, "OR"); + } + } + + class And extends Binary { + public And(Condition first, Condition second) { + super(first, second, "AND"); + } + } + + class Binary implements Condition { + private final Condition first; + private final Condition second; + private final String operator; + + public Binary(Condition first, Condition second, String operator) { + this.first = first; + this.second = second; + this.operator = operator; + } + + public Condition getFirst() { + return first; + } + + public Condition getSecond() { + return second; + } + + public String getOperator() { + return operator; + } + } + + class Permission implements Condition { + private final Class<?> ownerClass; + private final long ownerId; + private final Class<?> propertyClass; + private final long propertyId; + private final boolean excludeGroups; + + private Permission( + Class<?> ownerClass, long ownerId, Class<?> propertyClass, long propertyId, boolean excludeGroups) { + this.ownerClass = ownerClass; + this.ownerId = ownerId; + this.propertyClass = propertyClass; + this.propertyId = propertyId; + this.excludeGroups = excludeGroups; + } + + public Permission(Class<?> ownerClass, long ownerId, Class<?> propertyClass) { + this(ownerClass, ownerId, propertyClass, 0, false); + } + + public Permission(Class<?> ownerClass, Class<?> propertyClass, long propertyId) { + this(ownerClass, 0, propertyClass, propertyId, false); + } + + public Permission excludeGroups() { + return new Permission(this.ownerClass, this.ownerId, this.propertyClass, this.propertyId, true); + } + + public Class<?> getOwnerClass() { + return ownerClass; + } + + public long getOwnerId() { + return ownerId; + } + + public Class<?> getPropertyClass() { + return propertyClass; + } + + public long getPropertyId() { + return propertyId; + } + + public boolean getIncludeGroups() { + boolean ownerGroupModel = GroupedModel.class.isAssignableFrom(ownerClass); + boolean propertyGroupModel = GroupedModel.class.isAssignableFrom(propertyClass); + return (ownerGroupModel || propertyGroupModel) && !excludeGroups; + } + } + + class LatestPositions implements Condition { + private final long deviceId; + + public LatestPositions(long deviceId) { + this.deviceId = deviceId; + } + + public LatestPositions() { + this(0); + } + + public long getDeviceId() { + return deviceId; + } + } + +} diff --git a/src/main/java/org/traccar/database/OrderManager.java b/src/main/java/org/traccar/storage/query/Limit.java index c3253e52f..9673e5426 100644 --- a/src/main/java/org/traccar/database/OrderManager.java +++ b/src/main/java/org/traccar/storage/query/Limit.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.database; +package org.traccar.storage.query; -import org.traccar.model.Order; +public class Limit { -public class OrderManager extends ExtendedObjectManager<Order> { + private final int value; - public OrderManager(DataManager dataManager) { - super(dataManager, Order.class); + public Limit(int value) { + this.value = value; + } + + public int getValue() { + return value; } } diff --git a/src/main/java/org/traccar/storage/query/Order.java b/src/main/java/org/traccar/storage/query/Order.java new file mode 100644 index 000000000..f10970381 --- /dev/null +++ b/src/main/java/org/traccar/storage/query/Order.java @@ -0,0 +1,46 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.storage.query; + +public class Order { + + private final String column; + private final boolean descending; + private final int limit; + + public Order(String column) { + this(column, false, 0); + } + + public Order(String column, boolean descending, int limit) { + this.column = column; + this.descending = descending; + this.limit = limit; + } + + public String getColumn() { + return column; + } + + public boolean getDescending() { + return descending; + } + + public int getLimit() { + return limit; + } + +} diff --git a/src/main/java/org/traccar/storage/query/Request.java b/src/main/java/org/traccar/storage/query/Request.java new file mode 100644 index 000000000..b9c2c963c --- /dev/null +++ b/src/main/java/org/traccar/storage/query/Request.java @@ -0,0 +1,58 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.storage.query; + +public class Request { + + private final Columns columns; + private final Condition condition; + private final Order order; + + public Request(Columns columns) { + this(columns, null, null); + } + + public Request(Condition condition) { + this(null, condition, null); + } + + public Request(Columns columns, Condition condition) { + this(columns, condition, null); + } + + public Request(Columns columns, Order order) { + this(columns, null, order); + } + + public Request(Columns columns, Condition condition, Order order) { + this.columns = columns; + this.condition = condition; + this.order = order; + } + + public Columns getColumns() { + return columns; + } + + public Condition getCondition() { + return condition; + } + + public Order getOrder() { + return order; + } + +} diff --git a/src/main/java/org/traccar/web/ConsoleServlet.java b/src/main/java/org/traccar/web/ConsoleServlet.java index 0f3dcd8fd..902a4f7a9 100644 --- a/src/main/java/org/traccar/web/ConsoleServlet.java +++ b/src/main/java/org/traccar/web/ConsoleServlet.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import org.h2.server.web.ConnectionInfo; import org.h2.server.web.WebServlet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; +import org.traccar.config.Config; import org.traccar.config.Keys; import java.lang.reflect.Field; @@ -30,6 +30,12 @@ public class ConsoleServlet extends WebServlet { private static final Logger LOGGER = LoggerFactory.getLogger(ConsoleServlet.class); + private final Config config; + + public ConsoleServlet(Config config) { + this.config = config; + } + @Override public void init() { super.init(); @@ -40,9 +46,9 @@ public class ConsoleServlet extends WebServlet { org.h2.server.web.WebServer server = (org.h2.server.web.WebServer) field.get(this); ConnectionInfo connectionInfo = new ConnectionInfo("Traccar|" - + Context.getConfig().getString(Keys.DATABASE_DRIVER) + "|" - + Context.getConfig().getString(Keys.DATABASE_URL) + "|" - + Context.getConfig().getString(Keys.DATABASE_USER)); + + config.getString(Keys.DATABASE_DRIVER) + "|" + + config.getString(Keys.DATABASE_URL) + "|" + + config.getString(Keys.DATABASE_USER)); Method method; diff --git a/src/main/java/org/traccar/web/ThrottlingFilter.java b/src/main/java/org/traccar/web/ThrottlingFilter.java new file mode 100644 index 000000000..054af652f --- /dev/null +++ b/src/main/java/org/traccar/web/ThrottlingFilter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web; + +import org.eclipse.jetty.servlets.DoSFilter; +import org.traccar.config.Config; +import org.traccar.config.Keys; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +@Singleton +public class ThrottlingFilter extends DoSFilter { + + @Inject + private Config config; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + super.init(filterConfig); + if (config.hasKey(Keys.WEB_MAX_REQUESTS_PER_SECOND)) { + setMaxRequestsPerSec(config.getInteger(Keys.WEB_MAX_REQUESTS_PER_SECOND)); + } + } + + @Override + protected String extractUserId(ServletRequest request) { + HttpSession session = ((HttpServletRequest) request).getSession(false); + if (session != null) { + var userId = session.getAttribute("userId"); + return userId != null ? userId.toString() : null; + } + return null; + } +} diff --git a/src/main/java/org/traccar/web/WebInjectionManagerFactory.java b/src/main/java/org/traccar/web/WebInjectionManagerFactory.java new file mode 100644 index 000000000..14d9d3dbc --- /dev/null +++ b/src/main/java/org/traccar/web/WebInjectionManagerFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web; + +import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.jersey.inject.hk2.Hk2InjectionManagerFactory; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.internal.inject.InjectionManagerFactory; +import org.jvnet.hk2.guice.bridge.api.GuiceBridge; +import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge; +import org.traccar.Main; + +import javax.annotation.Priority; + +@Priority(20) +public class WebInjectionManagerFactory implements InjectionManagerFactory { + + private final InjectionManagerFactory originalFactory = new Hk2InjectionManagerFactory(); + + private InjectionManager injectGuiceBridge(InjectionManager injectionManager) { + var serviceLocator = injectionManager.getInstance(ServiceLocator.class); + GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator); + var guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class); + guiceBridge.bridgeGuiceInjector(Main.getInjector()); + return injectionManager; + } + + @Override + public InjectionManager create() { + return injectGuiceBridge(originalFactory.create()); + } + + @Override + public InjectionManager create(Object parent) { + return injectGuiceBridge(originalFactory.create(parent)); + } +} diff --git a/src/main/java/org/traccar/web/WebModule.java b/src/main/java/org/traccar/web/WebModule.java new file mode 100644 index 000000000..0722c5d1e --- /dev/null +++ b/src/main/java/org/traccar/web/WebModule.java @@ -0,0 +1,30 @@ +/* + * Copyright 2022 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web; + +import com.google.inject.servlet.ServletModule; +import org.traccar.api.AsyncSocketServlet; +import org.traccar.api.MediaFilter; + +public class WebModule extends ServletModule { + + @Override + protected void configureServlets() { + filter("/api/*").through(ThrottlingFilter.class); + filter("/api/media/*").through(MediaFilter.class); + serve("/api/socket").with(AsyncSocketServlet.class); + } +} diff --git a/src/main/java/org/traccar/web/WebServer.java b/src/main/java/org/traccar/web/WebServer.java index 604edfedc..79d19cc9b 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 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package org.traccar.web; +import com.google.inject.Injector; +import com.google.inject.servlet.GuiceFilter; import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; @@ -40,37 +42,42 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.servlet.ServletContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.api.DateParameterConverterProvider; -import org.traccar.config.Config; -import org.traccar.api.AsyncSocketServlet; +import org.traccar.LifecycleObject; import org.traccar.api.CorsResponseFilter; -import org.traccar.api.MediaFilter; -import org.traccar.api.ObjectMapperProvider; +import org.traccar.api.DateParameterConverterProvider; import org.traccar.api.ResourceErrorHandler; -import org.traccar.api.SecurityRequestFilter; import org.traccar.api.resource.ServerResource; +import org.traccar.api.security.SecurityRequestFilter; +import org.traccar.config.Config; import org.traccar.config.Keys; +import org.traccar.helper.ObjectMapperContextResolver; import javax.servlet.DispatcherType; import javax.servlet.ServletException; import javax.servlet.SessionCookieConfig; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.sql.DataSource; import java.io.File; import java.io.IOException; import java.io.Writer; import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.EnumSet; -public class WebServer { +public class WebServer implements LifecycleObject { private static final Logger LOGGER = LoggerFactory.getLogger(WebServer.class); - private Server server; - - private void initServer(Config config) { + private final Injector injector; + private final Config config; + private final Server server; + public WebServer(Injector injector, Config config) { + this.injector = injector; + this.config = config; String address = config.getString(Keys.WEB_ADDRESS); int port = config.getInteger(Keys.WEB_PORT); if (address == null) { @@ -78,39 +85,42 @@ public class WebServer { } else { server = new Server(new InetSocketAddress(address, port)); } - } - - public WebServer(Config config) { - - initServer(config); ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); + JettyWebSocketServletContainerInitializer.configure(servletHandler, null); + servletHandler.addFilter(GuiceFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); - initApi(config, servletHandler); - initSessionConfig(config, servletHandler); + initApi(servletHandler); + initSessionConfig(servletHandler); if (config.getBoolean(Keys.WEB_CONSOLE)) { - servletHandler.addServlet(new ServletHolder(new ConsoleServlet()), "/console/*"); + servletHandler.addServlet(new ServletHolder(new ConsoleServlet(config)), "/console/*"); } - initWebApp(config, servletHandler); + initWebApp(servletHandler); servletHandler.setErrorHandler(new ErrorHandler() { @Override protected void handleErrorPage( HttpServletRequest request, Writer writer, int code, String message) throws IOException { - writer.write("<!DOCTYPE><html><head><title>Error</title></head><html><body>" - + code + " - " + HttpStatus.getMessage(code) + "</body></html>"); + Path index = Paths.get(config.getString(Keys.WEB_PATH), "index.html"); + if (code == HttpStatus.NOT_FOUND_404 + && !request.getPathInfo().startsWith("/api/") && Files.exists(index)) { + writer.write(Files.readString(index)); + } else { + writer.write("<!DOCTYPE><html><head><title>Error</title></head><html><body>" + + code + " - " + HttpStatus.getMessage(code) + "</body></html>"); + } } }); HandlerList handlers = new HandlerList(); - initClientProxy(config, handlers); + initClientProxy(handlers); handlers.addHandler(servletHandler); handlers.addHandler(new GzipHandler()); server.setHandler(handlers); - if (config.getBoolean(Keys.WEB_REQUEST_LOG_ENABLE)) { + if (config.hasKey(Keys.WEB_REQUEST_LOG_PATH)) { RequestLogWriter logWriter = new RequestLogWriter(config.getString(Keys.WEB_REQUEST_LOG_PATH)); logWriter.setAppend(true); logWriter.setRetainDays(config.getInteger(Keys.WEB_REQUEST_LOG_RETAIN_DAYS)); @@ -119,7 +129,7 @@ public class WebServer { } } - private void initClientProxy(Config config, HandlerList handlers) { + private void initClientProxy(HandlerList handlers) { int port = config.getInteger(Keys.PROTOCOL_PORT.withPrefix("osmand")); if (port != 0) { ServletContextHandler servletHandler = new ServletContextHandler() { @@ -132,14 +142,14 @@ public class WebServer { } } }; - ServletHolder servletHolder = new ServletHolder(new AsyncProxyServlet.Transparent()); + ServletHolder servletHolder = new ServletHolder(AsyncProxyServlet.Transparent.class); servletHolder.setInitParameter("proxyTo", "http://localhost:" + port); servletHandler.addServlet(servletHolder, "/"); handlers.addHandler(servletHandler); } } - private void initWebApp(Config config, ServletContextHandler servletHandler) { + private void initWebApp(ServletContextHandler servletHandler) { ServletHolder servletHolder = new ServletHolder(DefaultServlet.class); servletHolder.setInitParameter("resourceBase", new File(config.getString(Keys.WEB_PATH)).getAbsolutePath()); servletHolder.setInitParameter("dirAllowed", "false"); @@ -155,10 +165,7 @@ public class WebServer { servletHandler.addServlet(servletHolder, "/*"); } - private void initApi(Config config, ServletContextHandler servletHandler) { - servletHandler.addServlet(new ServletHolder(new AsyncSocketServlet()), "/api/socket"); - JettyWebSocketServletContainerInitializer.configure(servletHandler, null); - + private void initApi(ServletContextHandler servletHandler) { String mediaPath = config.getString(Keys.MEDIA_PATH); if (mediaPath != null) { ServletHolder servletHolder = new ServletHolder(DefaultServlet.class); @@ -166,21 +173,27 @@ public class WebServer { servletHolder.setInitParameter("dirAllowed", "false"); servletHolder.setInitParameter("pathInfoOnly", "true"); servletHandler.addServlet(servletHolder, "/api/media/*"); - servletHandler.addFilter(MediaFilter.class, "/api/media/*", EnumSet.allOf(DispatcherType.class)); } ResourceConfig resourceConfig = new ResourceConfig(); resourceConfig.registerClasses( - JacksonFeature.class, ObjectMapperProvider.class, ResourceErrorHandler.class, - SecurityRequestFilter.class, CorsResponseFilter.class, DateParameterConverterProvider.class); + JacksonFeature.class, + ObjectMapperContextResolver.class, + DateParameterConverterProvider.class, + SecurityRequestFilter.class, + CorsResponseFilter.class, + ResourceErrorHandler.class); resourceConfig.packages(ServerResource.class.getPackage().getName()); + if (resourceConfig.getClasses().stream().filter(ServerResource.class::equals).findAny().isEmpty()) { + LOGGER.warn("Failed to load API resources"); + } servletHandler.addServlet(new ServletHolder(new ServletContainer(resourceConfig)), "/api/*"); } - private void initSessionConfig(Config config, ServletContextHandler servletHandler) { + private void initSessionConfig(ServletContextHandler servletHandler) { if (config.getBoolean(Keys.WEB_PERSIST_SESSION)) { DatabaseAdaptor databaseAdaptor = new DatabaseAdaptor(); - databaseAdaptor.setDatasource(Context.getDataManager().getDataSource()); + databaseAdaptor.setDatasource(injector.getInstance(DataSource.class)); JDBCSessionDataStoreFactory jdbcSessionDataStoreFactory = new JDBCSessionDataStoreFactory(); jdbcSessionDataStoreFactory.setDatabaseAdaptor(databaseAdaptor); SessionHandler sessionHandler = servletHandler.getSessionHandler(); @@ -214,20 +227,14 @@ public class WebServer { } } - public void start() { - try { - server.start(); - } catch (Exception error) { - LOGGER.warn("Web server start failed", error); - } + @Override + public void start() throws Exception { + server.start(); } - public void stop() { - try { - server.stop(); - } catch (Exception error) { - LOGGER.warn("Web server stop failed", error); - } + @Override + public void stop() throws Exception { + server.stop(); } } |