diff options
Diffstat (limited to 'src/main/java/org/traccar')
66 files changed, 1688 insertions, 417 deletions
diff --git a/src/main/java/org/traccar/BaseHttpProtocolDecoder.java b/src/main/java/org/traccar/BaseHttpProtocolDecoder.java index 57a68acac..b762be12c 100644 --- a/src/main/java/org/traccar/BaseHttpProtocolDecoder.java +++ b/src/main/java/org/traccar/BaseHttpProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package org.traccar; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.HttpHeaderNames; @@ -29,9 +31,16 @@ public abstract class BaseHttpProtocolDecoder extends BaseProtocolDecoder { } public void sendResponse(Channel channel, HttpResponseStatus status) { + sendResponse(channel, status, null); + } + + public void sendResponse(Channel channel, HttpResponseStatus status, ByteBuf buf) { if (channel != null) { - HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status); - response.headers().add(HttpHeaderNames.CONTENT_LENGTH, 0); + if (buf == null) { + buf = Unpooled.buffer(0); + } + HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buf); + response.headers().add(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes()); channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); } } diff --git a/src/main/java/org/traccar/BaseProtocol.java b/src/main/java/org/traccar/BaseProtocol.java index c0fd1e27f..bd3391822 100644 --- a/src/main/java/org/traccar/BaseProtocol.java +++ b/src/main/java/org/traccar/BaseProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,14 @@ */ package org.traccar; +import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; import io.netty.handler.codec.string.StringEncoder; -import org.traccar.database.ActiveDevice; import org.traccar.helper.DataConverter; import org.traccar.model.Command; +import java.net.SocketAddress; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -68,11 +70,6 @@ public abstract class BaseProtocol implements Protocol { supportedTextCommands.addAll(Arrays.asList(commands)); } - public void setSupportedCommands(String... commands) { - supportedDataCommands.addAll(Arrays.asList(commands)); - supportedTextCommands.addAll(Arrays.asList(commands)); - } - @Override public Collection<String> getSupportedDataCommands() { Set<String> commands = new HashSet<>(supportedDataCommands); @@ -88,15 +85,16 @@ public abstract class BaseProtocol implements Protocol { } @Override - public void sendDataCommand(ActiveDevice activeDevice, Command command) { + public void sendDataCommand(Channel channel, SocketAddress remoteAddress, Command command) { if (supportedDataCommands.contains(command.getType())) { - activeDevice.write(command); + channel.writeAndFlush(new NetworkMessage(command, remoteAddress)); } else if (command.getType().equals(Command.TYPE_CUSTOM)) { String data = command.getString(Command.KEY_DATA); - if (BasePipelineFactory.getHandler(activeDevice.getChannel().pipeline(), StringEncoder.class) != null) { - activeDevice.write(data); + if (BasePipelineFactory.getHandler(channel.pipeline(), StringEncoder.class) != null) { + channel.writeAndFlush(new NetworkMessage(data, remoteAddress)); } else { - activeDevice.write(Unpooled.wrappedBuffer(DataConverter.parseHex(data))); + ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(data)); + channel.writeAndFlush(new NetworkMessage(buf, remoteAddress)); } } else { throw new RuntimeException("Command " + command.getType() + " is not supported in protocol " + getName()); diff --git a/src/main/java/org/traccar/BaseProtocolDecoder.java b/src/main/java/org/traccar/BaseProtocolDecoder.java index e6e02c2d6..8981fe4c8 100644 --- a/src/main/java/org/traccar/BaseProtocolDecoder.java +++ b/src/main/java/org/traccar/BaseProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,12 @@ import io.netty.handler.codec.http.HttpRequestDecoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.config.Config; +import org.traccar.database.CommandsManager; import org.traccar.database.ConnectionManager; import org.traccar.database.IdentityManager; import org.traccar.database.StatisticsManager; import org.traccar.helper.UnitsConverter; +import org.traccar.model.Command; import org.traccar.model.Device; import org.traccar.model.Position; @@ -227,20 +229,32 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { if (decodedMessage instanceof Position) { position = (Position) decodedMessage; } else if (decodedMessage instanceof Collection) { - Collection positions = (Collection) decodedMessage; + Collection<Position> positions = (Collection) decodedMessage; if (!positions.isEmpty()) { - position = (Position) positions.iterator().next(); + position = positions.iterator().next(); } } } + long deviceId = 0; if (position != null) { - connectionManager.updateDevice( - position.getDeviceId(), Device.STATUS_ONLINE, new Date()); + deviceId = position.getDeviceId(); } else { DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); if (deviceSession != null) { - connectionManager.updateDevice( - deviceSession.getDeviceId(), Device.STATUS_ONLINE, new Date()); + deviceId = deviceSession.getDeviceId(); + } + } + if (deviceId > 0) { + connectionManager.updateDevice(deviceId, Device.STATUS_ONLINE, new Date()); + } + sendQueuedCommands(channel, remoteAddress, deviceId); + } + + 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); } } } diff --git a/src/main/java/org/traccar/Main.java b/src/main/java/org/traccar/Main.java index 6ebd1d399..47d6e91df 100644 --- a/src/main/java/org/traccar/Main.java +++ b/src/main/java/org/traccar/Main.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import com.google.inject.Guice; import com.google.inject.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.traccar.api.HealthCheckService; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; @@ -105,6 +106,27 @@ public final class Main { } } + private static void scheduleHealthCheck() { + HealthCheckService service = new HealthCheckService(); + if (service.isEnabled()) { + new Timer().scheduleAtFixedRate( + service.createTask(), service.getPeriod(), service.getPeriod()); + } + } + + private static void scheduleDatabaseCleanup() { + new Timer().scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + try { + Context.getDataManager().clearHistory(); + } catch (SQLException error) { + LOGGER.warn("Clear history error", error); + } + } + }, 0, CLEAN_PERIOD); + } + public static void run(String configFile) { try { Context.init(configFile); @@ -118,35 +140,19 @@ public final class Main { Context.getWebServer().start(); } - new Timer().scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - try { - Context.getDataManager().clearHistory(); - } catch (SQLException error) { - LOGGER.warn("Clear history error", error); - } - } - }, 0, CLEAN_PERIOD); + scheduleHealthCheck(); + scheduleDatabaseCleanup(); - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - LOGGER.error("Thread exception", e); - } - }); + Thread.setDefaultUncaughtExceptionHandler((t, e) -> LOGGER.error("Thread exception", e)); - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - LOGGER.info("Shutting down server..."); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + LOGGER.info("Shutting down server..."); - if (Context.getWebServer() != null) { - Context.getWebServer().stop(); - } - Context.getServerManager().stop(); + if (Context.getWebServer() != null) { + Context.getWebServer().stop(); } - }); + Context.getServerManager().stop(); + })); } catch (Exception e) { LOGGER.error("Main method error", e); throw new RuntimeException(e); diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java index 0957d9fe3..c3c0182d7 100644 --- a/src/main/java/org/traccar/MainModule.java +++ b/src/main/java/org/traccar/MainModule.java @@ -73,6 +73,7 @@ import org.traccar.reports.model.TripsConfig; import javax.annotation.Nullable; import javax.ws.rs.client.Client; +import io.netty.util.Timer; public class MainModule extends AbstractModule { @@ -375,6 +376,12 @@ public class MainModule extends AbstractModule { return new DriverEventHandler(identityManager); } + @Singleton + @Provides + public static Timer provideTimer() { + return GlobalTimer.getTimer(); + } + @Override protected void configure() { binder().requireExplicitBindings(); diff --git a/src/main/java/org/traccar/Protocol.java b/src/main/java/org/traccar/Protocol.java index 3b66f2598..aea69b353 100644 --- a/src/main/java/org/traccar/Protocol.java +++ b/src/main/java/org/traccar/Protocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,10 @@ */ package org.traccar; -import org.traccar.database.ActiveDevice; +import io.netty.channel.Channel; import org.traccar.model.Command; +import java.net.SocketAddress; import java.util.Collection; public interface Protocol { @@ -28,7 +29,7 @@ public interface Protocol { Collection<String> getSupportedDataCommands(); - void sendDataCommand(ActiveDevice activeDevice, Command command); + void sendDataCommand(Channel channel, SocketAddress remoteAddress, Command command); Collection<String> getSupportedTextCommands(); diff --git a/src/main/java/org/traccar/WebDataHandler.java b/src/main/java/org/traccar/WebDataHandler.java index 64396de03..d6bfb126b 100644 --- a/src/main/java/org/traccar/WebDataHandler.java +++ b/src/main/java/org/traccar/WebDataHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,12 @@ 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; @@ -27,9 +33,13 @@ 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; @@ -39,10 +49,14 @@ 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"; @@ -53,16 +67,33 @@ public class WebDataHandler extends BaseDataHandler { 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 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) { @@ -152,35 +183,111 @@ public class WebDataHandler extends BaseDataHandler { return request; } - @Override - protected Position handlePosition(Position position) { + class AsyncRequestAndCallback implements InvocationCallback<Response>, TimerTask { - String url; - if (json) { - url = this.url; - } else { + private int retries = 0; + private Map<String, Object> payload; + private Invocation.Builder requestBuilder; + private MediaType mediaType = MediaType.APPLICATION_JSON_TYPE; + + AsyncRequestAndCallback(Position position) { + + String formattedUrl; try { - url = formatRequest(position); + 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(); } - Invocation.Builder requestBuilder = client.target(url).request(); + private void send() { + 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); + } + } - 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()); + private void retry() { + 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"); } } - if (json) { - requestBuilder.async().post(Entity.json(prepareJsonPayload(position))); - } else { - requestBuilder.async().get(); + private void schedule() { + Main.getInjector().getInstance(Timer.class).newTimeout( + this, retryDelay * (int) Math.pow(2, retries++), TimeUnit.MILLISECONDS); + } + + @Override + public void completed(Response response) { + if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) { + deliveryPending.decrementAndGet(); + } else { + retry(); + } + } + + @Override + public void failed(Throwable throwable) { + retry(); + } + + @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; } diff --git a/src/main/java/org/traccar/api/ExtendedObjectResource.java b/src/main/java/org/traccar/api/ExtendedObjectResource.java index 007a7b1bd..9e554217e 100644 --- a/src/main/java/org/traccar/api/ExtendedObjectResource.java +++ b/src/main/java/org/traccar/api/ExtendedObjectResource.java @@ -55,8 +55,8 @@ public class ExtendedObjectResource<T extends BaseModel> extends BaseObjectResou Context.getPermissionsManager().checkDevice(getUserId(), deviceId); result.retainAll(manager.getDeviceItems(deviceId)); } - return manager.getItems(result); + return manager.getItems(result); } } diff --git a/src/main/java/org/traccar/api/HealthCheckService.java b/src/main/java/org/traccar/api/HealthCheckService.java new file mode 100644 index 000000000..1e8f0d731 --- /dev/null +++ b/src/main/java/org/traccar/api/HealthCheckService.java @@ -0,0 +1,91 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; + +import java.util.TimerTask; + +public class HealthCheckService { + + private static final Logger LOGGER = LoggerFactory.getLogger(HealthCheckService.class); + + private SystemD systemD; + + private boolean enabled; + private long period; + + public HealthCheckService() { + if (Context.getConfig().getBoolean("web.healthCheck") + && System.getProperty("os.name").toLowerCase().startsWith("linux")) { + try { + systemD = Native.load("systemd", SystemD.class); + String watchdogTimer = System.getenv("WATCHDOG_USEC"); + if (watchdogTimer != null && !watchdogTimer.isEmpty()) { + period = Long.parseLong(watchdogTimer) / 1000 * 4 / 5; + } + if (period > 0) { + LOGGER.info("Health check enabled with period {}", period); + enabled = true; + } + } catch (UnsatisfiedLinkError e) { + LOGGER.warn("No systemd support", e); + } + } + } + + public boolean isEnabled() { + return enabled; + } + + public long getPeriod() { + return period; + } + + private String getUrl() { + String address = Context.getConfig().getString("web.address", "localhost"); + int port = Context.getConfig().getInteger("web.port", 8082); + 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); + } + } + }; + } + + interface SystemD extends Library { + @SuppressWarnings("checkstyle:MethodName") + int sd_notify(@SuppressWarnings("checkstyle:ParameterName") int unset_environment, String state); + } + +} diff --git a/src/main/java/org/traccar/api/resource/PositionResource.java b/src/main/java/org/traccar/api/resource/PositionResource.java index c031b842f..67aa6dd32 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 - 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import javax.ws.rs.core.Response; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; @Path("positions") @@ -63,8 +64,12 @@ public class PositionResource extends BaseResource { return Context.getDeviceManager().getInitialState(getUserId()); } else { Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - return Context.getDataManager().getPositions( - deviceId, DateUtil.parseDate(from), DateUtil.parseDate(to)); + if (from != null && to != null) { + return Context.getDataManager().getPositions( + deviceId, DateUtil.parseDate(from), DateUtil.parseDate(to)); + } else { + return Collections.singleton(Context.getDeviceManager().getLastPosition(deviceId)); + } } } diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 2c5dcefd5..200ef8aa3 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -101,6 +101,44 @@ public final class Keys { "forward.json", Boolean.class); /** + * Boolean value to enable URL parameters in json mode. For example, {uniqueId} for device identifier, + * {latitude} and {longitude} for coordinates. + */ + public static final ConfigKey FORWARD_URL_VARIABLES = new ConfigKey( + "forward.urlVariables", Boolean.class); + + /** + * Position forwarding retrying enable. When enabled, additional attempts are made to deliver positions. If initial + * delivery fails, because of an unreachable server or an HTTP response different from '2xx', the software waits + * for 'forward.retry.delay' milliseconds to retry delivery. On subsequent failures, this delay is duplicated. + * 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 FORWARD_RETRY_ENABLE = new ConfigKey( + "forward.retry.enable", Boolean.class); + + /** + * Position forwarding retry first delay in milliseconds. + * Can be set to anything greater than 0. Defaults to 100 milliseconds. + */ + public static final ConfigKey FORWARD_RETRY_DELAY = new ConfigKey( + "forward.retry.delay", Integer.class); + + /** + * Position forwarding retry maximum retries. + * Can be set to anything greater than 0. Defaults to 10 retries. + */ + public static final ConfigKey FORWARD_RETRY_COUNT = new ConfigKey( + "forward.retry.count", Integer.class); + + /** + * Position forwarding retry pending positions limit. + * Can be set to anything greater than 0. Defaults to 100 positions. + */ + public static final ConfigKey FORWARD_RETRY_LIMIT = new ConfigKey( + "forward.retry.limit", Integer.class); + + /** * Boolean flag to enable or disable position filtering. */ public static final ConfigKey FILTER_ENABLE = new ConfigKey( diff --git a/src/main/java/org/traccar/database/ActiveDevice.java b/src/main/java/org/traccar/database/ActiveDevice.java index 207fc454b..e3ece6ad9 100644 --- a/src/main/java/org/traccar/database/ActiveDevice.java +++ b/src/main/java/org/traccar/database/ActiveDevice.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package org.traccar.database; import io.netty.channel.Channel; -import org.traccar.NetworkMessage; +import io.netty.handler.codec.http.HttpRequestDecoder; +import org.traccar.BasePipelineFactory; import org.traccar.Protocol; import org.traccar.model.Command; @@ -44,12 +45,12 @@ public class ActiveDevice { return deviceId; } - public void sendCommand(Command command) { - protocol.sendDataCommand(this, command); + public boolean supportsLiveCommands() { + return BasePipelineFactory.getHandler(channel.pipeline(), HttpRequestDecoder.class) == null; } - public void write(Object message) { - channel.writeAndFlush(new NetworkMessage(message, remoteAddress)); + public void sendCommand(Command command) { + protocol.sendDataCommand(channel, remoteAddress, command); } } diff --git a/src/main/java/org/traccar/database/BaseObjectManager.java b/src/main/java/org/traccar/database/BaseObjectManager.java index 8bf9ef860..e274e5aba 100644 --- a/src/main/java/org/traccar/database/BaseObjectManager.java +++ b/src/main/java/org/traccar/database/BaseObjectManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,8 @@ 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; @@ -32,6 +34,8 @@ 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 Map<Long, T> items; @@ -43,6 +47,22 @@ public class BaseObjectManager<T extends BaseModel> { 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; } @@ -52,12 +72,18 @@ public class BaseObjectManager<T extends BaseModel> { } public T getById(long itemId) { - return items.get(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()); @@ -78,12 +104,19 @@ public class BaseObjectManager<T extends BaseModel> { } } catch (SQLException error) { LOGGER.warn("Error refreshing items", error); + } finally { + writeUnlock(); } } } protected void addNewItem(T item) { - items.put(item.getId(), item); + try { + writeLock(); + items.put(item.getId(), item); + } finally { + writeUnlock(); + } } public void addItem(T item) throws SQLException { @@ -92,7 +125,12 @@ public class BaseObjectManager<T extends BaseModel> { } protected void updateCachedItem(T item) { - items.put(item.getId(), item); + try { + writeLock(); + items.put(item.getId(), item); + } finally { + writeUnlock(); + } } public void updateItem(T item) throws SQLException { @@ -101,7 +139,12 @@ public class BaseObjectManager<T extends BaseModel> { } protected void removeCachedItem(long itemId) { - items.remove(itemId); + try { + writeLock(); + items.remove(itemId); + } finally { + writeUnlock(); + } } public void removeItem(long itemId) throws SQLException { @@ -121,7 +164,12 @@ public class BaseObjectManager<T extends BaseModel> { } public Set<Long> getAllItems() { - return items.keySet(); + 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 dc9512d9e..485402807 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 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -76,7 +76,12 @@ public class CommandsManager extends ExtendedObjectManager<Command> { } else { ActiveDevice activeDevice = Context.getConnectionManager().getActiveDevice(deviceId); if (activeDevice != null) { - activeDevice.sendCommand(command); + if (activeDevice.supportsLiveCommands()) { + activeDevice.sendCommand(command); + } else { + getDeviceQueue(deviceId).add(command); + return false; + } } else if (!queueing) { throw new RuntimeException("Device is not online"); } else { @@ -142,21 +147,46 @@ public class CommandsManager extends ExtendedObjectManager<Command> { } private Queue<Command> getDeviceQueue(long deviceId) { - if (!deviceQueues.containsKey(deviceId)) { - deviceQueues.put(deviceId, new ConcurrentLinkedQueue<Command>()); + 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(); + } } - return deviceQueues.get(deviceId); } - public void sendQueuedCommands(ActiveDevice activeDevice) { - Queue<Command> deviceQueue = deviceQueues.get(activeDevice.getDeviceId()); + 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(); + } + Collection<Command> result = new ArrayList<>(); if (deviceQueue != null) { Command command = deviceQueue.poll(); - while (command != null) { - activeDevice.sendCommand(command); + while (command != null && result.size() < count) { + result.add(command); command = deviceQueue.poll(); } } + return result; } } diff --git a/src/main/java/org/traccar/database/ConnectionManager.java b/src/main/java/org/traccar/database/ConnectionManager.java index dd0071143..4d43bc71b 100644 --- a/src/main/java/org/traccar/database/ConnectionManager.java +++ b/src/main/java/org/traccar/database/ConnectionManager.java @@ -139,10 +139,6 @@ public class ConnectionManager { } updateDevice(device); - - if (status.equals(Device.STATUS_ONLINE) && !oldStatus.equals(Device.STATUS_ONLINE)) { - Context.getCommandsManager().sendQueuedCommands(getActiveDevice(deviceId)); - } } public Map<Event, Position> updateDeviceState(long deviceId) { diff --git a/src/main/java/org/traccar/database/DeviceManager.java b/src/main/java/org/traccar/database/DeviceManager.java index fa95adeb2..fe17f7ced 100644 --- a/src/main/java/org/traccar/database/DeviceManager.java +++ b/src/main/java/org/traccar/database/DeviceManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,11 +58,16 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity public DeviceManager(DataManager dataManager) { super(dataManager, Device.class); this.config = Context.getConfig(); - if (devicesByPhone == null) { - devicesByPhone = new ConcurrentHashMap<>(); - } - if (devicesByUniqueId == null) { - devicesByUniqueId = new ConcurrentHashMap<>(); + try { + writeLock(); + if (devicesByPhone == null) { + devicesByPhone = new ConcurrentHashMap<>(); + } + if (devicesByUniqueId == null) { + devicesByUniqueId = new ConcurrentHashMap<>(); + } + } finally { + writeUnlock(); } dataRefreshDelay = config.getLong("database.refreshDelay", DEFAULT_REFRESH_DELAY) * 1000; lookupGroupsAttribute = config.getBoolean("deviceManager.lookupGroupsAttribute"); @@ -108,11 +113,20 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity @Override public Device getByUniqueId(String uniqueId) throws SQLException { - boolean forceUpdate = !devicesByUniqueId.containsKey(uniqueId) && !config.getBoolean("database.ignoreUnknown"); - + boolean forceUpdate; + try { + readLock(); + forceUpdate = !devicesByUniqueId.containsKey(uniqueId) && !config.getBoolean("database.ignoreUnknown"); + } finally { + readUnlock(); + } updateDeviceCache(forceUpdate); - - return devicesByUniqueId.get(uniqueId); + try { + readLock(); + return devicesByUniqueId.get(uniqueId); + } finally { + readUnlock(); + } } @Override @@ -134,7 +148,12 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity } public Device getDeviceByPhone(String phone) { - return devicesByPhone.get(phone); + try { + readLock(); + return devicesByPhone.get(phone); + } finally { + readUnlock(); + } } @Override @@ -176,8 +195,7 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity } public Set<Long> getAllManagedItems(long userId) { - Set<Long> result = new HashSet<>(); - result.addAll(getAllUserItems(userId)); + Set<Long> result = new HashSet<>(getAllUserItems(userId)); for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { result.addAll(getAllUserItems(managedUserId)); } @@ -186,34 +204,68 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity @Override public Set<Long> getManagedItems(long userId) { - Set<Long> result = new HashSet<>(); - result.addAll(getUserItems(userId)); + Set<Long> result = new HashSet<>(getUserItems(userId)); for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { result.addAll(getUserItems(managedUserId)); } return result; } - private void putUniqueDeviceId(Device device) { - if (devicesByUniqueId == null) { - devicesByUniqueId = new ConcurrentHashMap<>(getAllItems().size()); + 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(); } - devicesByUniqueId.put(device.getUniqueId(), device); } - private void putPhone(Device device) { - if (devicesByPhone == null) { - devicesByPhone = new ConcurrentHashMap<>(getAllItems().size()); + private void removeByPhone(String phone) { + if (phone == null || phone.isEmpty()) { + return; + } + try { + writeLock(); + if (devicesByPhone != null) { + devicesByPhone.remove(phone); + } + } finally { + writeUnlock(); } - devicesByPhone.put(device.getPhone(), device); } @Override protected void addNewItem(Device device) { super.addNewItem(device); - putUniqueDeviceId(device); + addByUniqueId(device); if (device.getPhone() != null && !device.getPhone().isEmpty()) { - putPhone(device); + addByPhone(device); } if (Context.getGeofenceManager() != null) { Position lastPosition = getLastPosition(device.getId()); @@ -234,18 +286,16 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity cachedDevice.setDisabled(device.getDisabled()); cachedDevice.setAttributes(device.getAttributes()); if (!device.getUniqueId().equals(cachedDevice.getUniqueId())) { - devicesByUniqueId.remove(cachedDevice.getUniqueId()); + removeByUniqueId(cachedDevice.getUniqueId()); cachedDevice.setUniqueId(device.getUniqueId()); - putUniqueDeviceId(cachedDevice); + addByUniqueId(cachedDevice); } if (device.getPhone() != null && !device.getPhone().isEmpty() && !device.getPhone().equals(cachedDevice.getPhone())) { String phone = cachedDevice.getPhone(); - if (phone != null && !phone.isEmpty()) { - devicesByPhone.remove(phone); - } + removeByPhone(phone); cachedDevice.setPhone(device.getPhone()); - putPhone(cachedDevice); + addByPhone(cachedDevice); } } @@ -256,10 +306,8 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity String deviceUniqueId = cachedDevice.getUniqueId(); String phone = cachedDevice.getPhone(); super.removeCachedItem(deviceId); - devicesByUniqueId.remove(deviceUniqueId); - if (phone != null && !phone.isEmpty()) { - devicesByPhone.remove(phone); - } + removeByUniqueId(deviceUniqueId); + removeByPhone(phone); } positions.remove(deviceId); } diff --git a/src/main/java/org/traccar/database/DriversManager.java b/src/main/java/org/traccar/database/DriversManager.java index 930951460..d111cd643 100644 --- a/src/main/java/org/traccar/database/DriversManager.java +++ b/src/main/java/org/traccar/database/DriversManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,22 +27,44 @@ public class DriversManager extends ExtendedObjectManager<Driver> { public DriversManager(DataManager dataManager) { super(dataManager, Driver.class); - if (driversByUniqueId == null) { - driversByUniqueId = new ConcurrentHashMap<>(); + try { + writeLock(); + if (driversByUniqueId == null) { + driversByUniqueId = new ConcurrentHashMap<>(); + } + } finally { + writeUnlock(); } } - private void putUniqueDriverId(Driver driver) { - if (driversByUniqueId == null) { - driversByUniqueId = new ConcurrentHashMap<>(getAllItems().size()); + 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(); } - driversByUniqueId.put(driver.getUniqueId(), driver); } @Override protected void addNewItem(Driver driver) { super.addNewItem(driver); - putUniqueDriverId(driver); + addByUniqueId(driver); } @Override @@ -50,9 +72,9 @@ public class DriversManager extends ExtendedObjectManager<Driver> { Driver cachedDriver = getById(driver.getId()); cachedDriver.setName(driver.getName()); if (!driver.getUniqueId().equals(cachedDriver.getUniqueId())) { - driversByUniqueId.remove(cachedDriver.getUniqueId()); + removeByUniqueId(cachedDriver.getUniqueId()); cachedDriver.setUniqueId(driver.getUniqueId()); - putUniqueDriverId(cachedDriver); + addByUniqueId(cachedDriver); } cachedDriver.setAttributes(driver.getAttributes()); } @@ -63,11 +85,16 @@ public class DriversManager extends ExtendedObjectManager<Driver> { if (cachedDriver != null) { String driverUniqueId = cachedDriver.getUniqueId(); super.removeCachedItem(driverId); - driversByUniqueId.remove(driverUniqueId); + removeByUniqueId(driverUniqueId); } } public Driver getDriverByUniqueId(String uniqueId) { - return driversByUniqueId.get(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 index ceb85b537..93e5820fb 100644 --- a/src/main/java/org/traccar/database/ExtendedObjectManager.java +++ b/src/main/java/org/traccar/database/ExtendedObjectManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,24 +45,45 @@ public abstract class ExtendedObjectManager<T extends BaseModel> extends SimpleO } public final Set<Long> getGroupItems(long groupId) { - if (!groupItems.containsKey(groupId)) { - groupItems.put(groupId, new HashSet<Long>()); + try { + readLock(); + Set<Long> result = groupItems.get(groupId); + if (result != null) { + return new HashSet<>(result); + } else { + return new HashSet<>(); + } + } finally { + readUnlock(); } - return groupItems.get(groupId); } public final Set<Long> getDeviceItems(long deviceId) { - if (!deviceItems.containsKey(deviceId)) { - deviceItems.put(deviceId, new HashSet<Long>()); + try { + readLock(); + Set<Long> result = deviceItems.get(deviceId); + if (result != null) { + return new HashSet<>(result); + } else { + return new HashSet<>(); + } + } finally { + readUnlock(); } - return deviceItems.get(deviceId); } public Set<Long> getAllDeviceItems(long deviceId) { - if (!deviceItemsWithGroups.containsKey(deviceId)) { - deviceItemsWithGroups.put(deviceId, new HashSet<Long>()); + try { + readLock(); + Set<Long> result = deviceItemsWithGroups.get(deviceId); + if (result != null) { + return new HashSet<>(result); + } else { + return new HashSet<>(); + } + } finally { + readUnlock(); } - return deviceItemsWithGroups.get(deviceId); } @Override @@ -74,41 +95,48 @@ public abstract class ExtendedObjectManager<T extends BaseModel> extends SimpleO public void refreshExtendedPermissions() { if (getDataManager() != null) { try { - Collection<Permission> databaseGroupPermissions = getDataManager().getPermissions(Group.class, getBaseClass()); - groupItems.clear(); - for (Permission groupPermission : databaseGroupPermissions) { - getGroupItems(groupPermission.getOwnerId()).add(groupPermission.getPropertyId()); - } - 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) { - getDeviceItems(devicePermission.getOwnerId()).add(devicePermission.getPropertyId()); - getAllDeviceItems(devicePermission.getOwnerId()).add(devicePermission.getPropertyId()); + 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) { - getAllDeviceItems(device.getId()).addAll(getGroupItems(groupId)); + while (groupId > 0) { + deviceItemsWithGroups + .computeIfAbsent(device.getId(), key -> new HashSet<>()) + .addAll(groupItems.getOrDefault(groupId, new HashSet<>())); Group group = Context.getGroupsManager().getById(groupId); - if (group != null) { - groupId = group.getGroupId(); - } else { - groupId = 0; - } + 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/GroupsManager.java b/src/main/java/org/traccar/database/GroupsManager.java index d8404c614..81f1968aa 100644 --- a/src/main/java/org/traccar/database/GroupsManager.java +++ b/src/main/java/org/traccar/database/GroupsManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -95,8 +95,7 @@ public class GroupsManager extends BaseObjectManager<Group> implements Managable @Override public Set<Long> getManagedItems(long userId) { - Set<Long> result = new HashSet<>(); - result.addAll(getUserItems(userId)); + Set<Long> result = getUserItems(userId); for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { result.addAll(getUserItems(managedUserId)); } diff --git a/src/main/java/org/traccar/database/LdapProvider.java b/src/main/java/org/traccar/database/LdapProvider.java index d8b5c9f52..a8220ea8e 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 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,7 +81,7 @@ public class LdapProvider { if (this.adminFilter != null) { try { InitialDirContext context = initContext(); - String searchString = adminFilter.replace(":login", accountName); + String searchString = adminFilter.replace(":login", encodeForLdap(accountName)); SearchControls searchControls = new SearchControls(); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); NamingEnumeration<SearchResult> results = context.search(searchBase, searchString, searchControls); @@ -107,7 +107,7 @@ public class LdapProvider { private SearchResult lookupUser(String accountName) throws NamingException { InitialDirContext context = initContext(); - String searchString = searchFilter.replace(":login", accountName); + String searchString = searchFilter.replace(":login", encodeForLdap(accountName)); SearchControls searchControls = new SearchControls(); String[] attributeFilter = {idAttribute, nameAttribute, mailAttribute}; @@ -176,4 +176,34 @@ public class LdapProvider { return false; } + public String encodeForLdap(String input) { + if (input == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + switch (c) { + case '\\': + sb.append("\\5c"); + break; + case '*': + sb.append("\\2a"); + break; + case '(': + sb.append("\\28"); + break; + case ')': + sb.append("\\29"); + break; + case '\0': + sb.append("\\00"); + break; + default: + sb.append(c); + } + } + return sb.toString(); + } + } diff --git a/src/main/java/org/traccar/database/SimpleObjectManager.java b/src/main/java/org/traccar/database/SimpleObjectManager.java index 15dda4520..eb8284d4e 100644 --- a/src/main/java/org/traccar/database/SimpleObjectManager.java +++ b/src/main/java/org/traccar/database/SimpleObjectManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,16 +42,22 @@ public abstract class SimpleObjectManager<T extends BaseModel> extends BaseObjec @Override public final Set<Long> getUserItems(long userId) { - if (!userItems.containsKey(userId)) { - userItems.put(userId, new HashSet<Long>()); + try { + readLock(); + Set<Long> result = userItems.get(userId); + if (result != null) { + return new HashSet<>(result); + } else { + return new HashSet<>(); + } + } finally { + readUnlock(); } - return userItems.get(userId); } @Override public Set<Long> getManagedItems(long userId) { - Set<Long> result = new HashSet<>(); - result.addAll(getUserItems(userId)); + Set<Long> result = getUserItems(userId); for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { result.addAll(getUserItems(managedUserId)); } @@ -71,16 +77,16 @@ public abstract class SimpleObjectManager<T extends BaseModel> extends BaseObjec public final void refreshUserItems() { if (getDataManager() != null) { try { - if (userItems != null) { - userItems.clear(); - } else { - userItems = new ConcurrentHashMap<>(); - } + writeLock(); + userItems = new ConcurrentHashMap<>(); for (Permission permission : getDataManager().getPermissions(User.class, getBaseClass())) { - getUserItems(permission.getOwnerId()).add(permission.getPropertyId()); + 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(); } } } diff --git a/src/main/java/org/traccar/database/UsersManager.java b/src/main/java/org/traccar/database/UsersManager.java index 576a9e6c7..b741a85b6 100644 --- a/src/main/java/org/traccar/database/UsersManager.java +++ b/src/main/java/org/traccar/database/UsersManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +16,6 @@ */ package org.traccar.database; -import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -73,8 +72,7 @@ public class UsersManager extends SimpleObjectManager<User> { @Override public Set<Long> getManagedItems(long userId) { - Set<Long> result = new HashSet<>(); - result.addAll(getUserItems(userId)); + Set<Long> result = getUserItems(userId); result.add(userId); return result; } diff --git a/src/main/java/org/traccar/geocoder/FactualGeocoder.java b/src/main/java/org/traccar/geocoder/FactualGeocoder.java index c7a68c293..f540eb8fe 100644 --- a/src/main/java/org/traccar/geocoder/FactualGeocoder.java +++ b/src/main/java/org/traccar/geocoder/FactualGeocoder.java @@ -1,6 +1,6 @@ /* * Copyright 2014 - 2015 Stefaan Van Dooren (stefaan.vandooren@gmail.com) - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,16 @@ import javax.json.JsonObject; public class FactualGeocoder extends JsonGeocoder { + private static String formatUrl(String url, String key) { + if (url == null) { + url = "https://api.factual.com/geotag"; + } + url += "?latitude=%f&longitude=%f&KEY=" + key; + return url; + } + public FactualGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { - super(url + "?latitude=%f&longitude=%f&KEY=" + key, cacheSize, addressFormat); + super(formatUrl(url, key), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java b/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java index 3a173f985..b4881a006 100644 --- a/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java +++ b/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,16 @@ import javax.json.JsonObject; public class GisgraphyGeocoder extends JsonGeocoder { - public GisgraphyGeocoder(AddressFormat addressFormat) { - this("http://services.gisgraphy.com/reversegeocoding/search", 0, addressFormat); + private static String formatUrl(String url) { + if (url == null) { + url = "http://services.gisgraphy.com/reversegeocoding/search"; + } + url += "?format=json&lat=%f&lng=%f&from=1&to=1"; + return url; } public GisgraphyGeocoder(String url, int cacheSize, AddressFormat addressFormat) { - super(url + "?format=json&lat=%f&lng=%f&from=1&to=1", cacheSize, addressFormat); + super(formatUrl(url), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java b/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java index 4029e3f07..8dc3f76f0 100644 --- a/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java +++ b/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java @@ -1,6 +1,6 @@ /* * Copyright 2014 - 2015 Stefaan Van Dooren (stefaan.vandooren@gmail.com) - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,16 @@ import javax.json.JsonObject; public class MapQuestGeocoder extends JsonGeocoder { + private static String formatUrl(String url, String key) { + if (url == null) { + url = "http://www.mapquestapi.com/geocoding/v1/reverse"; + } + url += "?key=" + key + "&location=%f,%f"; + return url; + } + public MapQuestGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { - super(url + "?key=" + key + "&location=%f,%f", cacheSize, addressFormat); + super(formatUrl(url, key), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java b/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java index 822b6e91e..56161e52c 100644 --- a/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java +++ b/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java @@ -1,6 +1,6 @@ /* * Copyright 2014 - 2015 Stefaan Van Dooren (stefaan.vandooren@gmail.com) - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,16 @@ import javax.json.JsonObject; public class OpenCageGeocoder extends JsonGeocoder { + private static String formatUrl(String url, String key) { + if (url == null) { + url = "https://api.opencagedata.com/geocode/v1"; + } + url += "/json?q=%f,%f&no_annotations=1&key=" + key; + return url; + } + public OpenCageGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { - super(url + "/json?q=%f,%f&no_annotations=1&key=" + key, cacheSize, addressFormat); + super(formatUrl(url, key), cacheSize, addressFormat); } @Override diff --git a/src/main/java/org/traccar/helper/Checksum.java b/src/main/java/org/traccar/helper/Checksum.java index adfa697c5..d41dc2992 100644 --- a/src/main/java/org/traccar/helper/Checksum.java +++ b/src/main/java/org/traccar/helper/Checksum.java @@ -168,6 +168,14 @@ public final class Checksum { return checksum; } + public static int modulo256(ByteBuffer buf) { + int checksum = 0; + while (buf.hasRemaining()) { + checksum = (checksum + buf.get()) & 0xFF; + } + return checksum; + } + public static String sum(String msg) { byte checksum = 0; for (byte b : msg.getBytes(StandardCharsets.US_ASCII)) { diff --git a/src/main/java/org/traccar/helper/ServletHelper.java b/src/main/java/org/traccar/helper/ServletHelper.java index e3481e249..b6c587ec3 100644 --- a/src/main/java/org/traccar/helper/ServletHelper.java +++ b/src/main/java/org/traccar/helper/ServletHelper.java @@ -1,8 +1,22 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.traccar.helper; import javax.servlet.http.HttpServletRequest; - public final class ServletHelper { private ServletHelper() { @@ -14,10 +28,14 @@ public final class ServletHelper { String remoteAddress = request.getHeader("X-FORWARDED-FOR"); if (remoteAddress != null && !remoteAddress.isEmpty()) { - return remoteAddress.substring(0, remoteAddress.indexOf(",")); // removes the additional data + int separatorIndex = remoteAddress.indexOf(","); + if (separatorIndex > 0) { + return remoteAddress.substring(0, separatorIndex); // remove the additional data + } else { + return remoteAddress; + } } else { - remoteAddress = request.getRemoteAddr(); - return remoteAddress; + return request.getRemoteAddr(); } } else { return null; diff --git a/src/main/java/org/traccar/notification/NotificatorManager.java b/src/main/java/org/traccar/notification/NotificatorManager.java index 191748379..c5f1ad8a0 100644 --- a/src/main/java/org/traccar/notification/NotificatorManager.java +++ b/src/main/java/org/traccar/notification/NotificatorManager.java @@ -32,6 +32,7 @@ import org.traccar.notificators.Notificator; import org.traccar.notificators.NotificatorSms; import org.traccar.notificators.NotificatorWeb; import org.traccar.notificators.NotificatorTelegram; +import org.traccar.notificators.NotificatorPushover; public final class NotificatorManager { @@ -61,6 +62,9 @@ public final class NotificatorManager { case "telegram": defaultNotificator = NotificatorTelegram.class.getCanonicalName(); break; + case "pushover": + defaultNotificator = NotificatorPushover.class.getCanonicalName(); + break; default: break; } diff --git a/src/main/java/org/traccar/notificators/NotificatorPushover.java b/src/main/java/org/traccar/notificators/NotificatorPushover.java new file mode 100644 index 000000000..141d652ca --- /dev/null +++ b/src/main/java/org/traccar/notificators/NotificatorPushover.java @@ -0,0 +1,100 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notificators; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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 javax.ws.rs.client.Entity; +import javax.ws.rs.client.InvocationCallback; + +public class NotificatorPushover extends Notificator { + + private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorPushover.class); + + private final String url; + private final String token; + private final String puser; + + public static class Message { + @JsonProperty("token") + private String token; + @JsonProperty("user") + private String user; + @JsonProperty("device") + private String device; + @JsonProperty("message") + private String message; + } + + public NotificatorPushover() { + url = "https://api.pushover.net/1/messages.json"; + token = Context.getConfig().getString("notificator.pushover.token"); + puser = Context.getConfig().getString("notificator.pushover.user"); + } + + @Override + public void sendSync(long userId, Event event, Position position) { + + final User user = Context.getPermissionsManager().getUser(userId); + + String device = ""; + + if (user.getAttributes().containsKey("notificator.pushover.device")) { + device = user.getString("notificator.pushover.device").replaceAll(" *, *", ","); + } + + if (token == null) { + LOGGER.warn("Pushover token not found"); + return; + } + + if (puser == null) { + LOGGER.warn("Pushover user not found"); + return; + } + + Message message = new Message(); + message.token = token; + message.user = puser; + message.device = device; + message.message = NotificationFormatter.formatShortMessage(userId, event, position); + + 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); + } + +} diff --git a/src/main/java/org/traccar/protocol/ArnaviBinaryProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArnaviBinaryProtocolDecoder.java new file mode 100644 index 000000000..e957a6911 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArnaviBinaryProtocolDecoder.java @@ -0,0 +1,179 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Ivan Muratov (binakot@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class ArnaviBinaryProtocolDecoder extends BaseProtocolDecoder { + + private static final byte HEADER_START_SIGN = (byte) 0xff; + private static final byte HEADER_VERSION_1 = 0x22; + private static final byte HEADER_VERSION_2 = 0x23; + + private static final byte RECORD_PING = 0x00; + private static final byte RECORD_DATA = 0x01; + private static final byte RECORD_TEXT = 0x03; + private static final byte RECORD_FILE = 0x04; + private static final byte RECORD_BINARY = 0x06; + + private static final byte TAG_LATITUDE = 3; + private static final byte TAG_LONGITUDE = 4; + private static final byte TAG_COORD_PARAMS = 5; + + public ArnaviBinaryProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private void sendResponse(Channel channel, byte version, int index) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(0x7b); + if (version == HEADER_VERSION_1) { + response.writeByte(0x00); + response.writeByte((byte) index); + } else if (version == HEADER_VERSION_2) { + response.writeByte(0x04); + response.writeByte(0x00); + ByteBuffer time = ByteBuffer.allocate(4).putInt((int) (System.currentTimeMillis() / 1000)); + ((Buffer) time).position(0); + response.writeByte(Checksum.modulo256(time.slice())); + response.writeBytes(time); + } + response.writeByte(0x7d); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + private Position decodePosition(DeviceSession deviceSession, ByteBuf buf, int length, Date time) { + + final Position position = new Position(); + position.setProtocol(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(time); + + int readBytes = 0; + while (readBytes < length) { + short tag = buf.readUnsignedByte(); + switch (tag) { + case TAG_LATITUDE: + position.setLatitude(buf.readFloatLE()); + position.setValid(true); + break; + + case TAG_LONGITUDE: + position.setLongitude(buf.readFloatLE()); + position.setValid(true); + break; + + case TAG_COORD_PARAMS: + position.setCourse(buf.readUnsignedByte() * 2); + position.setAltitude(buf.readUnsignedByte() * 10); + byte satellites = buf.readByte(); + position.set(Position.KEY_SATELLITES, satellites & 0x0F + (satellites >> 4) & 0x0F); + position.setSpeed(buf.readUnsignedByte()); + break; + + default: + buf.skipBytes(4); + break; + } + + readBytes += 1 + 4; + } + + return position; + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + byte startSign = buf.readByte(); + + if (startSign == HEADER_START_SIGN) { + + byte version = buf.readByte(); + + String imei = String.valueOf(buf.readLongLE()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + + if (deviceSession != null) { + sendResponse(channel, version, 0); + } + + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + int index = buf.readUnsignedByte(); + + byte recordType = buf.readByte(); + while (buf.readableBytes() > 0) { + switch (recordType) { + case RECORD_PING: + case RECORD_DATA: + case RECORD_TEXT: + case RECORD_FILE: + case RECORD_BINARY: + int length = buf.readUnsignedShortLE(); + Date time = new Date(buf.readUnsignedIntLE() * 1000); + + if (recordType == RECORD_DATA) { + positions.add(decodePosition(deviceSession, buf, length, time)); + } else { + buf.readBytes(length); + } + + buf.readUnsignedByte(); // checksum + break; + + default: + return null; + } + + recordType = buf.readByte(); + } + + sendResponse(channel, HEADER_VERSION_1, index); + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/ArnaviFrameDecoder.java b/src/main/java/org/traccar/protocol/ArnaviFrameDecoder.java new file mode 100644 index 000000000..473e8b2c7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArnaviFrameDecoder.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Ivan Muratov (binakot@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; +import org.traccar.helper.BufferUtil; + +public class ArnaviFrameDecoder extends BaseFrameDecoder { + + private static final int HEADER_LENGTH = 10; + private static final int PACKET_WRAPPER_LENGTH = 8; + private static final int RESULT_TYPE = 0xfd; + private static final byte PACKAGE_END_SIGN = 0x5d; + + private boolean firstPacket = true; + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 4) { + return null; + } + + if (buf.getByte(buf.readerIndex()) == '$') { + + int index = BufferUtil.indexOf("\r\n", buf); + if (index > 0) { + ByteBuf frame = buf.readRetainedSlice(index - buf.readerIndex()); + buf.skipBytes(2); + return frame; + } + + } else { + + int length; + if (firstPacket) { + firstPacket = false; + length = HEADER_LENGTH; + } else { + int type = buf.getUnsignedByte(1); + if (type == RESULT_TYPE) { + length = 4; + } else { + int index = 2; + while (index + PACKET_WRAPPER_LENGTH < buf.readableBytes() + && buf.getByte(index) != PACKAGE_END_SIGN) { + index += PACKET_WRAPPER_LENGTH + buf.getUnsignedShortLE(index + 1); + } + if (buf.getByte(index) != PACKAGE_END_SIGN) { + return null; + } + length = index + 1; + } + } + + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/ArnaviProtocol.java b/src/main/java/org/traccar/protocol/ArnaviProtocol.java index afe491865..aecb42c8c 100644 --- a/src/main/java/org/traccar/protocol/ArnaviProtocol.java +++ b/src/main/java/org/traccar/protocol/ArnaviProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,6 @@ */ package org.traccar.protocol; -import io.netty.handler.codec.LineBasedFrameDecoder; -import io.netty.handler.codec.string.StringDecoder; -import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; @@ -28,9 +25,7 @@ public class ArnaviProtocol extends BaseProtocol { addServer(new TrackerServer(false, getName()) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { - pipeline.addLast(new LineBasedFrameDecoder(1024)); - pipeline.addLast(new StringDecoder()); - pipeline.addLast(new StringEncoder()); + 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 7996cf429..68a70c944 100644 --- a/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,91 +15,35 @@ */ package org.traccar.protocol; +import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; -import org.traccar.DeviceSession; import org.traccar.Protocol; -import org.traccar.helper.DateBuilder; -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 ArnaviProtocolDecoder extends BaseProtocolDecoder { + private final ArnaviTextProtocolDecoder textProtocolDecoder; + private final ArnaviBinaryProtocolDecoder binaryProtocolDecoder; + public ArnaviProtocolDecoder(Protocol protocol) { super(protocol); + textProtocolDecoder = new ArnaviTextProtocolDecoder(protocol); + binaryProtocolDecoder = new ArnaviBinaryProtocolDecoder(protocol); } - private static final Pattern PATTERN = new PatternBuilder() - .text("$AV,") - .number("Vd,") // type - .number("(d+),") // device id - .number("(d+),") // index - .number("(d+),") // power - .number("(d+),") // battery - .number("-?d+,") - .expression("[01],") // movement - .expression("([01]),") // ignition - .number("(d+),") // input - .number("d+,d+,") // input 1 - .number("d+,d+,").optional() // input 2 - .expression("[01],") // fix type - .number("(d+),") // satellites - .groupBegin() - .number("(d+.d+)?,") // altitude - .number("(?:d+.d+)?,") // geoid height - .groupEnd("?") - .number("(dd)(dd)(dd),") // time (hhmmss) - .number("(dd)(dd.d+)([NS]),") // latitude - .number("(ddd)(dd.d+)([EW]),") // longitude - .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 { - Parser parser = new Parser(PATTERN, (String) msg); - if (!parser.matches()) { - return null; - } + ByteBuf buf = (ByteBuf) msg; - Position position = new Position(getProtocolName()); - - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); - if (deviceSession == null) { - return null; + if (buf.getByte(buf.readerIndex()) == '$') { + return textProtocolDecoder.decode(channel, remoteAddress, msg); + } else { + return binaryProtocolDecoder.decode(channel, remoteAddress, msg); } - position.setDeviceId(deviceSession.getDeviceId()); - - position.set(Position.KEY_INDEX, parser.nextInt()); - position.set(Position.KEY_POWER, parser.nextInt() * 0.01); - position.set(Position.KEY_BATTERY, parser.nextInt() * 0.01); - position.set(Position.KEY_IGNITION, parser.nextInt() == 1); - position.set(Position.KEY_INPUT, parser.nextInt()); - position.set(Position.KEY_SATELLITES, parser.nextInt()); - - position.setAltitude(parser.nextDouble(0)); - - DateBuilder dateBuilder = new DateBuilder() - .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); - - position.setValid(true); - 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; } } diff --git a/src/main/java/org/traccar/protocol/ArnaviTextProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArnaviTextProtocolDecoder.java new file mode 100644 index 000000000..b99869e6e --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArnaviTextProtocolDecoder.java @@ -0,0 +1,108 @@ +/* + * 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.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +public class ArnaviTextProtocolDecoder extends BaseProtocolDecoder { + + public ArnaviTextProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$AV,") + .number("Vd,") // type + .number("(d+),") // device id + .number("(d+),") // index + .number("(d+),") // power + .number("(d+),") // battery + .number("-?d+,") + .expression("[01],") // movement + .expression("([01]),") // ignition + .number("(d+),") // input + .number("d+,d+,") // input 1 + .number("d+,d+,").optional() // input 2 + .expression("[01],") // fix type + .number("(d+),") // satellites + .groupBegin() + .number("(d+.d+)?,") // altitude + .number("(?:d+.d+)?,") // geoid height + .groupEnd("?") + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(dd)(dd.d+)([NS]),") // latitude + .number("(ddd)(dd.d+)([EW]),") // longitude + .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 { + + ByteBuf buf = (ByteBuf) msg; + Parser parser = new Parser(PATTERN, buf.toString(StandardCharsets.US_ASCII)); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_INDEX, parser.nextInt()); + position.set(Position.KEY_POWER, parser.nextInt() * 0.01); + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.01); + position.set(Position.KEY_IGNITION, parser.nextInt() == 1); + position.set(Position.KEY_INPUT, parser.nextInt()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + + position.setAltitude(parser.nextDouble(0)); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setValid(true); + 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; + } + +} diff --git a/src/main/java/org/traccar/protocol/BceProtocolDecoder.java b/src/main/java/org/traccar/protocol/BceProtocolDecoder.java index 30f9bb1f3..a259e027a 100644 --- a/src/main/java/org/traccar/protocol/BceProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/BceProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,10 +83,10 @@ public class BceProtocolDecoder extends BaseProtocolDecoder { buf.skipBytes(4); } if (BitUtil.check(mask, 12)) { - buf.skipBytes(2); + position.set("fuel1", buf.readUnsignedShort()); } if (BitUtil.check(mask, 13)) { - buf.skipBytes(2); + position.set("fuel2", buf.readUnsignedShort()); } if (BitUtil.check(mask, 14)) { @@ -113,7 +113,7 @@ public class BceProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte()); } if (BitUtil.check(mask, 4)) { - position.set(Position.KEY_RPM, buf.readUnsignedShortLE()); + position.set(Position.KEY_RPM, buf.readUnsignedShortLE() * 0.125); } if (BitUtil.check(mask, 5)) { position.set(Position.KEY_HOURS, buf.readUnsignedIntLE()); @@ -122,7 +122,7 @@ public class BceProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); } if (BitUtil.check(mask, 7)) { - position.set(Position.KEY_COOLANT_TEMP, (int) buf.readByte()); + position.set(Position.KEY_COOLANT_TEMP, buf.readByte() - 40); } if (BitUtil.check(mask, 8)) { position.set("fuel2", buf.readUnsignedByte()); @@ -217,9 +217,9 @@ public class BceProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedShortLE(); } if (BitUtil.check(mask, 6)) { - buf.readUnsignedByte(); // maximum acceleration - buf.readUnsignedByte(); // maximum deceleration - buf.readUnsignedByte(); // maximum cornering + position.set("maxAcceleration", buf.readUnsignedByte() * 0.02); + position.set("maxBraking", buf.readUnsignedByte() * 0.02); + position.set("maxCornering", buf.readUnsignedByte() * 0.02); } if (BitUtil.check(mask, 7)) { buf.skipBytes(16); diff --git a/src/main/java/org/traccar/protocol/BlueProtocol.java b/src/main/java/org/traccar/protocol/BlueProtocol.java index 79f0714ec..d5dc5c421 100644 --- a/src/main/java/org/traccar/protocol/BlueProtocol.java +++ b/src/main/java/org/traccar/protocol/BlueProtocol.java @@ -26,7 +26,7 @@ public class BlueProtocol extends BaseProtocol { addServer(new TrackerServer(false, getName()) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { - pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 1, 2, 3, 0)); + 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 98a8ae565..f35ac6fbe 100644 --- a/src/main/java/org/traccar/protocol/BlueProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/BlueProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,14 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; import org.traccar.helper.DateBuilder; import org.traccar.model.Position; @@ -41,6 +44,41 @@ public class BlueProtocolDecoder extends BaseProtocolDecoder { return negative ? -coordinate : coordinate; } + private void sendResponse(Channel channel, int deviceIndex) { + if (channel != null) { + + ByteBuf response = Unpooled.buffer(); + response.writeByte(0xaa); + response.writeShort(2 + 1 + 1 + 6 + 1); + response.writeByte(0x86); // version + response.writeByte(0); + + response.writeByte(6); // data length + response.writeByte(0xa4); // type + response.writeByte(0); // server index + response.writeByte(deviceIndex); + response.writeByte(0); + response.writeByte(0); + + response.writeByte(Checksum.xor(response.nioBuffer(1, response.writerIndex() - 1))); + + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + private String decodeAlarm(int value) { + switch (value) { + case 1: + return Position.ALARM_SOS; + case 8: + return Position.ALARM_OVERSPEED; + case 19: + return Position.ALARM_LOW_POWER; + default: + return null; + } + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -66,7 +104,7 @@ public class BlueProtocolDecoder extends BaseProtocolDecoder { int frameEnd = buf.readerIndex() + buf.readUnsignedByte(); int type = buf.readUnsignedByte(); - buf.readUnsignedByte(); // reference id + int index = buf.readUnsignedByte(); buf.readUnsignedByte(); buf.readUnsignedByte(); // flags @@ -98,10 +136,29 @@ public class BlueProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedByte(); // status 2 buf.readUnsignedByte(); // status 3 - buf.readUnsignedByte(); // status 4 + + status = buf.readUnsignedByte(); // status 4 + int ignition = BitUtil.between(status, 2, 4); + if (ignition == 0b01) { + position.set(Position.KEY_IGNITION, false); + } + if (ignition == 0b10) { + position.set(Position.KEY_IGNITION, true); + } + buf.readUnsignedByte(); // status 5 buf.readUnsignedByte(); // status 6 + position.set(Position.KEY_STATUS, buf.readUnsignedShort()); + + } else if (type == 0x81) { + + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + + } else if (type == 0x84) { + + sendResponse(channel, index); + } buf.readerIndex(frameEnd); diff --git a/src/main/java/org/traccar/protocol/EskyProtocol.java b/src/main/java/org/traccar/protocol/EskyProtocol.java index aaa92da58..fb047c207 100644 --- a/src/main/java/org/traccar/protocol/EskyProtocol.java +++ b/src/main/java/org/traccar/protocol/EskyProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,14 @@ public class EskyProtocol extends BaseProtocol { pipeline.addLast(new EskyProtocolDecoder(EskyProtocol.this)); } }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + 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 641b2e28f..1a4f9b906 100644 --- a/src/main/java/org/traccar/protocol/EskyProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/EskyProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,12 @@ 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.NetworkMessage; import org.traccar.Protocol; +import org.traccar.helper.BitUtil; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; import org.traccar.helper.UnitsConverter; @@ -35,7 +38,7 @@ public class EskyProtocolDecoder extends BaseProtocolDecoder { private static final Pattern PATTERN = new PatternBuilder() .expression("..;") // header - .number("d+;") // index + .number("d+;") .number("(d+);") // imei .text("R;") // data type .number("(d+)[+;]") // satellites @@ -58,7 +61,8 @@ public class EskyProtocolDecoder extends BaseProtocolDecoder { protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { - Parser parser = new Parser(PATTERN, (String) msg); + String sentence = (String) msg; + Parser parser = new Parser(PATTERN, sentence); if (!parser.matches()) { return null; } @@ -81,13 +85,21 @@ public class EskyProtocolDecoder extends BaseProtocolDecoder { position.setCourse(parser.nextDouble()); if (parser.hasNext(3)) { - position.set(Position.KEY_INPUT, parser.nextHexInt()); + int input = parser.nextHexInt(); + position.set(Position.KEY_IGNITION, !BitUtil.check(input, 0)); + position.set(Position.PREFIX_IN + 1, !BitUtil.check(input, 1)); + position.set(Position.PREFIX_IN + 2, !BitUtil.check(input, 2)); position.set(Position.KEY_EVENT, parser.nextInt()); position.set(Position.KEY_ODOMETER, parser.nextInt()); } position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001); + int index = sentence.lastIndexOf('+'); + if (index > 0 && channel instanceof DatagramChannel) { + channel.writeAndFlush(new NetworkMessage("ACK," + sentence.substring(index + 1) + "#", remoteAddress)); + } + return position; } diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java index 40e146e0b..9bc7cb504 100644 --- a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -116,6 +116,32 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { return Position.ALARM_POWER_CUT; case 16: return Position.ALARM_POWER_RESTORED; + case 17: + return Position.ALARM_LOW_BATTERY; + case 18: + return Position.ALARM_OVERSPEED; + case 20: + return Position.ALARM_GPS_ANTENNA_CUT; + case 21: + return Position.ALARM_VIBRATION; + case 23: + return Position.ALARM_ACCELERATION; + case 24: + return Position.ALARM_BRAKING; + case 27: + return Position.ALARM_FATIGUE_DRIVING; + case 30: + case 32: + return Position.ALARM_JAMMING; + case 33: + return Position.ALARM_GEOFENCE_EXIT; + case 34: + return Position.ALARM_GEOFENCE_ENTER; + case 35: + return Position.ALARM_IDLE; + case 40: + case 41: + return Position.ALARM_TEMPERATURE; default: return null; } diff --git a/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java b/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java index 043839be9..087861635 100644 --- a/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BcdUtil; +import org.traccar.helper.Checksum; import org.traccar.helper.DateBuilder; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; @@ -58,16 +59,16 @@ public class GatorProtocolDecoder extends BaseProtocolDecoder { return String.format("%02d%02d%02d%02d%02d", d1, d2, d3, d4, d5); } - private void sendResponse(Channel channel, SocketAddress remoteAddress, byte calibration) { + private void sendResponse(Channel channel, SocketAddress remoteAddress, int type, int checksum) { if (channel != null) { ByteBuf response = Unpooled.buffer(); - response.writeByte(0x24); response.writeByte(0x24); // header - response.writeByte(MSG_HEARTBEAT); // size - response.writeShort(5); - response.writeByte(calibration); - response.writeByte(0); // main order - response.writeByte(0); // slave order - response.writeByte(1); // calibration + response.writeShort(0x2424); // header + response.writeByte(MSG_HEARTBEAT); + response.writeShort(5); // length + response.writeByte(checksum); + response.writeByte(type); + response.writeByte(0); // subtype + response.writeByte(Checksum.xor(response.nioBuffer(2, response.writerIndex()))); response.writeByte(0x0D); channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } @@ -87,7 +88,7 @@ public class GatorProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); - sendResponse(channel, remoteAddress, buf.getByte(buf.writerIndex() - 2)); + sendResponse(channel, remoteAddress, type, buf.getByte(buf.writerIndex() - 2)); if (type == MSG_POSITION_DATA || type == MSG_ROLLCALL_RESPONSE || type == MSG_ALARM_DATA || type == MSG_BLIND_AREA) { diff --git a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java index 26af8d5af..ab89f10c8 100644 --- a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import io.netty.channel.Channel; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; +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; @@ -96,21 +97,23 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder { rootElement.appendChild(state); Element stateMessage = document.createElement("stateMessage"); - stateMessage.appendChild(document.createTextNode("Messages received and stored successfully")); + stateMessage.appendChild(document.createTextNode("Store OK")); rootElement.appendChild(stateMessage); Transformer transformer = TransformerFactory.newInstance().newTransformer(); ByteBuf content = Unpooled.buffer(); transformer.transform(new DOMSource(document), new StreamResult(new ByteBufOutputStream(content))); - FullHttpResponse response = new DefaultFullHttpResponse( - HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content); if (channel != null) { + FullHttpResponse response = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content); + response.headers() + .add(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()) + .add(HttpHeaderNames.CONTENT_TYPE, "text/xml"); channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); } } - @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java index c435b6cec..946652b03 100644 --- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java @@ -730,6 +730,14 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { decodeStatus(position, buf); } + if (type == MSG_GPS_LBS_1 && buf.readableBytes() > 75 + 6) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + String data = buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); + buf.readUnsignedByte(); // alarm + buf.readUnsignedByte(); // swiped + position.set("driverLicense", data.trim()); + } + if (type == MSG_GPS_LBS_1 && buf.readableBytes() == 2 + 6) { int mask = buf.readUnsignedShort(); position.set(Position.KEY_IGNITION, BitUtil.check(mask, 8 + 7)); diff --git a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java index 9449e2d5c..eac06bbfe 100644 --- a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,10 @@ import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; @@ -137,6 +140,8 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ODOMETER, buf.readUnsignedShort() * 1000); + Network network = new Network(); + while (buf.readableBytes() > 4) { int subtype = buf.readUnsignedShort(); int length = buf.readUnsignedShort() - 4; @@ -161,12 +166,34 @@ public class HuaShengProtocolDecoder extends BaseProtocolDecoder { position.set( Position.KEY_VIN, buf.readCharSequence(length, StandardCharsets.US_ASCII).toString()); break; + case 0x0020: + String[] cells = buf.readCharSequence( + length, StandardCharsets.US_ASCII).toString().split("\\+"); + for (String cell : cells) { + String[] values = cell.split("@"); + network.addCellTower(CellTower.from( + Integer.parseInt(values[0]), Integer.parseInt(values[1]), + Integer.parseInt(values[2], 16), Integer.parseInt(values[3], 16))); + } + break; + case 0x0021: + String[] points = buf.readCharSequence( + length, StandardCharsets.US_ASCII).toString().split("\\+"); + for (String point : points) { + String[] values = point.split("@"); + network.addWifiAccessPoint(WifiAccessPoint.from(values[0], Integer.parseInt(values[1]))); + } + break; default: buf.skipBytes(length); break; } } + if (network.getCellTowers() != null || network.getWifiAccessPoints() != null) { + position.setNetwork(network); + } + sendResponse(channel, MSG_POSITION_RSP, index, null); return position; diff --git a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java index e8d77f1a8..6a107d67d 100644 --- a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java @@ -51,7 +51,7 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { .groupEnd() .number("(d{15}),") // imei .groupBegin() - .expression("(..),") // status + .expression("([^,]{2}),") // status .or() .expression("[^,]*,") // vehicle registration .number("([01]),").optional() // valid diff --git a/src/main/java/org/traccar/protocol/LeafSpyProtocolDecoder.java b/src/main/java/org/traccar/protocol/LeafSpyProtocolDecoder.java index 5b352a961..ad0c9bd32 100644 --- a/src/main/java/org/traccar/protocol/LeafSpyProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/LeafSpyProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2019 Jesse Hills (jesserockz@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,16 +19,12 @@ 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.DefaultFullHttpResponse; -import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.QueryStringDecoder; import org.traccar.BaseHttpProtocolDecoder; import org.traccar.DeviceSession; import org.traccar.Protocol; import org.traccar.model.Position; -import org.traccar.NetworkMessage; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; @@ -122,13 +118,8 @@ public class LeafSpyProtocolDecoder extends BaseHttpProtocolDecoder { } if (position.getDeviceId() != 0) { - if (channel != null) { - HttpResponse response = new DefaultFullHttpResponse( - HttpVersion.HTTP_1_1, - HttpResponseStatus.OK, + sendResponse(channel, HttpResponseStatus.OK, Unpooled.copiedBuffer("\"status\":\"0\"", StandardCharsets.US_ASCII)); - channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); - } return position; } else { sendResponse(channel, HttpResponseStatus.BAD_REQUEST); diff --git a/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java index cbfc3660a..bd66cdc4b 100644 --- a/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder { } private static final Pattern PATTERN = new PatternBuilder() - .number("(dd)(dd)(dd).?d*,") // time (hhmmss) + .number("(d+)(dd)(dd).?d*,") // time (hhmmss) .expression("([AV]),") // validity .number("(d+)(dd.d+),") // latitude .expression("([NS]),") diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java index 55260ef0c..529496928 100644 --- a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java @@ -70,7 +70,8 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { .number("(d+)|") // mnc .number("(x+)|") // lac .number("(x+),") // cid - .number("(x+),") // state + .number("(xx)") // input + .number("(xx),") // output .number("(x+)?|") // adc1 .number("(x+)?|") // adc2 .number("(x+)?|") // adc3 @@ -149,39 +150,38 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { } position.setDeviceId(deviceSession.getDeviceId()); - int event = parser.nextInt(0); + int event = parser.nextInt(); position.set(Position.KEY_EVENT, event); position.set(Position.KEY_ALARM, decodeAlarm(event)); - position.setLatitude(parser.nextDouble(0)); - position.setLongitude(parser.nextDouble(0)); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); position.setTime(parser.nextDateTime()); position.setValid(parser.next().equals("A")); position.set(Position.KEY_SATELLITES, parser.nextInt()); - int rssi = parser.nextInt(0); + int rssi = parser.nextInt(); - position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); - position.setCourse(parser.nextDouble(0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); position.set(Position.KEY_HDOP, parser.nextDouble()); - position.setAltitude(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble()); - position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + position.set(Position.KEY_ODOMETER, parser.nextInt()); position.set("runtime", parser.next()); position.setNetwork(new Network(CellTower.from( - parser.nextInt(0), parser.nextInt(0), parser.nextHexInt(0), parser.nextHexInt(0), rssi))); + parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt(), rssi))); - position.set(Position.KEY_STATUS, parser.next()); + position.set(Position.KEY_OUTPUT, parser.nextHexInt()); + position.set(Position.KEY_INPUT, parser.nextHexInt()); for (int i = 1; i <= 3; i++) { - if (parser.hasNext()) { - position.set(Position.PREFIX_ADC + i, parser.nextHexInt(0)); - } + position.set(Position.PREFIX_ADC + i, parser.nextHexInt()); } String deviceModel = Context.getIdentityManager().getById(deviceSession.getDeviceId()).getModel(); diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java index b6f257d2c..b8ab134c5 100644 --- a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java @@ -154,6 +154,15 @@ public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { mac.substring(0, mac.length() - 1), rssi)); } break; + case 0x23: + if (endIndex > buf.readerIndex()) { + buf.skipBytes(6); // mac + } + if (endIndex > buf.readerIndex()) { + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setLongitude(buf.readIntLE() * 0.0000001); + } + break; case 0x24: position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); long status = buf.readUnsignedIntLE(); diff --git a/src/main/java/org/traccar/protocol/OkoProtocolDecoder.java b/src/main/java/org/traccar/protocol/OkoProtocolDecoder.java index 5adf61494..4d9c9afc4 100644 --- a/src/main/java/org/traccar/protocol/OkoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/OkoProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ public class OkoProtocolDecoder extends BaseProtocolDecoder { private static final Pattern PATTERN = new PatternBuilder() .text("{") .number("(d{15}),").optional() // imei - .number("(dd)(dd)(dd).d+,") // time + .number("(dd)(dd)(dd)(?:.d+)?,") // time .expression("([AV]),") // validity .number("(dd)(dd.d+),") // latitude .expression("([NS]),") @@ -46,14 +46,23 @@ public class OkoProtocolDecoder extends BaseProtocolDecoder { .number("(d+.?d*)?,") // course .number("(dd)(dd)(dd),") // date (ddmmyy) .number("(d+),") // satellites - .number("(d+.d+),") // adc + .number("(d+.d+|xx),") // adc .number("(xx),") // event - .number("(d+.d+),") // power + .number("(d+.d+|xx),") // power .number("d,") // memory status - .number("(xx)") // io + .number("(xx)?") // io .any() .compile(); + private double decodeVoltage(Parser parser) { + String value = parser.next(); + if (value.contains(".")) { + return Double.parseDouble(value); + } else { + return Integer.parseInt(value, 16) * 0.1; + } + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -89,9 +98,9 @@ public class OkoProtocolDecoder extends BaseProtocolDecoder { position.setTime(dateBuilder.getDate()); position.set(Position.KEY_SATELLITES, parser.nextInt()); - position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.PREFIX_ADC + 1, decodeVoltage(parser)); position.set(Position.KEY_EVENT, parser.next()); - position.set(Position.KEY_POWER, parser.nextDouble()); + position.set(Position.KEY_POWER, decodeVoltage(parser)); position.set(Position.KEY_INPUT, parser.nextHexInt()); return position; diff --git a/src/main/java/org/traccar/protocol/OmnicommFrameDecoder.java b/src/main/java/org/traccar/protocol/OmnicommFrameDecoder.java index 1caf6ceb9..314f19757 100644 --- a/src/main/java/org/traccar/protocol/OmnicommFrameDecoder.java +++ b/src/main/java/org/traccar/protocol/OmnicommFrameDecoder.java @@ -27,11 +27,11 @@ public class OmnicommFrameDecoder extends BaseFrameDecoder { protected Object decode( ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { - if (buf.readableBytes() < 10) { + if (buf.readableBytes() < 6) { return null; } - int endIndex = buf.getUnsignedShortLE(2) + buf.readerIndex() + 6; + int endIndex = buf.getUnsignedShortLE(buf.readerIndex() + 2) + buf.readerIndex() + 6; if (buf.writerIndex() < endIndex) { return null; } diff --git a/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java b/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java index cd8b74c9a..6e9cf52a5 100644 --- a/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -111,16 +111,29 @@ public class OmnicommProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); + if (message.hasGeneral()) { + OmnicommMessageOuterClass.OmnicommMessage.General data = message.getGeneral(); + position.set(Position.KEY_POWER, data.getUboard() * 0.1); + position.set(Position.KEY_BATTERY_LEVEL, data.getBatLife()); + } + if (message.hasNAV()) { - OmnicommMessageOuterClass.OmnicommMessage.NAV nav = message.getNAV(); + OmnicommMessageOuterClass.OmnicommMessage.NAV data = message.getNAV(); position.setValid(true); - position.setTime(new Date((nav.getGPSTime() + 1230768000) * 1000L)); // from 2009-01-01 12:00 - position.setLatitude(nav.getLAT() * 0.0000001); - position.setLongitude(nav.getLON() * 0.0000001); - position.setSpeed(UnitsConverter.knotsFromKph(nav.getGPSVel() * 0.1)); - position.setCourse(nav.getGPSDir()); - position.setAltitude(nav.getGPSAlt() * 0.1); - position.set(Position.KEY_SATELLITES, nav.getGPSNSat()); + position.setTime(new Date((data.getGPSTime() + 1230768000) * 1000L)); // from 2009-01-01 12:00 + position.setLatitude(data.getLAT() * 0.0000001); + position.setLongitude(data.getLON() * 0.0000001); + position.setSpeed(UnitsConverter.knotsFromKph(data.getGPSVel() * 0.1)); + position.setCourse(data.getGPSDir()); + position.setAltitude(data.getGPSAlt() * 0.1); + position.set(Position.KEY_SATELLITES, data.getGPSNSat()); + } + + if (message.hasLLSDt()) { + OmnicommMessageOuterClass.OmnicommMessage.LLSDt data = message.getLLSDt(); + position.set("fuel1Temp", data.getTLLS1()); + position.set("fuel1", data.getCLLS1()); + position.set("fuel1State", data.getFLLS1()); } if (position.getFixTime() != null) { diff --git a/src/main/java/org/traccar/protocol/OsmAndProtocolDecoder.java b/src/main/java/org/traccar/protocol/OsmAndProtocolDecoder.java index 3bc71de81..ec9bbc240 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 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,19 @@ */ 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.Context; import org.traccar.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; import org.traccar.model.Network; import org.traccar.model.Position; import org.traccar.model.WifiAccessPoint; @@ -173,7 +177,18 @@ public class OsmAndProtocolDecoder extends BaseHttpProtocolDecoder { } if (position.getDeviceId() != 0) { - sendResponse(channel, HttpResponseStatus.OK); + String response = null; + CommandsManager commandsManager = Context.getCommandsManager(); + if (commandsManager != null) { + for (Command command : commandsManager.readQueuedCommands(position.getDeviceId(), 1)) { + response = command.getString(Command.KEY_DATA); + } + } + if (response != null) { + sendResponse(channel, HttpResponseStatus.OK, Unpooled.copiedBuffer(response, StandardCharsets.UTF_8)); + } else { + sendResponse(channel, HttpResponseStatus.OK); + } return position; } else { sendResponse(channel, HttpResponseStatus.BAD_REQUEST); @@ -181,4 +196,8 @@ public class OsmAndProtocolDecoder extends BaseHttpProtocolDecoder { } } + @Override + protected void sendQueuedCommands(Channel channel, SocketAddress remoteAddress, long deviceId) { + } + } diff --git a/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java index 15e08d7b1..d49a73a86 100644 --- a/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,7 +63,7 @@ public class PacificTrackProtocolDecoder extends BaseProtocolDecoder { while (buf.isReadable()) { int segmentId = readBitExt(buf); - int segmentLength = readBitExt(buf); + int segmentEnd = readBitExt(buf) + buf.readerIndex(); switch (segmentId) { case 0x01: @@ -83,12 +83,60 @@ public class PacificTrackProtocolDecoder extends BaseProtocolDecoder { position.setSpeed(UnitsConverter.knotsFromKph(BitUtil.to(speedAndCourse, 12) * 0.1)); position.set(Position.KEY_INDEX, buf.readUnsignedShort()); break; + case 0x92: + while (buf.readerIndex() < segmentEnd) { + int field = buf.readUnsignedByte(); + int fieldPrefix = BitUtil.from(field, 5); + if (fieldPrefix < 0b100) { + switch (BitUtil.between(field, 2, 5)) { + case 0b000: + position.set("bus", BitUtil.to(field, 2)); + case 0b001: + position.set("currentGear", BitUtil.to(field, 2)); + break; + default: + break; + } + } else if (fieldPrefix < 0b101) { + switch (BitUtil.to(field, 5)) { + case 0b00000: + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedByte()); + break; + case 0b00001: + position.set(Position.KEY_RPM, buf.readUnsignedByte() * 32); + break; + default: + buf.readUnsignedByte(); + break; + } + } else if (fieldPrefix < 0b110) { + buf.readUnsignedShort(); + } else if (fieldPrefix < 0b111) { + switch (BitUtil.to(field, 5)) { + case 0b00000: + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 100); + break; + case 0b00001: + position.set(Position.KEY_HOURS, buf.readUnsignedInt() * 180); + break; + case 0b00010: + position.set("idleHours", buf.readUnsignedInt() * 180); + break; + default: + buf.readUnsignedInt(); + break; + } + } else { + buf.skipBytes(buf.readUnsignedByte()); + } + } + break; case 0x100: String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(0, 15); deviceSession = getDeviceSession(channel, remoteAddress, imei); break; default: - buf.skipBytes(segmentLength); + buf.readerIndex(segmentEnd); break; } } diff --git a/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java b/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java index 47aa86da7..26ce2fe53 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 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2014 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,15 +18,11 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; -import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.FullHttpResponse; 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.BaseHttpProtocolDecoder; import org.traccar.DeviceSession; -import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; @@ -37,19 +33,14 @@ import java.nio.charset.StandardCharsets; import java.util.LinkedList; import java.util.List; -public class PiligrimProtocolDecoder extends BaseProtocolDecoder { +public class PiligrimProtocolDecoder extends BaseHttpProtocolDecoder { public PiligrimProtocolDecoder(Protocol protocol) { super(protocol); } private void sendResponse(Channel channel, String message) { - if (channel != null) { - FullHttpResponse response = new DefaultFullHttpResponse( - HttpVersion.HTTP_1_1, HttpResponseStatus.OK, - Unpooled.copiedBuffer(message, StandardCharsets.US_ASCII)); - channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); - } + sendResponse(channel, HttpResponseStatus.OK, Unpooled.copiedBuffer(message, StandardCharsets.US_ASCII)); } public static final int MSG_GPS = 0xF1; diff --git a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java index 8981c117b..05601ed51 100644 --- a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java index 304f61836..5fc81085b 100644 --- a/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,6 +48,31 @@ public class SigfoxProtocolDecoder extends BaseHttpProtocolDecoder { super(protocol); } + private boolean jsonContains(JsonObject json, String key) { + if (json.containsKey(key)) { + JsonValue value = json.get(key); + if (value.getValueType() == JsonValue.ValueType.STRING) { + return !((JsonString) value).getString().equals("null"); + + } else { + return true; + } + } + return false; + } + + private boolean getJsonBoolean(JsonObject json, String key) { + JsonValue value = json.get(key); + if (value != null) { + if (value.getValueType() == JsonValue.ValueType.STRING) { + return Boolean.parseBoolean(((JsonString) value).getString()); + } else { + return value.getValueType() == JsonValue.ValueType.TRUE; + } + } + return false; + } + private int getJsonInt(JsonObject json, String key) { JsonValue value = json.get(key); if (value != null) { @@ -83,7 +108,14 @@ public class SigfoxProtocolDecoder extends BaseHttpProtocolDecoder { } JsonObject json = Json.createReader(new StringReader(content)).readObject(); - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, json.getString("device")); + String deviceId; + if (json.containsKey("device")) { + deviceId = json.getString("device"); + } else { + deviceId = json.getString("deviceId"); + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, deviceId); if (deviceSession == null) { sendResponse(channel, HttpResponseStatus.BAD_REQUEST); return null; @@ -92,29 +124,36 @@ public class SigfoxProtocolDecoder extends BaseHttpProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - if (json.containsKey("time")) { + if (jsonContains(json, "time")) { position.setTime(new Date(getJsonInt(json, "time") * 1000L)); + } else if (jsonContains(json, "positionTime")) { + position.setTime(new Date(getJsonInt(json, "positionTime") * 1000L)); } else { position.setTime(new Date()); } - if (json.containsKey("location") - || json.containsKey("lat") && json.containsKey("lng") && !json.containsKey("data")) { + if (jsonContains(json, "lastSeen")) { + position.setDeviceTime(new Date(getJsonInt(json, "lastSeen") * 1000L)); + } + + if (jsonContains(json, "location") + || jsonContains(json, "lat") && jsonContains(json, "lng") && !jsonContains(json, "data") + || jsonContains(json, "latitude") && jsonContains(json, "longitude") && !jsonContains(json, "data")) { JsonObject location; - if (json.containsKey("location")) { + if (jsonContains(json, "location")) { location = json.getJsonObject("location"); } else { location = json; } position.setValid(true); - position.setLatitude(getJsonDouble(location, "lat")); - position.setLongitude(getJsonDouble(location, "lng")); + position.setLatitude(getJsonDouble(location, jsonContains(location, "lat") ? "lat" : "latitude")); + position.setLongitude(getJsonDouble(location, jsonContains(location, "lng") ? "lng" : "longitude")); - } else { + } else if (jsonContains(json, "data") || jsonContains(json, "payload")) { - String data = json.getString(json.containsKey("data") ? "data" : "payload"); + String data = json.getString(jsonContains(json, "data") ? "data" : "payload"); ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(data)); try { int event = buf.readUnsignedByte(); @@ -205,10 +244,19 @@ public class SigfoxProtocolDecoder extends BaseHttpProtocolDecoder { getLastLocation(position, position.getDeviceTime()); } - if (json.containsKey("rssi")) { + if (jsonContains(json, "moving")) { + position.set(Position.KEY_MOTION, getJsonBoolean(json, "moving")); + } + if (jsonContains(json, "magStatus")) { + position.set(Position.KEY_BLOCKED, getJsonBoolean(json, "magStatus")); + } + if (jsonContains(json, "temperature")) { + position.set(Position.KEY_DEVICE_TEMP, getJsonDouble(json, "temperature")); + } + if (jsonContains(json, "rssi")) { position.set(Position.KEY_RSSI, getJsonDouble(json, "rssi")); } - if (json.containsKey("seqNumber")) { + if (jsonContains(json, "seqNumber")) { position.set(Position.KEY_INDEX, getJsonInt(json, "seqNumber")); } diff --git a/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java b/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java index eae37386a..6fe54b0b0 100644 --- a/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,10 +77,24 @@ public class SolarPoweredProtocolDecoder extends BaseProtocolDecoder { position.setLongitude(-position.getLongitude()); } position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); - position.set(Position.KEY_DEVICE_TEMP, (int) buf.readByte()); + int temperature = buf.readUnsignedByte(); + if (BitUtil.check(temperature, 7)) { + position.set(Position.KEY_DEVICE_TEMP, -BitUtil.to(temperature, 7)); + } else { + position.set(Position.KEY_DEVICE_TEMP, BitUtil.to(temperature, 7)); + } position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 0.02); position.setCourse(buf.readUnsignedByte()); break; + case 0x83: + buf.readUnsignedInt(); // uptime + buf.readUnsignedInt(); // gps count + buf.readUnsignedInt(); // gsm count + buf.readUnsignedByte(); // positioning time + buf.readUnsignedByte(); // registration time + buf.readUnsignedByte(); // connection time + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + break; default: buf.skipBytes(length); break; diff --git a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java index bad6f03a9..2d1613e03 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 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -140,6 +140,11 @@ public class StarLinkProtocolDecoder extends BaseProtocolDecoder { event = Integer.parseInt(data[i]); position.set(Position.KEY_ALARM, decodeAlarm(event)); position.set(Position.KEY_EVENT, event); + if (event == 24) { + position.set(Position.KEY_IGNITION, true); + } else if (event == 25) { + position.set(Position.KEY_IGNITION, false); + } break; case "#PDT#": position.setFixTime(dateFormat.parse(data[i])); diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java index 915f764e1..978d768be 100644 --- a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java @@ -397,7 +397,7 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { if (isIncludeAdc(deviceSession.getDeviceId())) { for (int i = 1; i <= 3; i++) { - if (!values[index++].isEmpty()) { + if (index < values.length && !values[index++].isEmpty()) { position.set(Position.PREFIX_ADC + i, Double.parseDouble(values[index - 1])); } } @@ -614,10 +614,21 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); } + int alertId = 0; if (BitUtil.check(mask, 19)) { - int value = buf.readUnsignedByte(); + alertId = buf.readUnsignedByte(); if (type == 0x82) { - position.set(Position.KEY_ALARM, decodeAlert(value)); + position.set(Position.KEY_ALARM, decodeAlert(alertId)); + } + } + + if (BitUtil.check(mask, 20)) { + buf.readUnsignedShort(); // alert mod + } + + if (BitUtil.check(mask, 21)) { + if (alertId == 59) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, ByteBufUtil.hexDump(buf.readSlice(8))); } } diff --git a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java index 9b146ec90..3331ebb71 100644 --- a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -177,8 +177,7 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { } private Position decodePosition( - Channel channel, DeviceSession deviceSession, - ByteBuf buf, int type, int index, ByteBuf imei) { + Channel channel, DeviceSession deviceSession, ByteBuf buf, int type, int index, ByteBuf imei) { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); @@ -214,8 +213,10 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { position.set(Position.PREFIX_OUT + (i + 1), BitUtil.check(io, 7 + i)); } - position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort()); - position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShort()); + if (header != 0x2626) { + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShort()); + } } diff --git a/src/main/java/org/traccar/protocol/TelicProtocolDecoder.java b/src/main/java/org/traccar/protocol/TelicProtocolDecoder.java index 457687b2e..a4f9e2989 100644 --- a/src/main/java/org/traccar/protocol/TelicProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TelicProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2014 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,8 +42,8 @@ public class TelicProtocolDecoder extends BaseProtocolDecoder { .number("(dd)(dd)(dd)") // date (ddmmyy) .number("(dd)(dd)(dd),") // time (hhmmss) .groupBegin() - .number("(-?d{9}),") // longitude - .number("(-?d{8}),") // latitude + .number("(-?d{8,}),") // longitude + .number("(-?d{7,}),") // latitude .or() .number("(-?d+),") // longitude .number("(-?d+),") // latitude diff --git a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java index 7ad99b37e..3ce6438f8 100644 --- a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java @@ -38,7 +38,7 @@ public class Tlt2hProtocolDecoder extends BaseProtocolDecoder { private static final Pattern PATTERN_HEADER = new PatternBuilder() .number("#(d+)") // imei .expression("#[^#]*") // user - .number("#d+") // password + .number("#d*") // password .groupBegin() .number("#([01])") // door .number("#(d+)") // fuel voltage diff --git a/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java index ea72b7cb8..eee0e9ae8 100644 --- a/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ import org.traccar.BaseProtocolDecoder; import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.DateBuilder; import org.traccar.model.CellTower; import org.traccar.model.Network; import org.traccar.model.Position; @@ -44,11 +46,11 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_WIFI_OFFLINE = 0x17; public static final int MSG_WIFI = 0x69; - private void sendResponse(Channel channel, int type, ByteBuf content) { + private void sendResponse(Channel channel, int length, int type, ByteBuf content) { if (channel != null) { ByteBuf response = Unpooled.buffer(); response.writeShort(0x7878); - response.writeByte(1 + content.readableBytes()); + response.writeByte(length); response.writeByte(type); response.writeBytes(content); response.writeByte('\r'); @@ -75,7 +77,7 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { deviceSession = getDeviceSession(channel, remoteAddress, imei); ByteBuf content = Unpooled.buffer(); content.writeByte(deviceSession != null ? 0x01 : 0x44); - sendResponse(channel, type, content); + sendResponse(channel, length, type, content); return null; } else { deviceSession = getDeviceSession(channel, remoteAddress); @@ -95,7 +97,7 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { ByteBuf content = Unpooled.buffer(); content.writeBytes(time); - sendResponse(channel, type, content); + sendResponse(channel, length, type, content); return position; @@ -127,7 +129,7 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { if (length >= 7) { content.writeByte(signal); } - sendResponse(channel, type, content); + sendResponse(channel, length, type, content); return position; @@ -136,9 +138,17 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - getLastLocation(position, null); - 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)); + time.resetReaderIndex(); + + getLastLocation(position, dateBuilder.getDate()); Network network = new Network(); for (int i = 0; i < length; i++) { @@ -160,7 +170,7 @@ public class TopinProtocolDecoder extends BaseProtocolDecoder { ByteBuf content = Unpooled.buffer(); content.writeBytes(time); - sendResponse(channel, type, content); + sendResponse(channel, length, type, content); return position; diff --git a/src/main/java/org/traccar/protocol/WondexProtocol.java b/src/main/java/org/traccar/protocol/WondexProtocol.java index 035dd9160..6401fde85 100644 --- a/src/main/java/org/traccar/protocol/WondexProtocol.java +++ b/src/main/java/org/traccar/protocol/WondexProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import io.netty.handler.codec.string.StringEncoder; public class WondexProtocol extends BaseProtocol { public WondexProtocol() { - setSupportedCommands( + setSupportedDataCommands( Command.TYPE_GET_DEVICE_STATUS, Command.TYPE_GET_MODEM_STATUS, Command.TYPE_REBOOT_DEVICE, @@ -33,6 +33,13 @@ public class WondexProtocol extends BaseProtocol { Command.TYPE_GET_VERSION, Command.TYPE_IDENTIFICATION); setTextCommandEncoder(new WondexProtocolEncoder(this)); + setSupportedTextCommands( + Command.TYPE_GET_DEVICE_STATUS, + Command.TYPE_GET_MODEM_STATUS, + Command.TYPE_REBOOT_DEVICE, + Command.TYPE_POSITION_SINGLE, + Command.TYPE_GET_VERSION, + Command.TYPE_IDENTIFICATION); addServer(new TrackerServer(false, getName()) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { |