diff options
Diffstat (limited to 'src/main/java/org/traccar')
741 files changed, 77568 insertions, 0 deletions
diff --git a/src/main/java/org/traccar/BaseDataHandler.java b/src/main/java/org/traccar/BaseDataHandler.java new file mode 100644 index 000000000..48794b0d7 --- /dev/null +++ b/src/main/java/org/traccar/BaseDataHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import org.traccar.model.Position; + +public abstract class BaseDataHandler extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof Position) { + Position position = handlePosition((Position) msg); + if (position != null) { + ctx.fireChannelRead(position); + } + } else { + super.channelRead(ctx, msg); + } + } + + protected abstract Position handlePosition(Position position); + +} diff --git a/src/main/java/org/traccar/BaseFrameDecoder.java b/src/main/java/org/traccar/BaseFrameDecoder.java new file mode 100644 index 000000000..f90f90e4b --- /dev/null +++ b/src/main/java/org/traccar/BaseFrameDecoder.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; + +import java.util.List; + +public abstract class BaseFrameDecoder extends ByteToMessageDecoder { + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { + Object decoded = decode(ctx, ctx != null ? ctx.channel() : null, in); + if (decoded != null) { + out.add(decoded); + } + } + + protected abstract Object decode(ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception; + +} diff --git a/src/main/java/org/traccar/BaseHttpProtocolDecoder.java b/src/main/java/org/traccar/BaseHttpProtocolDecoder.java new file mode 100644 index 000000000..57a68acac --- /dev/null +++ b/src/main/java/org/traccar/BaseHttpProtocolDecoder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; + +public abstract class BaseHttpProtocolDecoder extends BaseProtocolDecoder { + + public BaseHttpProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public void sendResponse(Channel channel, HttpResponseStatus status) { + if (channel != null) { + HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status); + response.headers().add(HttpHeaderNames.CONTENT_LENGTH, 0); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + +} diff --git a/src/main/java/org/traccar/BasePipelineFactory.java b/src/main/java/org/traccar/BasePipelineFactory.java new file mode 100644 index 000000000..5a5850a86 --- /dev/null +++ b/src/main/java/org/traccar/BasePipelineFactory.java @@ -0,0 +1,173 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInboundHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOutboundHandler; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.timeout.IdleStateHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Keys; +import org.traccar.handler.DefaultDataHandler; +import org.traccar.handler.TimeHandler; +import org.traccar.handler.events.AlertEventHandler; +import org.traccar.handler.events.CommandResultEventHandler; +import org.traccar.handler.events.DriverEventHandler; +import org.traccar.handler.events.FuelDropEventHandler; +import org.traccar.handler.events.GeofenceEventHandler; +import org.traccar.handler.events.IgnitionEventHandler; +import org.traccar.handler.events.MaintenanceEventHandler; +import org.traccar.handler.events.MotionEventHandler; +import org.traccar.handler.events.OverspeedEventHandler; +import org.traccar.handler.ComputedAttributesHandler; +import org.traccar.handler.CopyAttributesHandler; +import org.traccar.handler.DistanceHandler; +import org.traccar.handler.EngineHoursHandler; +import org.traccar.handler.FilterHandler; +import org.traccar.handler.GeocoderHandler; +import org.traccar.handler.GeolocationHandler; +import org.traccar.handler.HemisphereHandler; +import org.traccar.handler.MotionHandler; +import org.traccar.handler.NetworkMessageHandler; +import org.traccar.handler.OpenChannelHandler; +import org.traccar.handler.RemoteAddressHandler; +import org.traccar.handler.StandardLoggingHandler; + +import java.util.Map; + +public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { + + private static final Logger LOGGER = LoggerFactory.getLogger(BasePipelineFactory.class); + + private final TrackerServer server; + private final String protocol; + private boolean eventsEnabled; + private int timeout; + + public BasePipelineFactory(TrackerServer server, String protocol) { + this.server = server; + this.protocol = protocol; + eventsEnabled = Context.getConfig().getBoolean(Keys.EVENT_ENABLE); + timeout = Context.getConfig().getInteger(Keys.PROTOCOL_TIMEOUT.withPrefix(protocol)); + if (timeout == 0) { + timeout = Context.getConfig().getInteger(Keys.SERVER_TIMEOUT); + } + } + + protected abstract void addProtocolHandlers(PipelineBuilder pipeline); + + @SafeVarargs + private final void addHandlers(ChannelPipeline pipeline, Class<? extends ChannelHandler>... handlerClasses) { + for (Class<? extends ChannelHandler> handlerClass : handlerClasses) { + if (handlerClass != null) { + pipeline.addLast(Main.getInjector().getInstance(handlerClass)); + } + } + } + + public static <T extends ChannelHandler> T getHandler(ChannelPipeline pipeline, Class<T> clazz) { + for (Map.Entry<String, ChannelHandler> handlerEntry : pipeline) { + ChannelHandler handler = handlerEntry.getValue(); + if (handler instanceof WrapperInboundHandler) { + handler = ((WrapperInboundHandler) handler).getWrappedHandler(); + } else if (handler instanceof WrapperOutboundHandler) { + handler = ((WrapperOutboundHandler) handler).getWrappedHandler(); + } + if (clazz.isAssignableFrom(handler.getClass())) { + return (T) handler; + } + } + return null; + } + + @Override + protected void initChannel(Channel channel) { + final ChannelPipeline pipeline = channel.pipeline(); + + if (timeout > 0 && !server.isDatagram()) { + pipeline.addLast(new IdleStateHandler(timeout, 0, 0)); + } + pipeline.addLast(new OpenChannelHandler(server)); + pipeline.addLast(new NetworkMessageHandler()); + pipeline.addLast(new StandardLoggingHandler(protocol)); + + addProtocolHandlers(handler -> { + if (!(handler instanceof BaseProtocolDecoder || handler instanceof BaseProtocolEncoder)) { + if (handler instanceof ChannelInboundHandler) { + handler = new WrapperInboundHandler((ChannelInboundHandler) handler); + } else { + handler = new WrapperOutboundHandler((ChannelOutboundHandler) handler); + } + } + pipeline.addLast(handler); + }); + + addHandlers( + pipeline, + TimeHandler.class, + GeolocationHandler.class, + HemisphereHandler.class, + DistanceHandler.class, + RemoteAddressHandler.class); + + addDynamicHandlers(pipeline); + + addHandlers( + pipeline, + FilterHandler.class, + GeocoderHandler.class, + MotionHandler.class, + CopyAttributesHandler.class, + EngineHoursHandler.class, + ComputedAttributesHandler.class, + WebDataHandler.class, + DefaultDataHandler.class); + + if (eventsEnabled) { + addHandlers( + pipeline, + CommandResultEventHandler.class, + OverspeedEventHandler.class, + FuelDropEventHandler.class, + MotionEventHandler.class, + GeofenceEventHandler.class, + AlertEventHandler.class, + IgnitionEventHandler.class, + MaintenanceEventHandler.class, + DriverEventHandler.class); + } + + pipeline.addLast(new MainEventHandler()); + } + + private void addDynamicHandlers(ChannelPipeline pipeline) { + String handlers = Context.getConfig().getString(Keys.EXTRA_HANDLERS); + if (handlers != null) { + for (String handler : handlers.split(",")) { + try { + pipeline.addLast((ChannelHandler) Class.forName(handler).getDeclaredConstructor().newInstance()); + } catch (ReflectiveOperationException error) { + LOGGER.warn("Dynamic handler error", error); + } + } + } + } + +} diff --git a/src/main/java/org/traccar/BaseProtocol.java b/src/main/java/org/traccar/BaseProtocol.java new file mode 100644 index 000000000..c0fd1e27f --- /dev/null +++ b/src/main/java/org/traccar/BaseProtocol.java @@ -0,0 +1,131 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.database.ActiveDevice; +import org.traccar.helper.DataConverter; +import org.traccar.model.Command; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +public abstract class BaseProtocol implements Protocol { + + private final String name; + private final Set<String> supportedDataCommands = new HashSet<>(); + private final Set<String> supportedTextCommands = new HashSet<>(); + private final List<TrackerServer> serverList = new LinkedList<>(); + + private StringProtocolEncoder textCommandEncoder = null; + + public static String nameFromClass(Class<?> clazz) { + String className = clazz.getSimpleName(); + return className.substring(0, className.length() - 8).toLowerCase(); + } + + public BaseProtocol() { + name = nameFromClass(getClass()); + } + + @Override + public String getName() { + return name; + } + + protected void addServer(TrackerServer server) { + serverList.add(server); + } + + @Override + public Collection<TrackerServer> getServerList() { + return serverList; + } + + public void setSupportedDataCommands(String... commands) { + supportedDataCommands.addAll(Arrays.asList(commands)); + } + + public void setSupportedTextCommands(String... commands) { + 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); + commands.add(Command.TYPE_CUSTOM); + return commands; + } + + @Override + public Collection<String> getSupportedTextCommands() { + Set<String> commands = new HashSet<>(supportedTextCommands); + commands.add(Command.TYPE_CUSTOM); + return commands; + } + + @Override + public void sendDataCommand(ActiveDevice activeDevice, Command command) { + if (supportedDataCommands.contains(command.getType())) { + activeDevice.write(command); + } 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); + } else { + activeDevice.write(Unpooled.wrappedBuffer(DataConverter.parseHex(data))); + } + } else { + throw new RuntimeException("Command " + command.getType() + " is not supported in protocol " + getName()); + } + } + + public void setTextCommandEncoder(StringProtocolEncoder textCommandEncoder) { + this.textCommandEncoder = textCommandEncoder; + } + + @Override + public void sendTextCommand(String destAddress, Command command) throws Exception { + if (Context.getSmsManager() != null) { + if (command.getType().equals(Command.TYPE_CUSTOM)) { + Context.getSmsManager().sendMessageSync(destAddress, command.getString(Command.KEY_DATA), true); + } else if (supportedTextCommands.contains(command.getType()) && textCommandEncoder != null) { + String encodedCommand = (String) textCommandEncoder.encodeCommand(command); + if (encodedCommand != null) { + Context.getSmsManager().sendMessageSync(destAddress, encodedCommand, true); + } else { + throw new RuntimeException("Failed to encode command"); + } + } else { + throw new RuntimeException( + "Command " + command.getType() + " is not supported in protocol " + getName()); + } + } else { + throw new RuntimeException("SMS is not enabled"); + } + } + +} diff --git a/src/main/java/org/traccar/BaseProtocolDecoder.java b/src/main/java/org/traccar/BaseProtocolDecoder.java new file mode 100644 index 000000000..e6e02c2d6 --- /dev/null +++ b/src/main/java/org/traccar/BaseProtocolDecoder.java @@ -0,0 +1,261 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.Channel; +import io.netty.channel.socket.DatagramChannel; +import io.netty.handler.codec.http.HttpRequestDecoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.database.ConnectionManager; +import org.traccar.database.IdentityManager; +import org.traccar.database.StatisticsManager; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Device; +import org.traccar.model.Position; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; + +public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { + + private static final Logger LOGGER = LoggerFactory.getLogger(BaseProtocolDecoder.class); + + private static final String PROTOCOL_UNKNOWN = "unknown"; + + private final Config config = Context.getConfig(); + private final IdentityManager identityManager = Context.getIdentityManager(); + private final ConnectionManager connectionManager = Context.getConnectionManager(); + private final StatisticsManager statisticsManager; + private final Protocol protocol; + + public BaseProtocolDecoder(Protocol protocol) { + this.protocol = protocol; + statisticsManager = Main.getInjector() != null ? Main.getInjector().getInstance(StatisticsManager.class) : null; + } + + public String getProtocolName() { + return protocol != null ? protocol.getName() : PROTOCOL_UNKNOWN; + } + + public String getServer(Channel channel, char delimiter) { + String server = config.getString(getProtocolName() + ".server"); + if (server == null && channel != null) { + InetSocketAddress address = (InetSocketAddress) channel.localAddress(); + server = address.getAddress().getHostAddress() + ":" + address.getPort(); + } + return server != null ? server.replace(':', delimiter) : null; + } + + protected double convertSpeed(double value, String defaultUnits) { + switch (config.getString(getProtocolName() + ".speed", defaultUnits)) { + case "kmh": + return UnitsConverter.knotsFromKph(value); + case "mps": + return UnitsConverter.knotsFromMps(value); + case "mph": + return UnitsConverter.knotsFromMph(value); + case "kn": + default: + return value; + } + } + + protected TimeZone getTimeZone(long deviceId) { + return getTimeZone(deviceId, "UTC"); + } + + protected TimeZone getTimeZone(long deviceId, String defaultTimeZone) { + TimeZone result = TimeZone.getTimeZone(defaultTimeZone); + String timeZoneName = identityManager.lookupAttributeString(deviceId, "decoder.timezone", null, false, true); + if (timeZoneName != null) { + result = TimeZone.getTimeZone(timeZoneName); + } else { + int timeZoneOffset = config.getInteger(getProtocolName() + ".timezone", 0); + if (timeZoneOffset != 0) { + result.setRawOffset(timeZoneOffset * 1000); + LOGGER.warn("Config parameter " + getProtocolName() + ".timezone is deprecated"); + } + } + return result; + } + + private DeviceSession channelDeviceSession; // connection-based protocols + private Map<SocketAddress, DeviceSession> addressDeviceSessions = new HashMap<>(); // connectionless protocols + + private long findDeviceId(SocketAddress remoteAddress, String... uniqueIds) { + if (uniqueIds.length > 0) { + long deviceId = 0; + Device device = null; + try { + for (String uniqueId : uniqueIds) { + if (uniqueId != null) { + device = identityManager.getByUniqueId(uniqueId); + if (device != null) { + deviceId = device.getId(); + break; + } + } + } + } catch (Exception e) { + LOGGER.warn("Find device error", e); + } + if (deviceId == 0 && config.getBoolean("database.registerUnknown")) { + return identityManager.addUnknownDevice(uniqueIds[0]); + } + if (device != null && !device.getDisabled() || config.getBoolean("database.storeDisabled")) { + return deviceId; + } + StringBuilder message = new StringBuilder(); + if (deviceId == 0) { + message.append("Unknown device -"); + } else { + message.append("Disabled device -"); + } + for (String uniqueId : uniqueIds) { + message.append(" ").append(uniqueId); + } + if (remoteAddress != null) { + message.append(" (").append(((InetSocketAddress) remoteAddress).getHostString()).append(")"); + } + LOGGER.warn(message.toString()); + } + return 0; + } + + public DeviceSession getDeviceSession(Channel channel, SocketAddress remoteAddress, String... uniqueIds) { + return getDeviceSession(channel, remoteAddress, false, uniqueIds); + } + + public DeviceSession getDeviceSession( + Channel channel, SocketAddress remoteAddress, boolean ignoreCache, String... uniqueIds) { + if (channel != null && BasePipelineFactory.getHandler(channel.pipeline(), HttpRequestDecoder.class) != null + || ignoreCache || config.getBoolean(getProtocolName() + ".ignoreSessionCache") + || config.getBoolean("decoder.ignoreSessionCache")) { + long deviceId = findDeviceId(remoteAddress, uniqueIds); + if (deviceId != 0) { + if (connectionManager != null) { + connectionManager.addActiveDevice(deviceId, protocol, channel, remoteAddress); + } + return new DeviceSession(deviceId); + } else { + return null; + } + } + if (channel instanceof DatagramChannel) { + long deviceId = findDeviceId(remoteAddress, uniqueIds); + DeviceSession deviceSession = addressDeviceSessions.get(remoteAddress); + if (deviceSession != null && (deviceSession.getDeviceId() == deviceId || uniqueIds.length == 0)) { + return deviceSession; + } else if (deviceId != 0) { + deviceSession = new DeviceSession(deviceId); + addressDeviceSessions.put(remoteAddress, deviceSession); + if (connectionManager != null) { + connectionManager.addActiveDevice(deviceId, protocol, channel, remoteAddress); + } + return deviceSession; + } else { + return null; + } + } else { + if (channelDeviceSession == null) { + long deviceId = findDeviceId(remoteAddress, uniqueIds); + if (deviceId != 0) { + channelDeviceSession = new DeviceSession(deviceId); + if (connectionManager != null) { + connectionManager.addActiveDevice(deviceId, protocol, channel, remoteAddress); + } + } + } + return channelDeviceSession; + } + } + + public void getLastLocation(Position position, Date deviceTime) { + if (position.getDeviceId() != 0) { + position.setOutdated(true); + + Position last = identityManager.getLastPosition(position.getDeviceId()); + if (last != null) { + position.setFixTime(last.getFixTime()); + position.setValid(last.getValid()); + position.setLatitude(last.getLatitude()); + position.setLongitude(last.getLongitude()); + position.setAltitude(last.getAltitude()); + position.setSpeed(last.getSpeed()); + position.setCourse(last.getCourse()); + position.setAccuracy(last.getAccuracy()); + } else { + position.setFixTime(new Date(0)); + } + + if (deviceTime != null) { + position.setDeviceTime(deviceTime); + } else { + position.setDeviceTime(new Date()); + } + } + } + + @Override + protected void onMessageEvent( + Channel channel, SocketAddress remoteAddress, Object originalMessage, Object decodedMessage) { + if (statisticsManager != null) { + statisticsManager.registerMessageReceived(); + } + Position position = null; + if (decodedMessage != null) { + if (decodedMessage instanceof Position) { + position = (Position) decodedMessage; + } else if (decodedMessage instanceof Collection) { + Collection positions = (Collection) decodedMessage; + if (!positions.isEmpty()) { + position = (Position) positions.iterator().next(); + } + } + } + if (position != null) { + connectionManager.updateDevice( + position.getDeviceId(), Device.STATUS_ONLINE, new Date()); + } else { + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession != null) { + connectionManager.updateDevice( + deviceSession.getDeviceId(), Device.STATUS_ONLINE, new Date()); + } + } + } + + @Override + protected Object handleEmptyMessage(Channel channel, SocketAddress remoteAddress, Object msg) { + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (config.getBoolean("database.saveEmpty") && deviceSession != null) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, null); + return position; + } else { + return null; + } + } + +} diff --git a/src/main/java/org/traccar/BaseProtocolEncoder.java b/src/main/java/org/traccar/BaseProtocolEncoder.java new file mode 100644 index 000000000..b6df07b98 --- /dev/null +++ b/src/main/java/org/traccar/BaseProtocolEncoder.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.model.Command; + +public abstract class BaseProtocolEncoder extends ChannelOutboundHandlerAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(BaseProtocolEncoder.class); + + private static final String PROTOCOL_UNKNOWN = "unknown"; + + private final Protocol protocol; + + public BaseProtocolEncoder(Protocol protocol) { + this.protocol = protocol; + } + + public String getProtocolName() { + return protocol != null ? protocol.getName() : PROTOCOL_UNKNOWN; + } + + protected String getUniqueId(long deviceId) { + return Context.getIdentityManager().getById(deviceId).getUniqueId(); + } + + protected void initDevicePassword(Command command, String defaultPassword) { + if (!command.getAttributes().containsKey(Command.KEY_DEVICE_PASSWORD)) { + String password = Context.getIdentityManager() + .getDevicePassword(command.getDeviceId(), getProtocolName(), defaultPassword); + command.set(Command.KEY_DEVICE_PASSWORD, password); + } + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + + NetworkMessage networkMessage = (NetworkMessage) msg; + + if (networkMessage.getMessage() instanceof Command) { + + Command command = (Command) networkMessage.getMessage(); + Object encodedCommand = encodeCommand(ctx.channel(), command); + + StringBuilder s = new StringBuilder(); + s.append("[").append(ctx.channel().id().asShortText()).append("] "); + s.append("id: ").append(getUniqueId(command.getDeviceId())).append(", "); + s.append("command type: ").append(command.getType()).append(" "); + if (encodedCommand != null) { + s.append("sent"); + } else { + s.append("not sent"); + } + LOGGER.info(s.toString()); + + ctx.write(new NetworkMessage(encodedCommand, networkMessage.getRemoteAddress()), promise); + + } else { + + super.write(ctx, msg, promise); + + } + } + + protected Object encodeCommand(Channel channel, Command command) { + return encodeCommand(command); + } + + protected Object encodeCommand(Command command) { + return null; + } + +} diff --git a/src/main/java/org/traccar/CharacterDelimiterFrameDecoder.java b/src/main/java/org/traccar/CharacterDelimiterFrameDecoder.java new file mode 100644 index 000000000..eeb8834dc --- /dev/null +++ b/src/main/java/org/traccar/CharacterDelimiterFrameDecoder.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.DelimiterBasedFrameDecoder; + +public class CharacterDelimiterFrameDecoder extends DelimiterBasedFrameDecoder { + + private static ByteBuf createDelimiter(char delimiter) { + byte[] buf = {(byte) delimiter}; + return Unpooled.wrappedBuffer(buf); + } + + private static ByteBuf createDelimiter(String delimiter) { + byte[] buf = new byte[delimiter.length()]; + for (int i = 0; i < delimiter.length(); i++) { + buf[i] = (byte) delimiter.charAt(i); + } + return Unpooled.wrappedBuffer(buf); + } + + private static ByteBuf[] convertDelimiters(String[] delimiters) { + ByteBuf[] result = new ByteBuf[delimiters.length]; + for (int i = 0; i < delimiters.length; i++) { + result[i] = createDelimiter(delimiters[i]); + } + return result; + } + + public CharacterDelimiterFrameDecoder(int maxFrameLength, char delimiter) { + super(maxFrameLength, createDelimiter(delimiter)); + } + + public CharacterDelimiterFrameDecoder(int maxFrameLength, String delimiter) { + super(maxFrameLength, createDelimiter(delimiter)); + } + + public CharacterDelimiterFrameDecoder(int maxFrameLength, boolean stripDelimiter, String delimiter) { + super(maxFrameLength, stripDelimiter, createDelimiter(delimiter)); + } + + public CharacterDelimiterFrameDecoder(int maxFrameLength, String... delimiters) { + super(maxFrameLength, convertDelimiters(delimiters)); + } + + public CharacterDelimiterFrameDecoder(int maxFrameLength, boolean stripDelimiter, String... delimiters) { + super(maxFrameLength, stripDelimiter, convertDelimiters(delimiters)); + } + +} diff --git a/src/main/java/org/traccar/Context.java b/src/main/java/org/traccar/Context.java new file mode 100644 index 000000000..9c20db9e4 --- /dev/null +++ b/src/main/java/org/traccar/Context.java @@ -0,0 +1,410 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr353.JSR353Module; +import org.apache.velocity.app.VelocityEngine; +import org.eclipse.jetty.util.URIUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.database.AttributesManager; +import org.traccar.database.BaseObjectManager; +import org.traccar.database.CalendarManager; +import org.traccar.database.CommandsManager; +import org.traccar.database.ConnectionManager; +import org.traccar.database.DataManager; +import org.traccar.database.DeviceManager; +import org.traccar.database.DriversManager; +import org.traccar.database.GeofenceManager; +import org.traccar.database.GroupsManager; +import org.traccar.database.IdentityManager; +import org.traccar.database.LdapProvider; +import org.traccar.database.MailManager; +import org.traccar.database.MaintenancesManager; +import org.traccar.database.MediaManager; +import org.traccar.database.NotificationManager; +import org.traccar.database.PermissionsManager; +import org.traccar.database.UsersManager; +import org.traccar.geocoder.Geocoder; +import org.traccar.helper.Log; +import org.traccar.helper.SanitizerModule; +import org.traccar.model.Attribute; +import org.traccar.model.BaseModel; +import org.traccar.model.Calendar; +import org.traccar.model.Command; +import org.traccar.model.Device; +import org.traccar.model.Driver; +import org.traccar.model.Geofence; +import org.traccar.model.Group; +import org.traccar.model.Maintenance; +import org.traccar.model.Notification; +import org.traccar.model.User; +import org.traccar.notification.EventForwarder; +import org.traccar.notification.JsonTypeEventForwarder; +import org.traccar.notification.NotificatorManager; +import org.traccar.reports.model.TripsConfig; +import org.traccar.sms.SmsManager; +import org.traccar.sms.smpp.SmppClient; +import org.traccar.web.WebServer; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.ext.ContextResolver; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Properties; + +public final class Context { + + private static final Logger LOGGER = LoggerFactory.getLogger(Context.class); + + private Context() { + } + + private static Config config; + + public static Config getConfig() { + return config; + } + + private static ObjectMapper objectMapper; + + public static ObjectMapper getObjectMapper() { + return objectMapper; + } + + private static IdentityManager identityManager; + + public static IdentityManager getIdentityManager() { + return identityManager; + } + + private static DataManager dataManager; + + public static DataManager getDataManager() { + return dataManager; + } + + private static LdapProvider ldapProvider; + + public static LdapProvider getLdapProvider() { + return ldapProvider; + } + + private static MailManager mailManager; + + public static MailManager getMailManager() { + return mailManager; + } + + private static MediaManager mediaManager; + + public static MediaManager getMediaManager() { + return mediaManager; + } + + private static UsersManager usersManager; + + public static UsersManager getUsersManager() { + return usersManager; + } + + private static GroupsManager groupsManager; + + public static GroupsManager getGroupsManager() { + return groupsManager; + } + + private static DeviceManager deviceManager; + + public static DeviceManager getDeviceManager() { + return deviceManager; + } + + private static ConnectionManager connectionManager; + + public static ConnectionManager getConnectionManager() { + return connectionManager; + } + + private static PermissionsManager permissionsManager; + + public static PermissionsManager getPermissionsManager() { + return permissionsManager; + } + + public static Geocoder getGeocoder() { + return Main.getInjector() != null ? Main.getInjector().getInstance(Geocoder.class) : null; + } + + private static WebServer webServer; + + public static WebServer getWebServer() { + return webServer; + } + + private static ServerManager serverManager; + + public static ServerManager getServerManager() { + return serverManager; + } + + private static GeofenceManager geofenceManager; + + public static GeofenceManager getGeofenceManager() { + return geofenceManager; + } + + private static CalendarManager calendarManager; + + public static CalendarManager getCalendarManager() { + return calendarManager; + } + + private static NotificationManager notificationManager; + + public static NotificationManager getNotificationManager() { + return notificationManager; + } + + private static NotificatorManager notificatorManager; + + public static NotificatorManager getNotificatorManager() { + return notificatorManager; + } + + private static VelocityEngine velocityEngine; + + public static VelocityEngine getVelocityEngine() { + return velocityEngine; + } + + private static Client client = ClientBuilder.newClient(); + + public static Client getClient() { + return client; + } + + private static EventForwarder eventForwarder; + + public static EventForwarder getEventForwarder() { + return eventForwarder; + } + + private static AttributesManager attributesManager; + + public static AttributesManager getAttributesManager() { + return attributesManager; + } + + private static DriversManager driversManager; + + public static DriversManager getDriversManager() { + return driversManager; + } + + private static CommandsManager commandsManager; + + public static CommandsManager getCommandsManager() { + return commandsManager; + } + + private static MaintenancesManager maintenancesManager; + + public static MaintenancesManager getMaintenancesManager() { + return maintenancesManager; + } + + private static SmsManager smsManager; + + public static SmsManager getSmsManager() { + return smsManager; + } + + private static TripsConfig tripsConfig; + + public static TripsConfig getTripsConfig() { + return tripsConfig; + } + + public static TripsConfig initTripsConfig() { + return new TripsConfig( + config.getLong("report.trip.minimalTripDistance", 500), + config.getLong("report.trip.minimalTripDuration", 300) * 1000, + config.getLong("report.trip.minimalParkingDuration", 300) * 1000, + config.getLong("report.trip.minimalNoDataDuration", 3600) * 1000, + config.getBoolean("report.trip.useIgnition"), + config.getBoolean("event.motion.processInvalidPositions"), + config.getDouble("event.motion.speedThreshold", 0.01)); + } + + private static class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> { + + @Override + public ObjectMapper getContext(Class<?> clazz) { + return objectMapper; + } + + } + + public static void init(String configFile) throws Exception { + + try { + config = new Config(configFile); + } catch (Exception e) { + config = new Config(); + Log.setupDefaultLogger(); + throw e; + } + + if (config.getBoolean("logger.enable")) { + Log.setupLogger(config); + } + + objectMapper = new ObjectMapper(); + objectMapper.registerModule(new SanitizerModule()); + objectMapper.registerModule(new JSR353Module()); + objectMapper.setConfig( + objectMapper.getSerializationConfig().without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)); + if (Context.getConfig().getBoolean("mapper.prettyPrintedJson")) { + objectMapper.enable(SerializationFeature.INDENT_OUTPUT); + } + + client = ClientBuilder.newClient().register(new ObjectMapperContextResolver()); + + if (config.hasKey("database.url")) { + dataManager = new DataManager(config); + } + + if (config.getBoolean("ldap.enable")) { + ldapProvider = new LdapProvider(config); + } + + mailManager = new MailManager(); + + mediaManager = new MediaManager(config.getString("media.path")); + + if (dataManager != null) { + usersManager = new UsersManager(dataManager); + groupsManager = new GroupsManager(dataManager); + deviceManager = new DeviceManager(dataManager); + } + + identityManager = deviceManager; + + if (config.getBoolean("web.enable")) { + webServer = new WebServer(config); + } + + permissionsManager = new PermissionsManager(dataManager, usersManager); + + connectionManager = new ConnectionManager(); + + tripsConfig = initTripsConfig(); + + if (config.getBoolean("sms.enable")) { + final String smsManagerClass = config.getString("sms.manager.class", SmppClient.class.getCanonicalName()); + try { + smsManager = (SmsManager) Class.forName(smsManagerClass).newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + LOGGER.warn("Error loading SMS Manager class : " + smsManagerClass, e); + } + } + + if (config.getBoolean("event.enable")) { + initEventsModule(); + } + + serverManager = new ServerManager(); + + if (config.getBoolean("event.forward.enable")) { + eventForwarder = new JsonTypeEventForwarder(); + } + + attributesManager = new AttributesManager(dataManager); + + driversManager = new DriversManager(dataManager); + + commandsManager = new CommandsManager(dataManager, config.getBoolean("commands.queueing")); + + } + + private static void initEventsModule() { + + geofenceManager = new GeofenceManager(dataManager); + calendarManager = new CalendarManager(dataManager); + maintenancesManager = new MaintenancesManager(dataManager); + notificationManager = new NotificationManager(dataManager); + notificatorManager = new NotificatorManager(); + Properties velocityProperties = new Properties(); + velocityProperties.setProperty("file.resource.loader.path", + Context.getConfig().getString("templates.rootPath", "templates") + "/"); + velocityProperties.setProperty("runtime.log.logsystem.class", + "org.apache.velocity.runtime.log.NullLogChute"); + + String address; + try { + address = config.getString("web.address", InetAddress.getLocalHost().getHostAddress()); + } catch (UnknownHostException e) { + address = "localhost"; + } + + String webUrl = URIUtil.newURI("http", address, config.getInteger("web.port", 8082), "", ""); + webUrl = Context.getConfig().getString("web.url", webUrl); + velocityProperties.setProperty("web.url", webUrl); + + velocityEngine = new VelocityEngine(); + velocityEngine.init(velocityProperties); + } + + public static void init(IdentityManager testIdentityManager, MediaManager testMediaManager) { + config = new Config(); + objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JSR353Module()); + client = ClientBuilder.newClient().register(new ObjectMapperContextResolver()); + identityManager = testIdentityManager; + mediaManager = testMediaManager; + } + + public static <T extends BaseModel> BaseObjectManager<T> getManager(Class<T> clazz) { + if (clazz.equals(Device.class)) { + return (BaseObjectManager<T>) deviceManager; + } else if (clazz.equals(Group.class)) { + return (BaseObjectManager<T>) groupsManager; + } else if (clazz.equals(User.class)) { + return (BaseObjectManager<T>) usersManager; + } else if (clazz.equals(Calendar.class)) { + return (BaseObjectManager<T>) calendarManager; + } else if (clazz.equals(Attribute.class)) { + return (BaseObjectManager<T>) attributesManager; + } else if (clazz.equals(Geofence.class)) { + return (BaseObjectManager<T>) geofenceManager; + } else if (clazz.equals(Driver.class)) { + return (BaseObjectManager<T>) driversManager; + } else if (clazz.equals(Command.class)) { + return (BaseObjectManager<T>) commandsManager; + } else if (clazz.equals(Maintenance.class)) { + return (BaseObjectManager<T>) maintenancesManager; + } else if (clazz.equals(Notification.class)) { + return (BaseObjectManager<T>) notificationManager; + } + return null; + } + +} diff --git a/src/main/java/org/traccar/DeviceSession.java b/src/main/java/org/traccar/DeviceSession.java new file mode 100644 index 000000000..322381807 --- /dev/null +++ b/src/main/java/org/traccar/DeviceSession.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import java.util.TimeZone; + +public class DeviceSession { + + private final long deviceId; + + public DeviceSession(long deviceId) { + this.deviceId = deviceId; + } + + public long getDeviceId() { + return deviceId; + } + + private TimeZone timeZone; + + public void setTimeZone(TimeZone timeZone) { + this.timeZone = timeZone; + } + + public TimeZone getTimeZone() { + return timeZone; + } + +} diff --git a/src/main/java/org/traccar/EventLoopGroupFactory.java b/src/main/java/org/traccar/EventLoopGroupFactory.java new file mode 100644 index 000000000..482559253 --- /dev/null +++ b/src/main/java/org/traccar/EventLoopGroupFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; + +public final class EventLoopGroupFactory { + + private static EventLoopGroup bossGroup = new NioEventLoopGroup(); + private static EventLoopGroup workerGroup = new NioEventLoopGroup(); + + private EventLoopGroupFactory() { + } + + public static EventLoopGroup getBossGroup() { + return bossGroup; + } + + public static EventLoopGroup getWorkerGroup() { + return workerGroup; + } + +} diff --git a/src/main/java/org/traccar/ExtendedObjectDecoder.java b/src/main/java/org/traccar/ExtendedObjectDecoder.java new file mode 100644 index 000000000..681924e87 --- /dev/null +++ b/src/main/java/org/traccar/ExtendedObjectDecoder.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.ReferenceCountUtil; +import org.traccar.helper.DataConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Collection; + +public abstract class ExtendedObjectDecoder extends ChannelInboundHandlerAdapter { + + private void saveOriginal(Object decodedMessage, Object originalMessage) { + if (Context.getConfig().getBoolean("database.saveOriginal") && decodedMessage instanceof Position) { + Position position = (Position) decodedMessage; + if (originalMessage instanceof ByteBuf) { + ByteBuf buf = (ByteBuf) originalMessage; + position.set(Position.KEY_ORIGINAL, ByteBufUtil.hexDump(buf)); + } else if (originalMessage instanceof String) { + position.set(Position.KEY_ORIGINAL, DataConverter.printHex( + ((String) originalMessage).getBytes(StandardCharsets.US_ASCII))); + } + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + NetworkMessage networkMessage = (NetworkMessage) msg; + Object originalMessage = networkMessage.getMessage(); + try { + Object decodedMessage = decode(ctx.channel(), networkMessage.getRemoteAddress(), originalMessage); + onMessageEvent(ctx.channel(), networkMessage.getRemoteAddress(), originalMessage, decodedMessage); + if (decodedMessage == null) { + decodedMessage = handleEmptyMessage(ctx.channel(), networkMessage.getRemoteAddress(), originalMessage); + } + if (decodedMessage != null) { + if (decodedMessage instanceof Collection) { + for (Object o : (Collection) decodedMessage) { + saveOriginal(o, originalMessage); + ctx.fireChannelRead(o); + } + } else { + saveOriginal(decodedMessage, originalMessage); + ctx.fireChannelRead(decodedMessage); + } + } + } finally { + ReferenceCountUtil.release(originalMessage); + } + } + + protected void onMessageEvent( + Channel channel, SocketAddress remoteAddress, Object originalMessage, Object decodedMessage) { + } + + protected Object handleEmptyMessage(Channel channel, SocketAddress remoteAddress, Object msg) { + return null; + } + + protected abstract Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception; + +} diff --git a/src/main/java/org/traccar/GlobalTimer.java b/src/main/java/org/traccar/GlobalTimer.java new file mode 100644 index 000000000..a97321ba2 --- /dev/null +++ b/src/main/java/org/traccar/GlobalTimer.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; + +public final class GlobalTimer { + + private static Timer instance = null; + + private GlobalTimer() { + } + + public static void release() { + if (instance != null) { + instance.stop(); + } + instance = null; + } + + public static Timer getTimer() { + if (instance == null) { + instance = new HashedWheelTimer(); + } + return instance; + } + +} diff --git a/src/main/java/org/traccar/Main.java b/src/main/java/org/traccar/Main.java new file mode 100644 index 000000000..6ebd1d399 --- /dev/null +++ b/src/main/java/org/traccar/Main.java @@ -0,0 +1,156 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.OperatingSystemMXBean; +import java.lang.management.RuntimeMXBean; +import java.nio.charset.Charset; +import java.sql.SQLException; +import java.util.Timer; +import java.util.TimerTask; +import java.util.Locale; + +public final class Main { + + private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); + + private static final long CLEAN_PERIOD = 24 * 60 * 60 * 1000; + + private static Injector injector; + + public static Injector getInjector() { + return injector; + } + + private Main() { + } + + public static void logSystemInfo() { + try { + OperatingSystemMXBean operatingSystemBean = ManagementFactory.getOperatingSystemMXBean(); + LOGGER.info("Operating system" + + " name: " + operatingSystemBean.getName() + + " version: " + operatingSystemBean.getVersion() + + " architecture: " + operatingSystemBean.getArch()); + + RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); + LOGGER.info("Java runtime" + + " name: " + runtimeBean.getVmName() + + " vendor: " + runtimeBean.getVmVendor() + + " version: " + runtimeBean.getVmVersion()); + + MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); + LOGGER.info("Memory limit" + + " heap: " + memoryBean.getHeapMemoryUsage().getMax() / (1024 * 1024) + "mb" + + " non-heap: " + memoryBean.getNonHeapMemoryUsage().getMax() / (1024 * 1024) + "mb"); + + LOGGER.info("Character encoding: " + + System.getProperty("file.encoding") + " charset: " + Charset.defaultCharset()); + + } catch (Exception error) { + LOGGER.warn("Failed to get system info"); + } + } + + public static void main(String[] args) throws Exception { + Locale.setDefault(Locale.ENGLISH); + + if (args.length <= 0) { + throw new RuntimeException("Configuration file is not provided"); + } + + final String configFile = args[args.length - 1]; + + if (args[0].startsWith("--")) { + WindowsService windowsService = new WindowsService("traccar") { + @Override + public void run() { + Main.run(configFile); + } + }; + switch (args[0]) { + case "--install": + windowsService.install("traccar", null, null, null, null, configFile); + return; + case "--uninstall": + windowsService.uninstall(); + return; + case "--service": + default: + windowsService.init(); + break; + } + } else { + run(configFile); + } + } + + public static void run(String configFile) { + try { + Context.init(configFile); + injector = Guice.createInjector(new MainModule()); + logSystemInfo(); + LOGGER.info("Version: " + Main.class.getPackage().getImplementationVersion()); + LOGGER.info("Starting server..."); + + Context.getServerManager().start(); + if (Context.getWebServer() != null) { + 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); + + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + LOGGER.error("Thread exception", e); + } + }); + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + LOGGER.info("Shutting down server..."); + + 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/MainEventHandler.java b/src/main/java/org/traccar/MainEventHandler.java new file mode 100644 index 000000000..2309b1e70 --- /dev/null +++ b/src/main/java/org/traccar/MainEventHandler.java @@ -0,0 +1,161 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.socket.DatagramChannel; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.timeout.IdleStateEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.database.StatisticsManager; +import org.traccar.helper.DateUtil; +import org.traccar.model.Position; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +public class MainEventHandler extends ChannelInboundHandlerAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(MainEventHandler.class); + + private static final String DEFAULT_LOGGER_ATTRIBUTES = "time,position,speed,course,accuracy,result"; + + private final Set<String> connectionlessProtocols = new HashSet<>(); + private final Set<String> logAttributes = new LinkedHashSet<>(); + + public MainEventHandler() { + String connectionlessProtocolList = Context.getConfig().getString("status.ignoreOffline"); + if (connectionlessProtocolList != null) { + connectionlessProtocols.addAll(Arrays.asList(connectionlessProtocolList.split("[, ]"))); + } + logAttributes.addAll(Arrays.asList( + Context.getConfig().getString("logger.attributes", DEFAULT_LOGGER_ATTRIBUTES).split("[, ]"))); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (msg instanceof Position) { + + Position position = (Position) msg; + try { + Context.getDeviceManager().updateLatestPosition(position); + } catch (SQLException error) { + LOGGER.warn("Failed to update device", error); + } + + String uniqueId = Context.getIdentityManager().getById(position.getDeviceId()).getUniqueId(); + + StringBuilder builder = new StringBuilder(); + builder.append(formatChannel(ctx.channel())).append(" "); + builder.append("id: ").append(uniqueId); + for (String attribute : logAttributes) { + switch (attribute) { + case "time": + builder.append(", time: ").append(DateUtil.formatDate(position.getFixTime(), false)); + break; + case "position": + builder.append(", lat: ").append(String.format("%.5f", position.getLatitude())); + builder.append(", lon: ").append(String.format("%.5f", position.getLongitude())); + break; + case "speed": + if (position.getSpeed() > 0) { + builder.append(", speed: ").append(String.format("%.1f", position.getSpeed())); + } + break; + case "course": + builder.append(", course: ").append(String.format("%.1f", position.getCourse())); + break; + case "accuracy": + if (position.getAccuracy() > 0) { + builder.append(", accuracy: ").append(String.format("%.1f", position.getAccuracy())); + } + break; + case "outdated": + if (position.getOutdated()) { + builder.append(", outdated"); + } + break; + case "invalid": + if (!position.getValid()) { + builder.append(", invalid"); + } + break; + default: + Object value = position.getAttributes().get(attribute); + if (value != null) { + builder.append(", ").append(attribute).append(": ").append(value); + } + break; + } + } + LOGGER.info(builder.toString()); + + Main.getInjector().getInstance(StatisticsManager.class).registerMessageStored(position.getDeviceId()); + } + } + + private static String formatChannel(Channel channel) { + return String.format("[%s]", channel.id().asShortText()); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + if (!(ctx.channel() instanceof DatagramChannel)) { + LOGGER.info(formatChannel(ctx.channel()) + " connected"); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + LOGGER.info(formatChannel(ctx.channel()) + " disconnected"); + closeChannel(ctx.channel()); + + if (BasePipelineFactory.getHandler(ctx.pipeline(), HttpRequestDecoder.class) == null + && !connectionlessProtocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName())) { + Context.getConnectionManager().removeActiveDevice(ctx.channel()); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + while (cause.getCause() != null && cause.getCause() != cause) { + cause = cause.getCause(); + } + LOGGER.warn(formatChannel(ctx.channel()) + " error", cause); + closeChannel(ctx.channel()); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt instanceof IdleStateEvent) { + LOGGER.info(formatChannel(ctx.channel()) + " timed out"); + closeChannel(ctx.channel()); + } + } + + private void closeChannel(Channel channel) { + if (!(channel instanceof DatagramChannel)) { + channel.close(); + } + } + +} diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java new file mode 100644 index 000000000..0957d9fe3 --- /dev/null +++ b/src/main/java/org/traccar/MainModule.java @@ -0,0 +1,383 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.database.AttributesManager; +import org.traccar.database.CalendarManager; +import org.traccar.database.DataManager; +import org.traccar.database.DeviceManager; +import org.traccar.database.GeofenceManager; +import org.traccar.database.IdentityManager; +import org.traccar.database.MaintenancesManager; +import org.traccar.database.StatisticsManager; +import org.traccar.geocoder.AddressFormat; +import org.traccar.geocoder.BanGeocoder; +import org.traccar.geocoder.BingMapsGeocoder; +import org.traccar.geocoder.FactualGeocoder; +import org.traccar.geocoder.GeocodeFarmGeocoder; +import org.traccar.geocoder.GeocodeXyzGeocoder; +import org.traccar.geocoder.Geocoder; +import org.traccar.geocoder.GisgraphyGeocoder; +import org.traccar.geocoder.GoogleGeocoder; +import org.traccar.geocoder.HereGeocoder; +import org.traccar.geocoder.MapQuestGeocoder; +import org.traccar.geocoder.MapmyIndiaGeocoder; +import org.traccar.geocoder.NominatimGeocoder; +import org.traccar.geocoder.OpenCageGeocoder; +import org.traccar.geolocation.GeolocationProvider; +import org.traccar.geolocation.GoogleGeolocationProvider; +import org.traccar.geolocation.MozillaGeolocationProvider; +import org.traccar.geolocation.OpenCellIdGeolocationProvider; +import org.traccar.geolocation.UnwiredGeolocationProvider; +import org.traccar.handler.ComputedAttributesHandler; +import org.traccar.handler.CopyAttributesHandler; +import org.traccar.handler.DefaultDataHandler; +import org.traccar.handler.DistanceHandler; +import org.traccar.handler.EngineHoursHandler; +import org.traccar.handler.FilterHandler; +import org.traccar.handler.GeocoderHandler; +import org.traccar.handler.GeolocationHandler; +import org.traccar.handler.HemisphereHandler; +import org.traccar.handler.MotionHandler; +import org.traccar.handler.RemoteAddressHandler; +import org.traccar.handler.TimeHandler; +import org.traccar.handler.events.AlertEventHandler; +import org.traccar.handler.events.CommandResultEventHandler; +import org.traccar.handler.events.DriverEventHandler; +import org.traccar.handler.events.FuelDropEventHandler; +import org.traccar.handler.events.GeofenceEventHandler; +import org.traccar.handler.events.IgnitionEventHandler; +import org.traccar.handler.events.MaintenanceEventHandler; +import org.traccar.handler.events.MotionEventHandler; +import org.traccar.handler.events.OverspeedEventHandler; +import org.traccar.reports.model.TripsConfig; + +import javax.annotation.Nullable; +import javax.ws.rs.client.Client; + +public class MainModule extends AbstractModule { + + @Provides + public static ObjectMapper provideObjectMapper() { + return Context.getObjectMapper(); + } + + @Provides + public static Config provideConfig() { + return Context.getConfig(); + } + + @Provides + public static DataManager provideDataManager() { + return Context.getDataManager(); + } + + @Provides + public static IdentityManager provideIdentityManager() { + return Context.getIdentityManager(); + } + + @Provides + public static Client provideClient() { + return Context.getClient(); + } + + @Provides + public static TripsConfig provideTripsConfig() { + return Context.getTripsConfig(); + } + + @Provides + public static DeviceManager provideDeviceManager() { + return Context.getDeviceManager(); + } + + @Provides + public static GeofenceManager provideGeofenceManager() { + return Context.getGeofenceManager(); + } + + @Provides + public static CalendarManager provideCalendarManager() { + return Context.getCalendarManager(); + } + + @Provides + public static AttributesManager provideAttributesManager() { + return Context.getAttributesManager(); + } + + @Provides + public static MaintenancesManager provideMaintenancesManager() { + return Context.getMaintenancesManager(); + } + + @Singleton + @Provides + public static StatisticsManager provideStatisticsManager(Config config, DataManager dataManager, Client client) { + return new StatisticsManager(config, dataManager, client); + } + + @Singleton + @Provides + public static Geocoder provideGeocoder(Config config) { + if (config.getBoolean(Keys.GEOCODER_ENABLE)) { + String type = config.getString(Keys.GEOCODER_TYPE, "google"); + String url = config.getString(Keys.GEOCODER_URL); + String id = config.getString(Keys.GEOCODER_ID); + String key = config.getString(Keys.GEOCODER_KEY); + String language = config.getString(Keys.GEOCODER_LANGUAGE); + String formatString = config.getString(Keys.GEOCODER_FORMAT); + AddressFormat addressFormat = formatString != null ? new AddressFormat(formatString) : new AddressFormat(); + + int cacheSize = config.getInteger(Keys.GEOCODER_CACHE_SIZE); + switch (type) { + case "nominatim": + return new NominatimGeocoder(url, key, language, cacheSize, addressFormat); + case "gisgraphy": + return new GisgraphyGeocoder(url, cacheSize, addressFormat); + case "mapquest": + return new MapQuestGeocoder(url, key, cacheSize, addressFormat); + case "opencage": + return new OpenCageGeocoder(url, key, cacheSize, addressFormat); + case "bingmaps": + return new BingMapsGeocoder(url, key, cacheSize, addressFormat); + case "factual": + return new FactualGeocoder(url, key, cacheSize, addressFormat); + case "geocodefarm": + return new GeocodeFarmGeocoder(key, language, cacheSize, addressFormat); + case "geocodexyz": + return new GeocodeXyzGeocoder(key, cacheSize, addressFormat); + case "ban": + return new BanGeocoder(cacheSize, addressFormat); + case "here": + return new HereGeocoder(url, id, key, language, cacheSize, addressFormat); + case "mapmyindia": + return new MapmyIndiaGeocoder(url, key, cacheSize, addressFormat); + default: + return new GoogleGeocoder(key, language, cacheSize, addressFormat); + } + } + return null; + } + + @Singleton + @Provides + public static GeolocationProvider provideGeolocationProvider(Config config) { + if (config.getBoolean(Keys.GEOLOCATION_ENABLE)) { + String type = config.getString(Keys.GEOLOCATION_TYPE, "mozilla"); + String url = config.getString(Keys.GEOLOCATION_URL); + String key = config.getString(Keys.GEOLOCATION_KEY); + switch (type) { + case "google": + return new GoogleGeolocationProvider(key); + case "opencellid": + return new OpenCellIdGeolocationProvider(url, key); + case "unwired": + return new UnwiredGeolocationProvider(url, key); + default: + return new MozillaGeolocationProvider(key); + } + } + return null; + } + + @Singleton + @Provides + public static DistanceHandler provideDistanceHandler(Config config, IdentityManager identityManager) { + return new DistanceHandler(config, identityManager); + } + + @Singleton + @Provides + public static FilterHandler provideFilterHandler(Config config) { + if (config.getBoolean(Keys.FILTER_ENABLE)) { + return new FilterHandler(config); + } + return null; + } + + @Singleton + @Provides + public static HemisphereHandler provideHemisphereHandler(Config config) { + if (config.hasKey(Keys.LOCATION_LATITUDE_HEMISPHERE) || config.hasKey(Keys.LOCATION_LONGITUDE_HEMISPHERE)) { + return new HemisphereHandler(config); + } + return null; + } + + @Singleton + @Provides + public static RemoteAddressHandler provideRemoteAddressHandler(Config config) { + if (config.getBoolean(Keys.PROCESSING_REMOTE_ADDRESS_ENABLE)) { + return new RemoteAddressHandler(); + } + return null; + } + + @Singleton + @Provides + public static WebDataHandler provideWebDataHandler( + Config config, IdentityManager identityManager, ObjectMapper objectMapper, Client client) { + if (config.getBoolean(Keys.FORWARD_ENABLE)) { + return new WebDataHandler(config, identityManager, objectMapper, client); + } + return null; + } + + @Singleton + @Provides + public static GeolocationHandler provideGeolocationHandler( + Config config, @Nullable GeolocationProvider geolocationProvider, StatisticsManager statisticsManager) { + if (geolocationProvider != null) { + return new GeolocationHandler(config, geolocationProvider, statisticsManager); + } + return null; + } + + @Singleton + @Provides + public static GeocoderHandler provideGeocoderHandler( + Config config, @Nullable Geocoder geocoder, IdentityManager identityManager, + StatisticsManager statisticsManager) { + if (geocoder != null) { + return new GeocoderHandler(config, geocoder, identityManager, statisticsManager); + } + return null; + } + + @Singleton + @Provides + public static MotionHandler provideMotionHandler(TripsConfig tripsConfig) { + return new MotionHandler(tripsConfig.getSpeedThreshold()); + } + + @Singleton + @Provides + public static EngineHoursHandler provideEngineHoursHandler(Config config, IdentityManager identityManager) { + if (config.getBoolean(Keys.PROCESSING_ENGINE_HOURS_ENABLE)) { + return new EngineHoursHandler(identityManager); + } + return null; + } + + @Singleton + @Provides + public static CopyAttributesHandler provideCopyAttributesHandler(Config config, IdentityManager identityManager) { + if (config.getBoolean(Keys.PROCESSING_COPY_ATTRIBUTES_ENABLE)) { + return new CopyAttributesHandler(identityManager); + } + return null; + } + + @Singleton + @Provides + public static ComputedAttributesHandler provideComputedAttributesHandler( + Config config, IdentityManager identityManager, AttributesManager attributesManager) { + if (config.getBoolean(Keys.PROCESSING_COMPUTED_ATTRIBUTES_ENABLE)) { + return new ComputedAttributesHandler(config, identityManager, attributesManager); + } + return null; + } + + @Singleton + @Provides + public static TimeHandler provideTimeHandler(Config config) { + if (config.hasKey(Keys.TIME_OVERRIDE)) { + return new TimeHandler(config); + } + return null; + } + + @Singleton + @Provides + public static DefaultDataHandler provideDefaultDataHandler(@Nullable DataManager dataManager) { + if (dataManager != null) { + return new DefaultDataHandler(dataManager); + } + return null; + } + + @Singleton + @Provides + public static CommandResultEventHandler provideCommandResultEventHandler() { + return new CommandResultEventHandler(); + } + + @Singleton + @Provides + public static OverspeedEventHandler provideOverspeedEventHandler( + Config config, DeviceManager deviceManager, GeofenceManager geofenceManager) { + return new OverspeedEventHandler(config, deviceManager, geofenceManager); + } + + @Singleton + @Provides + public static FuelDropEventHandler provideFuelDropEventHandler(IdentityManager identityManager) { + return new FuelDropEventHandler(identityManager); + } + + @Singleton + @Provides + public static MotionEventHandler provideMotionEventHandler( + IdentityManager identityManager, DeviceManager deviceManager, TripsConfig tripsConfig) { + return new MotionEventHandler(identityManager, deviceManager, tripsConfig); + } + + @Singleton + @Provides + public static GeofenceEventHandler provideGeofenceEventHandler( + IdentityManager identityManager, GeofenceManager geofenceManager, CalendarManager calendarManager) { + return new GeofenceEventHandler(identityManager, geofenceManager, calendarManager); + } + + @Singleton + @Provides + public static AlertEventHandler provideAlertEventHandler(Config config, IdentityManager identityManager) { + return new AlertEventHandler(config, identityManager); + } + + @Singleton + @Provides + public static IgnitionEventHandler provideIgnitionEventHandler(IdentityManager identityManager) { + return new IgnitionEventHandler(identityManager); + } + + @Singleton + @Provides + public static MaintenanceEventHandler provideMaintenanceEventHandler( + IdentityManager identityManager, MaintenancesManager maintenancesManager) { + return new MaintenanceEventHandler(identityManager, maintenancesManager); + } + + @Singleton + @Provides + public static DriverEventHandler provideDriverEventHandler(IdentityManager identityManager) { + return new DriverEventHandler(identityManager); + } + + @Override + protected void configure() { + binder().requireExplicitBindings(); + } + +} diff --git a/src/main/java/org/traccar/NetworkMessage.java b/src/main/java/org/traccar/NetworkMessage.java new file mode 100644 index 000000000..14a397e69 --- /dev/null +++ b/src/main/java/org/traccar/NetworkMessage.java @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import java.net.SocketAddress; + +public class NetworkMessage { + + private final SocketAddress remoteAddress; + private final Object message; + + public NetworkMessage(Object message, SocketAddress remoteAddress) { + this.message = message; + this.remoteAddress = remoteAddress; + } + + public SocketAddress getRemoteAddress() { + return remoteAddress; + } + + public Object getMessage() { + return message; + } + +} diff --git a/src/main/java/org/traccar/PipelineBuilder.java b/src/main/java/org/traccar/PipelineBuilder.java new file mode 100644 index 000000000..3334040b1 --- /dev/null +++ b/src/main/java/org/traccar/PipelineBuilder.java @@ -0,0 +1,24 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.ChannelHandler; + +public interface PipelineBuilder { + + void addLast(ChannelHandler handler); + +} diff --git a/src/main/java/org/traccar/Protocol.java b/src/main/java/org/traccar/Protocol.java new file mode 100644 index 000000000..3b66f2598 --- /dev/null +++ b/src/main/java/org/traccar/Protocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import org.traccar.database.ActiveDevice; +import org.traccar.model.Command; + +import java.util.Collection; + +public interface Protocol { + + String getName(); + + Collection<TrackerServer> getServerList(); + + Collection<String> getSupportedDataCommands(); + + void sendDataCommand(ActiveDevice activeDevice, Command command); + + Collection<String> getSupportedTextCommands(); + + void sendTextCommand(String destAddress, Command command) throws Exception; + +} diff --git a/src/main/java/org/traccar/ServerManager.java b/src/main/java/org/traccar/ServerManager.java new file mode 100644 index 000000000..6a3273402 --- /dev/null +++ b/src/main/java/org/traccar/ServerManager.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.net.BindException; +import java.net.URI; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class ServerManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServerManager.class); + + private final List<TrackerServer> serverList = new LinkedList<>(); + private final Map<String, BaseProtocol> protocolList = new ConcurrentHashMap<>(); + + public ServerManager() throws Exception { + + List<String> names = new LinkedList<>(); + String packageName = "org.traccar.protocol"; + String packagePath = packageName.replace('.', '/'); + URL packageUrl = getClass().getClassLoader().getResource(packagePath); + + if (packageUrl.getProtocol().equals("jar")) { + String jarFileName = URLDecoder.decode(packageUrl.getFile(), StandardCharsets.UTF_8.name()); + try (JarFile jf = new JarFile(jarFileName.substring(5, jarFileName.indexOf("!")))) { + Enumeration<JarEntry> jarEntries = jf.entries(); + while (jarEntries.hasMoreElements()) { + String entryName = jarEntries.nextElement().getName(); + if (entryName.startsWith(packagePath) && entryName.length() > packagePath.length() + 5) { + names.add(entryName.substring(packagePath.length() + 1, entryName.lastIndexOf('.'))); + } + } + } + } else { + File folder = new File(new URI(packageUrl.toString())); + File[] files = folder.listFiles(); + if (files != null) { + for (File actual: files) { + String entryName = actual.getName(); + names.add(entryName.substring(0, entryName.lastIndexOf('.'))); + } + } + } + + for (String name : names) { + Class protocolClass = Class.forName(packageName + '.' + name); + if (BaseProtocol.class.isAssignableFrom(protocolClass) + && Context.getConfig().hasKey(BaseProtocol.nameFromClass(protocolClass) + ".port")) { + BaseProtocol protocol = (BaseProtocol) protocolClass.newInstance(); + serverList.addAll(protocol.getServerList()); + protocolList.put(protocol.getName(), protocol); + } + } + } + + public BaseProtocol getProtocol(String name) { + return protocolList.get(name); + } + + public void start() throws Exception { + for (TrackerServer server: serverList) { + try { + server.start(); + } catch (BindException e) { + LOGGER.warn("Port {} is disabled due to conflict", server.getPort()); + } + } + } + + public void stop() { + for (TrackerServer server: serverList) { + server.stop(); + } + GlobalTimer.release(); + } + +} diff --git a/src/main/java/org/traccar/StringProtocolEncoder.java b/src/main/java/org/traccar/StringProtocolEncoder.java new file mode 100644 index 000000000..e9fb65500 --- /dev/null +++ b/src/main/java/org/traccar/StringProtocolEncoder.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import org.traccar.model.Command; + +public abstract class StringProtocolEncoder extends BaseProtocolEncoder { + + public StringProtocolEncoder(Protocol protocol) { + super(protocol); + } + + public interface ValueFormatter { + String formatValue(String key, Object value); + } + + protected String formatCommand(Command command, String format, ValueFormatter valueFormatter, String... keys) { + + Object[] values = new String[keys.length]; + for (int i = 0; i < keys.length; i++) { + String value = null; + if (keys[i].equals(Command.KEY_UNIQUE_ID)) { + value = getUniqueId(command.getDeviceId()); + } else { + Object object = command.getAttributes().get(keys[i]); + if (valueFormatter != null) { + value = valueFormatter.formatValue(keys[i], object); + } + if (value == null && object != null) { + value = object.toString(); + } + if (value == null) { + value = ""; + } + } + values[i] = value; + } + + return String.format(format, values); + } + + protected String formatCommand(Command command, String format, String... keys) { + return formatCommand(command, format, null, keys); + } + +} diff --git a/src/main/java/org/traccar/TrackerServer.java b/src/main/java/org/traccar/TrackerServer.java new file mode 100644 index 000000000..3a1e1c4e8 --- /dev/null +++ b/src/main/java/org/traccar/TrackerServer.java @@ -0,0 +1,115 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.bootstrap.AbstractBootstrap; +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.util.concurrent.GlobalEventExecutor; + +import java.net.InetSocketAddress; + +public abstract class TrackerServer { + + private final boolean datagram; + private final AbstractBootstrap bootstrap; + + public boolean isDatagram() { + return datagram; + } + + public TrackerServer(boolean datagram, String protocol) { + this.datagram = datagram; + + address = Context.getConfig().getString(protocol + ".address"); + port = Context.getConfig().getInteger(protocol + ".port"); + + BasePipelineFactory pipelineFactory = new BasePipelineFactory(this, protocol) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + TrackerServer.this.addProtocolHandlers(pipeline); + } + }; + + if (datagram) { + + this.bootstrap = new Bootstrap() + .group(EventLoopGroupFactory.getWorkerGroup()) + .channel(NioDatagramChannel.class) + .handler(pipelineFactory); + + } else { + + this.bootstrap = new ServerBootstrap() + .group(EventLoopGroupFactory.getBossGroup(), EventLoopGroupFactory.getWorkerGroup()) + .channel(NioServerSocketChannel.class) + .childHandler(pipelineFactory); + + } + } + + protected abstract void addProtocolHandlers(PipelineBuilder pipeline); + + private int port; + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + private String address; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + public ChannelGroup getChannelGroup() { + return channelGroup; + } + + public void start() throws Exception { + InetSocketAddress endpoint; + if (address == null) { + endpoint = new InetSocketAddress(port); + } else { + endpoint = new InetSocketAddress(address, port); + } + + Channel channel = bootstrap.bind(endpoint).sync().channel(); + if (channel != null) { + getChannelGroup().add(channel); + } + } + + public void stop() { + channelGroup.close().awaitUninterruptibly(); + } + +} diff --git a/src/main/java/org/traccar/WebDataHandler.java b/src/main/java/org/traccar/WebDataHandler.java new file mode 100644 index 000000000..64396de03 --- /dev/null +++ b/src/main/java/org/traccar/WebDataHandler.java @@ -0,0 +1,201 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.channel.ChannelHandler; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.database.IdentityManager; +import org.traccar.helper.Checksum; +import org.traccar.model.Device; +import org.traccar.model.Position; +import org.traccar.model.Group; + +import javax.inject.Inject; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.Invocation; +import java.util.HashMap; +import java.util.Map; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Calendar; +import java.util.Formatter; +import java.util.Locale; +import java.util.TimeZone; + +@ChannelHandler.Sharable +public class WebDataHandler extends BaseDataHandler { + + private static final String KEY_POSITION = "position"; + private static final String KEY_DEVICE = "device"; + + private final IdentityManager identityManager; + private final ObjectMapper objectMapper; + private final Client client; + + private final String url; + private final String header; + private final boolean json; + + @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); + } + + private static String formatSentence(Position position) { + + StringBuilder s = new StringBuilder("$GPRMC,"); + + try (Formatter f = new Formatter(s, Locale.ENGLISH)) { + + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ENGLISH); + calendar.setTimeInMillis(position.getFixTime().getTime()); + + f.format("%1$tH%1$tM%1$tS.%1$tL,A,", calendar); + + double lat = position.getLatitude(); + double lon = position.getLongitude(); + + f.format("%02d%07.4f,%c,", (int) Math.abs(lat), Math.abs(lat) % 1 * 60, lat < 0 ? 'S' : 'N'); + f.format("%03d%07.4f,%c,", (int) Math.abs(lon), Math.abs(lon) % 1 * 60, lon < 0 ? 'W' : 'E'); + + f.format("%.2f,%.2f,", position.getSpeed(), position.getCourse()); + f.format("%1$td%1$tm%1$ty,,", calendar); + } + + s.append(Checksum.nmea(s.toString())); + + return s.toString(); + } + + private String calculateStatus(Position position) { + if (position.getAttributes().containsKey(Position.KEY_ALARM)) { + return "0xF841"; // STATUS_PANIC_ON + } else if (position.getSpeed() < 1.0) { + return "0xF020"; // STATUS_LOCATION + } else { + return "0xF11C"; // STATUS_MOTION_MOVING + } + } + + public String formatRequest(Position position) throws UnsupportedEncodingException, JsonProcessingException { + + Device device = identityManager.getById(position.getDeviceId()); + + String request = url + .replace("{name}", URLEncoder.encode(device.getName(), StandardCharsets.UTF_8.name())) + .replace("{uniqueId}", device.getUniqueId()) + .replace("{status}", device.getStatus()) + .replace("{deviceId}", String.valueOf(position.getDeviceId())) + .replace("{protocol}", String.valueOf(position.getProtocol())) + .replace("{deviceTime}", String.valueOf(position.getDeviceTime().getTime())) + .replace("{fixTime}", String.valueOf(position.getFixTime().getTime())) + .replace("{valid}", String.valueOf(position.getValid())) + .replace("{latitude}", String.valueOf(position.getLatitude())) + .replace("{longitude}", String.valueOf(position.getLongitude())) + .replace("{altitude}", String.valueOf(position.getAltitude())) + .replace("{speed}", String.valueOf(position.getSpeed())) + .replace("{course}", String.valueOf(position.getCourse())) + .replace("{accuracy}", String.valueOf(position.getAccuracy())) + .replace("{statusCode}", calculateStatus(position)); + + if (position.getAddress() != null) { + request = request.replace( + "{address}", URLEncoder.encode(position.getAddress(), StandardCharsets.UTF_8.name())); + } + + if (request.contains("{attributes}")) { + String attributes = objectMapper.writeValueAsString(position.getAttributes()); + request = request.replace( + "{attributes}", URLEncoder.encode(attributes, StandardCharsets.UTF_8.name())); + } + + if (request.contains("{gprmc}")) { + request = request.replace("{gprmc}", formatSentence(position)); + } + + if (request.contains("{group}")) { + String deviceGroupName = ""; + if (device.getGroupId() != 0) { + Group group = Context.getGroupsManager().getById(device.getGroupId()); + if (group != null) { + deviceGroupName = group.getName(); + } + } + + request = request.replace("{group}", URLEncoder.encode(deviceGroupName, StandardCharsets.UTF_8.name())); + } + + return request; + } + + @Override + protected Position handlePosition(Position position) { + + String url; + if (json) { + url = this.url; + } else { + try { + url = formatRequest(position); + } catch (UnsupportedEncodingException | JsonProcessingException e) { + throw new RuntimeException("Forwarding formatting error", e); + } + } + + Invocation.Builder requestBuilder = client.target(url).request(); + + if (header != null && !header.isEmpty()) { + for (String line: header.split("\\r?\\n")) { + String[] values = line.split(":", 2); + requestBuilder.header(values[0].trim(), values[1].trim()); + } + } + + if (json) { + requestBuilder.async().post(Entity.json(prepareJsonPayload(position))); + } else { + requestBuilder.async().get(); + } + + return position; + } + + private Map<String, Object> prepareJsonPayload(Position position) { + + Map<String, Object> data = new HashMap<>(); + Device device = identityManager.getById(position.getDeviceId()); + + data.put(KEY_POSITION, position); + + if (device != null) { + data.put(KEY_DEVICE, device); + } + + return data; + } + +} diff --git a/src/main/java/org/traccar/WindowsService.java b/src/main/java/org/traccar/WindowsService.java new file mode 100644 index 000000000..4a8955608 --- /dev/null +++ b/src/main/java/org/traccar/WindowsService.java @@ -0,0 +1,231 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.Advapi32; +import com.sun.jna.platform.win32.WinError; +import com.sun.jna.platform.win32.WinNT; +import com.sun.jna.platform.win32.Winsvc; +import com.sun.jna.platform.win32.Winsvc.HandlerEx; +import com.sun.jna.platform.win32.Winsvc.SC_HANDLE; +import com.sun.jna.platform.win32.Winsvc.SERVICE_DESCRIPTION; +import com.sun.jna.platform.win32.Winsvc.SERVICE_MAIN_FUNCTION; +import com.sun.jna.platform.win32.Winsvc.SERVICE_STATUS; +import com.sun.jna.platform.win32.Winsvc.SERVICE_STATUS_HANDLE; +import com.sun.jna.platform.win32.Winsvc.SERVICE_TABLE_ENTRY; +import jnr.posix.POSIXFactory; + +import java.io.File; +import java.net.URISyntaxException; + +public abstract class WindowsService { + + private static final Advapi32 ADVAPI_32 = Advapi32.INSTANCE; + + private final Object waitObject = new Object(); + + private String serviceName; + private ServiceMain serviceMain; + private ServiceControl serviceControl; + private SERVICE_STATUS_HANDLE serviceStatusHandle; + + public WindowsService(String serviceName) { + this.serviceName = serviceName; + } + + public boolean install( + String displayName, String description, String[] dependencies, + String account, String password, String config) throws URISyntaxException { + + String javaHome = System.getProperty("java.home"); + String javaBinary = javaHome + "\\bin\\java.exe"; + + File jar = new File(WindowsService.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + String command = javaBinary + + " -Duser.dir=\"" + jar.getParentFile().getAbsolutePath() + "\"" + + " -jar \"" + jar.getAbsolutePath() + "\"" + + " --service \"" + config + "\""; + + boolean success = false; + StringBuilder dep = new StringBuilder(); + + if (dependencies != null) { + for (String s : dependencies) { + dep.append(s); + dep.append("\0"); + } + } + dep.append("\0"); + + SERVICE_DESCRIPTION desc = new SERVICE_DESCRIPTION(); + desc.lpDescription = description; + + SC_HANDLE serviceManager = openServiceControlManager(null, Winsvc.SC_MANAGER_ALL_ACCESS); + + if (serviceManager != null) { + SC_HANDLE service = ADVAPI_32.CreateService(serviceManager, serviceName, displayName, + Winsvc.SERVICE_ALL_ACCESS, WinNT.SERVICE_WIN32_OWN_PROCESS, WinNT.SERVICE_AUTO_START, + WinNT.SERVICE_ERROR_NORMAL, + command, + null, null, dep.toString(), account, password); + + if (service != null) { + success = ADVAPI_32.ChangeServiceConfig2(service, Winsvc.SERVICE_CONFIG_DESCRIPTION, desc); + ADVAPI_32.CloseServiceHandle(service); + } + ADVAPI_32.CloseServiceHandle(serviceManager); + } + return success; + } + + public boolean uninstall() { + boolean success = false; + + SC_HANDLE serviceManager = openServiceControlManager(null, Winsvc.SC_MANAGER_ALL_ACCESS); + + if (serviceManager != null) { + SC_HANDLE service = ADVAPI_32.OpenService(serviceManager, serviceName, Winsvc.SERVICE_ALL_ACCESS); + + if (service != null) { + success = ADVAPI_32.DeleteService(service); + ADVAPI_32.CloseServiceHandle(service); + } + ADVAPI_32.CloseServiceHandle(serviceManager); + } + return success; + } + + public boolean start() { + boolean success = false; + + SC_HANDLE serviceManager = openServiceControlManager(null, WinNT.GENERIC_EXECUTE); + + if (serviceManager != null) { + SC_HANDLE service = ADVAPI_32.OpenService(serviceManager, serviceName, WinNT.GENERIC_EXECUTE); + + if (service != null) { + success = ADVAPI_32.StartService(service, 0, null); + ADVAPI_32.CloseServiceHandle(service); + } + ADVAPI_32.CloseServiceHandle(serviceManager); + } + + return success; + } + + public boolean stop() { + boolean success = false; + + SC_HANDLE serviceManager = openServiceControlManager(null, WinNT.GENERIC_EXECUTE); + + if (serviceManager != null) { + SC_HANDLE service = Advapi32.INSTANCE.OpenService(serviceManager, serviceName, WinNT.GENERIC_EXECUTE); + + if (service != null) { + SERVICE_STATUS serviceStatus = new SERVICE_STATUS(); + success = Advapi32.INSTANCE.ControlService(service, Winsvc.SERVICE_CONTROL_STOP, serviceStatus); + Advapi32.INSTANCE.CloseServiceHandle(service); + } + Advapi32.INSTANCE.CloseServiceHandle(serviceManager); + } + + return success; + } + + public void init() throws URISyntaxException { + String path = new File( + WindowsService.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent(); + + POSIXFactory.getPOSIX().chdir(path); + + serviceMain = new ServiceMain(); + SERVICE_TABLE_ENTRY entry = new SERVICE_TABLE_ENTRY(); + entry.lpServiceName = serviceName; + entry.lpServiceProc = serviceMain; + + Advapi32.INSTANCE.StartServiceCtrlDispatcher((SERVICE_TABLE_ENTRY[]) entry.toArray(2)); + } + + private SC_HANDLE openServiceControlManager(String machine, int access) { + return ADVAPI_32.OpenSCManager(machine, null, access); + } + + private void reportStatus(int status, int win32ExitCode, int waitHint) { + SERVICE_STATUS serviceStatus = new SERVICE_STATUS(); + serviceStatus.dwServiceType = WinNT.SERVICE_WIN32_OWN_PROCESS; + serviceStatus.dwControlsAccepted = Winsvc.SERVICE_ACCEPT_STOP | Winsvc.SERVICE_ACCEPT_SHUTDOWN; + serviceStatus.dwWin32ExitCode = win32ExitCode; + serviceStatus.dwWaitHint = waitHint; + serviceStatus.dwCurrentState = status; + + ADVAPI_32.SetServiceStatus(serviceStatusHandle, serviceStatus); + } + + public abstract void run(); + + private class ServiceMain implements SERVICE_MAIN_FUNCTION { + + public void callback(int dwArgc, Pointer lpszArgv) { + serviceControl = new ServiceControl(); + serviceStatusHandle = ADVAPI_32.RegisterServiceCtrlHandlerEx(serviceName, serviceControl, null); + + reportStatus(Winsvc.SERVICE_START_PENDING, WinError.NO_ERROR, 3000); + reportStatus(Winsvc.SERVICE_RUNNING, WinError.NO_ERROR, 0); + + Thread.currentThread().setContextClassLoader(WindowsService.class.getClassLoader()); + + run(); + + try { + synchronized (waitObject) { + waitObject.wait(); + } + } catch (InterruptedException ex) { + } + + reportStatus(Winsvc.SERVICE_STOPPED, WinError.NO_ERROR, 0); + + // Avoid returning from ServiceMain, which will cause a crash + // See http://support.microsoft.com/kb/201349, which recommends + // having init() wait for this thread. + // Waiting on this thread in init() won't fix the crash, though. + + System.exit(0); + } + + } + + private class ServiceControl implements HandlerEx { + + public int callback(int dwControl, int dwEventType, Pointer lpEventData, Pointer lpContext) { + switch (dwControl) { + case Winsvc.SERVICE_CONTROL_STOP: + case Winsvc.SERVICE_CONTROL_SHUTDOWN: + reportStatus(Winsvc.SERVICE_STOP_PENDING, WinError.NO_ERROR, 5000); + synchronized (waitObject) { + waitObject.notifyAll(); + } + break; + default: + break; + } + return WinError.NO_ERROR; + } + + } + +} diff --git a/src/main/java/org/traccar/WrapperContext.java b/src/main/java/org/traccar/WrapperContext.java new file mode 100644 index 000000000..372d3c60d --- /dev/null +++ b/src/main/java/org/traccar/WrapperContext.java @@ -0,0 +1,255 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelProgressivePromise; +import io.netty.channel.ChannelPromise; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import io.netty.util.concurrent.EventExecutor; + +import java.net.SocketAddress; + +public class WrapperContext implements ChannelHandlerContext { + + private ChannelHandlerContext context; + private SocketAddress remoteAddress; + + public WrapperContext(ChannelHandlerContext context, SocketAddress remoteAddress) { + this.context = context; + this.remoteAddress = remoteAddress; + } + + @Override + public Channel channel() { + return context.channel(); + } + + @Override + public EventExecutor executor() { + return context.executor(); + } + + @Override + public String name() { + return context.name(); + } + + @Override + public ChannelHandler handler() { + return context.handler(); + } + + @Override + public boolean isRemoved() { + return context.isRemoved(); + } + + @Override + public ChannelHandlerContext fireChannelRegistered() { + return context.fireChannelRegistered(); + } + + @Override + public ChannelHandlerContext fireChannelUnregistered() { + return context.fireChannelUnregistered(); + } + + @Override + public ChannelHandlerContext fireChannelActive() { + return context.fireChannelActive(); + } + + @Override + public ChannelHandlerContext fireChannelInactive() { + return context.fireChannelInactive(); + } + + @Override + public ChannelHandlerContext fireExceptionCaught(Throwable cause) { + return context.fireExceptionCaught(cause); + } + + @Override + public ChannelHandlerContext fireUserEventTriggered(Object evt) { + return context.fireUserEventTriggered(evt); + } + + @Override + public ChannelHandlerContext fireChannelRead(Object msg) { + if (!(msg instanceof NetworkMessage)) { + msg = new NetworkMessage(msg, remoteAddress); + } + return context.fireChannelRead(msg); + } + + @Override + public ChannelHandlerContext fireChannelReadComplete() { + return context.fireChannelReadComplete(); + } + + @Override + public ChannelHandlerContext fireChannelWritabilityChanged() { + return context.fireChannelWritabilityChanged(); + } + + @Override + public ChannelFuture bind(SocketAddress localAddress) { + return context.bind(localAddress); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress) { + return context.connect(remoteAddress); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { + return context.connect(remoteAddress, localAddress); + } + + @Override + public ChannelFuture disconnect() { + return context.disconnect(); + } + + @Override + public ChannelFuture close() { + return context.close(); + } + + @Override + public ChannelFuture deregister() { + return context.deregister(); + } + + @Override + public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { + return context.bind(localAddress, promise); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { + return context.connect(remoteAddress, promise); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { + return context.connect(remoteAddress, localAddress, promise); + } + + @Override + public ChannelFuture disconnect(ChannelPromise promise) { + return context.disconnect(promise); + } + + @Override + public ChannelFuture close(ChannelPromise promise) { + return context.close(promise); + } + + @Override + public ChannelFuture deregister(ChannelPromise promise) { + return context.deregister(promise); + } + + @Override + public ChannelHandlerContext read() { + return context.read(); + } + + @Override + public ChannelFuture write(Object msg) { + return context.write(msg); + } + + @Override + public ChannelFuture write(Object msg, ChannelPromise promise) { + if (!(msg instanceof NetworkMessage)) { + msg = new NetworkMessage(msg, remoteAddress); + } + return context.write(msg, promise); + } + + @Override + public ChannelHandlerContext flush() { + return context.flush(); + } + + @Override + public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { + return context.writeAndFlush(msg, promise); + } + + @Override + public ChannelFuture writeAndFlush(Object msg) { + return context.writeAndFlush(msg); + } + + @Override + public ChannelPromise newPromise() { + return context.newPromise(); + } + + @Override + public ChannelProgressivePromise newProgressivePromise() { + return context.newProgressivePromise(); + } + + @Override + public ChannelFuture newSucceededFuture() { + return context.newSucceededFuture(); + } + + @Override + public ChannelFuture newFailedFuture(Throwable cause) { + return context.newFailedFuture(cause); + } + + @Override + public ChannelPromise voidPromise() { + return context.voidPromise(); + } + + @Override + public ChannelPipeline pipeline() { + return context.pipeline(); + } + + @Override + public ByteBufAllocator alloc() { + return context.alloc(); + } + + @SuppressWarnings("deprecation") + @Override + public <T> Attribute<T> attr(AttributeKey<T> key) { + return context.attr(key); + } + + @SuppressWarnings("deprecation") + @Override + public <T> boolean hasAttr(AttributeKey<T> key) { + return context.hasAttr(key); + } + +} diff --git a/src/main/java/org/traccar/WrapperInboundHandler.java b/src/main/java/org/traccar/WrapperInboundHandler.java new file mode 100644 index 000000000..ca33d021f --- /dev/null +++ b/src/main/java/org/traccar/WrapperInboundHandler.java @@ -0,0 +1,94 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandler; + +public class WrapperInboundHandler implements ChannelInboundHandler { + + private ChannelInboundHandler handler; + + public ChannelInboundHandler getWrappedHandler() { + return handler; + } + + public WrapperInboundHandler(ChannelInboundHandler handler) { + this.handler = handler; + } + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + handler.channelRegistered(ctx); + } + + @Override + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { + handler.channelUnregistered(ctx); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + handler.channelActive(ctx); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + handler.channelInactive(ctx); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof NetworkMessage) { + NetworkMessage nm = (NetworkMessage) msg; + handler.channelRead(new WrapperContext(ctx, nm.getRemoteAddress()), nm.getMessage()); + } else { + handler.channelRead(ctx, msg); + } + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + handler.channelReadComplete(ctx); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + handler.userEventTriggered(ctx, evt); + } + + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { + handler.channelWritabilityChanged(ctx); + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + handler.handlerAdded(ctx); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + handler.handlerRemoved(ctx); + } + + @SuppressWarnings("deprecation") + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + handler.exceptionCaught(ctx, cause); + } + +} diff --git a/src/main/java/org/traccar/WrapperOutboundHandler.java b/src/main/java/org/traccar/WrapperOutboundHandler.java new file mode 100644 index 000000000..0136c5b22 --- /dev/null +++ b/src/main/java/org/traccar/WrapperOutboundHandler.java @@ -0,0 +1,99 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandler; +import io.netty.channel.ChannelPromise; + +import java.net.SocketAddress; + +public class WrapperOutboundHandler implements ChannelOutboundHandler { + + private ChannelOutboundHandler handler; + + public ChannelOutboundHandler getWrappedHandler() { + return handler; + } + + public WrapperOutboundHandler(ChannelOutboundHandler handler) { + this.handler = handler; + } + + @Override + public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception { + handler.bind(ctx, localAddress, promise); + } + + @Override + public void connect( + ChannelHandlerContext ctx, SocketAddress remoteAddress, + SocketAddress localAddress, ChannelPromise promise) throws Exception { + handler.connect(ctx, remoteAddress, localAddress, promise); + } + + @Override + public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + handler.disconnect(ctx, promise); + } + + @Override + public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + handler.close(ctx, promise); + } + + @Override + public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + handler.deregister(ctx, promise); + } + + @Override + public void read(ChannelHandlerContext ctx) throws Exception { + handler.read(ctx); + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (msg instanceof NetworkMessage) { + NetworkMessage nm = (NetworkMessage) msg; + handler.write(new WrapperContext(ctx, nm.getRemoteAddress()), nm.getMessage(), promise); + } else { + handler.write(ctx, msg, promise); + } + } + + @Override + public void flush(ChannelHandlerContext ctx) throws Exception { + handler.flush(ctx); + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + handler.handlerAdded(ctx); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + handler.handlerRemoved(ctx); + } + + @SuppressWarnings("deprecation") + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + handler.exceptionCaught(ctx, cause); + } + +} diff --git a/src/main/java/org/traccar/api/AsyncSocket.java b/src/main/java/org/traccar/api/AsyncSocket.java new file mode 100644 index 000000000..906d16b5b --- /dev/null +++ b/src/main/java/org/traccar/api/AsyncSocket.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015 - 2016 Anton Tananaev (anton@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.fasterxml.jackson.core.JsonProcessingException; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.database.ConnectionManager; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Position; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.UpdateListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(AsyncSocket.class); + + private static final String KEY_DEVICES = "devices"; + private static final String KEY_POSITIONS = "positions"; + private static final String KEY_EVENTS = "events"; + + private long userId; + + public AsyncSocket(long userId) { + this.userId = userId; + } + + @Override + public void onWebSocketConnect(Session session) { + super.onWebSocketConnect(session); + + Map<String, Collection<?>> data = new HashMap<>(); + data.put(KEY_POSITIONS, Context.getDeviceManager().getInitialState(userId)); + sendData(data); + + Context.getConnectionManager().addListener(userId, this); + } + + @Override + public void onWebSocketClose(int statusCode, String reason) { + super.onWebSocketClose(statusCode, reason); + + Context.getConnectionManager().removeListener(userId, this); + } + + @Override + public void onUpdateDevice(Device device) { + Map<String, Collection<?>> data = new HashMap<>(); + data.put(KEY_DEVICES, Collections.singletonList(device)); + sendData(data); + } + + @Override + public void onUpdatePosition(Position position) { + Map<String, Collection<?>> data = new HashMap<>(); + data.put(KEY_POSITIONS, Collections.singletonList(position)); + sendData(data); + } + + @Override + public void onUpdateEvent(Event event) { + Map<String, Collection<?>> data = new HashMap<>(); + data.put(KEY_EVENTS, Collections.singletonList(event)); + sendData(data); + } + + private void sendData(Map<String, Collection<?>> data) { + if (!data.isEmpty() && isConnected()) { + try { + getRemote().sendString(Context.getObjectMapper().writeValueAsString(data), null); + } catch (JsonProcessingException e) { + LOGGER.warn("Socket JSON formatting error", e); + } + } + } +} diff --git a/src/main/java/org/traccar/api/AsyncSocketServlet.java b/src/main/java/org/traccar/api/AsyncSocketServlet.java new file mode 100644 index 000000000..9318b6fc6 --- /dev/null +++ b/src/main/java/org/traccar/api/AsyncSocketServlet.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api; + +import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.servlet.WebSocketCreator; +import org.eclipse.jetty.websocket.servlet.WebSocketServlet; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; +import org.traccar.Context; +import org.traccar.api.resource.SessionResource; + +public class AsyncSocketServlet extends WebSocketServlet { + + private static final long ASYNC_TIMEOUT = 10 * 60 * 1000; + + @Override + public void configure(WebSocketServletFactory factory) { + factory.getPolicy().setIdleTimeout(Context.getConfig().getLong("web.timeout", ASYNC_TIMEOUT)); + factory.setCreator(new WebSocketCreator() { + @Override + public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) { + if (req.getSession() != null) { + long userId = (Long) req.getSession().getAttribute(SessionResource.USER_ID_KEY); + return new AsyncSocket(userId); + } else { + return null; + } + } + }); + } + +} diff --git a/src/main/java/org/traccar/api/BaseObjectResource.java b/src/main/java/org/traccar/api/BaseObjectResource.java new file mode 100644 index 000000000..7de6a3877 --- /dev/null +++ b/src/main/java/org/traccar/api/BaseObjectResource.java @@ -0,0 +1,176 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api; + +import java.sql.SQLException; +import java.util.Set; + +import javax.ws.rs.DELETE; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Response; + +import org.traccar.Context; +import org.traccar.database.BaseObjectManager; +import org.traccar.database.ExtendedObjectManager; +import org.traccar.database.ManagableObjects; +import org.traccar.database.SimpleObjectManager; +import org.traccar.helper.LogAction; +import org.traccar.model.BaseModel; +import org.traccar.model.Calendar; +import org.traccar.model.Command; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.model.GroupedModel; +import org.traccar.model.ScheduledModel; +import org.traccar.model.User; + +public abstract class BaseObjectResource<T extends BaseModel> extends BaseResource { + + private Class<T> baseClass; + + public BaseObjectResource(Class<T> baseClass) { + this.baseClass = baseClass; + } + + protected final Class<T> getBaseClass() { + return baseClass; + } + + protected final Set<Long> getSimpleManagerItems(BaseObjectManager<T> manager, boolean all, long userId) { + Set<Long> result = null; + if (all) { + if (Context.getPermissionsManager().getUserAdmin(getUserId())) { + result = manager.getAllItems(); + } else { + Context.getPermissionsManager().checkManager(getUserId()); + result = ((ManagableObjects) manager).getManagedItems(getUserId()); + } + } else { + if (userId == 0) { + userId = getUserId(); + } + Context.getPermissionsManager().checkUser(getUserId(), userId); + result = ((ManagableObjects) manager).getUserItems(userId); + } + return result; + } + + @POST + public Response add(T entity) throws SQLException { + Context.getPermissionsManager().checkReadonly(getUserId()); + if (baseClass.equals(Device.class)) { + Context.getPermissionsManager().checkDeviceReadonly(getUserId()); + Context.getPermissionsManager().checkDeviceLimit(getUserId()); + } else if (baseClass.equals(Command.class)) { + Context.getPermissionsManager().checkLimitCommands(getUserId()); + } else if (entity instanceof GroupedModel && ((GroupedModel) entity).getGroupId() != 0) { + Context.getPermissionsManager().checkPermission( + Group.class, getUserId(), ((GroupedModel) entity).getGroupId()); + } else if (entity instanceof ScheduledModel && ((ScheduledModel) entity).getCalendarId() != 0) { + Context.getPermissionsManager().checkPermission( + Calendar.class, getUserId(), ((ScheduledModel) entity).getCalendarId()); + } + + BaseObjectManager<T> manager = Context.getManager(baseClass); + manager.addItem(entity); + LogAction.create(getUserId(), entity); + + Context.getDataManager().linkObject(User.class, getUserId(), baseClass, entity.getId(), true); + LogAction.link(getUserId(), User.class, getUserId(), baseClass, entity.getId()); + + if (manager instanceof SimpleObjectManager) { + ((SimpleObjectManager<T>) manager).refreshUserItems(); + } else if (baseClass.equals(Group.class) || baseClass.equals(Device.class)) { + Context.getPermissionsManager().refreshDeviceAndGroupPermissions(); + Context.getPermissionsManager().refreshAllExtendedPermissions(); + } + return Response.ok(entity).build(); + } + + @Path("{id}") + @PUT + public Response update(T entity) throws SQLException { + Context.getPermissionsManager().checkReadonly(getUserId()); + if (baseClass.equals(Device.class)) { + Context.getPermissionsManager().checkDeviceReadonly(getUserId()); + } else if (baseClass.equals(User.class)) { + User before = Context.getPermissionsManager().getUser(entity.getId()); + Context.getPermissionsManager().checkUserUpdate(getUserId(), before, (User) entity); + } else if (baseClass.equals(Command.class)) { + Context.getPermissionsManager().checkLimitCommands(getUserId()); + } else if (entity instanceof GroupedModel && ((GroupedModel) entity).getGroupId() != 0) { + Context.getPermissionsManager().checkPermission( + Group.class, getUserId(), ((GroupedModel) entity).getGroupId()); + } else if (entity instanceof ScheduledModel && ((ScheduledModel) entity).getCalendarId() != 0) { + Context.getPermissionsManager().checkPermission( + Calendar.class, getUserId(), ((ScheduledModel) entity).getCalendarId()); + } + Context.getPermissionsManager().checkPermission(baseClass, getUserId(), entity.getId()); + + Context.getManager(baseClass).updateItem(entity); + LogAction.edit(getUserId(), entity); + + if (baseClass.equals(Group.class) || baseClass.equals(Device.class)) { + Context.getPermissionsManager().refreshDeviceAndGroupPermissions(); + Context.getPermissionsManager().refreshAllExtendedPermissions(); + } + return Response.ok(entity).build(); + } + + @Path("{id}") + @DELETE + public Response remove(@PathParam("id") long id) throws SQLException { + Context.getPermissionsManager().checkReadonly(getUserId()); + if (baseClass.equals(Device.class)) { + Context.getPermissionsManager().checkDeviceReadonly(getUserId()); + } else if (baseClass.equals(Command.class)) { + Context.getPermissionsManager().checkLimitCommands(getUserId()); + } + Context.getPermissionsManager().checkPermission(baseClass, getUserId(), id); + + BaseObjectManager<T> manager = Context.getManager(baseClass); + manager.removeItem(id); + LogAction.remove(getUserId(), baseClass, id); + + if (manager instanceof SimpleObjectManager) { + ((SimpleObjectManager<T>) manager).refreshUserItems(); + if (manager instanceof ExtendedObjectManager) { + ((ExtendedObjectManager<T>) manager).refreshExtendedPermissions(); + } + } + if (baseClass.equals(Group.class) || baseClass.equals(Device.class) || baseClass.equals(User.class)) { + if (baseClass.equals(Group.class)) { + Context.getGroupsManager().updateGroupCache(true); + Context.getDeviceManager().updateDeviceCache(true); + } + Context.getPermissionsManager().refreshDeviceAndGroupPermissions(); + if (baseClass.equals(User.class)) { + Context.getPermissionsManager().refreshAllUsersPermissions(); + } else { + Context.getPermissionsManager().refreshAllExtendedPermissions(); + } + } else if (baseClass.equals(Calendar.class)) { + Context.getGeofenceManager().refreshItems(); + Context.getNotificationManager().refreshItems(); + } + return Response.noContent().build(); + } + +} diff --git a/src/main/java/org/traccar/api/BaseResource.java b/src/main/java/org/traccar/api/BaseResource.java new file mode 100644 index 000000000..cc272df9c --- /dev/null +++ b/src/main/java/org/traccar/api/BaseResource.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api; + +import javax.ws.rs.core.SecurityContext; + +public class BaseResource { + + @javax.ws.rs.core.Context + private SecurityContext securityContext; + + protected long getUserId() { + UserPrincipal principal = (UserPrincipal) securityContext.getUserPrincipal(); + if (principal != null) { + return principal.getUserId(); + } + return 0; + } +} diff --git a/src/main/java/org/traccar/api/CorsResponseFilter.java b/src/main/java/org/traccar/api/CorsResponseFilter.java new file mode 100644 index 000000000..227f80609 --- /dev/null +++ b/src/main/java/org/traccar/api/CorsResponseFilter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 io.netty.handler.codec.http.HttpHeaderNames; +import org.traccar.Context; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import java.io.IOException; + +public class CorsResponseFilter implements ContainerResponseFilter { + + private static final String ORIGIN_ALL = "*"; + private static final String HEADERS_ALL = "origin, content-type, accept, authorization"; + private static final String METHODS_ALL = "GET, POST, PUT, DELETE, OPTIONS"; + + @Override + public void filter(ContainerRequestContext request, ContainerResponseContext response) throws IOException { + if (!response.getHeaders().containsKey(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS.toString())) { + response.getHeaders().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS.toString(), HEADERS_ALL); + } + + if (!response.getHeaders().containsKey(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS.toString())) { + response.getHeaders().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS.toString(), true); + } + + if (!response.getHeaders().containsKey(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS.toString())) { + response.getHeaders().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS.toString(), METHODS_ALL); + } + + if (!response.getHeaders().containsKey(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN.toString())) { + String origin = request.getHeaderString(HttpHeaderNames.ORIGIN.toString()); + String allowed = Context.getConfig().getString("web.origin"); + + if (origin == null) { + response.getHeaders().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN.toString(), ORIGIN_ALL); + } else if (allowed == null || allowed.equals(ORIGIN_ALL) || allowed.contains(origin)) { + response.getHeaders().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN.toString(), origin); + } + } + } + +} diff --git a/src/main/java/org/traccar/api/ExtendedObjectResource.java b/src/main/java/org/traccar/api/ExtendedObjectResource.java new file mode 100644 index 000000000..007a7b1bd --- /dev/null +++ b/src/main/java/org/traccar/api/ExtendedObjectResource.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api; + +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.ws.rs.GET; +import javax.ws.rs.QueryParam; + +import org.traccar.Context; +import org.traccar.database.ExtendedObjectManager; +import org.traccar.model.BaseModel; + +public class ExtendedObjectResource<T extends BaseModel> extends BaseObjectResource<T> { + + public ExtendedObjectResource(Class<T> baseClass) { + super(baseClass); + } + + @GET + public Collection<T> get( + @QueryParam("all") boolean all, @QueryParam("userId") long userId, @QueryParam("groupId") long groupId, + @QueryParam("deviceId") long deviceId, @QueryParam("refresh") boolean refresh) throws SQLException { + + ExtendedObjectManager<T> manager = (ExtendedObjectManager<T>) Context.getManager(getBaseClass()); + if (refresh) { + manager.refreshItems(); + } + + Set<Long> result = new HashSet<>(getSimpleManagerItems(manager, all, userId)); + + if (groupId != 0) { + Context.getPermissionsManager().checkGroup(getUserId(), groupId); + result.retainAll(manager.getGroupItems(groupId)); + } + + if (deviceId != 0) { + Context.getPermissionsManager().checkDevice(getUserId(), deviceId); + result.retainAll(manager.getDeviceItems(deviceId)); + } + return manager.getItems(result); + + } + +} diff --git a/src/main/java/org/traccar/api/MediaFilter.java b/src/main/java/org/traccar/api/MediaFilter.java new file mode 100644 index 000000000..53539770f --- /dev/null +++ b/src/main/java/org/traccar/api/MediaFilter.java @@ -0,0 +1,92 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api; + +import java.io.IOException; +import java.sql.SQLException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.traccar.Context; +import org.traccar.Main; +import org.traccar.api.resource.SessionResource; +import org.traccar.database.StatisticsManager; +import org.traccar.helper.Log; +import org.traccar.model.Device; + +public class MediaFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletResponse httpResponse = (HttpServletResponse) response; + try { + HttpSession session = ((HttpServletRequest) request).getSession(false); + Long userId = null; + if (session != null) { + userId = (Long) session.getAttribute(SessionResource.USER_ID_KEY); + if (userId != null) { + Context.getPermissionsManager().checkUserEnabled(userId); + Main.getInjector().getInstance(StatisticsManager.class).registerRequest(userId); + } + } + if (userId == null) { + httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + + String path = ((HttpServletRequest) request).getPathInfo(); + String[] parts = path.split("/"); + if (parts.length < 2 || parts.length == 2 && !path.endsWith("/")) { + Context.getPermissionsManager().checkAdmin(userId); + } else { + Device device = Context.getDeviceManager().getByUniqueId(parts[1]); + if (device != null) { + Context.getPermissionsManager().checkDevice(userId, device.getId()); + } else { + httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + } + + chain.doFilter(request, response); + } catch (SecurityException e) { + httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); + httpResponse.getWriter().println(Log.exceptionStack(e)); + } catch (SQLException e) { + httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); + httpResponse.getWriter().println(Log.exceptionStack(e)); + } + } + + @Override + public void destroy() { + } + +} diff --git a/src/main/java/org/traccar/api/ObjectMapperProvider.java b/src/main/java/org/traccar/api/ObjectMapperProvider.java new file mode 100644 index 000000000..f81b20917 --- /dev/null +++ b/src/main/java/org/traccar/api/ObjectMapperProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 - 2016 Anton Tananaev (anton@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.fasterxml.jackson.databind.ObjectMapper; +import org.traccar.Context; + +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.Provider; + +@Provider +public class ObjectMapperProvider implements ContextResolver<ObjectMapper> { + + @Override + public ObjectMapper getContext(Class<?> type) { + return Context.getObjectMapper(); + } + +} diff --git a/src/main/java/org/traccar/api/ResourceErrorHandler.java b/src/main/java/org/traccar/api/ResourceErrorHandler.java new file mode 100644 index 000000000..108a8e8cc --- /dev/null +++ b/src/main/java/org/traccar/api/ResourceErrorHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api; + +import org.traccar.helper.Log; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; + +public class ResourceErrorHandler implements ExceptionMapper<Exception> { + + @Override + public Response toResponse(Exception e) { + if (e instanceof WebApplicationException) { + WebApplicationException webException = (WebApplicationException) e; + return Response.fromResponse(webException.getResponse()).entity(Log.exceptionStack(webException)).build(); + } else { + return Response.status(Response.Status.BAD_REQUEST).entity(Log.exceptionStack(e)).build(); + } + } + +} diff --git a/src/main/java/org/traccar/api/SecurityRequestFilter.java b/src/main/java/org/traccar/api/SecurityRequestFilter.java new file mode 100644 index 000000000..33b6b37df --- /dev/null +++ b/src/main/java/org/traccar/api/SecurityRequestFilter.java @@ -0,0 +1,119 @@ +/* + * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.Main; +import org.traccar.api.resource.SessionResource; +import org.traccar.database.StatisticsManager; +import org.traccar.helper.DataConverter; +import org.traccar.model.User; + +import javax.annotation.security.PermitAll; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; + +public class SecurityRequestFilter implements ContainerRequestFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(SecurityRequestFilter.class); + + public static final String AUTHORIZATION_HEADER = "Authorization"; + public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + public static final String BASIC_REALM = "Basic realm=\"api\""; + public static final String X_REQUESTED_WITH = "X-Requested-With"; + public static final String XML_HTTP_REQUEST = "XMLHttpRequest"; + + public static String[] decodeBasicAuth(String auth) { + auth = auth.replaceFirst("[B|b]asic ", ""); + byte[] decodedBytes = DataConverter.parseBase64(auth); + if (decodedBytes != null && decodedBytes.length > 0) { + return new String(decodedBytes, StandardCharsets.US_ASCII).split(":", 2); + } + return null; + } + + @javax.ws.rs.core.Context + private HttpServletRequest request; + + @javax.ws.rs.core.Context + private ResourceInfo resourceInfo; + + @Override + public void filter(ContainerRequestContext requestContext) { + + if (requestContext.getMethod().equals("OPTIONS")) { + return; + } + + SecurityContext securityContext = null; + + try { + + String authHeader = requestContext.getHeaderString(AUTHORIZATION_HEADER); + if (authHeader != null) { + + try { + String[] auth = decodeBasicAuth(authHeader); + User user = Context.getPermissionsManager().login(auth[0], auth[1]); + if (user != null) { + Main.getInjector().getInstance(StatisticsManager.class).registerRequest(user.getId()); + securityContext = new UserSecurityContext(new UserPrincipal(user.getId())); + } + } catch (SQLException e) { + throw new WebApplicationException(e); + } + + } else if (request.getSession() != null) { + + Long userId = (Long) request.getSession().getAttribute(SessionResource.USER_ID_KEY); + if (userId != null) { + Context.getPermissionsManager().checkUserEnabled(userId); + Main.getInjector().getInstance(StatisticsManager.class).registerRequest(userId); + securityContext = new UserSecurityContext(new UserPrincipal(userId)); + } + + } + + } catch (SecurityException e) { + LOGGER.warn("Authentication error", e); + } + + if (securityContext != null) { + requestContext.setSecurityContext(securityContext); + } else { + Method method = resourceInfo.getResourceMethod(); + if (!method.isAnnotationPresent(PermitAll.class)) { + Response.ResponseBuilder responseBuilder = Response.status(Response.Status.UNAUTHORIZED); + if (!XML_HTTP_REQUEST.equals(request.getHeader(X_REQUESTED_WITH))) { + responseBuilder.header(WWW_AUTHENTICATE, BASIC_REALM); + } + throw new WebApplicationException(responseBuilder.build()); + } + } + + } + +} diff --git a/src/main/java/org/traccar/api/SimpleObjectResource.java b/src/main/java/org/traccar/api/SimpleObjectResource.java new file mode 100644 index 000000000..a7fcae0e7 --- /dev/null +++ b/src/main/java/org/traccar/api/SimpleObjectResource.java @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api; + +import java.sql.SQLException; +import java.util.Collection; + +import javax.ws.rs.GET; +import javax.ws.rs.QueryParam; + +import org.traccar.Context; +import org.traccar.database.BaseObjectManager; +import org.traccar.model.BaseModel; + +public class SimpleObjectResource<T extends BaseModel> extends BaseObjectResource<T> { + + public SimpleObjectResource(Class<T> baseClass) { + super(baseClass); + } + + @GET + public Collection<T> get( + @QueryParam("all") boolean all, @QueryParam("userId") long userId) throws SQLException { + + BaseObjectManager<T> manager = Context.getManager(getBaseClass()); + return manager.getItems(getSimpleManagerItems(manager, all, userId)); + } + +} diff --git a/src/main/java/org/traccar/api/UserPrincipal.java b/src/main/java/org/traccar/api/UserPrincipal.java new file mode 100644 index 000000000..80e92c2dd --- /dev/null +++ b/src/main/java/org/traccar/api/UserPrincipal.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Anton Tananaev (anton@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 java.security.Principal; + +public class UserPrincipal implements Principal { + + private long userId; + + public UserPrincipal(long userId) { + this.userId = userId; + } + + public Long getUserId() { + return userId; + } + + @Override + public String getName() { + return null; + } + +} diff --git a/src/main/java/org/traccar/api/UserSecurityContext.java b/src/main/java/org/traccar/api/UserSecurityContext.java new file mode 100644 index 000000000..55c0621bc --- /dev/null +++ b/src/main/java/org/traccar/api/UserSecurityContext.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 Anton Tananaev (anton@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 javax.ws.rs.core.SecurityContext; +import java.security.Principal; + +public class UserSecurityContext implements SecurityContext { + + private UserPrincipal principal; + + public UserSecurityContext(UserPrincipal principal) { + this.principal = principal; + } + + @Override + public Principal getUserPrincipal() { + return principal; + } + + @Override + public boolean isUserInRole(String role) { + return true; + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public String getAuthenticationScheme() { + return BASIC_AUTH; + } + +} diff --git a/src/main/java/org/traccar/api/resource/AttributeResource.java b/src/main/java/org/traccar/api/resource/AttributeResource.java new file mode 100644 index 000000000..de69d871c --- /dev/null +++ b/src/main/java/org/traccar/api/resource/AttributeResource.java @@ -0,0 +1,97 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import java.sql.SQLException; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.traccar.Context; +import org.traccar.api.ExtendedObjectResource; +import org.traccar.model.Attribute; +import org.traccar.model.Position; +import org.traccar.handler.ComputedAttributesHandler; + +@Path("attributes/computed") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class AttributeResource extends ExtendedObjectResource<Attribute> { + + public AttributeResource() { + super(Attribute.class); + } + + @POST + @Path("test") + public Response test(@QueryParam("deviceId") long deviceId, Attribute entity) { + Context.getPermissionsManager().checkAdmin(getUserId()); + Context.getPermissionsManager().checkDevice(getUserId(), deviceId); + Position last = Context.getIdentityManager().getLastPosition(deviceId); + if (last != null) { + Object result = new ComputedAttributesHandler( + Context.getConfig(), + Context.getIdentityManager(), + Context.getAttributesManager()).computeAttribute(entity, last); + if (result != null) { + switch (entity.getType()) { + case "number": + Number numberValue = (Number) result; + return Response.ok(numberValue).build(); + case "boolean": + Boolean booleanValue = (Boolean) result; + return Response.ok(booleanValue).build(); + default: + return Response.ok(result.toString()).build(); + } + } else { + return Response.noContent().build(); + } + } else { + throw new IllegalArgumentException("Device has no last position"); + } + } + + @POST + public Response add(Attribute entity) throws SQLException { + Context.getPermissionsManager().checkAdmin(getUserId()); + return super.add(entity); + } + + @Path("{id}") + @PUT + public Response update(Attribute entity) throws SQLException { + Context.getPermissionsManager().checkAdmin(getUserId()); + return super.update(entity); + } + + @Path("{id}") + @DELETE + public Response remove(@PathParam("id") long id) throws SQLException { + Context.getPermissionsManager().checkAdmin(getUserId()); + return super.remove(id); + } + +} diff --git a/src/main/java/org/traccar/api/resource/CalendarResource.java b/src/main/java/org/traccar/api/resource/CalendarResource.java new file mode 100644 index 000000000..9399c34a5 --- /dev/null +++ b/src/main/java/org/traccar/api/resource/CalendarResource.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.traccar.api.SimpleObjectResource; +import org.traccar.model.Calendar; + +@Path("calendars") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class CalendarResource extends SimpleObjectResource<Calendar> { + + public CalendarResource() { + super(Calendar.class); + } + +} diff --git a/src/main/java/org/traccar/api/resource/CommandResource.java b/src/main/java/org/traccar/api/resource/CommandResource.java new file mode 100644 index 000000000..a31345246 --- /dev/null +++ b/src/main/java/org/traccar/api/resource/CommandResource.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Gabor Somogyi (gabor.g.somogyi@gmail.com) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import org.traccar.Context; +import org.traccar.api.ExtendedObjectResource; +import org.traccar.database.CommandsManager; +import org.traccar.model.Command; +import org.traccar.model.Typed; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Path("commands") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class CommandResource extends ExtendedObjectResource<Command> { + + public CommandResource() { + super(Command.class); + } + + @GET + @Path("send") + public Collection<Command> get(@QueryParam("deviceId") long deviceId) { + Context.getPermissionsManager().checkDevice(getUserId(), deviceId); + CommandsManager commandsManager = Context.getCommandsManager(); + Set<Long> result = new HashSet<>(commandsManager.getUserItems(getUserId())); + result.retainAll(commandsManager.getSupportedCommands(deviceId)); + return commandsManager.getItems(result); + } + + @POST + @Path("send") + public Response send(Command entity) throws Exception { + Context.getPermissionsManager().checkReadonly(getUserId()); + long deviceId = entity.getDeviceId(); + long id = entity.getId(); + Context.getPermissionsManager().checkDevice(getUserId(), deviceId); + if (id != 0) { + Context.getPermissionsManager().checkPermission(Command.class, getUserId(), id); + Context.getPermissionsManager().checkUserDeviceCommand(getUserId(), deviceId, id); + } else { + Context.getPermissionsManager().checkLimitCommands(getUserId()); + } + if (!Context.getCommandsManager().sendCommand(entity)) { + return Response.accepted(entity).build(); + } + return Response.ok(entity).build(); + } + + @GET + @Path("types") + public Collection<Typed> get( + @QueryParam("deviceId") long deviceId, + @QueryParam("protocol") String protocol, + @QueryParam("textChannel") boolean textChannel) { + if (deviceId != 0) { + Context.getPermissionsManager().checkDevice(getUserId(), deviceId); + return Context.getCommandsManager().getCommandTypes(deviceId, textChannel); + } else if (protocol != null) { + return Context.getCommandsManager().getCommandTypes(protocol, textChannel); + } else { + return Context.getCommandsManager().getAllCommandTypes(); + } + } +} diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java new file mode 100644 index 000000000..f9c9a139d --- /dev/null +++ b/src/main/java/org/traccar/api/resource/DeviceResource.java @@ -0,0 +1,100 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import org.traccar.Context; +import org.traccar.api.BaseObjectResource; +import org.traccar.database.DeviceManager; +import org.traccar.helper.LogAction; +import org.traccar.model.Device; +import org.traccar.model.DeviceAccumulators; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Path("devices") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class DeviceResource extends BaseObjectResource<Device> { + + public DeviceResource() { + super(Device.class); + } + + @GET + public Collection<Device> get( + @QueryParam("all") boolean all, @QueryParam("userId") long userId, + @QueryParam("uniqueId") List<String> uniqueIds, + @QueryParam("id") List<Long> deviceIds) throws SQLException { + DeviceManager deviceManager = Context.getDeviceManager(); + Set<Long> result = null; + if (all) { + if (Context.getPermissionsManager().getUserAdmin(getUserId())) { + result = deviceManager.getAllItems(); + } else { + Context.getPermissionsManager().checkManager(getUserId()); + result = deviceManager.getManagedItems(getUserId()); + } + } else if (uniqueIds.isEmpty() && deviceIds.isEmpty()) { + if (userId == 0) { + userId = getUserId(); + } + Context.getPermissionsManager().checkUser(getUserId(), userId); + if (Context.getPermissionsManager().getUserAdmin(getUserId())) { + result = deviceManager.getAllUserItems(userId); + } else { + result = deviceManager.getUserItems(userId); + } + } else { + result = new HashSet<>(); + for (String uniqueId : uniqueIds) { + Device device = deviceManager.getByUniqueId(uniqueId); + Context.getPermissionsManager().checkDevice(getUserId(), device.getId()); + result.add(device.getId()); + } + for (Long deviceId : deviceIds) { + Context.getPermissionsManager().checkDevice(getUserId(), deviceId); + result.add(deviceId); + } + } + return deviceManager.getItems(result); + } + + @Path("{id}/accumulators") + @PUT + public Response updateAccumulators(DeviceAccumulators entity) throws SQLException { + if (!Context.getPermissionsManager().getUserAdmin(getUserId())) { + Context.getPermissionsManager().checkManager(getUserId()); + Context.getPermissionsManager().checkPermission(Device.class, getUserId(), entity.getDeviceId()); + } + Context.getDeviceManager().resetDeviceAccumulators(entity); + LogAction.resetDeviceAccumulators(getUserId(), entity.getDeviceId()); + return Response.noContent().build(); + } + +} diff --git a/src/main/java/org/traccar/api/resource/DriverResource.java b/src/main/java/org/traccar/api/resource/DriverResource.java new file mode 100644 index 000000000..91aa54c5e --- /dev/null +++ b/src/main/java/org/traccar/api/resource/DriverResource.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.traccar.api.ExtendedObjectResource; +import org.traccar.model.Driver; + +@Path("drivers") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class DriverResource extends ExtendedObjectResource<Driver> { + + public DriverResource() { + super(Driver.class); + } + +} diff --git a/src/main/java/org/traccar/api/resource/EventResource.java b/src/main/java/org/traccar/api/resource/EventResource.java new file mode 100644 index 000000000..e0ccf7020 --- /dev/null +++ b/src/main/java/org/traccar/api/resource/EventResource.java @@ -0,0 +1,38 @@ +package org.traccar.api.resource; + +import java.sql.SQLException; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.traccar.Context; +import org.traccar.api.BaseResource; +import org.traccar.model.Event; +import org.traccar.model.Geofence; +import org.traccar.model.Maintenance; + +@Path("events") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) + +public class EventResource extends BaseResource { + + @Path("{id}") + @GET + public Event get(@PathParam("id") long id) throws SQLException { + Event event = Context.getDataManager().getObject(Event.class, id); + Context.getPermissionsManager().checkDevice(getUserId(), event.getDeviceId()); + if (event.getGeofenceId() != 0) { + Context.getPermissionsManager().checkPermission(Geofence.class, getUserId(), event.getGeofenceId()); + } + if (event.getMaintenanceId() != 0) { + Context.getPermissionsManager().checkPermission(Maintenance.class, getUserId(), event.getMaintenanceId()); + } + return event; + } + +} diff --git a/src/main/java/org/traccar/api/resource/GeofenceResource.java b/src/main/java/org/traccar/api/resource/GeofenceResource.java new file mode 100644 index 000000000..58f2c188c --- /dev/null +++ b/src/main/java/org/traccar/api/resource/GeofenceResource.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import org.traccar.api.ExtendedObjectResource; +import org.traccar.model.Geofence; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("geofences") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class GeofenceResource extends ExtendedObjectResource<Geofence> { + + public GeofenceResource() { + super(Geofence.class); + } + +} diff --git a/src/main/java/org/traccar/api/resource/GroupResource.java b/src/main/java/org/traccar/api/resource/GroupResource.java new file mode 100644 index 000000000..fcea15d0a --- /dev/null +++ b/src/main/java/org/traccar/api/resource/GroupResource.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import org.traccar.api.SimpleObjectResource; +import org.traccar.model.Group; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("groups") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class GroupResource extends SimpleObjectResource<Group> { + + public GroupResource() { + super(Group.class); + } + +} diff --git a/src/main/java/org/traccar/api/resource/MaintenanceResource.java b/src/main/java/org/traccar/api/resource/MaintenanceResource.java new file mode 100644 index 000000000..fa1b359ce --- /dev/null +++ b/src/main/java/org/traccar/api/resource/MaintenanceResource.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.traccar.api.ExtendedObjectResource; +import org.traccar.model.Maintenance; + +@Path("maintenance") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class MaintenanceResource extends ExtendedObjectResource<Maintenance> { + + public MaintenanceResource() { + super(Maintenance.class); + } + +} diff --git a/src/main/java/org/traccar/api/resource/NotificationResource.java b/src/main/java/org/traccar/api/resource/NotificationResource.java new file mode 100644 index 000000000..9631a52b7 --- /dev/null +++ b/src/main/java/org/traccar/api/resource/NotificationResource.java @@ -0,0 +1,76 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import java.util.Collection; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.traccar.Context; +import org.traccar.api.ExtendedObjectResource; +import org.traccar.model.Event; +import org.traccar.model.Notification; +import org.traccar.model.Typed; +import org.traccar.notification.MessageException; + + +@Path("notifications") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class NotificationResource extends ExtendedObjectResource<Notification> { + + public NotificationResource() { + super(Notification.class); + } + + @GET + @Path("types") + public Collection<Typed> get() { + return Context.getNotificationManager().getAllNotificationTypes(); + } + + @GET + @Path("notificators") + public Collection<Typed> getNotificators() { + return Context.getNotificatorManager().getAllNotificatorTypes(); + } + + @POST + @Path("test") + public Response testMessage() throws MessageException, InterruptedException { + for (Typed method : Context.getNotificatorManager().getAllNotificatorTypes()) { + Context.getNotificatorManager() + .getNotificator(method.getType()).sendSync(getUserId(), new Event("test", 0), null); + } + return Response.noContent().build(); + } + + @POST + @Path("test/{notificator}") + public Response testMessage(@PathParam("notificator") String notificator) + throws MessageException, InterruptedException { + Context.getNotificatorManager().getNotificator(notificator).sendSync(getUserId(), new Event("test", 0), null); + return Response.noContent().build(); + } + +} diff --git a/src/main/java/org/traccar/api/resource/PermissionsResource.java b/src/main/java/org/traccar/api/resource/PermissionsResource.java new file mode 100644 index 000000000..b89d9d376 --- /dev/null +++ b/src/main/java/org/traccar/api/resource/PermissionsResource.java @@ -0,0 +1,84 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import java.sql.SQLException; +import java.util.LinkedHashMap; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.traccar.Context; +import org.traccar.api.BaseResource; +import org.traccar.helper.LogAction; +import org.traccar.model.Device; +import org.traccar.model.Permission; +import org.traccar.model.User; + +@Path("permissions") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class PermissionsResource extends BaseResource { + + private void checkPermission(Permission permission, boolean link) { + if (!link && permission.getOwnerClass().equals(User.class) + && permission.getPropertyClass().equals(Device.class)) { + if (getUserId() != permission.getOwnerId()) { + Context.getPermissionsManager().checkUser(getUserId(), permission.getOwnerId()); + } else { + Context.getPermissionsManager().checkAdmin(getUserId()); + } + } else { + Context.getPermissionsManager().checkPermission( + permission.getOwnerClass(), getUserId(), permission.getOwnerId()); + } + Context.getPermissionsManager().checkPermission( + permission.getPropertyClass(), getUserId(), permission.getPropertyId()); + } + + @POST + public Response add(LinkedHashMap<String, Long> entity) throws SQLException, ClassNotFoundException { + Context.getPermissionsManager().checkReadonly(getUserId()); + Permission permission = new Permission(entity); + checkPermission(permission, true); + Context.getDataManager().linkObject(permission.getOwnerClass(), permission.getOwnerId(), + permission.getPropertyClass(), permission.getPropertyId(), true); + LogAction.link(getUserId(), permission.getOwnerClass(), permission.getOwnerId(), + permission.getPropertyClass(), permission.getPropertyId()); + Context.getPermissionsManager().refreshPermissions(permission); + return Response.noContent().build(); + } + + @DELETE + public Response remove(LinkedHashMap<String, Long> entity) throws SQLException, ClassNotFoundException { + Context.getPermissionsManager().checkReadonly(getUserId()); + Permission permission = new Permission(entity); + checkPermission(permission, false); + Context.getDataManager().linkObject(permission.getOwnerClass(), permission.getOwnerId(), + permission.getPropertyClass(), permission.getPropertyId(), false); + LogAction.unlink(getUserId(), permission.getOwnerClass(), permission.getOwnerId(), + permission.getPropertyClass(), permission.getPropertyId()); + Context.getPermissionsManager().refreshPermissions(permission); + return Response.noContent().build(); + } + +} diff --git a/src/main/java/org/traccar/api/resource/PositionResource.java b/src/main/java/org/traccar/api/resource/PositionResource.java new file mode 100644 index 000000000..c031b842f --- /dev/null +++ b/src/main/java/org/traccar/api/resource/PositionResource.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import org.traccar.Context; +import org.traccar.api.BaseResource; +import org.traccar.helper.DateUtil; +import org.traccar.model.Position; +import org.traccar.web.CsvBuilder; +import org.traccar.web.GpxBuilder; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Path("positions") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class PositionResource extends BaseResource { + + public static final String TEXT_CSV = "text/csv"; + public static final String CONTENT_DISPOSITION_VALUE_CSV = "attachment; filename=positions.csv"; + public static final String GPX = "application/gpx+xml"; + public static final String CONTENT_DISPOSITION_VALUE_GPX = "attachment; filename=positions.gpx"; + + @GET + public Collection<Position> getJson( + @QueryParam("deviceId") long deviceId, @QueryParam("id") List<Long> positionIds, + @QueryParam("from") String from, @QueryParam("to") String to) + throws SQLException { + if (!positionIds.isEmpty()) { + ArrayList<Position> positions = new ArrayList<>(); + for (Long positionId : positionIds) { + Position position = Context.getDataManager().getObject(Position.class, positionId); + Context.getPermissionsManager().checkDevice(getUserId(), position.getDeviceId()); + positions.add(position); + } + return positions; + } else if (deviceId == 0) { + return Context.getDeviceManager().getInitialState(getUserId()); + } else { + Context.getPermissionsManager().checkDevice(getUserId(), deviceId); + return Context.getDataManager().getPositions( + deviceId, DateUtil.parseDate(from), DateUtil.parseDate(to)); + } + } + + @GET + @Produces(TEXT_CSV) + public Response getCsv( + @QueryParam("deviceId") long deviceId, @QueryParam("from") String from, @QueryParam("to") String to) + throws SQLException { + Context.getPermissionsManager().checkDevice(getUserId(), deviceId); + CsvBuilder csv = new CsvBuilder(); + csv.addHeaderLine(new Position()); + csv.addArray(Context.getDataManager().getPositions( + deviceId, DateUtil.parseDate(from), DateUtil.parseDate(to))); + return Response.ok(csv.build()).header(HttpHeaders.CONTENT_DISPOSITION, CONTENT_DISPOSITION_VALUE_CSV).build(); + } + + @GET + @Produces(GPX) + public Response getGpx( + @QueryParam("deviceId") long deviceId, @QueryParam("from") String from, @QueryParam("to") String to) + throws SQLException { + Context.getPermissionsManager().checkDevice(getUserId(), deviceId); + GpxBuilder gpx = new GpxBuilder(Context.getIdentityManager().getById(deviceId).getName()); + gpx.addPositions(Context.getDataManager().getPositions( + deviceId, DateUtil.parseDate(from), DateUtil.parseDate(to))); + return Response.ok(gpx.build()).header(HttpHeaders.CONTENT_DISPOSITION, CONTENT_DISPOSITION_VALUE_GPX).build(); + } + +} diff --git a/src/main/java/org/traccar/api/resource/ReportResource.java b/src/main/java/org/traccar/api/resource/ReportResource.java new file mode 100644 index 000000000..d371cf987 --- /dev/null +++ b/src/main/java/org/traccar/api/resource/ReportResource.java @@ -0,0 +1,210 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; + +import javax.activation.DataHandler; +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; +import javax.mail.util.ByteArrayDataSource; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.api.BaseResource; +import org.traccar.helper.DateUtil; +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.reports.Events; +import org.traccar.reports.Summary; +import org.traccar.reports.Trips; +import org.traccar.reports.model.StopReport; +import org.traccar.reports.model.SummaryReport; +import org.traccar.reports.model.TripReport; +import org.traccar.reports.Route; +import org.traccar.reports.Stops; + +@Path("reports") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ReportResource extends BaseResource { + + private static final Logger LOGGER = LoggerFactory.getLogger(ReportResource.class); + + private static final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + private static final String CONTENT_DISPOSITION_VALUE_XLSX = "attachment; filename=report.xlsx"; + + private interface ReportExecutor { + void execute(ByteArrayOutputStream stream) throws SQLException, IOException; + } + + private Response executeReport( + long userId, boolean mail, ReportExecutor executor) throws SQLException, IOException { + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + if (mail) { + new Thread(() -> { + try { + executor.execute(stream); + + MimeBodyPart attachment = new MimeBodyPart(); + + attachment.setFileName("report.xlsx"); + attachment.setDataHandler(new DataHandler(new ByteArrayDataSource( + stream.toByteArray(), "application/octet-stream"))); + + Context.getMailManager().sendMessage( + userId, "Report", "The report is in the attachment.", attachment); + } catch (SQLException | IOException | MessagingException e) { + LOGGER.warn("Report failed", e); + } + }).start(); + return Response.noContent().build(); + } else { + executor.execute(stream); + return Response.ok(stream.toByteArray()) + .header(HttpHeaders.CONTENT_DISPOSITION, CONTENT_DISPOSITION_VALUE_XLSX).build(); + } + } + + @Path("route") + @GET + public Collection<Position> getRoute( + @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { + return Route.getObjects(getUserId(), deviceIds, groupIds, + DateUtil.parseDate(from), DateUtil.parseDate(to)); + } + + @Path("route") + @GET + @Produces(XLSX) + public Response getRouteExcel( + @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("from") String from, @QueryParam("to") String to, @QueryParam("mail") boolean mail) + throws SQLException, IOException { + return executeReport(getUserId(), mail, stream -> { + Route.getExcel(stream, getUserId(), deviceIds, groupIds, + DateUtil.parseDate(from), DateUtil.parseDate(to)); + }); + } + + @Path("events") + @GET + public Collection<Event> getEvents( + @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("type") final List<String> types, + @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { + return Events.getObjects(getUserId(), deviceIds, groupIds, types, + DateUtil.parseDate(from), DateUtil.parseDate(to)); + } + + @Path("events") + @GET + @Produces(XLSX) + public Response getEventsExcel( + @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("type") final List<String> types, + @QueryParam("from") String from, @QueryParam("to") String to, @QueryParam("mail") boolean mail) + throws SQLException, IOException { + return executeReport(getUserId(), mail, stream -> { + Events.getExcel(stream, getUserId(), deviceIds, groupIds, types, + DateUtil.parseDate(from), DateUtil.parseDate(to)); + }); + } + + @Path("summary") + @GET + public Collection<SummaryReport> getSummary( + @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { + return Summary.getObjects(getUserId(), deviceIds, groupIds, + DateUtil.parseDate(from), DateUtil.parseDate(to)); + } + + @Path("summary") + @GET + @Produces(XLSX) + public Response getSummaryExcel( + @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("from") String from, @QueryParam("to") String to, @QueryParam("mail") boolean mail) + throws SQLException, IOException { + return executeReport(getUserId(), mail, stream -> { + Summary.getExcel(stream, getUserId(), deviceIds, groupIds, + DateUtil.parseDate(from), DateUtil.parseDate(to)); + }); + } + + @Path("trips") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Collection<TripReport> getTrips( + @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { + return Trips.getObjects(getUserId(), deviceIds, groupIds, + DateUtil.parseDate(from), DateUtil.parseDate(to)); + } + + @Path("trips") + @GET + @Produces(XLSX) + public Response getTripsExcel( + @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("from") String from, @QueryParam("to") String to, @QueryParam("mail") boolean mail) + throws SQLException, IOException { + return executeReport(getUserId(), mail, stream -> { + Trips.getExcel(stream, getUserId(), deviceIds, groupIds, + DateUtil.parseDate(from), DateUtil.parseDate(to)); + }); + } + + @Path("stops") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Collection<StopReport> getStops( + @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { + return Stops.getObjects(getUserId(), deviceIds, groupIds, + DateUtil.parseDate(from), DateUtil.parseDate(to)); + } + + @Path("stops") + @GET + @Produces(XLSX) + public Response getStopsExcel( + @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("from") String from, @QueryParam("to") String to, @QueryParam("mail") boolean mail) + throws SQLException, IOException { + return executeReport(getUserId(), mail, stream -> { + Stops.getExcel(stream, getUserId(), deviceIds, groupIds, + DateUtil.parseDate(from), DateUtil.parseDate(to)); + }); + } + +} diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java new file mode 100644 index 000000000..e7cad2a0c --- /dev/null +++ b/src/main/java/org/traccar/api/resource/ServerResource.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import org.traccar.Context; +import org.traccar.api.BaseResource; +import org.traccar.helper.LogAction; +import org.traccar.model.Server; + +import javax.annotation.security.PermitAll; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.sql.SQLException; + +@Path("server") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ServerResource extends BaseResource { + + @PermitAll + @GET + public Server get() throws SQLException { + return Context.getPermissionsManager().getServer(); + } + + @PUT + public Response update(Server entity) throws SQLException { + Context.getPermissionsManager().checkAdmin(getUserId()); + Context.getPermissionsManager().updateServer(entity); + LogAction.edit(getUserId(), entity); + return Response.ok(entity).build(); + } + + @Path("geocode") + @GET + public String geocode(@QueryParam("latitude") double latitude, @QueryParam("longitude") double longitude) { + if (Context.getGeocoder() != null) { + return Context.getGeocoder().getAddress(latitude, longitude, null); + } else { + throw new RuntimeException("Reverse geocoding is not enabled"); + } + } + +} diff --git a/src/main/java/org/traccar/api/resource/SessionResource.java b/src/main/java/org/traccar/api/resource/SessionResource.java new file mode 100644 index 000000000..e3c5d457f --- /dev/null +++ b/src/main/java/org/traccar/api/resource/SessionResource.java @@ -0,0 +1,122 @@ +/* + * Copyright 2015 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import org.traccar.Context; +import org.traccar.api.BaseResource; +import org.traccar.helper.DataConverter; +import org.traccar.helper.ServletHelper; +import org.traccar.helper.LogAction; +import org.traccar.model.User; + +import javax.annotation.security.PermitAll; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; + +@Path("session") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_FORM_URLENCODED) +public class SessionResource extends BaseResource { + + public static final String USER_ID_KEY = "userId"; + public static final String USER_COOKIE_KEY = "user"; + public static final String PASS_COOKIE_KEY = "password"; + + @javax.ws.rs.core.Context + private HttpServletRequest request; + + @PermitAll + @GET + public User get(@QueryParam("token") String token) throws SQLException, UnsupportedEncodingException { + Long userId = (Long) request.getSession().getAttribute(USER_ID_KEY); + if (userId == null) { + Cookie[] cookies = request.getCookies(); + String email = null, password = null; + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals(USER_COOKIE_KEY)) { + byte[] emailBytes = DataConverter.parseBase64( + URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII.name())); + email = new String(emailBytes, StandardCharsets.UTF_8); + } else if (cookie.getName().equals(PASS_COOKIE_KEY)) { + byte[] passwordBytes = DataConverter.parseBase64( + URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII.name())); + password = new String(passwordBytes, StandardCharsets.UTF_8); + } + } + } + if (email != null && password != null) { + User user = Context.getPermissionsManager().login(email, password); + if (user != null) { + userId = user.getId(); + request.getSession().setAttribute(USER_ID_KEY, userId); + } + } else if (token != null) { + User user = Context.getUsersManager().getUserByToken(token); + if (user != null) { + userId = user.getId(); + request.getSession().setAttribute(USER_ID_KEY, userId); + } + } + } + + if (userId != null) { + Context.getPermissionsManager().checkUserEnabled(userId); + return Context.getPermissionsManager().getUser(userId); + } else { + throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build()); + } + } + + @PermitAll + @POST + public User add( + @FormParam("email") String email, @FormParam("password") String password) throws SQLException { + User user = Context.getPermissionsManager().login(email, password); + if (user != null) { + request.getSession().setAttribute(USER_ID_KEY, user.getId()); + LogAction.login(user.getId()); + return user; + } else { + LogAction.failedLogin(ServletHelper.retrieveRemoteAddress(request)); + throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED).build()); + } + } + + @DELETE + public Response remove() { + LogAction.logout(getUserId()); + request.getSession().removeAttribute(USER_ID_KEY); + return Response.noContent().build(); + } + +} diff --git a/src/main/java/org/traccar/api/resource/StatisticsResource.java b/src/main/java/org/traccar/api/resource/StatisticsResource.java new file mode 100644 index 000000000..e801d4ff3 --- /dev/null +++ b/src/main/java/org/traccar/api/resource/StatisticsResource.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import org.traccar.Context; +import org.traccar.api.BaseResource; +import org.traccar.helper.DateUtil; +import org.traccar.model.Statistics; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import java.sql.SQLException; +import java.util.Collection; + +@Path("statistics") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class StatisticsResource extends BaseResource { + + @GET + public Collection<Statistics> get( + @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { + Context.getPermissionsManager().checkAdmin(getUserId()); + return Context.getDataManager().getStatistics(DateUtil.parseDate(from), DateUtil.parseDate(to)); + } + +} diff --git a/src/main/java/org/traccar/api/resource/UserResource.java b/src/main/java/org/traccar/api/resource/UserResource.java new file mode 100644 index 000000000..0b42d8d92 --- /dev/null +++ b/src/main/java/org/traccar/api/resource/UserResource.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.api.resource; + +import org.traccar.Context; +import org.traccar.api.BaseObjectResource; +import org.traccar.database.UsersManager; +import org.traccar.helper.LogAction; +import org.traccar.model.ManagedUser; +import org.traccar.model.User; + +import javax.annotation.security.PermitAll; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.sql.SQLException; +import java.util.Collection; +import java.util.Date; +import java.util.Set; + +@Path("users") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class UserResource extends BaseObjectResource<User> { + + public UserResource() { + super(User.class); + } + + @GET + public Collection<User> get(@QueryParam("userId") long userId) throws SQLException { + UsersManager usersManager = Context.getUsersManager(); + Set<Long> result = null; + if (Context.getPermissionsManager().getUserAdmin(getUserId())) { + if (userId != 0) { + result = usersManager.getUserItems(userId); + } else { + result = usersManager.getAllItems(); + } + } else if (Context.getPermissionsManager().getUserManager(getUserId())) { + result = usersManager.getManagedItems(getUserId()); + } else { + throw new SecurityException("Admin or manager access required"); + } + return usersManager.getItems(result); + } + + @Override + @PermitAll + @POST + public Response add(User entity) throws SQLException { + if (!Context.getPermissionsManager().getUserAdmin(getUserId())) { + Context.getPermissionsManager().checkUserUpdate(getUserId(), new User(), entity); + if (Context.getPermissionsManager().getUserManager(getUserId())) { + Context.getPermissionsManager().checkUserLimit(getUserId()); + } else { + Context.getPermissionsManager().checkRegistration(getUserId()); + entity.setDeviceLimit(Context.getConfig().getInteger("users.defaultDeviceLimit", -1)); + int expirationDays = Context.getConfig().getInteger("users.defaultExpirationDays"); + if (expirationDays > 0) { + entity.setExpirationTime( + new Date(System.currentTimeMillis() + (long) expirationDays * 24 * 3600 * 1000)); + } + } + } + Context.getUsersManager().addItem(entity); + LogAction.create(getUserId(), entity); + if (Context.getPermissionsManager().getUserManager(getUserId())) { + Context.getDataManager().linkObject(User.class, getUserId(), ManagedUser.class, entity.getId(), true); + LogAction.link(getUserId(), User.class, getUserId(), ManagedUser.class, entity.getId()); + } + Context.getUsersManager().refreshUserItems(); + return Response.ok(entity).build(); + } + +} diff --git a/src/main/java/org/traccar/config/Config.java b/src/main/java/org/traccar/config/Config.java new file mode 100644 index 000000000..d8f2a0e99 --- /dev/null +++ b/src/main/java/org/traccar/config/Config.java @@ -0,0 +1,166 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.config; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.InvalidPropertiesFormatException; +import java.util.Properties; + +public class Config { + + private final Properties properties = new Properties(); + + private boolean useEnvironmentVariables; + + public Config() { + } + + public Config(String file) throws IOException { + try { + Properties mainProperties = new Properties(); + try (InputStream inputStream = new FileInputStream(file)) { + mainProperties.loadFromXML(inputStream); + } + + String defaultConfigFile = mainProperties.getProperty("config.default"); + if (defaultConfigFile != null) { + try (InputStream inputStream = new FileInputStream(defaultConfigFile)) { + properties.loadFromXML(inputStream); + } + } + + properties.putAll(mainProperties); // override defaults + + useEnvironmentVariables = Boolean.parseBoolean(System.getenv("CONFIG_USE_ENVIRONMENT_VARIABLES")) + || Boolean.parseBoolean(properties.getProperty("config.useEnvironmentVariables")); + } catch (InvalidPropertiesFormatException e) { + throw new RuntimeException("Configuration file is not a valid XML document", e); + } + } + + public boolean hasKey(ConfigKey key) { + return hasKey(key.getKey()); + } + + @Deprecated + public boolean hasKey(String key) { + return useEnvironmentVariables && System.getenv().containsKey(getEnvironmentVariableName(key)) + || properties.containsKey(key); + } + + public String getString(ConfigKey key) { + return getString(key.getKey()); + } + + @Deprecated + public String getString(String key) { + if (useEnvironmentVariables) { + String value = System.getenv(getEnvironmentVariableName(key)); + if (value != null && !value.isEmpty()) { + return value; + } + } + return properties.getProperty(key); + } + + public String getString(ConfigKey key, String defaultValue) { + return getString(key.getKey(), defaultValue); + } + + @Deprecated + public String getString(String key, String defaultValue) { + return hasKey(key) ? getString(key) : defaultValue; + } + + public boolean getBoolean(ConfigKey key) { + return getBoolean(key.getKey()); + } + + @Deprecated + public boolean getBoolean(String key) { + return Boolean.parseBoolean(getString(key)); + } + + public int getInteger(ConfigKey key) { + return getInteger(key.getKey()); + } + + @Deprecated + public int getInteger(String key) { + return getInteger(key, 0); + } + + public int getInteger(ConfigKey key, int defaultValue) { + return getInteger(key.getKey(), defaultValue); + } + + @Deprecated + public int getInteger(String key, int defaultValue) { + return hasKey(key) ? Integer.parseInt(getString(key)) : defaultValue; + } + + public long getLong(ConfigKey key) { + return getLong(key.getKey()); + } + + @Deprecated + public long getLong(String key) { + return getLong(key, 0); + } + + public long getLong(ConfigKey key, long defaultValue) { + return getLong(key.getKey(), defaultValue); + } + + @Deprecated + public long getLong(String key, long defaultValue) { + return hasKey(key) ? Long.parseLong(getString(key)) : defaultValue; + } + + public double getDouble(ConfigKey key) { + return getDouble(key.getKey()); + } + + @Deprecated + public double getDouble(String key) { + return getDouble(key, 0.0); + } + + public double getDouble(ConfigKey key, double defaultValue) { + return getDouble(key.getKey(), defaultValue); + } + + @Deprecated + public double getDouble(String key, double defaultValue) { + return hasKey(key) ? Double.parseDouble(getString(key)) : defaultValue; + } + + public void setString(ConfigKey key, String value) { + setString(key.getKey(), value); + } + + @Deprecated + public void setString(String key, String value) { + properties.put(key, value); + } + + static String getEnvironmentVariableName(String key) { + return key.replaceAll("\\.", "_").replaceAll("(\\p{Lu})", "_$1").toUpperCase(); + } + +} diff --git a/src/main/java/org/traccar/config/ConfigKey.java b/src/main/java/org/traccar/config/ConfigKey.java new file mode 100644 index 000000000..2e54ad392 --- /dev/null +++ b/src/main/java/org/traccar/config/ConfigKey.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.config; + +public class ConfigKey { + + private final String key; + private final Class clazz; + + ConfigKey(String key, Class clazz) { + this.key = key; + this.clazz = clazz; + } + + String getKey() { + return key; + } + + Class getValueClass() { + return clazz; + } + +} diff --git a/src/main/java/org/traccar/config/ConfigSuffix.java b/src/main/java/org/traccar/config/ConfigSuffix.java new file mode 100644 index 000000000..149b2cd00 --- /dev/null +++ b/src/main/java/org/traccar/config/ConfigSuffix.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.config; + +public class ConfigSuffix extends ConfigKey { + + ConfigSuffix(String key, Class clazz) { + super(key, clazz); + } + + public ConfigKey withPrefix(String prefix) { + return new ConfigKey(prefix + getKey(), getValueClass()); + } + +} diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java new file mode 100644 index 000000000..2c5dcefd5 --- /dev/null +++ b/src/main/java/org/traccar/config/Keys.java @@ -0,0 +1,370 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.config; + +public final class Keys { + + /** + * Connection timeout value in seconds. Because sometimes there is no way to detect lost TCP connection old + * connections stay in open state. On most systems there is a limit on number of open connection, so this leads to + * problems with establishing new connections when number of devices is high or devices data connections are + * unstable. + */ + public static final ConfigSuffix PROTOCOL_TIMEOUT = new ConfigSuffix( + ".timeout", Integer.class); + + /** + * Server wide connection timeout value in seconds. See protocol timeout for more information. + */ + public static final ConfigKey SERVER_TIMEOUT = new ConfigKey( + "server.timeout", Integer.class); + + /** + * Address for uploading aggregated anonymous usage statistics. Uploaded information is the same you can see on the + * statistics screen in the web app. It does not include any sensitive (e.g. locations). + */ + public static final ConfigKey SERVER_STATISTICS = new ConfigKey( + "server.statistics", Boolean.class); + + /** + * Enable events subsystem. Flag to enable all events handlers. + */ + public static final ConfigKey EVENT_ENABLE = new ConfigKey( + "event.enable", Boolean.class); + + /** + * If true, the event is generated once at the beginning of overspeeding period. + */ + public static final ConfigKey EVENT_OVERSPEED_NOT_REPEAT = new ConfigKey( + "event.overspeed.notRepeat", Boolean.class); + + /** + * Minimal over speed duration to trigger the event. Value in seconds. + */ + public static final ConfigKey EVENT_OVERSPEED_MINIMAL_DURATION = new ConfigKey( + "event.overspeed.minimalDuration", Long.class); + + /** + * Relevant only for geofence speed limits. Use lowest speed limits from all geofences. + */ + public static final ConfigKey EVENT_OVERSPEED_PREFER_LOWEST = new ConfigKey( + "event.overspeed.preferLowest", Boolean.class); + + /** + * Do not generate alert event if same alert was present in last known location. + */ + public static final ConfigKey EVENT_IGNORE_DUPLICATE_ALERTS = new ConfigKey( + "event.ignoreDuplicateAlerts", Boolean.class); + + /** + * List of external handler classes to use in Netty pipeline. + */ + public static final ConfigKey EXTRA_HANDLERS = new ConfigKey( + "extra.handlers", String.class); + + /** + * Enable positions forwarding to other web server. + */ + public static final ConfigKey FORWARD_ENABLE = new ConfigKey( + "forward.enable", Boolean.class); + + /** + * URL to forward positions. Data is passed through URL parameters. For example, {uniqueId} for device identifier, + * {latitude} and {longitude} for coordinates. + */ + public static final ConfigKey FORWARD_URL = new ConfigKey( + "forward.url", String.class); + + /** + * Additional HTTP header, can be used for authorization. + */ + public static final ConfigKey FORWARD_HEADER = new ConfigKey( + "forward.header", String.class); + + /** + * Boolean value to enable forwarding in JSON format. + */ + public static final ConfigKey FORWARD_JSON = new ConfigKey( + "forward.json", Boolean.class); + + /** + * Boolean flag to enable or disable position filtering. + */ + public static final ConfigKey FILTER_ENABLE = new ConfigKey( + "filter.enable", Boolean.class); + + /** + * Filter invalid (valid field is set to false) positions. + */ + public static final ConfigKey FILTER_INVALID = new ConfigKey( + "filter.invalid", Boolean.class); + + /** + * Filter zero coordinates. Zero latitude and longitude are theoretically valid values, but it practice it usually + * indicates invalid GPS data. + */ + public static final ConfigKey FILTER_ZERO = new ConfigKey( + "filter.zero", Boolean.class); + + /** + * Filter duplicate records (duplicates are detected by time value). + */ + public static final ConfigKey FILTER_DUPLICATE = new ConfigKey( + "filter.duplicate", Boolean.class); + + /** + * Filter records with fix time in future. The values is specified in seconds. Records that have fix time more than + * specified number of seconds later than current server time would be filtered out. + */ + public static final ConfigKey FILTER_FUTURE = new ConfigKey( + "filter.future", Long.class); + + /** + * Filter positions with accuracy less than specified value in meters. + */ + public static final ConfigKey FILTER_ACCURACY = new ConfigKey( + "filter.accuracy", Integer.class); + + /** + * Filter cell and wifi locations that are coming from geolocation provider. + */ + public static final ConfigKey FILTER_APPROXIMATE = new ConfigKey( + "filter.approximate", Boolean.class); + + /** + * Filter positions with exactly zero speed values. + */ + public static final ConfigKey FILTER_STATIC = new ConfigKey( + "filter.static", Boolean.class); + + /** + * Filter records by distance. The values is specified in meters. If the new position is less far than this value + * from the last one it gets filtered out. + */ + public static final ConfigKey FILTER_DISTANCE = new ConfigKey( + "filter.distance", Integer.class); + + /** + * Filter records by Maximum Speed value in knots. Can be used to filter jumps to far locations even if they're + * marked as valid. Shouldn't be too low. Start testing with values at about 25000. + */ + public static final ConfigKey FILTER_MAX_SPEED = new ConfigKey( + "filter.maxSpeed", Integer.class); + + /** + * Filter position if time from previous position is less than specified value in seconds. + */ + public static final ConfigKey FILTER_MIN_PERIOD = new ConfigKey( + "filter.minPeriod", Integer.class); + + /** + * Time limit for the filtering in seconds. If the time difference between last position and a new one is more than + * this limit, the new position will not be filtered out. + */ + public static final ConfigKey FILTER_SKIP_LIMIT = new ConfigKey( + "filter.skipLimit", Long.class); + + /** + * Enable attributes skipping. Attribute skipping can be enabled in the config or device attributes. + */ + public static final ConfigKey FILTER_SKIP_ATTRIBUTES_ENABLE = new ConfigKey( + "filter.skipAttributes.enable", Boolean.class); + + /** + * Override device time. Possible values are 'deviceTime' and 'serverTime' + */ + public static final ConfigKey TIME_OVERRIDE = new ConfigKey( + "time.override", String.class); + + /** + * List of protocols for overriding time. If not specified override is applied globally. List consist of protocol + * names that can be separated by comma or single space character. + */ + public static final ConfigKey TIME_PROTOCOLS = new ConfigKey( + "time.protocols", String.class); + + /** + * Replaces coordinates with last known if change is less than a 'coordinates.minError' meters + * or more than a 'coordinates.maxError' meters. Helps to avoid coordinates jumps during parking period + * or jumps to zero coordinates. + */ + public static final ConfigKey COORDINATES_FILTER = new ConfigKey( + "coordinates.filter", Boolean.class); + + /** + * Distance in meters. Distances below this value gets handled like explained in 'coordinates.filter'. + */ + public static final ConfigKey COORDINATES_MIN_ERROR = new ConfigKey( + "coordinates.minError", Integer.class); + + /** + * Distance in meters. Distances above this value gets handled like explained in 'coordinates.filter', but only if + * Position is also marked as 'invalid'. + */ + public static final ConfigKey COORDINATES_MAX_ERROR = new ConfigKey( + "coordinates.maxError", Integer.class); + + /** + * Enable to save device IP addresses information. Disabled by default. + */ + public static final ConfigKey PROCESSING_REMOTE_ADDRESS_ENABLE = new ConfigKey( + "processing.remoteAddress.enable", Boolean.class); + + /** + * Enable engine hours calculation on the server side. It uses ignition value to determine engine state. + */ + public static final ConfigKey PROCESSING_ENGINE_HOURS_ENABLE = new ConfigKey( + "processing.engineHours.enable", Boolean.class); + + /** + * Enable copying of missing attributes from last position to the current one. Might be useful if device doesn't + * send some values in every message. + */ + public static final ConfigKey PROCESSING_COPY_ATTRIBUTES_ENABLE = new ConfigKey( + "processing.copyAttributes.enable", Boolean.class); + + /** + * Enable computed attributes processing. + */ + public static final ConfigKey PROCESSING_COMPUTED_ATTRIBUTES_ENABLE = new ConfigKey( + "processing.computedAttributes.enable", Boolean.class); + + /** + * Enable computed attributes processing. + */ + public static final ConfigKey PROCESSING_COMPUTED_ATTRIBUTES_DEVICE_ATTRIBUTES = new ConfigKey( + "processing.computedAttributes.deviceAttributes", Boolean.class); + + /** + * Boolean flag to enable or disable reverse geocoder. + */ + public static final ConfigKey GEOCODER_ENABLE = new ConfigKey( + "geocoder.enable", Boolean.class); + + /** + * Reverse geocoder type. Check reverse geocoding documentation for more info. By default (if the value is not + * specified) server uses Google API. + */ + public static final ConfigKey GEOCODER_TYPE = new ConfigKey( + "geocoder.type", String.class); + + /** + * Geocoder server URL. Applicable only to Nominatim and Gisgraphy providers. + */ + public static final ConfigKey GEOCODER_URL = new ConfigKey( + "geocoder.url", String.class); + + /** + * App id for use with Here provider. + */ + public static final ConfigKey GEOCODER_ID = new ConfigKey( + "geocoder.id", String.class); + + /** + * Provider API key. Most providers require API keys. + */ + public static final ConfigKey GEOCODER_KEY = new ConfigKey( + "geocoder.key", String.class); + + /** + * Language parameter for providers that support localization (e.g. Google and Nominatim). + */ + public static final ConfigKey GEOCODER_LANGUAGE = new ConfigKey( + "geocoder.language", String.class); + + /** + * Address format string. Default value is %h %r, %t, %s, %c. See AddressFormat for more info. + */ + public static final ConfigKey GEOCODER_FORMAT = new ConfigKey( + "geocoder.format", String.class); + + /** + * Cache size for geocoding results. + */ + public static final ConfigKey GEOCODER_CACHE_SIZE = new ConfigKey( + "geocoder.cacheSize", Integer.class); + + /** + * Disable automatic reverse geocoding requests for all positions. + */ + public static final ConfigKey GEOCODER_IGNORE_POSITIONS = new ConfigKey( + "geocoder.ignorePositions", Boolean.class); + + /** + * Boolean flag to apply reverse geocoding to invalid positions. + */ + public static final ConfigKey GEOCODER_PROCESS_INVALID_POSITIONS = new ConfigKey( + "geocoder.processInvalidPositions", Boolean.class); + + /** + * Optional parameter to specify minimum distance for new reverse geocoding request. If distance is less than + * specified value (in meters), then Traccar will reuse last known address. + */ + public static final ConfigKey GEOCODER_REUSE_DISTANCE = new ConfigKey( + "geocoder.reuseDistance", Integer.class); + + /** + * Boolean flag to enable LBS location resolution. Some devices send cell towers information and WiFi point when GPS + * location is not available. Traccar can determine coordinates based on that information using third party + * services. Default value is false. + */ + public static final ConfigKey GEOLOCATION_ENABLE = new ConfigKey( + "geolocation.enable", Boolean.class); + + /** + * Provider to use for LBS location. Available options: google, mozilla and opencellid. By default opencellid is + * used. You have to supply a key that you get from corresponding provider. For more information see LBS geolocation + * documentation. + */ + public static final ConfigKey GEOLOCATION_TYPE = new ConfigKey( + "geolocation.type", String.class); + + /** + * Geolocation provider API URL address. Not required for most providers. + */ + public static final ConfigKey GEOLOCATION_URL = new ConfigKey( + "geolocation.url", String.class); + + /** + * Provider API key. OpenCellID service requires API key. + */ + public static final ConfigKey GEOLOCATION_KEY = new ConfigKey( + "geolocation.key", String.class); + + /** + * Boolean flag to apply geolocation to invalid positions. + */ + public static final ConfigKey GEOLOCATION_PROCESS_INVALID_POSITIONS = new ConfigKey( + "geolocation.processInvalidPositions", Boolean.class); + + /** + * Override latitude sign / hemisphere. Useful in cases where value is incorrect because of device bug. Value can be + * N for North or S for South. + */ + public static final ConfigKey LOCATION_LATITUDE_HEMISPHERE = new ConfigKey( + "location.latitudeHemisphere", Boolean.class); + + /** + * Override longitude sign / hemisphere. Useful in cases where value is incorrect because of device bug. Value can + * be E for East or W for West. + */ + public static final ConfigKey LOCATION_LONGITUDE_HEMISPHERE = new ConfigKey( + "location.longitudeHemisphere", Boolean.class); + + private Keys() { + } + +} diff --git a/src/main/java/org/traccar/database/ActiveDevice.java b/src/main/java/org/traccar/database/ActiveDevice.java new file mode 100644 index 000000000..207fc454b --- /dev/null +++ b/src/main/java/org/traccar/database/ActiveDevice.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import io.netty.channel.Channel; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.model.Command; + +import java.net.SocketAddress; + +public class ActiveDevice { + + private final long deviceId; + private final Protocol protocol; + private final Channel channel; + private final SocketAddress remoteAddress; + + public ActiveDevice(long deviceId, Protocol protocol, Channel channel, SocketAddress remoteAddress) { + this.deviceId = deviceId; + this.protocol = protocol; + this.channel = channel; + this.remoteAddress = remoteAddress; + } + + public Channel getChannel() { + return channel; + } + + public long getDeviceId() { + return deviceId; + } + + public void sendCommand(Command command) { + protocol.sendDataCommand(this, command); + } + + public void write(Object message) { + channel.writeAndFlush(new NetworkMessage(message, remoteAddress)); + } + +} diff --git a/src/main/java/org/traccar/database/AttributesManager.java b/src/main/java/org/traccar/database/AttributesManager.java new file mode 100644 index 000000000..28816645a --- /dev/null +++ b/src/main/java/org/traccar/database/AttributesManager.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import org.traccar.model.Attribute; + +public class AttributesManager extends ExtendedObjectManager<Attribute> { + + public AttributesManager(DataManager dataManager) { + super(dataManager, Attribute.class); + } + + @Override + public void updateCachedItem(Attribute attribute) { + Attribute cachedAttribute = getById(attribute.getId()); + cachedAttribute.setDescription(attribute.getDescription()); + cachedAttribute.setAttribute(attribute.getAttribute()); + cachedAttribute.setExpression(attribute.getExpression()); + cachedAttribute.setType(attribute.getType()); + } + +} diff --git a/src/main/java/org/traccar/database/BaseObjectManager.java b/src/main/java/org/traccar/database/BaseObjectManager.java new file mode 100644 index 000000000..8bf9ef860 --- /dev/null +++ b/src/main/java/org/traccar/database/BaseObjectManager.java @@ -0,0 +1,127 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.model.BaseModel; + +public class BaseObjectManager<T extends BaseModel> { + + private static final Logger LOGGER = LoggerFactory.getLogger(BaseObjectManager.class); + + private final DataManager dataManager; + + private Map<Long, T> items; + private Class<T> baseClass; + + protected BaseObjectManager(DataManager dataManager, Class<T> baseClass) { + this.dataManager = dataManager; + this.baseClass = baseClass; + refreshItems(); + } + + protected final DataManager getDataManager() { + return dataManager; + } + + protected final Class<T> getBaseClass() { + return baseClass; + } + + public T getById(long itemId) { + return items.get(itemId); + } + + public void refreshItems() { + if (dataManager != null) { + try { + Collection<T> databaseItems = dataManager.getObjects(baseClass); + if (items == null) { + items = new ConcurrentHashMap<>(databaseItems.size()); + } + Set<Long> databaseItemIds = new HashSet<>(); + for (T item : databaseItems) { + databaseItemIds.add(item.getId()); + if (items.containsKey(item.getId())) { + updateCachedItem(item); + } else { + addNewItem(item); + } + } + for (Long cachedItemId : items.keySet()) { + if (!databaseItemIds.contains(cachedItemId)) { + removeCachedItem(cachedItemId); + } + } + } catch (SQLException error) { + LOGGER.warn("Error refreshing items", error); + } + } + } + + protected void addNewItem(T item) { + items.put(item.getId(), item); + } + + public void addItem(T item) throws SQLException { + dataManager.addObject(item); + addNewItem(item); + } + + protected void updateCachedItem(T item) { + items.put(item.getId(), item); + } + + public void updateItem(T item) throws SQLException { + dataManager.updateObject(item); + updateCachedItem(item); + } + + protected void removeCachedItem(long itemId) { + items.remove(itemId); + } + + public void removeItem(long itemId) throws SQLException { + BaseModel item = getById(itemId); + if (item != null) { + dataManager.removeObject(baseClass, itemId); + removeCachedItem(itemId); + } + } + + public final Collection<T> getItems(Set<Long> itemIds) { + Collection<T> result = new LinkedList<>(); + for (long itemId : itemIds) { + result.add(getById(itemId)); + } + return result; + } + + public Set<Long> getAllItems() { + return items.keySet(); + } + +} diff --git a/src/main/java/org/traccar/database/CalendarManager.java b/src/main/java/org/traccar/database/CalendarManager.java new file mode 100644 index 000000000..44ced1082 --- /dev/null +++ b/src/main/java/org/traccar/database/CalendarManager.java @@ -0,0 +1,27 @@ +/* + * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import org.traccar.model.Calendar; + +public class CalendarManager extends SimpleObjectManager<Calendar> { + + public CalendarManager(DataManager dataManager) { + super(dataManager, Calendar.class); + } + +} diff --git a/src/main/java/org/traccar/database/CommandsManager.java b/src/main/java/org/traccar/database/CommandsManager.java new file mode 100644 index 000000000..dc9512d9e --- /dev/null +++ b/src/main/java/org/traccar/database/CommandsManager.java @@ -0,0 +1,162 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BaseProtocol; +import org.traccar.Context; +import org.traccar.model.Command; +import org.traccar.model.Typed; +import org.traccar.model.Position; + +public class CommandsManager extends ExtendedObjectManager<Command> { + + private static final Logger LOGGER = LoggerFactory.getLogger(CommandsManager.class); + + private final Map<Long, Queue<Command>> deviceQueues = new ConcurrentHashMap<>(); + + private boolean queueing; + + public CommandsManager(DataManager dataManager, boolean queueing) { + super(dataManager, Command.class); + this.queueing = queueing; + } + + public boolean checkDeviceCommand(long deviceId, long commandId) { + return !getAllDeviceItems(deviceId).contains(commandId); + } + + public boolean sendCommand(Command command) throws Exception { + long deviceId = command.getDeviceId(); + if (command.getId() != 0) { + command = getById(command.getId()).clone(); + command.setDeviceId(deviceId); + } + if (command.getTextChannel()) { + Position lastPosition = Context.getIdentityManager().getLastPosition(deviceId); + String phone = Context.getIdentityManager().getById(deviceId).getPhone(); + if (lastPosition != null) { + BaseProtocol protocol = Context.getServerManager().getProtocol(lastPosition.getProtocol()); + protocol.sendTextCommand(phone, command); + } else if (command.getType().equals(Command.TYPE_CUSTOM)) { + if (Context.getSmsManager() != null) { + Context.getSmsManager().sendMessageSync(phone, command.getString(Command.KEY_DATA), true); + } else { + throw new RuntimeException("SMS is not enabled"); + } + } else { + throw new RuntimeException("Command " + command.getType() + " is not supported"); + } + } else { + ActiveDevice activeDevice = Context.getConnectionManager().getActiveDevice(deviceId); + if (activeDevice != null) { + activeDevice.sendCommand(command); + } else if (!queueing) { + throw new RuntimeException("Device is not online"); + } else { + getDeviceQueue(deviceId).add(command); + return false; + } + } + return true; + } + + public Collection<Long> getSupportedCommands(long deviceId) { + List<Long> result = new ArrayList<>(); + Position lastPosition = Context.getIdentityManager().getLastPosition(deviceId); + for (long commandId : getAllDeviceItems(deviceId)) { + Command command = getById(commandId); + if (lastPosition != null) { + BaseProtocol protocol = Context.getServerManager().getProtocol(lastPosition.getProtocol()); + if (command.getTextChannel() && protocol.getSupportedTextCommands().contains(command.getType()) + || !command.getTextChannel() + && protocol.getSupportedDataCommands().contains(command.getType())) { + result.add(commandId); + } + } else if (command.getType().equals(Command.TYPE_CUSTOM)) { + result.add(commandId); + } + } + return result; + } + + public Collection<Typed> getCommandTypes(long deviceId, boolean textChannel) { + Position lastPosition = Context.getIdentityManager().getLastPosition(deviceId); + if (lastPosition != null) { + return getCommandTypes(lastPosition.getProtocol(), textChannel); + } else { + return Collections.singletonList(new Typed(Command.TYPE_CUSTOM)); + } + } + + public Collection<Typed> getCommandTypes(String protocolName, boolean textChannel) { + List<Typed> result = new ArrayList<>(); + BaseProtocol protocol = Context.getServerManager().getProtocol(protocolName); + Collection<String> commands; + commands = textChannel ? protocol.getSupportedTextCommands() : protocol.getSupportedDataCommands(); + for (String commandKey : commands) { + result.add(new Typed(commandKey)); + } + return result; + } + + public Collection<Typed> getAllCommandTypes() { + List<Typed> result = new ArrayList<>(); + Field[] fields = Command.class.getDeclaredFields(); + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers()) && field.getName().startsWith("TYPE_")) { + try { + result.add(new Typed(field.get(null).toString())); + } catch (IllegalArgumentException | IllegalAccessException error) { + LOGGER.warn("Get command types error", error); + } + } + } + return result; + } + + private Queue<Command> getDeviceQueue(long deviceId) { + if (!deviceQueues.containsKey(deviceId)) { + deviceQueues.put(deviceId, new ConcurrentLinkedQueue<Command>()); + } + return deviceQueues.get(deviceId); + } + + public void sendQueuedCommands(ActiveDevice activeDevice) { + Queue<Command> deviceQueue = deviceQueues.get(activeDevice.getDeviceId()); + if (deviceQueue != null) { + Command command = deviceQueue.poll(); + while (command != null) { + activeDevice.sendCommand(command); + command = deviceQueue.poll(); + } + } + } + +} diff --git a/src/main/java/org/traccar/database/ConnectionManager.java b/src/main/java/org/traccar/database/ConnectionManager.java new file mode 100644 index 000000000..dd0071143 --- /dev/null +++ b/src/main/java/org/traccar/database/ConnectionManager.java @@ -0,0 +1,218 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import io.netty.channel.Channel; +import io.netty.util.Timeout; +import io.netty.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.GlobalTimer; +import org.traccar.Main; +import org.traccar.Protocol; +import org.traccar.handler.events.MotionEventHandler; +import org.traccar.handler.events.OverspeedEventHandler; +import org.traccar.model.Device; +import org.traccar.model.DeviceState; +import org.traccar.model.Event; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.sql.SQLException; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +public class ConnectionManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class); + + private static final long DEFAULT_TIMEOUT = 600; + + private final long deviceTimeout; + private final boolean enableStatusEvents; + private final boolean updateDeviceState; + + private final Map<Long, ActiveDevice> activeDevices = new ConcurrentHashMap<>(); + private final Map<Long, Set<UpdateListener>> listeners = new ConcurrentHashMap<>(); + private final Map<Long, Timeout> timeouts = new ConcurrentHashMap<>(); + + public ConnectionManager() { + deviceTimeout = Context.getConfig().getLong("status.timeout", DEFAULT_TIMEOUT) * 1000; + enableStatusEvents = Context.getConfig().getBoolean("event.enable"); + updateDeviceState = Context.getConfig().getBoolean("status.updateDeviceState"); + } + + public void addActiveDevice(long deviceId, Protocol protocol, Channel channel, SocketAddress remoteAddress) { + activeDevices.put(deviceId, new ActiveDevice(deviceId, protocol, channel, remoteAddress)); + } + + public void removeActiveDevice(Channel channel) { + for (ActiveDevice activeDevice : activeDevices.values()) { + if (activeDevice.getChannel() == channel) { + updateDevice(activeDevice.getDeviceId(), Device.STATUS_OFFLINE, null); + activeDevices.remove(activeDevice.getDeviceId()); + break; + } + } + } + + public ActiveDevice getActiveDevice(long deviceId) { + return activeDevices.get(deviceId); + } + + public void updateDevice(final long deviceId, String status, Date time) { + Device device = Context.getIdentityManager().getById(deviceId); + if (device == null) { + return; + } + + String oldStatus = device.getStatus(); + device.setStatus(status); + + if (enableStatusEvents && !status.equals(oldStatus)) { + String eventType; + Map<Event, Position> events = new HashMap<>(); + switch (status) { + case Device.STATUS_ONLINE: + eventType = Event.TYPE_DEVICE_ONLINE; + break; + case Device.STATUS_UNKNOWN: + eventType = Event.TYPE_DEVICE_UNKNOWN; + if (updateDeviceState) { + events.putAll(updateDeviceState(deviceId)); + } + break; + default: + eventType = Event.TYPE_DEVICE_OFFLINE; + if (updateDeviceState) { + events.putAll(updateDeviceState(deviceId)); + } + break; + } + events.put(new Event(eventType, deviceId), null); + Context.getNotificationManager().updateEvents(events); + } + + Timeout timeout = timeouts.remove(deviceId); + if (timeout != null) { + timeout.cancel(); + } + + if (time != null) { + device.setLastUpdate(time); + } + + if (status.equals(Device.STATUS_ONLINE)) { + timeouts.put(deviceId, GlobalTimer.getTimer().newTimeout(new TimerTask() { + @Override + public void run(Timeout timeout) { + if (!timeout.isCancelled()) { + updateDevice(deviceId, Device.STATUS_UNKNOWN, null); + } + } + }, deviceTimeout, TimeUnit.MILLISECONDS)); + } + + try { + Context.getDeviceManager().updateDeviceStatus(device); + } catch (SQLException error) { + LOGGER.warn("Update device status error", error); + } + + updateDevice(device); + + if (status.equals(Device.STATUS_ONLINE) && !oldStatus.equals(Device.STATUS_ONLINE)) { + Context.getCommandsManager().sendQueuedCommands(getActiveDevice(deviceId)); + } + } + + public Map<Event, Position> updateDeviceState(long deviceId) { + DeviceState deviceState = Context.getDeviceManager().getDeviceState(deviceId); + Map<Event, Position> result = new HashMap<>(); + + Map<Event, Position> event = Main.getInjector() + .getInstance(MotionEventHandler.class).updateMotionState(deviceState); + if (event != null) { + result.putAll(event); + } + + event = Main.getInjector().getInstance(OverspeedEventHandler.class) + .updateOverspeedState(deviceState, Context.getDeviceManager(). + lookupAttributeDouble(deviceId, OverspeedEventHandler.ATTRIBUTE_SPEED_LIMIT, 0, true, false)); + if (event != null) { + result.putAll(event); + } + + return result; + } + + public synchronized void updateDevice(Device device) { + for (long userId : Context.getPermissionsManager().getDeviceUsers(device.getId())) { + if (listeners.containsKey(userId)) { + for (UpdateListener listener : listeners.get(userId)) { + listener.onUpdateDevice(device); + } + } + } + } + + public synchronized void updatePosition(Position position) { + long deviceId = position.getDeviceId(); + + for (long userId : Context.getPermissionsManager().getDeviceUsers(deviceId)) { + if (listeners.containsKey(userId)) { + for (UpdateListener listener : listeners.get(userId)) { + listener.onUpdatePosition(position); + } + } + } + } + + public synchronized void updateEvent(long userId, Event event) { + if (listeners.containsKey(userId)) { + for (UpdateListener listener : listeners.get(userId)) { + listener.onUpdateEvent(event); + } + } + } + + public interface UpdateListener { + void onUpdateDevice(Device device); + void onUpdatePosition(Position position); + void onUpdateEvent(Event event); + } + + public synchronized void addListener(long userId, UpdateListener listener) { + if (!listeners.containsKey(userId)) { + listeners.put(userId, new HashSet<UpdateListener>()); + } + listeners.get(userId).add(listener); + } + + public synchronized void removeListener(long userId, UpdateListener listener) { + if (!listeners.containsKey(userId)) { + listeners.put(userId, new HashSet<UpdateListener>()); + } + listeners.get(userId).remove(listener); + } + +} diff --git a/src/main/java/org/traccar/database/DataManager.java b/src/main/java/org/traccar/database/DataManager.java new file mode 100644 index 000000000..8e9071736 --- /dev/null +++ b/src/main/java/org/traccar/database/DataManager.java @@ -0,0 +1,478 @@ +/* + * Copyright 2012 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.beans.Introspector; +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import javax.naming.InitialContext; +import javax.sql.DataSource; + +import liquibase.Contexts; +import liquibase.Liquibase; +import liquibase.database.Database; +import liquibase.database.DatabaseFactory; +import liquibase.exception.LiquibaseException; +import liquibase.resource.FileSystemResourceAccessor; +import liquibase.resource.ResourceAccessor; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.Context; +import org.traccar.helper.DateUtil; +import org.traccar.model.Attribute; +import org.traccar.model.Device; +import org.traccar.model.Driver; +import org.traccar.model.Event; +import org.traccar.model.Geofence; +import org.traccar.model.Group; +import org.traccar.model.Maintenance; +import org.traccar.model.ManagedUser; +import org.traccar.model.Notification; +import org.traccar.model.Permission; +import org.traccar.model.BaseModel; +import org.traccar.model.Calendar; +import org.traccar.model.Command; +import org.traccar.model.Position; +import org.traccar.model.Server; +import org.traccar.model.Statistics; +import org.traccar.model.User; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +public class DataManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(DataManager.class); + + public static final String ACTION_SELECT_ALL = "selectAll"; + public static final String ACTION_SELECT = "select"; + public static final String ACTION_INSERT = "insert"; + public static final String ACTION_UPDATE = "update"; + public static final String ACTION_DELETE = "delete"; + + private final Config config; + + private DataSource dataSource; + + private boolean generateQueries; + + private boolean forceLdap; + + public DataManager(Config config) throws Exception { + this.config = config; + + forceLdap = config.getBoolean("ldap.force"); + + initDatabase(); + initDatabaseSchema(); + } + + private void initDatabase() throws Exception { + + String jndiName = config.getString("database.jndi"); + + if (jndiName != null) { + + dataSource = (DataSource) new InitialContext().lookup(jndiName); + + } else { + + String driverFile = config.getString("database.driverFile"); + if (driverFile != null) { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + try { + Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class); + method.setAccessible(true); + method.invoke(classLoader, new File(driverFile).toURI().toURL()); + } catch (NoSuchMethodException e) { + Method method = classLoader.getClass() + .getDeclaredMethod("appendToClassPathForInstrumentation", String.class); + method.setAccessible(true); + method.invoke(classLoader, driverFile); + } + } + + String driver = config.getString("database.driver"); + if (driver != null) { + Class.forName(driver); + } + + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setDriverClassName(config.getString("database.driver")); + hikariConfig.setJdbcUrl(config.getString("database.url")); + hikariConfig.setUsername(config.getString("database.user")); + hikariConfig.setPassword(config.getString("database.password")); + hikariConfig.setConnectionInitSql(config.getString("database.checkConnection", "SELECT 1")); + hikariConfig.setIdleTimeout(600000); + + int maxPoolSize = config.getInteger("database.maxPoolSize"); + + if (maxPoolSize != 0) { + hikariConfig.setMaximumPoolSize(maxPoolSize); + } + + generateQueries = config.getBoolean("database.generateQueries"); + + dataSource = new HikariDataSource(hikariConfig); + + } + } + + public static String constructObjectQuery(String action, Class<?> clazz, boolean extended) { + switch (action) { + case ACTION_INSERT: + case ACTION_UPDATE: + StringBuilder result = new StringBuilder(); + StringBuilder fields = new StringBuilder(); + StringBuilder values = new StringBuilder(); + + Set<Method> methods = new HashSet<>(Arrays.asList(clazz.getMethods())); + methods.removeAll(Arrays.asList(Object.class.getMethods())); + methods.removeAll(Arrays.asList(BaseModel.class.getMethods())); + for (Method method : methods) { + boolean skip; + if (extended) { + skip = !method.isAnnotationPresent(QueryExtended.class); + } else { + skip = method.isAnnotationPresent(QueryIgnore.class) + || method.isAnnotationPresent(QueryExtended.class) && !action.equals(ACTION_INSERT); + } + if (!skip && method.getName().startsWith("get") && method.getParameterTypes().length == 0) { + String name = Introspector.decapitalize(method.getName().substring(3)); + if (action.equals(ACTION_INSERT)) { + fields.append(name).append(", "); + values.append(":").append(name).append(", "); + } else { + fields.append(name).append(" = :").append(name).append(", "); + } + } + } + fields.setLength(fields.length() - 2); + if (action.equals(ACTION_INSERT)) { + values.setLength(values.length() - 2); + result.append("INSERT INTO ").append(getObjectsTableName(clazz)).append(" ("); + result.append(fields).append(") "); + result.append("VALUES (").append(values).append(")"); + } else { + result.append("UPDATE ").append(getObjectsTableName(clazz)).append(" SET "); + result.append(fields); + result.append(" WHERE id = :id"); + } + return result.toString(); + case ACTION_SELECT_ALL: + return "SELECT * FROM " + getObjectsTableName(clazz); + case ACTION_SELECT: + return "SELECT * FROM " + getObjectsTableName(clazz) + " WHERE id = :id"; + case ACTION_DELETE: + return "DELETE FROM " + getObjectsTableName(clazz) + " WHERE id = :id"; + default: + throw new IllegalArgumentException("Unknown action"); + } + } + + public static String constructPermissionQuery(String action, Class<?> owner, Class<?> property) { + switch (action) { + case ACTION_SELECT_ALL: + return "SELECT " + makeNameId(owner) + ", " + makeNameId(property) + " FROM " + + getPermissionsTableName(owner, property); + case ACTION_INSERT: + return "INSERT INTO " + getPermissionsTableName(owner, property) + + " (" + makeNameId(owner) + ", " + makeNameId(property) + ") VALUES (:" + + makeNameId(owner) + ", :" + makeNameId(property) + ")"; + case ACTION_DELETE: + return "DELETE FROM " + getPermissionsTableName(owner, property) + + " WHERE " + makeNameId(owner) + " = :" + makeNameId(owner) + + " AND " + makeNameId(property) + " = :" + makeNameId(property); + default: + throw new IllegalArgumentException("Unknown action"); + } + } + + private String getQuery(String key) { + String query = config.getString(key); + if (query == null) { + LOGGER.info("Query not provided: " + key); + } + return query; + } + + public String getQuery(String action, Class<?> clazz) { + return getQuery(action, clazz, false); + } + + public String getQuery(String action, Class<?> clazz, boolean extended) { + String queryName; + if (action.equals(ACTION_SELECT_ALL)) { + queryName = "database.select" + clazz.getSimpleName() + "s"; + } else { + queryName = "database." + action.toLowerCase() + clazz.getSimpleName(); + if (extended) { + queryName += "Extended"; + } + } + String query = config.getString(queryName); + if (query == null) { + if (generateQueries) { + query = constructObjectQuery(action, clazz, extended); + config.setString(queryName, query); + } else { + LOGGER.info("Query not provided: " + queryName); + } + } + + return query; + } + + public String getQuery(String action, Class<?> owner, Class<?> property) { + String queryName; + switch (action) { + case ACTION_SELECT_ALL: + queryName = "database.select" + owner.getSimpleName() + property.getSimpleName() + "s"; + break; + case ACTION_INSERT: + queryName = "database.link" + owner.getSimpleName() + property.getSimpleName(); + break; + default: + queryName = "database.unlink" + owner.getSimpleName() + property.getSimpleName(); + break; + } + String query = config.getString(queryName); + if (query == null) { + if (generateQueries) { + query = constructPermissionQuery(action, owner, + property.equals(User.class) ? ManagedUser.class : property); + config.setString(queryName, query); + } else { + LOGGER.info("Query not provided: " + queryName); + } + } + + return query; + } + + private static String getPermissionsTableName(Class<?> owner, Class<?> property) { + String propertyName = property.getSimpleName(); + if (propertyName.equals("ManagedUser")) { + propertyName = "User"; + } + return "tc_" + Introspector.decapitalize(owner.getSimpleName()) + + "_" + Introspector.decapitalize(propertyName); + } + + private static String getObjectsTableName(Class<?> clazz) { + String result = "tc_" + Introspector.decapitalize(clazz.getSimpleName()); + // Add "s" ending if object name is not plural already + if (!result.endsWith("s")) { + result += "s"; + } + return result; + } + + private void initDatabaseSchema() throws SQLException, LiquibaseException { + + if (config.hasKey("database.changelog")) { + + ResourceAccessor resourceAccessor = new FileSystemResourceAccessor(); + + Database database = DatabaseFactory.getInstance().openDatabase( + config.getString("database.url"), + config.getString("database.user"), + config.getString("database.password"), + config.getString("database.driver"), + null, null, null, resourceAccessor); + + Liquibase liquibase = new Liquibase( + config.getString("database.changelog"), resourceAccessor, database); + + liquibase.clearCheckSums(); + + liquibase.update(new Contexts()); + } + } + + public User login(String email, String password) throws SQLException { + User user = QueryBuilder.create(dataSource, getQuery("database.loginUser")) + .setString("email", email.trim()) + .executeQuerySingle(User.class); + LdapProvider ldapProvider = Context.getLdapProvider(); + if (user != null) { + if (ldapProvider != null && user.getLogin() != null && ldapProvider.login(user.getLogin(), password) + || !forceLdap && user.isPasswordValid(password)) { + return user; + } + } else { + if (ldapProvider != null && ldapProvider.login(email, password)) { + user = ldapProvider.getUser(email); + Context.getUsersManager().addItem(user); + return user; + } + } + return null; + } + + public void updateDeviceStatus(Device device) throws SQLException { + QueryBuilder.create(dataSource, getQuery(ACTION_UPDATE, Device.class, true)) + .setObject(device) + .executeUpdate(); + } + + public Collection<Position> getPositions(long deviceId, Date from, Date to) throws SQLException { + return QueryBuilder.create(dataSource, getQuery("database.selectPositions")) + .setLong("deviceId", deviceId) + .setDate("from", from) + .setDate("to", to) + .executeQuery(Position.class); + } + + public void updateLatestPosition(Position position) throws SQLException { + QueryBuilder.create(dataSource, getQuery("database.updateLatestPosition")) + .setDate("now", new Date()) + .setObject(position) + .executeUpdate(); + } + + public Collection<Position> getLatestPositions() throws SQLException { + return QueryBuilder.create(dataSource, getQuery("database.selectLatestPositions")) + .executeQuery(Position.class); + } + + public void clearHistory() throws SQLException { + long historyDays = config.getInteger("database.historyDays"); + if (historyDays != 0) { + Date timeLimit = new Date(System.currentTimeMillis() - historyDays * 24 * 3600 * 1000); + LOGGER.info("Clearing history earlier than " + DateUtil.formatDate(timeLimit, false)); + QueryBuilder.create(dataSource, getQuery("database.deletePositions")) + .setDate("serverTime", timeLimit) + .executeUpdate(); + QueryBuilder.create(dataSource, getQuery("database.deleteEvents")) + .setDate("serverTime", timeLimit) + .executeUpdate(); + } + } + + public Server getServer() throws SQLException { + return QueryBuilder.create(dataSource, getQuery(ACTION_SELECT_ALL, Server.class)) + .executeQuerySingle(Server.class); + } + + public Collection<Event> getEvents(long deviceId, Date from, Date to) throws SQLException { + return QueryBuilder.create(dataSource, getQuery("database.selectEvents")) + .setLong("deviceId", deviceId) + .setDate("from", from) + .setDate("to", to) + .executeQuery(Event.class); + } + + public Collection<Statistics> getStatistics(Date from, Date to) throws SQLException { + return QueryBuilder.create(dataSource, getQuery("database.selectStatistics")) + .setDate("from", from) + .setDate("to", to) + .executeQuery(Statistics.class); + } + + public static Class<?> getClassByName(String name) throws ClassNotFoundException { + switch (name.toLowerCase().replace("id", "")) { + case "device": + return Device.class; + case "group": + return Group.class; + case "user": + return User.class; + case "manageduser": + return ManagedUser.class; + case "geofence": + return Geofence.class; + case "driver": + return Driver.class; + case "attribute": + return Attribute.class; + case "calendar": + return Calendar.class; + case "command": + return Command.class; + case "maintenance": + return Maintenance.class; + case "notification": + return Notification.class; + default: + throw new ClassNotFoundException(); + } + } + + private static String makeNameId(Class<?> clazz) { + String name = clazz.getSimpleName(); + return Introspector.decapitalize(name) + (!name.contains("Id") ? "Id" : ""); + } + + public Collection<Permission> getPermissions(Class<? extends BaseModel> owner, Class<? extends BaseModel> property) + throws SQLException, ClassNotFoundException { + return QueryBuilder.create(dataSource, getQuery(ACTION_SELECT_ALL, owner, property)) + .executePermissionsQuery(); + } + + public void linkObject(Class<?> owner, long ownerId, Class<?> property, long propertyId, boolean link) + throws SQLException { + QueryBuilder.create(dataSource, getQuery(link ? ACTION_INSERT : ACTION_DELETE, owner, property)) + .setLong(makeNameId(owner), ownerId) + .setLong(makeNameId(property), propertyId) + .executeUpdate(); + } + + public <T extends BaseModel> T getObject(Class<T> clazz, long entityId) throws SQLException { + return QueryBuilder.create(dataSource, getQuery(ACTION_SELECT, clazz)) + .setLong("id", entityId) + .executeQuerySingle(clazz); + } + + public <T extends BaseModel> Collection<T> getObjects(Class<T> clazz) throws SQLException { + return QueryBuilder.create(dataSource, getQuery(ACTION_SELECT_ALL, clazz)) + .executeQuery(clazz); + } + + public void addObject(BaseModel entity) throws SQLException { + entity.setId(QueryBuilder.create(dataSource, getQuery(ACTION_INSERT, entity.getClass()), true) + .setObject(entity) + .executeUpdate()); + } + + public void updateObject(BaseModel entity) throws SQLException { + QueryBuilder.create(dataSource, getQuery(ACTION_UPDATE, entity.getClass())) + .setObject(entity) + .executeUpdate(); + if (entity instanceof User && ((User) entity).getHashedPassword() != null) { + QueryBuilder.create(dataSource, getQuery(ACTION_UPDATE, User.class, true)) + .setObject(entity) + .executeUpdate(); + } + } + + public void removeObject(Class<? extends BaseModel> clazz, long entityId) throws SQLException { + QueryBuilder.create(dataSource, getQuery(ACTION_DELETE, clazz)) + .setLong("id", entityId) + .executeUpdate(); + } + +} diff --git a/src/main/java/org/traccar/database/DeviceManager.java b/src/main/java/org/traccar/database/DeviceManager.java new file mode 100644 index 000000000..fa95adeb2 --- /dev/null +++ b/src/main/java/org/traccar/database/DeviceManager.java @@ -0,0 +1,438 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.Context; +import org.traccar.model.Command; +import org.traccar.model.Device; +import org.traccar.model.DeviceState; +import org.traccar.model.DeviceAccumulators; +import org.traccar.model.Group; +import org.traccar.model.Position; +import org.traccar.model.Server; + +public class DeviceManager extends BaseObjectManager<Device> implements IdentityManager, ManagableObjects { + + private static final Logger LOGGER = LoggerFactory.getLogger(DeviceManager.class); + + public static final long DEFAULT_REFRESH_DELAY = 300; + + private final Config config; + private final long dataRefreshDelay; + private boolean lookupGroupsAttribute; + + private Map<String, Device> devicesByUniqueId; + private Map<String, Device> devicesByPhone; + private AtomicLong devicesLastUpdate = new AtomicLong(); + + private final Map<Long, Position> positions = new ConcurrentHashMap<>(); + + private final Map<Long, DeviceState> deviceStates = new ConcurrentHashMap<>(); + + public DeviceManager(DataManager dataManager) { + super(dataManager, Device.class); + this.config = Context.getConfig(); + if (devicesByPhone == null) { + devicesByPhone = new ConcurrentHashMap<>(); + } + if (devicesByUniqueId == null) { + devicesByUniqueId = new ConcurrentHashMap<>(); + } + dataRefreshDelay = config.getLong("database.refreshDelay", DEFAULT_REFRESH_DELAY) * 1000; + lookupGroupsAttribute = config.getBoolean("deviceManager.lookupGroupsAttribute"); + refreshLastPositions(); + } + + @Override + public long addUnknownDevice(String uniqueId) { + Device device = new Device(); + device.setName(uniqueId); + device.setUniqueId(uniqueId); + device.setCategory(Context.getConfig().getString("database.registerUnknown.defaultCategory")); + + long defaultGroupId = Context.getConfig().getLong("database.registerUnknown.defaultGroupId"); + if (defaultGroupId != 0) { + device.setGroupId(defaultGroupId); + } + + try { + addItem(device); + + LOGGER.info("Automatically registered device " + uniqueId); + + if (defaultGroupId != 0) { + Context.getPermissionsManager().refreshDeviceAndGroupPermissions(); + Context.getPermissionsManager().refreshAllExtendedPermissions(); + } + + return device.getId(); + } catch (SQLException e) { + LOGGER.warn("Automatic device registration error", e); + return 0; + } + } + + public void updateDeviceCache(boolean force) throws SQLException { + long lastUpdate = devicesLastUpdate.get(); + if ((force || System.currentTimeMillis() - lastUpdate > dataRefreshDelay) + && devicesLastUpdate.compareAndSet(lastUpdate, System.currentTimeMillis())) { + refreshItems(); + } + } + + @Override + public Device getByUniqueId(String uniqueId) throws SQLException { + boolean forceUpdate = !devicesByUniqueId.containsKey(uniqueId) && !config.getBoolean("database.ignoreUnknown"); + + updateDeviceCache(forceUpdate); + + return devicesByUniqueId.get(uniqueId); + } + + @Override + public String getDevicePassword(long id, String protocol, String defaultPassword) { + + String password = lookupAttributeString(id, Command.KEY_DEVICE_PASSWORD, null, false, false); + if (password != null) { + return password; + } + + if (protocol != null) { + password = Context.getConfig().getString(protocol + "." + Command.KEY_DEVICE_PASSWORD); + if (password != null) { + return password; + } + } + + return defaultPassword; + } + + public Device getDeviceByPhone(String phone) { + return devicesByPhone.get(phone); + } + + @Override + public Set<Long> getAllItems() { + Set<Long> result = super.getAllItems(); + if (result.isEmpty()) { + try { + updateDeviceCache(true); + } catch (SQLException e) { + LOGGER.warn("Update device cache error", e); + } + result = super.getAllItems(); + } + return result; + } + + public Collection<Device> getAllDevices() { + return getItems(getAllItems()); + } + + public Set<Long> getAllUserItems(long userId) { + return Context.getPermissionsManager().getDevicePermissions(userId); + } + + @Override + public Set<Long> getUserItems(long userId) { + if (Context.getPermissionsManager() != null) { + Set<Long> result = new HashSet<>(); + for (long deviceId : Context.getPermissionsManager().getDevicePermissions(userId)) { + Device device = getById(deviceId); + if (device != null && !device.getDisabled()) { + result.add(deviceId); + } + } + return result; + } else { + return new HashSet<>(); + } + } + + public Set<Long> getAllManagedItems(long userId) { + Set<Long> result = new HashSet<>(); + result.addAll(getAllUserItems(userId)); + for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { + result.addAll(getAllUserItems(managedUserId)); + } + return result; + } + + @Override + public Set<Long> getManagedItems(long userId) { + Set<Long> result = new HashSet<>(); + result.addAll(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()); + } + devicesByUniqueId.put(device.getUniqueId(), device); + } + + private void putPhone(Device device) { + if (devicesByPhone == null) { + devicesByPhone = new ConcurrentHashMap<>(getAllItems().size()); + } + devicesByPhone.put(device.getPhone(), device); + } + + @Override + protected void addNewItem(Device device) { + super.addNewItem(device); + putUniqueDeviceId(device); + if (device.getPhone() != null && !device.getPhone().isEmpty()) { + putPhone(device); + } + if (Context.getGeofenceManager() != null) { + Position lastPosition = getLastPosition(device.getId()); + if (lastPosition != null) { + device.setGeofenceIds(Context.getGeofenceManager().getCurrentDeviceGeofences(lastPosition)); + } + } + } + + @Override + protected void updateCachedItem(Device device) { + Device cachedDevice = getById(device.getId()); + cachedDevice.setName(device.getName()); + cachedDevice.setGroupId(device.getGroupId()); + cachedDevice.setCategory(device.getCategory()); + cachedDevice.setContact(device.getContact()); + cachedDevice.setModel(device.getModel()); + cachedDevice.setDisabled(device.getDisabled()); + cachedDevice.setAttributes(device.getAttributes()); + if (!device.getUniqueId().equals(cachedDevice.getUniqueId())) { + devicesByUniqueId.remove(cachedDevice.getUniqueId()); + cachedDevice.setUniqueId(device.getUniqueId()); + putUniqueDeviceId(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); + } + cachedDevice.setPhone(device.getPhone()); + putPhone(cachedDevice); + } + } + + @Override + protected void removeCachedItem(long deviceId) { + Device cachedDevice = getById(deviceId); + if (cachedDevice != null) { + String deviceUniqueId = cachedDevice.getUniqueId(); + String phone = cachedDevice.getPhone(); + super.removeCachedItem(deviceId); + devicesByUniqueId.remove(deviceUniqueId); + if (phone != null && !phone.isEmpty()) { + devicesByPhone.remove(phone); + } + } + positions.remove(deviceId); + } + + public void updateDeviceStatus(Device device) throws SQLException { + getDataManager().updateDeviceStatus(device); + Device cachedDevice = getById(device.getId()); + if (cachedDevice != null) { + cachedDevice.setStatus(device.getStatus()); + } + } + + private void refreshLastPositions() { + if (getDataManager() != null) { + try { + for (Position position : getDataManager().getLatestPositions()) { + positions.put(position.getDeviceId(), position); + } + } catch (SQLException error) { + LOGGER.warn("Load latest positions error", error); + } + } + } + + public boolean isLatestPosition(Position position) { + Position lastPosition = getLastPosition(position.getDeviceId()); + return lastPosition == null || position.getFixTime().compareTo(lastPosition.getFixTime()) >= 0; + } + + public void updateLatestPosition(Position position) throws SQLException { + + if (isLatestPosition(position)) { + + getDataManager().updateLatestPosition(position); + + Device device = getById(position.getDeviceId()); + if (device != null) { + device.setPositionId(position.getId()); + } + + positions.put(position.getDeviceId(), position); + + if (Context.getConnectionManager() != null) { + Context.getConnectionManager().updatePosition(position); + } + } + } + + @Override + public Position getLastPosition(long deviceId) { + return positions.get(deviceId); + } + + public Collection<Position> getInitialState(long userId) { + + List<Position> result = new LinkedList<>(); + + if (Context.getPermissionsManager() != null) { + for (long deviceId : Context.getPermissionsManager().getUserAdmin(userId) + ? getAllUserItems(userId) : getUserItems(userId)) { + if (positions.containsKey(deviceId)) { + result.add(positions.get(deviceId)); + } + } + } + + return result; + } + + @Override + public boolean lookupAttributeBoolean( + long deviceId, String attributeName, boolean defaultValue, boolean lookupServer, boolean lookupConfig) { + Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); + if (result != null) { + return result instanceof String ? Boolean.parseBoolean((String) result) : (Boolean) result; + } + return defaultValue; + } + + @Override + public String lookupAttributeString( + long deviceId, String attributeName, String defaultValue, boolean lookupServer, boolean lookupConfig) { + Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); + return result != null ? (String) result : defaultValue; + } + + @Override + public int lookupAttributeInteger( + long deviceId, String attributeName, int defaultValue, boolean lookupServer, boolean lookupConfig) { + Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); + if (result != null) { + return result instanceof String ? Integer.parseInt((String) result) : ((Number) result).intValue(); + } + return defaultValue; + } + + @Override + public long lookupAttributeLong( + long deviceId, String attributeName, long defaultValue, boolean lookupServer, boolean lookupConfig) { + Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); + if (result != null) { + return result instanceof String ? Long.parseLong((String) result) : ((Number) result).longValue(); + } + return defaultValue; + } + + public double lookupAttributeDouble( + long deviceId, String attributeName, double defaultValue, boolean lookupServer, boolean lookupConfig) { + Object result = lookupAttribute(deviceId, attributeName, lookupServer, lookupConfig); + if (result != null) { + return result instanceof String ? Double.parseDouble((String) result) : ((Number) result).doubleValue(); + } + return defaultValue; + } + + private Object lookupAttribute(long deviceId, String attributeName, boolean lookupServer, boolean lookupConfig) { + Object result = null; + Device device = getById(deviceId); + if (device != null) { + result = device.getAttributes().get(attributeName); + if (result == null && lookupGroupsAttribute) { + long groupId = device.getGroupId(); + while (groupId != 0) { + Group group = Context.getGroupsManager().getById(groupId); + if (group != null) { + result = group.getAttributes().get(attributeName); + if (result != null) { + break; + } + groupId = group.getGroupId(); + } else { + groupId = 0; + } + } + } + if (result == null && lookupServer) { + Server server = Context.getPermissionsManager().getServer(); + result = server.getAttributes().get(attributeName); + } + if (result == null && lookupConfig) { + result = Context.getConfig().getString(attributeName); + } + } + return result; + } + + public void resetDeviceAccumulators(DeviceAccumulators deviceAccumulators) throws SQLException { + Position last = positions.get(deviceAccumulators.getDeviceId()); + if (last != null) { + if (deviceAccumulators.getTotalDistance() != null) { + last.getAttributes().put(Position.KEY_TOTAL_DISTANCE, deviceAccumulators.getTotalDistance()); + } + if (deviceAccumulators.getHours() != null) { + last.getAttributes().put(Position.KEY_HOURS, deviceAccumulators.getHours()); + } + getDataManager().addObject(last); + updateLatestPosition(last); + } else { + throw new IllegalArgumentException(); + } + } + + public DeviceState getDeviceState(long deviceId) { + DeviceState deviceState = deviceStates.get(deviceId); + if (deviceState == null) { + deviceState = new DeviceState(); + deviceStates.put(deviceId, deviceState); + } + return deviceState; + } + + public void setDeviceState(long deviceId, DeviceState deviceState) { + deviceStates.put(deviceId, deviceState); + } + +} diff --git a/src/main/java/org/traccar/database/DriversManager.java b/src/main/java/org/traccar/database/DriversManager.java new file mode 100644 index 000000000..930951460 --- /dev/null +++ b/src/main/java/org/traccar/database/DriversManager.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.traccar.model.Driver; + +public class DriversManager extends ExtendedObjectManager<Driver> { + + private Map<String, Driver> driversByUniqueId; + + public DriversManager(DataManager dataManager) { + super(dataManager, Driver.class); + if (driversByUniqueId == null) { + driversByUniqueId = new ConcurrentHashMap<>(); + } + } + + private void putUniqueDriverId(Driver driver) { + if (driversByUniqueId == null) { + driversByUniqueId = new ConcurrentHashMap<>(getAllItems().size()); + } + driversByUniqueId.put(driver.getUniqueId(), driver); + } + + @Override + protected void addNewItem(Driver driver) { + super.addNewItem(driver); + putUniqueDriverId(driver); + } + + @Override + protected void updateCachedItem(Driver driver) { + Driver cachedDriver = getById(driver.getId()); + cachedDriver.setName(driver.getName()); + if (!driver.getUniqueId().equals(cachedDriver.getUniqueId())) { + driversByUniqueId.remove(cachedDriver.getUniqueId()); + cachedDriver.setUniqueId(driver.getUniqueId()); + putUniqueDriverId(cachedDriver); + } + cachedDriver.setAttributes(driver.getAttributes()); + } + + @Override + protected void removeCachedItem(long driverId) { + Driver cachedDriver = getById(driverId); + if (cachedDriver != null) { + String driverUniqueId = cachedDriver.getUniqueId(); + super.removeCachedItem(driverId); + driversByUniqueId.remove(driverUniqueId); + } + } + + public Driver getDriverByUniqueId(String uniqueId) { + return driversByUniqueId.get(uniqueId); + } +} diff --git a/src/main/java/org/traccar/database/ExtendedObjectManager.java b/src/main/java/org/traccar/database/ExtendedObjectManager.java new file mode 100644 index 000000000..ceb85b537 --- /dev/null +++ b/src/main/java/org/traccar/database/ExtendedObjectManager.java @@ -0,0 +1,115 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.model.Permission; +import org.traccar.model.BaseModel; + +public abstract class ExtendedObjectManager<T extends BaseModel> extends SimpleObjectManager<T> { + + private static final Logger LOGGER = LoggerFactory.getLogger(ExtendedObjectManager.class); + + private final Map<Long, Set<Long>> deviceItems = new ConcurrentHashMap<>(); + private final Map<Long, Set<Long>> deviceItemsWithGroups = new ConcurrentHashMap<>(); + private final Map<Long, Set<Long>> groupItems = new ConcurrentHashMap<>(); + + protected ExtendedObjectManager(DataManager dataManager, Class<T> baseClass) { + super(dataManager, baseClass); + refreshExtendedPermissions(); + } + + public final Set<Long> getGroupItems(long groupId) { + if (!groupItems.containsKey(groupId)) { + groupItems.put(groupId, new HashSet<Long>()); + } + return groupItems.get(groupId); + } + + public final Set<Long> getDeviceItems(long deviceId) { + if (!deviceItems.containsKey(deviceId)) { + deviceItems.put(deviceId, new HashSet<Long>()); + } + return deviceItems.get(deviceId); + } + + public Set<Long> getAllDeviceItems(long deviceId) { + if (!deviceItemsWithGroups.containsKey(deviceId)) { + deviceItemsWithGroups.put(deviceId, new HashSet<Long>()); + } + return deviceItemsWithGroups.get(deviceId); + } + + @Override + public void removeItem(long itemId) throws SQLException { + super.removeItem(itemId); + refreshExtendedPermissions(); + } + + public void refreshExtendedPermissions() { + if (getDataManager() != null) { + try { + + Collection<Permission> databaseGroupPermissions = + getDataManager().getPermissions(Group.class, getBaseClass()); + + groupItems.clear(); + for (Permission groupPermission : databaseGroupPermissions) { + getGroupItems(groupPermission.getOwnerId()).add(groupPermission.getPropertyId()); + } + + Collection<Permission> databaseDevicePermissions = + getDataManager().getPermissions(Device.class, getBaseClass()); + + deviceItems.clear(); + deviceItemsWithGroups.clear(); + + for (Permission devicePermission : databaseDevicePermissions) { + getDeviceItems(devicePermission.getOwnerId()).add(devicePermission.getPropertyId()); + getAllDeviceItems(devicePermission.getOwnerId()).add(devicePermission.getPropertyId()); + } + + for (Device device : Context.getDeviceManager().getAllDevices()) { + long groupId = device.getGroupId(); + while (groupId != 0) { + getAllDeviceItems(device.getId()).addAll(getGroupItems(groupId)); + Group group = Context.getGroupsManager().getById(groupId); + if (group != null) { + groupId = group.getGroupId(); + } else { + groupId = 0; + } + } + } + + } catch (SQLException | ClassNotFoundException error) { + LOGGER.warn("Refresh permissions error", error); + } + } + } +} diff --git a/src/main/java/org/traccar/database/GeofenceManager.java b/src/main/java/org/traccar/database/GeofenceManager.java new file mode 100644 index 000000000..a32847cf9 --- /dev/null +++ b/src/main/java/org/traccar/database/GeofenceManager.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.util.ArrayList; +import java.util.List; + +import org.traccar.Context; +import org.traccar.model.Device; +import org.traccar.model.Geofence; +import org.traccar.model.Position; + +public class GeofenceManager extends ExtendedObjectManager<Geofence> { + + public GeofenceManager(DataManager dataManager) { + super(dataManager, Geofence.class); + } + + @Override + public final void refreshExtendedPermissions() { + super.refreshExtendedPermissions(); + recalculateDevicesGeofences(); + } + + public List<Long> getCurrentDeviceGeofences(Position position) { + List<Long> result = new ArrayList<>(); + for (long geofenceId : getAllDeviceItems(position.getDeviceId())) { + Geofence geofence = getById(geofenceId); + if (geofence != null && geofence.getGeometry() + .containsPoint(position.getLatitude(), position.getLongitude())) { + result.add(geofenceId); + } + } + return result; + } + + public void recalculateDevicesGeofences() { + for (Device device : Context.getDeviceManager().getAllDevices()) { + List<Long> deviceGeofenceIds = device.getGeofenceIds(); + if (deviceGeofenceIds == null) { + deviceGeofenceIds = new ArrayList<>(); + } else { + deviceGeofenceIds.clear(); + } + Position lastPosition = Context.getIdentityManager().getLastPosition(device.getId()); + if (lastPosition != null && getAllDeviceItems(device.getId()) != null) { + deviceGeofenceIds.addAll(getCurrentDeviceGeofences(lastPosition)); + } + device.setGeofenceIds(deviceGeofenceIds); + } + } + +} diff --git a/src/main/java/org/traccar/database/GroupTree.java b/src/main/java/org/traccar/database/GroupTree.java new file mode 100644 index 000000000..8798f55bc --- /dev/null +++ b/src/main/java/org/traccar/database/GroupTree.java @@ -0,0 +1,151 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import org.traccar.model.Device; +import org.traccar.model.Group; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class GroupTree { + + private static class TreeNode { + + private Group group; + private Device device; + private Collection<TreeNode> children = new HashSet<>(); + + TreeNode(Group group) { + this.group = group; + } + + TreeNode(Device device) { + this.device = device; + } + + @Override + public int hashCode() { + if (group != null) { + return (int) group.getId(); + } else { + return (int) device.getId(); + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TreeNode)) { + return false; + } + TreeNode other = (TreeNode) obj; + if (other == this) { + return true; + } + if (group != null && other.group != null) { + return group.getId() == other.group.getId(); + } else if (device != null && other.device != null) { + return device.getId() == other.device.getId(); + } + return false; + } + + public Group getGroup() { + return group; + } + + public Device getDevice() { + return device; + } + + public void setParent(TreeNode parent) { + if (parent != null) { + parent.children.add(this); + } + } + + public Collection<TreeNode> getChildren() { + return children; + } + + } + + private final Map<Long, TreeNode> groupMap = new HashMap<>(); + + public GroupTree(Collection<Group> groups, Collection<Device> devices) { + + for (Group group : groups) { + groupMap.put(group.getId(), new TreeNode(group)); + } + + for (TreeNode node : groupMap.values()) { + if (node.getGroup().getGroupId() != 0) { + node.setParent(groupMap.get(node.getGroup().getGroupId())); + } + } + + Map<Long, TreeNode> deviceMap = new HashMap<>(); + + for (Device device : devices) { + deviceMap.put(device.getId(), new TreeNode(device)); + } + + for (TreeNode node : deviceMap.values()) { + if (node.getDevice().getGroupId() != 0) { + node.setParent(groupMap.get(node.getDevice().getGroupId())); + } + } + + } + + public Collection<Group> getGroups(long groupId) { + Set<TreeNode> results = new HashSet<>(); + getNodes(results, groupMap.get(groupId)); + Collection<Group> groups = new ArrayList<>(); + for (TreeNode node : results) { + if (node.getGroup() != null) { + groups.add(node.getGroup()); + } + } + return groups; + } + + public Collection<Device> getDevices(long groupId) { + Set<TreeNode> results = new HashSet<>(); + getNodes(results, groupMap.get(groupId)); + Collection<Device> devices = new ArrayList<>(); + for (TreeNode node : results) { + if (node.getDevice() != null) { + devices.add(node.getDevice()); + } + } + return devices; + } + + private void getNodes(Set<TreeNode> results, TreeNode node) { + if (node != null) { + for (TreeNode child : node.getChildren()) { + results.add(child); + getNodes(results, child); + } + } + } + +} diff --git a/src/main/java/org/traccar/database/GroupsManager.java b/src/main/java/org/traccar/database/GroupsManager.java new file mode 100644 index 000000000..d8404c614 --- /dev/null +++ b/src/main/java/org/traccar/database/GroupsManager.java @@ -0,0 +1,106 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.model.Group; + +public class GroupsManager extends BaseObjectManager<Group> implements ManagableObjects { + + private static final Logger LOGGER = LoggerFactory.getLogger(GroupsManager.class); + + private AtomicLong groupsLastUpdate = new AtomicLong(); + private final long dataRefreshDelay; + + public GroupsManager(DataManager dataManager) { + super(dataManager, Group.class); + dataRefreshDelay = Context.getConfig().getLong("database.refreshDelay", + DeviceManager.DEFAULT_REFRESH_DELAY) * 1000; + } + + private void checkGroupCycles(Group group) { + Set<Long> groups = new HashSet<>(); + while (group != null) { + if (groups.contains(group.getId())) { + throw new IllegalArgumentException("Cycle in group hierarchy"); + } + groups.add(group.getId()); + group = getById(group.getGroupId()); + } + } + + public void updateGroupCache(boolean force) throws SQLException { + long lastUpdate = groupsLastUpdate.get(); + if ((force || System.currentTimeMillis() - lastUpdate > dataRefreshDelay) + && groupsLastUpdate.compareAndSet(lastUpdate, System.currentTimeMillis())) { + refreshItems(); + } + } + + @Override + public Set<Long> getAllItems() { + Set<Long> result = super.getAllItems(); + if (result.isEmpty()) { + try { + updateGroupCache(true); + } catch (SQLException e) { + LOGGER.warn("Update group cache error", e); + } + result = super.getAllItems(); + } + return result; + } + + @Override + protected void addNewItem(Group group) { + checkGroupCycles(group); + super.addNewItem(group); + } + + @Override + public void updateItem(Group group) throws SQLException { + checkGroupCycles(group); + super.updateItem(group); + } + + @Override + public Set<Long> getUserItems(long userId) { + if (Context.getPermissionsManager() != null) { + return Context.getPermissionsManager().getGroupPermissions(userId); + } else { + return new HashSet<>(); + } + } + + @Override + public Set<Long> getManagedItems(long userId) { + Set<Long> result = new HashSet<>(); + result.addAll(getUserItems(userId)); + for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { + result.addAll(getUserItems(managedUserId)); + } + return result; + } + +} diff --git a/src/main/java/org/traccar/database/IdentityManager.java b/src/main/java/org/traccar/database/IdentityManager.java new file mode 100644 index 000000000..af6a6ce71 --- /dev/null +++ b/src/main/java/org/traccar/database/IdentityManager.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import org.traccar.model.Device; +import org.traccar.model.Position; + +public interface IdentityManager { + + long addUnknownDevice(String uniqueId); + + Device getById(long id); + + Device getByUniqueId(String uniqueId) throws Exception; + + String getDevicePassword(long id, String protocol, String defaultPassword); + + Position getLastPosition(long deviceId); + + boolean isLatestPosition(Position position); + + boolean lookupAttributeBoolean( + long deviceId, String attributeName, boolean defaultValue, boolean lookupServer, boolean lookupConfig); + + String lookupAttributeString( + long deviceId, String attributeName, String defaultValue, boolean lookupServer, boolean lookupConfig); + + int lookupAttributeInteger( + long deviceId, String attributeName, int defaultValue, boolean lookupServer, boolean lookupConfig); + + long lookupAttributeLong( + long deviceId, String attributeName, long defaultValue, boolean lookupServer, boolean lookupConfig); + + double lookupAttributeDouble( + long deviceId, String attributeName, double defaultValue, boolean lookupServer, boolean lookupConfig); + +} diff --git a/src/main/java/org/traccar/database/LdapProvider.java b/src/main/java/org/traccar/database/LdapProvider.java new file mode 100644 index 000000000..d8b5c9f52 --- /dev/null +++ b/src/main/java/org/traccar/database/LdapProvider.java @@ -0,0 +1,179 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import javax.naming.Context; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.model.User; + +import java.util.Hashtable; + +public class LdapProvider { + + private static final Logger LOGGER = LoggerFactory.getLogger(LdapProvider.class); + + private String url; + private String searchBase; + private String idAttribute; + private String nameAttribute; + private String mailAttribute; + private String searchFilter; + private String adminFilter; + private String serviceUser; + private String servicePassword; + + public LdapProvider(Config config) { + String url = config.getString("ldap.url"); + if (url != null) { + this.url = url; + } else { + this.url = "ldap://" + config.getString("ldap.server") + ":" + config.getInteger("ldap.port", 389); + } + this.searchBase = config.getString("ldap.base"); + this.idAttribute = config.getString("ldap.idAttribute", "uid"); + this.nameAttribute = config.getString("ldap.nameAttribute", "cn"); + this.mailAttribute = config.getString("ldap.mailAttribute", "mail"); + this.searchFilter = config.getString("ldap.searchFilter", "(" + idAttribute + "=:login)"); + String adminGroup = config.getString("ldap.adminGroup"); + this.adminFilter = config.getString("ldap.adminFilter"); + if (this.adminFilter == null && adminGroup != null) { + this.adminFilter = "(&(" + idAttribute + "=:login)(memberOf=" + adminGroup + "))"; + } + this.serviceUser = config.getString("ldap.user"); + this.servicePassword = config.getString("ldap.password"); + } + + private InitialDirContext auth(String accountName, String password) throws NamingException { + Hashtable<String, String> env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, url); + + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, accountName); + env.put(Context.SECURITY_CREDENTIALS, password); + + return new InitialDirContext(env); + } + + private boolean isAdmin(String accountName) { + if (this.adminFilter != null) { + try { + InitialDirContext context = initContext(); + String searchString = adminFilter.replace(":login", accountName); + SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + NamingEnumeration<SearchResult> results = context.search(searchBase, searchString, searchControls); + if (results.hasMoreElements()) { + results.nextElement(); + if (results.hasMoreElements()) { + LOGGER.warn("Matched multiple users for the accountName: " + accountName); + return false; + } + return true; + } + } catch (NamingException e) { + return false; + } + } + return false; + } + + public InitialDirContext initContext() throws NamingException { + return auth(serviceUser, servicePassword); + } + + private SearchResult lookupUser(String accountName) throws NamingException { + InitialDirContext context = initContext(); + + String searchString = searchFilter.replace(":login", accountName); + + SearchControls searchControls = new SearchControls(); + String[] attributeFilter = {idAttribute, nameAttribute, mailAttribute}; + searchControls.setReturningAttributes(attributeFilter); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + NamingEnumeration<SearchResult> results = context.search(searchBase, searchString, searchControls); + + SearchResult searchResult = null; + if (results.hasMoreElements()) { + searchResult = results.nextElement(); + if (results.hasMoreElements()) { + LOGGER.warn("Matched multiple users for the accountName: " + accountName); + return null; + } + } + + return searchResult; + } + + public User getUser(String accountName) { + SearchResult ldapUser; + User user = new User(); + try { + ldapUser = lookupUser(accountName); + if (ldapUser != null) { + Attribute attribute = ldapUser.getAttributes().get(idAttribute); + if (attribute != null) { + user.setLogin((String) attribute.get()); + } else { + user.setLogin(accountName); + } + attribute = ldapUser.getAttributes().get(nameAttribute); + if (attribute != null) { + user.setName((String) attribute.get()); + } else { + user.setName(accountName); + } + attribute = ldapUser.getAttributes().get(mailAttribute); + if (attribute != null) { + user.setEmail((String) attribute.get()); + } else { + user.setEmail(accountName); + } + } + user.setAdministrator(isAdmin(accountName)); + } catch (NamingException e) { + user.setLogin(accountName); + user.setName(accountName); + user.setEmail(accountName); + LOGGER.warn("User lookup error", e); + } + return user; + } + + public boolean login(String username, String password) { + try { + SearchResult ldapUser = lookupUser(username); + if (ldapUser != null) { + auth(ldapUser.getNameInNamespace(), password).close(); + return true; + } + } catch (NamingException e) { + return false; + } + return false; + } + +} diff --git a/src/main/java/org/traccar/database/MailManager.java b/src/main/java/org/traccar/database/MailManager.java new file mode 100644 index 000000000..8a2f002cd --- /dev/null +++ b/src/main/java/org/traccar/database/MailManager.java @@ -0,0 +1,147 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.Main; +import org.traccar.model.User; +import org.traccar.notification.PropertiesProvider; + +import javax.mail.BodyPart; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import java.util.Date; +import java.util.Properties; + +public final class MailManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(MailManager.class); + + private static Properties getProperties(PropertiesProvider provider) { + Properties properties = new Properties(); + String host = provider.getString("mail.smtp.host"); + if (host != null) { + properties.put("mail.transport.protocol", provider.getString("mail.transport.protocol", "smtp")); + properties.put("mail.smtp.host", host); + properties.put("mail.smtp.port", String.valueOf(provider.getInteger("mail.smtp.port", 25))); + + Boolean starttlsEnable = provider.getBoolean("mail.smtp.starttls.enable"); + if (starttlsEnable != null) { + properties.put("mail.smtp.starttls.enable", String.valueOf(starttlsEnable)); + } + Boolean starttlsRequired = provider.getBoolean("mail.smtp.starttls.required"); + if (starttlsRequired != null) { + properties.put("mail.smtp.starttls.required", String.valueOf(starttlsRequired)); + } + + Boolean sslEnable = provider.getBoolean("mail.smtp.ssl.enable"); + if (sslEnable != null) { + properties.put("mail.smtp.ssl.enable", String.valueOf(sslEnable)); + } + String sslTrust = provider.getString("mail.smtp.ssl.trust"); + if (sslTrust != null) { + properties.put("mail.smtp.ssl.trust", sslTrust); + } + + String sslProtocols = provider.getString("mail.smtp.ssl.protocols"); + if (sslProtocols != null) { + properties.put("mail.smtp.ssl.protocols", sslProtocols); + } + + String username = provider.getString("mail.smtp.username"); + if (username != null) { + properties.put("mail.smtp.username", username); + } + String password = provider.getString("mail.smtp.password"); + if (password != null) { + properties.put("mail.smtp.password", password); + } + String from = provider.getString("mail.smtp.from"); + if (from != null) { + properties.put("mail.smtp.from", from); + } + } + return properties; + } + + public void sendMessage( + long userId, String subject, String body) throws MessagingException { + sendMessage(userId, subject, body, null); + } + + public void sendMessage( + long userId, String subject, String body, MimeBodyPart attachment) throws MessagingException { + User user = Context.getPermissionsManager().getUser(userId); + + Properties properties = null; + if (!Context.getConfig().getBoolean("mail.smtp.ignoreUserConfig")) { + properties = getProperties(new PropertiesProvider(user)); + } + if (properties == null || !properties.containsKey("mail.smtp.host")) { + properties = getProperties(new PropertiesProvider(Context.getConfig())); + } + if (!properties.containsKey("mail.smtp.host")) { + LOGGER.warn("No SMTP configuration found"); + return; + } + + Session session = Session.getInstance(properties); + + MimeMessage message = new MimeMessage(session); + + String from = properties.getProperty("mail.smtp.from"); + if (from != null) { + message.setFrom(new InternetAddress(from)); + } + + message.addRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmail())); + message.setSubject(subject); + message.setSentDate(new Date()); + + if (attachment != null) { + Multipart multipart = new MimeMultipart(); + + BodyPart messageBodyPart = new MimeBodyPart(); + messageBodyPart.setContent(body, "text/html; charset=utf-8"); + multipart.addBodyPart(messageBodyPart); + multipart.addBodyPart(attachment); + + message.setContent(multipart); + } else { + message.setContent(body, "text/html; charset=utf-8"); + } + + try (Transport transport = session.getTransport()) { + Main.getInjector().getInstance(StatisticsManager.class).registerMail(); + transport.connect( + properties.getProperty("mail.smtp.host"), + properties.getProperty("mail.smtp.username"), + properties.getProperty("mail.smtp.password")); + transport.sendMessage(message, message.getAllRecipients()); + } + } + +} diff --git a/src/main/java/org/traccar/database/MaintenancesManager.java b/src/main/java/org/traccar/database/MaintenancesManager.java new file mode 100644 index 000000000..4e266cb78 --- /dev/null +++ b/src/main/java/org/traccar/database/MaintenancesManager.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import org.traccar.model.Maintenance; + +public class MaintenancesManager extends ExtendedObjectManager<Maintenance> { + + public MaintenancesManager(DataManager dataManager) { + super(dataManager, Maintenance.class); + } + +} diff --git a/src/main/java/org/traccar/database/ManagableObjects.java b/src/main/java/org/traccar/database/ManagableObjects.java new file mode 100644 index 000000000..ec9549493 --- /dev/null +++ b/src/main/java/org/traccar/database/ManagableObjects.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.util.Set; + +public interface ManagableObjects { + + Set<Long> getUserItems(long userId); + + Set<Long> getManagedItems(long userId); + +} diff --git a/src/main/java/org/traccar/database/MediaManager.java b/src/main/java/org/traccar/database/MediaManager.java new file mode 100644 index 000000000..edade5766 --- /dev/null +++ b/src/main/java/org/traccar/database/MediaManager.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import io.netty.buffer.ByteBuf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class MediaManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(MediaManager.class); + + private String path; + + public MediaManager(String path) { + this.path = path; + } + + private File createFile(String uniqueId, String name) throws IOException { + Path filePath = Paths.get(path, uniqueId, name); + Path directoryPath = filePath.getParent(); + if (directoryPath != null) { + Files.createDirectories(directoryPath); + } + return filePath.toFile(); + } + + public String writeFile(String uniqueId, ByteBuf buf, String extension) { + if (path != null) { + int size = buf.readableBytes(); + String name = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "." + extension; + try (FileOutputStream output = new FileOutputStream(createFile(uniqueId, name)); + FileChannel fileChannel = output.getChannel()) { + ByteBuffer byteBuffer = buf.nioBuffer(); + int written = 0; + while (written < size) { + written += fileChannel.write(byteBuffer); + } + fileChannel.force(false); + return name; + } catch (IOException e) { + LOGGER.warn("Save media file error", e); + } + } + return null; + } + +} diff --git a/src/main/java/org/traccar/database/NotificationManager.java b/src/main/java/org/traccar/database/NotificationManager.java new file mode 100644 index 000000000..09df4c571 --- /dev/null +++ b/src/main/java/org/traccar/database/NotificationManager.java @@ -0,0 +1,135 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.sql.SQLException; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.model.Calendar; +import org.traccar.model.Event; +import org.traccar.model.Notification; +import org.traccar.model.Position; +import org.traccar.model.Typed; + +public class NotificationManager extends ExtendedObjectManager<Notification> { + + private static final Logger LOGGER = LoggerFactory.getLogger(NotificationManager.class); + + private boolean geocodeOnRequest; + + public NotificationManager(DataManager dataManager) { + super(dataManager, Notification.class); + geocodeOnRequest = Context.getConfig().getBoolean("geocoder.onRequest"); + } + + private Set<Long> getEffectiveNotifications(long userId, long deviceId, Date time) { + Set<Long> result = new HashSet<>(); + Set<Long> deviceNotifications = getAllDeviceItems(deviceId); + for (long itemId : getUserItems(userId)) { + if (getById(itemId).getAlways() || deviceNotifications.contains(itemId)) { + long calendarId = getById(itemId).getCalendarId(); + Calendar calendar = calendarId != 0 ? Context.getCalendarManager().getById(calendarId) : null; + if (calendar == null || calendar.checkMoment(time)) { + result.add(itemId); + } + } + } + return result; + } + + public void updateEvent(Event event, Position position) { + try { + getDataManager().addObject(event); + } catch (SQLException error) { + LOGGER.warn("Event save error", error); + } + + if (position != null && geocodeOnRequest && Context.getGeocoder() != null && position.getAddress() == null) { + position.setAddress(Context.getGeocoder() + .getAddress(position.getLatitude(), position.getLongitude(), null)); + } + + long deviceId = event.getDeviceId(); + Set<Long> users = Context.getPermissionsManager().getDeviceUsers(deviceId); + Set<Long> usersToForward = null; + if (Context.getEventForwarder() != null) { + usersToForward = new HashSet<>(); + } + for (long userId : users) { + if ((event.getGeofenceId() == 0 + || Context.getGeofenceManager().checkItemPermission(userId, event.getGeofenceId())) + && (event.getMaintenanceId() == 0 + || Context.getMaintenancesManager().checkItemPermission(userId, event.getMaintenanceId()))) { + if (usersToForward != null) { + usersToForward.add(userId); + } + final Set<String> notificators = new HashSet<>(); + for (long notificationId : getEffectiveNotifications(userId, deviceId, event.getServerTime())) { + Notification notification = getById(notificationId); + if (getById(notificationId).getType().equals(event.getType())) { + boolean filter = false; + if (event.getType().equals(Event.TYPE_ALARM)) { + String alarms = notification.getString("alarms"); + if (alarms == null || !alarms.contains(event.getString(Position.KEY_ALARM))) { + filter = true; + } + } + if (!filter) { + notificators.addAll(notification.getNotificatorsTypes()); + } + } + } + for (String notificator : notificators) { + Context.getNotificatorManager().getNotificator(notificator).sendAsync(userId, event, position); + } + } + } + if (Context.getEventForwarder() != null) { + Context.getEventForwarder().forwardEvent(event, position, usersToForward); + } + } + + public void updateEvents(Map<Event, Position> events) { + for (Entry<Event, Position> event : events.entrySet()) { + updateEvent(event.getKey(), event.getValue()); + } + } + + public Set<Typed> getAllNotificationTypes() { + Set<Typed> types = new HashSet<>(); + Field[] fields = Event.class.getDeclaredFields(); + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers()) && field.getName().startsWith("TYPE_")) { + try { + types.add(new Typed(field.get(null).toString())); + } catch (IllegalArgumentException | IllegalAccessException error) { + LOGGER.warn("Get event types error", error); + } + } + } + return types; + } +} diff --git a/src/main/java/org/traccar/database/PermissionsManager.java b/src/main/java/org/traccar/database/PermissionsManager.java new file mode 100644 index 000000000..ced0df1c0 --- /dev/null +++ b/src/main/java/org/traccar/database/PermissionsManager.java @@ -0,0 +1,459 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.model.Attribute; +import org.traccar.model.BaseModel; +import org.traccar.model.Calendar; +import org.traccar.model.Command; +import org.traccar.model.Device; +import org.traccar.model.Driver; +import org.traccar.model.Geofence; +import org.traccar.model.Group; +import org.traccar.model.Maintenance; +import org.traccar.model.ManagedUser; +import org.traccar.model.Notification; +import org.traccar.model.Permission; +import org.traccar.model.Server; +import org.traccar.model.User; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class PermissionsManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(PermissionsManager.class); + + private final DataManager dataManager; + private final UsersManager usersManager; + + private volatile Server server; + + private final Map<Long, Set<Long>> groupPermissions = new HashMap<>(); + private final Map<Long, Set<Long>> devicePermissions = new HashMap<>(); + private final Map<Long, Set<Long>> deviceUsers = new HashMap<>(); + private final Map<Long, Set<Long>> groupDevices = new HashMap<>(); + + public PermissionsManager(DataManager dataManager, UsersManager usersManager) { + this.dataManager = dataManager; + this.usersManager = usersManager; + refreshServer(); + refreshDeviceAndGroupPermissions(); + } + + public User getUser(long userId) { + return usersManager.getById(userId); + } + + public Set<Long> getGroupPermissions(long userId) { + if (!groupPermissions.containsKey(userId)) { + groupPermissions.put(userId, new HashSet<>()); + } + return groupPermissions.get(userId); + } + + public Set<Long> getDevicePermissions(long userId) { + if (!devicePermissions.containsKey(userId)) { + devicePermissions.put(userId, new HashSet<>()); + } + return devicePermissions.get(userId); + } + + private Set<Long> getAllDeviceUsers(long deviceId) { + if (!deviceUsers.containsKey(deviceId)) { + deviceUsers.put(deviceId, new HashSet<>()); + } + return deviceUsers.get(deviceId); + } + + public Set<Long> getDeviceUsers(long deviceId) { + Device device = Context.getIdentityManager().getById(deviceId); + if (device != null && !device.getDisabled()) { + return getAllDeviceUsers(deviceId); + } else { + Set<Long> result = new HashSet<>(); + for (long userId : getAllDeviceUsers(deviceId)) { + if (getUserAdmin(userId)) { + result.add(userId); + } + } + return result; + } + } + + public Set<Long> getGroupDevices(long groupId) { + if (!groupDevices.containsKey(groupId)) { + groupDevices.put(groupId, new HashSet<>()); + } + return groupDevices.get(groupId); + } + + public void refreshServer() { + try { + server = dataManager.getServer(); + } catch (SQLException error) { + LOGGER.warn("Refresh server config error", error); + } + } + + public final void refreshDeviceAndGroupPermissions() { + groupPermissions.clear(); + devicePermissions.clear(); + try { + GroupTree groupTree = new GroupTree(Context.getGroupsManager().getItems( + Context.getGroupsManager().getAllItems()), + Context.getDeviceManager().getAllDevices()); + for (Permission groupPermission : dataManager.getPermissions(User.class, Group.class)) { + Set<Long> userGroupPermissions = getGroupPermissions(groupPermission.getOwnerId()); + Set<Long> userDevicePermissions = getDevicePermissions(groupPermission.getOwnerId()); + userGroupPermissions.add(groupPermission.getPropertyId()); + for (Group group : groupTree.getGroups(groupPermission.getPropertyId())) { + userGroupPermissions.add(group.getId()); + } + for (Device device : groupTree.getDevices(groupPermission.getPropertyId())) { + userDevicePermissions.add(device.getId()); + } + } + + for (Permission devicePermission : dataManager.getPermissions(User.class, Device.class)) { + getDevicePermissions(devicePermission.getOwnerId()).add(devicePermission.getPropertyId()); + } + + groupDevices.clear(); + for (long groupId : Context.getGroupsManager().getAllItems()) { + for (Device device : groupTree.getDevices(groupId)) { + getGroupDevices(groupId).add(device.getId()); + } + } + + } catch (SQLException | ClassNotFoundException error) { + LOGGER.warn("Refresh device permissions error", error); + } + + deviceUsers.clear(); + for (Map.Entry<Long, Set<Long>> entry : devicePermissions.entrySet()) { + for (long deviceId : entry.getValue()) { + getAllDeviceUsers(deviceId).add(entry.getKey()); + } + } + } + + public boolean getUserAdmin(long userId) { + User user = getUser(userId); + return user != null && user.getAdministrator(); + } + + public void checkAdmin(long userId) throws SecurityException { + if (!getUserAdmin(userId)) { + throw new SecurityException("Admin access required"); + } + } + + public boolean getUserManager(long userId) { + User user = getUser(userId); + return user != null && user.getUserLimit() != 0; + } + + public void checkManager(long userId) throws SecurityException { + if (!getUserManager(userId)) { + throw new SecurityException("Manager access required"); + } + } + + public void checkManager(long userId, long managedUserId) throws SecurityException { + checkManager(userId); + if (!usersManager.getUserItems(userId).contains(managedUserId)) { + throw new SecurityException("User access denied"); + } + } + + public void checkUserLimit(long userId) throws SecurityException { + int userLimit = getUser(userId).getUserLimit(); + if (userLimit != -1 && usersManager.getUserItems(userId).size() >= userLimit) { + throw new SecurityException("Manager user limit reached"); + } + } + + public void checkDeviceLimit(long userId) throws SecurityException { + int deviceLimit = getUser(userId).getDeviceLimit(); + if (deviceLimit != -1) { + int deviceCount = 0; + if (getUserManager(userId)) { + deviceCount = Context.getDeviceManager().getAllManagedItems(userId).size(); + } else { + deviceCount = Context.getDeviceManager().getAllUserItems(userId).size(); + } + if (deviceCount >= deviceLimit) { + throw new SecurityException("User device limit reached"); + } + } + } + + public boolean getUserReadonly(long userId) { + User user = getUser(userId); + return user != null && user.getReadonly(); + } + + public boolean getUserDeviceReadonly(long userId) { + User user = getUser(userId); + return user != null && user.getDeviceReadonly(); + } + + public boolean getUserLimitCommands(long userId) { + User user = getUser(userId); + return user != null && user.getLimitCommands(); + } + + public void checkReadonly(long userId) throws SecurityException { + if (!getUserAdmin(userId) && (server.getReadonly() || getUserReadonly(userId))) { + throw new SecurityException("Account is readonly"); + } + } + + public void checkDeviceReadonly(long userId) throws SecurityException { + if (!getUserAdmin(userId) && (server.getDeviceReadonly() || getUserDeviceReadonly(userId))) { + throw new SecurityException("Account is device readonly"); + } + } + + public void checkLimitCommands(long userId) throws SecurityException { + if (!getUserAdmin(userId) && (server.getLimitCommands() || getUserLimitCommands(userId))) { + throw new SecurityException("Account has limit sending commands"); + } + } + + public void checkUserDeviceCommand(long userId, long deviceId, long commandId) throws SecurityException { + if (!getUserAdmin(userId) && Context.getCommandsManager().checkDeviceCommand(deviceId, commandId)) { + throw new SecurityException("Command can not be sent to this device"); + } + } + + public void checkUserEnabled(long userId) throws SecurityException { + User user = getUser(userId); + if (user == null) { + throw new SecurityException("Unknown account"); + } + if (user.getDisabled()) { + throw new SecurityException("Account is disabled"); + } + if (user.getExpirationTime() != null && System.currentTimeMillis() > user.getExpirationTime().getTime()) { + throw new SecurityException("Account has expired"); + } + } + + public void checkUserUpdate(long userId, User before, User after) throws SecurityException { + if (before.getAdministrator() != after.getAdministrator() + || before.getDeviceLimit() != after.getDeviceLimit() + || before.getUserLimit() != after.getUserLimit()) { + checkAdmin(userId); + } + User user = getUser(userId); + if (user != null && user.getExpirationTime() != null + && (after.getExpirationTime() == null + || user.getExpirationTime().compareTo(after.getExpirationTime()) < 0)) { + checkAdmin(userId); + } + if (before.getReadonly() != after.getReadonly() + || before.getDeviceReadonly() != after.getDeviceReadonly() + || before.getDisabled() != after.getDisabled() + || before.getLimitCommands() != after.getLimitCommands()) { + if (userId == after.getId()) { + checkAdmin(userId); + } + if (!getUserAdmin(userId)) { + checkManager(userId); + } + } + } + + public void checkUser(long userId, long managedUserId) throws SecurityException { + if (userId != managedUserId && !getUserAdmin(userId)) { + checkManager(userId, managedUserId); + } + } + + public void checkGroup(long userId, long groupId) throws SecurityException { + if (!getGroupPermissions(userId).contains(groupId) && !getUserAdmin(userId)) { + checkManager(userId); + for (long managedUserId : usersManager.getUserItems(userId)) { + if (getGroupPermissions(managedUserId).contains(groupId)) { + return; + } + } + throw new SecurityException("Group access denied"); + } + } + + public void checkDevice(long userId, long deviceId) throws SecurityException { + if (!Context.getDeviceManager().getUserItems(userId).contains(deviceId) && !getUserAdmin(userId)) { + checkManager(userId); + for (long managedUserId : usersManager.getUserItems(userId)) { + if (Context.getDeviceManager().getUserItems(managedUserId).contains(deviceId)) { + return; + } + } + throw new SecurityException("Device access denied"); + } + } + + public void checkRegistration(long userId) { + if (!server.getRegistration() && !getUserAdmin(userId)) { + throw new SecurityException("Registration disabled"); + } + } + + public void checkPermission(Class<?> object, long userId, long objectId) + throws SecurityException { + SimpleObjectManager<? extends BaseModel> manager = null; + + if (object.equals(Device.class)) { + checkDevice(userId, objectId); + } else if (object.equals(Group.class)) { + checkGroup(userId, objectId); + } else if (object.equals(User.class) || object.equals(ManagedUser.class)) { + checkUser(userId, objectId); + } else if (object.equals(Geofence.class)) { + manager = Context.getGeofenceManager(); + } else if (object.equals(Attribute.class)) { + manager = Context.getAttributesManager(); + } else if (object.equals(Driver.class)) { + manager = Context.getDriversManager(); + } else if (object.equals(Calendar.class)) { + manager = Context.getCalendarManager(); + } else if (object.equals(Command.class)) { + manager = Context.getCommandsManager(); + } else if (object.equals(Maintenance.class)) { + manager = Context.getMaintenancesManager(); + } else if (object.equals(Notification.class)) { + manager = Context.getNotificationManager(); + } else { + throw new IllegalArgumentException("Unknown object type"); + } + + if (manager != null && !manager.checkItemPermission(userId, objectId) && !getUserAdmin(userId)) { + checkManager(userId); + for (long managedUserId : usersManager.getManagedItems(userId)) { + if (manager.checkItemPermission(managedUserId, objectId)) { + return; + } + } + throw new SecurityException("Type " + object + " access denied"); + } + } + + public void refreshAllUsersPermissions() { + if (Context.getGeofenceManager() != null) { + Context.getGeofenceManager().refreshUserItems(); + } + Context.getCalendarManager().refreshUserItems(); + Context.getDriversManager().refreshUserItems(); + Context.getAttributesManager().refreshUserItems(); + Context.getCommandsManager().refreshUserItems(); + Context.getMaintenancesManager().refreshUserItems(); + if (Context.getNotificationManager() != null) { + Context.getNotificationManager().refreshUserItems(); + } + } + + public void refreshAllExtendedPermissions() { + if (Context.getGeofenceManager() != null) { + Context.getGeofenceManager().refreshExtendedPermissions(); + } + Context.getDriversManager().refreshExtendedPermissions(); + Context.getAttributesManager().refreshExtendedPermissions(); + Context.getCommandsManager().refreshExtendedPermissions(); + Context.getMaintenancesManager().refreshExtendedPermissions(); + } + + public void refreshPermissions(Permission permission) { + if (permission.getOwnerClass().equals(User.class)) { + if (permission.getPropertyClass().equals(Device.class) + || permission.getPropertyClass().equals(Group.class)) { + refreshDeviceAndGroupPermissions(); + refreshAllExtendedPermissions(); + } else if (permission.getPropertyClass().equals(ManagedUser.class)) { + usersManager.refreshUserItems(); + } else if (permission.getPropertyClass().equals(Geofence.class) && Context.getGeofenceManager() != null) { + Context.getGeofenceManager().refreshUserItems(); + } else if (permission.getPropertyClass().equals(Driver.class)) { + Context.getDriversManager().refreshUserItems(); + } else if (permission.getPropertyClass().equals(Attribute.class)) { + Context.getAttributesManager().refreshUserItems(); + } else if (permission.getPropertyClass().equals(Calendar.class)) { + Context.getCalendarManager().refreshUserItems(); + } else if (permission.getPropertyClass().equals(Command.class)) { + Context.getCommandsManager().refreshUserItems(); + } else if (permission.getPropertyClass().equals(Maintenance.class)) { + Context.getMaintenancesManager().refreshUserItems(); + } else if (permission.getPropertyClass().equals(Notification.class) + && Context.getNotificationManager() != null) { + Context.getNotificationManager().refreshUserItems(); + } + } else if (permission.getOwnerClass().equals(Device.class) || permission.getOwnerClass().equals(Group.class)) { + if (permission.getPropertyClass().equals(Geofence.class) && Context.getGeofenceManager() != null) { + Context.getGeofenceManager().refreshExtendedPermissions(); + } else if (permission.getPropertyClass().equals(Driver.class)) { + Context.getDriversManager().refreshExtendedPermissions(); + } else if (permission.getPropertyClass().equals(Attribute.class)) { + Context.getAttributesManager().refreshExtendedPermissions(); + } else if (permission.getPropertyClass().equals(Command.class)) { + Context.getCommandsManager().refreshExtendedPermissions(); + } else if (permission.getPropertyClass().equals(Maintenance.class)) { + Context.getMaintenancesManager().refreshExtendedPermissions(); + } else if (permission.getPropertyClass().equals(Notification.class) + && Context.getNotificationManager() != null) { + Context.getNotificationManager().refreshExtendedPermissions(); + } + } + } + + public Server getServer() { + return server; + } + + public void updateServer(Server server) throws SQLException { + dataManager.updateObject(server); + this.server = server; + } + + public User login(String email, String password) throws SQLException { + User user = dataManager.login(email, password); + if (user != null) { + checkUserEnabled(user.getId()); + return getUser(user.getId()); + } + return null; + } + + public Object lookupAttribute(long userId, String key, Object defaultValue) { + Object preference; + Object serverPreference = server.getAttributes().get(key); + Object userPreference = getUser(userId).getAttributes().get(key); + if (server.getForceSettings()) { + preference = serverPreference != null ? serverPreference : userPreference; + } else { + preference = userPreference != null ? userPreference : serverPreference; + } + return preference != null ? preference : defaultValue; + } + +} diff --git a/src/main/java/org/traccar/database/QueryBuilder.java b/src/main/java/org/traccar/database/QueryBuilder.java new file mode 100644 index 000000000..5528b2320 --- /dev/null +++ b/src/main/java/org/traccar/database/QueryBuilder.java @@ -0,0 +1,519 @@ +/* + * Copyright 2015 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.model.MiscFormatter; +import org.traccar.model.Permission; + +import javax.sql.DataSource; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public final class QueryBuilder { + + private static final Logger LOGGER = LoggerFactory.getLogger(QueryBuilder.class); + + private final Map<String, List<Integer>> indexMap = new HashMap<>(); + private Connection connection; + private PreparedStatement statement; + private final String query; + private final boolean returnGeneratedKeys; + + private QueryBuilder(DataSource dataSource, String query, boolean returnGeneratedKeys) throws SQLException { + this.query = query; + this.returnGeneratedKeys = returnGeneratedKeys; + if (query != null) { + connection = dataSource.getConnection(); + String parsedQuery = parse(query.trim(), indexMap); + try { + if (returnGeneratedKeys) { + statement = connection.prepareStatement(parsedQuery, Statement.RETURN_GENERATED_KEYS); + } else { + statement = connection.prepareStatement(parsedQuery); + } + } catch (SQLException error) { + connection.close(); + throw error; + } + } + } + + private static String parse(String query, Map<String, List<Integer>> paramMap) { + + int length = query.length(); + StringBuilder parsedQuery = new StringBuilder(length); + boolean inSingleQuote = false; + boolean inDoubleQuote = false; + int index = 1; + + for (int i = 0; i < length; i++) { + + char c = query.charAt(i); + + // String end + if (inSingleQuote) { + if (c == '\'') { + inSingleQuote = false; + } + } else if (inDoubleQuote) { + if (c == '"') { + inDoubleQuote = false; + } + } else { + + // String begin + if (c == '\'') { + inSingleQuote = true; + } else if (c == '"') { + inDoubleQuote = true; + } else if (c == ':' && i + 1 < length + && Character.isJavaIdentifierStart(query.charAt(i + 1))) { + + // Identifier name + int j = i + 2; + while (j < length && Character.isJavaIdentifierPart(query.charAt(j))) { + j++; + } + + String name = query.substring(i + 1, j); + c = '?'; + i += name.length(); + name = name.toLowerCase(); + + // Add to list + List<Integer> indexList = paramMap.get(name); + if (indexList == null) { + indexList = new LinkedList<>(); + paramMap.put(name, indexList); + } + indexList.add(index); + + index++; + } + } + + parsedQuery.append(c); + } + + return parsedQuery.toString(); + } + + public static QueryBuilder create(DataSource dataSource, String query) throws SQLException { + return new QueryBuilder(dataSource, query, false); + } + + public static QueryBuilder create( + DataSource dataSource, String query, boolean returnGeneratedKeys) throws SQLException { + return new QueryBuilder(dataSource, query, returnGeneratedKeys); + } + + private List<Integer> indexes(String name) { + name = name.toLowerCase(); + List<Integer> result = indexMap.get(name); + if (result == null) { + result = new LinkedList<>(); + } + return result; + } + + public QueryBuilder setBoolean(String name, boolean value) throws SQLException { + for (int i : indexes(name)) { + try { + statement.setBoolean(i, value); + } catch (SQLException error) { + statement.close(); + connection.close(); + throw error; + } + } + return this; + } + + public QueryBuilder setInteger(String name, int value) throws SQLException { + for (int i : indexes(name)) { + try { + statement.setInt(i, value); + } catch (SQLException error) { + statement.close(); + connection.close(); + throw error; + } + } + return this; + } + + public QueryBuilder setLong(String name, long value) throws SQLException { + return setLong(name, value, false); + } + + public QueryBuilder setLong(String name, long value, boolean nullIfZero) throws SQLException { + for (int i : indexes(name)) { + try { + if (value == 0 && nullIfZero) { + statement.setNull(i, Types.INTEGER); + } else { + statement.setLong(i, value); + } + } catch (SQLException error) { + statement.close(); + connection.close(); + throw error; + } + } + return this; + } + + public QueryBuilder setDouble(String name, double value) throws SQLException { + for (int i : indexes(name)) { + try { + statement.setDouble(i, value); + } catch (SQLException error) { + statement.close(); + connection.close(); + throw error; + } + } + return this; + } + + public QueryBuilder setString(String name, String value) throws SQLException { + for (int i : indexes(name)) { + try { + if (value == null) { + statement.setNull(i, Types.VARCHAR); + } else { + statement.setString(i, value); + } + } catch (SQLException error) { + statement.close(); + connection.close(); + throw error; + } + } + return this; + } + + public QueryBuilder setDate(String name, Date value) throws SQLException { + for (int i : indexes(name)) { + try { + if (value == null) { + statement.setNull(i, Types.TIMESTAMP); + } else { + statement.setTimestamp(i, new Timestamp(value.getTime())); + } + } catch (SQLException error) { + statement.close(); + connection.close(); + throw error; + } + } + return this; + } + + public QueryBuilder setBlob(String name, byte[] value) throws SQLException { + for (int i : indexes(name)) { + try { + if (value == null) { + statement.setNull(i, Types.BLOB); + } else { + statement.setBytes(i, value); + } + } catch (SQLException error) { + statement.close(); + connection.close(); + throw error; + } + } + return this; + } + + public QueryBuilder setObject(Object object) throws SQLException { + + Method[] methods = object.getClass().getMethods(); + + for (Method method : methods) { + if (method.getName().startsWith("get") && method.getParameterTypes().length == 0 + && !method.isAnnotationPresent(QueryIgnore.class)) { + String name = method.getName().substring(3); + try { + if (method.getReturnType().equals(boolean.class)) { + setBoolean(name, (Boolean) method.invoke(object)); + } else if (method.getReturnType().equals(int.class)) { + setInteger(name, (Integer) method.invoke(object)); + } else if (method.getReturnType().equals(long.class)) { + setLong(name, (Long) method.invoke(object), name.endsWith("Id")); + } else if (method.getReturnType().equals(double.class)) { + setDouble(name, (Double) method.invoke(object)); + } else if (method.getReturnType().equals(String.class)) { + setString(name, (String) method.invoke(object)); + } else if (method.getReturnType().equals(Date.class)) { + setDate(name, (Date) method.invoke(object)); + } else if (method.getReturnType().equals(byte[].class)) { + setBlob(name, (byte[]) method.invoke(object)); + } else { + if (method.getReturnType().equals(Map.class) + && Context.getConfig().getBoolean("database.xml")) { + setString(name, MiscFormatter.toXmlString((Map) method.invoke(object))); + } else { + setString(name, Context.getObjectMapper().writeValueAsString(method.invoke(object))); + } + } + } catch (IllegalAccessException | InvocationTargetException | JsonProcessingException error) { + LOGGER.warn("Get property error", error); + } + } + } + + return this; + } + + private interface ResultSetProcessor<T> { + void process(T object, ResultSet resultSet) throws SQLException; + } + + public <T> T executeQuerySingle(Class<T> clazz) throws SQLException { + Collection<T> result = executeQuery(clazz); + if (!result.isEmpty()) { + return result.iterator().next(); + } else { + return null; + } + } + + private <T> void addProcessors( + List<ResultSetProcessor<T>> processors, + final Class<?> parameterType, final Method method, final String name) { + + if (parameterType.equals(boolean.class)) { + processors.add(new ResultSetProcessor<T>() { + @Override + public void process(T object, ResultSet resultSet) throws SQLException { + try { + method.invoke(object, resultSet.getBoolean(name)); + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); + } + } + }); + } else if (parameterType.equals(int.class)) { + processors.add(new ResultSetProcessor<T>() { + @Override + public void process(T object, ResultSet resultSet) throws SQLException { + try { + method.invoke(object, resultSet.getInt(name)); + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); + } + } + }); + } else if (parameterType.equals(long.class)) { + processors.add(new ResultSetProcessor<T>() { + @Override + public void process(T object, ResultSet resultSet) throws SQLException { + try { + method.invoke(object, resultSet.getLong(name)); + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); + } + } + }); + } else if (parameterType.equals(double.class)) { + processors.add(new ResultSetProcessor<T>() { + @Override + public void process(T object, ResultSet resultSet) throws SQLException { + try { + method.invoke(object, resultSet.getDouble(name)); + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); + } + } + }); + } else if (parameterType.equals(String.class)) { + processors.add(new ResultSetProcessor<T>() { + @Override + public void process(T object, ResultSet resultSet) throws SQLException { + try { + method.invoke(object, resultSet.getString(name)); + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); + } + } + }); + } else if (parameterType.equals(Date.class)) { + processors.add(new ResultSetProcessor<T>() { + @Override + public void process(T object, ResultSet resultSet) throws SQLException { + try { + Timestamp timestamp = resultSet.getTimestamp(name); + if (timestamp != null) { + method.invoke(object, new Date(timestamp.getTime())); + } + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); + } + } + }); + } else if (parameterType.equals(byte[].class)) { + processors.add(new ResultSetProcessor<T>() { + @Override + public void process(T object, ResultSet resultSet) throws SQLException { + try { + method.invoke(object, resultSet.getBytes(name)); + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Set property error", error); + } + } + }); + } else { + processors.add(new ResultSetProcessor<T>() { + @Override + public void process(T object, ResultSet resultSet) throws SQLException { + String value = resultSet.getString(name); + if (value != null && !value.isEmpty()) { + try { + method.invoke(object, Context.getObjectMapper().readValue(value, parameterType)); + } catch (InvocationTargetException | IllegalAccessException | IOException error) { + LOGGER.warn("Set property error", error); + } + } + } + }); + } + } + + public <T> Collection<T> executeQuery(Class<T> clazz) throws SQLException { + List<T> result = new LinkedList<>(); + + if (query != null) { + + try { + + try (ResultSet resultSet = statement.executeQuery()) { + + ResultSetMetaData resultMetaData = resultSet.getMetaData(); + + List<ResultSetProcessor<T>> processors = new LinkedList<>(); + + Method[] methods = clazz.getMethods(); + + for (final Method method : methods) { + if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 + && !method.isAnnotationPresent(QueryIgnore.class)) { + + final String name = method.getName().substring(3); + + // Check if column exists + boolean column = false; + for (int i = 1; i <= resultMetaData.getColumnCount(); i++) { + if (name.equalsIgnoreCase(resultMetaData.getColumnLabel(i))) { + column = true; + break; + } + } + if (!column) { + continue; + } + + addProcessors(processors, method.getParameterTypes()[0], method, name); + } + } + + while (resultSet.next()) { + try { + T object = clazz.newInstance(); + for (ResultSetProcessor<T> processor : processors) { + processor.process(object, resultSet); + } + result.add(object); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalArgumentException(); + } + } + } + + } finally { + statement.close(); + connection.close(); + } + } + + return result; + } + + public long executeUpdate() throws SQLException { + + if (query != null) { + try { + statement.execute(); + if (returnGeneratedKeys) { + ResultSet resultSet = statement.getGeneratedKeys(); + if (resultSet.next()) { + return resultSet.getLong(1); + } + } + } finally { + statement.close(); + connection.close(); + } + } + return 0; + } + + public Collection<Permission> executePermissionsQuery() throws SQLException, ClassNotFoundException { + List<Permission> result = new LinkedList<>(); + if (query != null) { + try { + try (ResultSet resultSet = statement.executeQuery()) { + ResultSetMetaData resultMetaData = resultSet.getMetaData(); + while (resultSet.next()) { + LinkedHashMap<String, Long> map = new LinkedHashMap<>(); + for (int i = 1; i <= resultMetaData.getColumnCount(); i++) { + String label = resultMetaData.getColumnLabel(i); + map.put(label, resultSet.getLong(label)); + } + result.add(new Permission(map)); + } + } + } finally { + statement.close(); + connection.close(); + } + } + + return result; + } + +} diff --git a/src/main/java/org/traccar/database/QueryExtended.java b/src/main/java/org/traccar/database/QueryExtended.java new file mode 100644 index 000000000..07bc2c211 --- /dev/null +++ b/src/main/java/org/traccar/database/QueryExtended.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface QueryExtended { +} diff --git a/src/main/java/org/traccar/database/QueryIgnore.java b/src/main/java/org/traccar/database/QueryIgnore.java new file mode 100644 index 000000000..ac835cf2f --- /dev/null +++ b/src/main/java/org/traccar/database/QueryIgnore.java @@ -0,0 +1,26 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface QueryIgnore { +} diff --git a/src/main/java/org/traccar/database/SimpleObjectManager.java b/src/main/java/org/traccar/database/SimpleObjectManager.java new file mode 100644 index 000000000..15dda4520 --- /dev/null +++ b/src/main/java/org/traccar/database/SimpleObjectManager.java @@ -0,0 +1,94 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.model.BaseModel; +import org.traccar.model.Permission; +import org.traccar.model.User; + +public abstract class SimpleObjectManager<T extends BaseModel> extends BaseObjectManager<T> + implements ManagableObjects { + + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleObjectManager.class); + + private Map<Long, Set<Long>> userItems; + + protected SimpleObjectManager(DataManager dataManager, Class<T> baseClass) { + super(dataManager, baseClass); + } + + @Override + public final Set<Long> getUserItems(long userId) { + if (!userItems.containsKey(userId)) { + userItems.put(userId, new HashSet<Long>()); + } + return userItems.get(userId); + } + + @Override + public Set<Long> getManagedItems(long userId) { + Set<Long> result = new HashSet<>(); + result.addAll(getUserItems(userId)); + for (long managedUserId : Context.getUsersManager().getUserItems(userId)) { + result.addAll(getUserItems(managedUserId)); + } + return result; + } + + public final boolean checkItemPermission(long userId, long itemId) { + return getUserItems(userId).contains(itemId); + } + + @Override + public void refreshItems() { + super.refreshItems(); + refreshUserItems(); + } + + public final void refreshUserItems() { + if (getDataManager() != null) { + try { + if (userItems != null) { + userItems.clear(); + } else { + userItems = new ConcurrentHashMap<>(); + } + for (Permission permission : getDataManager().getPermissions(User.class, getBaseClass())) { + getUserItems(permission.getOwnerId()).add(permission.getPropertyId()); + } + } catch (SQLException | ClassNotFoundException error) { + LOGGER.warn("Error getting permissions", error); + } + } + } + + @Override + public void removeItem(long itemId) throws SQLException { + super.removeItem(itemId); + refreshUserItems(); + } + +} diff --git a/src/main/java/org/traccar/database/StatisticsManager.java b/src/main/java/org/traccar/database/StatisticsManager.java new file mode 100644 index 000000000..e59f8e767 --- /dev/null +++ b/src/main/java/org/traccar/database/StatisticsManager.java @@ -0,0 +1,160 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.DateUtil; +import org.traccar.model.Statistics; + +import javax.inject.Inject; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Form; +import java.sql.SQLException; +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +public class StatisticsManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(StatisticsManager.class); + + private static final int SPLIT_MODE = Calendar.DAY_OF_MONTH; + + private final Config config; + private final DataManager dataManager; + private final Client client; + + private AtomicInteger lastUpdate = new AtomicInteger(Calendar.getInstance().get(SPLIT_MODE)); + + private Set<Long> users = new HashSet<>(); + private Set<Long> devices = new HashSet<>(); + + private int requests; + private int messagesReceived; + private int messagesStored; + private int mailSent; + private int smsSent; + private int geocoderRequests; + private int geolocationRequests; + + @Inject + public StatisticsManager(Config config, DataManager dataManager, Client client) { + this.config = config; + this.dataManager = dataManager; + this.client = client; + } + + private void checkSplit() { + int currentUpdate = Calendar.getInstance().get(SPLIT_MODE); + if (lastUpdate.getAndSet(currentUpdate) != currentUpdate) { + Statistics statistics = new Statistics(); + statistics.setCaptureTime(new Date()); + statistics.setActiveUsers(users.size()); + statistics.setActiveDevices(devices.size()); + statistics.setRequests(requests); + statistics.setMessagesReceived(messagesReceived); + statistics.setMessagesStored(messagesStored); + statistics.setMailSent(mailSent); + statistics.setSmsSent(smsSent); + statistics.setGeocoderRequests(geocoderRequests); + statistics.setGeolocationRequests(geolocationRequests); + + try { + dataManager.addObject(statistics); + } catch (SQLException e) { + LOGGER.warn("Error saving statistics", e); + } + + String url = config.getString(Keys.SERVER_STATISTICS); + if (url != null) { + String time = DateUtil.formatDate(statistics.getCaptureTime()); + + Form form = new Form(); + form.param("version", getClass().getPackage().getImplementationVersion()); + form.param("captureTime", time); + form.param("activeUsers", String.valueOf(statistics.getActiveUsers())); + form.param("activeDevices", String.valueOf(statistics.getActiveDevices())); + form.param("requests", String.valueOf(statistics.getRequests())); + form.param("messagesReceived", String.valueOf(statistics.getMessagesReceived())); + form.param("messagesStored", String.valueOf(statistics.getMessagesStored())); + form.param("mailSent", String.valueOf(statistics.getMailSent())); + form.param("smsSent", String.valueOf(statistics.getSmsSent())); + form.param("geocoderRequests", String.valueOf(statistics.getGeocoderRequests())); + form.param("geolocationRequests", String.valueOf(statistics.getGeolocationRequests())); + + client.target(url).request().async().post(Entity.form(form)); + } + + users.clear(); + devices.clear(); + requests = 0; + messagesReceived = 0; + messagesStored = 0; + mailSent = 0; + smsSent = 0; + geocoderRequests = 0; + geolocationRequests = 0; + } + } + + public synchronized void registerRequest(long userId) { + checkSplit(); + requests += 1; + if (userId != 0) { + users.add(userId); + } + } + + public synchronized void registerMessageReceived() { + checkSplit(); + messagesReceived += 1; + } + + public synchronized void registerMessageStored(long deviceId) { + checkSplit(); + messagesStored += 1; + if (deviceId != 0) { + devices.add(deviceId); + } + } + + public synchronized void registerMail() { + checkSplit(); + mailSent += 1; + } + + public synchronized void registerSms() { + checkSplit(); + smsSent += 1; + } + + public synchronized void registerGeocoderRequest() { + checkSplit(); + geocoderRequests += 1; + } + + public synchronized void registerGeolocationRequest() { + checkSplit(); + geolocationRequests += 1; + } + +} diff --git a/src/main/java/org/traccar/database/UsersManager.java b/src/main/java/org/traccar/database/UsersManager.java new file mode 100644 index 000000000..576a9e6c7 --- /dev/null +++ b/src/main/java/org/traccar/database/UsersManager.java @@ -0,0 +1,86 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.database; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.traccar.model.User; + +public class UsersManager extends SimpleObjectManager<User> { + + private Map<String, User> usersTokens; + + public UsersManager(DataManager dataManager) { + super(dataManager, User.class); + if (usersTokens == null) { + usersTokens = new ConcurrentHashMap<>(); + } + } + + private void putToken(User user) { + if (usersTokens == null) { + usersTokens = new ConcurrentHashMap<>(); + } + if (user.getToken() != null) { + usersTokens.put(user.getToken(), user); + } + } + + @Override + protected void addNewItem(User user) { + super.addNewItem(user); + putToken(user); + } + + @Override + protected void updateCachedItem(User user) { + User cachedUser = getById(user.getId()); + super.updateCachedItem(user); + putToken(user); + if (cachedUser.getToken() != null && !cachedUser.getToken().equals(user.getToken())) { + usersTokens.remove(cachedUser.getToken()); + } + } + + @Override + protected void removeCachedItem(long userId) { + User cachedUser = getById(userId); + if (cachedUser != null) { + String userToken = cachedUser.getToken(); + super.removeCachedItem(userId); + if (userToken != null) { + usersTokens.remove(userToken); + } + } + } + + @Override + public Set<Long> getManagedItems(long userId) { + Set<Long> result = new HashSet<>(); + result.addAll(getUserItems(userId)); + result.add(userId); + return result; + } + + public User getUserByToken(String token) { + return usersTokens.get(token); + } + +} diff --git a/src/main/java/org/traccar/geocoder/Address.java b/src/main/java/org/traccar/geocoder/Address.java new file mode 100644 index 000000000..fe39da8e1 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/Address.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +public class Address { + + private String postcode; + + public String getPostcode() { + return postcode; + } + + public void setPostcode(String postcode) { + this.postcode = postcode; + } + + private String country; + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + private String state; + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + private String district; + + public String getDistrict() { + return district; + } + + public void setDistrict(String district) { + this.district = district; + } + + private String settlement; + + public String getSettlement() { + return settlement; + } + + public void setSettlement(String settlement) { + this.settlement = settlement; + } + + private String suburb; + + public String getSuburb() { + return suburb; + } + + public void setSuburb(String suburb) { + this.suburb = suburb; + } + + private String street; + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + private String house; + + public String getHouse() { + return house; + } + + public void setHouse(String house) { + this.house = house; + } + + private String formattedAddress; + + public String getFormattedAddress() { + return formattedAddress; + } + + public void setFormattedAddress(String formattedAddress) { + this.formattedAddress = formattedAddress; + } + +} diff --git a/src/main/java/org/traccar/geocoder/AddressFormat.java b/src/main/java/org/traccar/geocoder/AddressFormat.java new file mode 100644 index 000000000..ad19432b9 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/AddressFormat.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParsePosition; + +/** + * Available parameters: + * + * %p - postcode + * %c - country + * %s - state + * %d - district + * %t - settlement (town) + * %u - suburb + * %r - street (road) + * %h - house + * %f - formatted address + * + */ +public class AddressFormat extends Format { + + private final String format; + + public AddressFormat() { + this("%h %r, %t, %s, %c"); + } + + public AddressFormat(String format) { + this.format = format; + } + + private static String replace(String s, String key, String value) { + if (value != null) { + s = s.replace(key, value); + } else { + s = s.replaceAll("[, ]*" + key, ""); + } + return s; + } + + @Override + public StringBuffer format(Object o, StringBuffer stringBuffer, FieldPosition fieldPosition) { + Address address = (Address) o; + String result = format; + + result = replace(result, "%p", address.getPostcode()); + result = replace(result, "%c", address.getCountry()); + result = replace(result, "%s", address.getState()); + result = replace(result, "%d", address.getDistrict()); + result = replace(result, "%t", address.getSettlement()); + result = replace(result, "%u", address.getSuburb()); + result = replace(result, "%r", address.getStreet()); + result = replace(result, "%h", address.getHouse()); + result = replace(result, "%f", address.getFormattedAddress()); + + result = result.replaceAll("^[, ]*", ""); + + return stringBuffer.append(result); + } + + @Override + public Address parseObject(String s, ParsePosition parsePosition) { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/org/traccar/geocoder/BanGeocoder.java b/src/main/java/org/traccar/geocoder/BanGeocoder.java new file mode 100644 index 000000000..b1f0900a4 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/BanGeocoder.java @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Olivier Girondel (olivier@biniou.info) + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +/* + * API documentation: https://adresse.data.gouv.fr/api + */ + +import javax.json.JsonArray; +import javax.json.JsonObject; + +public class BanGeocoder extends JsonGeocoder { + + public BanGeocoder(int cacheSize, AddressFormat addressFormat) { + super("https://api-adresse.data.gouv.fr/reverse/?lat=%f&lon=%f", cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + JsonArray result = json.getJsonArray("features"); + + if (result != null && !result.isEmpty()) { + JsonObject location = result.getJsonObject(0).getJsonObject("properties"); + Address address = new Address(); + + address.setCountry("FR"); + if (location.containsKey("postcode")) { + address.setPostcode(location.getString("postcode")); + } + if (location.containsKey("context")) { + address.setDistrict(location.getString("context")); + } + if (location.containsKey("name")) { + address.setStreet(location.getString("name")); + } + if (location.containsKey("city")) { + address.setSettlement(location.getString("city")); + } + if (location.containsKey("housenumber")) { + address.setHouse(location.getString("housenumber")); + } + if (location.containsKey("label")) { + address.setFormattedAddress(location.getString("label")); + } + + return address; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/geocoder/BingMapsGeocoder.java b/src/main/java/org/traccar/geocoder/BingMapsGeocoder.java new file mode 100644 index 000000000..32a26ee0c --- /dev/null +++ b/src/main/java/org/traccar/geocoder/BingMapsGeocoder.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014 - 2015 Stefaan Van Dooren (stefaan.vandooren@gmail.com) + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.json.JsonArray; +import javax.json.JsonObject; + +public class BingMapsGeocoder extends JsonGeocoder { + + public BingMapsGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { + super(url + "/Locations/%f,%f?key=" + key + "&include=ciso2", cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + JsonArray result = json.getJsonArray("resourceSets"); + if (result != null) { + JsonObject location = + result.getJsonObject(0).getJsonArray("resources").getJsonObject(0).getJsonObject("address"); + if (location != null) { + Address address = new Address(); + if (location.containsKey("addressLine")) { + address.setStreet(location.getString("addressLine")); + } + if (location.containsKey("locality")) { + address.setSettlement(location.getString("locality")); + } + if (location.containsKey("adminDistrict2")) { + address.setDistrict(location.getString("adminDistrict2")); + } + if (location.containsKey("adminDistrict")) { + address.setState(location.getString("adminDistrict")); + } + if (location.containsKey("countryRegionIso2")) { + address.setCountry(location.getString("countryRegionIso2").toUpperCase()); + } + if (location.containsKey("postalCode")) { + address.setPostcode(location.getString("postalCode")); + } + if (location.containsKey("formattedAddress")) { + address.setFormattedAddress(location.getString("formattedAddress")); + } + return address; + } + } + return null; + } + +} diff --git a/src/main/java/org/traccar/geocoder/FactualGeocoder.java b/src/main/java/org/traccar/geocoder/FactualGeocoder.java new file mode 100644 index 000000000..c7a68c293 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/FactualGeocoder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2014 - 2015 Stefaan Van Dooren (stefaan.vandooren@gmail.com) + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.json.JsonObject; + +public class FactualGeocoder extends JsonGeocoder { + + public FactualGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { + super(url + "?latitude=%f&longitude=%f&KEY=" + key, cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + JsonObject result = json.getJsonObject("response").getJsonObject("data"); + if (result != null) { + Address address = new Address(); + if (result.getJsonObject("street_number") != null) { + address.setHouse(result.getJsonObject("street_number").getString("name")); + } + if (result.getJsonObject("street_name") != null) { + address.setStreet(result.getJsonObject("street_name").getString("name")); + } + if (result.getJsonObject("locality") != null) { + address.setSettlement(result.getJsonObject("locality").getString("name")); + } + if (result.getJsonObject("county") != null) { + address.setDistrict(result.getJsonObject("county").getString("name")); + } + if (result.getJsonObject("region") != null) { + address.setState(result.getJsonObject("region").getString("name")); + } + if (result.getJsonObject("country") != null) { + address.setCountry(result.getJsonObject("country").getString("name")); + } + if (result.getJsonObject("postcode") != null) { + address.setPostcode(result.getJsonObject("postcode").getString("name")); + } + return address; + } + return null; + } + +} diff --git a/src/main/java/org/traccar/geocoder/GeocodeFarmGeocoder.java b/src/main/java/org/traccar/geocoder/GeocodeFarmGeocoder.java new file mode 100644 index 000000000..39a3300a0 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/GeocodeFarmGeocoder.java @@ -0,0 +1,70 @@ +/* + * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.json.JsonObject; + +public class GeocodeFarmGeocoder extends JsonGeocoder { + + private static String formatUrl(String key, String language) { + String url = "https://www.geocode.farm/v3/json/reverse/"; + url += "?lat=%f&lon=%f&country=us&count=1"; + if (key != null) { + url += "&key=" + key; + } + if (language != null) { + url += "&lang=" + language; + } + return url; + } + public GeocodeFarmGeocoder(String key, String language, int cacheSize, AddressFormat addressFormat) { + super(formatUrl(key, language), cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + Address address = new Address(); + + JsonObject result = json + .getJsonObject("geocoding_results") + .getJsonArray("RESULTS") + .getJsonObject(0); + + JsonObject resultAddress = result.getJsonObject("ADDRESS"); + + if (result.containsKey("formatted_address")) { + address.setFormattedAddress(result.getString("formatted_address")); + } + if (resultAddress.containsKey("street_number")) { + address.setStreet(resultAddress.getString("street_number")); + } + if (resultAddress.containsKey("street_name")) { + address.setStreet(resultAddress.getString("street_name")); + } + if (resultAddress.containsKey("locality")) { + address.setSettlement(resultAddress.getString("locality")); + } + if (resultAddress.containsKey("admin_1")) { + address.setState(resultAddress.getString("admin_1")); + } + if (resultAddress.containsKey("country")) { + address.setCountry(resultAddress.getString("country")); + } + + return address; + } + +} diff --git a/src/main/java/org/traccar/geocoder/GeocodeXyzGeocoder.java b/src/main/java/org/traccar/geocoder/GeocodeXyzGeocoder.java new file mode 100644 index 000000000..aca360c3d --- /dev/null +++ b/src/main/java/org/traccar/geocoder/GeocodeXyzGeocoder.java @@ -0,0 +1,60 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.json.JsonObject; + +public class GeocodeXyzGeocoder extends JsonGeocoder { + + private static String formatUrl(String key) { + String url = "https://geocode.xyz/%f,%f?geoit=JSON"; + if (key != null) { + url += "&key=" + key; + } + return url; + } + + public GeocodeXyzGeocoder(String key, int cacheSize, AddressFormat addressFormat) { + super(formatUrl(key), cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + Address address = new Address(); + + if (json.containsKey("stnumber")) { + address.setHouse(json.getString("stnumber")); + } + if (json.containsKey("staddress")) { + address.setStreet(json.getString("staddress")); + } + if (json.containsKey("city")) { + address.setSettlement(json.getString("city")); + } + if (json.containsKey("region")) { + address.setState(json.getString("region")); + } + if (json.containsKey("prov")) { + address.setCountry(json.getString("prov")); + } + if (json.containsKey("postal")) { + address.setPostcode(json.getString("postal")); + } + + return address; + } + +} diff --git a/src/main/java/org/traccar/geocoder/Geocoder.java b/src/main/java/org/traccar/geocoder/Geocoder.java new file mode 100644 index 000000000..587a27520 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/Geocoder.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +public interface Geocoder { + + interface ReverseGeocoderCallback { + + void onSuccess(String address); + + void onFailure(Throwable e); + + } + + String getAddress(double latitude, double longitude, ReverseGeocoderCallback callback); + +} diff --git a/src/main/java/org/traccar/geocoder/GeocoderException.java b/src/main/java/org/traccar/geocoder/GeocoderException.java new file mode 100644 index 000000000..608916641 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/GeocoderException.java @@ -0,0 +1,24 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +public class GeocoderException extends RuntimeException { + + public GeocoderException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java b/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java new file mode 100644 index 000000000..3a173f985 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/GisgraphyGeocoder.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.json.JsonObject; + +public class GisgraphyGeocoder extends JsonGeocoder { + + public GisgraphyGeocoder(AddressFormat addressFormat) { + this("http://services.gisgraphy.com/reversegeocoding/search", 0, addressFormat); + } + + public GisgraphyGeocoder(String url, int cacheSize, AddressFormat addressFormat) { + super(url + "?format=json&lat=%f&lng=%f&from=1&to=1", cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + Address address = new Address(); + + JsonObject result = json.getJsonArray("result").getJsonObject(0); + + if (result.containsKey("streetName")) { + address.setStreet(result.getString("streetName")); + } + if (result.containsKey("city")) { + address.setSettlement(result.getString("city")); + } + if (result.containsKey("state")) { + address.setState(result.getString("state")); + } + if (result.containsKey("countryCode")) { + address.setCountry(result.getString("countryCode")); + } + if (result.containsKey("formatedFull")) { + address.setFormattedAddress(result.getString("formatedFull")); + } + + return address; + } + +} diff --git a/src/main/java/org/traccar/geocoder/GoogleGeocoder.java b/src/main/java/org/traccar/geocoder/GoogleGeocoder.java new file mode 100644 index 000000000..9494cab45 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/GoogleGeocoder.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonString; + +public class GoogleGeocoder extends JsonGeocoder { + + private static String formatUrl(String key, String language) { + String url = "https://maps.googleapis.com/maps/api/geocode/json?latlng=%f,%f"; + if (key != null) { + url += "&key=" + key; + } + if (language != null) { + url += "&language=" + language; + } + return url; + } + + public GoogleGeocoder(String key, String language, int cacheSize, AddressFormat addressFormat) { + super(formatUrl(key, language), cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + JsonArray results = json.getJsonArray("results"); + + if (!results.isEmpty()) { + Address address = new Address(); + + JsonObject result = (JsonObject) results.get(0); + JsonArray components = result.getJsonArray("address_components"); + + if (result.containsKey("formatted_address")) { + address.setFormattedAddress(result.getString("formatted_address")); + } + + for (JsonObject component : components.getValuesAs(JsonObject.class)) { + + String value = component.getString("short_name"); + + typesLoop: for (JsonString type : component.getJsonArray("types").getValuesAs(JsonString.class)) { + + switch (type.getString()) { + case "street_number": + address.setHouse(value); + break typesLoop; + case "route": + address.setStreet(value); + break typesLoop; + case "locality": + address.setSettlement(value); + break typesLoop; + case "administrative_area_level_2": + address.setDistrict(value); + break typesLoop; + case "administrative_area_level_1": + address.setState(value); + break typesLoop; + case "country": + address.setCountry(value); + break typesLoop; + case "postal_code": + address.setPostcode(value); + break typesLoop; + default: + break; + } + } + } + + return address; + } + + return null; + } + + @Override + protected String parseError(JsonObject json) { + return json.getString("error_message"); + } + +} diff --git a/src/main/java/org/traccar/geocoder/HereGeocoder.java b/src/main/java/org/traccar/geocoder/HereGeocoder.java new file mode 100644 index 000000000..aaf11d74d --- /dev/null +++ b/src/main/java/org/traccar/geocoder/HereGeocoder.java @@ -0,0 +1,88 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.json.JsonObject; + +public class HereGeocoder extends JsonGeocoder { + + private static String formatUrl(String url, String id, String key, String language) { + if (url == null) { + url = "https://reverse.geocoder.ls.hereapi.com/6.2/reversegeocode.json"; + } + url += "?mode=retrieveAddresses&maxresults=1"; + url += "&prox=%f,%f,0"; + url += "&app_id=" + id; + url += "&app_code=" + key; + url += "&apiKey=" + key; + if (language != null) { + url += "&language=" + language; + } + return url; + } + + public HereGeocoder( + String url, String id, String key, String language, int cacheSize, AddressFormat addressFormat) { + super(formatUrl(url, id, key, language), cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + JsonObject result = json + .getJsonObject("Response") + .getJsonArray("View") + .getJsonObject(0) + .getJsonArray("Result") + .getJsonObject(0) + .getJsonObject("Location") + .getJsonObject("Address"); + + if (result != null) { + Address address = new Address(); + + if (json.containsKey("Label")) { + address.setFormattedAddress(json.getString("Label")); + } + + if (result.containsKey("HouseNumber")) { + address.setHouse(result.getString("HouseNumber")); + } + if (result.containsKey("Street")) { + address.setStreet(result.getString("Street")); + } + if (result.containsKey("City")) { + address.setSettlement(result.getString("City")); + } + if (result.containsKey("District")) { + address.setDistrict(result.getString("District")); + } + if (result.containsKey("State")) { + address.setState(result.getString("State")); + } + if (result.containsKey("Country")) { + address.setCountry(result.getString("Country").toUpperCase()); + } + if (result.containsKey("PostalCode")) { + address.setPostcode(result.getString("PostalCode")); + } + + return address; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/geocoder/JsonGeocoder.java b/src/main/java/org/traccar/geocoder/JsonGeocoder.java new file mode 100644 index 000000000..ed59a1d8d --- /dev/null +++ b/src/main/java/org/traccar/geocoder/JsonGeocoder.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; + +import javax.json.JsonObject; +import javax.ws.rs.ClientErrorException; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.InvocationCallback; +import java.util.AbstractMap; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public abstract class JsonGeocoder implements Geocoder { + + private static final Logger LOGGER = LoggerFactory.getLogger(JsonGeocoder.class); + + private final String url; + private final AddressFormat addressFormat; + + private Map<Map.Entry<Double, Double>, String> cache; + + public JsonGeocoder(String url, final int cacheSize, AddressFormat addressFormat) { + this.url = url; + this.addressFormat = addressFormat; + if (cacheSize > 0) { + this.cache = Collections.synchronizedMap(new LinkedHashMap<Map.Entry<Double, Double>, String>() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > cacheSize; + } + }); + } + } + + private String handleResponse( + double latitude, double longitude, JsonObject json, ReverseGeocoderCallback callback) { + + Address address = parseAddress(json); + if (address != null) { + String formattedAddress = addressFormat.format(address); + if (cache != null) { + cache.put(new AbstractMap.SimpleImmutableEntry<>(latitude, longitude), formattedAddress); + } + if (callback != null) { + callback.onSuccess(formattedAddress); + } + return formattedAddress; + } else { + String msg = "Empty address. Error: " + parseError(json); + if (callback != null) { + callback.onFailure(new GeocoderException(msg)); + } else { + LOGGER.warn(msg); + } + } + return null; + } + + @Override + public String getAddress( + final double latitude, final double longitude, final ReverseGeocoderCallback callback) { + + if (cache != null) { + String cachedAddress = cache.get(new AbstractMap.SimpleImmutableEntry<>(latitude, longitude)); + if (cachedAddress != null) { + if (callback != null) { + callback.onSuccess(cachedAddress); + } + return cachedAddress; + } + } + + Invocation.Builder request = Context.getClient().target(String.format(url, latitude, longitude)).request(); + + if (callback != null) { + request.async().get(new InvocationCallback<JsonObject>() { + @Override + public void completed(JsonObject json) { + handleResponse(latitude, longitude, json, callback); + } + + @Override + public void failed(Throwable throwable) { + callback.onFailure(throwable); + } + }); + } else { + try { + return handleResponse(latitude, longitude, request.get(JsonObject.class), null); + } catch (ClientErrorException e) { + LOGGER.warn("Geocoder network error", e); + } + } + return null; + } + + public abstract Address parseAddress(JsonObject json); + + protected String parseError(JsonObject json) { + return null; + } + +} diff --git a/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java b/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java new file mode 100644 index 000000000..4029e3f07 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/MapQuestGeocoder.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014 - 2015 Stefaan Van Dooren (stefaan.vandooren@gmail.com) + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.json.JsonArray; +import javax.json.JsonObject; + +public class MapQuestGeocoder extends JsonGeocoder { + + public MapQuestGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { + super(url + "?key=" + key + "&location=%f,%f", cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + JsonArray result = json.getJsonArray("results"); + if (result != null) { + JsonArray locations = result.getJsonObject(0).getJsonArray("locations"); + if (locations != null) { + JsonObject location = locations.getJsonObject(0); + + Address address = new Address(); + + if (location.containsKey("street")) { + address.setStreet(location.getString("street")); + } + if (location.containsKey("adminArea5")) { + address.setSettlement(location.getString("adminArea5")); + } + if (location.containsKey("adminArea4")) { + address.setDistrict(location.getString("adminArea4")); + } + if (location.containsKey("adminArea3")) { + address.setState(location.getString("adminArea3")); + } + if (location.containsKey("adminArea1")) { + address.setCountry(location.getString("adminArea1").toUpperCase()); + } + if (location.containsKey("postalCode")) { + address.setPostcode(location.getString("postalCode")); + } + + return address; + } + } + return null; + } + +} diff --git a/src/main/java/org/traccar/geocoder/MapmyIndiaGeocoder.java b/src/main/java/org/traccar/geocoder/MapmyIndiaGeocoder.java new file mode 100644 index 000000000..2b70708a1 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/MapmyIndiaGeocoder.java @@ -0,0 +1,82 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.json.JsonArray; +import javax.json.JsonObject; + +public class MapmyIndiaGeocoder extends JsonGeocoder { + + public MapmyIndiaGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { + super(url + "/" + key + "/rev_geocode?lat=%f&lng=%f", cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + JsonArray results = json.getJsonArray("results"); + + if (!results.isEmpty()) { + Address address = new Address(); + + JsonObject result = (JsonObject) results.get(0); + + if (result.containsKey("formatted_address")) { + address.setFormattedAddress(result.getString("formatted_address")); + } + + if (result.containsKey("house_number") && !result.getString("house_number").isEmpty()) { + address.setHouse(result.getString("house_number")); + } else if (result.containsKey("house_name") && !result.getString("house_name").isEmpty()) { + address.setHouse(result.getString("house_name")); + } + + if (result.containsKey("street")) { + address.setStreet(result.getString("street")); + } + + if (result.containsKey("locality") && !result.getString("locality").isEmpty()) { + address.setSuburb(result.getString("locality")); + } else if (result.containsKey("sublocality") && !result.getString("sublocality").isEmpty()) { + address.setSuburb(result.getString("sublocality")); + } else if (result.containsKey("subsublocality") && !result.getString("subsublocality").isEmpty()) { + address.setSuburb(result.getString("subsublocality")); + } + + if (result.containsKey("city") && !result.getString("city").isEmpty()) { + address.setSettlement(result.getString("city")); + } else if (result.containsKey("village") && !result.getString("village").isEmpty()) { + address.setSettlement(result.getString("village")); + } + + if (result.containsKey("district")) { + address.setDistrict(result.getString("district")); + } else if (result.containsKey("subDistrict")) { + address.setDistrict(result.getString("subDistrict")); + } + + if (result.containsKey("state")) { + address.setState(result.getString("state")); + } + + if (result.containsKey("pincode")) { + address.setPostcode(result.getString("pincode")); + } + + return address; + } + return null; + } +} diff --git a/src/main/java/org/traccar/geocoder/NominatimGeocoder.java b/src/main/java/org/traccar/geocoder/NominatimGeocoder.java new file mode 100644 index 000000000..8db25bf15 --- /dev/null +++ b/src/main/java/org/traccar/geocoder/NominatimGeocoder.java @@ -0,0 +1,91 @@ +/* + * Copyright 2014 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.json.JsonObject; + +public class NominatimGeocoder extends JsonGeocoder { + + private static String formatUrl(String url, String key, String language) { + if (url == null) { + url = "https://nominatim.openstreetmap.org/reverse"; + } + url += "?format=json&lat=%f&lon=%f&zoom=18&addressdetails=1"; + if (key != null) { + url += "&key=" + key; + } + if (language != null) { + url += "&accept-language=" + language; + } + return url; + } + + public NominatimGeocoder(String url, String key, String language, int cacheSize, AddressFormat addressFormat) { + super(formatUrl(url, key, language), cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + JsonObject result = json.getJsonObject("address"); + + if (result != null) { + Address address = new Address(); + + if (json.containsKey("display_name")) { + address.setFormattedAddress(json.getString("display_name")); + } + + if (result.containsKey("house_number")) { + address.setHouse(result.getString("house_number")); + } + if (result.containsKey("road")) { + address.setStreet(result.getString("road")); + } + if (result.containsKey("suburb")) { + address.setSuburb(result.getString("suburb")); + } + + if (result.containsKey("village")) { + address.setSettlement(result.getString("village")); + } else if (result.containsKey("town")) { + address.setSettlement(result.getString("town")); + } else if (result.containsKey("city")) { + address.setSettlement(result.getString("city")); + } + + if (result.containsKey("state_district")) { + address.setDistrict(result.getString("state_district")); + } else if (result.containsKey("region")) { + address.setDistrict(result.getString("region")); + } + + if (result.containsKey("state")) { + address.setState(result.getString("state")); + } + if (result.containsKey("country_code")) { + address.setCountry(result.getString("country_code").toUpperCase()); + } + if (result.containsKey("postcode")) { + address.setPostcode(result.getString("postcode")); + } + + return address; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java b/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java new file mode 100644 index 000000000..822b6e91e --- /dev/null +++ b/src/main/java/org/traccar/geocoder/OpenCageGeocoder.java @@ -0,0 +1,76 @@ +/* + * Copyright 2014 - 2015 Stefaan Van Dooren (stefaan.vandooren@gmail.com) + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geocoder; + +import javax.json.JsonArray; +import javax.json.JsonObject; + +public class OpenCageGeocoder extends JsonGeocoder { + + public OpenCageGeocoder(String url, String key, int cacheSize, AddressFormat addressFormat) { + super(url + "/json?q=%f,%f&no_annotations=1&key=" + key, cacheSize, addressFormat); + } + + @Override + public Address parseAddress(JsonObject json) { + JsonArray result = json.getJsonArray("results"); + if (result != null) { + JsonObject location = result.getJsonObject(0).getJsonObject("components"); + if (location != null) { + Address address = new Address(); + + if (result.getJsonObject(0).containsKey("formatted")) { + address.setFormattedAddress(result.getJsonObject(0).getString("formatted")); + } + if (location.containsKey("building")) { + address.setHouse(location.getString("building")); + } + if (location.containsKey("house_number")) { + address.setHouse(location.getString("house_number")); + } + if (location.containsKey("road")) { + address.setStreet(location.getString("road")); + } + if (location.containsKey("suburb")) { + address.setSuburb(location.getString("suburb")); + } + if (location.containsKey("city")) { + address.setSettlement(location.getString("city")); + } + if (location.containsKey("city_district")) { + address.setSettlement(location.getString("city_district")); + } + if (location.containsKey("county")) { + address.setDistrict(location.getString("county")); + } + if (location.containsKey("state")) { + address.setState(location.getString("state")); + } + if (location.containsKey("country_code")) { + address.setCountry(location.getString("country_code").toUpperCase()); + } + if (location.containsKey("postcode")) { + address.setPostcode(location.getString("postcode")); + } + + return address; + } + } + return null; + } + +} diff --git a/src/main/java/org/traccar/geofence/GeofenceCircle.java b/src/main/java/org/traccar/geofence/GeofenceCircle.java new file mode 100644 index 000000000..f6fca63ca --- /dev/null +++ b/src/main/java/org/traccar/geofence/GeofenceCircle.java @@ -0,0 +1,98 @@ +/* + * Copyright 2016 Anton Tananaev (anton@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.geofence; + +import java.text.DecimalFormat; +import java.text.ParseException; + +import org.traccar.helper.DistanceCalculator; + +public class GeofenceCircle extends GeofenceGeometry { + + private double centerLatitude; + private double centerLongitude; + private double radius; + + public GeofenceCircle() { + } + + public GeofenceCircle(String wkt) throws ParseException { + fromWkt(wkt); + } + + public GeofenceCircle(double latitude, double longitude, double radius) { + this.centerLatitude = latitude; + this.centerLongitude = longitude; + this.radius = radius; + } + + public double distanceFromCenter(double latitude, double longitude) { + return DistanceCalculator.distance(centerLatitude, centerLongitude, latitude, longitude); + } + + @Override + public boolean containsPoint(double latitude, double longitude) { + return distanceFromCenter(latitude, longitude) <= radius; + } + + @Override + public String toWkt() { + String wkt = ""; + wkt = "CIRCLE ("; + wkt += String.valueOf(centerLatitude); + wkt += " "; + wkt += String.valueOf(centerLongitude); + wkt += ", "; + DecimalFormat format = new DecimalFormat("0.#"); + wkt += format.format(radius); + wkt += ")"; + return wkt; + } + + @Override + public void fromWkt(String wkt) throws ParseException { + if (!wkt.startsWith("CIRCLE")) { + throw new ParseException("Mismatch geometry type", 0); + } + String content = wkt.substring(wkt.indexOf("(") + 1, wkt.indexOf(")")); + if (content == null || content.equals("")) { + throw new ParseException("No content", 0); + } + String[] commaTokens = content.split(","); + if (commaTokens.length != 2) { + throw new ParseException("Not valid content", 0); + } + String[] tokens = commaTokens[0].split("\\s"); + if (tokens.length != 2) { + throw new ParseException("Too much or less coordinates", 0); + } + try { + centerLatitude = Double.parseDouble(tokens[0]); + } catch (NumberFormatException e) { + throw new ParseException(tokens[0] + " is not a double", 0); + } + try { + centerLongitude = Double.parseDouble(tokens[1]); + } catch (NumberFormatException e) { + throw new ParseException(tokens[1] + " is not a double", 0); + } + try { + radius = Double.parseDouble(commaTokens[1]); + } catch (NumberFormatException e) { + throw new ParseException(commaTokens[1] + " is not a double", 0); + } + } +} diff --git a/src/main/java/org/traccar/geofence/GeofenceGeometry.java b/src/main/java/org/traccar/geofence/GeofenceGeometry.java new file mode 100644 index 000000000..857ba3414 --- /dev/null +++ b/src/main/java/org/traccar/geofence/GeofenceGeometry.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Anton Tananaev (anton@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.geofence; + +import java.text.ParseException; + +public abstract class GeofenceGeometry { + + public abstract boolean containsPoint(double latitude, double longitude); + + public abstract String toWkt(); + + public abstract void fromWkt(String wkt) throws ParseException; + + public static class Coordinate { + + private double lat; + private double lon; + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLon() { + return lon; + } + + public void setLon(double lon) { + this.lon = lon; + } + } + +} diff --git a/src/main/java/org/traccar/geofence/GeofencePolygon.java b/src/main/java/org/traccar/geofence/GeofencePolygon.java new file mode 100644 index 000000000..2048ba26d --- /dev/null +++ b/src/main/java/org/traccar/geofence/GeofencePolygon.java @@ -0,0 +1,164 @@ +/* + * Copyright 2016 Anton Tananaev (anton@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.geofence; + +import java.text.ParseException; +import java.util.ArrayList; + +public class GeofencePolygon extends GeofenceGeometry { + + public GeofencePolygon() { + } + + public GeofencePolygon(String wkt) throws ParseException { + fromWkt(wkt); + } + + private ArrayList<Coordinate> coordinates; + + private double[] constant; + private double[] multiple; + + private boolean needNormalize = false; + + private void precalc() { + if (coordinates == null) { + return; + } + + int polyCorners = coordinates.size(); + int i; + int j = polyCorners - 1; + + if (constant != null) { + constant = null; + } + if (multiple != null) { + multiple = null; + } + + constant = new double[polyCorners]; + multiple = new double[polyCorners]; + + boolean hasNegative = false; + boolean hasPositive = false; + for (i = 0; i < polyCorners; i++) { + if (coordinates.get(i).getLon() > 90) { + hasPositive = true; + } else if (coordinates.get(i).getLon() < -90) { + hasNegative = true; + } + } + needNormalize = hasPositive && hasNegative; + + for (i = 0; i < polyCorners; j = i++) { + if (normalizeLon(coordinates.get(j).getLon()) == normalizeLon(coordinates.get(i).getLon())) { + constant[i] = coordinates.get(i).getLat(); + multiple[i] = 0; + } else { + constant[i] = coordinates.get(i).getLat() + - (normalizeLon(coordinates.get(i).getLon()) * coordinates.get(j).getLat()) + / (normalizeLon(coordinates.get(j).getLon()) - normalizeLon(coordinates.get(i).getLon())) + + (normalizeLon(coordinates.get(i).getLon()) * coordinates.get(i).getLat()) + / (normalizeLon(coordinates.get(j).getLon()) - normalizeLon(coordinates.get(i).getLon())); + multiple[i] = (coordinates.get(j).getLat() - coordinates.get(i).getLat()) + / (normalizeLon(coordinates.get(j).getLon()) - normalizeLon(coordinates.get(i).getLon())); + } + } + } + + private double normalizeLon(double lon) { + if (needNormalize && lon < -90) { + return lon + 360; + } + return lon; + } + + @Override + public boolean containsPoint(double latitude, double longitude) { + + int polyCorners = coordinates.size(); + int i; + int j = polyCorners - 1; + double longitudeNorm = normalizeLon(longitude); + boolean oddNodes = false; + + for (i = 0; i < polyCorners; j = i++) { + if (normalizeLon(coordinates.get(i).getLon()) < longitudeNorm + && normalizeLon(coordinates.get(j).getLon()) >= longitudeNorm + || normalizeLon(coordinates.get(j).getLon()) < longitudeNorm + && normalizeLon(coordinates.get(i).getLon()) >= longitudeNorm) { + oddNodes ^= longitudeNorm * multiple[i] + constant[i] < latitude; + } + } + return oddNodes; + } + + @Override + public String toWkt() { + StringBuilder buf = new StringBuilder(); + buf.append("POLYGON (("); + for (Coordinate coordinate : coordinates) { + buf.append(String.valueOf(coordinate.getLat())); + buf.append(" "); + buf.append(String.valueOf(coordinate.getLon())); + buf.append(", "); + } + return buf.substring(0, buf.length() - 2) + "))"; + } + + @Override + public void fromWkt(String wkt) throws ParseException { + if (coordinates == null) { + coordinates = new ArrayList<>(); + } else { + coordinates.clear(); + } + + if (!wkt.startsWith("POLYGON")) { + throw new ParseException("Mismatch geometry type", 0); + } + String content = wkt.substring(wkt.indexOf("((") + 2, wkt.indexOf("))")); + if (content.isEmpty()) { + throw new ParseException("No content", 0); + } + String[] commaTokens = content.split(","); + if (commaTokens.length < 3) { + throw new ParseException("Not valid content", 0); + } + + for (String commaToken : commaTokens) { + String[] tokens = commaToken.trim().split("\\s"); + if (tokens.length != 2) { + throw new ParseException("Here must be two coordinates: " + commaToken, 0); + } + Coordinate coordinate = new Coordinate(); + try { + coordinate.setLat(Double.parseDouble(tokens[0])); + } catch (NumberFormatException e) { + throw new ParseException(tokens[0] + " is not a double", 0); + } + try { + coordinate.setLon(Double.parseDouble(tokens[1])); + } catch (NumberFormatException e) { + throw new ParseException(tokens[1] + " is not a double", 0); + } + coordinates.add(coordinate); + } + precalc(); + } + +} diff --git a/src/main/java/org/traccar/geofence/GeofencePolyline.java b/src/main/java/org/traccar/geofence/GeofencePolyline.java new file mode 100644 index 000000000..d84f512e3 --- /dev/null +++ b/src/main/java/org/traccar/geofence/GeofencePolyline.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.geofence; + +import java.text.ParseException; +import java.util.ArrayList; + +import org.traccar.helper.DistanceCalculator; + +public class GeofencePolyline extends GeofenceGeometry { + + private ArrayList<Coordinate> coordinates; + private double distance; + + public GeofencePolyline() { + } + + public GeofencePolyline(String wkt, double distance) throws ParseException { + fromWkt(wkt); + this.distance = distance; + } + + @Override + public boolean containsPoint(double latitude, double longitude) { + for (int i = 1; i < coordinates.size(); i++) { + if (DistanceCalculator.distanceToLine( + latitude, longitude, coordinates.get(i - 1).getLat(), coordinates.get(i - 1).getLon(), + coordinates.get(i).getLat(), coordinates.get(i).getLon()) <= distance) { + return true; + } + } + return false; + } + + @Override + public String toWkt() { + StringBuilder buf = new StringBuilder(); + buf.append("LINESTRING ("); + for (Coordinate coordinate : coordinates) { + buf.append(String.valueOf(coordinate.getLat())); + buf.append(" "); + buf.append(String.valueOf(coordinate.getLon())); + buf.append(", "); + } + return buf.substring(0, buf.length() - 2) + ")"; + } + + @Override + public void fromWkt(String wkt) throws ParseException { + if (coordinates == null) { + coordinates = new ArrayList<>(); + } else { + coordinates.clear(); + } + + if (!wkt.startsWith("LINESTRING")) { + throw new ParseException("Mismatch geometry type", 0); + } + String content = wkt.substring(wkt.indexOf("(") + 1, wkt.indexOf(")")); + if (content.isEmpty()) { + throw new ParseException("No content", 0); + } + String[] commaTokens = content.split(","); + if (commaTokens.length < 2) { + throw new ParseException("Not valid content", 0); + } + + for (String commaToken : commaTokens) { + String[] tokens = commaToken.trim().split("\\s"); + if (tokens.length != 2) { + throw new ParseException("Here must be two coordinates: " + commaToken, 0); + } + Coordinate coordinate = new Coordinate(); + try { + coordinate.setLat(Double.parseDouble(tokens[0])); + } catch (NumberFormatException e) { + throw new ParseException(tokens[0] + " is not a double", 0); + } + try { + coordinate.setLon(Double.parseDouble(tokens[1])); + } catch (NumberFormatException e) { + throw new ParseException(tokens[1] + " is not a double", 0); + } + coordinates.add(coordinate); + } + + } + + public void setDistance(double distance) { + this.distance = distance; + } + +} diff --git a/src/main/java/org/traccar/geolocation/GeolocationException.java b/src/main/java/org/traccar/geolocation/GeolocationException.java new file mode 100644 index 000000000..5847cc807 --- /dev/null +++ b/src/main/java/org/traccar/geolocation/GeolocationException.java @@ -0,0 +1,24 @@ +/* + * Copyright 2016 Anton Tananaev (anton@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.geolocation; + +public class GeolocationException extends RuntimeException { + + public GeolocationException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/traccar/geolocation/GeolocationProvider.java b/src/main/java/org/traccar/geolocation/GeolocationProvider.java new file mode 100644 index 000000000..d9dec6bbb --- /dev/null +++ b/src/main/java/org/traccar/geolocation/GeolocationProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 - 2016 Anton Tananaev (anton@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.geolocation; + +import org.traccar.model.Network; + +public interface GeolocationProvider { + + interface LocationProviderCallback { + + void onSuccess(double latitude, double longitude, double accuracy); + + void onFailure(Throwable e); + + } + + void getLocation(Network network, LocationProviderCallback callback); + +} diff --git a/src/main/java/org/traccar/geolocation/GoogleGeolocationProvider.java b/src/main/java/org/traccar/geolocation/GoogleGeolocationProvider.java new file mode 100644 index 000000000..5901b47cd --- /dev/null +++ b/src/main/java/org/traccar/geolocation/GoogleGeolocationProvider.java @@ -0,0 +1,26 @@ +/* + * Copyright 2016 Anton Tananaev (anton@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.geolocation; + +public class GoogleGeolocationProvider extends UniversalGeolocationProvider { + + private static final String URL = "https://www.googleapis.com/geolocation/v1/geolocate"; + + public GoogleGeolocationProvider(String key) { + super(URL, key); + } + +} diff --git a/src/main/java/org/traccar/geolocation/MozillaGeolocationProvider.java b/src/main/java/org/traccar/geolocation/MozillaGeolocationProvider.java new file mode 100644 index 000000000..c6a73a52b --- /dev/null +++ b/src/main/java/org/traccar/geolocation/MozillaGeolocationProvider.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015 - 2016 Anton Tananaev (anton@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.geolocation; + +public class MozillaGeolocationProvider extends UniversalGeolocationProvider { + + private static final String URL = "https://location.services.mozilla.com/v1/geolocate"; + + public MozillaGeolocationProvider(String key) { + super(URL, key != null ? key : "test"); + } + +} diff --git a/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java b/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java new file mode 100644 index 000000000..cb3094e16 --- /dev/null +++ b/src/main/java/org/traccar/geolocation/OpenCellIdGeolocationProvider.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.geolocation; + +import org.traccar.Context; +import org.traccar.model.CellTower; +import org.traccar.model.Network; + +import javax.json.JsonObject; +import javax.ws.rs.client.InvocationCallback; + +public class OpenCellIdGeolocationProvider implements GeolocationProvider { + + private String url; + + public OpenCellIdGeolocationProvider(String url, String key) { + if (url == null) { + url = "http://opencellid.org/cell/get"; + } + this.url = url + "?format=json&mcc=%d&mnc=%d&lac=%d&cellid=%d&key=" + key; + } + + @Override + public void getLocation(Network network, final LocationProviderCallback callback) { + if (network.getCellTowers() != null && !network.getCellTowers().isEmpty()) { + + CellTower cellTower = network.getCellTowers().iterator().next(); + String request = String.format(url, cellTower.getMobileCountryCode(), cellTower.getMobileNetworkCode(), + cellTower.getLocationAreaCode(), cellTower.getCellId()); + + Context.getClient().target(request).request().async().get(new InvocationCallback<JsonObject>() { + @Override + public void completed(JsonObject json) { + if (json.containsKey("lat") && json.containsKey("lon")) { + callback.onSuccess( + json.getJsonNumber("lat").doubleValue(), + json.getJsonNumber("lon").doubleValue(), 0); + } else { + callback.onFailure(new GeolocationException("Coordinates are missing")); + } + } + + @Override + public void failed(Throwable throwable) { + callback.onFailure(throwable); + } + }); + + } else { + callback.onFailure(new GeolocationException("No network information")); + } + } + +} diff --git a/src/main/java/org/traccar/geolocation/UniversalGeolocationProvider.java b/src/main/java/org/traccar/geolocation/UniversalGeolocationProvider.java new file mode 100644 index 000000000..f71620d8a --- /dev/null +++ b/src/main/java/org/traccar/geolocation/UniversalGeolocationProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.geolocation; + +import org.traccar.Context; +import org.traccar.model.Network; + +import javax.json.JsonObject; +import javax.ws.rs.client.AsyncInvoker; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.InvocationCallback; + +public class UniversalGeolocationProvider implements GeolocationProvider { + + private String url; + + public UniversalGeolocationProvider(String url, String key) { + this.url = url + "?key=" + key; + } + + @Override + public void getLocation(Network network, final LocationProviderCallback callback) { + AsyncInvoker invoker = Context.getClient().target(url).request().async(); + invoker.post(Entity.json(network), new InvocationCallback<JsonObject>() { + @Override + public void completed(JsonObject json) { + if (json.containsKey("error")) { + callback.onFailure(new GeolocationException(json.getJsonObject("error").getString("message"))); + } else { + JsonObject location = json.getJsonObject("location"); + callback.onSuccess( + location.getJsonNumber("lat").doubleValue(), + location.getJsonNumber("lng").doubleValue(), + json.getJsonNumber("accuracy").doubleValue()); + } + } + + @Override + public void failed(Throwable throwable) { + callback.onFailure(throwable); + } + }); + } + +} diff --git a/src/main/java/org/traccar/geolocation/UnwiredGeolocationProvider.java b/src/main/java/org/traccar/geolocation/UnwiredGeolocationProvider.java new file mode 100644 index 000000000..963bcb688 --- /dev/null +++ b/src/main/java/org/traccar/geolocation/UnwiredGeolocationProvider.java @@ -0,0 +1,111 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.geolocation; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.traccar.Context; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.WifiAccessPoint; + +import javax.json.JsonObject; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.InvocationCallback; +import java.util.Collection; + +public class UnwiredGeolocationProvider implements GeolocationProvider { + + private String url; + private String key; + + private ObjectMapper objectMapper; + + private abstract static class NetworkMixIn { + @JsonProperty("mcc") + abstract Integer getHomeMobileCountryCode(); + @JsonProperty("mnc") + abstract Integer getHomeMobileNetworkCode(); + @JsonProperty("radio") + abstract String getRadioType(); + @JsonIgnore + abstract String getCarrier(); + @JsonIgnore + abstract Boolean getConsiderIp(); + @JsonProperty("cells") + abstract Collection<CellTower> getCellTowers(); + @JsonProperty("wifi") + abstract Collection<WifiAccessPoint> getWifiAccessPoints(); + } + + private abstract static class CellTowerMixIn { + @JsonProperty("radio") + abstract String getRadioType(); + @JsonProperty("mcc") + abstract Integer getMobileCountryCode(); + @JsonProperty("mnc") + abstract Integer getMobileNetworkCode(); + @JsonProperty("lac") + abstract Integer getLocationAreaCode(); + @JsonProperty("cid") + abstract Long getCellId(); + } + + private abstract static class WifiAccessPointMixIn { + @JsonProperty("bssid") + abstract String getMacAddress(); + @JsonProperty("signal") + abstract Integer getSignalStrength(); + } + + public UnwiredGeolocationProvider(String url, String key) { + this.url = url; + this.key = key; + + objectMapper = new ObjectMapper(); + objectMapper.addMixIn(Network.class, NetworkMixIn.class); + objectMapper.addMixIn(CellTower.class, CellTowerMixIn.class); + objectMapper.addMixIn(WifiAccessPoint.class, WifiAccessPointMixIn.class); + } + + @Override + public void getLocation(Network network, final LocationProviderCallback callback) { + ObjectNode json = objectMapper.valueToTree(network); + json.put("token", key); + + Context.getClient().target(url).request().async().post(Entity.json(json), new InvocationCallback<JsonObject>() { + @Override + public void completed(JsonObject json) { + if (json.getString("status").equals("error")) { + callback.onFailure(new GeolocationException(json.getString("message"))); + } else { + callback.onSuccess( + json.getJsonNumber("lat").doubleValue(), + json.getJsonNumber("lon").doubleValue(), + json.getJsonNumber("accuracy").doubleValue()); + } + } + + @Override + public void failed(Throwable throwable) { + callback.onFailure(throwable); + } + }); + } + +} diff --git a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java new file mode 100644 index 000000000..153da29b9 --- /dev/null +++ b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java @@ -0,0 +1,140 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import io.netty.channel.ChannelHandler; +import org.apache.commons.jexl2.JexlEngine; +import org.apache.commons.jexl2.JexlException; +import org.apache.commons.jexl2.MapContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BaseDataHandler; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.database.AttributesManager; +import org.traccar.database.IdentityManager; +import org.traccar.model.Attribute; +import org.traccar.model.Device; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class ComputedAttributesHandler extends BaseDataHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(ComputedAttributesHandler.class); + + private final IdentityManager identityManager; + private final AttributesManager attributesManager; + + private final JexlEngine engine; + + private final boolean includeDeviceAttributes; + + public ComputedAttributesHandler( + Config config, IdentityManager identityManager, AttributesManager attributesManager) { + this.identityManager = identityManager; + this.attributesManager = attributesManager; + engine = new JexlEngine(); + engine.setStrict(true); + engine.setFunctions(Collections.singletonMap("math", Math.class)); + includeDeviceAttributes = config.getBoolean(Keys.PROCESSING_COMPUTED_ATTRIBUTES_DEVICE_ATTRIBUTES); + } + + private MapContext prepareContext(Position position) { + MapContext result = new MapContext(); + if (includeDeviceAttributes) { + Device device = identityManager.getById(position.getDeviceId()); + if (device != null) { + for (Object key : device.getAttributes().keySet()) { + result.set((String) key, device.getAttributes().get(key)); + } + } + } + Set<Method> methods = new HashSet<>(Arrays.asList(position.getClass().getMethods())); + methods.removeAll(Arrays.asList(Object.class.getMethods())); + for (Method method : methods) { + if (method.getName().startsWith("get") && method.getParameterTypes().length == 0) { + String name = Character.toLowerCase(method.getName().charAt(3)) + method.getName().substring(4); + + try { + if (!method.getReturnType().equals(Map.class)) { + result.set(name, method.invoke(position)); + } else { + for (Object key : ((Map) method.invoke(position)).keySet()) { + result.set((String) key, ((Map) method.invoke(position)).get(key)); + } + } + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Attribute reflection error", error); + } + } + } + return result; + } + + /** + * @deprecated logic needs to be extracted to be used in API resource + */ + @Deprecated + public Object computeAttribute(Attribute attribute, Position position) throws JexlException { + return engine.createExpression(attribute.getExpression()).evaluate(prepareContext(position)); + } + + @Override + protected Position handlePosition(Position position) { + Collection<Attribute> attributes = attributesManager.getItems( + attributesManager.getAllDeviceItems(position.getDeviceId())); + for (Attribute attribute : attributes) { + if (attribute.getAttribute() != null) { + Object result = null; + try { + result = computeAttribute(attribute, position); + } catch (JexlException error) { + LOGGER.warn("Attribute computation error", error); + } + if (result != null) { + try { + switch (attribute.getType()) { + case "number": + Number numberValue = (Number) result; + position.getAttributes().put(attribute.getAttribute(), numberValue); + break; + case "boolean": + Boolean booleanValue = (Boolean) result; + position.getAttributes().put(attribute.getAttribute(), booleanValue); + break; + default: + position.getAttributes().put(attribute.getAttribute(), result.toString()); + } + } catch (ClassCastException error) { + LOGGER.warn("Attribute cast error", error); + } + } + } + } + return position; + } + +} diff --git a/src/main/java/org/traccar/handler/CopyAttributesHandler.java b/src/main/java/org/traccar/handler/CopyAttributesHandler.java new file mode 100644 index 000000000..3cd7d144d --- /dev/null +++ b/src/main/java/org/traccar/handler/CopyAttributesHandler.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelHandler; +import org.traccar.BaseDataHandler; +import org.traccar.database.IdentityManager; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class CopyAttributesHandler extends BaseDataHandler { + + private IdentityManager identityManager; + + public CopyAttributesHandler(IdentityManager identityManager) { + this.identityManager = identityManager; + } + + @Override + protected Position handlePosition(Position position) { + String attributesString = identityManager.lookupAttributeString( + position.getDeviceId(), "processing.copyAttributes", "", false, true); + if (attributesString.isEmpty()) { + attributesString = Position.KEY_DRIVER_UNIQUE_ID; + } else { + attributesString += "," + Position.KEY_DRIVER_UNIQUE_ID; + } + Position last = identityManager.getLastPosition(position.getDeviceId()); + if (last != null) { + for (String attribute : attributesString.split("[ ,]")) { + if (last.getAttributes().containsKey(attribute) && !position.getAttributes().containsKey(attribute)) { + position.getAttributes().put(attribute, last.getAttributes().get(attribute)); + } + } + } + return position; + } + +} diff --git a/src/main/java/org/traccar/handler/DefaultDataHandler.java b/src/main/java/org/traccar/handler/DefaultDataHandler.java new file mode 100644 index 000000000..9d8ea044d --- /dev/null +++ b/src/main/java/org/traccar/handler/DefaultDataHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BaseDataHandler; +import org.traccar.database.DataManager; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class DefaultDataHandler extends BaseDataHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataHandler.class); + + private final DataManager dataManager; + + public DefaultDataHandler(DataManager dataManager) { + this.dataManager = dataManager; + } + + @Override + protected Position handlePosition(Position position) { + + try { + dataManager.addObject(position); + } catch (Exception error) { + LOGGER.warn("Failed to store position", error); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/handler/DistanceHandler.java b/src/main/java/org/traccar/handler/DistanceHandler.java new file mode 100644 index 000000000..a336a884e --- /dev/null +++ b/src/main/java/org/traccar/handler/DistanceHandler.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015 Amila Silva + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelHandler; +import org.traccar.BaseDataHandler; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.database.IdentityManager; +import org.traccar.helper.DistanceCalculator; +import org.traccar.model.Position; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +@ChannelHandler.Sharable +public class DistanceHandler extends BaseDataHandler { + + private final IdentityManager identityManager; + + private final boolean filter; + private final int coordinatesMinError; + private final int coordinatesMaxError; + + public DistanceHandler(Config config, IdentityManager identityManager) { + this.identityManager = identityManager; + this.filter = config.getBoolean(Keys.COORDINATES_FILTER); + this.coordinatesMinError = config.getInteger(Keys.COORDINATES_MIN_ERROR); + this.coordinatesMaxError = config.getInteger(Keys.COORDINATES_MAX_ERROR); + } + + @Override + protected Position handlePosition(Position position) { + + double distance = 0.0; + if (position.getAttributes().containsKey(Position.KEY_DISTANCE)) { + distance = position.getDouble(Position.KEY_DISTANCE); + } + double totalDistance = 0.0; + + Position last = identityManager != null ? identityManager.getLastPosition(position.getDeviceId()) : null; + if (last != null) { + totalDistance = last.getDouble(Position.KEY_TOTAL_DISTANCE); + if (!position.getAttributes().containsKey(Position.KEY_DISTANCE)) { + distance = DistanceCalculator.distance( + position.getLatitude(), position.getLongitude(), + last.getLatitude(), last.getLongitude()); + distance = BigDecimal.valueOf(distance).setScale(2, RoundingMode.HALF_EVEN).doubleValue(); + } + if (filter && last.getValid() && last.getLatitude() != 0 && last.getLongitude() != 0) { + boolean satisfiesMin = coordinatesMinError == 0 || distance > coordinatesMinError; + boolean satisfiesMax = coordinatesMaxError == 0 + || distance < coordinatesMaxError || position.getValid(); + if (!satisfiesMin || !satisfiesMax) { + position.setLatitude(last.getLatitude()); + position.setLongitude(last.getLongitude()); + distance = 0; + } + } + } + position.set(Position.KEY_DISTANCE, distance); + totalDistance = BigDecimal.valueOf(totalDistance + distance).setScale(2, RoundingMode.HALF_EVEN).doubleValue(); + position.set(Position.KEY_TOTAL_DISTANCE, totalDistance); + + return position; + } + +} diff --git a/src/main/java/org/traccar/handler/EngineHoursHandler.java b/src/main/java/org/traccar/handler/EngineHoursHandler.java new file mode 100644 index 000000000..92da84e6b --- /dev/null +++ b/src/main/java/org/traccar/handler/EngineHoursHandler.java @@ -0,0 +1,50 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelHandler; +import org.traccar.BaseDataHandler; +import org.traccar.database.IdentityManager; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class EngineHoursHandler extends BaseDataHandler { + + private final IdentityManager identityManager; + + public EngineHoursHandler(IdentityManager identityManager) { + this.identityManager = identityManager; + } + + @Override + protected Position handlePosition(Position position) { + if (!position.getAttributes().containsKey(Position.KEY_HOURS)) { + Position last = identityManager.getLastPosition(position.getDeviceId()); + if (last != null) { + long hours = last.getLong(Position.KEY_HOURS); + if (last.getBoolean(Position.KEY_IGNITION) && position.getBoolean(Position.KEY_IGNITION)) { + hours += position.getFixTime().getTime() - last.getFixTime().getTime(); + } + if (hours != 0) { + position.set(Position.KEY_HOURS, hours); + } + } + } + return position; + } + +} diff --git a/src/main/java/org/traccar/handler/FilterHandler.java b/src/main/java/org/traccar/handler/FilterHandler.java new file mode 100644 index 000000000..7cd9153c1 --- /dev/null +++ b/src/main/java/org/traccar/handler/FilterHandler.java @@ -0,0 +1,206 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BaseDataHandler; +import org.traccar.Context; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class FilterHandler extends BaseDataHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(FilterHandler.class); + + private boolean filterInvalid; + private boolean filterZero; + private boolean filterDuplicate; + private long filterFuture; + private boolean filterApproximate; + private int filterAccuracy; + private boolean filterStatic; + private int filterDistance; + private int filterMaxSpeed; + private long filterMinPeriod; + private long skipLimit; + private boolean skipAttributes; + + public FilterHandler(Config config) { + filterInvalid = config.getBoolean(Keys.FILTER_INVALID); + filterZero = config.getBoolean(Keys.FILTER_ZERO); + filterDuplicate = config.getBoolean(Keys.FILTER_DUPLICATE); + filterFuture = config.getLong(Keys.FILTER_FUTURE) * 1000; + filterAccuracy = config.getInteger(Keys.FILTER_ACCURACY); + filterApproximate = config.getBoolean(Keys.FILTER_APPROXIMATE); + filterStatic = config.getBoolean(Keys.FILTER_STATIC); + filterDistance = config.getInteger(Keys.FILTER_DISTANCE); + filterMaxSpeed = config.getInteger(Keys.FILTER_MAX_SPEED); + filterMinPeriod = config.getInteger(Keys.FILTER_MIN_PERIOD) * 1000; + skipLimit = config.getLong(Keys.FILTER_SKIP_LIMIT) * 1000; + skipAttributes = config.getBoolean(Keys.FILTER_SKIP_ATTRIBUTES_ENABLE); + } + + private boolean filterInvalid(Position position) { + return filterInvalid && (!position.getValid() + || position.getLatitude() > 90 || position.getLongitude() > 180 + || position.getLatitude() < -90 || position.getLongitude() < -180); + } + + private boolean filterZero(Position position) { + return filterZero && position.getLatitude() == 0.0 && position.getLongitude() == 0.0; + } + + private boolean filterDuplicate(Position position, Position last) { + if (filterDuplicate && last != null && position.getFixTime().equals(last.getFixTime())) { + for (String key : position.getAttributes().keySet()) { + if (!last.getAttributes().containsKey(key)) { + return false; + } + } + return true; + } + return false; + } + + private boolean filterFuture(Position position) { + return filterFuture != 0 && position.getFixTime().getTime() > System.currentTimeMillis() + filterFuture; + } + + private boolean filterAccuracy(Position position) { + return filterAccuracy != 0 && position.getAccuracy() > filterAccuracy; + } + + private boolean filterApproximate(Position position) { + return filterApproximate && position.getBoolean(Position.KEY_APPROXIMATE); + } + + private boolean filterStatic(Position position) { + return filterStatic && position.getSpeed() == 0.0; + } + + private boolean filterDistance(Position position, Position last) { + if (filterDistance != 0 && last != null) { + return position.getDouble(Position.KEY_DISTANCE) < filterDistance; + } + return false; + } + + private boolean filterMaxSpeed(Position position, Position last) { + if (filterMaxSpeed != 0 && last != null) { + double distance = position.getDouble(Position.KEY_DISTANCE); + double time = position.getFixTime().getTime() - last.getFixTime().getTime(); + return UnitsConverter.knotsFromMps(distance / (time / 1000)) > filterMaxSpeed; + } + return false; + } + + private boolean filterMinPeriod(Position position, Position last) { + if (filterMinPeriod != 0 && last != null) { + long time = position.getFixTime().getTime() - last.getFixTime().getTime(); + return time > 0 && time < filterMinPeriod; + } + return false; + } + + private boolean skipLimit(Position position, Position last) { + if (skipLimit != 0 && last != null) { + return (position.getServerTime().getTime() - last.getServerTime().getTime()) > skipLimit; + } + return false; + } + + private boolean skipAttributes(Position position) { + if (skipAttributes) { + String attributesString = Context.getIdentityManager().lookupAttributeString( + position.getDeviceId(), "filter.skipAttributes", "", false, true); + for (String attribute : attributesString.split("[ ,]")) { + if (position.getAttributes().containsKey(attribute)) { + return true; + } + } + } + return false; + } + + private boolean filter(Position position) { + + StringBuilder filterType = new StringBuilder(); + + Position last = null; + if (Context.getIdentityManager() != null) { + last = Context.getIdentityManager().getLastPosition(position.getDeviceId()); + } + + if (filterInvalid(position)) { + filterType.append("Invalid "); + } + if (filterZero(position)) { + filterType.append("Zero "); + } + if (filterDuplicate(position, last) && !skipLimit(position, last) && !skipAttributes(position)) { + filterType.append("Duplicate "); + } + if (filterFuture(position)) { + filterType.append("Future "); + } + if (filterAccuracy(position)) { + filterType.append("Accuracy "); + } + if (filterApproximate(position)) { + filterType.append("Approximate "); + } + if (filterStatic(position) && !skipLimit(position, last) && !skipAttributes(position)) { + filterType.append("Static "); + } + if (filterDistance(position, last) && !skipLimit(position, last) && !skipAttributes(position)) { + filterType.append("Distance "); + } + if (filterMaxSpeed(position, last)) { + filterType.append("MaxSpeed "); + } + if (filterMinPeriod(position, last)) { + filterType.append("MinPeriod "); + } + + if (filterType.length() > 0) { + + StringBuilder message = new StringBuilder(); + message.append("Position filtered by "); + message.append(filterType.toString()); + message.append("filters from device: "); + message.append(Context.getIdentityManager().getById(position.getDeviceId()).getUniqueId()); + + LOGGER.info(message.toString()); + return true; + } + + return false; + } + + @Override + protected Position handlePosition(Position position) { + if (filter(position)) { + return null; + } + return position; + } + +} diff --git a/src/main/java/org/traccar/handler/GeocoderHandler.java b/src/main/java/org/traccar/handler/GeocoderHandler.java new file mode 100644 index 000000000..b96f01b3a --- /dev/null +++ b/src/main/java/org/traccar/handler/GeocoderHandler.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.database.IdentityManager; +import org.traccar.database.StatisticsManager; +import org.traccar.geocoder.Geocoder; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class GeocoderHandler extends ChannelInboundHandlerAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(GeocoderHandler.class); + + private final Geocoder geocoder; + private final IdentityManager identityManager; + private final StatisticsManager statisticsManager; + private final boolean ignorePositions; + private final boolean processInvalidPositions; + private final int geocoderReuseDistance; + + public GeocoderHandler( + Config config, Geocoder geocoder, IdentityManager identityManager, StatisticsManager statisticsManager) { + this.geocoder = geocoder; + this.identityManager = identityManager; + this.statisticsManager = statisticsManager; + ignorePositions = Context.getConfig().getBoolean(Keys.GEOCODER_IGNORE_POSITIONS); + processInvalidPositions = config.getBoolean(Keys.GEOCODER_PROCESS_INVALID_POSITIONS); + geocoderReuseDistance = config.getInteger(Keys.GEOCODER_REUSE_DISTANCE, 0); + } + + @Override + public void channelRead(final ChannelHandlerContext ctx, Object message) { + if (message instanceof Position && !ignorePositions) { + final Position position = (Position) message; + if (processInvalidPositions || position.getValid()) { + if (geocoderReuseDistance != 0) { + Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); + if (lastPosition != null && lastPosition.getAddress() != null + && position.getDouble(Position.KEY_DISTANCE) <= geocoderReuseDistance) { + position.setAddress(lastPosition.getAddress()); + ctx.fireChannelRead(position); + return; + } + } + + if (statisticsManager != null) { + statisticsManager.registerGeocoderRequest(); + } + + geocoder.getAddress(position.getLatitude(), position.getLongitude(), + new Geocoder.ReverseGeocoderCallback() { + @Override + public void onSuccess(String address) { + position.setAddress(address); + ctx.fireChannelRead(position); + } + + @Override + public void onFailure(Throwable e) { + LOGGER.warn("Geocoding failed", e); + ctx.fireChannelRead(position); + } + }); + } else { + ctx.fireChannelRead(position); + } + } else { + ctx.fireChannelRead(message); + } + } + +} diff --git a/src/main/java/org/traccar/handler/GeolocationHandler.java b/src/main/java/org/traccar/handler/GeolocationHandler.java new file mode 100644 index 000000000..0e78322c8 --- /dev/null +++ b/src/main/java/org/traccar/handler/GeolocationHandler.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.database.StatisticsManager; +import org.traccar.geolocation.GeolocationProvider; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class GeolocationHandler extends ChannelInboundHandlerAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(GeolocationHandler.class); + + private final GeolocationProvider geolocationProvider; + private final StatisticsManager statisticsManager; + private final boolean processInvalidPositions; + + public GeolocationHandler( + Config config, GeolocationProvider geolocationProvider, StatisticsManager statisticsManager) { + this.geolocationProvider = geolocationProvider; + this.statisticsManager = statisticsManager; + this.processInvalidPositions = config.getBoolean(Keys.GEOLOCATION_PROCESS_INVALID_POSITIONS); + } + + @Override + public void channelRead(final ChannelHandlerContext ctx, Object message) { + if (message instanceof Position) { + final Position position = (Position) message; + if ((position.getOutdated() || processInvalidPositions && !position.getValid()) + && position.getNetwork() != null) { + if (statisticsManager != null) { + statisticsManager.registerGeolocationRequest(); + } + + geolocationProvider.getLocation(position.getNetwork(), + new GeolocationProvider.LocationProviderCallback() { + @Override + public void onSuccess(double latitude, double longitude, double accuracy) { + position.set(Position.KEY_APPROXIMATE, true); + position.setValid(true); + position.setFixTime(position.getDeviceTime()); + position.setLatitude(latitude); + position.setLongitude(longitude); + position.setAccuracy(accuracy); + position.setAltitude(0); + position.setSpeed(0); + position.setCourse(0); + ctx.fireChannelRead(position); + } + + @Override + public void onFailure(Throwable e) { + LOGGER.warn("Geolocation network error", e); + ctx.fireChannelRead(position); + } + }); + } else { + ctx.fireChannelRead(position); + } + } else { + ctx.fireChannelRead(message); + } + } + +} diff --git a/src/main/java/org/traccar/handler/HemisphereHandler.java b/src/main/java/org/traccar/handler/HemisphereHandler.java new file mode 100644 index 000000000..aff3d8a64 --- /dev/null +++ b/src/main/java/org/traccar/handler/HemisphereHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelHandler; +import org.traccar.BaseDataHandler; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class HemisphereHandler extends BaseDataHandler { + + private int latitudeFactor; + private int longitudeFactor; + + public HemisphereHandler(Config config) { + String latitudeHemisphere = config.getString(Keys.LOCATION_LATITUDE_HEMISPHERE); + if (latitudeHemisphere != null) { + if (latitudeHemisphere.equalsIgnoreCase("N")) { + latitudeFactor = 1; + } else if (latitudeHemisphere.equalsIgnoreCase("S")) { + latitudeFactor = -1; + } + } + String longitudeHemisphere = config.getString(Keys.LOCATION_LATITUDE_HEMISPHERE); + if (longitudeHemisphere != null) { + if (longitudeHemisphere.equalsIgnoreCase("E")) { + longitudeFactor = 1; + } else if (longitudeHemisphere.equalsIgnoreCase("W")) { + longitudeFactor = -1; + } + } + } + + @Override + protected Position handlePosition(Position position) { + if (latitudeFactor != 0) { + position.setLatitude(Math.abs(position.getLatitude()) * latitudeFactor); + } + if (longitudeFactor != 0) { + position.setLongitude(Math.abs(position.getLongitude()) * longitudeFactor); + } + return position; + } + +} diff --git a/src/main/java/org/traccar/handler/MotionHandler.java b/src/main/java/org/traccar/handler/MotionHandler.java new file mode 100644 index 000000000..e8051dd75 --- /dev/null +++ b/src/main/java/org/traccar/handler/MotionHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelHandler; +import org.traccar.BaseDataHandler; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class MotionHandler extends BaseDataHandler { + + private double speedThreshold; + + public MotionHandler(double speedThreshold) { + this.speedThreshold = speedThreshold; + } + + @Override + protected Position handlePosition(Position position) { + if (!position.getAttributes().containsKey(Position.KEY_MOTION)) { + position.set(Position.KEY_MOTION, position.getSpeed() > speedThreshold); + } + return position; + } + +} diff --git a/src/main/java/org/traccar/handler/NetworkMessageHandler.java b/src/main/java/org/traccar/handler/NetworkMessageHandler.java new file mode 100644 index 000000000..b1d926bfa --- /dev/null +++ b/src/main/java/org/traccar/handler/NetworkMessageHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.DatagramPacket; +import org.traccar.NetworkMessage; + +import java.net.InetSocketAddress; + +public class NetworkMessageHandler extends ChannelDuplexHandler { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (ctx.channel() instanceof DatagramChannel) { + DatagramPacket packet = (DatagramPacket) msg; + ctx.fireChannelRead(new NetworkMessage(packet.content(), packet.sender())); + } else if (msg instanceof ByteBuf) { + ByteBuf buffer = (ByteBuf) msg; + ctx.fireChannelRead(new NetworkMessage(buffer, ctx.channel().remoteAddress())); + } + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { + if (msg instanceof NetworkMessage) { + NetworkMessage message = (NetworkMessage) msg; + if (ctx.channel() instanceof DatagramChannel) { + InetSocketAddress recipient = (InetSocketAddress) message.getRemoteAddress(); + InetSocketAddress sender = (InetSocketAddress) ctx.channel().localAddress(); + ctx.write(new DatagramPacket((ByteBuf) message.getMessage(), recipient, sender), promise); + } else { + ctx.write(message.getMessage(), promise); + } + } else { + ctx.write(msg, promise); + } + } + +} diff --git a/src/main/java/org/traccar/handler/OpenChannelHandler.java b/src/main/java/org/traccar/handler/OpenChannelHandler.java new file mode 100644 index 000000000..d09d617ab --- /dev/null +++ b/src/main/java/org/traccar/handler/OpenChannelHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.TrackerServer; + +public class OpenChannelHandler extends ChannelDuplexHandler { + + private final TrackerServer server; + + public OpenChannelHandler(TrackerServer server) { + this.server = server; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + server.getChannelGroup().add(ctx.channel()); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + server.getChannelGroup().remove(ctx.channel()); + } + +} diff --git a/src/main/java/org/traccar/handler/RemoteAddressHandler.java b/src/main/java/org/traccar/handler/RemoteAddressHandler.java new file mode 100644 index 000000000..c09b8c39a --- /dev/null +++ b/src/main/java/org/traccar/handler/RemoteAddressHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import org.traccar.model.Position; + +import java.net.InetSocketAddress; + +@ChannelHandler.Sharable +public class RemoteAddressHandler extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + + InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress(); + String hostAddress = remoteAddress != null ? remoteAddress.getAddress().getHostAddress() : null; + + if (msg instanceof Position) { + Position position = (Position) msg; + position.set(Position.KEY_IP, hostAddress); + } + + ctx.fireChannelRead(msg); + } + +} diff --git a/src/main/java/org/traccar/handler/StandardLoggingHandler.java b/src/main/java/org/traccar/handler/StandardLoggingHandler.java new file mode 100644 index 000000000..13c5f8281 --- /dev/null +++ b/src/main/java/org/traccar/handler/StandardLoggingHandler.java @@ -0,0 +1,87 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.NetworkMessage; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +public class StandardLoggingHandler extends ChannelDuplexHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(StandardLoggingHandler.class); + + private final String protocol; + + public StandardLoggingHandler(String protocol) { + this.protocol = protocol; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + log(ctx, false, msg); + super.channelRead(ctx, msg); + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + log(ctx, true, msg); + super.write(ctx, msg, promise); + } + + public void log(ChannelHandlerContext ctx, boolean downstream, Object o) { + if (o instanceof NetworkMessage) { + NetworkMessage networkMessage = (NetworkMessage) o; + if (networkMessage.getMessage() instanceof ByteBuf) { + log(ctx, downstream, networkMessage.getRemoteAddress(), (ByteBuf) networkMessage.getMessage()); + } + } else if (o instanceof ByteBuf) { + log(ctx, downstream, ctx.channel().remoteAddress(), (ByteBuf) o); + } + } + + public void log(ChannelHandlerContext ctx, boolean downstream, SocketAddress remoteAddress, ByteBuf buf) { + StringBuilder message = new StringBuilder(); + + message.append("[").append(ctx.channel().id().asShortText()).append(": "); + message.append(protocol); + if (downstream) { + message.append(" > "); + } else { + message.append(" < "); + } + + if (remoteAddress instanceof InetSocketAddress) { + message.append(((InetSocketAddress) remoteAddress).getHostString()); + } else { + message.append("unknown"); + } + message.append("]"); + + message.append(" HEX: "); + message.append(ByteBufUtil.hexDump(buf)); + + LOGGER.info(message.toString()); + } + +} diff --git a/src/main/java/org/traccar/handler/TimeHandler.java b/src/main/java/org/traccar/handler/TimeHandler.java new file mode 100644 index 000000000..d8039518c --- /dev/null +++ b/src/main/java/org/traccar/handler/TimeHandler.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.model.Position; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +@ChannelHandler.Sharable +public class TimeHandler extends ChannelInboundHandlerAdapter { + + private final boolean useServerTime; + private final Set<String> protocols; + + public TimeHandler(Config config) { + useServerTime = config.getString(Keys.TIME_OVERRIDE).equalsIgnoreCase("serverTime"); + String protocolList = Context.getConfig().getString(Keys.TIME_PROTOCOLS); + if (protocolList != null) { + protocols = new HashSet<>(Arrays.asList(protocolList.split("[, ]"))); + } else { + protocols = null; + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + + if (msg instanceof Position && (protocols == null + || protocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName()))) { + + Position position = (Position) msg; + if (useServerTime) { + position.setDeviceTime(position.getServerTime()); + } + position.setFixTime(position.getDeviceTime()); + + } + ctx.fireChannelRead(msg); + } + +} diff --git a/src/main/java/org/traccar/handler/events/AlertEventHandler.java b/src/main/java/org/traccar/handler/events/AlertEventHandler.java new file mode 100644 index 000000000..0b7c8d23e --- /dev/null +++ b/src/main/java/org/traccar/handler/events/AlertEventHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler.events; + +import java.util.Collections; +import java.util.Map; + +import io.netty.channel.ChannelHandler; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.database.IdentityManager; +import org.traccar.model.Event; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class AlertEventHandler extends BaseEventHandler { + + private final IdentityManager identityManager; + private final boolean ignoreDuplicateAlerts; + + public AlertEventHandler(Config config, IdentityManager identityManager) { + this.identityManager = identityManager; + ignoreDuplicateAlerts = config.getBoolean(Keys.EVENT_IGNORE_DUPLICATE_ALERTS); + } + + @Override + protected Map<Event, Position> analyzePosition(Position position) { + Object alarm = position.getAttributes().get(Position.KEY_ALARM); + if (alarm != null) { + boolean ignoreAlert = false; + if (ignoreDuplicateAlerts) { + Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); + if (lastPosition != null && alarm.equals(lastPosition.getAttributes().get(Position.KEY_ALARM))) { + ignoreAlert = true; + } + } + if (!ignoreAlert) { + Event event = new Event(Event.TYPE_ALARM, position.getDeviceId(), position.getId()); + event.set(Position.KEY_ALARM, (String) alarm); + return Collections.singletonMap(event, position); + } + } + return null; + } + +} diff --git a/src/main/java/org/traccar/handler/events/BaseEventHandler.java b/src/main/java/org/traccar/handler/events/BaseEventHandler.java new file mode 100644 index 000000000..41f677f6c --- /dev/null +++ b/src/main/java/org/traccar/handler/events/BaseEventHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler.events; + +import java.util.Map; + +import org.traccar.BaseDataHandler; +import org.traccar.Context; +import org.traccar.model.Event; +import org.traccar.model.Position; + +public abstract class BaseEventHandler extends BaseDataHandler { + + @Override + protected Position handlePosition(Position position) { + Map<Event, Position> events = analyzePosition(position); + if (events != null && Context.getNotificationManager() != null) { + Context.getNotificationManager().updateEvents(events); + } + return position; + } + + protected abstract Map<Event, Position> analyzePosition(Position position); + +} diff --git a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java new file mode 100644 index 000000000..cfe676653 --- /dev/null +++ b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler.events; + +import java.util.Collections; +import java.util.Map; + +import io.netty.channel.ChannelHandler; +import org.traccar.model.Event; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class CommandResultEventHandler extends BaseEventHandler { + + @Override + protected Map<Event, Position> analyzePosition(Position position) { + Object commandResult = position.getAttributes().get(Position.KEY_RESULT); + if (commandResult != null) { + Event event = new Event(Event.TYPE_COMMAND_RESULT, position.getDeviceId(), position.getId()); + event.set(Position.KEY_RESULT, (String) commandResult); + return Collections.singletonMap(event, position); + } + return null; + } + +} diff --git a/src/main/java/org/traccar/handler/events/DriverEventHandler.java b/src/main/java/org/traccar/handler/events/DriverEventHandler.java new file mode 100644 index 000000000..994df93fa --- /dev/null +++ b/src/main/java/org/traccar/handler/events/DriverEventHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler.events; + +import java.util.Collections; +import java.util.Map; + +import io.netty.channel.ChannelHandler; +import org.traccar.database.IdentityManager; +import org.traccar.model.Event; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class DriverEventHandler extends BaseEventHandler { + + private final IdentityManager identityManager; + + public DriverEventHandler(IdentityManager identityManager) { + this.identityManager = identityManager; + } + + @Override + protected Map<Event, Position> analyzePosition(Position position) { + if (!identityManager.isLatestPosition(position)) { + return null; + } + String driverUniqueId = position.getString(Position.KEY_DRIVER_UNIQUE_ID); + if (driverUniqueId != null) { + String oldDriverUniqueId = null; + Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); + if (lastPosition != null) { + oldDriverUniqueId = lastPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); + } + if (!driverUniqueId.equals(oldDriverUniqueId)) { + Event event = new Event(Event.TYPE_DRIVER_CHANGED, position.getDeviceId(), position.getId()); + event.set(Position.KEY_DRIVER_UNIQUE_ID, driverUniqueId); + return Collections.singletonMap(event, position); + } + } + return null; + } + +} diff --git a/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java b/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java new file mode 100644 index 000000000..bc1426b86 --- /dev/null +++ b/src/main/java/org/traccar/handler/events/FuelDropEventHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler.events; + +import io.netty.channel.ChannelHandler; +import org.traccar.database.IdentityManager; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Position; + +import java.util.Collections; +import java.util.Map; + +@ChannelHandler.Sharable +public class FuelDropEventHandler extends BaseEventHandler { + + public static final String ATTRIBUTE_FUEL_DROP_THRESHOLD = "fuelDropThreshold"; + + private final IdentityManager identityManager; + + public FuelDropEventHandler(IdentityManager identityManager) { + this.identityManager = identityManager; + } + + @Override + protected Map<Event, Position> analyzePosition(Position position) { + + Device device = identityManager.getById(position.getDeviceId()); + if (device == null) { + return null; + } + if (!identityManager.isLatestPosition(position)) { + return null; + } + + double fuelDropThreshold = identityManager + .lookupAttributeDouble(device.getId(), ATTRIBUTE_FUEL_DROP_THRESHOLD, 0, true, false); + + if (fuelDropThreshold > 0) { + Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); + if (position.getAttributes().containsKey(Position.KEY_FUEL_LEVEL) + && lastPosition != null && lastPosition.getAttributes().containsKey(Position.KEY_FUEL_LEVEL)) { + + double drop = lastPosition.getDouble(Position.KEY_FUEL_LEVEL) + - position.getDouble(Position.KEY_FUEL_LEVEL); + if (drop >= fuelDropThreshold) { + Event event = new Event(Event.TYPE_DEVICE_FUEL_DROP, position.getDeviceId(), position.getId()); + event.set(ATTRIBUTE_FUEL_DROP_THRESHOLD, fuelDropThreshold); + return Collections.singletonMap(event, position); + } + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java new file mode 100644 index 000000000..067c97957 --- /dev/null +++ b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java @@ -0,0 +1,89 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler.events; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.netty.channel.ChannelHandler; +import org.traccar.database.CalendarManager; +import org.traccar.database.GeofenceManager; +import org.traccar.database.IdentityManager; +import org.traccar.model.Calendar; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class GeofenceEventHandler extends BaseEventHandler { + + private final IdentityManager identityManager; + private final GeofenceManager geofenceManager; + private final CalendarManager calendarManager; + + public GeofenceEventHandler( + IdentityManager identityManager, GeofenceManager geofenceManager, CalendarManager calendarManager) { + this.identityManager = identityManager; + this.geofenceManager = geofenceManager; + this.calendarManager = calendarManager; + } + + @Override + protected Map<Event, Position> analyzePosition(Position position) { + Device device = identityManager.getById(position.getDeviceId()); + if (device == null) { + return null; + } + if (!identityManager.isLatestPosition(position) || !position.getValid()) { + return null; + } + + List<Long> currentGeofences = geofenceManager.getCurrentDeviceGeofences(position); + List<Long> oldGeofences = new ArrayList<>(); + if (device.getGeofenceIds() != null) { + oldGeofences.addAll(device.getGeofenceIds()); + } + List<Long> newGeofences = new ArrayList<>(currentGeofences); + newGeofences.removeAll(oldGeofences); + oldGeofences.removeAll(currentGeofences); + + device.setGeofenceIds(currentGeofences); + + Map<Event, Position> events = new HashMap<>(); + for (long geofenceId : oldGeofences) { + long calendarId = geofenceManager.getById(geofenceId).getCalendarId(); + Calendar calendar = calendarId != 0 ? calendarManager.getById(calendarId) : null; + if (calendar == null || calendar.checkMoment(position.getFixTime())) { + Event event = new Event(Event.TYPE_GEOFENCE_EXIT, position.getDeviceId(), position.getId()); + event.setGeofenceId(geofenceId); + events.put(event, position); + } + } + for (long geofenceId : newGeofences) { + long calendarId = geofenceManager.getById(geofenceId).getCalendarId(); + Calendar calendar = calendarId != 0 ? calendarManager.getById(calendarId) : null; + if (calendar == null || calendar.checkMoment(position.getFixTime())) { + Event event = new Event(Event.TYPE_GEOFENCE_ENTER, position.getDeviceId(), position.getId()); + event.setGeofenceId(geofenceId); + events.put(event, position); + } + } + return events; + } + +} diff --git a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java new file mode 100644 index 000000000..ec133bafc --- /dev/null +++ b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler.events; + +import java.util.Collections; +import java.util.Map; + +import io.netty.channel.ChannelHandler; +import org.traccar.database.IdentityManager; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class IgnitionEventHandler extends BaseEventHandler { + + private final IdentityManager identityManager; + + public IgnitionEventHandler(IdentityManager identityManager) { + this.identityManager = identityManager; + } + + @Override + protected Map<Event, Position> analyzePosition(Position position) { + Device device = identityManager.getById(position.getDeviceId()); + if (device == null || !identityManager.isLatestPosition(position)) { + return null; + } + + Map<Event, Position> result = null; + + if (position.getAttributes().containsKey(Position.KEY_IGNITION)) { + boolean ignition = position.getBoolean(Position.KEY_IGNITION); + + Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); + if (lastPosition != null && lastPosition.getAttributes().containsKey(Position.KEY_IGNITION)) { + boolean oldIgnition = lastPosition.getBoolean(Position.KEY_IGNITION); + + if (ignition && !oldIgnition) { + result = Collections.singletonMap( + new Event(Event.TYPE_IGNITION_ON, position.getDeviceId(), position.getId()), position); + } else if (!ignition && oldIgnition) { + result = Collections.singletonMap( + new Event(Event.TYPE_IGNITION_OFF, position.getDeviceId(), position.getId()), position); + } + } + } + return result; + } + +} diff --git a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java new file mode 100644 index 000000000..93ae74142 --- /dev/null +++ b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler.events; + +import java.util.HashMap; +import java.util.Map; + +import io.netty.channel.ChannelHandler; +import org.traccar.database.IdentityManager; +import org.traccar.database.MaintenancesManager; +import org.traccar.model.Event; +import org.traccar.model.Maintenance; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class MaintenanceEventHandler extends BaseEventHandler { + + private final IdentityManager identityManager; + private final MaintenancesManager maintenancesManager; + + public MaintenanceEventHandler(IdentityManager identityManager, MaintenancesManager maintenancesManager) { + this.identityManager = identityManager; + this.maintenancesManager = maintenancesManager; + } + + @Override + protected Map<Event, Position> analyzePosition(Position position) { + if (identityManager.getById(position.getDeviceId()) == null + || !identityManager.isLatestPosition(position)) { + return null; + } + + Position lastPosition = identityManager.getLastPosition(position.getDeviceId()); + if (lastPosition == null) { + return null; + } + + Map<Event, Position> events = new HashMap<>(); + for (long maintenanceId : maintenancesManager.getAllDeviceItems(position.getDeviceId())) { + Maintenance maintenance = maintenancesManager.getById(maintenanceId); + if (maintenance.getPeriod() != 0) { + double oldValue = lastPosition.getDouble(maintenance.getType()); + double newValue = position.getDouble(maintenance.getType()); + if (oldValue != 0.0 && newValue != 0.0 + && (long) ((oldValue - maintenance.getStart()) / maintenance.getPeriod()) + < (long) ((newValue - maintenance.getStart()) / maintenance.getPeriod())) { + Event event = new Event(Event.TYPE_MAINTENANCE, position.getDeviceId(), position.getId()); + event.setMaintenanceId(maintenanceId); + event.set(maintenance.getType(), newValue); + events.put(event, position); + } + } + } + + return events; + } + +} diff --git a/src/main/java/org/traccar/handler/events/MotionEventHandler.java b/src/main/java/org/traccar/handler/events/MotionEventHandler.java new file mode 100644 index 000000000..9ec02ccfb --- /dev/null +++ b/src/main/java/org/traccar/handler/events/MotionEventHandler.java @@ -0,0 +1,135 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler.events; + +import java.util.Collections; +import java.util.Map; + +import io.netty.channel.ChannelHandler; +import org.traccar.database.DeviceManager; +import org.traccar.database.IdentityManager; +import org.traccar.model.Device; +import org.traccar.model.DeviceState; +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.reports.ReportUtils; +import org.traccar.reports.model.TripsConfig; + +@ChannelHandler.Sharable +public class MotionEventHandler extends BaseEventHandler { + + private final IdentityManager identityManager; + private final DeviceManager deviceManager; + private final TripsConfig tripsConfig; + + public MotionEventHandler(IdentityManager identityManager, DeviceManager deviceManager, TripsConfig tripsConfig) { + this.identityManager = identityManager; + this.deviceManager = deviceManager; + this.tripsConfig = tripsConfig; + } + + private Map<Event, Position> newEvent(DeviceState deviceState, boolean newMotion) { + String eventType = newMotion ? Event.TYPE_DEVICE_MOVING : Event.TYPE_DEVICE_STOPPED; + Position position = deviceState.getMotionPosition(); + Event event = new Event(eventType, position.getDeviceId(), position.getId()); + deviceState.setMotionState(newMotion); + deviceState.setMotionPosition(null); + return Collections.singletonMap(event, position); + } + + public Map<Event, Position> updateMotionState(DeviceState deviceState) { + Map<Event, Position> result = null; + if (deviceState.getMotionState() != null && deviceState.getMotionPosition() != null) { + boolean newMotion = !deviceState.getMotionState(); + Position motionPosition = deviceState.getMotionPosition(); + long currentTime = System.currentTimeMillis(); + long motionTime = motionPosition.getFixTime().getTime() + + (newMotion ? tripsConfig.getMinimalTripDuration() : tripsConfig.getMinimalParkingDuration()); + if (motionTime <= currentTime) { + result = newEvent(deviceState, newMotion); + } + } + return result; + } + + public Map<Event, Position> updateMotionState(DeviceState deviceState, Position position) { + return updateMotionState(deviceState, position, position.getBoolean(Position.KEY_MOTION)); + } + + public Map<Event, Position> updateMotionState(DeviceState deviceState, Position position, boolean newMotion) { + Map<Event, Position> result = null; + Boolean oldMotion = deviceState.getMotionState(); + + long currentTime = position.getFixTime().getTime(); + if (newMotion != oldMotion) { + if (deviceState.getMotionPosition() == null) { + deviceState.setMotionPosition(position); + } + } else { + deviceState.setMotionPosition(null); + } + + Position motionPosition = deviceState.getMotionPosition(); + if (motionPosition != null) { + long motionTime = motionPosition.getFixTime().getTime(); + double distance = ReportUtils.calculateDistance(motionPosition, position, false); + Boolean ignition = null; + if (tripsConfig.getUseIgnition() + && position.getAttributes().containsKey(Position.KEY_IGNITION)) { + ignition = position.getBoolean(Position.KEY_IGNITION); + } + if (newMotion) { + if (motionTime + tripsConfig.getMinimalTripDuration() <= currentTime + || distance >= tripsConfig.getMinimalTripDistance()) { + result = newEvent(deviceState, newMotion); + } + } else { + if (motionTime + tripsConfig.getMinimalParkingDuration() <= currentTime + || ignition != null && !ignition) { + result = newEvent(deviceState, newMotion); + } + } + } + return result; + } + + @Override + protected Map<Event, Position> analyzePosition(Position position) { + + long deviceId = position.getDeviceId(); + Device device = identityManager.getById(deviceId); + if (device == null) { + return null; + } + if (!identityManager.isLatestPosition(position) + || !tripsConfig.getProcessInvalidPositions() && !position.getValid()) { + return null; + } + + Map<Event, Position> result = null; + DeviceState deviceState = deviceManager.getDeviceState(deviceId); + + if (deviceState.getMotionState() == null) { + deviceState.setMotionState(position.getBoolean(Position.KEY_MOTION)); + } else { + result = updateMotionState(deviceState, position); + } + deviceManager.setDeviceState(deviceId, deviceState); + return result; + } + +} diff --git a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java new file mode 100644 index 000000000..e534df9de --- /dev/null +++ b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java @@ -0,0 +1,164 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.handler.events; + +import java.util.Collections; +import java.util.Map; + +import io.netty.channel.ChannelHandler; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.database.DeviceManager; +import org.traccar.database.GeofenceManager; +import org.traccar.model.Device; +import org.traccar.model.DeviceState; +import org.traccar.model.Event; +import org.traccar.model.Geofence; +import org.traccar.model.Position; + +@ChannelHandler.Sharable +public class OverspeedEventHandler extends BaseEventHandler { + + public static final String ATTRIBUTE_SPEED = "speed"; + public static final String ATTRIBUTE_SPEED_LIMIT = "speedLimit"; + + private final DeviceManager deviceManager; + private final GeofenceManager geofenceManager; + + private final boolean notRepeat; + private final long minimalDuration; + private final boolean preferLowest; + + public OverspeedEventHandler(Config config, DeviceManager deviceManager, GeofenceManager geofenceManager) { + this.deviceManager = deviceManager; + this.geofenceManager = geofenceManager; + notRepeat = config.getBoolean(Keys.EVENT_OVERSPEED_NOT_REPEAT); + minimalDuration = config.getLong(Keys.EVENT_OVERSPEED_MINIMAL_DURATION) * 1000; + preferLowest = config.getBoolean(Keys.EVENT_OVERSPEED_PREFER_LOWEST); + } + + private Map<Event, Position> newEvent(DeviceState deviceState, double speedLimit) { + Position position = deviceState.getOverspeedPosition(); + Event event = new Event(Event.TYPE_DEVICE_OVERSPEED, position.getDeviceId(), position.getId()); + event.set(ATTRIBUTE_SPEED, deviceState.getOverspeedPosition().getSpeed()); + event.set(ATTRIBUTE_SPEED_LIMIT, speedLimit); + event.setGeofenceId(deviceState.getOverspeedGeofenceId()); + deviceState.setOverspeedState(notRepeat); + deviceState.setOverspeedPosition(null); + deviceState.setOverspeedGeofenceId(0); + return Collections.singletonMap(event, position); + } + + public Map<Event, Position> updateOverspeedState(DeviceState deviceState, double speedLimit) { + Map<Event, Position> result = null; + if (deviceState.getOverspeedState() != null && !deviceState.getOverspeedState() + && deviceState.getOverspeedPosition() != null && speedLimit != 0) { + long currentTime = System.currentTimeMillis(); + Position overspeedPosition = deviceState.getOverspeedPosition(); + long overspeedTime = overspeedPosition.getFixTime().getTime(); + if (overspeedTime + minimalDuration <= currentTime) { + result = newEvent(deviceState, speedLimit); + } + } + return result; + } + + public Map<Event, Position> updateOverspeedState( + DeviceState deviceState, Position position, double speedLimit, long geofenceId) { + Map<Event, Position> result = null; + + Boolean oldOverspeed = deviceState.getOverspeedState(); + + long currentTime = position.getFixTime().getTime(); + boolean newOverspeed = position.getSpeed() > speedLimit; + if (newOverspeed && !oldOverspeed) { + if (deviceState.getOverspeedPosition() == null) { + deviceState.setOverspeedPosition(position); + deviceState.setOverspeedGeofenceId(geofenceId); + } + } else if (oldOverspeed && !newOverspeed) { + deviceState.setOverspeedState(false); + deviceState.setOverspeedPosition(null); + deviceState.setOverspeedGeofenceId(0); + } else { + deviceState.setOverspeedPosition(null); + deviceState.setOverspeedGeofenceId(0); + } + Position overspeedPosition = deviceState.getOverspeedPosition(); + if (overspeedPosition != null) { + long overspeedTime = overspeedPosition.getFixTime().getTime(); + if (newOverspeed && overspeedTime + minimalDuration <= currentTime) { + result = newEvent(deviceState, speedLimit); + } + } + return result; + } + + @Override + protected Map<Event, Position> analyzePosition(Position position) { + + long deviceId = position.getDeviceId(); + Device device = deviceManager.getById(deviceId); + if (device == null) { + return null; + } + if (!deviceManager.isLatestPosition(position) || !position.getValid()) { + return null; + } + + double speedLimit = deviceManager.lookupAttributeDouble(deviceId, ATTRIBUTE_SPEED_LIMIT, 0, true, false); + + double geofenceSpeedLimit = 0; + long overspeedGeofenceId = 0; + + if (geofenceManager != null && device.getGeofenceIds() != null) { + for (long geofenceId : device.getGeofenceIds()) { + Geofence geofence = geofenceManager.getById(geofenceId); + if (geofence != null) { + double currentSpeedLimit = geofence.getDouble(ATTRIBUTE_SPEED_LIMIT); + if (currentSpeedLimit > 0 && geofenceSpeedLimit == 0 + || preferLowest && currentSpeedLimit < geofenceSpeedLimit + || !preferLowest && currentSpeedLimit > geofenceSpeedLimit) { + geofenceSpeedLimit = currentSpeedLimit; + overspeedGeofenceId = geofenceId; + } + } + } + } + if (geofenceSpeedLimit > 0) { + speedLimit = geofenceSpeedLimit; + } + + if (speedLimit == 0) { + return null; + } + + Map<Event, Position> result = null; + DeviceState deviceState = deviceManager.getDeviceState(deviceId); + + if (deviceState.getOverspeedState() == null) { + deviceState.setOverspeedState(position.getSpeed() > speedLimit); + deviceState.setOverspeedGeofenceId(position.getSpeed() > speedLimit ? overspeedGeofenceId : 0); + } else { + result = updateOverspeedState(deviceState, position, speedLimit, overspeedGeofenceId); + } + + deviceManager.setDeviceState(deviceId, deviceState); + return result; + } + +} diff --git a/src/main/java/org/traccar/handler/protocol/Arnavi4FrameDecoder.java b/src/main/java/org/traccar/handler/protocol/Arnavi4FrameDecoder.java new file mode 100644 index 000000000..b13f3fd7d --- /dev/null +++ b/src/main/java/org/traccar/handler/protocol/Arnavi4FrameDecoder.java @@ -0,0 +1,74 @@ +/* + * 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 org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.frame.FrameDecoder; + +public class Arnavi4FrameDecoder extends FrameDecoder { + + private static final int MIN_LENGTH = 4; + private static final int HEADER_LENGTH = 10; + private static final int PACKET_WRAPPER_LENGTH = 8; + private static final int COMMAND_ANSWER_PACKET_LENGTH = 4; + private static final int COMMAND_ANSWER_PARCEL_NUMBER = 0xfd; + private static final byte PACKAGE_END_SIGN = 0x5d; + + private boolean firstPacket = true; + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ChannelBuffer buf) throws Exception { + + if (buf.readableBytes() < MIN_LENGTH) { + return null; + } + + int length; + if (firstPacket) { + firstPacket = false; + length = HEADER_LENGTH; + } else { + int index = buf.getUnsignedByte(1); // parcel number + if (index == COMMAND_ANSWER_PARCEL_NUMBER) { + length = COMMAND_ANSWER_PACKET_LENGTH; + } else { + int pos = 2; // start sign + parcel number + while (pos + PACKET_WRAPPER_LENGTH < buf.readableBytes() + && buf.getByte(pos) != PACKAGE_END_SIGN) { + + int dataLength = buf.getUnsignedShort(pos + 1); + pos += PACKET_WRAPPER_LENGTH + dataLength; // packet type + data length + unixtime + data + crc + } + + if (buf.getByte(pos) != PACKAGE_END_SIGN) { // end sign + return null; + } + + length = pos + 1; + } + } + + if (buf.readableBytes() >= length) { + return buf.readBytes(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/handler/protocol/Arnavi4Protocol.java b/src/main/java/org/traccar/handler/protocol/Arnavi4Protocol.java new file mode 100644 index 000000000..381a9b457 --- /dev/null +++ b/src/main/java/org/traccar/handler/protocol/Arnavi4Protocol.java @@ -0,0 +1,45 @@ +/* + * 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 org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.ChannelPipeline; +import org.traccar.BaseProtocol; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; +import java.util.List; + +public class Arnavi4Protocol extends BaseProtocol { + + public Arnavi4Protocol() { + super("arnavi4"); + } + + @Override + public void initTrackerServers(List<TrackerServer> serverList) { + TrackerServer server = new TrackerServer(new ServerBootstrap(), getName()) { + @Override + protected void addSpecificHandlers(ChannelPipeline pipeline) { + pipeline.addLast("frameDecoder", new Arnavi4FrameDecoder()); + pipeline.addLast("objectDecoder", new Arnavi4ProtocolDecoder(Arnavi4Protocol.this)); + } + }; + server.setEndianness(ByteOrder.LITTLE_ENDIAN); + serverList.add(server); + } + +} diff --git a/src/main/java/org/traccar/handler/protocol/Arnavi4ProtocolDecoder.java b/src/main/java/org/traccar/handler/protocol/Arnavi4ProtocolDecoder.java new file mode 100644 index 000000000..06abb563f --- /dev/null +++ b/src/main/java/org/traccar/handler/protocol/Arnavi4ProtocolDecoder.java @@ -0,0 +1,182 @@ +/* + * 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 org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.helper.Checksum; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class Arnavi4ProtocolDecoder 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 Arnavi4ProtocolDecoder(Arnavi4Protocol protocol) { + super(protocol); + } + + private void sendResponse(Channel channel, byte version, int index) { + if (channel != null) { + final ChannelBuffer response; + if (version == HEADER_VERSION_1) { + response = ChannelBuffers.dynamicBuffer(ByteOrder.LITTLE_ENDIAN, 4); + response.writeByte(0x7b); + response.writeByte(0x00); + response.writeByte((byte) index); + response.writeByte(0x7d); + } else if (version == HEADER_VERSION_2) { + response = ChannelBuffers.dynamicBuffer(ByteOrder.LITTLE_ENDIAN, 9); + response.writeByte(0x7b); + response.writeByte(0x04); + response.writeByte(0x00); + byte[] timeBytes = ByteBuffer.allocate(4).putInt((int) (System.currentTimeMillis() / 1000)).array(); + response.writeByte(Checksum.modulo256(timeBytes)); + response.writeBytes(timeBytes); + response.writeByte(0x7d); + } else { + return; // Ignore unsupported header's versions + } + channel.write(response); + } + } + + private Position decodePosition(DeviceSession deviceSession, ChannelBuffer 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.readFloat()); + position.setValid(true); + break; + + case TAG_LONGITUDE: + position.setLongitude(buf.readFloat()); + 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); // gps+glonass + position.setSpeed(buf.readUnsignedByte()); + break; + + default: + buf.readBytes(4); // Skip unsupported tags + break; + } + + readBytes += 5; // 1 byte tag + 4 bytes value + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ChannelBuffer buf = (ChannelBuffer) msg; + + byte startSign = buf.readByte(); + + if (startSign == HEADER_START_SIGN) { + + byte version = buf.readByte(); + + String imei = String.valueOf(buf.readLong()); + 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.readUnsignedShort(); + Date time = new Date(buf.readUnsignedInt() * 1000); + + if (recordType == RECORD_DATA) { + positions.add(decodePosition(deviceSession, buf, length, time)); + } else { + buf.readBytes(length); // Skip other types of record + } + + buf.readUnsignedByte(); // crc + break; + + default: + return null; // Ignore unsupported types of record + } + + recordType = buf.readByte(); + } + + sendResponse(channel, HEADER_VERSION_1, index); + + return positions; + } + +} diff --git a/src/main/java/org/traccar/helper/BcdUtil.java b/src/main/java/org/traccar/helper/BcdUtil.java new file mode 100644 index 000000000..c87529e32 --- /dev/null +++ b/src/main/java/org/traccar/helper/BcdUtil.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import io.netty.buffer.ByteBuf; + +public final class BcdUtil { + + private BcdUtil() { + } + + public static int readInteger(ByteBuf buf, int digits) { + int result = 0; + + for (int i = 0; i < digits / 2; i++) { + int b = buf.readUnsignedByte(); + result *= 10; + result += b >>> 4; + result *= 10; + result += b & 0x0f; + } + + if (digits % 2 != 0) { + int b = buf.getUnsignedByte(buf.readerIndex()); + result *= 10; + result += b >>> 4; + } + + return result; + } + + public static double readCoordinate(ByteBuf buf) { + int b1 = buf.readUnsignedByte(); + int b2 = buf.readUnsignedByte(); + int b3 = buf.readUnsignedByte(); + int b4 = buf.readUnsignedByte(); + + double value = (b2 & 0xf) * 10 + (b3 >> 4); + value += (((b3 & 0xf) * 10 + (b4 >> 4)) * 10 + (b4 & 0xf)) / 1000.0; + value /= 60; + value += ((b1 >> 4 & 0x7) * 10 + (b1 & 0xf)) * 10 + (b2 >> 4); + + if ((b1 & 0x80) != 0) { + value = -value; + } + + return value; + } + +} diff --git a/src/main/java/org/traccar/helper/BitBuffer.java b/src/main/java/org/traccar/helper/BitBuffer.java new file mode 100644 index 000000000..f30a4557b --- /dev/null +++ b/src/main/java/org/traccar/helper/BitBuffer.java @@ -0,0 +1,101 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +public class BitBuffer { + + private final ByteBuf buffer; + + private int writeByte; + private int writeCount; + + private int readByte; + private int readCount; + + public BitBuffer() { + buffer = Unpooled.buffer(); + } + + public BitBuffer(ByteBuf buffer) { + this.buffer = buffer; + } + + public void writeEncoded(byte[] bytes) { + for (byte b : bytes) { + b -= 48; + if (b > 40) { + b -= 8; + } + write(b); + } + } + + public void write(int b) { + if (writeCount == 0) { + writeByte |= b; + writeCount = 6; + } else { + int remaining = 8 - writeCount; + writeByte <<= remaining; + writeByte |= b >> (6 - remaining); + buffer.writeByte(writeByte); + writeByte = b & ((1 << (6 - remaining)) - 1); + writeCount = 6 - remaining; + } + } + + public int readUnsigned(int length) { + int result = 0; + + while (length > 0) { + if (readCount == 0) { + readByte = buffer.readUnsignedByte(); + readCount = 8; + } + if (readCount >= length) { + result <<= length; + result |= readByte >> (readCount - length); + readByte &= (1 << (readCount - length)) - 1; + readCount -= length; + length = 0; + } else { + result <<= readCount; + result |= readByte; + length -= readCount; + readByte = 0; + readCount = 0; + } + } + + return result; + } + + public int readSigned(int length) { + int result = readUnsigned(length); + int signBit = 1 << (length - 1); + if ((result & signBit) == 0) { + return result; + } else { + result &= signBit - 1; + result += ~(signBit - 1); + return result; + } + } + +} diff --git a/src/main/java/org/traccar/helper/BitUtil.java b/src/main/java/org/traccar/helper/BitUtil.java new file mode 100644 index 000000000..b6108edff --- /dev/null +++ b/src/main/java/org/traccar/helper/BitUtil.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015 Anton Tananaev (anton@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; + +public final class BitUtil { + + private BitUtil() { + } + + public static boolean check(long number, int index) { + return (number & (1 << index)) != 0; + } + + public static int between(int number, int from, int to) { + return (number >> from) & ((1 << to - from) - 1); + } + + public static int from(int number, int from) { + return number >> from; + } + + public static int to(int number, int to) { + return between(number, 0, to); + } + + public static long between(long number, int from, int to) { + return (number >> from) & ((1L << to - from) - 1L); + } + + public static long from(long number, int from) { + return number >> from; + } + + public static long to(long number, int to) { + return between(number, 0, to); + } + +} diff --git a/src/main/java/org/traccar/helper/BufferUtil.java b/src/main/java/org/traccar/helper/BufferUtil.java new file mode 100644 index 000000000..15c619ec5 --- /dev/null +++ b/src/main/java/org/traccar/helper/BufferUtil.java @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import java.nio.charset.StandardCharsets; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; + +public final class BufferUtil { + + private BufferUtil() { + } + + public static int indexOf(String needle, ByteBuf haystack) { + ByteBuf needleBuffer = Unpooled.wrappedBuffer(needle.getBytes(StandardCharsets.US_ASCII)); + try { + return ByteBufUtil.indexOf(needleBuffer, haystack); + } finally { + needleBuffer.release(); + } + } + + public static int indexOf(String needle, ByteBuf haystack, int startIndex, int endIndex) { + ByteBuf wrappedHaystack = Unpooled.wrappedBuffer(haystack); + wrappedHaystack.readerIndex(startIndex - haystack.readerIndex()); + wrappedHaystack.writerIndex(endIndex - haystack.readerIndex()); + int result = indexOf(needle, wrappedHaystack); + return result < 0 ? result : haystack.readerIndex() + result; + } + +} diff --git a/src/main/java/org/traccar/helper/Checksum.java b/src/main/java/org/traccar/helper/Checksum.java new file mode 100644 index 000000000..adfa697c5 --- /dev/null +++ b/src/main/java/org/traccar/helper/Checksum.java @@ -0,0 +1,200 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.zip.CRC32; + +public final class Checksum { + + private Checksum() { + } + + public static class Algorithm { + + private int poly; + private int init; + private boolean refIn; + private boolean refOut; + private int xorOut; + private int[] table; + + public Algorithm(int bits, int poly, int init, boolean refIn, boolean refOut, int xorOut) { + this.poly = poly; + this.init = init; + this.refIn = refIn; + this.refOut = refOut; + this.xorOut = xorOut; + this.table = bits == 8 ? initTable8() : initTable16(); + } + + private int[] initTable8() { + int[] table = new int[256]; + int crc; + for (int i = 0; i < 256; i++) { + crc = i; + for (int j = 0; j < 8; j++) { + boolean bit = (crc & 0x80) != 0; + crc <<= 1; + if (bit) { + crc ^= poly; + } + } + table[i] = crc & 0xFF; + } + return table; + } + + private int[] initTable16() { + int[] table = new int[256]; + int crc; + for (int i = 0; i < 256; i++) { + crc = i << 8; + for (int j = 0; j < 8; j++) { + boolean bit = (crc & 0x8000) != 0; + crc <<= 1; + if (bit) { + crc ^= poly; + } + } + table[i] = crc & 0xFFFF; + } + return table; + } + + } + + private static int reverse(int value, int bits) { + int result = 0; + for (int i = 0; i < bits; i++) { + result = (result << 1) | (value & 1); + value >>= 1; + } + return result; + } + + public static int crc8(Algorithm algorithm, ByteBuffer buf) { + int crc = algorithm.init; + while (buf.hasRemaining()) { + int b = buf.get() & 0xFF; + if (algorithm.refIn) { + b = reverse(b, 8); + } + crc = algorithm.table[(crc & 0xFF) ^ b]; + } + if (algorithm.refOut) { + crc = reverse(crc, 8); + } + return (crc ^ algorithm.xorOut) & 0xFF; + } + + public static int crc16(Algorithm algorithm, ByteBuffer buf) { + int crc = algorithm.init; + while (buf.hasRemaining()) { + int b = buf.get() & 0xFF; + if (algorithm.refIn) { + b = reverse(b, 8); + } + crc = (crc << 8) ^ algorithm.table[((crc >> 8) & 0xFF) ^ b]; + } + if (algorithm.refOut) { + crc = reverse(crc, 16); + } + return (crc ^ algorithm.xorOut) & 0xFFFF; + } + + public static final Algorithm CRC8_EGTS = new Algorithm(8, 0x31, 0xFF, false, false, 0x00); + public static final Algorithm CRC8_ROHC = new Algorithm(8, 0x07, 0xFF, true, true, 0x00); + + public static final Algorithm CRC16_IBM = new Algorithm(16, 0x8005, 0x0000, true, true, 0x0000); + public static final Algorithm CRC16_X25 = new Algorithm(16, 0x1021, 0xFFFF, true, true, 0xFFFF); + public static final Algorithm CRC16_MODBUS = new Algorithm(16, 0x8005, 0xFFFF, true, true, 0x0000); + public static final Algorithm CRC16_CCITT_FALSE = new Algorithm(16, 0x1021, 0xFFFF, false, false, 0x0000); + public static final Algorithm CRC16_KERMIT = new Algorithm(16, 0x1021, 0x0000, true, true, 0x0000); + public static final Algorithm CRC16_XMODEM = new Algorithm(16, 0x1021, 0x0000, false, false, 0x0000); + + public static int crc32(ByteBuffer buf) { + CRC32 checksum = new CRC32(); + while (buf.hasRemaining()) { + checksum.update(buf.get()); + } + return (int) checksum.getValue(); + } + + public static int xor(ByteBuffer buf) { + int checksum = 0; + while (buf.hasRemaining()) { + checksum ^= buf.get(); + } + return checksum; + } + + public static int xor(String string) { + byte checksum = 0; + for (byte b : string.getBytes(StandardCharsets.US_ASCII)) { + checksum ^= b; + } + return checksum; + } + + public static String nmea(String msg) { + int checksum = 0; + byte[] bytes = msg.getBytes(StandardCharsets.US_ASCII); + for (int i = 1; i < bytes.length; i++) { + checksum ^= bytes[i]; + } + return String.format("*%02x", checksum).toUpperCase(); + } + + public static int sum(ByteBuffer buf) { + byte checksum = 0; + while (buf.hasRemaining()) { + checksum += buf.get(); + } + return checksum; + } + + public static String sum(String msg) { + byte checksum = 0; + for (byte b : msg.getBytes(StandardCharsets.US_ASCII)) { + checksum += b; + } + return String.format("%02X", checksum).toUpperCase(); + } + + public static long luhn(long imei) { + long checksum = 0; + long remain = imei; + + for (int i = 0; remain != 0; i++) { + long digit = remain % 10; + + if (i % 2 == 0) { + digit *= 2; + if (digit >= 10) { + digit = 1 + (digit % 10); + } + } + + checksum += digit; + remain /= 10; + } + + return (10 - (checksum % 10)) % 10; + } + +} diff --git a/src/main/java/org/traccar/helper/DataConverter.java b/src/main/java/org/traccar/helper/DataConverter.java new file mode 100644 index 000000000..7abd4ae93 --- /dev/null +++ b/src/main/java/org/traccar/helper/DataConverter.java @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; + +public final class DataConverter { + + private DataConverter() { + } + + public static byte[] parseHex(String string) { + try { + return Hex.decodeHex(string); + } catch (DecoderException e) { + throw new RuntimeException(e); + } + } + + public static String printHex(byte[] data) { + return Hex.encodeHexString(data); + } + + public static byte[] parseBase64(String string) { + return Base64.decodeBase64(string); + } + + public static String printBase64(byte[] data) { + return Base64.encodeBase64String(data); + } + +} diff --git a/src/main/java/org/traccar/helper/DateBuilder.java b/src/main/java/org/traccar/helper/DateBuilder.java new file mode 100644 index 000000000..9752f6977 --- /dev/null +++ b/src/main/java/org/traccar/helper/DateBuilder.java @@ -0,0 +1,128 @@ +/* + * Copyright 2015 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +public class DateBuilder { + + private Calendar calendar; + + public DateBuilder() { + this(TimeZone.getTimeZone("UTC")); + } + + public DateBuilder(Date time) { + this(time, TimeZone.getTimeZone("UTC")); + } + + public DateBuilder(TimeZone timeZone) { + this(new Date(0), timeZone); + } + + public DateBuilder(Date time, TimeZone timeZone) { + calendar = Calendar.getInstance(timeZone); + calendar.clear(); + calendar.setTimeInMillis(time.getTime()); + } + + public DateBuilder setYear(int year) { + if (year < 100) { + year += 2000; + } + calendar.set(Calendar.YEAR, year); + return this; + } + + public DateBuilder setMonth(int month) { + calendar.set(Calendar.MONTH, month - 1); + return this; + } + + public DateBuilder setDay(int day) { + calendar.set(Calendar.DAY_OF_MONTH, day); + return this; + } + + public DateBuilder setDate(int year, int month, int day) { + return setYear(year).setMonth(month).setDay(day); + } + + public DateBuilder setDateReverse(int day, int month, int year) { + return setDate(year, month, day); + } + + public DateBuilder setCurrentDate() { + Calendar now = Calendar.getInstance(calendar.getTimeZone()); + return setYear(now.get(Calendar.YEAR)) + .setMonth(now.get(Calendar.MONTH) + 1) + .setDay(now.get(Calendar.DAY_OF_MONTH)); + } + + public DateBuilder setHour(int hour) { + calendar.set(Calendar.HOUR_OF_DAY, hour); + return this; + } + + public DateBuilder setMinute(int minute) { + calendar.set(Calendar.MINUTE, minute); + return this; + } + + public DateBuilder addMinute(int minute) { + calendar.add(Calendar.MINUTE, minute); + return this; + } + + public DateBuilder setSecond(int second) { + calendar.set(Calendar.SECOND, second); + return this; + } + + public DateBuilder addSeconds(long seconds) { + calendar.setTimeInMillis(calendar.getTimeInMillis() + seconds * 1000); + return this; + } + + public DateBuilder setMillis(int millis) { + calendar.set(Calendar.MILLISECOND, millis); + return this; + } + + public DateBuilder addMillis(long millis) { + calendar.setTimeInMillis(calendar.getTimeInMillis() + millis); + return this; + } + + public DateBuilder setTime(int hour, int minute, int second) { + return setHour(hour).setMinute(minute).setSecond(second); + } + + public DateBuilder setTimeReverse(int second, int minute, int hour) { + return setHour(hour).setMinute(minute).setSecond(second); + } + + public DateBuilder setTime(int hour, int minute, int second, int millis) { + return setHour(hour).setMinute(minute).setSecond(second).setMillis(millis); + } + + public Date getDate() { + return calendar.getTime(); + } + +} diff --git a/src/main/java/org/traccar/helper/DateUtil.java b/src/main/java/org/traccar/helper/DateUtil.java new file mode 100644 index 000000000..20a483e3c --- /dev/null +++ b/src/main/java/org/traccar/helper/DateUtil.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.Date; + +public final class DateUtil { + + private DateUtil() { + } + + public static Date correctDay(Date guess) { + return correctDate(new Date(), guess, Calendar.DAY_OF_MONTH); + } + + public static Date correctYear(Date guess) { + return correctDate(new Date(), guess, Calendar.YEAR); + } + + public static Date correctDate(Date now, Date guess, int field) { + + if (guess.getTime() > now.getTime()) { + Date previous = dateAdd(guess, field, -1); + if (now.getTime() - previous.getTime() < guess.getTime() - now.getTime()) { + return previous; + } + } else if (guess.getTime() < now.getTime()) { + Date next = dateAdd(guess, field, 1); + if (next.getTime() - now.getTime() < now.getTime() - guess.getTime()) { + return next; + } + } + + return guess; + } + + private static Date dateAdd(Date guess, int field, int amount) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(guess); + calendar.add(field, amount); + return calendar.getTime(); + } + + public static Date parseDate(String value) { + return Date.from(Instant.from(DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(value))); + } + + public static String formatDate(Date date) { + return formatDate(date, true); + } + + public static String formatDate(Date date, boolean zoned) { + if (zoned) { + return DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.systemDefault()).format(date.toInstant()); + } else { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); + } + } + +} diff --git a/src/main/java/org/traccar/helper/DistanceCalculator.java b/src/main/java/org/traccar/helper/DistanceCalculator.java new file mode 100644 index 000000000..88d4ef8a4 --- /dev/null +++ b/src/main/java/org/traccar/helper/DistanceCalculator.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 - 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +public final class DistanceCalculator { + + private DistanceCalculator() { + } + + private static final double EQUATORIAL_EARTH_RADIUS = 6378.1370; + private static final double DEG_TO_RAD = Math.PI / 180; + + public static double distance(double lat1, double lon1, double lat2, double lon2) { + double dlong = (lon2 - lon1) * DEG_TO_RAD; + double dlat = (lat2 - lat1) * DEG_TO_RAD; + double a = Math.pow(Math.sin(dlat / 2), 2) + + Math.cos(lat1 * DEG_TO_RAD) * Math.cos(lat2 * DEG_TO_RAD) * Math.pow(Math.sin(dlong / 2), 2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + double d = EQUATORIAL_EARTH_RADIUS * c; + return d * 1000; + } + + public static double distanceToLine( + double pointLat, double pointLon, double lat1, double lon1, double lat2, double lon2) { + double d0 = distance(pointLat, pointLon, lat1, lon1); + double d1 = distance(lat1, lon1, lat2, lon2); + double d2 = distance(lat2, lon2, pointLat, pointLon); + if (Math.pow(d0, 2) > Math.pow(d1, 2) + Math.pow(d2, 2)) { + return d2; + } + if (Math.pow(d2, 2) > Math.pow(d1, 2) + Math.pow(d0, 2)) { + return d0; + } + double halfP = (d0 + d1 + d2) * 0.5; + double area = Math.sqrt(halfP * (halfP - d0) * (halfP - d1) * (halfP - d2)); + return 2 * area / d1; + } + +} diff --git a/src/main/java/org/traccar/helper/Hashing.java b/src/main/java/org/traccar/helper/Hashing.java new file mode 100644 index 000000000..e91310eda --- /dev/null +++ b/src/main/java/org/traccar/helper/Hashing.java @@ -0,0 +1,100 @@ +/* + * Copyright 2015 Anton Tananaev (anton@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.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; + +public final class Hashing { + + public static final int ITERATIONS = 1000; + public static final int SALT_SIZE = 24; + public static final int HASH_SIZE = 24; + + private static SecretKeyFactory factory; + static { + try { + factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + public static class HashingResult { + + private final String hash; + private final String salt; + + public HashingResult(String hash, String salt) { + this.hash = hash; + this.salt = salt; + } + + public String getHash() { + return hash; + } + + public String getSalt() { + return salt; + } + } + + private Hashing() { + } + + private static byte[] function(char[] password, byte[] salt) { + try { + PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, HASH_SIZE * Byte.SIZE); + return factory.generateSecret(spec).getEncoded(); + } catch (InvalidKeySpecException e) { + throw new SecurityException(e); + } + } + + private static final SecureRandom RANDOM = new SecureRandom(); + + public static HashingResult createHash(String password) { + byte[] salt = new byte[SALT_SIZE]; + RANDOM.nextBytes(salt); + byte[] hash = function(password.toCharArray(), salt); + return new HashingResult( + DataConverter.printHex(hash), + DataConverter.printHex(salt)); + } + + public static boolean validatePassword(String password, String hashHex, String saltHex) { + byte[] hash = DataConverter.parseHex(hashHex); + byte[] salt = DataConverter.parseHex(saltHex); + return slowEquals(hash, function(password.toCharArray(), salt)); + } + + /** + * Compares two byte arrays in length-constant time. This comparison method + * is used so that password hashes cannot be extracted from an on-line + * system using a timing attack and then attacked off-line. + */ + private static boolean slowEquals(byte[] a, byte[] b) { + int diff = a.length ^ b.length; + for (int i = 0; i < a.length && i < b.length; i++) { + diff |= a[i] ^ b[i]; + } + return diff == 0; + } + +} diff --git a/src/main/java/org/traccar/helper/LocationTree.java b/src/main/java/org/traccar/helper/LocationTree.java new file mode 100644 index 000000000..3aff3ce33 --- /dev/null +++ b/src/main/java/org/traccar/helper/LocationTree.java @@ -0,0 +1,125 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class LocationTree { + + public static class Item { + + private Item left, right; + private float x, y; + private String data; + + public Item(float x, float y) { + this(x, y, null); + } + + public Item(float x, float y, String data) { + this.x = x; + this.y = y; + this.data = data; + } + + public String getData() { + return data; + } + + private float squaredDistance(Item item) { + return (x - item.x) * (x - item.x) + (y - item.y) * (y - item.y); + } + + private float axisSquaredDistance(Item item, int axis) { + if (axis == 0) { + return (x - item.x) * (x - item.x); + } else { + return (y - item.y) * (y - item.y); + } + } + + } + + private Item root; + + private ArrayList<Comparator<Item>> comparators = new ArrayList<>(); + + public LocationTree(List<Item> items) { + comparators.add(new Comparator<Item>() { + @Override + public int compare(Item o1, Item o2) { + return Float.compare(o1.x, o2.x); + } + }); + comparators.add(new Comparator<Item>() { + @Override + public int compare(Item o1, Item o2) { + return Float.compare(o1.y, o2.y); + } + }); + root = createTree(items, 0); + } + + private Item createTree(List<Item> items, int depth) { + if (items.isEmpty()) { + return null; + } + Collections.sort(items, comparators.get(depth % 2)); + int currentIndex = items.size() / 2; + Item median = items.get(currentIndex); + median.left = createTree(new ArrayList<>(items.subList(0, currentIndex)), depth + 1); + median.right = createTree(new ArrayList<>(items.subList(currentIndex + 1, items.size())), depth + 1); + return median; + } + + public Item findNearest(Item search) { + return findNearest(root, search, 0); + } + + private Item findNearest(Item current, Item search, int depth) { + int direction = comparators.get(depth % 2).compare(search, current); + + Item next, other; + if (direction < 0) { + next = current.left; + other = current.right; + } else { + next = current.right; + other = current.left; + } + + Item best = current; + if (next != null) { + best = findNearest(next, search, depth + 1); + } + + if (current.squaredDistance(search) < best.squaredDistance(search)) { + best = current; + } + if (other != null && current.axisSquaredDistance(search, depth % 2) < best.squaredDistance(search)) { + Item possibleBest = findNearest(other, search, depth + 1); + if (possibleBest.squaredDistance(search) < best.squaredDistance(search)) { + best = possibleBest; + } + } + + return best; + } + +} diff --git a/src/main/java/org/traccar/helper/Log.java b/src/main/java/org/traccar/helper/Log.java new file mode 100644 index 000000000..358f11a31 --- /dev/null +++ b/src/main/java/org/traccar/helper/Log.java @@ -0,0 +1,271 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import org.traccar.config.Config; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.ConsoleHandler; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +public final class Log { + + private Log() { + } + + private static final String STACK_PACKAGE = "org.traccar"; + private static final int STACK_LIMIT = 3; + + private static class RollingFileHandler extends Handler { + + private String name; + private String suffix; + private Writer writer; + private boolean rotate; + + RollingFileHandler(String name, boolean rotate) { + this.name = name; + this.rotate = rotate; + } + + @Override + public synchronized void publish(LogRecord record) { + if (isLoggable(record)) { + try { + String suffix = ""; + if (rotate) { + suffix = new SimpleDateFormat("yyyyMMdd").format(new Date(record.getMillis())); + if (writer != null && !suffix.equals(this.suffix)) { + writer.close(); + writer = null; + if (!new File(name).renameTo(new File(name + "." + this.suffix))) { + throw new RuntimeException("Log file renaming failed"); + } + } + } + if (writer == null) { + this.suffix = suffix; + writer = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(name, true), StandardCharsets.UTF_8)); + } + writer.write(getFormatter().format(record)); + writer.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public synchronized void flush() { + if (writer != null) { + try { + writer.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public synchronized void close() throws SecurityException { + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + } + + public static class LogFormatter extends Formatter { + + private boolean fullStackTraces; + + LogFormatter(boolean fullStackTraces) { + this.fullStackTraces = fullStackTraces; + } + + private static String formatLevel(Level level) { + switch (level.getName()) { + case "FINEST": + return "TRACE"; + case "FINER": + case "FINE": + case "CONFIG": + return "DEBUG"; + case "INFO": + return "INFO"; + case "WARNING": + return "WARN"; + case "SEVERE": + default: + return "ERROR"; + } + } + + @Override + public String format(LogRecord record) { + StringBuilder message = new StringBuilder(); + + if (record.getMessage() != null) { + message.append(record.getMessage()); + } + + if (record.getThrown() != null) { + if (message.length() > 0) { + message.append(" - "); + } + if (fullStackTraces) { + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + record.getThrown().printStackTrace(printWriter); + message.append(System.lineSeparator()).append(stringWriter.toString()); + } else { + message.append(exceptionStack(record.getThrown())); + } + } + + return String.format("%1$tF %1$tT %2$5s: %3$s%n", + new Date(record.getMillis()), formatLevel(record.getLevel()), message.toString()); + } + + } + + public static void setupDefaultLogger() { + String path = null; + URL url = ClassLoader.getSystemClassLoader().getResource("."); + if (url != null) { + File jarPath = new File(url.getPath()); + File logsPath = new File(jarPath, "logs"); + if (!logsPath.exists() || !logsPath.isDirectory()) { + logsPath = jarPath; + } + path = new File(logsPath, "tracker-server.log").getPath(); + } + setupLogger(path == null, path, Level.WARNING.getName(), false, true); + } + + public static void setupLogger(Config config) { + setupLogger( + config.getBoolean("logger.console"), + config.getString("logger.file"), + config.getString("logger.level"), + config.getBoolean("logger.fullStackTraces"), + config.getBoolean("logger.rotate")); + } + + private static void setupLogger( + boolean console, String file, String levelString, boolean fullStackTraces, boolean rotate) { + + Logger rootLogger = Logger.getLogger(""); + for (Handler handler : rootLogger.getHandlers()) { + rootLogger.removeHandler(handler); + } + + Handler handler; + if (console) { + handler = new ConsoleHandler(); + } else { + handler = new RollingFileHandler(file, rotate); + } + + handler.setFormatter(new LogFormatter(fullStackTraces)); + + Level level = Level.parse(levelString.toUpperCase()); + rootLogger.setLevel(level); + handler.setLevel(level); + handler.setFilter(record -> record != null && !record.getLoggerName().startsWith("sun")); + + rootLogger.addHandler(handler); + } + + public static String exceptionStack(Throwable exception) { + Throwable cause = exception.getCause(); + while (cause != null && exception != cause) { + exception = cause; + cause = cause.getCause(); + } + + StringBuilder s = new StringBuilder(); + String exceptionMsg = exception.getMessage(); + if (exceptionMsg != null) { + s.append(exceptionMsg); + s.append(" - "); + } + s.append(exception.getClass().getSimpleName()); + StackTraceElement[] stack = exception.getStackTrace(); + + if (stack.length > 0) { + int count = STACK_LIMIT; + boolean first = true; + boolean skip = false; + String file = ""; + s.append(" ("); + for (StackTraceElement element : stack) { + if (count > 0 && element.getClassName().startsWith(STACK_PACKAGE)) { + if (!first) { + s.append(" < "); + } else { + first = false; + } + + if (skip) { + s.append("... < "); + skip = false; + } + + if (file.equals(element.getFileName())) { + s.append("*"); + } else { + file = element.getFileName(); + s.append(file, 0, file.length() - 5); // remove ".java" + count -= 1; + } + s.append(":").append(element.getLineNumber()); + } else { + skip = true; + } + } + if (skip) { + if (!first) { + s.append(" < "); + } + s.append("..."); + } + s.append(")"); + } + return s.toString(); + } + +} diff --git a/src/main/java/org/traccar/helper/LogAction.java b/src/main/java/org/traccar/helper/LogAction.java new file mode 100644 index 000000000..16d55ec60 --- /dev/null +++ b/src/main/java/org/traccar/helper/LogAction.java @@ -0,0 +1,107 @@ +/* + * Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import java.beans.Introspector; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.model.BaseModel; + +public final class LogAction { + + private static final Logger LOGGER = LoggerFactory.getLogger(LogAction.class); + + private LogAction() { + } + + private static final String ACTION_CREATE = "create"; + private static final String ACTION_EDIT = "edit"; + private static final String ACTION_REMOVE = "remove"; + + private static final String ACTION_LINK = "link"; + private static final String ACTION_UNLINK = "unlink"; + + private static final String ACTION_LOGIN = "login"; + private static final String ACTION_LOGOUT = "logout"; + + private static final String ACTION_DEVICE_ACCUMULATORS = "resetDeviceAccumulators"; + + private static final String PATTERN_OBJECT = "user: %d, action: %s, object: %s, id: %d"; + private static final String PATTERN_LINK = "user: %d, action: %s, owner: %s, id: %d, property: %s, id: %d"; + private static final String PATTERN_LOGIN = "user: %d, action: %s"; + private static final String PATTERN_LOGIN_FAILED = "login failed from: %s"; + private static final String PATTERN_DEVICE_ACCUMULATORS = "user: %d, action: %s, deviceId: %d"; + + public static void create(long userId, BaseModel object) { + logObjectAction(ACTION_CREATE, userId, object.getClass(), object.getId()); + } + + public static void edit(long userId, BaseModel object) { + logObjectAction(ACTION_EDIT, userId, object.getClass(), object.getId()); + } + + public static void remove(long userId, Class<?> clazz, long objectId) { + logObjectAction(ACTION_REMOVE, userId, clazz, objectId); + } + + public static void link(long userId, Class<?> owner, long ownerId, Class<?> property, long propertyId) { + logLinkAction(ACTION_LINK, userId, owner, ownerId, property, propertyId); + } + + public static void unlink(long userId, Class<?> owner, long ownerId, Class<?> property, long propertyId) { + logLinkAction(ACTION_UNLINK, userId, owner, ownerId, property, propertyId); + } + + public static void login(long userId) { + logLoginAction(ACTION_LOGIN, userId); + } + + public static void logout(long userId) { + logLoginAction(ACTION_LOGOUT, userId); + } + + public static void failedLogin(String remoteAddress) { + if (remoteAddress == null || remoteAddress.isEmpty()) { + remoteAddress = "unknown"; + } + LOGGER.info(String.format(PATTERN_LOGIN_FAILED, remoteAddress)); + } + + public static void resetDeviceAccumulators(long userId, long deviceId) { + LOGGER.info(String.format( + PATTERN_DEVICE_ACCUMULATORS, userId, ACTION_DEVICE_ACCUMULATORS, deviceId)); + } + + private static void logObjectAction(String action, long userId, Class<?> clazz, long objectId) { + LOGGER.info(String.format( + PATTERN_OBJECT, userId, action, Introspector.decapitalize(clazz.getSimpleName()), objectId)); + } + + private static void logLinkAction(String action, long userId, + Class<?> owner, long ownerId, Class<?> property, long propertyId) { + LOGGER.info(String.format( + PATTERN_LINK, userId, action, + Introspector.decapitalize(owner.getSimpleName()), ownerId, + Introspector.decapitalize(property.getSimpleName()), propertyId)); + } + + private static void logLoginAction(String action, long userId) { + LOGGER.info(String.format(PATTERN_LOGIN, userId, action)); + } + +} diff --git a/src/main/java/org/traccar/helper/ObdDecoder.java b/src/main/java/org/traccar/helper/ObdDecoder.java new file mode 100644 index 000000000..1bdcce352 --- /dev/null +++ b/src/main/java/org/traccar/helper/ObdDecoder.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.traccar.model.Position; + +import java.util.AbstractMap; +import java.util.Map; + +public final class ObdDecoder { + + private ObdDecoder() { + } + + private static final int MODE_CURRENT = 0x01; + private static final int MODE_FREEZE_FRAME = 0x02; + private static final int MODE_CODES = 0x03; + + public static Map.Entry<String, Object> decode(int mode, String value) { + switch (mode) { + case MODE_CURRENT: + case MODE_FREEZE_FRAME: + return decodeData( + Integer.parseInt(value.substring(0, 2), 16), + Integer.parseInt(value.substring(2), 16), true); + case MODE_CODES: + return decodeCodes(value); + default: + return null; + } + } + + private static Map.Entry<String, Object> createEntry(String key, Object value) { + return new AbstractMap.SimpleEntry<>(key, value); + } + + public static Map.Entry<String, Object> decodeCodes(String value) { + StringBuilder codes = new StringBuilder(); + for (int i = 0; i < value.length() / 4; i++) { + int numValue = Integer.parseInt(value.substring(i * 4, (i + 1) * 4), 16); + codes.append(' '); + switch (numValue >> 14) { + case 1: + codes.append('C'); + break; + case 2: + codes.append('B'); + break; + case 3: + codes.append('U'); + break; + default: + codes.append('P'); + break; + } + codes.append(String.format("%04X", numValue & 0x3FFF)); + } + if (codes.length() > 0) { + return createEntry(Position.KEY_DTCS, codes.toString().trim()); + } else { + return null; + } + } + + public static Map.Entry<String, Object> decodeData(int pid, int value, boolean convert) { + switch (pid) { + case 0x04: + return createEntry(Position.KEY_ENGINE_LOAD, convert ? value * 100 / 255 : value); + case 0x05: + return createEntry(Position.KEY_COOLANT_TEMP, convert ? value - 40 : value); + case 0x0B: + return createEntry("mapIntake", value); + case 0x0C: + return createEntry(Position.KEY_RPM, convert ? value / 4 : value); + case 0x0D: + return createEntry(Position.KEY_OBD_SPEED, value); + case 0x0F: + return createEntry("intakeTemp", convert ? value - 40 : value); + case 0x11: + return createEntry(Position.KEY_THROTTLE, convert ? value * 100 / 255 : value); + case 0x21: + return createEntry("milDistance", value); + case 0x2F: + return createEntry(Position.KEY_FUEL_LEVEL, convert ? value * 100 / 255 : value); + case 0x31: + return createEntry("clearedDistance", value); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/helper/Parser.java b/src/main/java/org/traccar/helper/Parser.java new file mode 100644 index 000000000..1471ec237 --- /dev/null +++ b/src/main/java/org/traccar/helper/Parser.java @@ -0,0 +1,348 @@ +/* + * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import java.util.Date; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Parser { + + private int position; + private final Matcher matcher; + + public Parser(Pattern pattern, String input) { + matcher = pattern.matcher(input); + } + + public boolean matches() { + position = 1; + return matcher.matches(); + } + + public boolean find() { + position = 1; + return matcher.find(); + } + + public void skip(int number) { + position += number; + } + + public boolean hasNext() { + return hasNext(1); + } + + public boolean hasNext(int number) { + String value = matcher.group(position); + if (value != null && !value.isEmpty()) { + return true; + } else { + position += number; + return false; + } + } + + public String next() { + return matcher.group(position++); + } + + public Integer nextInt() { + if (hasNext()) { + return Integer.parseInt(next()); + } else { + return null; + } + } + + public int nextInt(int defaultValue) { + if (hasNext()) { + return Integer.parseInt(next()); + } else { + return defaultValue; + } + } + + public Integer nextHexInt() { + if (hasNext()) { + return Integer.parseInt(next(), 16); + } else { + return null; + } + } + + public int nextHexInt(int defaultValue) { + if (hasNext()) { + return Integer.parseInt(next(), 16); + } else { + return defaultValue; + } + } + + public Integer nextBinInt() { + if (hasNext()) { + return Integer.parseInt(next(), 2); + } else { + return null; + } + } + + public int nextBinInt(int defaultValue) { + if (hasNext()) { + return Integer.parseInt(next(), 2); + } else { + return defaultValue; + } + } + + public Long nextLong() { + if (hasNext()) { + return Long.parseLong(next()); + } else { + return null; + } + } + + public Long nextHexLong() { + if (hasNext()) { + return Long.parseLong(next(), 16); + } else { + return null; + } + } + + public long nextLong(long defaultValue) { + return nextLong(10, defaultValue); + } + + public long nextLong(int radix, long defaultValue) { + if (hasNext()) { + return Long.parseLong(next(), radix); + } else { + return defaultValue; + } + } + + public Double nextDouble() { + if (hasNext()) { + return Double.parseDouble(next()); + } else { + return null; + } + } + + public double nextDouble(double defaultValue) { + if (hasNext()) { + return Double.parseDouble(next()); + } else { + return defaultValue; + } + } + + public enum CoordinateFormat { + DEG_DEG, + DEG_HEM, + DEG_MIN_MIN, + DEG_MIN_HEM, + DEG_MIN_MIN_HEM, + HEM_DEG_MIN_MIN, + HEM_DEG, + HEM_DEG_MIN, + HEM_DEG_MIN_HEM + } + + public double nextCoordinate(CoordinateFormat format) { + double coordinate; + String hemisphere = null; + + switch (format) { + case DEG_DEG: + coordinate = Double.parseDouble(next() + '.' + next()); + break; + case DEG_HEM: + coordinate = nextDouble(0); + hemisphere = next(); + break; + case DEG_MIN_MIN: + coordinate = nextInt(0); + coordinate += Double.parseDouble(next() + '.' + next()) / 60; + break; + case DEG_MIN_MIN_HEM: + coordinate = nextInt(0); + coordinate += Double.parseDouble(next() + '.' + next()) / 60; + hemisphere = next(); + break; + case HEM_DEG: + hemisphere = next(); + coordinate = nextDouble(0); + break; + case HEM_DEG_MIN: + hemisphere = next(); + coordinate = nextInt(0); + coordinate += nextDouble(0) / 60; + break; + case HEM_DEG_MIN_HEM: + hemisphere = next(); + coordinate = nextInt(0); + coordinate += nextDouble(0) / 60; + if (hasNext()) { + hemisphere = next(); + } + break; + case HEM_DEG_MIN_MIN: + hemisphere = next(); + coordinate = nextInt(0); + coordinate += Double.parseDouble(next() + '.' + next()) / 60; + break; + case DEG_MIN_HEM: + default: + coordinate = nextInt(0); + coordinate += nextDouble(0) / 60; + hemisphere = next(); + break; + } + + if (hemisphere != null && (hemisphere.equals("S") || hemisphere.equals("W") || hemisphere.equals("-"))) { + coordinate = -Math.abs(coordinate); + } + + return coordinate; + } + + public double nextCoordinate() { + return nextCoordinate(CoordinateFormat.DEG_MIN_HEM); + } + + public enum DateTimeFormat { + HMS, + SMH, + HMS_YMD, + HMS_DMY, + SMH_YMD, + SMH_DMY, + DMY_HMS, + DMY_HMSS, + YMD_HMS, + YMD_HMSS, + } + + public Date nextDateTime(DateTimeFormat format, String timeZone) { + int year = 0, month = 0, day = 0; + int hour = 0, minute = 0, second = 0, millisecond = 0; + + switch (format) { + case HMS: + hour = nextInt(0); + minute = nextInt(0); + second = nextInt(0); + break; + case SMH: + second = nextInt(0); + minute = nextInt(0); + hour = nextInt(0); + break; + case HMS_YMD: + hour = nextInt(0); + minute = nextInt(0); + second = nextInt(0); + year = nextInt(0); + month = nextInt(0); + day = nextInt(0); + break; + case HMS_DMY: + hour = nextInt(0); + minute = nextInt(0); + second = nextInt(0); + day = nextInt(0); + month = nextInt(0); + year = nextInt(0); + break; + case SMH_YMD: + second = nextInt(0); + minute = nextInt(0); + hour = nextInt(0); + year = nextInt(0); + month = nextInt(0); + day = nextInt(0); + break; + case SMH_DMY: + second = nextInt(0); + minute = nextInt(0); + hour = nextInt(0); + day = nextInt(0); + month = nextInt(0); + year = nextInt(0); + break; + case DMY_HMS: + case DMY_HMSS: + day = nextInt(0); + month = nextInt(0); + year = nextInt(0); + hour = nextInt(0); + minute = nextInt(0); + second = nextInt(0); + break; + case YMD_HMS: + case YMD_HMSS: + default: + year = nextInt(0); + month = nextInt(0); + day = nextInt(0); + hour = nextInt(0); + minute = nextInt(0); + second = nextInt(0); + break; + } + + if (format == DateTimeFormat.YMD_HMSS || format == DateTimeFormat.DMY_HMSS) { + millisecond = nextInt(0); // (ddd) + } + + if (year >= 0 && year < 100) { + year += 2000; + } + + DateBuilder dateBuilder; + if (format != DateTimeFormat.HMS && format != DateTimeFormat.SMH) { + if (timeZone != null) { + dateBuilder = new DateBuilder(TimeZone.getTimeZone(timeZone)); + } else { + dateBuilder = new DateBuilder(); + } + dateBuilder.setDate(year, month, day); + } else { + if (timeZone != null) { + dateBuilder = new DateBuilder(new Date(), TimeZone.getTimeZone(timeZone)); + } else { + dateBuilder = new DateBuilder(new Date()); + } + } + + dateBuilder.setTime(hour, minute, second, millisecond); + + return dateBuilder.getDate(); + } + + public Date nextDateTime(DateTimeFormat format) { + return nextDateTime(format, null); + } + + public Date nextDateTime() { + return nextDateTime(DateTimeFormat.YMD_HMS, null); + } + +} diff --git a/src/main/java/org/traccar/helper/PatternBuilder.java b/src/main/java/org/traccar/helper/PatternBuilder.java new file mode 100644 index 000000000..5c4638189 --- /dev/null +++ b/src/main/java/org/traccar/helper/PatternBuilder.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class PatternBuilder { + + private final ArrayList<String> fragments = new ArrayList<>(); + + public PatternBuilder optional() { + return optional(1); + } + + public PatternBuilder optional(int count) { + fragments.add(fragments.size() - count, "(?:"); + fragments.add(")?"); + return this; + } + + public PatternBuilder expression(String s) { + s = s.replaceAll("\\|$", "\\\\|"); // special case for delimiter + + fragments.add(s); + return this; + } + + public PatternBuilder text(String s) { + fragments.add(s.replaceAll("([\\\\\\.\\[\\{\\(\\)\\*\\+\\?\\^\\$\\|])", "\\\\$1")); + return this; + } + + public PatternBuilder number(String s) { + s = s.replace("dddd", "d{4}").replace("ddd", "d{3}").replace("dd", "d{2}"); + s = s.replace("xxxx", "x{4}").replace("xxx", "x{3}").replace("xx", "x{2}"); + + s = s.replace("d", "\\d").replace("x", "[0-9a-fA-F]").replaceAll("([\\.])", "\\\\$1"); + s = s.replaceAll("\\|$", "\\\\|").replaceAll("^\\|", "\\\\|"); // special case for delimiter + + fragments.add(s); + return this; + } + + public PatternBuilder any() { + fragments.add(".*"); + return this; + } + + public PatternBuilder binary(String s) { + fragments.add(s.replaceAll("(\\p{XDigit}{2})", "\\\\$1")); + return this; + } + + public PatternBuilder or() { + fragments.add("|"); + return this; + } + + public PatternBuilder groupBegin() { + return expression("(?:"); + } + + public PatternBuilder groupEnd() { + return expression(")"); + } + + public PatternBuilder groupEnd(String s) { + return expression(")" + s); + } + + public Pattern compile() { + return Pattern.compile(toString(), Pattern.DOTALL); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for (String fragment : fragments) { + builder.append(fragment); + } + return builder.toString(); + } + +} diff --git a/src/main/java/org/traccar/helper/PatternUtil.java b/src/main/java/org/traccar/helper/PatternUtil.java new file mode 100644 index 000000000..74813e1d9 --- /dev/null +++ b/src/main/java/org/traccar/helper/PatternUtil.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.helper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.management.ManagementFactory; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +public final class PatternUtil { + + private static final Logger LOGGER = LoggerFactory.getLogger(PatternUtil.class); + + private PatternUtil() { + } + + public static class MatchResult { + private String patternMatch; + private String patternTail; + private String stringMatch; + private String stringTail; + + public String getPatternMatch() { + return patternMatch; + } + + public String getPatternTail() { + return patternTail; + } + + public String getStringMatch() { + return stringMatch; + } + + public String getStringTail() { + return stringTail; + } + } + + public static MatchResult checkPattern(String pattern, String input) { + + if (!ManagementFactory.getRuntimeMXBean().getInputArguments().toString().contains("-agentlib:jdwp")) { + throw new RuntimeException("PatternUtil usage detected"); + } + + MatchResult result = new MatchResult(); + + for (int i = 0; i < pattern.length(); i++) { + try { + Matcher matcher = Pattern.compile("(" + pattern.substring(0, i) + ").*").matcher(input); + if (matcher.matches()) { + result.patternMatch = pattern.substring(0, i); + result.patternTail = pattern.substring(i); + result.stringMatch = matcher.group(1); + result.stringTail = input.substring(matcher.group(1).length()); + } + } catch (PatternSyntaxException error) { + LOGGER.warn("Pattern matching error", error); + } + } + + return result; + } + +} diff --git a/src/main/java/org/traccar/helper/SanitizerModule.java b/src/main/java/org/traccar/helper/SanitizerModule.java new file mode 100644 index 000000000..af9ac5c2b --- /dev/null +++ b/src/main/java/org/traccar/helper/SanitizerModule.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.owasp.encoder.Encode; + +import java.io.IOException; + +public class SanitizerModule extends SimpleModule { + + public static class SanitizerSerializer extends StdSerializer<String> { + + protected SanitizerSerializer() { + super(String.class); + } + + @Override + public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeString(Encode.forHtml(value)); + } + + } + + public SanitizerModule() { + addSerializer(new SanitizerSerializer()); + } + +} diff --git a/src/main/java/org/traccar/helper/ServletHelper.java b/src/main/java/org/traccar/helper/ServletHelper.java new file mode 100644 index 000000000..b6c587ec3 --- /dev/null +++ b/src/main/java/org/traccar/helper/ServletHelper.java @@ -0,0 +1,45 @@ +/* + * 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() { + } + + public static String retrieveRemoteAddress(HttpServletRequest request) { + + if (request != null) { + String remoteAddress = request.getHeader("X-FORWARDED-FOR"); + + if (remoteAddress != null && !remoteAddress.isEmpty()) { + int separatorIndex = remoteAddress.indexOf(","); + if (separatorIndex > 0) { + return remoteAddress.substring(0, separatorIndex); // remove the additional data + } else { + return remoteAddress; + } + } else { + return request.getRemoteAddr(); + } + } else { + return null; + } + } + +} diff --git a/src/main/java/org/traccar/helper/UnitsConverter.java b/src/main/java/org/traccar/helper/UnitsConverter.java new file mode 100644 index 000000000..3dd435df4 --- /dev/null +++ b/src/main/java/org/traccar/helper/UnitsConverter.java @@ -0,0 +1,88 @@ +/* + * Copyright 2015 Anton Tananaev (anton@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; + +public final class UnitsConverter { + + private static final double KNOTS_TO_KPH_RATIO = 0.539957; + private static final double KNOTS_TO_MPH_RATIO = 0.868976; + private static final double KNOTS_TO_MPS_RATIO = 1.94384; + private static final double KNOTS_TO_CPS_RATIO = 0.0194384449; + private static final double METERS_TO_FEET_RATIO = 0.3048; + private static final double METERS_TO_MILE_RATIO = 1609.34; + private static final long MILLISECONDS_TO_HOURS_RATIO = 3600000; + private static final long MILLISECONDS_TO_MINUTES_RATIO = 60000; + + private UnitsConverter() { + } + + public static double knotsFromKph(double value) { // km/h + return value * KNOTS_TO_KPH_RATIO; + } + + public static double kphFromKnots(double value) { + return value / KNOTS_TO_KPH_RATIO; + } + + public static double knotsFromMph(double value) { + return value * KNOTS_TO_MPH_RATIO; + } + + public static double mphFromKnots(double value) { + return value / KNOTS_TO_MPH_RATIO; + } + + public static double knotsFromMps(double value) { // m/s + return value * KNOTS_TO_MPS_RATIO; + } + + public static double mpsFromKnots(double value) { + return value / KNOTS_TO_MPS_RATIO; + } + + public static double knotsFromCps(double value) { // cm/s + return value * KNOTS_TO_CPS_RATIO; + } + + public static double feetFromMeters(double value) { + return value / METERS_TO_FEET_RATIO; + } + + public static double metersFromFeet(double value) { + return value * METERS_TO_FEET_RATIO; + } + + public static double milesFromMeters(double value) { + return value / METERS_TO_MILE_RATIO; + } + + public static double metersFromMiles(double value) { + return value * METERS_TO_MILE_RATIO; + } + + public static long msFromHours(long value) { + return value * MILLISECONDS_TO_HOURS_RATIO; + } + + public static long msFromHours(double value) { + return (long) (value * MILLISECONDS_TO_HOURS_RATIO); + } + + public static long msFromMinutes(long value) { + return value * MILLISECONDS_TO_MINUTES_RATIO; + } + +} diff --git a/src/main/java/org/traccar/model/Attribute.java b/src/main/java/org/traccar/model/Attribute.java new file mode 100644 index 000000000..45d40b3ec --- /dev/null +++ b/src/main/java/org/traccar/model/Attribute.java @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +public class Attribute extends BaseModel { + + private String description; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + private String attribute; + + public String getAttribute() { + return attribute; + } + + public void setAttribute(String attribute) { + this.attribute = attribute; + } + + private String expression; + + public String getExpression() { + return expression; + } + + public void setExpression(String expression) { + this.expression = expression; + } + + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + +} diff --git a/src/main/java/org/traccar/model/BaseModel.java b/src/main/java/org/traccar/model/BaseModel.java new file mode 100644 index 000000000..8bdb916e8 --- /dev/null +++ b/src/main/java/org/traccar/model/BaseModel.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +public class BaseModel { + + private long id; + + public final long getId() { + return id; + } + + public final void setId(long id) { + this.id = id; + } + +} diff --git a/src/main/java/org/traccar/model/Calendar.java b/src/main/java/org/traccar/model/Calendar.java new file mode 100644 index 000000000..56d3eb74c --- /dev/null +++ b/src/main/java/org/traccar/model/Calendar.java @@ -0,0 +1,82 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import net.fortuna.ical4j.data.CalendarBuilder; +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.filter.Filter; +import net.fortuna.ical4j.filter.PeriodRule; +import net.fortuna.ical4j.model.DateTime; +import net.fortuna.ical4j.model.Dur; +import net.fortuna.ical4j.model.Period; +import net.fortuna.ical4j.model.component.CalendarComponent; +import org.apache.commons.collections4.Predicate; +import org.traccar.database.QueryIgnore; + +public class Calendar extends ExtendedModel { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private byte[] data; + + public byte[] getData() { + return data.clone(); + } + + public void setData(byte[] data) throws IOException, ParserException { + CalendarBuilder builder = new CalendarBuilder(); + calendar = builder.build(new ByteArrayInputStream(data)); + this.data = data.clone(); + } + + private net.fortuna.ical4j.model.Calendar calendar; + + @QueryIgnore + @JsonIgnore + public net.fortuna.ical4j.model.Calendar getCalendar() { + return calendar; + } + + public boolean checkMoment(Date date) { + if (calendar != null) { + Period period = new Period(new DateTime(date), new Dur(0, 0, 0, 0)); + Predicate<CalendarComponent> periodRule = new PeriodRule<>(period); + Filter<CalendarComponent> filter = new Filter<>(new Predicate[] {periodRule}, Filter.MATCH_ANY); + Collection<CalendarComponent> events = filter.filter(calendar.getComponents(CalendarComponent.VEVENT)); + if (events != null && !events.isEmpty()) { + return true; + } + } + return false; + } + +} diff --git a/src/main/java/org/traccar/model/CellTower.java b/src/main/java/org/traccar/model/CellTower.java new file mode 100644 index 000000000..6d1dfbd7f --- /dev/null +++ b/src/main/java/org/traccar/model/CellTower.java @@ -0,0 +1,115 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.traccar.Context; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CellTower { + + public static CellTower from(int mcc, int mnc, int lac, long cid) { + CellTower cellTower = new CellTower(); + cellTower.setMobileCountryCode(mcc); + cellTower.setMobileNetworkCode(mnc); + cellTower.setLocationAreaCode(lac); + cellTower.setCellId(cid); + return cellTower; + } + + public static CellTower from(int mcc, int mnc, int lac, long cid, int rssi) { + CellTower cellTower = CellTower.from(mcc, mnc, lac, cid); + cellTower.setSignalStrength(rssi); + return cellTower; + } + + public static CellTower fromLacCid(int lac, long cid) { + return from( + Context.getConfig().getInteger("geolocation.mcc"), + Context.getConfig().getInteger("geolocation.mnc"), lac, cid); + } + + public static CellTower fromCidLac(long cid, int lac) { + return fromLacCid(lac, cid); + } + + private String radioType; + + public String getRadioType() { + return radioType; + } + + public void setRadioType(String radioType) { + this.radioType = radioType; + } + + private Long cellId; + + public Long getCellId() { + return cellId; + } + + public void setCellId(Long cellId) { + this.cellId = cellId; + } + + private Integer locationAreaCode; + + public Integer getLocationAreaCode() { + return locationAreaCode; + } + + public void setLocationAreaCode(Integer locationAreaCode) { + this.locationAreaCode = locationAreaCode; + } + + private Integer mobileCountryCode; + + public Integer getMobileCountryCode() { + return mobileCountryCode; + } + + public void setMobileCountryCode(Integer mobileCountryCode) { + this.mobileCountryCode = mobileCountryCode; + } + + private Integer mobileNetworkCode; + + public Integer getMobileNetworkCode() { + return mobileNetworkCode; + } + + public void setMobileNetworkCode(Integer mobileNetworkCode) { + this.mobileNetworkCode = mobileNetworkCode; + } + + private Integer signalStrength; + + public Integer getSignalStrength() { + return signalStrength; + } + + public void setSignalStrength(Integer signalStrength) { + this.signalStrength = signalStrength; + } + + public void setOperator(long operator) { + String operatorString = String.valueOf(operator); + mobileCountryCode = Integer.parseInt(operatorString.substring(0, 3)); + mobileNetworkCode = Integer.parseInt(operatorString.substring(3)); + } + +} diff --git a/src/main/java/org/traccar/model/Command.java b/src/main/java/org/traccar/model/Command.java new file mode 100644 index 000000000..abe538a10 --- /dev/null +++ b/src/main/java/org/traccar/model/Command.java @@ -0,0 +1,114 @@ +/* + * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import org.traccar.database.QueryIgnore; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Command extends Message implements Cloneable { + + public static final String TYPE_CUSTOM = "custom"; + public static final String TYPE_IDENTIFICATION = "deviceIdentification"; + public static final String TYPE_POSITION_SINGLE = "positionSingle"; + public static final String TYPE_POSITION_PERIODIC = "positionPeriodic"; + public static final String TYPE_POSITION_STOP = "positionStop"; + public static final String TYPE_ENGINE_STOP = "engineStop"; + public static final String TYPE_ENGINE_RESUME = "engineResume"; + public static final String TYPE_ALARM_ARM = "alarmArm"; + public static final String TYPE_ALARM_DISARM = "alarmDisarm"; + public static final String TYPE_SET_TIMEZONE = "setTimezone"; + public static final String TYPE_REQUEST_PHOTO = "requestPhoto"; + public static final String TYPE_POWER_OFF = "powerOff"; + public static final String TYPE_REBOOT_DEVICE = "rebootDevice"; + public static final String TYPE_SEND_SMS = "sendSms"; + public static final String TYPE_SEND_USSD = "sendUssd"; + public static final String TYPE_SOS_NUMBER = "sosNumber"; + public static final String TYPE_SILENCE_TIME = "silenceTime"; + public static final String TYPE_SET_PHONEBOOK = "setPhonebook"; + public static final String TYPE_MESSAGE = "message"; + public static final String TYPE_VOICE_MESSAGE = "voiceMessage"; + public static final String TYPE_OUTPUT_CONTROL = "outputControl"; + public static final String TYPE_VOICE_MONITORING = "voiceMonitoring"; + public static final String TYPE_SET_AGPS = "setAgps"; + public static final String TYPE_SET_INDICATOR = "setIndicator"; + public static final String TYPE_CONFIGURATION = "configuration"; + public static final String TYPE_GET_VERSION = "getVersion"; + public static final String TYPE_FIRMWARE_UPDATE = "firmwareUpdate"; + public static final String TYPE_SET_CONNECTION = "setConnection"; + public static final String TYPE_SET_ODOMETER = "setOdometer"; + public static final String TYPE_GET_MODEM_STATUS = "getModemStatus"; + public static final String TYPE_GET_DEVICE_STATUS = "getDeviceStatus"; + + public static final String TYPE_MODE_POWER_SAVING = "modePowerSaving"; + public static final String TYPE_MODE_DEEP_SLEEP = "modeDeepSleep"; + + public static final String TYPE_ALARM_GEOFENCE = "movementAlarm"; + public static final String TYPE_ALARM_BATTERY = "alarmBattery"; + public static final String TYPE_ALARM_SOS = "alarmSos"; + public static final String TYPE_ALARM_REMOVE = "alarmRemove"; + public static final String TYPE_ALARM_CLOCK = "alarmClock"; + public static final String TYPE_ALARM_SPEED = "alarmSpeed"; + public static final String TYPE_ALARM_FALL = "alarmFall"; + public static final String TYPE_ALARM_VIBRATION = "alarmVibration"; + + public static final String KEY_UNIQUE_ID = "uniqueId"; + public static final String KEY_FREQUENCY = "frequency"; + public static final String KEY_LANGUAGE = "language"; + public static final String KEY_TIMEZONE = "timezone"; + public static final String KEY_DEVICE_PASSWORD = "devicePassword"; + public static final String KEY_RADIUS = "radius"; + public static final String KEY_MESSAGE = "message"; + public static final String KEY_ENABLE = "enable"; + public static final String KEY_DATA = "data"; + public static final String KEY_INDEX = "index"; + public static final String KEY_PHONE = "phone"; + public static final String KEY_SERVER = "server"; + public static final String KEY_PORT = "port"; + + @Override + public Command clone() throws CloneNotSupportedException { + return (Command) super.clone(); + } + + private boolean textChannel; + + public boolean getTextChannel() { + return textChannel; + } + + public void setTextChannel(boolean textChannel) { + this.textChannel = textChannel; + } + + @QueryIgnore + @Override + public long getDeviceId() { + return super.getDeviceId(); + } + + private String description; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + +} diff --git a/src/main/java/org/traccar/model/Device.java b/src/main/java/org/traccar/model/Device.java new file mode 100644 index 000000000..0c9be932d --- /dev/null +++ b/src/main/java/org/traccar/model/Device.java @@ -0,0 +1,152 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import java.util.Date; +import java.util.List; + +import org.traccar.database.QueryExtended; +import org.traccar.database.QueryIgnore; + +public class Device extends GroupedModel { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private String uniqueId; + + public String getUniqueId() { + return uniqueId; + } + + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + } + + public static final String STATUS_UNKNOWN = "unknown"; + public static final String STATUS_ONLINE = "online"; + public static final String STATUS_OFFLINE = "offline"; + + private String status; + + @QueryIgnore + public String getStatus() { + return status != null ? status : STATUS_OFFLINE; + } + + public void setStatus(String status) { + this.status = status; + } + + private Date lastUpdate; + + @QueryExtended + public Date getLastUpdate() { + if (lastUpdate != null) { + return new Date(lastUpdate.getTime()); + } else { + return null; + } + } + + public void setLastUpdate(Date lastUpdate) { + if (lastUpdate != null) { + this.lastUpdate = new Date(lastUpdate.getTime()); + } else { + this.lastUpdate = null; + } + } + + private long positionId; + + @QueryIgnore + public long getPositionId() { + return positionId; + } + + public void setPositionId(long positionId) { + this.positionId = positionId; + } + + private List<Long> geofenceIds; + + @QueryIgnore + public List<Long> getGeofenceIds() { + return geofenceIds; + } + + public void setGeofenceIds(List<Long> geofenceIds) { + this.geofenceIds = geofenceIds; + } + + private String phone; + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + private String model; + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + private String contact; + + public String getContact() { + return contact; + } + + public void setContact(String contact) { + this.contact = contact; + } + + private String category; + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + private boolean disabled; + + public boolean getDisabled() { + return disabled; + } + + public void setDisabled(boolean disabled) { + this.disabled = disabled; + } + +} diff --git a/src/main/java/org/traccar/model/DeviceAccumulators.java b/src/main/java/org/traccar/model/DeviceAccumulators.java new file mode 100644 index 000000000..8a90826c4 --- /dev/null +++ b/src/main/java/org/traccar/model/DeviceAccumulators.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +public class DeviceAccumulators { + + private long deviceId; + + public long getDeviceId() { + return deviceId; + } + + public void setDeviceId(long deviceId) { + this.deviceId = deviceId; + } + + private Double totalDistance; + + public Double getTotalDistance() { + return totalDistance; + } + + public void setTotalDistance(Double totalDistance) { + this.totalDistance = totalDistance; + } + + private Long hours; + + public Long getHours() { + return hours; + } + + public void setHours(Long hours) { + this.hours = hours; + } + +} diff --git a/src/main/java/org/traccar/model/DeviceState.java b/src/main/java/org/traccar/model/DeviceState.java new file mode 100644 index 000000000..75d6726ee --- /dev/null +++ b/src/main/java/org/traccar/model/DeviceState.java @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +public class DeviceState { + + private Boolean motionState; + + public void setMotionState(boolean motionState) { + this.motionState = motionState; + } + + public Boolean getMotionState() { + return motionState; + } + + private Position motionPosition; + + public void setMotionPosition(Position motionPosition) { + this.motionPosition = motionPosition; + } + + public Position getMotionPosition() { + return motionPosition; + } + + private Boolean overspeedState; + + public void setOverspeedState(boolean overspeedState) { + this.overspeedState = overspeedState; + } + + public Boolean getOverspeedState() { + return overspeedState; + } + + private Position overspeedPosition; + + public void setOverspeedPosition(Position overspeedPosition) { + this.overspeedPosition = overspeedPosition; + } + + public Position getOverspeedPosition() { + return overspeedPosition; + } + + private long overspeedGeofenceId; + + public void setOverspeedGeofenceId(long overspeedGeofenceId) { + this.overspeedGeofenceId = overspeedGeofenceId; + } + + public long getOverspeedGeofenceId() { + return overspeedGeofenceId; + } + +} diff --git a/src/main/java/org/traccar/model/Driver.java b/src/main/java/org/traccar/model/Driver.java new file mode 100644 index 000000000..05f52fd4d --- /dev/null +++ b/src/main/java/org/traccar/model/Driver.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +public class Driver extends ExtendedModel { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private String uniqueId; + + public String getUniqueId() { + return uniqueId; + } + + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + } +} diff --git a/src/main/java/org/traccar/model/Event.java b/src/main/java/org/traccar/model/Event.java new file mode 100644 index 000000000..ee7fcc679 --- /dev/null +++ b/src/main/java/org/traccar/model/Event.java @@ -0,0 +1,104 @@ +/* + * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import java.util.Date; + +public class Event extends Message { + + public Event(String type, long deviceId, long positionId) { + this(type, deviceId); + setPositionId(positionId); + } + + public Event(String type, long deviceId) { + setType(type); + setDeviceId(deviceId); + this.serverTime = new Date(); + } + + public Event() { + } + + public static final String ALL_EVENTS = "allEvents"; + + public static final String TYPE_COMMAND_RESULT = "commandResult"; + + public static final String TYPE_DEVICE_ONLINE = "deviceOnline"; + public static final String TYPE_DEVICE_UNKNOWN = "deviceUnknown"; + public static final String TYPE_DEVICE_OFFLINE = "deviceOffline"; + + public static final String TYPE_DEVICE_MOVING = "deviceMoving"; + public static final String TYPE_DEVICE_STOPPED = "deviceStopped"; + + public static final String TYPE_DEVICE_OVERSPEED = "deviceOverspeed"; + public static final String TYPE_DEVICE_FUEL_DROP = "deviceFuelDrop"; + + public static final String TYPE_GEOFENCE_ENTER = "geofenceEnter"; + public static final String TYPE_GEOFENCE_EXIT = "geofenceExit"; + + public static final String TYPE_ALARM = "alarm"; + + public static final String TYPE_IGNITION_ON = "ignitionOn"; + public static final String TYPE_IGNITION_OFF = "ignitionOff"; + + public static final String TYPE_MAINTENANCE = "maintenance"; + + public static final String TYPE_TEXT_MESSAGE = "textMessage"; + + public static final String TYPE_DRIVER_CHANGED = "driverChanged"; + + private Date serverTime; + + public Date getServerTime() { + return serverTime; + } + + public void setServerTime(Date serverTime) { + this.serverTime = serverTime; + } + + private long positionId; + + public long getPositionId() { + return positionId; + } + + public void setPositionId(long positionId) { + this.positionId = positionId; + } + + private long geofenceId = 0; + + public long getGeofenceId() { + return geofenceId; + } + + public void setGeofenceId(long geofenceId) { + this.geofenceId = geofenceId; + } + + private long maintenanceId = 0; + + public long getMaintenanceId() { + return maintenanceId; + } + + public void setMaintenanceId(long maintenanceId) { + this.maintenanceId = maintenanceId; + } + +} diff --git a/src/main/java/org/traccar/model/ExtendedModel.java b/src/main/java/org/traccar/model/ExtendedModel.java new file mode 100644 index 000000000..8353d0e66 --- /dev/null +++ b/src/main/java/org/traccar/model/ExtendedModel.java @@ -0,0 +1,127 @@ +/* + * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class ExtendedModel extends BaseModel { + + private Map<String, Object> attributes = new LinkedHashMap<>(); + + public Map<String, Object> getAttributes() { + return attributes; + } + + public void setAttributes(Map<String, Object> attributes) { + this.attributes = attributes; + } + + public void set(String key, Boolean value) { + if (value != null) { + attributes.put(key, value); + } + } + + public void set(String key, Byte value) { + if (value != null) { + attributes.put(key, value.intValue()); + } + } + + public void set(String key, Short value) { + if (value != null) { + attributes.put(key, value.intValue()); + } + } + + public void set(String key, Integer value) { + if (value != null) { + attributes.put(key, value); + } + } + + public void set(String key, Long value) { + if (value != null) { + attributes.put(key, value); + } + } + + public void set(String key, Float value) { + if (value != null) { + attributes.put(key, value.doubleValue()); + } + } + + public void set(String key, Double value) { + if (value != null) { + attributes.put(key, value); + } + } + + public void set(String key, String value) { + if (value != null && !value.isEmpty()) { + attributes.put(key, value); + } + } + + public void add(Map.Entry<String, Object> entry) { + if (entry != null && entry.getValue() != null) { + attributes.put(entry.getKey(), entry.getValue()); + } + } + + public String getString(String key) { + if (attributes.containsKey(key)) { + return (String) attributes.get(key); + } else { + return null; + } + } + + public double getDouble(String key) { + if (attributes.containsKey(key)) { + return ((Number) attributes.get(key)).doubleValue(); + } else { + return 0.0; + } + } + + public boolean getBoolean(String key) { + if (attributes.containsKey(key)) { + return (Boolean) attributes.get(key); + } else { + return false; + } + } + + public int getInteger(String key) { + if (attributes.containsKey(key)) { + return ((Number) attributes.get(key)).intValue(); + } else { + return 0; + } + } + + public long getLong(String key) { + if (attributes.containsKey(key)) { + return ((Number) attributes.get(key)).longValue(); + } else { + return 0; + } + } + +} diff --git a/src/main/java/org/traccar/model/Geofence.java b/src/main/java/org/traccar/model/Geofence.java new file mode 100644 index 000000000..8560d22e9 --- /dev/null +++ b/src/main/java/org/traccar/model/Geofence.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import java.text.ParseException; + +import org.traccar.Context; +import org.traccar.database.QueryIgnore; +import org.traccar.geofence.GeofenceCircle; +import org.traccar.geofence.GeofenceGeometry; +import org.traccar.geofence.GeofencePolygon; +import org.traccar.geofence.GeofencePolyline; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public class Geofence extends ScheduledModel { + + public static final String TYPE_GEOFENCE_CILCLE = "geofenceCircle"; + public static final String TYPE_GEOFENCE_POLYGON = "geofencePolygon"; + public static final String TYPE_GEOFENCE_POLYLINE = "geofencePolyline"; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private String description; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + private String area; + + public String getArea() { + return area; + } + + public void setArea(String area) throws ParseException { + + if (area.startsWith("CIRCLE")) { + geometry = new GeofenceCircle(area); + } else if (area.startsWith("POLYGON")) { + geometry = new GeofencePolygon(area); + } else if (area.startsWith("LINESTRING")) { + final double distance = getDouble("polylineDistance"); + geometry = new GeofencePolyline(area, distance > 0 ? distance + : Context.getConfig().getDouble("geofence.polylineDistance", 25)); + } else { + throw new ParseException("Unknown geometry type", 0); + } + + this.area = area; + } + + private GeofenceGeometry geometry; + + @QueryIgnore + @JsonIgnore + public GeofenceGeometry getGeometry() { + return geometry; + } + + @QueryIgnore + @JsonIgnore + public void setGeometry(GeofenceGeometry geometry) { + area = geometry.toWkt(); + this.geometry = geometry; + } +} diff --git a/src/main/java/org/traccar/model/Group.java b/src/main/java/org/traccar/model/Group.java new file mode 100644 index 000000000..91ea2319d --- /dev/null +++ b/src/main/java/org/traccar/model/Group.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +public class Group extends GroupedModel { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/src/main/java/org/traccar/model/GroupedModel.java b/src/main/java/org/traccar/model/GroupedModel.java new file mode 100644 index 000000000..6b1aa75b1 --- /dev/null +++ b/src/main/java/org/traccar/model/GroupedModel.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +public class GroupedModel extends ExtendedModel { + + private long groupId; + + public long getGroupId() { + return groupId; + } + + public void setGroupId(long groupId) { + this.groupId = groupId; + } + +} diff --git a/src/main/java/org/traccar/model/Maintenance.java b/src/main/java/org/traccar/model/Maintenance.java new file mode 100644 index 000000000..73f67ea96 --- /dev/null +++ b/src/main/java/org/traccar/model/Maintenance.java @@ -0,0 +1,61 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +public class Maintenance extends ExtendedModel { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + private double start; + + public double getStart() { + return start; + } + + public void setStart(double start) { + this.start = start; + } + + private double period; + + public double getPeriod() { + return period; + } + + public void setPeriod(double period) { + this.period = period; + } + +} diff --git a/src/main/java/org/traccar/model/ManagedUser.java b/src/main/java/org/traccar/model/ManagedUser.java new file mode 100644 index 000000000..03c5ef48d --- /dev/null +++ b/src/main/java/org/traccar/model/ManagedUser.java @@ -0,0 +1,21 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +public class ManagedUser extends User { + +} diff --git a/src/main/java/org/traccar/model/Message.java b/src/main/java/org/traccar/model/Message.java new file mode 100644 index 000000000..dad9c20f0 --- /dev/null +++ b/src/main/java/org/traccar/model/Message.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013 - 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +public class Message extends ExtendedModel { + + private long deviceId; + + public long getDeviceId() { + return deviceId; + } + + public void setDeviceId(long deviceId) { + this.deviceId = deviceId; + } + + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + +} diff --git a/src/main/java/org/traccar/model/MiscFormatter.java b/src/main/java/org/traccar/model/MiscFormatter.java new file mode 100644 index 000000000..c6511f063 --- /dev/null +++ b/src/main/java/org/traccar/model/MiscFormatter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013 - 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + + +import java.text.DecimalFormat; +import java.util.Map; + +public final class MiscFormatter { + + private MiscFormatter() { + } + + private static final String XML_ROOT_NODE = "info"; + + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##"); + + private static String format(Object value) { + if (value instanceof Double || value instanceof Float) { + return DECIMAL_FORMAT.format(value); + } else { + return value.toString(); + } + } + + public static String toXmlString(Map<String, Object> attributes) { + StringBuilder result = new StringBuilder(); + + result.append("<").append(XML_ROOT_NODE).append(">"); + + for (Map.Entry<String, Object> entry : attributes.entrySet()) { + + result.append("<").append(entry.getKey()).append(">"); + result.append(format(entry.getValue())); + result.append("</").append(entry.getKey()).append(">"); + } + + result.append("</").append(XML_ROOT_NODE).append(">"); + + return result.toString(); + } + +} diff --git a/src/main/java/org/traccar/model/Network.java b/src/main/java/org/traccar/model/Network.java new file mode 100644 index 000000000..4d67fc5d8 --- /dev/null +++ b/src/main/java/org/traccar/model/Network.java @@ -0,0 +1,121 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.ArrayList; +import java.util.Collection; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Network { + + public Network() { + } + + public Network(CellTower cellTower) { + addCellTower(cellTower); + } + + public Network(WifiAccessPoint wifiAccessPoint) { + addWifiAccessPoint(wifiAccessPoint); + } + + private Integer homeMobileCountryCode; + + public Integer getHomeMobileCountryCode() { + return homeMobileCountryCode; + } + + public void setHomeMobileCountryCode(Integer homeMobileCountryCode) { + this.homeMobileCountryCode = homeMobileCountryCode; + } + + private Integer homeMobileNetworkCode; + + public Integer getHomeMobileNetworkCode() { + return homeMobileNetworkCode; + } + + public void setHomeMobileNetworkCode(Integer homeMobileNetworkCode) { + this.homeMobileNetworkCode = homeMobileNetworkCode; + } + + private String radioType = "gsm"; + + public String getRadioType() { + return radioType; + } + + public void setRadioType(String radioType) { + this.radioType = radioType; + } + + private String carrier; + + public String getCarrier() { + return carrier; + } + + public void setCarrier(String carrier) { + this.carrier = carrier; + } + + private Boolean considerIp = false; + + public Boolean getConsiderIp() { + return considerIp; + } + + public void setConsiderIp(Boolean considerIp) { + this.considerIp = considerIp; + } + + private Collection<CellTower> cellTowers; + + public Collection<CellTower> getCellTowers() { + return cellTowers; + } + + public void setCellTowers(Collection<CellTower> cellTowers) { + this.cellTowers = cellTowers; + } + + public void addCellTower(CellTower cellTower) { + if (cellTowers == null) { + cellTowers = new ArrayList<>(); + } + cellTowers.add(cellTower); + } + + private Collection<WifiAccessPoint> wifiAccessPoints; + + public Collection<WifiAccessPoint> getWifiAccessPoints() { + return wifiAccessPoints; + } + + public void setWifiAccessPoints(Collection<WifiAccessPoint> wifiAccessPoints) { + this.wifiAccessPoints = wifiAccessPoints; + } + + public void addWifiAccessPoint(WifiAccessPoint wifiAccessPoint) { + if (wifiAccessPoints == null) { + wifiAccessPoints = new ArrayList<>(); + } + wifiAccessPoints.add(wifiAccessPoint); + } + +} diff --git a/src/main/java/org/traccar/model/Notification.java b/src/main/java/org/traccar/model/Notification.java new file mode 100644 index 000000000..f1983a03a --- /dev/null +++ b/src/main/java/org/traccar/model/Notification.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import java.util.HashSet; +import java.util.Set; + +import org.traccar.database.QueryIgnore; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public class Notification extends ScheduledModel { + + private boolean always; + + public boolean getAlways() { + return always; + } + + public void setAlways(boolean always) { + this.always = always; + } + + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + + private String notificators; + + public String getNotificators() { + return notificators; + } + + public void setNotificators(String transports) { + this.notificators = transports; + } + + + @JsonIgnore + @QueryIgnore + public Set<String> getNotificatorsTypes() { + final Set<String> result = new HashSet<>(); + if (notificators != null) { + final String[] transportsList = notificators.split(","); + for (String transport : transportsList) { + result.add(transport.trim()); + } + } + return result; + } + +} diff --git a/src/main/java/org/traccar/model/Permission.java b/src/main/java/org/traccar/model/Permission.java new file mode 100644 index 000000000..1006b1c47 --- /dev/null +++ b/src/main/java/org/traccar/model/Permission.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.traccar.database.DataManager; + +public class Permission { + + private Class<?> ownerClass; + private long ownerId; + private Class<?> propertyClass; + private long propertyId; + + public Permission(LinkedHashMap<String, Long> permissionMap) throws ClassNotFoundException { + Iterator<Map.Entry<String, Long>> iterator = permissionMap.entrySet().iterator(); + String owner = iterator.next().getKey(); + ownerClass = DataManager.getClassByName(owner); + String property = iterator.next().getKey(); + propertyClass = DataManager.getClassByName(property); + ownerId = permissionMap.get(owner); + propertyId = permissionMap.get(property); + } + + public Class<?> getOwnerClass() { + return ownerClass; + } + + public long getOwnerId() { + return ownerId; + } + + public Class<?> getPropertyClass() { + return propertyClass; + } + + public long getPropertyId() { + return propertyId; + } +} diff --git a/src/main/java/org/traccar/model/Position.java b/src/main/java/org/traccar/model/Position.java new file mode 100644 index 000000000..2c0e22c9e --- /dev/null +++ b/src/main/java/org/traccar/model/Position.java @@ -0,0 +1,305 @@ +/* + * Copyright 2012 - 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import java.util.Date; + +import org.traccar.database.QueryIgnore; + +public class Position extends Message { + + public static final String KEY_ORIGINAL = "raw"; + public static final String KEY_INDEX = "index"; + public static final String KEY_HDOP = "hdop"; + public static final String KEY_VDOP = "vdop"; + public static final String KEY_PDOP = "pdop"; + public static final String KEY_SATELLITES = "sat"; // in use + public static final String KEY_SATELLITES_VISIBLE = "satVisible"; + public static final String KEY_RSSI = "rssi"; + public static final String KEY_GPS = "gps"; + public static final String KEY_ROAMING = "roaming"; + public static final String KEY_EVENT = "event"; + public static final String KEY_ALARM = "alarm"; + public static final String KEY_STATUS = "status"; + public static final String KEY_ODOMETER = "odometer"; // meters + public static final String KEY_ODOMETER_SERVICE = "serviceOdometer"; // meters + public static final String KEY_ODOMETER_TRIP = "tripOdometer"; // meters + public static final String KEY_HOURS = "hours"; + public static final String KEY_STEPS = "steps"; + public static final String KEY_HEART_RATE = "heartRate"; + public static final String KEY_INPUT = "input"; + public static final String KEY_OUTPUT = "output"; + public static final String KEY_IMAGE = "image"; + public static final String KEY_VIDEO = "video"; + public static final String KEY_AUDIO = "audio"; + + // The units for the below four KEYs currently vary. + // The preferred units of measure are specified in the comment for each. + public static final String KEY_POWER = "power"; // volts + public static final String KEY_BATTERY = "battery"; // volts + public static final String KEY_BATTERY_LEVEL = "batteryLevel"; // percentage + public static final String KEY_FUEL_LEVEL = "fuel"; // liters + public static final String KEY_FUEL_USED = "fuelUsed"; // liters + public static final String KEY_FUEL_CONSUMPTION = "fuelConsumption"; // liters/hour + + public static final String KEY_VERSION_FW = "versionFw"; + public static final String KEY_VERSION_HW = "versionHw"; + public static final String KEY_TYPE = "type"; + public static final String KEY_IGNITION = "ignition"; + public static final String KEY_FLAGS = "flags"; + public static final String KEY_ANTENNA = "antenna"; + public static final String KEY_CHARGE = "charge"; + public static final String KEY_IP = "ip"; + public static final String KEY_ARCHIVE = "archive"; + public static final String KEY_DISTANCE = "distance"; // meters + public static final String KEY_TOTAL_DISTANCE = "totalDistance"; // meters + public static final String KEY_RPM = "rpm"; + public static final String KEY_VIN = "vin"; + public static final String KEY_APPROXIMATE = "approximate"; + public static final String KEY_THROTTLE = "throttle"; + public static final String KEY_MOTION = "motion"; + public static final String KEY_ARMED = "armed"; + public static final String KEY_GEOFENCE = "geofence"; + public static final String KEY_ACCELERATION = "acceleration"; + public static final String KEY_DEVICE_TEMP = "deviceTemp"; // celsius + public static final String KEY_COOLANT_TEMP = "coolantTemp"; // celsius + public static final String KEY_ENGINE_LOAD = "engineLoad"; + public static final String KEY_OPERATOR = "operator"; + public static final String KEY_COMMAND = "command"; + public static final String KEY_BLOCKED = "blocked"; + public static final String KEY_DOOR = "door"; + public static final String KEY_AXLE_WEIGHT = "axleWeight"; + public static final String KEY_G_SENSOR = "gSensor"; + public static final String KEY_ICCID = "iccid"; + public static final String KEY_PHONE = "phone"; + + public static final String KEY_DTCS = "dtcs"; + public static final String KEY_OBD_SPEED = "obdSpeed"; // knots + public static final String KEY_OBD_ODOMETER = "obdOdometer"; // meters + + public static final String KEY_RESULT = "result"; + + public static final String KEY_DRIVER_UNIQUE_ID = "driverUniqueId"; + + // Start with 1 not 0 + public static final String PREFIX_TEMP = "temp"; + public static final String PREFIX_ADC = "adc"; + public static final String PREFIX_IO = "io"; + public static final String PREFIX_COUNT = "count"; + public static final String PREFIX_IN = "in"; + public static final String PREFIX_OUT = "out"; + + public static final String ALARM_GENERAL = "general"; + public static final String ALARM_SOS = "sos"; + public static final String ALARM_VIBRATION = "vibration"; + public static final String ALARM_MOVEMENT = "movement"; + public static final String ALARM_LOW_SPEED = "lowspeed"; + public static final String ALARM_OVERSPEED = "overspeed"; + public static final String ALARM_FALL_DOWN = "fallDown"; + public static final String ALARM_LOW_POWER = "lowPower"; + public static final String ALARM_LOW_BATTERY = "lowBattery"; + public static final String ALARM_FAULT = "fault"; + public static final String ALARM_POWER_OFF = "powerOff"; + public static final String ALARM_POWER_ON = "powerOn"; + public static final String ALARM_DOOR = "door"; + public static final String ALARM_LOCK = "lock"; + public static final String ALARM_UNLOCK = "unlock"; + public static final String ALARM_GEOFENCE = "geofence"; + public static final String ALARM_GEOFENCE_ENTER = "geofenceEnter"; + public static final String ALARM_GEOFENCE_EXIT = "geofenceExit"; + public static final String ALARM_GPS_ANTENNA_CUT = "gpsAntennaCut"; + public static final String ALARM_ACCIDENT = "accident"; + public static final String ALARM_TOW = "tow"; + public static final String ALARM_IDLE = "idle"; + public static final String ALARM_HIGH_RPM = "highRpm"; + public static final String ALARM_ACCELERATION = "hardAcceleration"; + public static final String ALARM_BRAKING = "hardBraking"; + public static final String ALARM_CORNERING = "hardCornering"; + public static final String ALARM_LANE_CHANGE = "laneChange"; + public static final String ALARM_FATIGUE_DRIVING = "fatigueDriving"; + public static final String ALARM_POWER_CUT = "powerCut"; + public static final String ALARM_POWER_RESTORED = "powerRestored"; + public static final String ALARM_JAMMING = "jamming"; + public static final String ALARM_TEMPERATURE = "temperature"; + public static final String ALARM_PARKING = "parking"; + public static final String ALARM_SHOCK = "shock"; + public static final String ALARM_BONNET = "bonnet"; + public static final String ALARM_FOOT_BRAKE = "footBrake"; + public static final String ALARM_FUEL_LEAK = "fuelLeak"; + public static final String ALARM_TAMPERING = "tampering"; + public static final String ALARM_REMOVING = "removing"; + + public Position() { + } + + public Position(String protocol) { + this.protocol = protocol; + this.serverTime = new Date(); + } + + private String protocol; + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + private Date serverTime = new Date(); + + public Date getServerTime() { + return serverTime; + } + + public void setServerTime(Date serverTime) { + this.serverTime = serverTime; + } + + private Date deviceTime; + + public Date getDeviceTime() { + return deviceTime; + } + + public void setDeviceTime(Date deviceTime) { + this.deviceTime = deviceTime; + } + + private Date fixTime; + + public Date getFixTime() { + return fixTime; + } + + public void setFixTime(Date fixTime) { + this.fixTime = fixTime; + } + + public void setTime(Date time) { + setDeviceTime(time); + setFixTime(time); + } + + private boolean outdated; + + @QueryIgnore + public boolean getOutdated() { + return outdated; + } + + public void setOutdated(boolean outdated) { + this.outdated = outdated; + } + + private boolean valid; + + public boolean getValid() { + return valid; + } + + public void setValid(boolean valid) { + this.valid = valid; + } + + private double latitude; + + public double getLatitude() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + private double longitude; + + public double getLongitude() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + private double altitude; // value in meters + + public double getAltitude() { + return altitude; + } + + public void setAltitude(double altitude) { + this.altitude = altitude; + } + + private double speed; // value in knots + + public double getSpeed() { + return speed; + } + + public void setSpeed(double speed) { + this.speed = speed; + } + + private double course; + + public double getCourse() { + return course; + } + + public void setCourse(double course) { + this.course = course; + } + + private String address; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + private double accuracy; + + public double getAccuracy() { + return accuracy; + } + + public void setAccuracy(double accuracy) { + this.accuracy = accuracy; + } + + private Network network; + + public Network getNetwork() { + return network; + } + + public void setNetwork(Network network) { + this.network = network; + } + + @Override + @QueryIgnore + public String getType() { + return super.getType(); + } + +} diff --git a/src/main/java/org/traccar/model/ScheduledModel.java b/src/main/java/org/traccar/model/ScheduledModel.java new file mode 100644 index 000000000..9e6a4b9a6 --- /dev/null +++ b/src/main/java/org/traccar/model/ScheduledModel.java @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +public class ScheduledModel extends ExtendedModel { + + private long calendarId; + + public long getCalendarId() { + return calendarId; + } + + public void setCalendarId(long calendarId) { + this.calendarId = calendarId; + } +} diff --git a/src/main/java/org/traccar/model/Server.java b/src/main/java/org/traccar/model/Server.java new file mode 100644 index 000000000..ad37e7078 --- /dev/null +++ b/src/main/java/org/traccar/model/Server.java @@ -0,0 +1,169 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import org.traccar.database.QueryIgnore; + +public class Server extends ExtendedModel { + + @QueryIgnore + public String getVersion() { + return getClass().getPackage().getImplementationVersion(); + } + + public void setVersion(String version) { + } + + private boolean registration; + + public boolean getRegistration() { + return registration; + } + + public void setRegistration(boolean registration) { + this.registration = registration; + } + + private boolean readonly; + + public boolean getReadonly() { + return readonly; + } + + public void setReadonly(boolean readonly) { + this.readonly = readonly; + } + + private boolean deviceReadonly; + + public boolean getDeviceReadonly() { + return deviceReadonly; + } + + public void setDeviceReadonly(boolean deviceReadonly) { + this.deviceReadonly = deviceReadonly; + } + + private String map; + + public String getMap() { + return map; + } + + public void setMap(String map) { + this.map = map; + } + + private String bingKey; + + public String getBingKey() { + return bingKey; + } + + public void setBingKey(String bingKey) { + this.bingKey = bingKey; + } + + private String mapUrl; + + public String getMapUrl() { + return mapUrl; + } + + public void setMapUrl(String mapUrl) { + this.mapUrl = mapUrl; + } + + private double latitude; + + public double getLatitude() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + private double longitude; + + public double getLongitude() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + private int zoom; + + public int getZoom() { + return zoom; + } + + public void setZoom(int zoom) { + this.zoom = zoom; + } + + private boolean twelveHourFormat; + + public boolean getTwelveHourFormat() { + return twelveHourFormat; + } + + public void setTwelveHourFormat(boolean twelveHourFormat) { + this.twelveHourFormat = twelveHourFormat; + } + + private boolean forceSettings; + + public boolean getForceSettings() { + return forceSettings; + } + + public void setForceSettings(boolean forceSettings) { + this.forceSettings = forceSettings; + } + + private String coordinateFormat; + + public String getCoordinateFormat() { + return coordinateFormat; + } + + public void setCoordinateFormat(String coordinateFormat) { + this.coordinateFormat = coordinateFormat; + } + + private boolean limitCommands; + + public boolean getLimitCommands() { + return limitCommands; + } + + public void setLimitCommands(boolean limitCommands) { + this.limitCommands = limitCommands; + } + + private String poiLayer; + + public String getPoiLayer() { + return poiLayer; + } + + public void setPoiLayer(String poiLayer) { + this.poiLayer = poiLayer; + } +} diff --git a/src/main/java/org/traccar/model/Statistics.java b/src/main/java/org/traccar/model/Statistics.java new file mode 100644 index 000000000..cb72c91dd --- /dev/null +++ b/src/main/java/org/traccar/model/Statistics.java @@ -0,0 +1,122 @@ +/* + * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import java.util.Date; + +public class Statistics extends ExtendedModel { + + private Date captureTime; + + public Date getCaptureTime() { + return captureTime; + } + + public void setCaptureTime(Date captureTime) { + this.captureTime = captureTime; + } + + private int activeUsers; + + public int getActiveUsers() { + return activeUsers; + } + + public void setActiveUsers(int activeUsers) { + this.activeUsers = activeUsers; + } + + private int activeDevices; + + public int getActiveDevices() { + return activeDevices; + } + + public void setActiveDevices(int activeDevices) { + this.activeDevices = activeDevices; + } + + private int requests; + + public int getRequests() { + return requests; + } + + public void setRequests(int requests) { + this.requests = requests; + } + + private int messagesReceived; + + public int getMessagesReceived() { + return messagesReceived; + } + + public void setMessagesReceived(int messagesReceived) { + this.messagesReceived = messagesReceived; + } + + private int messagesStored; + + public int getMessagesStored() { + return messagesStored; + } + + public void setMessagesStored(int messagesStored) { + this.messagesStored = messagesStored; + } + + private int mailSent; + + public int getMailSent() { + return mailSent; + } + + public void setMailSent(int mailSent) { + this.mailSent = mailSent; + } + + private int smsSent; + + public int getSmsSent() { + return smsSent; + } + + public void setSmsSent(int smsSent) { + this.smsSent = smsSent; + } + + private int geocoderRequests; + + public int getGeocoderRequests() { + return geocoderRequests; + } + + public void setGeocoderRequests(int geocoderRequests) { + this.geocoderRequests = geocoderRequests; + } + + private int geolocationRequests; + + public int getGeolocationRequests() { + return geolocationRequests; + } + + public void setGeolocationRequests(int geolocationRequests) { + this.geolocationRequests = geolocationRequests; + } + +} diff --git a/src/main/java/org/traccar/model/Typed.java b/src/main/java/org/traccar/model/Typed.java new file mode 100644 index 000000000..313ec7bcd --- /dev/null +++ b/src/main/java/org/traccar/model/Typed.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Gabor Somogyi (gabor.g.somogyi@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.model; + +public class Typed { + + private String type; + + public Typed(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/src/main/java/org/traccar/model/User.java b/src/main/java/org/traccar/model/User.java new file mode 100644 index 000000000..976b6aac0 --- /dev/null +++ b/src/main/java/org/traccar/model/User.java @@ -0,0 +1,276 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import org.traccar.database.QueryExtended; +import org.traccar.database.QueryIgnore; +import org.traccar.helper.Hashing; + +import java.util.Date; + +public class User extends ExtendedModel { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private String login; + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + private String email; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email.trim(); + } + + private String phone; + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + private boolean readonly; + + public boolean getReadonly() { + return readonly; + } + + public void setReadonly(boolean readonly) { + this.readonly = readonly; + } + + private boolean administrator; + + public boolean getAdministrator() { + return administrator; + } + + public void setAdministrator(boolean administrator) { + this.administrator = administrator; + } + + private String map; + + public String getMap() { + return map; + } + + public void setMap(String map) { + this.map = map; + } + + private double latitude; + + public double getLatitude() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + private double longitude; + + public double getLongitude() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + private int zoom; + + public int getZoom() { + return zoom; + } + + public void setZoom(int zoom) { + this.zoom = zoom; + } + + private boolean twelveHourFormat; + + public boolean getTwelveHourFormat() { + return twelveHourFormat; + } + + public void setTwelveHourFormat(boolean twelveHourFormat) { + this.twelveHourFormat = twelveHourFormat; + } + + private String coordinateFormat; + + public String getCoordinateFormat() { + return coordinateFormat; + } + + public void setCoordinateFormat(String coordinateFormat) { + this.coordinateFormat = coordinateFormat; + } + + private boolean disabled; + + public boolean getDisabled() { + return disabled; + } + + public void setDisabled(boolean disabled) { + this.disabled = disabled; + } + + private Date expirationTime; + + public Date getExpirationTime() { + return expirationTime; + } + + public void setExpirationTime(Date expirationTime) { + this.expirationTime = expirationTime; + } + + private int deviceLimit; + + public int getDeviceLimit() { + return deviceLimit; + } + + public void setDeviceLimit(int deviceLimit) { + this.deviceLimit = deviceLimit; + } + + private int userLimit; + + public int getUserLimit() { + return userLimit; + } + + public void setUserLimit(int userLimit) { + this.userLimit = userLimit; + } + + private boolean deviceReadonly; + + public boolean getDeviceReadonly() { + return deviceReadonly; + } + + public void setDeviceReadonly(boolean deviceReadonly) { + this.deviceReadonly = deviceReadonly; + } + + private String token; + + public String getToken() { + return token; + } + + public void setToken(String token) { + if (token != null && !token.isEmpty()) { + if (!token.matches("^[a-zA-Z0-9-]{16,}$")) { + throw new IllegalArgumentException("Illegal token"); + } + this.token = token; + } else { + this.token = null; + } + } + + private boolean limitCommands; + + public boolean getLimitCommands() { + return limitCommands; + } + + public void setLimitCommands(boolean limitCommands) { + this.limitCommands = limitCommands; + } + + private String poiLayer; + + public String getPoiLayer() { + return poiLayer; + } + + public void setPoiLayer(String poiLayer) { + this.poiLayer = poiLayer; + } + + @QueryIgnore + public String getPassword() { + return null; + } + + public void setPassword(String password) { + if (password != null && !password.isEmpty()) { + Hashing.HashingResult hashingResult = Hashing.createHash(password); + hashedPassword = hashingResult.getHash(); + salt = hashingResult.getSalt(); + } + } + + private String hashedPassword; + + @JsonIgnore + @QueryExtended + public String getHashedPassword() { + return hashedPassword; + } + + public void setHashedPassword(String hashedPassword) { + this.hashedPassword = hashedPassword; + } + + private String salt; + + @JsonIgnore + @QueryExtended + public String getSalt() { + return salt; + } + + public void setSalt(String salt) { + this.salt = salt; + } + + public boolean isPasswordValid(String password) { + return Hashing.validatePassword(password, hashedPassword, salt); + } + +} diff --git a/src/main/java/org/traccar/model/WifiAccessPoint.java b/src/main/java/org/traccar/model/WifiAccessPoint.java new file mode 100644 index 000000000..87a77f3c0 --- /dev/null +++ b/src/main/java/org/traccar/model/WifiAccessPoint.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class WifiAccessPoint { + + public static WifiAccessPoint from(String macAddress, int signalStrength) { + WifiAccessPoint wifiAccessPoint = new WifiAccessPoint(); + wifiAccessPoint.setMacAddress(macAddress); + wifiAccessPoint.setSignalStrength(signalStrength); + return wifiAccessPoint; + } + + public static WifiAccessPoint from(String macAddress, int signalStrength, int channel) { + WifiAccessPoint wifiAccessPoint = from(macAddress, signalStrength); + wifiAccessPoint.setChannel(channel); + return wifiAccessPoint; + } + + private String macAddress; + + public String getMacAddress() { + return macAddress; + } + + public void setMacAddress(String macAddress) { + this.macAddress = macAddress; + } + + private Integer signalStrength; + + public Integer getSignalStrength() { + return signalStrength; + } + + public void setSignalStrength(Integer signalStrength) { + this.signalStrength = signalStrength; + } + + private Integer channel; + + public Integer getChannel() { + return channel; + } + + public void setChannel(Integer channel) { + this.channel = channel; + } + +} diff --git a/src/main/java/org/traccar/notification/EventForwarder.java b/src/main/java/org/traccar/notification/EventForwarder.java new file mode 100644 index 000000000..c0010ebbd --- /dev/null +++ b/src/main/java/org/traccar/notification/EventForwarder.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notification; + +import org.traccar.Context; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Geofence; +import org.traccar.model.Maintenance; +import org.traccar.model.Position; + +import javax.ws.rs.client.AsyncInvoker; +import javax.ws.rs.client.Invocation; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public abstract class EventForwarder { + + private final String url; + private final String header; + + public EventForwarder() { + url = Context.getConfig().getString("event.forward.url", "http://localhost/"); + header = Context.getConfig().getString("event.forward.header"); + } + + private static final String KEY_POSITION = "position"; + private static final String KEY_EVENT = "event"; + private static final String KEY_GEOFENCE = "geofence"; + private static final String KEY_DEVICE = "device"; + private static final String KEY_MAINTENANCE = "maintenance"; + private static final String KEY_USERS = "users"; + + public final void forwardEvent(Event event, Position position, Set<Long> users) { + + Invocation.Builder requestBuilder = Context.getClient().target(url).request(); + + if (header != null && !header.isEmpty()) { + for (String line: header.split("\\r?\\n")) { + String[] values = line.split(":", 2); + requestBuilder.header(values[0].trim(), values[1].trim()); + } + } + + executeRequest(event, position, users, requestBuilder.async()); + } + + protected Map<String, Object> preparePayload(Event event, Position position, Set<Long> users) { + Map<String, Object> data = new HashMap<>(); + data.put(KEY_EVENT, event); + if (position != null) { + data.put(KEY_POSITION, position); + } + Device device = Context.getIdentityManager().getById(event.getDeviceId()); + if (device != null) { + data.put(KEY_DEVICE, device); + } + if (event.getGeofenceId() != 0) { + Geofence geofence = Context.getGeofenceManager().getById(event.getGeofenceId()); + if (geofence != null) { + data.put(KEY_GEOFENCE, geofence); + } + } + if (event.getMaintenanceId() != 0) { + Maintenance maintenance = Context.getMaintenancesManager().getById(event.getMaintenanceId()); + if (maintenance != null) { + data.put(KEY_MAINTENANCE, maintenance); + } + } + data.put(KEY_USERS, Context.getUsersManager().getItems(users)); + return data; + } + + protected abstract void executeRequest( + Event event, Position position, Set<Long> users, AsyncInvoker invoker); + +} diff --git a/src/main/java/org/traccar/notification/FullMessage.java b/src/main/java/org/traccar/notification/FullMessage.java new file mode 100644 index 000000000..f66537c6e --- /dev/null +++ b/src/main/java/org/traccar/notification/FullMessage.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notification; + +public class FullMessage { + + private String subject; + private String body; + + public FullMessage(String subject, String body) { + this.subject = subject; + this.body = body; + } + + public String getSubject() { + return subject; + } + + public String getBody() { + return body; + } +} diff --git a/src/main/java/org/traccar/notification/JsonTypeEventForwarder.java b/src/main/java/org/traccar/notification/JsonTypeEventForwarder.java new file mode 100644 index 000000000..55d926fc8 --- /dev/null +++ b/src/main/java/org/traccar/notification/JsonTypeEventForwarder.java @@ -0,0 +1,18 @@ +package org.traccar.notification; + +import java.util.Set; + +import org.traccar.model.Event; +import org.traccar.model.Position; + +import javax.ws.rs.client.AsyncInvoker; +import javax.ws.rs.client.Entity; + +public class JsonTypeEventForwarder extends EventForwarder { + + @Override + protected void executeRequest(Event event, Position position, Set<Long> users, AsyncInvoker invoker) { + invoker.post(Entity.json(preparePayload(event, position, users))); + } + +} diff --git a/src/main/java/org/traccar/notification/MessageException.java b/src/main/java/org/traccar/notification/MessageException.java new file mode 100644 index 000000000..710b927b0 --- /dev/null +++ b/src/main/java/org/traccar/notification/MessageException.java @@ -0,0 +1,29 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notification; + +public class MessageException extends Exception { + + public MessageException(Throwable cause) { + super(cause); + } + + public MessageException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/traccar/notification/NotificationFormatter.java b/src/main/java/org/traccar/notification/NotificationFormatter.java new file mode 100644 index 000000000..2f8100226 --- /dev/null +++ b/src/main/java/org/traccar/notification/NotificationFormatter.java @@ -0,0 +1,118 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notification; + +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.util.Locale; + +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.tools.generic.DateTool; +import org.apache.velocity.tools.generic.NumberTool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.model.User; +import org.traccar.reports.ReportUtils; + +public final class NotificationFormatter { + + private static final Logger LOGGER = LoggerFactory.getLogger(NotificationFormatter.class); + + private NotificationFormatter() { + } + + public static VelocityContext prepareContext(long userId, Event event, Position position) { + + User user = Context.getPermissionsManager().getUser(userId); + Device device = Context.getIdentityManager().getById(event.getDeviceId()); + + VelocityContext velocityContext = new VelocityContext(); + velocityContext.put("user", user); + velocityContext.put("device", device); + velocityContext.put("event", event); + if (position != null) { + velocityContext.put("position", position); + velocityContext.put("speedUnit", ReportUtils.getSpeedUnit(userId)); + velocityContext.put("distanceUnit", ReportUtils.getDistanceUnit(userId)); + velocityContext.put("volumeUnit", ReportUtils.getVolumeUnit(userId)); + } + if (event.getGeofenceId() != 0) { + velocityContext.put("geofence", Context.getGeofenceManager().getById(event.getGeofenceId())); + } + if (event.getMaintenanceId() != 0) { + velocityContext.put("maintenance", Context.getMaintenancesManager().getById(event.getMaintenanceId())); + } + String driverUniqueId = event.getString(Position.KEY_DRIVER_UNIQUE_ID); + if (driverUniqueId != null) { + velocityContext.put("driver", Context.getDriversManager().getDriverByUniqueId(driverUniqueId)); + } + velocityContext.put("webUrl", Context.getVelocityEngine().getProperty("web.url")); + velocityContext.put("dateTool", new DateTool()); + velocityContext.put("numberTool", new NumberTool()); + velocityContext.put("timezone", ReportUtils.getTimezone(userId)); + velocityContext.put("locale", Locale.getDefault()); + return velocityContext; + } + + public static Template getTemplate(Event event, String path) { + + String templateFilePath; + Template template; + + try { + templateFilePath = Paths.get(path, event.getType() + ".vm").toString(); + template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); + } catch (ResourceNotFoundException error) { + LOGGER.warn("Notification template error", error); + templateFilePath = Paths.get(path, "unknown.vm").toString(); + template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); + } + return template; + } + + public static FullMessage formatFullMessage(long userId, Event event, Position position) { + VelocityContext velocityContext = prepareContext(userId, event, position); + String formattedMessage = formatMessage(velocityContext, userId, event, position, "full"); + + return new FullMessage((String) velocityContext.get("subject"), formattedMessage); + } + + public static String formatShortMessage(long userId, Event event, Position position) { + return formatMessage(null, userId, event, position, "short"); + } + + private static String formatMessage(VelocityContext vc, Long userId, Event event, Position position, + String templatePath) { + + VelocityContext velocityContext = vc; + if (velocityContext == null) { + velocityContext = prepareContext(userId, event, position); + } + StringWriter writer = new StringWriter(); + getTemplate(event, templatePath).merge(velocityContext, writer); + + return writer.toString(); + } + +} diff --git a/src/main/java/org/traccar/notification/NotificatorManager.java b/src/main/java/org/traccar/notification/NotificatorManager.java new file mode 100644 index 000000000..191748379 --- /dev/null +++ b/src/main/java/org/traccar/notification/NotificatorManager.java @@ -0,0 +1,94 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notification; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.model.Typed; +import org.traccar.notificators.NotificatorFirebase; +import org.traccar.notificators.NotificatorMail; +import org.traccar.notificators.NotificatorNull; +import org.traccar.notificators.Notificator; +import org.traccar.notificators.NotificatorSms; +import org.traccar.notificators.NotificatorWeb; +import org.traccar.notificators.NotificatorTelegram; + +public final class NotificatorManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorManager.class); + + private static final Notificator NULL_NOTIFICATOR = new NotificatorNull(); + + private final Map<String, Notificator> notificators = new HashMap<>(); + + public NotificatorManager() { + final String[] types = Context.getConfig().getString("notificator.types", "").split(","); + for (String type : types) { + String defaultNotificator = ""; + switch (type) { + case "web": + defaultNotificator = NotificatorWeb.class.getCanonicalName(); + break; + case "mail": + defaultNotificator = NotificatorMail.class.getCanonicalName(); + break; + case "sms": + defaultNotificator = NotificatorSms.class.getCanonicalName(); + break; + case "firebase": + defaultNotificator = NotificatorFirebase.class.getCanonicalName(); + break; + case "telegram": + defaultNotificator = NotificatorTelegram.class.getCanonicalName(); + break; + default: + break; + } + final String className = Context.getConfig() + .getString("notificator." + type + ".class", defaultNotificator); + try { + notificators.put(type, (Notificator) Class.forName(className).newInstance()); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + LOGGER.warn("Unable to load notificator class for " + type + " " + className + " " + e.getMessage()); + } + } + } + + public Notificator getNotificator(String type) { + final Notificator notificator = notificators.get(type); + if (notificator == null) { + LOGGER.warn("No notificator configured for type : " + type); + return NULL_NOTIFICATOR; + } + return notificator; + } + + public Set<Typed> getAllNotificatorTypes() { + Set<Typed> result = new HashSet<>(); + for (String notificator : notificators.keySet()) { + result.add(new Typed(notificator)); + } + return result; + } + +} diff --git a/src/main/java/org/traccar/notification/PropertiesProvider.java b/src/main/java/org/traccar/notification/PropertiesProvider.java new file mode 100644 index 000000000..f0078feef --- /dev/null +++ b/src/main/java/org/traccar/notification/PropertiesProvider.java @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notification; + +import org.traccar.config.Config; +import org.traccar.model.ExtendedModel; + +public class PropertiesProvider { + + private Config config; + + private ExtendedModel extendedModel; + + public PropertiesProvider(Config config) { + this.config = config; + } + + public PropertiesProvider(ExtendedModel extendedModel) { + this.extendedModel = extendedModel; + } + + public String getString(String key) { + if (config != null) { + return config.getString(key); + } else { + return extendedModel.getString(key); + } + } + + public String getString(String key, String defaultValue) { + String value = getString(key); + if (value == null) { + value = defaultValue; + } + return value; + } + + public int getInteger(String key, int defaultValue) { + if (config != null) { + return config.getInteger(key, defaultValue); + } else { + Object result = extendedModel.getAttributes().get(key); + if (result != null) { + return result instanceof String ? Integer.parseInt((String) result) : (Integer) result; + } else { + return defaultValue; + } + } + } + + public Boolean getBoolean(String key) { + if (config != null) { + if (config.hasKey(key)) { + return config.getBoolean(key); + } else { + return null; + } + } else { + Object result = extendedModel.getAttributes().get(key); + if (result != null) { + return result instanceof String ? Boolean.valueOf((String) result) : (Boolean) result; + } else { + return null; + } + } + } + +} diff --git a/src/main/java/org/traccar/notificators/Notificator.java b/src/main/java/org/traccar/notificators/Notificator.java new file mode 100644 index 000000000..5e40971c6 --- /dev/null +++ b/src/main/java/org/traccar/notificators/Notificator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notificators; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.notification.MessageException; + +public abstract class Notificator { + + private static final Logger LOGGER = LoggerFactory.getLogger(Notificator.class); + + public void sendAsync(final long userId, final Event event, final Position position) { + new Thread(new Runnable() { + public void run() { + try { + sendSync(userId, event, position); + } catch (MessageException | InterruptedException error) { + LOGGER.warn("Event send error", error); + } + } + }).start(); + } + + public abstract void sendSync(long userId, Event event, Position position) + throws MessageException, InterruptedException; + +} diff --git a/src/main/java/org/traccar/notificators/NotificatorFirebase.java b/src/main/java/org/traccar/notificators/NotificatorFirebase.java new file mode 100644 index 000000000..75d325de2 --- /dev/null +++ b/src/main/java/org/traccar/notificators/NotificatorFirebase.java @@ -0,0 +1,87 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notificators; + +import 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 NotificatorFirebase extends Notificator { + + private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorFirebase.class); + + private static final String URL = "https://fcm.googleapis.com/fcm/send"; + + private String key; + + public static class Notification { + @JsonProperty("body") + private String body; + } + + public static class Message { + @JsonProperty("registration_ids") + private String[] tokens; + @JsonProperty("notification") + private Notification notification; + } + + public NotificatorFirebase() { + key = Context.getConfig().getString("notificator.firebase.key"); + } + + @Override + public void sendSync(long userId, Event event, Position position) { + final User user = Context.getPermissionsManager().getUser(userId); + if (user.getAttributes().containsKey("notificationTokens")) { + + Notification notification = new Notification(); + notification.body = NotificationFormatter.formatShortMessage(userId, event, position).trim(); + + Message message = new Message(); + message.tokens = user.getString("notificationTokens").split("[, ]"); + message.notification = notification; + + Context.getClient().target(URL).request() + .header("Authorization", "key=" + key) + .async().post(Entity.json(message), new InvocationCallback<Object>() { + @Override + public void completed(Object o) { + } + + @Override + public void failed(Throwable throwable) { + LOGGER.warn("Firebase notification error", throwable); + } + }); + } + } + + @Override + public void sendAsync(long userId, Event event, Position position) { + sendSync(userId, event, position); + } + +} diff --git a/src/main/java/org/traccar/notificators/NotificatorMail.java b/src/main/java/org/traccar/notificators/NotificatorMail.java new file mode 100644 index 000000000..6b9774c58 --- /dev/null +++ b/src/main/java/org/traccar/notificators/NotificatorMail.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notificators; + +import org.traccar.Context; +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.notification.FullMessage; +import org.traccar.notification.MessageException; +import org.traccar.notification.NotificationFormatter; + +import javax.mail.MessagingException; + +public final class NotificatorMail extends Notificator { + + @Override + public void sendSync(long userId, Event event, Position position) throws MessageException { + try { + FullMessage message = NotificationFormatter.formatFullMessage(userId, event, position); + Context.getMailManager().sendMessage(userId, message.getSubject(), message.getBody()); + } catch (MessagingException e) { + throw new MessageException(e); + } + } + +} diff --git a/src/main/java/org/traccar/notificators/NotificatorNull.java b/src/main/java/org/traccar/notificators/NotificatorNull.java new file mode 100644 index 000000000..9364336be --- /dev/null +++ b/src/main/java/org/traccar/notificators/NotificatorNull.java @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notificators; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.model.Event; +import org.traccar.model.Position; + +public final class NotificatorNull extends Notificator { + + private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorNull.class); + + @Override + public void sendAsync(long userId, Event event, Position position) { + LOGGER.warn("You are using null notificatior, please check your configuration, notification not sent"); + } + + @Override + public void sendSync(long userId, Event event, Position position) { + LOGGER.warn("You are using null notificatior, please check your configuration, notification not sent"); + } + +} diff --git a/src/main/java/org/traccar/notificators/NotificatorSms.java b/src/main/java/org/traccar/notificators/NotificatorSms.java new file mode 100644 index 000000000..d5c791eae --- /dev/null +++ b/src/main/java/org/traccar/notificators/NotificatorSms.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notificators; + +import org.traccar.Context; +import org.traccar.Main; +import org.traccar.database.StatisticsManager; +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.model.User; +import org.traccar.notification.MessageException; +import org.traccar.notification.NotificationFormatter; +import org.traccar.sms.SmsManager; + +public final class NotificatorSms extends Notificator { + + private final SmsManager smsManager; + + public NotificatorSms() throws ClassNotFoundException, InstantiationException, IllegalAccessException { + final String smsClass = Context.getConfig().getString("notificator.sms.manager.class", ""); + if (smsClass.length() > 0) { + smsManager = (SmsManager) Class.forName(smsClass).newInstance(); + } else { + smsManager = Context.getSmsManager(); + } + } + + @Override + public void sendAsync(long userId, Event event, Position position) { + final User user = Context.getPermissionsManager().getUser(userId); + if (user.getPhone() != null) { + Main.getInjector().getInstance(StatisticsManager.class).registerSms(); + smsManager.sendMessageAsync(user.getPhone(), + NotificationFormatter.formatShortMessage(userId, event, position), false); + } + } + + @Override + public void sendSync(long userId, Event event, Position position) throws MessageException, InterruptedException { + final User user = Context.getPermissionsManager().getUser(userId); + if (user.getPhone() != null) { + Main.getInjector().getInstance(StatisticsManager.class).registerSms(); + smsManager.sendMessageSync(user.getPhone(), + NotificationFormatter.formatShortMessage(userId, event, position), false); + } + } + +} diff --git a/src/main/java/org/traccar/notificators/NotificatorTelegram.java b/src/main/java/org/traccar/notificators/NotificatorTelegram.java new file mode 100644 index 000000000..c0b51d043 --- /dev/null +++ b/src/main/java/org/traccar/notificators/NotificatorTelegram.java @@ -0,0 +1,77 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.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.notification.NotificationFormatter; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.InvocationCallback; + +public class NotificatorTelegram extends Notificator { + + private static final Logger LOGGER = LoggerFactory.getLogger(NotificatorTelegram.class); + + private String url; + private String chatId; + + public static class Message { + @JsonProperty("chat_id") + private String chatId; + @JsonProperty("text") + private String text; + @JsonProperty("parse_mode") + private String parseMode = "html"; + } + + public NotificatorTelegram() { + url = String.format( + "https://api.telegram.org/bot%s/sendMessage", + Context.getConfig().getString("notificator.telegram.key")); + chatId = Context.getConfig().getString("notificator.telegram.chatId"); + } + + @Override + public void sendSync(long userId, Event event, Position position) { + + Message message = new Message(); + message.chatId = chatId; + message.text = 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("Telegram 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/notificators/NotificatorWeb.java b/src/main/java/org/traccar/notificators/NotificatorWeb.java new file mode 100644 index 000000000..1d11c0b46 --- /dev/null +++ b/src/main/java/org/traccar/notificators/NotificatorWeb.java @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.notificators; + +import org.traccar.Context; +import org.traccar.model.Event; +import org.traccar.model.Position; + +public final class NotificatorWeb extends Notificator { + + @Override + public void sendSync(long userId, Event event, Position position) { + Context.getConnectionManager().updateEvent(userId, event); + } + +} diff --git a/src/main/java/org/traccar/protocol/AdmProtocol.java b/src/main/java/org/traccar/protocol/AdmProtocol.java new file mode 100644 index 000000000..93b1e355a --- /dev/null +++ b/src/main/java/org/traccar/protocol/AdmProtocol.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +import java.nio.ByteOrder; + +public class AdmProtocol extends BaseProtocol { + + public AdmProtocol() { + setSupportedDataCommands( + Command.TYPE_GET_DEVICE_STATUS, + Command.TYPE_CUSTOM); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 1, -3, 0, true)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new AdmProtocolEncoder(AdmProtocol.this)); + pipeline.addLast(new AdmProtocolDecoder(AdmProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java b/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java new file mode 100644 index 000000000..52d1439ed --- /dev/null +++ b/src/main/java/org/traccar/protocol/AdmProtocolDecoder.java @@ -0,0 +1,198 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.BitUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class AdmProtocolDecoder extends BaseProtocolDecoder { + + public AdmProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int CMD_RESPONSE_SIZE = 0x84; + public static final int MSG_IMEI = 0x03; + public static final int MSG_PHOTO = 0x0A; + public static final int MSG_ADM5 = 0x01; + + private Position decodeData(Channel channel, SocketAddress remoteAddress, ByteBuf buf, int type) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + if (BitUtil.to(type, 2) == 0) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_VERSION_FW, buf.readUnsignedByte()); + position.set(Position.KEY_INDEX, buf.readUnsignedShortLE()); + + int status = buf.readUnsignedShortLE(); + position.set(Position.KEY_STATUS, status); + position.setValid(!BitUtil.check(status, 5)); + position.setLatitude(buf.readFloatLE()); + position.setLongitude(buf.readFloatLE()); + position.setCourse(buf.readUnsignedShortLE() * 0.1); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE() * 0.1)); + + position.set(Position.KEY_ACCELERATION, buf.readUnsignedByte() * 0.1); + position.setAltitude(buf.readShortLE()); + position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1); + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte() & 0x0f); + + position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); + + position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.001); + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); + + if (BitUtil.check(type, 2)) { + buf.readUnsignedByte(); // vib + buf.readUnsignedByte(); // vib_count + + int out = buf.readUnsignedByte(); + for (int i = 0; i <= 3; i++) { + position.set(Position.PREFIX_OUT + (i + 1), BitUtil.check(out, i) ? 1 : 0); + } + + buf.readUnsignedByte(); // in_alarm + } + + if (BitUtil.check(type, 3)) { + for (int i = 1; i <= 6; i++) { + position.set(Position.PREFIX_ADC + i, buf.readUnsignedShortLE() * 0.001); + } + } + + if (BitUtil.check(type, 4)) { + for (int i = 1; i <= 2; i++) { + position.set(Position.PREFIX_COUNT + i, buf.readUnsignedIntLE()); + } + } + + if (BitUtil.check(type, 5)) { + for (int i = 1; i <= 3; i++) { + buf.readUnsignedShortLE(); // fuel level + } + for (int i = 1; i <= 3; i++) { + position.set(Position.PREFIX_TEMP + i, buf.readUnsignedByte()); + } + } + + if (BitUtil.check(type, 6)) { + int endIndex = buf.readerIndex() + buf.readUnsignedByte(); + while (buf.readerIndex() < endIndex) { + int mask = buf.readUnsignedByte(); + long value; + switch (BitUtil.from(mask, 6)) { + case 3: + value = buf.readLongLE(); + break; + case 2: + value = buf.readUnsignedIntLE(); + break; + case 1: + value = buf.readUnsignedShortLE(); + break; + default: + value = buf.readUnsignedByte(); + break; + } + int index = BitUtil.to(mask, 6); + switch (index) { + case 1: + position.set(Position.PREFIX_TEMP + 1, value); + break; + case 2: + position.set("humidity", value); + break; + case 3: + position.set("illumination", value); + break; + case 4: + position.set(Position.KEY_BATTERY, value); + break; + default: + position.set("can" + index, value); + break; + } + } + } + + if (BitUtil.check(type, 7)) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + } + + return position; + } + + return null; + } + + private Position parseCommandResponse(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + int responseTextLength = buf.bytesBefore((byte) 0); + if (responseTextLength < 0) { + responseTextLength = CMD_RESPONSE_SIZE - 3; + } + position.set(Position.KEY_RESULT, buf.readSlice(responseTextLength).toString(StandardCharsets.UTF_8)); + + return position; + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedShortLE(); // device id + + int size = buf.readUnsignedByte(); + if (size != CMD_RESPONSE_SIZE) { + int type = buf.readUnsignedByte(); + if (type == MSG_IMEI) { + getDeviceSession(channel, remoteAddress, buf.readSlice(15).toString(StandardCharsets.UTF_8)); + } else { + return decodeData(channel, remoteAddress, buf, type); + } + } else { + return parseCommandResponse(channel, remoteAddress, buf); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/AdmProtocolEncoder.java b/src/main/java/org/traccar/protocol/AdmProtocolEncoder.java new file mode 100644 index 000000000..c02fa4112 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AdmProtocolEncoder.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Anatoliy Golubev (darth.naihil@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class AdmProtocolEncoder extends StringProtocolEncoder { + + public AdmProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_GET_DEVICE_STATUS: + return formatCommand(command, "STATUS\r\n"); + + case Command.TYPE_CUSTOM: + return formatCommand(command, "%s\r\n", Command.KEY_DATA); + + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/AisProtocol.java b/src/main/java/org/traccar/protocol/AisProtocol.java new file mode 100644 index 000000000..3b9cad7c8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AisProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AisProtocol extends BaseProtocol { + + public AisProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new AisProtocolDecoder(AisProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AisProtocolDecoder.java b/src/main/java/org/traccar/protocol/AisProtocolDecoder.java new file mode 100644 index 000000000..8970f3d4a --- /dev/null +++ b/src/main/java/org/traccar/protocol/AisProtocolDecoder.java @@ -0,0 +1,140 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitBuffer; +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.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class AisProtocolDecoder extends BaseProtocolDecoder { + + public AisProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("!AIVDM,") + .number("(d+),") // count + .number("(d+),") // index + .number("(d+)?,") // id + .expression(".,") // radio channel + .expression("([^,]+),") // payload + .any() + .compile(); + + private Position decodePayload(Channel channel, SocketAddress remoteAddress, BitBuffer buf) { + + int type = buf.readUnsigned(6); + if (type == 1 || type == 2 || type == 3 || type == 18) { + + buf.readUnsigned(2); + int mmsi = buf.readUnsigned(30); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(mmsi)); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date()); + + if (type == 18) { + buf.readUnsigned(8); // reserved + } else { + position.set(Position.KEY_STATUS, buf.readUnsigned(4)); + position.set("turn", buf.readSigned(8)); + } + + position.setSpeed(buf.readUnsigned(10) * 0.1); + position.setValid(buf.readUnsigned(1) != 0); + position.setLongitude(buf.readSigned(28) * 0.0001 / 60.0); + position.setLatitude(buf.readSigned(27) * 0.0001 / 60.0); + position.setCourse(buf.readUnsigned(12) * 0.1); + + position.set("heading", buf.readUnsigned(9)); + + return position; + + } + + return null; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String[] sentences = ((String) msg).split("\\r\\n"); + + List<Position> positions = new ArrayList<>(); + Map<Integer, BitBuffer> buffers = new HashMap<>(); + + for (String sentence : sentences) { + if (!sentence.isEmpty()) { + Parser parser = new Parser(PATTERN, sentence); + if (parser.matches()) { + + int count = parser.nextInt(0); + int index = parser.nextInt(0); + int id = parser.nextInt(0); + + Position position = null; + + if (count == 1) { + BitBuffer bits = new BitBuffer(); + bits.writeEncoded(parser.next().getBytes(StandardCharsets.US_ASCII)); + position = decodePayload(channel, remoteAddress, bits); + } else { + BitBuffer bits = buffers.get(id); + if (bits == null) { + bits = new BitBuffer(); + buffers.put(id, bits); + } + bits.writeEncoded(parser.next().getBytes(StandardCharsets.US_ASCII)); + if (count == index) { + position = decodePayload(channel, remoteAddress, bits); + buffers.remove(id); + } + } + + if (position != null) { + positions.add(position); + } + + } + } + } + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/AlematicsFrameDecoder.java b/src/main/java/org/traccar/protocol/AlematicsFrameDecoder.java new file mode 100644 index 000000000..be7666657 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AlematicsFrameDecoder.java @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.LineBasedFrameDecoder; +import org.traccar.NetworkMessage; + +public class AlematicsFrameDecoder extends LineBasedFrameDecoder { + + private static final int MESSAGE_MINIMUM_LENGTH = 2; + + public AlematicsFrameDecoder(int maxFrameLength) { + super(maxFrameLength); + } + + // example of heartbeat: FA F8 00 07 00 03 15 AD 4E 78 3A D2 + + @Override + protected Object decode(ChannelHandlerContext ctx, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < MESSAGE_MINIMUM_LENGTH) { + return null; + } + + if (buf.getUnsignedShort(buf.readerIndex()) == 0xFAF8) { + ByteBuf heartbeat = buf.readRetainedSlice(12); + if (ctx != null && ctx.channel() != null) { + ctx.channel().writeAndFlush(new NetworkMessage(heartbeat, ctx.channel().remoteAddress())); + } + } + + return super.decode(ctx, buf); + } + +} diff --git a/src/main/java/org/traccar/protocol/AlematicsProtocol.java b/src/main/java/org/traccar/protocol/AlematicsProtocol.java new file mode 100644 index 000000000..8da2356b9 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AlematicsProtocol.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AlematicsProtocol extends BaseProtocol { + + public AlematicsProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new AlematicsFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new AlematicsProtocolDecoder(AlematicsProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AlematicsProtocolDecoder.java b/src/main/java/org/traccar/protocol/AlematicsProtocolDecoder.java new file mode 100644 index 000000000..25ccf6856 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AlematicsProtocolDecoder.java @@ -0,0 +1,157 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class AlematicsProtocolDecoder extends BaseProtocolDecoder { + + public AlematicsProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$T,") + .number("(d+),") // type + .number("(d+),") // index + .number("(d+),") // id + .number("(dddd)(dd)(dd)") // gps date + .number("(dd)(dd)(dd),") // gps time + .number("(dddd)(dd)(dd)") // device date + .number("(dd)(dd)(dd),") // device time + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(-?d+),") // altitude + .number("(d+.d),") // hdop + .number("(d+),") // satellites + .number("(d+),") // input + .number("(d+),") // output + .number("(d+.d+),") // adc + .number("(d+.d+),") // power + .number("(d+),") // odometer + .groupBegin() + .text("0,$S,") + .expression("(.*)") // text message + .or() + .number("(d+),") // extra mask + .expression("(.*)") // extra data + .or() + .any() + .groupEnd() + .compile(); + + private void decodeExtras(Position position, Parser parser) { + + int mask = parser.nextInt(); + String[] data = parser.next().split(","); + + int index = 0; + + if (BitUtil.check(mask, 0)) { + index++; // pulse counter 3 + } + + if (BitUtil.check(mask, 1)) { + position.set(Position.KEY_POWER, Integer.parseInt(data[index++])); + } + + if (BitUtil.check(mask, 2)) { + position.set(Position.KEY_BATTERY, Integer.parseInt(data[index++])); + } + + if (BitUtil.check(mask, 3)) { + position.set(Position.KEY_OBD_SPEED, Integer.parseInt(data[index++])); + } + + if (BitUtil.check(mask, 4)) { + position.set(Position.KEY_RPM, Integer.parseInt(data[index++])); + } + + if (BitUtil.check(mask, 5)) { + position.set(Position.KEY_RSSI, Integer.parseInt(data[index++])); + } + + if (BitUtil.check(mask, 6)) { + index++; // pulse counter 2 + } + + if (BitUtil.check(mask, 7)) { + index++; // magnetic rotation sensor rpm + } + + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + position.set(Position.KEY_TYPE, parser.nextInt()); + position.set(Position.KEY_INDEX, parser.nextInt()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setFixTime(parser.nextDateTime()); + position.setDeviceTime(parser.nextDateTime()); + + position.setValid(true); + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt(0))); + position.setCourse(parser.nextInt(0)); + position.setAltitude(parser.nextInt(0)); + + position.set(Position.KEY_HDOP, parser.nextDouble()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_INPUT, parser.nextInt()); + position.set(Position.KEY_OUTPUT, parser.nextInt()); + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.KEY_POWER, parser.nextDouble()); + position.set(Position.KEY_ODOMETER, parser.nextInt()); + + if (parser.hasNext()) { + position.set("text", parser.next()); + } else if (parser.hasNext()) { + decodeExtras(position, parser); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/AnytrekProtocol.java b/src/main/java/org/traccar/protocol/AnytrekProtocol.java new file mode 100644 index 000000000..4ab5833f7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AnytrekProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AnytrekProtocol extends BaseProtocol { + + public AnytrekProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 2, 2, 0)); + pipeline.addLast(new AnytrekProtocolDecoder(AnytrekProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AnytrekProtocolDecoder.java b/src/main/java/org/traccar/protocol/AnytrekProtocolDecoder.java new file mode 100644 index 000000000..c48f59c90 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AnytrekProtocolDecoder.java @@ -0,0 +1,120 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class AnytrekProtocolDecoder extends BaseProtocolDecoder { + + public AnytrekProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private void sendResponse(Channel channel, SocketAddress remoteAddress, int type) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeShort(0x7878); + response.writeShortLE(1 + 1 + 2 + 1 + 2); // length + response.writeByte(type); + response.writeByte(0); // error + response.writeShortLE(0); // report interval + response.writeByte(0); // clear alarm + response.writeShortLE(0); // checksum + response.writeByte('\r'); + response.writeByte('\n'); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + buf.readUnsignedShortLE(); // size + int type = buf.readUnsignedByte(); + + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(2); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_VERSION_FW, buf.readUnsignedShortLE()); + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.01); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + position.set(Position.KEY_SATELLITES, BitUtil.to(buf.readUnsignedByte(), 4)); + + double latitude = buf.readUnsignedIntLE() / 1800000.0; + double longitude = buf.readUnsignedIntLE() / 1800000.0; + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + + int flags = buf.readUnsignedShortLE(); + position.setCourse(BitUtil.to(flags, 10)); + position.setValid(BitUtil.check(flags, 12)); + + if (!BitUtil.check(flags, 10)) { + latitude = -latitude; + } + if (BitUtil.check(flags, 11)) { + longitude = -longitude; + } + + position.setLatitude(latitude); + position.setLongitude(longitude); + + buf.readUnsignedIntLE(); // info index + buf.readUnsignedIntLE(); // setting index + + flags = buf.readUnsignedByte(); + position.set(Position.KEY_CHARGE, BitUtil.check(flags, 0)); + position.set(Position.KEY_IGNITION, BitUtil.check(flags, 1)); + position.set(Position.KEY_ALARM, BitUtil.check(flags, 4) ? Position.ALARM_GENERAL : null); + + buf.readUnsignedShortLE(); // charge current + + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + + sendResponse(channel, remoteAddress, type); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/ApelProtocol.java b/src/main/java/org/traccar/protocol/ApelProtocol.java new file mode 100644 index 000000000..382aa16af --- /dev/null +++ b/src/main/java/org/traccar/protocol/ApelProtocol.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; +public class ApelProtocol extends BaseProtocol { + + public ApelProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 2, 4, 0, true)); + pipeline.addLast(new ApelProtocolDecoder(ApelProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ApelProtocolDecoder.java b/src/main/java/org/traccar/protocol/ApelProtocolDecoder.java new file mode 100644 index 000000000..c95a0366a --- /dev/null +++ b/src/main/java/org/traccar/protocol/ApelProtocolDecoder.java @@ -0,0 +1,210 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class ApelProtocolDecoder extends BaseProtocolDecoder { + + private long lastIndex; + private long newIndex; + + public ApelProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final short MSG_NULL = 0; + public static final short MSG_REQUEST_TRACKER_ID = 10; + public static final short MSG_TRACKER_ID = 11; + public static final short MSG_TRACKER_ID_EXT = 12; + public static final short MSG_DISCONNECT = 20; + public static final short MSG_REQUEST_PASSWORD = 30; + public static final short MSG_PASSWORD = 31; + public static final short MSG_REQUEST_STATE_FULL_INFO = 90; + public static final short MSG_STATE_FULL_INFO_T104 = 92; + public static final short MSG_REQUEST_CURRENT_GPS_DATA = 100; + public static final short MSG_CURRENT_GPS_DATA = 101; + public static final short MSG_REQUEST_SENSORS_STATE = 110; + public static final short MSG_SENSORS_STATE = 111; + public static final short MSG_SENSORS_STATE_T100 = 112; + public static final short MSG_SENSORS_STATE_T100_4 = 113; + public static final short MSG_REQUEST_LAST_LOG_INDEX = 120; + public static final short MSG_LAST_LOG_INDEX = 121; + public static final short MSG_REQUEST_LOG_RECORDS = 130; + public static final short MSG_LOG_RECORDS = 131; + public static final short MSG_EVENT = 141; + public static final short MSG_TEXT = 150; + public static final short MSG_ACK_ALARM = 160; + public static final short MSG_SET_TRACKER_MODE = 170; + public static final short MSG_GPRS_COMMAND = 180; + + private void sendSimpleMessage(Channel channel, short type) { + ByteBuf request = Unpooled.buffer(8); + request.writeShortLE(type); + request.writeShortLE(0); + request.writeIntLE(Checksum.crc32(request.nioBuffer(0, 4))); + channel.writeAndFlush(new NetworkMessage(request, channel.remoteAddress())); + } + + private void requestArchive(Channel channel) { + if (lastIndex == 0) { + lastIndex = newIndex; + } else if (newIndex > lastIndex) { + ByteBuf request = Unpooled.buffer(14); + request.writeShortLE(MSG_REQUEST_LOG_RECORDS); + request.writeShortLE(6); + request.writeIntLE((int) lastIndex); + request.writeShortLE(512); + request.writeIntLE(Checksum.crc32(request.nioBuffer(0, 10))); + channel.writeAndFlush(new NetworkMessage(request, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + int type = buf.readUnsignedShortLE(); + boolean alarm = (type & 0x8000) != 0; + type = type & 0x7FFF; + buf.readUnsignedShortLE(); // length + + if (alarm) { + sendSimpleMessage(channel, MSG_ACK_ALARM); + } + + if (type == MSG_TRACKER_ID) { + return null; // unsupported authentication type + } + + if (type == MSG_TRACKER_ID_EXT) { + + buf.readUnsignedIntLE(); // id + int length = buf.readUnsignedShortLE(); + buf.skipBytes(length); + length = buf.readUnsignedShortLE(); + getDeviceSession(channel, remoteAddress, buf.readSlice(length).toString(StandardCharsets.US_ASCII)); + + } else if (type == MSG_LAST_LOG_INDEX) { + + long index = buf.readUnsignedIntLE(); + if (index > 0) { + newIndex = index; + requestArchive(channel); + } + + } else if (type == MSG_CURRENT_GPS_DATA || type == MSG_STATE_FULL_INFO_T104 || type == MSG_LOG_RECORDS) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + int recordCount = 1; + if (type == MSG_LOG_RECORDS) { + recordCount = buf.readUnsignedShortLE(); + } + + for (int j = 0; j < recordCount; j++) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int subtype = type; + if (type == MSG_LOG_RECORDS) { + position.set(Position.KEY_ARCHIVE, true); + lastIndex = buf.readUnsignedIntLE() + 1; + position.set(Position.KEY_INDEX, lastIndex); + + subtype = buf.readUnsignedShortLE(); + if (subtype != MSG_CURRENT_GPS_DATA && subtype != MSG_STATE_FULL_INFO_T104) { + buf.skipBytes(buf.readUnsignedShortLE()); + continue; + } + buf.readUnsignedShortLE(); // length + } + + position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); + position.setLatitude(buf.readIntLE() * 180.0 / 0x7FFFFFFF); + position.setLongitude(buf.readIntLE() * 180.0 / 0x7FFFFFFF); + + if (subtype == MSG_STATE_FULL_INFO_T104) { + int speed = buf.readUnsignedByte(); + position.setValid(speed != 255); + position.setSpeed(UnitsConverter.knotsFromKph(speed)); + position.set(Position.KEY_HDOP, buf.readByte()); + } else { + int speed = buf.readShortLE(); + position.setValid(speed != -1); + position.setSpeed(UnitsConverter.knotsFromKph(speed * 0.01)); + } + + position.setCourse(buf.readShortLE() * 0.01); + position.setAltitude(buf.readShortLE()); + + if (subtype == MSG_STATE_FULL_INFO_T104) { + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + position.set(Position.KEY_EVENT, buf.readUnsignedShortLE()); + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + position.set(Position.KEY_INPUT, buf.readUnsignedByte()); + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + + for (int i = 1; i <= 8; i++) { + position.set(Position.PREFIX_ADC + i, buf.readUnsignedShortLE()); + } + + position.set(Position.PREFIX_COUNT + 1, buf.readUnsignedIntLE()); + position.set(Position.PREFIX_COUNT + 2, buf.readUnsignedIntLE()); + position.set(Position.PREFIX_COUNT + 3, buf.readUnsignedIntLE()); + } + + positions.add(position); + } + + buf.readUnsignedIntLE(); // crc + + if (type == MSG_LOG_RECORDS) { + requestArchive(channel); + } else { + sendSimpleMessage(channel, MSG_REQUEST_LAST_LOG_INDEX); + } + + return positions; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/AplicomFrameDecoder.java b/src/main/java/org/traccar/protocol/AplicomFrameDecoder.java new file mode 100644 index 000000000..56fca27c7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AplicomFrameDecoder.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class AplicomFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + // Skip Alive message + while (buf.isReadable() && Character.isDigit(buf.getByte(buf.readerIndex()))) { + buf.readByte(); + } + + // Check minimum length + if (buf.readableBytes() < 11) { + return null; + } + + // Read flags + int version = buf.getUnsignedByte(buf.readerIndex() + 1); + int offset = 1 + 1 + 3; + if ((version & 0x80) != 0) { + offset += 4; + } + + // Get data length + int length = buf.getUnsignedShort(buf.readerIndex() + offset); + offset += 2; + if ((version & 0x40) != 0) { + offset += 3; + } + length += offset; // add header + + // Return buffer + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/AplicomProtocol.java b/src/main/java/org/traccar/protocol/AplicomProtocol.java new file mode 100644 index 000000000..2b9dbf97c --- /dev/null +++ b/src/main/java/org/traccar/protocol/AplicomProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AplicomProtocol extends BaseProtocol { + + public AplicomProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new AplicomFrameDecoder()); + pipeline.addLast(new AplicomProtocolDecoder(AplicomProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AplicomProtocolDecoder.java b/src/main/java/org/traccar/protocol/AplicomProtocolDecoder.java new file mode 100644 index 000000000..215aa0211 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AplicomProtocolDecoder.java @@ -0,0 +1,702 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Date; + +public class AplicomProtocolDecoder extends BaseProtocolDecoder { + + private static final Logger LOGGER = LoggerFactory.getLogger(AplicomProtocolDecoder.class); + + public AplicomProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final long IMEI_BASE_TC65_V20 = 0x1437207000000L; + private static final long IMEI_BASE_TC65_V28 = 358244010000000L; + private static final long IMEI_BASE_TC65I_V11 = 0x14143B4000000L; + + private static boolean validateImei(long imei) { + return Checksum.luhn(imei / 10) == imei % 10; + } + + private static long imeiFromUnitId(long unitId) { + + if (unitId == 0) { + + return 0; + + } else { + + // Try TC65i + long imei = IMEI_BASE_TC65I_V11 + unitId; + if (validateImei(imei)) { + return imei; + } + + // Try TC65 v2.8 + imei = IMEI_BASE_TC65_V28 + ((unitId + 0xA8180) & 0xFFFFFF); + if (validateImei(imei)) { + return imei; + } + + // Try TC65 v2.0 + imei = IMEI_BASE_TC65_V20 + unitId; + if (validateImei(imei)) { + return imei; + } + + } + + return unitId; + } + + private static final int DEFAULT_SELECTOR_D = 0x0002fC; + private static final int DEFAULT_SELECTOR_E = 0x007ffc; + private static final int DEFAULT_SELECTOR_F = 0x0007fd; + + private static final int EVENT_DATA = 119; + + private void decodeEventData(Position position, ByteBuf buf, int event) { + switch (event) { + case 2: + case 40: + buf.readUnsignedByte(); + break; + case 9: + buf.readUnsignedMedium(); + break; + case 31: + case 32: + buf.readUnsignedShort(); + break; + case 38: + buf.skipBytes(4 * 9); + break; + case 113: + buf.readUnsignedInt(); + buf.readUnsignedByte(); + break; + case 119: + position.set("eventData", ByteBufUtil.hexDump( + buf, buf.readerIndex(), Math.min(buf.readableBytes(), 1024))); + break; + case 121: + case 142: + buf.readLong(); + break; + case 130: + buf.readUnsignedInt(); // incorrect + break; + case 188: + decodeEB(position, buf); + break; + default: + break; + } + } + + private void decodeCanData(ByteBuf buf, Position position) { + + buf.readUnsignedMedium(); // packet identifier + position.set(Position.KEY_VERSION_FW, buf.readUnsignedByte()); + int count = buf.readUnsignedByte(); + buf.readUnsignedByte(); // batch count + buf.readUnsignedShort(); // selector bit + buf.readUnsignedInt(); // timestamp + + buf.skipBytes(8); + + ArrayList<ByteBuf> values = new ArrayList<>(count); + + for (int i = 0; i < count; i++) { + values.add(buf.readSlice(8)); + } + + for (int i = 0; i < count; i++) { + ByteBuf value = values.get(i); + switch (buf.readInt()) { + case 0x20D: + position.set(Position.KEY_RPM, value.readShortLE()); + position.set("dieselTemperature", value.readShortLE() * 0.1); + position.set("batteryVoltage", value.readShortLE() * 0.01); + position.set("supplyAirTempDep1", value.readShortLE() * 0.1); + break; + case 0x30D: + position.set("activeAlarm", ByteBufUtil.hexDump(value)); + break; + case 0x40C: + position.set("airTempDep1", value.readShortLE() * 0.1); + position.set("airTempDep2", value.readShortLE() * 0.1); + break; + case 0x40D: + position.set("coldUnitState", ByteBufUtil.hexDump(value)); + break; + case 0x50C: + position.set("defrostTempDep1", value.readShortLE() * 0.1); + position.set("defrostTempDep2", value.readShortLE() * 0.1); + break; + case 0x50D: + position.set("condenserPressure", value.readShortLE() * 0.1); + position.set("suctionPressure", value.readShortLE() * 0.1); + break; + case 0x58C: + value.readByte(); + value.readShort(); // index + switch (value.readByte()) { + case 0x01: + position.set("setpointZone1", value.readIntLE() * 0.1); + break; + case 0x02: + position.set("setpointZone2", value.readIntLE() * 0.1); + break; + case 0x05: + position.set("unitType", value.readIntLE()); + break; + case 0x13: + position.set("dieselHours", value.readIntLE() / 60 / 60); + break; + case 0x14: + position.set("electricHours", value.readIntLE() / 60 / 60); + break; + case 0x17: + position.set("serviceIndicator", value.readIntLE()); + break; + case 0x18: + position.set("softwareVersion", value.readIntLE() * 0.01); + break; + default: + break; + } + break; + default: + LOGGER.warn("Aplicom CAN decoding error", new UnsupportedOperationException()); + break; + } + } + } + + private void decodeD(Position position, ByteBuf buf, int selector, int event) { + + if ((selector & 0x0008) != 0) { + position.setValid((buf.readUnsignedByte() & 0x40) != 0); + } else { + getLastLocation(position, null); + } + + if ((selector & 0x0004) != 0) { + position.setDeviceTime(new Date(buf.readUnsignedInt() * 1000)); + } + + if ((selector & 0x0008) != 0) { + position.setFixTime(new Date(buf.readUnsignedInt() * 1000)); + if (position.getDeviceTime() == null) { + position.setDeviceTime(position.getFixTime()); + } + position.setLatitude(buf.readInt() / 1000000.0); + position.setLongitude(buf.readInt() / 1000000.0); + position.set(Position.KEY_SATELLITES_VISIBLE, buf.readUnsignedByte()); + } + + if ((selector & 0x0010) != 0) { + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.set("maximumSpeed", buf.readUnsignedByte()); + position.setCourse(buf.readUnsignedByte() * 2.0); + } + + if ((selector & 0x0040) != 0) { + position.set(Position.KEY_INPUT, buf.readUnsignedByte()); + } + + if ((selector & 0x0020) != 0) { + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 3, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 4, buf.readUnsignedShort()); + } + + if ((selector & 0x8000) != 0) { + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.001); + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.001); + } + + // Pulse rate 1 + if ((selector & 0x10000) != 0) { + buf.readUnsignedShort(); + buf.readUnsignedInt(); + } + + // Pulse rate 2 + if ((selector & 0x20000) != 0) { + buf.readUnsignedShort(); + buf.readUnsignedInt(); + } + + if ((selector & 0x0080) != 0) { + position.set("trip1", buf.readUnsignedInt()); + } + + if ((selector & 0x0100) != 0) { + position.set("trip2", buf.readUnsignedInt()); + } + + if ((selector & 0x0040) != 0) { + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + } + + if ((selector & 0x0200) != 0) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, + String.valueOf(((long) buf.readUnsignedShort()) << 32) + buf.readUnsignedInt()); + } + + if ((selector & 0x0400) != 0) { + buf.readUnsignedByte(); // Keypad + } + + if ((selector & 0x0800) != 0) { + position.setAltitude(buf.readShort()); + } + + if ((selector & 0x2000) != 0) { + buf.readUnsignedShort(); // snapshot counter + } + + if ((selector & 0x4000) != 0) { + buf.skipBytes(8); // state flags + } + + if ((selector & 0x80000) != 0) { + buf.skipBytes(11); // cell info + } + + if ((selector & 0x1000) != 0) { + decodeEventData(position, buf, event); + } + + if (Context.getConfig().getBoolean(getProtocolName() + ".can") + && buf.isReadable() && (selector & 0x1000) != 0 && event == EVENT_DATA) { + decodeCanData(buf, position); + } + } + + private void decodeE(Position position, ByteBuf buf, int selector) { + + if ((selector & 0x0008) != 0) { + position.set("tachographEvent", buf.readUnsignedShort()); + } + + if ((selector & 0x0004) != 0) { + getLastLocation(position, new Date(buf.readUnsignedInt() * 1000)); + } else { + getLastLocation(position, null); + } + + if ((selector & 0x0010) != 0) { + String time = buf.readUnsignedByte() + "s " + buf.readUnsignedByte() + "m " + buf.readUnsignedByte() + "h " + + buf.readUnsignedByte() + "M " + buf.readUnsignedByte() + "D " + buf.readUnsignedByte() + "Y " + + buf.readByte() + "m " + buf.readByte() + "h"; + position.set("tachographTime", time); + } + + position.set("workState", buf.readUnsignedByte()); + position.set("driver1State", buf.readUnsignedByte()); + position.set("driver2State", buf.readUnsignedByte()); + + if ((selector & 0x0020) != 0) { + position.set("tachographStatus", buf.readUnsignedByte()); + } + + if ((selector & 0x0040) != 0) { + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedShort() / 256.0); + } + + if ((selector & 0x0080) != 0) { + position.set(Position.KEY_OBD_ODOMETER, buf.readUnsignedInt() * 5); + } + + if ((selector & 0x0100) != 0) { + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedInt() * 5); + } + + if ((selector & 0x8000) != 0) { + position.set("kFactor", buf.readUnsignedShort() * 0.001 + " pulses/m"); + } + + if ((selector & 0x0200) != 0) { + position.set(Position.KEY_RPM, buf.readUnsignedShort() * 0.125); + } + + if ((selector & 0x0400) != 0) { + position.set("extraInfo", buf.readUnsignedShort()); + } + + if ((selector & 0x0800) != 0) { + position.set(Position.KEY_VIN, buf.readSlice(18).toString(StandardCharsets.US_ASCII).trim()); + } + + if ((selector & 0x2000) != 0) { + buf.readUnsignedByte(); // card 1 type + buf.readUnsignedByte(); // card 1 country code + String card = buf.readSlice(20).toString(StandardCharsets.US_ASCII).trim(); + if (!card.isEmpty()) { + position.set("card1", card); + } + } + + if ((selector & 0x4000) != 0) { + buf.readUnsignedByte(); // card 2 type + buf.readUnsignedByte(); // card 2 country code + String card = buf.readSlice(20).toString(StandardCharsets.US_ASCII).trim(); + if (!card.isEmpty()) { + position.set("card2", card); + } + } + + if ((selector & 0x10000) != 0) { + int count = buf.readUnsignedByte(); + for (int i = 1; i <= count; i++) { + position.set("driver" + i, buf.readSlice(22).toString(StandardCharsets.US_ASCII).trim()); + position.set("driverTime" + i, buf.readUnsignedInt()); + } + } + } + + private void decodeH(Position position, ByteBuf buf, int selector) { + + if ((selector & 0x0004) != 0) { + getLastLocation(position, new Date(buf.readUnsignedInt() * 1000)); + } else { + getLastLocation(position, null); + } + + if ((selector & 0x0040) != 0) { + buf.readUnsignedInt(); // reset time + } + + if ((selector & 0x2000) != 0) { + buf.readUnsignedShort(); // snapshot counter + } + + int index = 1; + while (buf.readableBytes() > 0) { + + position.set("h" + index + "Index", buf.readUnsignedByte()); + + buf.readUnsignedShort(); // length + + int n = buf.readUnsignedByte(); + int m = buf.readUnsignedByte(); + + position.set("h" + index + "XLength", n); + position.set("h" + index + "YLength", m); + + if ((selector & 0x0008) != 0) { + position.set("h" + index + "XType", buf.readUnsignedByte()); + position.set("h" + index + "YType", buf.readUnsignedByte()); + position.set("h" + index + "Parameters", buf.readUnsignedByte()); + } + + boolean percentageFormat = (selector & 0x0020) != 0; + + StringBuilder data = new StringBuilder(); + for (int i = 0; i < n * m; i++) { + if (percentageFormat) { + data.append(buf.readUnsignedByte() * 0.5).append("%").append(" "); + } else { + data.append(buf.readUnsignedShort()).append(" "); + } + } + + position.set("h" + index + "Data", data.toString().trim()); + + position.set("h" + index + "Total", buf.readUnsignedInt()); + + if ((selector & 0x0010) != 0) { + int k = buf.readUnsignedByte(); + + data = new StringBuilder(); + for (int i = 1; i < n; i++) { + if (k == 1) { + data.append(buf.readByte()).append(" "); + } else if (k == 2) { + data.append(buf.readShort()).append(" "); + } + } + position.set("h" + index + "XLimits", data.toString().trim()); + + data = new StringBuilder(); + for (int i = 1; i < m; i++) { + if (k == 1) { + data.append(buf.readByte()).append(" "); + } else if (k == 2) { + data.append(buf.readShort()).append(" "); + } + } + position.set("h" + index + "YLimits", data.toString().trim()); + } + + index += 1; + } + } + + private void decodeEB(Position position, ByteBuf buf) { + + if (buf.readByte() != (byte) 'E' || buf.readByte() != (byte) 'B') { + return; + } + + position.set(Position.KEY_VERSION_FW, buf.readUnsignedByte()); + position.set(Position.KEY_EVENT, buf.readUnsignedShort()); + position.set("dataValidity", buf.readUnsignedByte()); + position.set("towed", buf.readUnsignedByte()); + buf.readUnsignedShort(); // length + + while (buf.readableBytes() > 0) { + buf.readUnsignedByte(); // towed position + int type = buf.readUnsignedByte(); + int length = buf.readUnsignedByte(); + int end = buf.readerIndex() + length; + + switch (type) { + case 0x01: + position.set("brakeFlags", ByteBufUtil.hexDump(buf.readSlice(length))); + break; + case 0x02: + position.set("wheelSpeed", buf.readUnsignedShort() / 256.0); + position.set("wheelSpeedDifference", buf.readUnsignedShort() / 256.0 - 125.0); + position.set("lateralAcceleration", buf.readUnsignedByte() / 10.0 - 12.5); + position.set("vehicleSpeed", buf.readUnsignedShort() / 256.0); + break; + case 0x03: + position.set(Position.KEY_AXLE_WEIGHT, buf.readUnsignedShort() * 2); + break; + case 0x04: + position.set("tyrePressure", buf.readUnsignedByte() * 10); + position.set("pneumaticPressure", buf.readUnsignedByte() * 5); + break; + case 0x05: + position.set("brakeLining", buf.readUnsignedByte() * 0.4); + position.set("brakeTemperature", buf.readUnsignedByte() * 10); + break; + case 0x06: + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 5L); + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedInt() * 5L); + position.set(Position.KEY_ODOMETER_SERVICE, (buf.readUnsignedInt() - 2105540607) * 5L); + break; + case 0x0A: + position.set("absStatusCounter", buf.readUnsignedShort()); + position.set("atvbStatusCounter", buf.readUnsignedShort()); + position.set("vdcActiveCounter", buf.readUnsignedShort()); + break; + case 0x0B: + position.set("brakeMinMaxData", ByteBufUtil.hexDump(buf.readSlice(length))); + break; + case 0x0C: + position.set("missingPgn", ByteBufUtil.hexDump(buf.readSlice(length))); + break; + case 0x0D: + buf.readUnsignedByte(); + position.set("towedDetectionStatus", buf.readUnsignedInt()); + buf.skipBytes(17); // vin + break; + case 0x0E: + default: + break; + } + + buf.readerIndex(end); + } + } + + private void decodeF(Position position, ByteBuf buf, int selector) { + + Date deviceTime = null; + + if ((selector & 0x0004) != 0) { + deviceTime = new Date(buf.readUnsignedInt() * 1000); + } + + getLastLocation(position, deviceTime); + + buf.readUnsignedByte(); // data validity + + if ((selector & 0x0008) != 0) { + position.set(Position.KEY_RPM, buf.readUnsignedShort()); + position.set("rpmMax", buf.readUnsignedShort()); + position.set("rpmMin", buf.readUnsignedShort()); + } + + if ((selector & 0x0010) != 0) { + position.set("engineTemp", buf.readShort()); + position.set("engineTempMax", buf.readShort()); + position.set("engineTempMin", buf.readShort()); + } + + if ((selector & 0x0020) != 0) { + position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(buf.readUnsignedInt())); + position.set("serviceDistance", buf.readInt()); + position.set("driverActivity", buf.readUnsignedByte()); + position.set(Position.KEY_THROTTLE, buf.readUnsignedByte()); + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte()); + } + + if ((selector & 0x0040) != 0) { + position.set(Position.KEY_FUEL_USED, buf.readUnsignedInt()); + } + + if ((selector & 0x0080) != 0) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + } + + if ((selector & 0x0100) != 0) { + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedByte()); + position.set("speedMax", buf.readUnsignedByte()); + position.set("speedMin", buf.readUnsignedByte()); + position.set("hardBraking", buf.readUnsignedByte()); + } + + if ((selector & 0x0200) != 0) { + position.set("tachographSpeed", buf.readUnsignedByte()); + position.set("driver1State", buf.readUnsignedByte()); + position.set("driver2State", buf.readUnsignedByte()); + position.set("tachographStatus", buf.readUnsignedByte()); + position.set("overspeedCount", buf.readUnsignedByte()); + } + + if ((selector & 0x0800) != 0) { + position.set(Position.KEY_HOURS, buf.readUnsignedInt() * 0.05); + position.set(Position.KEY_RPM, buf.readUnsignedShort() * 0.125); + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedShort() / 256.0); + position.set(Position.KEY_FUEL_USED, buf.readUnsignedInt() * 0.5); + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte() * 0.4); + } + + if ((selector & 0x1000) != 0) { + position.set("ambientTemperature", buf.readUnsignedShort() * 0.03125 - 273); + buf.readUnsignedShort(); // fuel rate + position.set("fuelEconomy", buf.readUnsignedShort() / 512.0); + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedInt() * 0.001); + buf.readUnsignedByte(); // pto drive engagement + } + + if ((selector & 0x2000) != 0) { + buf.skipBytes(buf.readUnsignedByte()); // driver identification + } + + if ((selector & 0x4000) != 0) { + position.set("torque", buf.readUnsignedByte()); + position.set("brakePressure1", buf.readUnsignedByte() * 8); + position.set("brakePressure2", buf.readUnsignedByte() * 8); + position.set("grossWeight", buf.readUnsignedShort() * 10); + position.set("exhaustFluid", buf.readUnsignedByte() * 0.4); + buf.readUnsignedByte(); // retarder torque mode + position.set("retarderTorque", buf.readUnsignedByte()); + position.set("retarderSelection", buf.readUnsignedByte() * 0.4); + buf.skipBytes(8); // tell tale status block 1 + buf.skipBytes(8); // tell tale status block 2 + buf.skipBytes(8); // tell tale status block 3 + buf.skipBytes(8); // tell tale status block 4 + } + + if ((selector & 0x8000) != 0) { + position.set("parkingBrakeStatus", buf.readUnsignedByte()); + position.set("doorStatus", buf.readUnsignedByte()); + buf.skipBytes(8); // status per door + position.set("alternatorStatus", buf.readUnsignedByte()); + position.set("selectedGear", buf.readUnsignedByte()); + position.set("currentGear", buf.readUnsignedByte()); + buf.skipBytes(4 * 2); // air suspension pressure + } + + if ((selector & 0x0400) != 0) { + int count = buf.readUnsignedByte(); + for (int i = 0; i < count; i++) { + position.set("axle" + i, buf.readUnsignedShort()); + } + } + + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + char protocol = (char) buf.readByte(); + int version = buf.readUnsignedByte(); + + String imei; + if ((version & 0x80) != 0) { + imei = String.valueOf((buf.readUnsignedInt() << (3 * 8)) | buf.readUnsignedMedium()); + } else { + imei = String.valueOf(imeiFromUnitId(buf.readUnsignedMedium())); + } + + buf.readUnsignedShort(); // length + + int selector = DEFAULT_SELECTOR_D; + if (protocol == 'E') { + selector = DEFAULT_SELECTOR_E; + } else if (protocol == 'F') { + selector = DEFAULT_SELECTOR_F; + } + if ((version & 0x40) != 0) { + selector = buf.readUnsignedMedium(); + } + + Position position = new Position(getProtocolName()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + int event = buf.readUnsignedByte(); + position.set(Position.KEY_EVENT, event); + position.set("eventInfo", buf.readUnsignedByte()); + + if (protocol == 'D') { + decodeD(position, buf, selector, event); + } else if (protocol == 'E') { + decodeE(position, buf, selector); + } else if (protocol == 'H') { + decodeH(position, buf, selector); + } else if (protocol == 'F') { + decodeF(position, buf, selector); + } else { + return null; + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/AppelloProtocol.java b/src/main/java/org/traccar/protocol/AppelloProtocol.java new file mode 100644 index 000000000..1ca4168e4 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AppelloProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AppelloProtocol extends BaseProtocol { + + public AppelloProtocol() { + 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 AppelloProtocolDecoder(AppelloProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AppelloProtocolDecoder.java b/src/main/java/org/traccar/protocol/AppelloProtocolDecoder.java new file mode 100644 index 000000000..47e329234 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AppelloProtocolDecoder.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class AppelloProtocolDecoder extends BaseProtocolDecoder { + + public AppelloProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("FOLLOWIT,") // brand + .number("(d+),") // imei + .groupBegin() + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd).?d*,") // time (hhmmss) + .or() + .text("UTCTIME,") + .groupEnd() + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(d+),") // satellites + .number("(-?d+),") // altitude + .expression("([FL]),") // gps state + .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; + } + + String imei = parser.next(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (parser.hasNext(6)) { + position.setTime(parser.nextDateTime()); + } else { + getLastLocation(position, null); + } + + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + + position.setAltitude(parser.nextDouble(0)); + + position.setValid(parser.next().equals("F")); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/AppletProtocol.java b/src/main/java/org/traccar/protocol/AppletProtocol.java new file mode 100644 index 000000000..2297dca03 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AppletProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AppletProtocol extends BaseProtocol { + + public AppletProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(16384)); + pipeline.addLast(new AppletProtocolDecoder(AppletProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AppletProtocolDecoder.java b/src/main/java/org/traccar/protocol/AppletProtocolDecoder.java new file mode 100644 index 000000000..7a995cc24 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AppletProtocolDecoder.java @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; + +import java.net.SocketAddress; + +public class AppletProtocolDecoder extends BaseHttpProtocolDecoder { + + public AppletProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, request.headers().get("From")); + if (deviceSession != null) { + sendResponse(channel, HttpResponseStatus.OK); + } else { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/AquilaProtocol.java b/src/main/java/org/traccar/protocol/AquilaProtocol.java new file mode 100644 index 000000000..5ca1ec091 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AquilaProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AquilaProtocol extends BaseProtocol { + + public AquilaProtocol() { + 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 AquilaProtocolDecoder(AquilaProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AquilaProtocolDecoder.java b/src/main/java/org/traccar/protocol/AquilaProtocolDecoder.java new file mode 100644 index 000000000..3c43ddf2a --- /dev/null +++ b/src/main/java/org/traccar/protocol/AquilaProtocolDecoder.java @@ -0,0 +1,219 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class AquilaProtocolDecoder extends BaseProtocolDecoder { + + public AquilaProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_A = new PatternBuilder() + .text("$$") + .expression("[^,]*,") // client + .number("(d+),") // device serial number + .number("(d+),") // event + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([AV]),") // validity + .groupBegin() + .number("(d+),") // gsm + .number("(d+),") // speed + .number("(d+),") // distance + .groupBegin() + .number("d+,") // driver code + .number("(d+),") // fuel + .number("([01]),") // io 1 + .number("[01],") // case open switch + .number("[01],") // over speed start + .number("[01],") // over speed end + .number("(?:d+,){3}") // reserved + .number("([01]),") // power status + .number("([01]),") // io 2 + .number("d+,") // reserved + .number("([01]),") // ignition + .number("[01],") // ignition off event + .number("(?:d+,){7}") // reserved + .number("[01],") // corner packet + .number("(?:d+,){8}") // reserved + .number("([01]),") // course bit 0 + .number("([01]),") // course bit 1 + .number("([01]),") // course bit 2 + .number("([01]),") // course bit 3 + .or() + .number("(d+),") // course + .number("(?:d+,){3}") // reserved + .number("[01],") // over speed start + .number("[01],") // over speed end + .number("(?:d+,){3}") // reserved + .number("([01]),") // power status + .number("(?:d+,){2}") // reserved + .number("[01],") // ignition on event + .number("([01]),") // ignition + .number("[01],") // ignition off event + .number("(?:d+,){5}") // reserved + .number("[01],") // low battery + .number("[01],") // corner packet + .number("(?:d+,){6}") // reserved + .number("[01],") // hard acceleration + .number("[01],") // hard braking + .number("[01],[01],[01],[01],") // course bits + .number("(d+),") // external voltage + .number("(d+),") // internal voltage + .number("(?:d+,){6}") // reserved + .expression("P([^,]+),") // obd + .expression("D([^,]+),") // dtcs + .number("-?d+,") // accelerometer x + .number("-?d+,") // accelerometer y + .number("-?d+,") // accelerometer z + .number("d+,") // delta distance + .or() + .number("(d+),") // course + .number("(d+),") // satellites + .number("(d+.d+),") // hdop + .number("(?:d+,){2}") // reserved + .number("(d+),") // adc 1 + .number("([01]),") // di 1 + .number("[01],") // case open + .number("[01],") // over speed start + .number("[01],") // over speed end + .number("(?:[01],){2}") // reserved + .number("[01],") // immobilizer + .number("([01]),") // power status + .number("([01]),") // di 2 + .number("(?:[01],){2}") // reserved + .number("([01]),") // ignition + .number("(?:[01],){6}") // reserved + .number("[01],") // low battery + .number("[01],") // corner packet + .number("(?:[01],){4}") // reserved + .number("[01],") // do 1 + .number("[01],") // reserved + .number("[01],") // hard acceleration + .number("[01],") // hard braking + .number("(?:[01],){4}") // reserved + .number("(d+),") // external voltage + .number("(d+),") // internal voltage + .groupEnd() + .or() + .number("(d+),") // sensor id + .expression("([^,]+),") // sensor data + .groupEnd() + .text("*") + .number("xx") // checksum + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN_A, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_EVENT, parser.nextInt(0)); + + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + + position.setTime(parser.nextDateTime()); + + position.setValid(parser.next().equals("A")); + + if (parser.hasNext(3)) { + position.set(Position.KEY_RSSI, parser.nextInt(0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + } + + if (parser.hasNext(9)) { + + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); + position.set(Position.PREFIX_IN + 1, parser.next()); + position.set(Position.KEY_CHARGE, parser.next().equals("1")); + position.set(Position.PREFIX_IN + 2, parser.next()); + + position.set(Position.KEY_IGNITION, parser.nextInt(0) == 1); + + int course = (parser.nextInt(0) << 3) + (parser.nextInt(0) << 2) + + (parser.nextInt(0) << 1) + parser.nextInt(0); + if (course > 0 && course <= 8) { + position.setCourse((course - 1) * 45); + } + + } else if (parser.hasNext(7)) { + + position.setCourse(parser.nextInt(0)); + + position.set(Position.KEY_CHARGE, parser.next().equals("1")); + position.set(Position.KEY_IGNITION, parser.nextInt(0) == 1); + position.set(Position.KEY_POWER, parser.nextInt(0)); + position.set(Position.KEY_BATTERY, parser.nextInt(0)); + + String obd = parser.next(); + position.set("obd", obd.substring(1, obd.length() - 1)); + + String dtcs = parser.next(); + position.set(Position.KEY_DTCS, dtcs.substring(1, dtcs.length() - 1).replace('|', ' ')); + + } else if (parser.hasNext(10)) { + + position.setCourse(parser.nextInt(0)); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + position.set(Position.KEY_HDOP, parser.nextDouble(0)); + position.set(Position.PREFIX_ADC + 1, parser.nextInt(0)); + position.set(Position.PREFIX_IN + 1, parser.nextInt(0)); + position.set(Position.KEY_CHARGE, parser.next().equals("1")); + position.set(Position.PREFIX_IN + 2, parser.nextInt(0)); + position.set(Position.KEY_IGNITION, parser.nextInt(0) == 1); + position.set(Position.KEY_POWER, parser.nextInt(0)); + position.set(Position.KEY_BATTERY, parser.nextInt(0)); + + } else if (parser.hasNext(2)) { + + position.set("sensorId", parser.nextInt()); + position.set("sensorData", parser.next()); + + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/Ardi01Protocol.java b/src/main/java/org/traccar/protocol/Ardi01Protocol.java new file mode 100644 index 000000000..f7826430f --- /dev/null +++ b/src/main/java/org/traccar/protocol/Ardi01Protocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Ardi01Protocol extends BaseProtocol { + + public Ardi01Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Ardi01ProtocolDecoder(Ardi01Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Ardi01ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Ardi01ProtocolDecoder.java new file mode 100644 index 000000000..85e9ecfde --- /dev/null +++ b/src/main/java/org/traccar/protocol/Ardi01ProtocolDecoder.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class Ardi01ProtocolDecoder extends BaseProtocolDecoder { + + public Ardi01ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("(d+),") // imei + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(-?d+.d+),") // longitude + .number("(-?d+.d+),") // latitude + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .number("(-?d+.?d*),") // altitude + .number("(d+),") // satellites + .number("(d+),") // event + .number("(d+),") // battery + .number("(-?d+)") // temperature + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime()); + + position.setLongitude(parser.nextDouble(0)); + position.setLatitude(parser.nextDouble(0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + int satellites = parser.nextInt(0); + position.setValid(satellites >= 3); + position.set(Position.KEY_SATELLITES, satellites); + + position.set(Position.KEY_EVENT, parser.next()); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt(0)); + position.set(Position.PREFIX_TEMP + 1, parser.next()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/ArknavProtocol.java b/src/main/java/org/traccar/protocol/ArknavProtocol.java new file mode 100644 index 000000000..3b485e4a5 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArknavProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class ArknavProtocol extends BaseProtocol { + + public ArknavProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '\r')); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new ArknavProtocolDecoder(ArknavProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ArknavProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArknavProtocolDecoder.java new file mode 100644 index 000000000..4982e02fc --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArknavProtocolDecoder.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class ArknavProtocolDecoder extends BaseProtocolDecoder { + + public ArknavProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("(d+),") // imei + .expression(".{6},") // id code + .number("ddd,") // status + .number("Lddd,") // version + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .number("(d+.?d*),") // hdop + .number("(dd):(dd):(dd) ") // time (hh:mm:ss) + .number("(dd)-(dd)-(dd),") // date (dd-mm-yy) + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_HDOP, parser.nextDouble(0)); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY)); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/ArknavX8Protocol.java b/src/main/java/org/traccar/protocol/ArknavX8Protocol.java new file mode 100644 index 000000000..a29bc1ad3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArknavX8Protocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class ArknavX8Protocol extends BaseProtocol { + + public ArknavX8Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ';')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new ArknavX8ProtocolDecoder(ArknavX8Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ArknavX8ProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArknavX8ProtocolDecoder.java new file mode 100644 index 000000000..b570f5423 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArknavX8ProtocolDecoder.java @@ -0,0 +1,139 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class ArknavX8ProtocolDecoder extends BaseProtocolDecoder { + + public ArknavX8ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_1G = new PatternBuilder() + .expression("(..),") // type + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([AV]),") // validity + .number("(d+)(dd.d+)([NS]),") // latitude + .number("(d+)(dd.d+)([EW]),") // longitude + .number("(d+.d+),") // speed + .number("(d+),") // course + .number("(d+.d+),") // hdop + .number("(d+)") // status + .compile(); + + private static final Pattern PATTERN_2G = new PatternBuilder() + .expression("..,") // type + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d+),") // satellites + .number("(d+.d+),") // altitude + .number("(d+.d+),") // power + .number("(d+.d+),") // battery + .number("(d+.d+)") // odometer + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.charAt(2) != ',') { + getDeviceSession(channel, remoteAddress, sentence.substring(0, 15)); + return null; + } + + switch (sentence.substring(0, 2)) { + case "1G": + case "1R": + case "1M": + return decode1G(channel, remoteAddress, sentence); + case "2G": + return decode2G(channel, remoteAddress, sentence); + default: + return null; + } + } + + private Position decode1G(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_1G, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_TYPE, parser.next()); + + position.setTime(parser.nextDateTime()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_HDOP, parser.nextDouble(0)); + position.set(Position.KEY_STATUS, parser.next()); + + return position; + } + + private Position decode2G(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_2G, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, parser.nextDateTime()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.setAltitude(parser.nextDouble()); + position.set(Position.KEY_POWER, parser.nextDouble()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1852 / 3600); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/ArnaviProtocol.java b/src/main/java/org/traccar/protocol/ArnaviProtocol.java new file mode 100644 index 000000000..afe491865 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArnaviProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class ArnaviProtocol extends BaseProtocol { + + public ArnaviProtocol() { + 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 ArnaviProtocolDecoder(ArnaviProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java b/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java new file mode 100644 index 000000000..7996cf429 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ArnaviProtocolDecoder.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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 { + + public ArnaviProtocolDecoder(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 { + + Parser parser = new Parser(PATTERN, (String) msg); + 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/AstraProtocol.java b/src/main/java/org/traccar/protocol/AstraProtocol.java new file mode 100644 index 000000000..12b0dfb68 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AstraProtocol.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AstraProtocol extends BaseProtocol { + + public AstraProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 1, 2, -3, 0)); + pipeline.addLast(new AstraProtocolDecoder(AstraProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new AstraProtocolDecoder(AstraProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AstraProtocolDecoder.java b/src/main/java/org/traccar/protocol/AstraProtocolDecoder.java new file mode 100644 index 000000000..e6f546b9f --- /dev/null +++ b/src/main/java/org/traccar/protocol/AstraProtocolDecoder.java @@ -0,0 +1,129 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; + +public class AstraProtocolDecoder extends BaseProtocolDecoder { + + private static final Logger LOGGER = LoggerFactory.getLogger(AstraProtocolDecoder.class); + + public AstraProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_HEARTBEAT = 0x1A; + public static final int MSG_DATA = 0x10; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(Unpooled.wrappedBuffer(new byte[] {0x06}), remoteAddress)); + } + + buf.readUnsignedByte(); // protocol + buf.readUnsignedShort(); // length + + String imei = String.format("%08d", buf.readUnsignedInt()) + String.format("%07d", buf.readUnsignedMedium()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + while (buf.readableBytes() > 2) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedByte(); // index + + position.setValid(true); + position.setLatitude(buf.readInt() * 0.000001); + position.setLongitude(buf.readInt() * 0.000001); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(1980, 1, 6).addMillis(buf.readUnsignedInt() * 1000L); + position.setTime(dateBuilder.getDate()); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte() * 2)); + position.setCourse(buf.readUnsignedByte() * 2); + + int reason = buf.readUnsignedMedium(); + position.set(Position.KEY_EVENT, reason); + + int status = buf.readUnsignedShort(); + position.set(Position.KEY_STATUS, status); + + position.set(Position.PREFIX_IO + 1, buf.readUnsignedByte()); + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedByte()); + position.set(Position.KEY_BATTERY, buf.readUnsignedByte()); + position.set(Position.KEY_POWER, buf.readUnsignedByte()); + + buf.readUnsignedByte(); // max journey speed + buf.skipBytes(6); // accelerometer + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedShort()); + buf.readUnsignedShort(); // journey idle time + + position.setAltitude(buf.readUnsignedByte() * 20); + + int quality = buf.readUnsignedByte(); + position.set(Position.KEY_SATELLITES, quality & 0xf); + position.set(Position.KEY_RSSI, quality >> 4); + + buf.readUnsignedByte(); // geofence events + + if (BitUtil.check(status, 8)) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, buf.readSlice(7).toString(StandardCharsets.US_ASCII)); + position.set(Position.KEY_ODOMETER, buf.readUnsignedMedium() * 1000); + position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(buf.readUnsignedShort())); + } + + if (BitUtil.check(status, 6)) { + LOGGER.warn("Extension data is not supported"); + return position; + } + + positions.add(position); + + } + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/At2000FrameDecoder.java b/src/main/java/org/traccar/protocol/At2000FrameDecoder.java new file mode 100644 index 000000000..5fa82a5f7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/At2000FrameDecoder.java @@ -0,0 +1,81 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; +import org.traccar.NetworkMessage; + +public class At2000FrameDecoder extends BaseFrameDecoder { + + private static final int BLOCK_LENGTH = 16; + private static final int ACK_LENGTH = 496; + + private boolean firstPacket = true; + + private ByteBuf currentBuffer; + private int acknowledgedBytes; + + private void sendResponse(Channel channel) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(2 * BLOCK_LENGTH); + response.writeByte(At2000ProtocolDecoder.MSG_ACKNOWLEDGEMENT); + response.writeMedium(1); + response.writeByte(0x00); // success + response.writerIndex(2 * BLOCK_LENGTH); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 5) { + return null; + } + + int length; + if (firstPacket) { + firstPacket = false; + length = buf.getUnsignedMediumLE(buf.readerIndex() + 2); + } else { + length = buf.getUnsignedMediumLE(buf.readerIndex() + 1); + } + + length += BLOCK_LENGTH; + if (length % BLOCK_LENGTH != 0) { + length = (length / BLOCK_LENGTH + 1) * BLOCK_LENGTH; + } + + if ((buf.readableBytes() >= length || buf.readableBytes() % ACK_LENGTH == 0) + && (buf != currentBuffer || buf.readableBytes() > acknowledgedBytes)) { + sendResponse(channel); + currentBuffer = buf; + acknowledgedBytes = buf.readableBytes(); + } + + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/At2000Protocol.java b/src/main/java/org/traccar/protocol/At2000Protocol.java new file mode 100644 index 000000000..5894f3eab --- /dev/null +++ b/src/main/java/org/traccar/protocol/At2000Protocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class At2000Protocol extends BaseProtocol { + + public At2000Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new At2000FrameDecoder()); + pipeline.addLast(new At2000ProtocolDecoder(At2000Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/At2000ProtocolDecoder.java b/src/main/java/org/traccar/protocol/At2000ProtocolDecoder.java new file mode 100644 index 000000000..43798eb67 --- /dev/null +++ b/src/main/java/org/traccar/protocol/At2000ProtocolDecoder.java @@ -0,0 +1,171 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.DataConverter; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class At2000ProtocolDecoder extends BaseProtocolDecoder { + + private static final int BLOCK_LENGTH = 16; + + public At2000ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_ACKNOWLEDGEMENT = 0x00; + public static final int MSG_DEVICE_ID = 0x01; + public static final int MSG_TRACK_REQUEST = 0x88; + public static final int MSG_TRACK_RESPONSE = 0x89; + public static final int MSG_SESSION_END = 0x0c; + + private Cipher cipher; + + private static void sendRequest(Channel channel) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(BLOCK_LENGTH); + response.writeByte(MSG_TRACK_REQUEST); + response.writeMedium(0); + response.writerIndex(BLOCK_LENGTH); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (buf.getUnsignedByte(buf.readerIndex()) == 0x01) { + buf.readUnsignedByte(); // codec id + } + + int type = buf.readUnsignedByte(); + buf.readUnsignedMediumLE(); // length + buf.skipBytes(BLOCK_LENGTH - 1 - 3); + + if (type == MSG_DEVICE_ID) { + + String imei = buf.readSlice(15).toString(StandardCharsets.US_ASCII); + if (getDeviceSession(channel, remoteAddress, imei) != null) { + + byte[] iv = new byte[BLOCK_LENGTH]; + buf.readBytes(iv); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + + SecretKeySpec keySpec = new SecretKeySpec( + DataConverter.parseHex("000102030405060708090a0b0c0d0e0f"), "AES"); + + cipher = Cipher.getInstance("AES/CBC/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + + byte[] data = new byte[BLOCK_LENGTH]; + buf.readBytes(data); + cipher.update(data); + + } + + } else if (type == MSG_TRACK_RESPONSE) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + if (buf.capacity() <= BLOCK_LENGTH) { + return null; // empty message + } + + List<Position> positions = new LinkedList<>(); + + byte[] data = new byte[buf.capacity() - BLOCK_LENGTH]; + buf.readBytes(data); + buf = Unpooled.wrappedBuffer(cipher.update(data)); + try { + while (buf.readableBytes() >= 63) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedShortLE(); // index + buf.readUnsignedShortLE(); // reserved + + position.setValid(true); + + position.setTime(new Date(buf.readLongLE() * 1000)); + + position.setLatitude(buf.readFloatLE()); + position.setLongitude(buf.readFloatLE()); + position.setAltitude(buf.readFloatLE()); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readFloatLE())); + position.setCourse(buf.readFloatLE()); + + buf.readUnsignedIntLE(); // geozone event + buf.readUnsignedIntLE(); // io events + buf.readUnsignedIntLE(); // geozone value + buf.readUnsignedIntLE(); // io values + buf.readUnsignedShortLE(); // operator + + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShortLE()); + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShortLE()); + + position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.001); + + buf.readUnsignedShortLE(); // cid + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + buf.readUnsignedByte(); // current profile + + position.set(Position.KEY_BATTERY, buf.readUnsignedByte()); + position.set(Position.PREFIX_TEMP + 1, buf.readUnsignedByte()); + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + + positions.add(position); + + } + } finally { + buf.release(); + } + + return positions; + + } + + if (type == MSG_DEVICE_ID) { + sendRequest(channel); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/AtrackFrameDecoder.java b/src/main/java/org/traccar/protocol/AtrackFrameDecoder.java new file mode 100644 index 000000000..f071e2d97 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AtrackFrameDecoder.java @@ -0,0 +1,80 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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; + +import java.nio.charset.StandardCharsets; + +public class AtrackFrameDecoder extends BaseFrameDecoder { + + private static final int KEEPALIVE_LENGTH = 12; + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() >= 2) { + + if (buf.getUnsignedShort(buf.readerIndex()) == 0xfe02) { + + if (buf.readableBytes() >= KEEPALIVE_LENGTH) { + return buf.readRetainedSlice(KEEPALIVE_LENGTH); + } + + } else if (buf.getUnsignedShort(buf.readerIndex()) == 0x4050 && buf.getByte(buf.readerIndex() + 2) != ',') { + + if (buf.readableBytes() > 6) { + int length = buf.getUnsignedShort(buf.readerIndex() + 4) + 4 + 2; + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + } + + } else { + + int lengthStart = buf.indexOf(buf.readerIndex() + 3, buf.writerIndex(), (byte) ',') + 1; + if (lengthStart > 0) { + int lengthEnd = buf.indexOf(lengthStart, buf.writerIndex(), (byte) ','); + if (lengthEnd > 0) { + int length = lengthEnd + Integer.parseInt(buf.toString( + lengthStart, lengthEnd - lengthStart, StandardCharsets.US_ASCII)); + if (buf.readableBytes() > length && buf.getByte(buf.readerIndex() + length) == '\n') { + length += 1; + } + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + } + } else { + int endIndex = BufferUtil.indexOf("\r\n", buf); + if (endIndex > 0) { + return buf.readRetainedSlice(endIndex - buf.readerIndex() + 2); + } + } + + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/AtrackProtocol.java b/src/main/java/org/traccar/protocol/AtrackProtocol.java new file mode 100644 index 000000000..429708b26 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AtrackProtocol.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class AtrackProtocol extends BaseProtocol { + + public AtrackProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new AtrackFrameDecoder()); + pipeline.addLast(new AtrackProtocolEncoder(AtrackProtocol.this)); + pipeline.addLast(new AtrackProtocolDecoder(AtrackProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new AtrackProtocolEncoder(AtrackProtocol.this)); + pipeline.addLast(new AtrackProtocolDecoder(AtrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java new file mode 100644 index 000000000..53f04234d --- /dev/null +++ b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java @@ -0,0 +1,620 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class AtrackProtocolDecoder extends BaseProtocolDecoder { + + private static final int MIN_DATA_LENGTH = 40; + + private boolean longDate; + private boolean decimalFuel; + private boolean custom; + private String form; + + private final Map<Integer, String> alarmMap = new HashMap<>(); + + public AtrackProtocolDecoder(Protocol protocol) { + super(protocol); + + longDate = Context.getConfig().getBoolean(getProtocolName() + ".longDate"); + decimalFuel = Context.getConfig().getBoolean(getProtocolName() + ".decimalFuel"); + + custom = Context.getConfig().getBoolean(getProtocolName() + ".custom"); + form = Context.getConfig().getString(getProtocolName() + ".form"); + if (form != null) { + custom = true; + } + + for (String pair : Context.getConfig().getString(getProtocolName() + ".alarmMap", "").split(",")) { + if (!pair.isEmpty()) { + alarmMap.put( + Integer.parseInt(pair.substring(0, pair.indexOf('='))), pair.substring(pair.indexOf('=') + 1)); + } + } + } + + public void setLongDate(boolean longDate) { + this.longDate = longDate; + } + + public void setCustom(boolean custom) { + this.custom = custom; + } + + public void setForm(String form) { + this.form = form; + } + + private static void sendResponse(Channel channel, SocketAddress remoteAddress, long rawId, int index) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(12); + response.writeShort(0xfe02); + response.writeLong(rawId); + response.writeShort(index); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + private static String readString(ByteBuf buf) { + String result = null; + int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0); + if (index > buf.readerIndex()) { + result = buf.readSlice(index - buf.readerIndex()).toString(StandardCharsets.US_ASCII); + } + buf.readByte(); + return result; + } + + private void readTextCustomData(Position position, String data, String form) { + CellTower cellTower = new CellTower(); + String[] keys = form.substring(1).split("%"); + String[] values = data.split(",|\r\n"); + for (int i = 0; i < Math.min(keys.length, values.length); i++) { + switch (keys[i]) { + case "SA": + position.set(Position.KEY_SATELLITES, Integer.parseInt(values[i])); + break; + case "MV": + position.set(Position.KEY_POWER, Integer.parseInt(values[i]) * 0.1); + break; + case "BV": + position.set(Position.KEY_BATTERY, Integer.parseInt(values[i]) * 0.1); + break; + case "GQ": + cellTower.setSignalStrength(Integer.parseInt(values[i])); + break; + case "CE": + cellTower.setCellId(Long.parseLong(values[i])); + break; + case "LC": + cellTower.setLocationAreaCode(Integer.parseInt(values[i])); + break; + case "CN": + if (values[i].length() > 3) { + cellTower.setMobileCountryCode(Integer.parseInt(values[i].substring(0, 3))); + cellTower.setMobileNetworkCode(Integer.parseInt(values[i].substring(3))); + } + break; + case "PC": + position.set(Position.PREFIX_COUNT + 1, Integer.parseInt(values[i])); + break; + case "AT": + position.setAltitude(Integer.parseInt(values[i])); + break; + case "RP": + position.set(Position.KEY_RPM, Integer.parseInt(values[i])); + break; + case "GS": + position.set(Position.KEY_RSSI, Integer.parseInt(values[i])); + break; + case "DT": + position.set(Position.KEY_ARCHIVE, Integer.parseInt(values[i]) == 1); + break; + case "VN": + position.set(Position.KEY_VIN, values[i]); + break; + case "TR": + position.set(Position.KEY_THROTTLE, Integer.parseInt(values[i])); + break; + case "ET": + position.set(Position.KEY_COOLANT_TEMP, Integer.parseInt(values[i])); + break; + case "FL": + position.set(Position.KEY_FUEL_LEVEL, Integer.parseInt(values[i])); + break; + case "FC": + position.set(Position.KEY_FUEL_CONSUMPTION, Integer.parseInt(values[i])); + break; + case "AV1": + position.set(Position.PREFIX_ADC + 1, Integer.parseInt(values[i])); + break; + case "CD": + position.set(Position.KEY_ICCID, values[i]); + break; + case "EH": + position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(Integer.parseInt(values[i]) * 0.1)); + break; + case "IA": + position.set("intakeTemp", Integer.parseInt(values[i])); + break; + case "EL": + position.set(Position.KEY_ENGINE_LOAD, Integer.parseInt(values[i])); + break; + case "HA": + if (Integer.parseInt(values[i]) > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + } + break; + case "HB": + if (Integer.parseInt(values[i]) > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + } + break; + case "HC": + if (Integer.parseInt(values[i]) > 0) { + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + } + break; + case "MT": + position.set(Position.KEY_MOTION, Integer.parseInt(values[i]) > 0); + break; + default: + break; + } + } + + if (cellTower.getMobileCountryCode() != null + && cellTower.getMobileNetworkCode() != null + && cellTower.getCellId() != null + && cellTower.getLocationAreaCode() != null) { + position.setNetwork(new Network(cellTower)); + } else if (cellTower.getSignalStrength() != null) { + position.set(Position.KEY_RSSI, cellTower.getSignalStrength()); + } + } + + private void readBinaryCustomData(Position position, ByteBuf buf, String form) { + CellTower cellTower = new CellTower(); + String[] keys = form.substring(1).split("%"); + for (String key : keys) { + switch (key) { + case "SA": + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + break; + case "MV": + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1); + break; + case "BV": + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.1); + break; + case "GQ": + cellTower.setSignalStrength((int) buf.readUnsignedByte()); + break; + case "CE": + cellTower.setCellId(buf.readUnsignedInt()); + break; + case "LC": + cellTower.setLocationAreaCode(buf.readUnsignedShort()); + break; + case "CN": + int combinedMobileCodes = (int) (buf.readUnsignedInt() % 100000); // cccnn + cellTower.setMobileCountryCode(combinedMobileCodes / 100); + cellTower.setMobileNetworkCode(combinedMobileCodes % 100); + break; + case "RL": + buf.readUnsignedByte(); // rxlev + break; + case "PC": + position.set(Position.PREFIX_COUNT + 1, buf.readUnsignedInt()); + break; + case "AT": + position.setAltitude(buf.readUnsignedInt()); + break; + case "RP": + position.set(Position.KEY_RPM, buf.readUnsignedShort()); + break; + case "GS": + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + break; + case "DT": + position.set(Position.KEY_ARCHIVE, buf.readUnsignedByte() == 1); + break; + case "VN": + position.set(Position.KEY_VIN, readString(buf)); + break; + case "MF": + buf.readUnsignedShort(); // mass air flow rate + break; + case "EL": + buf.readUnsignedByte(); // engine load + break; + case "TR": + position.set(Position.KEY_THROTTLE, buf.readUnsignedByte()); + break; + case "ET": + position.set(Position.PREFIX_TEMP + 1, buf.readUnsignedShort()); + break; + case "FL": + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte()); + break; + case "ML": + buf.readUnsignedByte(); // mil status + break; + case "FC": + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedInt()); + break; + case "CI": + readString(buf); // format string + break; + case "AV1": + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort()); + break; + case "NC": + readString(buf); // gsm neighbor cell info + break; + case "SM": + buf.readUnsignedShort(); // max speed between reports + break; + case "GL": + readString(buf); // google link + break; + case "MA": + readString(buf); // mac address + break; + case "PD": + buf.readUnsignedByte(); // pending code status + break; + case "CD": + position.set(Position.KEY_ICCID, readString(buf)); + break; + case "CM": + buf.readLong(); // imsi + break; + case "GN": + buf.skipBytes(60); // g sensor data + break; + case "GV": + buf.skipBytes(6); // maximum g force + break; + case "ME": + buf.readLong(); // imei + break; + case "IA": + buf.readUnsignedByte(); // intake air temperature + break; + case "MP": + buf.readUnsignedByte(); // manifold absolute pressure + break; + default: + break; + } + } + + if (cellTower.getMobileCountryCode() != null + && cellTower.getMobileNetworkCode() != null + && cellTower.getCellId() != null && cellTower.getCellId() != 0 + && cellTower.getLocationAreaCode() != null) { + position.setNetwork(new Network(cellTower)); + } else if (cellTower.getSignalStrength() != null) { + position.set(Position.KEY_RSSI, cellTower.getSignalStrength()); + } + } + + private static final Pattern PATTERN_INFO = new PatternBuilder() + .text("$INFO=") + .number("(d+),") // unit id + .expression("([^,]+),") // model + .expression("([^,]+),") // firmware version + .number("d+,") // imei + .number("d+,") // imsi + .number("d+,") // sim card id + .number("(d+),") // power + .number("(d+),") // battery + .number("(d+),") // satellites + .number("d+,") // gsm status + .number("(d+),") // rssi + .number("d+,") // connection status + .number("d+") // antenna status + .any() + .compile(); + + private Position decodeInfo(Channel channel, SocketAddress remoteAddress, String sentence) { + + Position position = new Position(getProtocolName()); + + getLastLocation(position, null); + + DeviceSession deviceSession; + + if (sentence.startsWith("$INFO")) { + + Parser parser = new Parser(PATTERN_INFO, sentence); + if (!parser.matches()) { + return null; + } + + deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + + position.set("model", parser.next()); + position.set(Position.KEY_VERSION_FW, parser.next()); + position.set(Position.KEY_POWER, parser.nextInt() * 0.1); + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.1); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_RSSI, parser.nextInt()); + + } else { + + deviceSession = getDeviceSession(channel, remoteAddress); + + position.set(Position.KEY_RESULT, sentence); + + } + + if (deviceSession == null) { + return null; + } else { + position.setDeviceId(deviceSession.getDeviceId()); + return position; + } + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("(d+),") // date and time + .number("d+,") // rtc date and time + .number("d+,") // device date and time + .number("(-?d+),") // longitude + .number("(-?d+),") // latitude + .number("(d+),") // course + .number("(d+),") // report id + .number("(d+.?d*),") // odometer + .number("(d+),") // hdop + .number("(d+),") // inputs + .number("(d+),") // speed + .number("(d+),") // outputs + .number("(d+),") // adc + .number("([^,]+)?,") // driver + .number("(d+),") // temp1 + .number("(d+),") // temp2 + .expression("[^,]*,") // text message + .expression("(.*)") // custom data + .optional(2) + .compile(); + + private List<Position> decodeText(Channel channel, SocketAddress remoteAddress, String sentence) { + + int startIndex = 0; + for (int i = 0; i < 4; i++) { + startIndex = sentence.indexOf(',', startIndex + 1); + } + int endIndex = sentence.indexOf(',', startIndex + 1); + + String imei = sentence.substring(startIndex + 1, endIndex); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + String[] lines = sentence.substring(endIndex + 1).split("\r\n"); + + for (String line : lines) { + Position position = decodeTextLine(deviceSession, line); + if (position != null) { + positions.add(position); + } + } + + return positions; + } + + + private Position decodeTextLine(DeviceSession deviceSession, String sentence) { + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + + String time = parser.next(); + if (time.length() >= 14) { + try { + DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + position.setTime(dateFormat.parse(time)); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } else { + position.setTime(new Date(Long.parseLong(time) * 1000)); + } + + position.setLongitude(parser.nextInt() * 0.000001); + position.setLatitude(parser.nextInt() * 0.000001); + position.setCourse(parser.nextInt()); + + position.set(Position.KEY_EVENT, parser.nextInt()); + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 100); + position.set(Position.KEY_HDOP, parser.nextInt() * 0.1); + position.set(Position.KEY_INPUT, parser.nextInt()); + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + + position.set(Position.KEY_OUTPUT, parser.nextInt()); + position.set(Position.PREFIX_ADC + 1, parser.nextInt()); + + if (parser.hasNext()) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + } + + position.set(Position.PREFIX_TEMP + 1, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 2, parser.nextInt()); + + if (custom) { + String data = parser.next(); + String form = this.form; + if (form == null) { + form = data.substring(0, data.indexOf(',')).substring("%CI".length()); + data = data.substring(data.indexOf(',') + 1); + } + readTextCustomData(position, data, form); + } + + return position; + } + + private List<Position> decodeBinary(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + buf.skipBytes(2); // prefix + buf.readUnsignedShort(); // checksum + buf.readUnsignedShort(); // length + int index = buf.readUnsignedShort(); + + long id = buf.readLong(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(id)); + if (deviceSession == null) { + return null; + } + + sendResponse(channel, remoteAddress, id, index); + + List<Position> positions = new LinkedList<>(); + + while (buf.readableBytes() >= MIN_DATA_LENGTH) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (longDate) { + + DateBuilder dateBuilder = new DateBuilder() + .setDate(buf.readUnsignedShort(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + buf.skipBytes(7 + 7); + + } else { + position.setFixTime(new Date(buf.readUnsignedInt() * 1000)); + position.setDeviceTime(new Date(buf.readUnsignedInt() * 1000)); + buf.readUnsignedInt(); // send time + } + + position.setValid(true); + position.setLongitude(buf.readInt() * 0.000001); + position.setLatitude(buf.readInt() * 0.000001); + position.setCourse(buf.readUnsignedShort()); + + int type = buf.readUnsignedByte(); + position.set(Position.KEY_TYPE, type); + position.set(Position.KEY_ALARM, alarmMap.get(type)); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 100); + position.set(Position.KEY_HDOP, buf.readUnsignedShort() * 0.1); + position.set(Position.KEY_INPUT, buf.readUnsignedByte()); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort())); + + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort() * 0.001); + + position.set(Position.KEY_DRIVER_UNIQUE_ID, readString(buf)); + + position.set(Position.PREFIX_TEMP + 1, buf.readShort() * 0.1); + position.set(Position.PREFIX_TEMP + 2, buf.readShort() * 0.1); + + String message = readString(buf); + if (message != null && !message.isEmpty()) { + Pattern pattern = Pattern.compile("FULS:F=(\\p{XDigit}+) t=(\\p{XDigit}+) N=(\\p{XDigit}+)"); + Matcher matcher = pattern.matcher(message); + if (matcher.find()) { + int value = Integer.parseInt(matcher.group(3), decimalFuel ? 10 : 16); + position.set(Position.KEY_FUEL_LEVEL, value * 0.1); + } else { + position.set("message", message); + } + } + + if (custom) { + String form = this.form; + if (form == null) { + form = readString(buf).trim().substring("%CI".length()); + } + readBinaryCustomData(position, buf, form); + } + + positions.add(position); + + } + + return positions; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (buf.getUnsignedShort(buf.readerIndex()) == 0xfe02) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(buf.retain(), remoteAddress)); // keep-alive message + } + return null; + } else if (buf.getByte(buf.readerIndex()) == '$') { + return decodeInfo(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII).trim()); + } else if (buf.getByte(buf.readerIndex() + 2) == ',') { + return decodeText(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII).trim()); + } else { + return decodeBinary(channel, remoteAddress, buf); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/AtrackProtocolEncoder.java b/src/main/java/org/traccar/protocol/AtrackProtocolEncoder.java new file mode 100644 index 000000000..d803ae391 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AtrackProtocolEncoder.java @@ -0,0 +1,43 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.nio.charset.StandardCharsets; + +public class AtrackProtocolEncoder extends BaseProtocolEncoder { + + public AtrackProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return Unpooled.copiedBuffer( + command.getString(Command.KEY_DATA) + "\r\n", StandardCharsets.US_ASCII); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/AuroProtocol.java b/src/main/java/org/traccar/protocol/AuroProtocol.java new file mode 100644 index 000000000..b8ebdaa75 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AuroProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AuroProtocol extends BaseProtocol { + + public AuroProtocol() { + 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 AuroProtocolDecoder(AuroProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AuroProtocolDecoder.java b/src/main/java/org/traccar/protocol/AuroProtocolDecoder.java new file mode 100644 index 000000000..d7916147b --- /dev/null +++ b/src/main/java/org/traccar/protocol/AuroProtocolDecoder.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class AuroProtocolDecoder extends BaseProtocolDecoder { + + public AuroProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("M(dddd)") // index + .number("Td+") // phone + .number("I(d+)") // imei + .number("Ed+W") + .text("*****") + .number("d{8}d{4}") // local time + .expression(".{8}#.{8}") + .number("d{10}") // status + .number("([-+])(ddd)(dd)(dddd)") // longitude + .number("([-+])(ddd)(dd)(dddd)") // latitude + .number("(dd)(dd)(dddd)") // date (ddmmyyyy) + .number("(dd)(dd)(dd)") // time (hhmmss) + .number("(ddd)") // course + .number("d{6}") + .number("(ddd)") // speed + .number("d") + .number("(dd)") // battery + .expression("([01])") // charging + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + position.set(Position.KEY_INDEX, parser.nextInt(0)); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN_MIN)); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN_MIN)); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setCourse(parser.nextDouble(0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + + position.set(Position.KEY_BATTERY, parser.nextInt(0)); + position.set(Position.KEY_CHARGE, parser.nextInt(0) == 1); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/AustinNbProtocol.java b/src/main/java/org/traccar/protocol/AustinNbProtocol.java new file mode 100644 index 000000000..32bfc0aae --- /dev/null +++ b/src/main/java/org/traccar/protocol/AustinNbProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AustinNbProtocol extends BaseProtocol { + + public AustinNbProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new AustinNbProtocolDecoder(AustinNbProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AustinNbProtocolDecoder.java b/src/main/java/org/traccar/protocol/AustinNbProtocolDecoder.java new file mode 100644 index 000000000..dc6f3d280 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AustinNbProtocolDecoder.java @@ -0,0 +1,81 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.TimeZone; +import java.util.regex.Pattern; + +public class AustinNbProtocolDecoder extends BaseProtocolDecoder { + + public AustinNbProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("(d+);") // imei + .number("(dddd)-(dd)-(dd) ") // date + .number("(dd):(dd):(dd);") // time + .number("(-?d+,d+);") // latitude + .number("(-?d+,d+);") // longitude + .number("(d+);") // azimuth + .number("(d+);") // angle + .number("(d+);") // range + .number("(d+);") // out of range + .expression("(.*)") // operator + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.YMD_HMS, TimeZone.getDefault().getID())); + + position.setValid(true); + position.setLatitude(Double.parseDouble(parser.next().replace(',', '.'))); + position.setLongitude(Double.parseDouble(parser.next().replace(',', '.'))); + position.setCourse(parser.nextInt()); + position.set("angle", parser.nextInt()); + position.set("range", parser.nextInt()); + position.set("outOfRange", parser.nextInt()); + position.set("carrier", parser.next()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/AutoFonFrameDecoder.java b/src/main/java/org/traccar/protocol/AutoFonFrameDecoder.java new file mode 100644 index 000000000..69f28133f --- /dev/null +++ b/src/main/java/org/traccar/protocol/AutoFonFrameDecoder.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2015 Vitaly Litvak (vitavaque@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; + +public class AutoFonFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + // Check minimum length + if (buf.readableBytes() < 12) { + return null; + } + + int length; + switch (buf.getUnsignedByte(buf.readerIndex())) { + case AutoFonProtocolDecoder.MSG_LOGIN: + length = 12; + break; + case AutoFonProtocolDecoder.MSG_LOCATION: + length = 78; + break; + case AutoFonProtocolDecoder.MSG_HISTORY: + length = 257; + break; + case AutoFonProtocolDecoder.MSG_45_LOGIN: + length = 19; + break; + case AutoFonProtocolDecoder.MSG_45_LOCATION: + length = 34; + break; + default: + length = 0; + break; + } + + // Check length and return buffer + if (length != 0 && buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/AutoFonProtocol.java b/src/main/java/org/traccar/protocol/AutoFonProtocol.java new file mode 100644 index 000000000..08b5edc7d --- /dev/null +++ b/src/main/java/org/traccar/protocol/AutoFonProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AutoFonProtocol extends BaseProtocol { + + public AutoFonProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new AutoFonFrameDecoder()); + pipeline.addLast(new AutoFonProtocolDecoder(AutoFonProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AutoFonProtocolDecoder.java b/src/main/java/org/traccar/protocol/AutoFonProtocolDecoder.java new file mode 100644 index 000000000..aa05ca2d7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AutoFonProtocolDecoder.java @@ -0,0 +1,215 @@ +/* + * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2015 Vitaly Litvak (vitavaque@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.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; + +public class AutoFonProtocolDecoder extends BaseProtocolDecoder { + + public AutoFonProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN = 0x10; + public static final int MSG_LOCATION = 0x11; + public static final int MSG_HISTORY = 0x12; + + public static final int MSG_45_LOGIN = 0x41; + public static final int MSG_45_LOCATION = 0x02; + + private static double convertCoordinate(int raw) { + int degrees = raw / 1000000; + double minutes = (raw % 1000000) / 10000.0; + return degrees + minutes / 60; + } + + private static double convertCoordinate(short degrees, int minutes) { + double value = degrees + BitUtil.from(minutes, 4) / 600000.0; + if (BitUtil.check(minutes, 0)) { + return value; + } else { + return -value; + } + } + + private Position decodePosition(DeviceSession deviceSession, ByteBuf buf, boolean history) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (!history) { + buf.readUnsignedByte(); // interval + buf.skipBytes(8); // settings + } + position.set(Position.KEY_STATUS, buf.readUnsignedByte()); + if (!history) { + buf.readUnsignedShort(); + } + position.set(Position.KEY_BATTERY, buf.readUnsignedByte()); + buf.skipBytes(6); // time + + if (!history) { + for (int i = 0; i < 2; i++) { + buf.skipBytes(5); // time + buf.readUnsignedShort(); // interval + buf.skipBytes(5); // mode + } + } + + position.set(Position.PREFIX_TEMP + 1, buf.readByte()); + + int rssi = buf.readUnsignedByte(); + CellTower cellTower = CellTower.from( + buf.readUnsignedShort(), buf.readUnsignedShort(), + buf.readUnsignedShort(), buf.readUnsignedShort(), rssi); + position.setNetwork(new Network(cellTower)); + + int valid = buf.readUnsignedByte(); + position.setValid((valid & 0xc0) != 0); + position.set(Position.KEY_SATELLITES, valid & 0x3f); + + DateBuilder dateBuilder = new DateBuilder() + .setDateReverse(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + position.setLatitude(convertCoordinate(buf.readInt())); + position.setLongitude(convertCoordinate(buf.readInt())); + position.setAltitude(buf.readShort()); + position.setSpeed(buf.readUnsignedByte()); + position.setCourse(buf.readUnsignedByte() * 2.0); + + position.set(Position.KEY_HDOP, buf.readUnsignedShort()); + + buf.readUnsignedShort(); // reserved + buf.readUnsignedByte(); // checksum + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int type = buf.readUnsignedByte(); + + if (type == MSG_LOGIN || type == MSG_45_LOGIN) { + + if (type == MSG_LOGIN) { + buf.readUnsignedByte(); // hardware version + buf.readUnsignedByte(); // software version + } + + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(1); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + + if (deviceSession != null && channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeBytes("resp_crc=".getBytes(StandardCharsets.US_ASCII)); + response.writeByte(buf.getByte(buf.writerIndex() - 1)); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + return null; + + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + if (type == MSG_LOCATION) { + + return decodePosition(deviceSession, buf, false); + + } else if (type == MSG_HISTORY) { + + int count = buf.readUnsignedByte() & 0x0f; + buf.readUnsignedShort(); // total count + List<Position> positions = new LinkedList<>(); + + for (int i = 0; i < count; i++) { + positions.add(decodePosition(deviceSession, buf, true)); + } + + return positions; + + } else if (type == MSG_45_LOCATION) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + short status = buf.readUnsignedByte(); + if (BitUtil.check(status, 7)) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + position.set(Position.KEY_BATTERY, BitUtil.to(status, 7)); + + buf.skipBytes(2); // remaining time + + position.set(Position.PREFIX_TEMP + 1, buf.readByte()); + + buf.skipBytes(2); // timer (interval and units) + buf.readByte(); // mode + buf.readByte(); // gprs sending interval + + buf.skipBytes(6); // mcc, mnc, lac, cid + + int valid = buf.readUnsignedByte(); + position.setValid(BitUtil.from(valid, 6) != 0); + position.set(Position.KEY_SATELLITES, BitUtil.from(valid, 6)); + + int time = buf.readUnsignedMedium(); + int date = buf.readUnsignedMedium(); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(time / 10000, time / 100 % 100, time % 100) + .setDateReverse(date / 10000, date / 100 % 100, date % 100); + position.setTime(dateBuilder.getDate()); + + position.setLatitude(convertCoordinate(buf.readUnsignedByte(), buf.readUnsignedMedium())); + position.setLongitude(convertCoordinate(buf.readUnsignedByte(), buf.readUnsignedMedium())); + position.setSpeed(buf.readUnsignedByte()); + position.setCourse(buf.readUnsignedShort()); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/AutoGradeProtocol.java b/src/main/java/org/traccar/protocol/AutoGradeProtocol.java new file mode 100644 index 000000000..c6dbb681e --- /dev/null +++ b/src/main/java/org/traccar/protocol/AutoGradeProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AutoGradeProtocol extends BaseProtocol { + + public AutoGradeProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ')')); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new AutoGradeProtocolDecoder(AutoGradeProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AutoGradeProtocolDecoder.java b/src/main/java/org/traccar/protocol/AutoGradeProtocolDecoder.java new file mode 100644 index 000000000..5052450b5 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AutoGradeProtocolDecoder.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.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 AutoGradeProtocolDecoder extends BaseProtocolDecoder { + + public AutoGradeProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("(") + .number("d{12}") // index + .number("(d{15})") // imei + .number("(dd)(dd)(dd)") // date (ddmmyy) + .expression("([AV])") // validity + .number("(d+)(dd.d+)([NS])") // latitude + .number("(d+)(dd.d+)([EW])") // longitude + .number("([d.]{5})") // speed + .number("(dd)(dd)(dd)") // time (hhmmss) + .number("([d.]{6})") // course + .expression("(.)") // status + .number("A(xxxx)") + .number("B(xxxx)") + .number("C(xxxx)") + .number("D(xxxx)") + .number("E(xxxx)") + .number("K(xxxx)") + .number("L(xxxx)") + .number("M(xxxx)") + .number("N(xxxx)") + .number("O(xxxx)") + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + + dateBuilder.setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + position.setCourse(parser.nextDouble(0)); + + int status = parser.next().charAt(0); + position.set(Position.KEY_STATUS, status); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 0)); + + for (int i = 1; i <= 5; i++) { + position.set(Position.PREFIX_ADC + i, parser.next()); + } + + for (int i = 1; i <= 5; i++) { + position.set("can" + i, parser.next()); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/AutoTrackProtocol.java b/src/main/java/org/traccar/protocol/AutoTrackProtocol.java new file mode 100644 index 000000000..6aa7558bf --- /dev/null +++ b/src/main/java/org/traccar/protocol/AutoTrackProtocol.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; +public class AutoTrackProtocol extends BaseProtocol { + + public AutoTrackProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 5, 2, 2, 0, true)); + pipeline.addLast(new AutoTrackProtocolDecoder(AutoTrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AutoTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/AutoTrackProtocolDecoder.java new file mode 100644 index 000000000..da7f6b5a6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/AutoTrackProtocolDecoder.java @@ -0,0 +1,138 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; + +public class AutoTrackProtocolDecoder extends BaseProtocolDecoder { + + public AutoTrackProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN_REQUEST = 51; + public static final int MSG_LOGIN_CONFIRM = 101; + public static final int MSG_TELEMETRY_1 = 52; + public static final int MSG_TELEMETRY_2 = 66; + public static final int MSG_TELEMETRY_3 = 67; + public static final int MSG_KEEP_ALIVE = 114; + public static final int MSG_TELEMETRY_CONFIRM = 123; + + private Position decodeTelemetry( + Channel channel, SocketAddress remoteAddress, DeviceSession deviceSession, ByteBuf buf) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date(1009843200000L + buf.readUnsignedIntLE() * 1000)); // seconds since 2002 + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setLongitude(buf.readIntLE() * 0.0000001); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + position.set(Position.KEY_FUEL_USED, buf.readUnsignedIntLE()); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE())); + buf.readUnsignedShortLE(); // max speed + + position.set(Position.KEY_INPUT, buf.readUnsignedShortLE()); + buf.readUnsignedIntLE(); // di 3 count + buf.readUnsignedIntLE(); // di 4 count + + for (int i = 0; i < 5; i++) { + position.set(Position.PREFIX_ADC + (i + 1), buf.readUnsignedShortLE()); + } + + position.setCourse(buf.readUnsignedShortLE()); + + position.set(Position.KEY_STATUS, buf.readUnsignedShortLE()); + position.set(Position.KEY_EVENT, buf.readUnsignedShortLE()); + position.set(Position.KEY_DRIVER_UNIQUE_ID, buf.readLongLE()); + + int index = buf.readUnsignedShortLE(); + + buf.readUnsignedShortLE(); // checksum + + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeInt(0xF1F1F1F1); // sync + response.writeByte(MSG_TELEMETRY_CONFIRM); + response.writeShortLE(2); // length + response.writeShortLE(index); + response.writeShort(Checksum.crc16(Checksum.CRC16_XMODEM, response.nioBuffer())); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(4); // sync + int type = buf.readUnsignedByte(); + buf.readUnsignedShortLE(); // length + + switch (type) { + case MSG_LOGIN_REQUEST: + String imei = ByteBufUtil.hexDump(buf.readSlice(8)); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + int fuelConst = buf.readUnsignedShortLE(); + int tripConst = buf.readUnsignedShortLE(); + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeInt(0xF1F1F1F1); // sync + response.writeByte(MSG_LOGIN_CONFIRM); + response.writeShortLE(12); // length + response.writeBytes(ByteBufUtil.decodeHexDump(imei)); + response.writeShortLE(fuelConst); + response.writeShortLE(tripConst); + response.writeShort(Checksum.crc16(Checksum.CRC16_XMODEM, response.nioBuffer())); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + return null; + case MSG_TELEMETRY_1: + case MSG_TELEMETRY_2: + case MSG_TELEMETRY_3: + deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + return decodeTelemetry(channel, remoteAddress, deviceSession, buf); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/AvemaProtocol.java b/src/main/java/org/traccar/protocol/AvemaProtocol.java new file mode 100644 index 000000000..dbfab4dea --- /dev/null +++ b/src/main/java/org/traccar/protocol/AvemaProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class AvemaProtocol extends BaseProtocol { + + public AvemaProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new AvemaProtocolDecoder(AvemaProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/AvemaProtocolDecoder.java b/src/main/java/org/traccar/protocol/AvemaProtocolDecoder.java new file mode 100644 index 000000000..37836ad5f --- /dev/null +++ b/src/main/java/org/traccar/protocol/AvemaProtocolDecoder.java @@ -0,0 +1,110 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class AvemaProtocolDecoder extends BaseProtocolDecoder { + + public AvemaProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("(d+),") // device id + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(-?d+.d+),") // longitude + .number("(-?d+.d+),") // latitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(-?d+),") // altitude + .number("(d+),") // satellites + .number("(d+),") // event + .number("(d+.d+),") // odometer + .number("(d+),") // input + .number("(d+.d+)V?,") // adc 1 + .number("(d+.d+)V?,") // adc 2 + .number("(d+),") // output + .number("(d),") // roaming + .number("(d+),") // rssi + .number("d,") // communication system + .number("(ddd)-?") // mcc + .number("(d+),") // mnc + .number("(x+),") // lac + .number("(x+),") // cid + .number("(d+.d+),").optional() // battery + .number("([^,]+)?") // rfid + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + position.setTime(parser.nextDateTime()); + position.setLongitude(parser.nextDouble()); + position.setLatitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.setCourse(parser.nextInt()); + position.setAltitude(parser.nextInt()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_EVENT, parser.nextInt()); + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); + position.set(Position.KEY_INPUT, parser.nextInt()); + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.PREFIX_ADC + 2, parser.nextDouble()); + position.set(Position.KEY_OUTPUT, parser.nextInt()); + position.set(Position.KEY_ROAMING, parser.nextInt() == 1); + + int rssi = parser.nextInt(); + position.setNetwork(new Network(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt(), rssi))); + + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/Avl301Protocol.java b/src/main/java/org/traccar/protocol/Avl301Protocol.java new file mode 100644 index 000000000..71fc7cb26 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Avl301Protocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Avl301Protocol extends BaseProtocol { + + public Avl301Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(256, 2, 1, -3, 0)); + pipeline.addLast(new Avl301ProtocolDecoder(Avl301Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Avl301ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Avl301ProtocolDecoder.java new file mode 100644 index 000000000..f6b7db2d6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Avl301ProtocolDecoder.java @@ -0,0 +1,145 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.DateBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class Avl301ProtocolDecoder extends BaseProtocolDecoder { + + public Avl301ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private String readImei(ByteBuf buf) { + int b = buf.readUnsignedByte(); + StringBuilder imei = new StringBuilder(); + imei.append(b & 0x0F); + for (int i = 0; i < 7; i++) { + b = buf.readUnsignedByte(); + imei.append((b & 0xF0) >> 4); + imei.append(b & 0x0F); + } + return imei.toString(); + } + + public static final int MSG_LOGIN = 'L'; + public static final int MSG_STATUS = 'H'; + public static final int MSG_GPS_LBS_STATUS = '$'; + + private void sendResponse(Channel channel, int type) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(5); + response.writeByte('$'); + response.writeByte(type); + response.writeByte('#'); + response.writeByte('\r'); response.writeByte('\n'); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(1); // header + int type = buf.readUnsignedByte(); + buf.readUnsignedByte(); // length + + if (type == MSG_LOGIN) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, readImei(buf)); + if (deviceSession == null) { + sendResponse(channel, type); + } + + } else if (type == MSG_STATUS) { + + sendResponse(channel, type); + + } else if (type == MSG_GPS_LBS_STATUS) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + int gpsLength = buf.readUnsignedByte(); // gps len and sat + position.set(Position.KEY_SATELLITES, gpsLength & 0xf); + + position.set(Position.KEY_SATELLITES_VISIBLE, buf.readUnsignedByte()); // satellites + + double latitude = buf.readUnsignedInt() / 600000.0; + double longitude = buf.readUnsignedInt() / 600000.0; + position.setSpeed(buf.readUnsignedByte()); + + int union = buf.readUnsignedShort(); // course and flags + position.setCourse(union & 0x03FF); + position.setValid((union & 0x1000) != 0); + if ((union & 0x0400) != 0) { + latitude = -latitude; + } + if ((union & 0x0800) != 0) { + longitude = -longitude; + } + + position.setLatitude(latitude); + position.setLongitude(longitude); + + if ((union & 0x4000) != 0) { + position.set("acc", (union & 0x8000) != 0); + } + + position.setNetwork(new Network( + CellTower.fromLacCid(buf.readUnsignedShort(), buf.readUnsignedMedium()))); + + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + int flags = buf.readUnsignedByte(); + position.set("acc", (flags & 0x2) != 0); + + // parse other flags + + position.set(Position.KEY_POWER, buf.readUnsignedByte()); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + + return position; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/BceFrameDecoder.java b/src/main/java/org/traccar/protocol/BceFrameDecoder.java new file mode 100644 index 000000000..381a97696 --- /dev/null +++ b/src/main/java/org/traccar/protocol/BceFrameDecoder.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class BceFrameDecoder extends BaseFrameDecoder { + + private static final int HANDSHAKE_LENGTH = 7; // "#BCE#\r\n" + + private boolean header = true; + + private static byte checksum(ByteBuf buf, int end) { + byte result = 0; + for (int i = 0; i < end; i++) { + result += buf.getByte(buf.readerIndex() + i); + } + return result; + } + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (header && buf.readableBytes() >= HANDSHAKE_LENGTH) { + buf.skipBytes(HANDSHAKE_LENGTH); + header = false; + } + + int end = 8; // IMEI + + while (buf.readableBytes() >= end + 2 + 1 + 1 + 1) { + end += buf.getUnsignedShortLE(buf.readerIndex() + end) + 2; + + if (buf.readableBytes() > end && checksum(buf, end) == buf.getByte(buf.readerIndex() + end)) { + return buf.readRetainedSlice(end + 1); + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/BceProtocol.java b/src/main/java/org/traccar/protocol/BceProtocol.java new file mode 100644 index 000000000..c5e1dd04c --- /dev/null +++ b/src/main/java/org/traccar/protocol/BceProtocol.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class BceProtocol extends BaseProtocol { + + public BceProtocol() { + setSupportedDataCommands( + Command.TYPE_OUTPUT_CONTROL); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new BceFrameDecoder()); + pipeline.addLast(new BceProtocolEncoder(BceProtocol.this)); + pipeline.addLast(new BceProtocolDecoder(BceProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/BceProtocolDecoder.java b/src/main/java/org/traccar/protocol/BceProtocolDecoder.java new file mode 100644 index 000000000..c71cdffd9 --- /dev/null +++ b/src/main/java/org/traccar/protocol/BceProtocolDecoder.java @@ -0,0 +1,336 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class BceProtocolDecoder extends BaseProtocolDecoder { + + public BceProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final int DATA_TYPE = 7; + + public static final int MSG_ASYNC_STACK = 0xA5; + public static final int MSG_STACK_COFIRM = 0x19; + public static final int MSG_TIME_TRIGGERED = 0xA0; + public static final int MSG_OUTPUT_CONTROL = 0x41; + public static final int MSG_OUTPUT_CONTROL_ACK = 0xC1; + + private void decodeMask1(ByteBuf buf, int mask, Position position) { + + if (BitUtil.check(mask, 0)) { + position.setValid(true); + position.setLongitude(buf.readFloatLE()); + position.setLatitude(buf.readFloatLE()); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + + int status = buf.readUnsignedByte(); + position.set(Position.KEY_SATELLITES, BitUtil.to(status, 4)); + position.set(Position.KEY_HDOP, BitUtil.from(status, 4)); + + position.setCourse(buf.readUnsignedByte() * 2); + position.setAltitude(buf.readUnsignedShortLE()); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + } + + if (BitUtil.check(mask, 1)) { + position.set(Position.KEY_INPUT, buf.readUnsignedShortLE()); + } + + for (int i = 1; i <= 8; i++) { + if (BitUtil.check(mask, i + 1)) { + position.set(Position.PREFIX_ADC + i, buf.readUnsignedShortLE()); + } + } + + if (BitUtil.check(mask, 10)) { + buf.skipBytes(4); + } + if (BitUtil.check(mask, 11)) { + buf.skipBytes(4); + } + if (BitUtil.check(mask, 12)) { + position.set("fuel1", buf.readUnsignedShort()); + } + if (BitUtil.check(mask, 13)) { + position.set("fuel2", buf.readUnsignedShort()); + } + + if (BitUtil.check(mask, 14)) { + position.setNetwork(new Network(CellTower.from( + buf.readUnsignedShortLE(), buf.readUnsignedByte(), + buf.readUnsignedShortLE(), buf.readUnsignedShortLE(), + buf.readUnsignedByte()))); + buf.readUnsignedByte(); + } + } + + private void decodeMask2(ByteBuf buf, int mask, Position position) { + + if (BitUtil.check(mask, 0)) { + buf.readUnsignedShortLE(); // wheel speed + } + if (BitUtil.check(mask, 1)) { + buf.readUnsignedByte(); // acceleration pedal + } + if (BitUtil.check(mask, 2)) { + position.set(Position.KEY_FUEL_USED, buf.readUnsignedIntLE()); + } + if (BitUtil.check(mask, 3)) { + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte()); + } + if (BitUtil.check(mask, 4)) { + position.set(Position.KEY_RPM, buf.readUnsignedShortLE()); + } + if (BitUtil.check(mask, 5)) { + position.set(Position.KEY_HOURS, buf.readUnsignedIntLE()); + } + if (BitUtil.check(mask, 6)) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + } + if (BitUtil.check(mask, 7)) { + position.set(Position.KEY_COOLANT_TEMP, (int) buf.readByte()); + } + if (BitUtil.check(mask, 8)) { + position.set("fuel2", buf.readUnsignedByte()); + } + if (BitUtil.check(mask, 9)) { + position.set(Position.KEY_ENGINE_LOAD, buf.readUnsignedByte()); + } + if (BitUtil.check(mask, 10)) { + position.set(Position.KEY_ODOMETER_SERVICE, buf.readUnsignedShortLE()); + } + if (BitUtil.check(mask, 11)) { + buf.skipBytes(8); // sensors + } + if (BitUtil.check(mask, 12)) { + buf.readUnsignedShortLE(); // ambient air temperature + } + if (BitUtil.check(mask, 13)) { + buf.skipBytes(8); // trailer id + } + if (BitUtil.check(mask, 14)) { + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShortLE()); + } + } + + private void decodeMask3(ByteBuf buf, int mask, Position position) { + + if (BitUtil.check(mask, 0)) { + buf.readUnsignedShortLE(); // fuel economy + } + if (BitUtil.check(mask, 1)) { + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedIntLE()); + } + if (BitUtil.check(mask, 2)) { + position.set(Position.KEY_AXLE_WEIGHT, buf.readUnsignedMediumLE()); + } + if (BitUtil.check(mask, 3)) { + buf.readUnsignedByte(); // mil status + } + if (BitUtil.check(mask, 4)) { + buf.skipBytes(20); // dtc + } + if (BitUtil.check(mask, 5)) { + buf.readUnsignedShortLE(); + } + if (BitUtil.check(mask, 6)) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(buf.readLongLE())); + } + if (BitUtil.check(mask, 7)) { + buf.readUnsignedShortLE(); // dallas temperature + } + if (BitUtil.check(mask, 8)) { + buf.readUnsignedShortLE(); // dallas humidity + } + if (BitUtil.check(mask, 9)) { + buf.skipBytes(6); // lls group 1 + } + if (BitUtil.check(mask, 10)) { + buf.skipBytes(6); // lls group 2 + } + if (BitUtil.check(mask, 11)) { + buf.skipBytes(21); // j1979 group 1 + } + if (BitUtil.check(mask, 12)) { + buf.skipBytes(20); // j1979 dtc + } + if (BitUtil.check(mask, 13)) { + buf.skipBytes(9); // j1708 group 1 + } + if (BitUtil.check(mask, 14)) { + buf.skipBytes(21); // driving quality + } + } + + private void decodeMask4(ByteBuf buf, int mask, Position position) { + + if (BitUtil.check(mask, 0)) { + buf.readUnsignedIntLE(); + } + if (BitUtil.check(mask, 1)) { + buf.skipBytes(30); // lls group 3 + } + if (BitUtil.check(mask, 2)) { + buf.readUnsignedIntLE(); // instant fuel consumption + } + if (BitUtil.check(mask, 3)) { + buf.skipBytes(10); // axle weight group + } + if (BitUtil.check(mask, 4)) { + buf.readUnsignedByte(); + } + if (BitUtil.check(mask, 5)) { + buf.readUnsignedShortLE(); + } + if (BitUtil.check(mask, 6)) { + buf.readUnsignedByte(); // maximum acceleration + buf.readUnsignedByte(); // maximum deceleration + buf.readUnsignedByte(); // maximum cornering + } + if (BitUtil.check(mask, 7)) { + buf.skipBytes(16); + } + if (BitUtil.check(mask, 8)) { + buf.skipBytes(40); // temperature sensors + } + if (BitUtil.check(mask, 9)) { + position.set("driver1", buf.readCharSequence(16, StandardCharsets.US_ASCII).toString().trim()); + position.set("driver2", buf.readCharSequence(16, StandardCharsets.US_ASCII).toString().trim()); + } + if (BitUtil.check(mask, 10)) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + String imei = String.format("%015d", buf.readLongLE()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + while (buf.readableBytes() > 1) { + + int dataEnd = buf.readUnsignedShortLE() + buf.readerIndex(); + int type = buf.readUnsignedByte(); + + if (type != MSG_ASYNC_STACK && type != MSG_TIME_TRIGGERED) { + return null; + } + + int confirmKey = buf.readUnsignedByte() & 0x7F; + + while (buf.readerIndex() < dataEnd) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int structEnd = buf.readUnsignedByte() + buf.readerIndex(); + + long time = buf.readUnsignedIntLE(); + if ((time & 0x0f) == DATA_TYPE) { + + time = time >> 4 << 1; + time += 0x47798280; // 01/01/2008 + position.setTime(new Date(time * 1000)); + + // Read masks + int mask; + List<Integer> masks = new LinkedList<>(); + do { + mask = buf.readUnsignedShortLE(); + masks.add(mask); + } while (BitUtil.check(mask, 15)); + + mask = masks.get(0); + decodeMask1(buf, mask, position); + + if (masks.size() >= 2) { + mask = masks.get(1); + decodeMask2(buf, mask, position); + } + + if (masks.size() >= 3) { + mask = masks.get(2); + decodeMask3(buf, mask, position); + } + + if (masks.size() >= 4) { + mask = masks.get(3); + decodeMask4(buf, mask, position); + } + } + + buf.readerIndex(structEnd); + + if (position.getValid()) { + positions.add(position); + } else if (!position.getAttributes().isEmpty()) { + getLastLocation(position, null); + positions.add(position); + } + } + + // Send response + if (type == MSG_ASYNC_STACK && channel != null) { + ByteBuf response = Unpooled.buffer(8 + 2 + 2 + 1); + response.writeLongLE(Long.parseLong(imei)); + response.writeShortLE(2); + response.writeByte(MSG_STACK_COFIRM); + response.writeByte(confirmKey); + + int checksum = 0; + for (int i = 0; i < response.writerIndex(); i++) { + checksum += response.getUnsignedByte(i); + } + response.writeByte(checksum); + + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/BceProtocolEncoder.java b/src/main/java/org/traccar/protocol/BceProtocolEncoder.java new file mode 100644 index 000000000..53ac51581 --- /dev/null +++ b/src/main/java/org/traccar/protocol/BceProtocolEncoder.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.helper.Checksum; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class BceProtocolEncoder extends BaseProtocolEncoder { + + public BceProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + if (command.getType().equals(Command.TYPE_OUTPUT_CONTROL)) { + ByteBuf buf = Unpooled.buffer(); + + buf.writeLongLE(Long.parseLong(getUniqueId(command.getDeviceId()))); + buf.writeShortLE(1 + 1 + 3 + 1); // length + buf.writeByte(BceProtocolDecoder.MSG_OUTPUT_CONTROL); + buf.writeByte(command.getInteger(Command.KEY_INDEX) == 1 ? 0x0A : 0x0B); + buf.writeByte(0xFF); // index + buf.writeByte(0x00); // form id + buf.writeShortLE(Integer.parseInt(command.getString(Command.KEY_DATA)) > 0 ? 0x0055 : 0x0000); + buf.writeByte(Checksum.sum(buf.nioBuffer())); + + return buf; + } else { + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/BlackKiteProtocol.java b/src/main/java/org/traccar/protocol/BlackKiteProtocol.java new file mode 100644 index 000000000..617a24d7a --- /dev/null +++ b/src/main/java/org/traccar/protocol/BlackKiteProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 Vijay Kumar (vijaykumar@zilogic.com) + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class BlackKiteProtocol extends BaseProtocol { + + public BlackKiteProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new GalileoFrameDecoder()); + pipeline.addLast(new BlackKiteProtocolDecoder(BlackKiteProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/BlackKiteProtocolDecoder.java b/src/main/java/org/traccar/protocol/BlackKiteProtocolDecoder.java new file mode 100644 index 000000000..474ceabdc --- /dev/null +++ b/src/main/java/org/traccar/protocol/BlackKiteProtocolDecoder.java @@ -0,0 +1,195 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 Vijay Kumar (vijaykumar@zilogic.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.BitUtil; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +public class BlackKiteProtocolDecoder extends BaseProtocolDecoder { + + public BlackKiteProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final int TAG_IMEI = 0x03; + private static final int TAG_DATE = 0x20; + private static final int TAG_COORDINATES = 0x30; + private static final int TAG_SPEED_COURSE = 0x33; + private static final int TAG_ALTITUDE = 0x34; + private static final int TAG_STATUS = 0x40; + private static final int TAG_DIGITAL_OUTPUTS = 0x45; + private static final int TAG_DIGITAL_INPUTS = 0x46; + private static final int TAG_INPUT_VOLTAGE1 = 0x50; + private static final int TAG_INPUT_VOLTAGE2 = 0x51; + private static final int TAG_INPUT_VOLTAGE3 = 0x52; + private static final int TAG_INPUT_VOLTAGE4 = 0x53; + private static final int TAG_XT1 = 0x60; + private static final int TAG_XT2 = 0x61; + private static final int TAG_XT3 = 0x62; + + private void sendResponse(Channel channel, int checksum) { + if (channel != null) { + ByteBuf reply = Unpooled.buffer(3); + reply.writeByte(0x02); + reply.writeShortLE((short) checksum); + channel.writeAndFlush(new NetworkMessage(reply, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // header + int length = (buf.readUnsignedShortLE() & 0x7fff) + 3; + + List<Position> positions = new LinkedList<>(); + Set<Integer> tags = new HashSet<>(); + boolean hasLocation = false; + Position position = new Position(getProtocolName()); + + while (buf.readerIndex() < length) { + + // Check if new message started + int tag = buf.readUnsignedByte(); + if (tags.contains(tag)) { + if (hasLocation && position.getFixTime() != null) { + positions.add(position); + } + tags.clear(); + hasLocation = false; + position = new Position(getProtocolName()); + } + tags.add(tag); + + switch (tag) { + + case TAG_IMEI: + getDeviceSession(channel, remoteAddress, buf.readSlice(15).toString(StandardCharsets.US_ASCII)); + break; + + case TAG_DATE: + position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); + break; + + case TAG_COORDINATES: + hasLocation = true; + position.setValid((buf.readUnsignedByte() & 0xf0) == 0x00); + position.setLatitude(buf.readIntLE() / 1000000.0); + position.setLongitude(buf.readIntLE() / 1000000.0); + break; + + case TAG_SPEED_COURSE: + position.setSpeed(buf.readUnsignedShortLE() * 0.0539957); + position.setCourse(buf.readUnsignedShortLE() * 0.1); + break; + + case TAG_ALTITUDE: + position.setAltitude(buf.readShortLE()); + break; + + case TAG_STATUS: + int status = buf.readUnsignedShortLE(); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 9)); + if (BitUtil.check(status, 15)) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + position.set(Position.KEY_CHARGE, BitUtil.check(status, 2)); + break; + + case TAG_DIGITAL_INPUTS: + int input = buf.readUnsignedShortLE(); + for (int i = 0; i < 16; i++) { + position.set(Position.PREFIX_IO + (i + 1), BitUtil.check(input, i)); + } + break; + + case TAG_DIGITAL_OUTPUTS: + int output = buf.readUnsignedShortLE(); + for (int i = 0; i < 16; i++) { + position.set(Position.PREFIX_IO + (i + 17), BitUtil.check(output, i)); + } + break; + + case TAG_INPUT_VOLTAGE1: + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShortLE() / 1000.0); + break; + + case TAG_INPUT_VOLTAGE2: + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShortLE() / 1000.0); + break; + + case TAG_INPUT_VOLTAGE3: + position.set(Position.PREFIX_ADC + 3, buf.readUnsignedShortLE() / 1000.0); + break; + + case TAG_INPUT_VOLTAGE4: + position.set(Position.PREFIX_ADC + 4, buf.readUnsignedShortLE() / 1000.0); + break; + + case TAG_XT1: + case TAG_XT2: + case TAG_XT3: + buf.skipBytes(16); + break; + + default: + break; + + } + } + + if (hasLocation && position.getFixTime() != null) { + positions.add(position); + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + sendResponse(channel, buf.readUnsignedShortLE()); + + for (Position p : positions) { + p.setDeviceId(deviceSession.getDeviceId()); + } + + if (positions.isEmpty()) { + return null; + } + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/BlueProtocol.java b/src/main/java/org/traccar/protocol/BlueProtocol.java new file mode 100644 index 000000000..d5dc5c421 --- /dev/null +++ b/src/main/java/org/traccar/protocol/BlueProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class BlueProtocol extends BaseProtocol { + + public BlueProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + 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 new file mode 100644 index 000000000..61edcd101 --- /dev/null +++ b/src/main/java/org/traccar/protocol/BlueProtocolDecoder.java @@ -0,0 +1,142 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.DateBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class BlueProtocolDecoder extends BaseProtocolDecoder { + + public BlueProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private double readCoordinate(ByteBuf buf, boolean negative) { + + int value = buf.readUnsignedShort(); + int degrees = value / 100; + double minutes = value % 100 + buf.readUnsignedShort() * 0.0001; + double coordinate = degrees + minutes / 60; + 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())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // header + buf.readUnsignedShort(); // length + buf.readUnsignedByte(); // version + buf.readUnsignedByte(); + + String id = String.valueOf(buf.readUnsignedInt()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + while (buf.readableBytes() > 1) { + + int frameEnd = buf.readerIndex() + buf.readUnsignedByte(); + + int type = buf.readUnsignedByte(); + int index = buf.readUnsignedByte(); + buf.readUnsignedByte(); + buf.readUnsignedByte(); // flags + + if (type == 0x01) { + + buf.readUnsignedByte(); // reserved + int flags = buf.readUnsignedByte(); + + position.setValid(BitUtil.check(flags, 7)); + position.setLatitude(readCoordinate(buf, BitUtil.check(flags, 6))); + position.setLongitude(readCoordinate(buf, BitUtil.check(flags, 5))); + position.setSpeed(buf.readUnsignedShort() + buf.readUnsignedShort() * 0.001); + position.setCourse(buf.readUnsignedShort() + buf.readUnsignedByte() * 0.01); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + buf.readUnsignedShort(); // lac + buf.readUnsignedShort(); // cid + + } else if (type == 0x12) { + + int status; + + status = buf.readUnsignedByte(); // status 1 + position.set(Position.KEY_ALARM, BitUtil.check(status, 1) ? Position.ALARM_VIBRATION : null); + + buf.readUnsignedByte(); // status 2 + buf.readUnsignedByte(); // status 3 + buf.readUnsignedByte(); // status 4 + buf.readUnsignedByte(); // status 5 + buf.readUnsignedByte(); // status 6 + + } else if (type == 0x84) { + + sendResponse(channel, index); + + } + + buf.readerIndex(frameEnd); + } + + return position.getFixTime() != null ? position : null; + } + +} diff --git a/src/main/java/org/traccar/protocol/BoxProtocol.java b/src/main/java/org/traccar/protocol/BoxProtocol.java new file mode 100644 index 000000000..dfea15938 --- /dev/null +++ b/src/main/java/org/traccar/protocol/BoxProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class BoxProtocol extends BaseProtocol { + + public BoxProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '\r')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new BoxProtocolDecoder(BoxProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/BoxProtocolDecoder.java b/src/main/java/org/traccar/protocol/BoxProtocolDecoder.java new file mode 100644 index 000000000..853fa8f81 --- /dev/null +++ b/src/main/java/org/traccar/protocol/BoxProtocolDecoder.java @@ -0,0 +1,119 @@ +/* + * Copyright 2014 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class BoxProtocolDecoder extends BaseProtocolDecoder { + + public BoxProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("L,") + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .text("G,") + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .number("(d+.?d*),") // distance + .number("(d+),") // event + .number("(d+)") // status + .groupBegin() + .text(";") + .expression("(.+)") + .groupEnd("?") + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.startsWith("H,")) { + + int index = sentence.indexOf(',', 2) + 1; + String id = sentence.substring(index, sentence.indexOf(',', index)); + getDeviceSession(channel, remoteAddress, id); + + } else if (sentence.startsWith("E,")) { + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("A," + sentence.substring(2) + "\r", remoteAddress)); + } + + } else if (sentence.startsWith("L,")) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime()); + + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + + position.set(Position.KEY_ODOMETER_TRIP, parser.nextDouble() * 1000); + position.set(Position.KEY_EVENT, parser.next()); + + int status = parser.nextInt(); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 0)); + position.set(Position.KEY_MOTION, BitUtil.check(status, 1)); + position.setValid(!BitUtil.check(status, 2)); + position.set(Position.KEY_STATUS, status); + + if (parser.hasNext()) { + String[] data = parser.next().split(";"); + for (String item : data) { + int valueIndex = item.indexOf(','); + position.set(item.substring(0, valueIndex).toLowerCase(), item.substring(valueIndex + 1)); + } + } + + return position; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/C2stekProtocol.java b/src/main/java/org/traccar/protocol/C2stekProtocol.java new file mode 100644 index 000000000..804621fd3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/C2stekProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class C2stekProtocol extends BaseProtocol { + + public C2stekProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, false, "$AP")); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new C2stekProtocolDecoder(C2stekProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/C2stekProtocolDecoder.java b/src/main/java/org/traccar/protocol/C2stekProtocolDecoder.java new file mode 100644 index 000000000..6a31cb2f4 --- /dev/null +++ b/src/main/java/org/traccar/protocol/C2stekProtocolDecoder.java @@ -0,0 +1,121 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class C2stekProtocolDecoder extends BaseProtocolDecoder { + + public C2stekProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("PA$") + .number("(d+)") // imei + .text("$") + .expression(".#") // data type + .number("(dd)(dd)(dd)#") // date (yymmdd) + .number("(dd)(dd)(dd)#") // time (hhmmss) + .number("([01])#") // valid + .number("([+-]?d+.d+)#") // latitude + .number("([+-]?d+.d+)#") // longitude + .number("(d+.d+)#") // speed + .number("(d+.d+)#") // course + .number("(-?d+.d+)#") // altitude + .number("(d+)#") // battery + .number("d+#") // geo area alarm + .number("(x+)#") // alarm + .number("([01])") // armed + .number("([01])") // door + .number("([01])#") // ignition + .any() + .text("$AP") + .compile(); + + private String decodeAlarm(int alarm) { + switch (alarm) { + case 0x2: + return Position.ALARM_SHOCK; + case 0x3: + return Position.ALARM_POWER_CUT; + case 0x4: + return Position.ALARM_OVERSPEED; + case 0x5: + return Position.ALARM_SOS; + case 0x6: + return Position.ALARM_DOOR; + case 0xA: + return Position.ALARM_LOW_BATTERY; + case 0xB: + return Position.ALARM_FAULT; + default: + return null; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + if (sentence.contains("$20$") && channel != null) { + channel.writeAndFlush(new NetworkMessage(sentence, remoteAddress)); + } + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime()); + position.setValid(parser.nextInt() > 0); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + position.setAltitude(parser.nextDouble()); + + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001); + position.set(Position.KEY_ALARM, decodeAlarm(parser.nextHexInt())); + + position.set(Position.KEY_ARMED, parser.nextInt() > 0); + position.set(Position.KEY_DOOR, parser.nextInt() > 0); + position.set(Position.KEY_IGNITION, parser.nextInt() > 0); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/CalAmpProtocol.java b/src/main/java/org/traccar/protocol/CalAmpProtocol.java new file mode 100644 index 000000000..232e72a8c --- /dev/null +++ b/src/main/java/org/traccar/protocol/CalAmpProtocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class CalAmpProtocol extends BaseProtocol { + + public CalAmpProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CalAmpProtocolDecoder(CalAmpProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/CalAmpProtocolDecoder.java b/src/main/java/org/traccar/protocol/CalAmpProtocolDecoder.java new file mode 100644 index 000000000..dad0ae774 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CalAmpProtocolDecoder.java @@ -0,0 +1,202 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; + +public class CalAmpProtocolDecoder extends BaseProtocolDecoder { + + public CalAmpProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_NULL = 0; + public static final int MSG_ACK = 1; + public static final int MSG_EVENT_REPORT = 2; + public static final int MSG_ID_REPORT = 3; + public static final int MSG_USER_DATA = 4; + public static final int MSG_APP_DATA = 5; + public static final int MSG_CONFIG = 6; + public static final int MSG_UNIT_REQUEST = 7; + public static final int MSG_LOCATE_REPORT = 8; + public static final int MSG_USER_DATA_ACC = 9; + public static final int MSG_MINI_EVENT_REPORT = 10; + public static final int MSG_MINI_USER_DATA = 11; + + public static final int SERVICE_UNACKNOWLEDGED = 0; + public static final int SERVICE_ACKNOWLEDGED = 1; + public static final int SERVICE_RESPONSE = 2; + + private void sendResponse(Channel channel, SocketAddress remoteAddress, int type, int index, int result) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(10); + response.writeByte(SERVICE_RESPONSE); + response.writeByte(MSG_ACK); + response.writeShort(index); + response.writeByte(type); + response.writeByte(result); + response.writeByte(0); + response.writeMedium(0); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + private Position decodePosition(DeviceSession deviceSession, int type, ByteBuf buf) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + if (type != MSG_MINI_EVENT_REPORT) { + buf.readUnsignedInt(); // fix time + } + position.setLatitude(buf.readInt() * 0.0000001); + position.setLongitude(buf.readInt() * 0.0000001); + if (type != MSG_MINI_EVENT_REPORT) { + position.setAltitude(buf.readInt() * 0.01); + position.setSpeed(UnitsConverter.knotsFromCps(buf.readUnsignedInt())); + } + position.setCourse(buf.readShort()); + if (type == MSG_MINI_EVENT_REPORT) { + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + } + + if (type == MSG_MINI_EVENT_REPORT) { + position.set(Position.KEY_SATELLITES, buf.getUnsignedByte(buf.readerIndex()) & 0xf); + position.setValid((buf.readUnsignedByte() & 0x20) == 0); + } else { + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.setValid((buf.readUnsignedByte() & 0x08) == 0); + } + + if (type != MSG_MINI_EVENT_REPORT) { + position.set("carrier", buf.readUnsignedShort()); + position.set(Position.KEY_RSSI, buf.readShort()); + } + + position.set("modem", buf.readUnsignedByte()); + + if (type != MSG_MINI_EVENT_REPORT) { + position.set(Position.KEY_HDOP, buf.readUnsignedByte()); + } + + int input = buf.readUnsignedByte(); + position.set(Position.KEY_INPUT, input); + position.set(Position.KEY_IGNITION, BitUtil.check(input, 0)); + + if (type != MSG_MINI_EVENT_REPORT) { + position.set(Position.KEY_STATUS, buf.readUnsignedByte()); + } + + if (type == MSG_EVENT_REPORT || type == MSG_MINI_EVENT_REPORT) { + if (type != MSG_MINI_EVENT_REPORT) { + buf.readUnsignedByte(); // event index + } + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + } + + int accType = BitUtil.from(buf.getUnsignedByte(buf.readerIndex()), 6); + int accCount = BitUtil.to(buf.readUnsignedByte(), 6); + + if (type != MSG_MINI_EVENT_REPORT) { + position.set("append", buf.readUnsignedByte()); + } + + if (accType == 1) { + buf.readUnsignedInt(); // threshold + buf.readUnsignedInt(); // mask + } + + for (int i = 0; i < accCount; i++) { + if (buf.readableBytes() >= 4) { + position.set("acc" + i, buf.readUnsignedInt()); + } + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (BitUtil.check(buf.getByte(buf.readerIndex()), 7)) { + + int content = buf.readUnsignedByte(); + + if (BitUtil.check(content, 0)) { + String id = ByteBufUtil.hexDump(buf.readSlice(buf.readUnsignedByte())).replace("f", ""); + getDeviceSession(channel, remoteAddress, id); + } + + if (BitUtil.check(content, 1)) { + buf.skipBytes(buf.readUnsignedByte()); // identifier type + } + + if (BitUtil.check(content, 2)) { + buf.skipBytes(buf.readUnsignedByte()); // authentication + } + + if (BitUtil.check(content, 3)) { + buf.skipBytes(buf.readUnsignedByte()); // routing + } + + if (BitUtil.check(content, 4)) { + buf.skipBytes(buf.readUnsignedByte()); // forwarding + } + + if (BitUtil.check(content, 5)) { + buf.skipBytes(buf.readUnsignedByte()); // response redirection + } + + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + int service = buf.readUnsignedByte(); + int type = buf.readUnsignedByte(); + int index = buf.readUnsignedShort(); + + if (service == SERVICE_ACKNOWLEDGED) { + sendResponse(channel, remoteAddress, type, index, 0); + } + + if (type == MSG_EVENT_REPORT || type == MSG_LOCATE_REPORT || type == MSG_MINI_EVENT_REPORT) { + return decodePosition(deviceSession, type, buf); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/CarTrackProtocol.java b/src/main/java/org/traccar/protocol/CarTrackProtocol.java new file mode 100644 index 000000000..e340fba25 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CarTrackProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class CarTrackProtocol extends BaseProtocol { + + public CarTrackProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "##")); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new CarTrackProtocolDecoder(CarTrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/CarTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/CarTrackProtocolDecoder.java new file mode 100644 index 000000000..ce3345826 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CarTrackProtocolDecoder.java @@ -0,0 +1,108 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2014 Rohit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.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 CarTrackProtocolDecoder extends BaseProtocolDecoder { + + public CarTrackProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$$") // header + .number("(d+)") // device id + .text("?").expression("*") + .text("&A") + .number("(dddd)") // command + .text("&B") + .number("(dd)(dd)(dd).(ddd),") // time (hhmmss.sss) + .expression("([AV]),") // validity + .number("(dd)(dd.dddd),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.dddd),") // longitude + .expression("([EW]),") + .number("(d+.d*)?,") // speed + .number("(d+.d*)?,") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .any() + .expression("&C([^&]*)") // io + .expression("&D([^&]*)") // odometer + .expression("&E([^&]*)") // alarm + .expression("&Y([^&]*)").optional() // adc + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_COMMAND, parser.next()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + position.set(Position.PREFIX_IO + 1, parser.next()); + + String odometer = parser.next(); + odometer = odometer.replace(":", "A"); + odometer = odometer.replace(";", "B"); + odometer = odometer.replace("<", "C"); + odometer = odometer.replace("=", "D"); + odometer = odometer.replace(">", "E"); + odometer = odometer.replace("?", "F"); + position.set(Position.KEY_ODOMETER, Integer.parseInt(odometer, 16)); + + parser.next(); // there is no meaningful alarms + position.set(Position.PREFIX_ADC + 1, parser.next()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/CarcellProtocol.java b/src/main/java/org/traccar/protocol/CarcellProtocol.java new file mode 100644 index 000000000..f08ab3bd9 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CarcellProtocol.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class CarcellProtocol extends BaseProtocol { + + public CarcellProtocol() { + setSupportedDataCommands( + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '\r')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new CarcellProtocolEncoder(CarcellProtocol.this)); + pipeline.addLast(new CarcellProtocolDecoder(CarcellProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/CarcellProtocolDecoder.java b/src/main/java/org/traccar/protocol/CarcellProtocolDecoder.java new file mode 100644 index 000000000..ec640ba71 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CarcellProtocolDecoder.java @@ -0,0 +1,164 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 java.net.SocketAddress; +import java.util.regex.Pattern; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.Parser.CoordinateFormat; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +public class CarcellProtocolDecoder extends BaseProtocolDecoder { + + public CarcellProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression("([$%])") // memory flag + .number("(d+),") // imei + .groupBegin() + .number("([NS])(dd)(dd).(dddd),") // latitude + .number("([EW])(ddd)(dd).(dddd),") // longitude + .or() + .text("CEL,") + .number("([NS])(d+.d+),") // latitude + .number("([EW])(d+.d+),") // longitude + .groupEnd() + .number("(d+),") // speed + .number("(d+),") // course + .groupBegin() + .number("([-+]ddd)([-+]ddd)([-+]ddd),") // x,y,z + .or() + .number("(d+),") // accel + .groupEnd() + .number("(d+),") // battery + .number("(d+),") // csq + .number("(d),") // jamming + .number("(d+),") // hdop + .expression("([CG]),?") // clock type + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d),") // block + .number("(d),") // ignition + .groupBegin() + .number("(d),") // cloned + .expression("([AF])") // panic + .number("(d),") // painel + .number("(d+),") // battery voltage + .or() + .number("(dd),") // time until delivery + .expression("([AF])") // panic + .number("(d),") // aux + .number("(d{2,4}),") // battery voltage + .number("(d{20}),") // ccid + .groupEnd() + .number("(xx)") // crc + .any() // full format + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.set(Position.KEY_ARCHIVE, parser.next().equals("%")); + position.setValid(true); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + if (parser.hasNext(8)) { + position.setLatitude(parser.nextCoordinate(CoordinateFormat.HEM_DEG_MIN_MIN)); + position.setLongitude(parser.nextCoordinate(CoordinateFormat.HEM_DEG_MIN_MIN)); + } + + if (parser.hasNext(4)) { + position.setLatitude(parser.nextCoordinate(CoordinateFormat.HEM_DEG)); + position.setLongitude(parser.nextCoordinate(CoordinateFormat.HEM_DEG)); + } + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt(0))); + position.setCourse(parser.nextInt(0)); + + if (parser.hasNext(3)) { + position.set("x", parser.nextInt(0)); + position.set("y", parser.nextInt(0)); + position.set("z", parser.nextInt(0)); + } + + if (parser.hasNext(1)) { + position.set(Position.KEY_ACCELERATION, parser.nextInt(0)); + } + + Double internalBattery = (parser.nextDouble(0) + 100d) * 0.0294d; + position.set(Position.KEY_BATTERY, internalBattery); + position.set(Position.KEY_RSSI, parser.nextInt(0)); + position.set("jamming", parser.next().equals("1")); + position.set(Position.KEY_GPS, parser.nextInt(0)); + + position.set("clockType", parser.next()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.set("blocked", parser.next().equals("1")); + position.set(Position.KEY_IGNITION, parser.next().equals("1")); + + if (parser.hasNext(4)) { + position.set("cloned", parser.next().equals("1")); + + parser.next(); // panic button status + + String painelStatus = parser.next(); + if (painelStatus.equals("1")) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + position.set("painel", painelStatus.equals("2")); + + Double mainVoltage = parser.nextDouble(0) / 100d; + position.set(Position.KEY_POWER, mainVoltage); + } + + if (parser.hasNext(5)) { + position.set("timeUntilDelivery", parser.nextInt(0)); + parser.next(); // panic button status + position.set(Position.KEY_INPUT, parser.next()); + + Double mainVoltage = parser.nextDouble(0) / 100d; + position.set(Position.KEY_POWER, mainVoltage); + + position.set(Position.KEY_ICCID, parser.next()); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/CarcellProtocolEncoder.java b/src/main/java/org/traccar/protocol/CarcellProtocolEncoder.java new file mode 100644 index 000000000..78dbe7e91 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CarcellProtocolEncoder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class CarcellProtocolEncoder extends StringProtocolEncoder { + + public CarcellProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_ENGINE_STOP: + return formatCommand(command, "$SRVCMD,%s,BA#\r\n", Command.KEY_UNIQUE_ID); + case Command.TYPE_ENGINE_RESUME: + return formatCommand(command, "$SRVCMD,%s,BD#\r\n", Command.KEY_UNIQUE_ID); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/CarscopProtocol.java b/src/main/java/org/traccar/protocol/CarscopProtocol.java new file mode 100644 index 000000000..2c754a97f --- /dev/null +++ b/src/main/java/org/traccar/protocol/CarscopProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class CarscopProtocol extends BaseProtocol { + + public CarscopProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '^')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new CarscopProtocolDecoder(CarscopProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/CarscopProtocolDecoder.java b/src/main/java/org/traccar/protocol/CarscopProtocolDecoder.java new file mode 100644 index 000000000..161666adc --- /dev/null +++ b/src/main/java/org/traccar/protocol/CarscopProtocolDecoder.java @@ -0,0 +1,101 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.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 CarscopProtocolDecoder extends BaseProtocolDecoder { + + public CarscopProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("*") + .any() + .number("(dd)(dd)(dd)") // time (hhmmss) + .expression("([AV])") // validity + .number("(dd)(dd.dddd)") // latitude + .expression("([NS])") + .number("(ddd)(dd.dddd)") // longitude + .expression("([EW])") + .number("(ddd.d)") // speed + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(ddd.dd)") // course + .groupBegin() + .number("(d{8})") // state + .number("L(d{6})") // odometer + .groupEnd("?") + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + DeviceSession deviceSession; + int index = sentence.indexOf("UB05"); + if (index != -1) { + String imei = sentence.substring(index + 4, index + 4 + 15); + deviceSession = getDeviceSession(channel, remoteAddress, imei); + } else { + deviceSession = getDeviceSession(channel, remoteAddress); + } + if (deviceSession == null) { + return null; + } + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + + dateBuilder.setDate(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + position.setCourse(parser.nextDouble(0)); + + if (parser.hasNext(2)) { + position.set(Position.KEY_STATUS, parser.next()); + position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/CastelProtocol.java b/src/main/java/org/traccar/protocol/CastelProtocol.java new file mode 100644 index 000000000..44c52d68f --- /dev/null +++ b/src/main/java/org/traccar/protocol/CastelProtocol.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +import java.nio.ByteOrder; +public class CastelProtocol extends BaseProtocol { + + public CastelProtocol() { + setSupportedDataCommands( + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 2, -4, 0, true)); + pipeline.addLast(new CastelProtocolEncoder(CastelProtocol.this)); + pipeline.addLast(new CastelProtocolDecoder(CastelProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CastelProtocolEncoder(CastelProtocol.this)); + pipeline.addLast(new CastelProtocolDecoder(CastelProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java b/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java new file mode 100644 index 000000000..23401b5ee --- /dev/null +++ b/src/main/java/org/traccar/protocol/CastelProtocolDecoder.java @@ -0,0 +1,609 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.ObdDecoder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class CastelProtocolDecoder extends BaseProtocolDecoder { + + private static final Map<Integer, Integer> PID_LENGTH_MAP = new HashMap<>(); + + static { + int[] l1 = { + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0b, 0x0d, + 0x0e, 0x0f, 0x11, 0x12, 0x13, 0x1c, 0x1d, 0x1e, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x33, 0x43, 0x45, 0x46, + 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x51, 0x52, + 0x5a + }; + int[] l2 = { + 0x02, 0x03, 0x0a, 0x0c, 0x10, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1f, 0x21, 0x22, + 0x23, 0x31, 0x32, 0x3c, 0x3d, 0x3e, 0x3f, 0x42, + 0x44, 0x4d, 0x4e, 0x50, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59 + }; + int[] l4 = { + 0x00, 0x01, 0x20, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x40, 0x41, 0x4f + }; + for (int i : l1) { + PID_LENGTH_MAP.put(i, 1); + } + for (int i : l2) { + PID_LENGTH_MAP.put(i, 2); + } + for (int i : l4) { + PID_LENGTH_MAP.put(i, 4); + } + } + + public CastelProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final short MSG_SC_LOGIN = 0x1001; + public static final short MSG_SC_LOGIN_RESPONSE = (short) 0x9001; + public static final short MSG_SC_LOGOUT = 0x1002; + public static final short MSG_SC_HEARTBEAT = 0x1003; + public static final short MSG_SC_HEARTBEAT_RESPONSE = (short) 0x9003; + public static final short MSG_SC_GPS = 0x4001; + public static final short MSG_SC_PID_DATA = 0x4002; + public static final short MSG_SC_G_SENSOR = 0x4003; + public static final short MSG_SC_SUPPORTED_PID = 0x4004; + public static final short MSG_SC_OBD_DATA = 0x4005; + public static final short MSG_SC_DTCS_PASSENGER = 0x4006; + public static final short MSG_SC_DTCS_COMMERCIAL = 0x400B; + public static final short MSG_SC_ALARM = 0x4007; + public static final short MSG_SC_CELL = 0x4008; + public static final short MSG_SC_GPS_SLEEP = 0x4009; + public static final short MSG_SC_FUEL = 0x400E; + public static final short MSG_SC_AGPS_REQUEST = 0x5101; + public static final short MSG_SC_QUERY_RESPONSE = (short) 0xA002; + public static final short MSG_SC_CURRENT_LOCATION = (short) 0xB001; + + public static final short MSG_CC_LOGIN = 0x4001; + public static final short MSG_CC_LOGIN_RESPONSE = (short) 0x8001; + public static final short MSG_CC_HEARTBEAT = 0x4206; + public static final short MSG_CC_PETROL_CONTROL = 0x4583; + public static final short MSG_CC_HEARTBEAT_RESPONSE = (short) 0x8206; + + private Position readPosition(DeviceSession deviceSession, ByteBuf buf) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setDateReverse(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + double lat = buf.readUnsignedIntLE() / 3600000.0; + double lon = buf.readUnsignedIntLE() / 3600000.0; + position.setSpeed(UnitsConverter.knotsFromCps(buf.readUnsignedShortLE())); + position.setCourse(buf.readUnsignedShortLE() * 0.1); + + int flags = buf.readUnsignedByte(); + if ((flags & 0x02) == 0) { + lat = -lat; + } + if ((flags & 0x01) == 0) { + lon = -lon; + } + position.setLatitude(lat); + position.setLongitude(lon); + position.setValid((flags & 0x0C) > 0); + position.set(Position.KEY_SATELLITES, flags >> 4); + + return position; + } + + private Position createPosition(DeviceSession deviceSession) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + return position; + } + + private void decodeObd(Position position, ByteBuf buf, boolean groups) { + + int count = buf.readUnsignedByte(); + + int[] pids = new int[count]; + for (int i = 0; i < count; i++) { + pids[i] = buf.readUnsignedShortLE() & 0xff; + } + + if (groups) { + buf.readUnsignedByte(); // group count + buf.readUnsignedByte(); // group size + } + + for (int i = 0; i < count; i++) { + int value; + switch (PID_LENGTH_MAP.get(pids[i])) { + case 1: + value = buf.readUnsignedByte(); + break; + case 2: + value = buf.readUnsignedShortLE(); + break; + case 4: + value = buf.readIntLE(); + break; + default: + value = 0; + break; + } + position.add(ObdDecoder.decodeData(pids[i], value, false)); + } + } + + private void decodeStat(Position position, ByteBuf buf) { + + buf.readUnsignedIntLE(); // ACC ON time + buf.readUnsignedIntLE(); // UTC time + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedIntLE()); + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedIntLE()); + buf.readUnsignedShortLE(); // current fuel consumption + + long state = buf.readUnsignedIntLE(); + position.set(Position.KEY_ALARM, BitUtil.check(state, 4) ? Position.ALARM_ACCELERATION : null); + position.set(Position.KEY_ALARM, BitUtil.check(state, 5) ? Position.ALARM_BRAKING : null); + position.set(Position.KEY_ALARM, BitUtil.check(state, 6) ? Position.ALARM_IDLE : null); + position.set(Position.KEY_IGNITION, BitUtil.check(state, 2 * 8 + 2)); + position.set(Position.KEY_STATUS, state); + + buf.skipBytes(8); + } + + private void sendResponse( + Channel channel, SocketAddress remoteAddress, + int version, ByteBuf id, short type, ByteBuf content) { + + if (channel != null) { + int length = 2 + 2 + 1 + id.readableBytes() + 2 + 2 + 2; + if (content != null) { + length += content.readableBytes(); + } + + ByteBuf response = Unpooled.buffer(length); + response.writeByte('@'); response.writeByte('@'); + response.writeShortLE(length); + response.writeByte(version); + response.writeBytes(id); + response.writeShort(type); + if (content != null) { + response.writeBytes(content); + content.release(); + } + response.writeShortLE( + Checksum.crc16(Checksum.CRC16_X25, response.nioBuffer(0, response.writerIndex()))); + response.writeByte(0x0D); response.writeByte(0x0A); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + private void sendResponse( + Channel channel, SocketAddress remoteAddress, ByteBuf id, short type) { + + if (channel != null) { + int length = 2 + 2 + id.readableBytes() + 2 + 4 + 8 + 2 + 2; + + ByteBuf response = Unpooled.buffer(length); + response.writeByte('@'); response.writeByte('@'); + response.writeShortLE(length); + response.writeBytes(id); + response.writeShort(type); + response.writeIntLE(0); + for (int i = 0; i < 8; i++) { + response.writeByte(0xff); + } + response.writeShortLE( + Checksum.crc16(Checksum.CRC16_X25, response.nioBuffer(0, response.writerIndex()))); + response.writeByte(0x0D); response.writeByte(0x0A); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + private void decodeAlarm(Position position, int alarm) { + switch (alarm) { + case 0x01: + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + break; + case 0x02: + position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); + break; + case 0x03: + position.set(Position.KEY_ALARM, Position.ALARM_TEMPERATURE); + break; + case 0x04: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 0x05: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 0x06: + position.set(Position.KEY_ALARM, Position.ALARM_IDLE); + break; + case 0x07: + position.set(Position.KEY_ALARM, Position.ALARM_TOW); + break; + case 0x08: + position.set(Position.KEY_ALARM, Position.ALARM_HIGH_RPM); + break; + case 0x09: + position.set(Position.KEY_ALARM, Position.ALARM_POWER_ON); + break; + case 0x0B: + position.set(Position.KEY_ALARM, Position.ALARM_LANE_CHANGE); + break; + case 0x0C: + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + break; + case 0x0E: + position.set(Position.KEY_ALARM, Position.ALARM_POWER_OFF); + break; + case 0x16: + position.set(Position.KEY_IGNITION, true); + break; + case 0x17: + position.set(Position.KEY_IGNITION, false); + break; + default: + break; + } + } + + private Object decodeSc( + Channel channel, SocketAddress remoteAddress, ByteBuf buf, + int version, ByteBuf id, short type, DeviceSession deviceSession) { + + Position position; + int count; + + switch (type) { + + case MSG_SC_HEARTBEAT: + sendResponse(channel, remoteAddress, version, id, MSG_SC_HEARTBEAT_RESPONSE, null); + return null; + + case MSG_SC_LOGIN: + case MSG_SC_LOGOUT: + case MSG_SC_GPS: + case MSG_SC_ALARM: + case MSG_SC_CURRENT_LOCATION: + case MSG_SC_FUEL: + if (type == MSG_SC_LOGIN) { + ByteBuf response = Unpooled.buffer(10); + response.writeIntLE(0xFFFFFFFF); + response.writeShortLE(0); + response.writeIntLE((int) (System.currentTimeMillis() / 1000)); + sendResponse(channel, remoteAddress, version, id, MSG_SC_LOGIN_RESPONSE, response); + } + + if (type == MSG_SC_GPS) { + buf.readUnsignedByte(); // historical + } else if (type == MSG_SC_ALARM) { + buf.readUnsignedIntLE(); // alarm + } else if (type == MSG_SC_CURRENT_LOCATION) { + buf.readUnsignedShortLE(); + } + + buf.readUnsignedIntLE(); // ACC ON time + buf.readUnsignedIntLE(); // UTC time + long odometer = buf.readUnsignedIntLE(); + long tripOdometer = buf.readUnsignedIntLE(); + long fuelConsumption = buf.readUnsignedIntLE(); + buf.readUnsignedShortLE(); // current fuel consumption + long status = buf.readUnsignedIntLE(); + buf.skipBytes(8); + + count = buf.readUnsignedByte(); + + List<Position> positions = new LinkedList<>(); + + for (int i = 0; i < count; i++) { + position = readPosition(deviceSession, buf); + position.set(Position.KEY_ODOMETER, odometer); + position.set(Position.KEY_ODOMETER_TRIP, tripOdometer); + position.set(Position.KEY_FUEL_CONSUMPTION, fuelConsumption); + position.set(Position.KEY_STATUS, status); + positions.add(position); + } + + if (type == MSG_SC_ALARM) { + int alarmCount = buf.readUnsignedByte(); + for (int i = 0; i < alarmCount; i++) { + if (buf.readUnsignedByte() != 0) { + int alarm = buf.readUnsignedByte(); + for (Position p : positions) { + decodeAlarm(p, alarm); + } + buf.readUnsignedShortLE(); // description + buf.readUnsignedShortLE(); // threshold + } + } + } else if (type == MSG_SC_FUEL) { + for (Position p : positions) { + p.set(Position.PREFIX_ADC + 1, buf.readUnsignedShortLE()); + } + } + + return positions.isEmpty() ? null : positions; + + case MSG_SC_GPS_SLEEP: + buf.readUnsignedIntLE(); // device time + return readPosition(deviceSession, buf); + + case MSG_SC_AGPS_REQUEST: + return readPosition(deviceSession, buf); + + case MSG_SC_PID_DATA: + position = createPosition(deviceSession); + + decodeStat(position, buf); + + buf.readUnsignedShortLE(); // sample rate + decodeObd(position, buf, true); + + return position; + + case MSG_SC_G_SENSOR: + position = createPosition(deviceSession); + + decodeStat(position, buf); + + buf.readUnsignedShortLE(); // sample rate + + count = buf.readUnsignedByte(); + + StringBuilder data = new StringBuilder("["); + for (int i = 0; i < count; i++) { + if (i > 0) { + data.append(","); + } + data.append("["); + data.append(buf.readShortLE() * 0.015625); + data.append(","); + data.append(buf.readShortLE() * 0.015625); + data.append(","); + data.append(buf.readShortLE() * 0.015625); + data.append("]"); + } + data.append("]"); + + position.set(Position.KEY_G_SENSOR, data.toString()); + + return position; + + case MSG_SC_DTCS_PASSENGER: + position = createPosition(deviceSession); + + decodeStat(position, buf); + + buf.readUnsignedByte(); // flag + position.add(ObdDecoder.decodeCodes(ByteBufUtil.hexDump(buf.readSlice(buf.readUnsignedByte())))); + + return position; + + case MSG_SC_OBD_DATA: + position = createPosition(deviceSession); + + decodeStat(position, buf); + + buf.readUnsignedByte(); // flag + decodeObd(position, buf, false); + + return position; + + case MSG_SC_CELL: + position = createPosition(deviceSession); + + decodeStat(position, buf); + + position.setNetwork(new Network( + CellTower.fromLacCid(buf.readUnsignedShortLE(), buf.readUnsignedShortLE()))); + + return position; + + case MSG_SC_QUERY_RESPONSE: + position = createPosition(deviceSession); + + buf.readUnsignedShortLE(); // index + buf.readUnsignedByte(); // response count + buf.readUnsignedByte(); // response index + + int failureCount = buf.readUnsignedByte(); + for (int i = 0; i < failureCount; i++) { + buf.readUnsignedShortLE(); // tag + } + + int successCount = buf.readUnsignedByte(); + for (int i = 0; i < successCount; i++) { + buf.readUnsignedShortLE(); // tag + position.set(Position.KEY_RESULT, + buf.readSlice(buf.readUnsignedShortLE()).toString(StandardCharsets.US_ASCII)); + } + + return position; + + default: + return null; + + } + } + + private Object decodeCc( + Channel channel, SocketAddress remoteAddress, ByteBuf buf, + int version, ByteBuf id, short type, DeviceSession deviceSession) { + + if (type == MSG_CC_HEARTBEAT) { + + sendResponse(channel, remoteAddress, version, id, MSG_CC_HEARTBEAT_RESPONSE, null); + + buf.readUnsignedByte(); // 0x01 for history + int count = buf.readUnsignedByte(); + + List<Position> positions = new LinkedList<>(); + + for (int i = 0; i < count; i++) { + Position position = readPosition(deviceSession, buf); + + position.set(Position.KEY_STATUS, buf.readUnsignedIntLE()); + position.set(Position.KEY_BATTERY, buf.readUnsignedByte()); + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + + buf.readUnsignedByte(); // geo-fencing id + buf.readUnsignedByte(); // geo-fencing flags + buf.readUnsignedByte(); // additional flags + + position.setNetwork(new Network( + CellTower.fromLacCid(buf.readUnsignedShortLE(), buf.readUnsignedShortLE()))); + + positions.add(position); + } + + return positions; + + } else if (type == MSG_CC_LOGIN) { + + sendResponse(channel, remoteAddress, version, id, MSG_CC_LOGIN_RESPONSE, null); + + Position position = readPosition(deviceSession, buf); + + position.set(Position.KEY_STATUS, buf.readUnsignedIntLE()); + position.set(Position.KEY_BATTERY, buf.readUnsignedByte()); + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + + buf.readUnsignedByte(); // geo-fencing id + buf.readUnsignedByte(); // geo-fencing flags + buf.readUnsignedByte(); // additional flags + + // GSM_CELL_CODE + // STR_Z - firmware version + // STR_Z - hardware version + + return position; + + } + + return null; + } + + private Object decodeMpip( + Channel channel, SocketAddress remoteAddress, ByteBuf buf, + int version, ByteBuf id, short type, DeviceSession deviceSession) { + + if (type == 0x4001) { + + sendResponse(channel, remoteAddress, version, id, (short) type, null); + + return readPosition(deviceSession, buf); + + } else if (type == 0x2001) { + + sendResponse(channel, remoteAddress, id, (short) 0x1001); + + buf.readUnsignedIntLE(); // index + buf.readUnsignedIntLE(); // unix time + buf.readUnsignedByte(); + + return readPosition(deviceSession, buf); + + } else if (type == 0x4201 || type == 0x4202 || type == 0x4206) { + + return readPosition(deviceSession, buf); + + } else if (type == 0x4204) { + + List<Position> positions = new LinkedList<>(); + + for (int i = 0; i < 8; i++) { + Position position = readPosition(deviceSession, buf); + buf.skipBytes(31); + positions.add(position); + } + + return positions; + + } + + return null; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int header = buf.readUnsignedShortLE(); + buf.readUnsignedShortLE(); // length + + int version = -1; + if (header == 0x4040) { + version = buf.readUnsignedByte(); + } + + ByteBuf id = buf.readSlice(20); + short type = buf.readShort(); + + DeviceSession deviceSession = getDeviceSession( + channel, remoteAddress, id.toString(StandardCharsets.US_ASCII).trim()); + if (deviceSession == null) { + return null; + } + + switch (version) { + case -1: + return decodeMpip(channel, remoteAddress, buf, version, id, type, deviceSession); + case 3: + case 4: + return decodeSc(channel, remoteAddress, buf, version, id, type, deviceSession); + default: + return decodeCc(channel, remoteAddress, buf, version, id, type, deviceSession); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/CastelProtocolEncoder.java b/src/main/java/org/traccar/protocol/CastelProtocolEncoder.java new file mode 100644 index 000000000..dc694da28 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CastelProtocolEncoder.java @@ -0,0 +1,75 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.Context; +import org.traccar.helper.Checksum; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.nio.charset.StandardCharsets; + +public class CastelProtocolEncoder extends BaseProtocolEncoder { + + public CastelProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf encodeContent(long deviceId, short type, ByteBuf content) { + + ByteBuf buf = Unpooled.buffer(0); + String uniqueId = Context.getIdentityManager().getById(deviceId).getUniqueId(); + + buf.writeByte('@'); + buf.writeByte('@'); + + buf.writeShortLE(2 + 2 + 1 + 20 + 2 + content.readableBytes() + 2 + 2); // length + + buf.writeByte(1); // protocol version + + buf.writeBytes(uniqueId.getBytes(StandardCharsets.US_ASCII)); + buf.writeZero(20 - uniqueId.length()); + + buf.writeShort(type); + buf.writeBytes(content); + + buf.writeShortLE(Checksum.crc16(Checksum.CRC16_X25, buf.nioBuffer())); + + buf.writeByte('\r'); + buf.writeByte('\n'); + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + ByteBuf content = Unpooled.buffer(0); + switch (command.getType()) { + case Command.TYPE_ENGINE_STOP: + content.writeByte(1); + return encodeContent(command.getDeviceId(), CastelProtocolDecoder.MSG_CC_PETROL_CONTROL, content); + case Command.TYPE_ENGINE_RESUME: + content.writeByte(0); + return encodeContent(command.getDeviceId(), CastelProtocolDecoder.MSG_CC_PETROL_CONTROL, content); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/CautelaProtocol.java b/src/main/java/org/traccar/protocol/CautelaProtocol.java new file mode 100644 index 000000000..452bdf8d4 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CautelaProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class CautelaProtocol extends BaseProtocol { + + public CautelaProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new CautelaProtocolDecoder(CautelaProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/CautelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/CautelaProtocolDecoder.java new file mode 100644 index 000000000..bddf19b41 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CautelaProtocolDecoder.java @@ -0,0 +1,78 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.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 CautelaProtocolDecoder extends BaseProtocolDecoder { + + public CautelaProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("(d+),") // type + .number("(d+),") // imei + .number("(dd),(dd),(dd),") // date (ddmmyy) + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(dd)(dd),") // time (hhmm) + .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; + } + + parser.next(); // type + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder(); + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setValid(true); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + + dateBuilder.setHour(parser.nextInt()).setMinute(parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/CellocatorFrameDecoder.java b/src/main/java/org/traccar/protocol/CellocatorFrameDecoder.java new file mode 100644 index 000000000..ee2adde6d --- /dev/null +++ b/src/main/java/org/traccar/protocol/CellocatorFrameDecoder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class CellocatorFrameDecoder extends BaseFrameDecoder { + + private static final int MESSAGE_MINIMUM_LENGTH = 15; + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < MESSAGE_MINIMUM_LENGTH) { + return null; + } + + int length = 0; + int type = buf.getUnsignedByte(4); + switch (type) { + case CellocatorProtocolDecoder.MSG_CLIENT_STATUS: + length = 70; + break; + case CellocatorProtocolDecoder.MSG_CLIENT_PROGRAMMING: + length = 31; + break; + case CellocatorProtocolDecoder.MSG_CLIENT_SERIAL_LOG: + length = 70; + break; + case CellocatorProtocolDecoder.MSG_CLIENT_SERIAL: + if (buf.readableBytes() >= 19) { + length = 19 + buf.getUnsignedShortLE(buf.readerIndex() + 16); + } + break; + case CellocatorProtocolDecoder.MSG_CLIENT_MODULAR: + length = 15 + buf.getUnsignedByte(buf.readerIndex() + 13); + break; + case CellocatorProtocolDecoder.MSG_CLIENT_MODULAR_EXT: + length = 16 + buf.getUnsignedShortLE(buf.readerIndex() + 13); + break; + default: + break; + } + + if (length > 0 && buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/CellocatorProtocol.java b/src/main/java/org/traccar/protocol/CellocatorProtocol.java new file mode 100644 index 000000000..d910877cf --- /dev/null +++ b/src/main/java/org/traccar/protocol/CellocatorProtocol.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class CellocatorProtocol extends BaseProtocol { + + public CellocatorProtocol() { + setSupportedDataCommands( + Command.TYPE_OUTPUT_CONTROL); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CellocatorFrameDecoder()); + pipeline.addLast(new CellocatorProtocolEncoder(CellocatorProtocol.this)); + pipeline.addLast(new CellocatorProtocolDecoder(CellocatorProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CellocatorProtocolEncoder(CellocatorProtocol.this)); + pipeline.addLast(new CellocatorProtocolDecoder(CellocatorProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java b/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java new file mode 100644 index 000000000..aa13a0aa2 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CellocatorProtocolDecoder.java @@ -0,0 +1,266 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class CellocatorProtocolDecoder extends BaseProtocolDecoder { + + public CellocatorProtocolDecoder(Protocol protocol) { + super(protocol); + } + + static final int MSG_CLIENT_STATUS = 0; + static final int MSG_CLIENT_PROGRAMMING = 3; + static final int MSG_CLIENT_SERIAL_LOG = 7; + static final int MSG_CLIENT_SERIAL = 8; + static final int MSG_CLIENT_MODULAR = 9; + static final int MSG_CLIENT_MODULAR_EXT = 11; + + public static final int MSG_SERVER_ACKNOWLEDGE = 4; + + public static ByteBuf encodeContent(int type, int uniqueId, int packetNumber, ByteBuf content) { + + ByteBuf buf = Unpooled.buffer(); + buf.writeByte('M'); + buf.writeByte('C'); + buf.writeByte('G'); + buf.writeByte('P'); + buf.writeByte(type); + buf.writeIntLE(uniqueId); + buf.writeByte(packetNumber); + buf.writeIntLE(0); // authentication code + buf.writeBytes(content); + + byte checksum = 0; + for (int i = 4; i < buf.writerIndex(); i++) { + checksum += buf.getByte(i); + } + buf.writeByte(checksum); + + return buf; + } + + private void sendResponse(Channel channel, SocketAddress remoteAddress, long deviceId, byte packetNumber) { + if (channel != null) { + ByteBuf content = Unpooled.buffer(); + content.writeByte(0); + content.writeByte(packetNumber); + content.writeZero(11); + + ByteBuf reply = encodeContent(MSG_SERVER_ACKNOWLEDGE, (int) deviceId, packetNumber, content); + channel.writeAndFlush(new NetworkMessage(reply, remoteAddress)); + } + } + + private void sendModuleResponse(Channel channel, SocketAddress remoteAddress, long deviceId, byte packetNumber) { + if (channel != null) { + ByteBuf content = Unpooled.buffer(); + content.writeByte(0x80); + content.writeShortLE(10); // modules length + content.writeIntLE(0); // reserved + content.writeByte(9); // ack module type + content.writeShortLE(3); // module length + content.writeByte(0); // ack + content.writeShortLE(0); // reserved + + ByteBuf reply = encodeContent(MSG_CLIENT_MODULAR_EXT, (int) deviceId, packetNumber, content); + channel.writeAndFlush(new NetworkMessage(reply, remoteAddress)); + } + } + + private String decodeAlarm(short reason) { + switch (reason) { + case 70: + return Position.ALARM_SOS; + case 80: + return Position.ALARM_POWER_CUT; + case 81: + return Position.ALARM_LOW_POWER; + default: + return null; + } + } + + private Position decodeStatus(ByteBuf buf, DeviceSession deviceSession, boolean alternative) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_VERSION_HW, buf.readUnsignedByte()); + position.set(Position.KEY_VERSION_FW, buf.readUnsignedByte()); + buf.readUnsignedByte(); // protocol version + + position.set(Position.KEY_STATUS, buf.readUnsignedByte() & 0x0f); + + buf.readUnsignedByte(); // operator / configuration flags + buf.readUnsignedByte(); // reason data + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + + position.set("mode", buf.readUnsignedByte()); + position.set(Position.KEY_INPUT, buf.readUnsignedIntLE()); + + if (alternative) { + buf.readUnsignedByte(); // input + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShortLE()); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShortLE()); + } else { + buf.readUnsignedByte(); // operator + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedIntLE()); + } + + position.set(Position.KEY_ODOMETER, buf.readUnsignedMediumLE()); + + buf.skipBytes(6); // multi-purpose data + buf.readUnsignedShortLE(); // fix time + buf.readUnsignedByte(); // location status + buf.readUnsignedByte(); // mode 1 + buf.readUnsignedByte(); // mode 2 + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + + position.setValid(true); + + if (alternative) { + position.setLongitude(buf.readIntLE() / 10000000.0); + position.setLatitude(buf.readIntLE() / 10000000.0); + } else { + position.setLongitude(buf.readIntLE() / Math.PI * 180 / 100000000); + position.setLatitude(buf.readIntLE() / Math.PI * 180 / 100000000); + } + + position.setAltitude(buf.readIntLE() * 0.01); + + if (alternative) { + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedIntLE())); + position.setCourse(buf.readUnsignedShortLE() / 1000.0); + } else { + position.setSpeed(UnitsConverter.knotsFromMps(buf.readUnsignedIntLE() * 0.01)); + position.setCourse(buf.readUnsignedShortLE() / Math.PI * 180.0 / 1000.0); + } + + DateBuilder dateBuilder = new DateBuilder() + .setTimeReverse(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setDateReverse(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedShortLE()); + position.setTime(dateBuilder.getDate()); + + return position; + } + + private Position decodeModular(ByteBuf buf, DeviceSession deviceSession) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedByte(); // packet control + buf.readUnsignedShortLE(); // length + buf.readUnsignedShortLE(); // reserved + buf.readUnsignedShortLE(); // reserved + + while (buf.readableBytes() > 3) { + + int moduleType = buf.readUnsignedByte(); + int endIndex = buf.readUnsignedShortLE() + buf.readerIndex(); + + switch (moduleType) { + case 2: + buf.readUnsignedShortLE(); // operator id + buf.readUnsignedIntLE(); // pl signature + int count = buf.readUnsignedByte(); + for (int i = 0; i < count; i++) { + int id = buf.readUnsignedShortLE(); + buf.readUnsignedByte(); // variable length + position.set(Position.PREFIX_IO + id, buf.readUnsignedIntLE()); + } + break; + case 6: + buf.readUnsignedByte(); // hdop + buf.readUnsignedByte(); // mode 1 + buf.readUnsignedByte(); // mode 2 + buf.readUnsignedByte(); // satellites + position.setLongitude(buf.readIntLE() / Math.PI * 180 / 100000000); + position.setLatitude(buf.readIntLE() / Math.PI * 180 / 100000000); + position.setAltitude(buf.readIntLE() * 0.01); + position.setSpeed(UnitsConverter.knotsFromMps(buf.readUnsignedByte() * 0.01)); + position.setCourse(buf.readUnsignedShortLE() / Math.PI * 180.0 / 1000.0); + break; + case 7: + buf.readUnsignedByte(); // valid + DateBuilder dateBuilder = new DateBuilder() + .setTimeReverse(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setDateReverse(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + break; + default: + break; + } + + buf.readerIndex(endIndex); + + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + boolean alternative = buf.getByte(buf.readerIndex() + 3) != 'P'; + + buf.skipBytes(4); // system code + int type = buf.readUnsignedByte(); + + long deviceUniqueId = buf.readUnsignedIntLE(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(deviceUniqueId)); + if (deviceSession == null) { + return null; + } + + if (type != MSG_CLIENT_SERIAL) { + buf.readUnsignedShortLE(); // communication control + } + byte packetNumber = buf.readByte(); + + if (type == MSG_CLIENT_MODULAR_EXT) { + sendModuleResponse(channel, remoteAddress, deviceUniqueId, packetNumber); + } else { + sendResponse(channel, remoteAddress, deviceUniqueId, packetNumber); + } + + if (type == MSG_CLIENT_STATUS) { + return decodeStatus(buf, deviceSession, alternative); + } else if (type == MSG_CLIENT_MODULAR_EXT) { + return decodeModular(buf, deviceSession); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/CellocatorProtocolEncoder.java b/src/main/java/org/traccar/protocol/CellocatorProtocolEncoder.java new file mode 100644 index 000000000..76fa67686 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CellocatorProtocolEncoder.java @@ -0,0 +1,82 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class CellocatorProtocolEncoder extends BaseProtocolEncoder { + + public CellocatorProtocolEncoder(Protocol protocol) { + super(protocol); + } + + public static ByteBuf encodeContent(int type, int uniqueId, int packetNumber, ByteBuf content) { + + ByteBuf buf = Unpooled.buffer(); + buf.writeByte('M'); + buf.writeByte('C'); + buf.writeByte('G'); + buf.writeByte('P'); + buf.writeByte(type); + buf.writeIntLE(uniqueId); + buf.writeByte(packetNumber); + buf.writeIntLE(0); // authentication code + buf.writeBytes(content); + + byte checksum = 0; + for (int i = 4; i < buf.writerIndex(); i++) { + checksum += buf.getByte(i); + } + buf.writeByte(checksum); + + return buf; + } + + private ByteBuf encodeCommand(long deviceId, int command, int data1, int data2) { + + ByteBuf content = Unpooled.buffer(); + content.writeByte(command); + content.writeByte(command); + content.writeByte(data1); + content.writeByte(data1); + content.writeByte(data2); + content.writeByte(data2); + content.writeIntLE(0); // command specific data + + ByteBuf buf = encodeContent(0, Integer.parseInt(getUniqueId(deviceId)), 0, content); + content.release(); + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_OUTPUT_CONTROL: + int data = Integer.parseInt(command.getString(Command.KEY_DATA)) << 4 + + command.getInteger(Command.KEY_INDEX); + return encodeCommand(command.getDeviceId(), 0x03, data, 0); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/CguardProtocol.java b/src/main/java/org/traccar/protocol/CguardProtocol.java new file mode 100644 index 000000000..9157ca35c --- /dev/null +++ b/src/main/java/org/traccar/protocol/CguardProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class CguardProtocol extends BaseProtocol { + + public CguardProtocol() { + 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 CguardProtocolDecoder(CguardProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/CguardProtocolDecoder.java b/src/main/java/org/traccar/protocol/CguardProtocolDecoder.java new file mode 100644 index 000000000..d934921f1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CguardProtocolDecoder.java @@ -0,0 +1,147 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class CguardProtocolDecoder extends BaseProtocolDecoder { + + public CguardProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_NV = new PatternBuilder() + .text("NV:") + .number("(dd)(dd)(dd) ") // date (yymmdd) + .number("(dd)(dd)(dd)") // time (hhmmss) + .number(":(-?d+.d+)") // longitude + .number(":(-?d+.d+)") // latitude + .number(":(d+.?d*)") // speed + .number(":(?:NAN|(d+.?d*))") // accuracy + .number(":(?:NAN|(d+.?d*))") // course + .number(":(?:NAN|(d+.?d*))").optional() // altitude + .compile(); + + private static final Pattern PATTERN_BC = new PatternBuilder() + .text("BC:") + .number("(dd)(dd)(dd) ") // date (yymmdd) + .number("(dd)(dd)(dd):") // time (hhmmss) + .expression("(.+)") // data + .compile(); + + private Position decodePosition(DeviceSession deviceSession, String sentence) { + + Parser parser = new Parser(PATTERN_NV, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime()); + + position.setValid(true); + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + + position.setAccuracy(parser.nextDouble(0)); + + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + return position; + } + + private Position decodeStatus(DeviceSession deviceSession, String sentence) { + + Parser parser = new Parser(PATTERN_BC, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, parser.nextDateTime()); + + String[] data = parser.next().split(":"); + for (int i = 0; i < data.length / 2; i++) { + String key = data[i * 2]; + String value = data[i * 2 + 1]; + switch (key) { + case "CSQ1": + position.set(Position.KEY_RSSI, Integer.parseInt(value)); + break; + case "NSQ1": + position.set(Position.KEY_SATELLITES, Integer.parseInt(value)); + break; + case "BAT1": + if (value.contains(".")) { + position.set(Position.KEY_BATTERY, Double.parseDouble(value)); + } else { + position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(value)); + } + break; + case "PWR1": + position.set(Position.KEY_POWER, Double.parseDouble(value)); + break; + default: + position.set(key.toLowerCase(), value); + break; + } + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.startsWith("ID:") || sentence.startsWith("IDRO:")) { + getDeviceSession(channel, remoteAddress, sentence.substring(sentence.indexOf(':') + 1)); + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + if (sentence.startsWith("NV:")) { + return decodePosition(deviceSession, sentence); + } else if (sentence.startsWith("BC:")) { + return decodeStatus(deviceSession, sentence); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/CityeasyProtocol.java b/src/main/java/org/traccar/protocol/CityeasyProtocol.java new file mode 100644 index 000000000..8ab4ce93a --- /dev/null +++ b/src/main/java/org/traccar/protocol/CityeasyProtocol.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class CityeasyProtocol extends BaseProtocol { + + public CityeasyProtocol() { + setSupportedDataCommands( + Command.TYPE_POSITION_SINGLE, + Command.TYPE_POSITION_PERIODIC, + Command.TYPE_POSITION_STOP, + Command.TYPE_SET_TIMEZONE); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 2, -4, 0)); + pipeline.addLast(new CityeasyProtocolEncoder(CityeasyProtocol.this)); + pipeline.addLast(new CityeasyProtocolDecoder(CityeasyProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/CityeasyProtocolDecoder.java b/src/main/java/org/traccar/protocol/CityeasyProtocolDecoder.java new file mode 100644 index 000000000..9c4c7e11d --- /dev/null +++ b/src/main/java/org/traccar/protocol/CityeasyProtocolDecoder.java @@ -0,0 +1,127 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +public class CityeasyProtocolDecoder extends BaseProtocolDecoder { + + public CityeasyProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .groupBegin() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("([AV]),") // validity + .number("(d+),") // satellites + .number("([NS]),(d+.d+),") // latitude + .number("([EW]),(d+.d+),") // longitude + .number("(d+.d),") // speed + .number("(d+.d),") // hdop + .number("(d+.d)") // altitude + .groupEnd("?").text(";") + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(d+),") // lac + .number("(d+)") // cell + .any() + .compile(); + + public static final int MSG_ADDRESS_REQUEST = 0x0001; + public static final int MSG_STATUS = 0x0002; + public static final int MSG_LOCATION_REPORT = 0x0003; + public static final int MSG_LOCATION_REQUEST = 0x0004; + public static final int MSG_LOCATION_INTERVAL = 0x0005; + public static final int MSG_PHONE_NUMBER = 0x0006; + public static final int MSG_MONITORING = 0x0007; + public static final int MSG_TIMEZONE = 0x0008; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + buf.readUnsignedShort(); // length + + String imei = ByteBufUtil.hexDump(buf.readSlice(7)); + DeviceSession deviceSession = getDeviceSession( + channel, remoteAddress, imei, imei + Checksum.luhn(Long.parseLong(imei))); + if (deviceSession == null) { + return null; + } + + int type = buf.readUnsignedShort(); + + if (type == MSG_LOCATION_REPORT || type == MSG_LOCATION_REQUEST) { + + String sentence = buf.toString(buf.readerIndex(), buf.readableBytes() - 8, StandardCharsets.US_ASCII); + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (parser.hasNext(15)) { + + position.setTime(parser.nextDateTime()); + + position.setValid(parser.next().equals("A")); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + + position.setSpeed(parser.nextDouble(0)); + position.set(Position.KEY_HDOP, parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + } else { + + getLastLocation(position, null); + + } + + position.setNetwork(new Network(CellTower.from( + parser.nextInt(0), parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)))); + + return position; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/CityeasyProtocolEncoder.java b/src/main/java/org/traccar/protocol/CityeasyProtocolEncoder.java new file mode 100644 index 000000000..934105862 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CityeasyProtocolEncoder.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import java.util.TimeZone; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.helper.Checksum; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class CityeasyProtocolEncoder extends BaseProtocolEncoder { + + public CityeasyProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf encodeContent(int type, ByteBuf content) { + + ByteBuf buf = Unpooled.buffer(); + + buf.writeByte('S'); + buf.writeByte('S'); + buf.writeShort(2 + 2 + 2 + content.readableBytes() + 4 + 2 + 2); + buf.writeShort(type); + buf.writeBytes(content); + buf.writeInt(0x0B); + buf.writeShort(Checksum.crc16(Checksum.CRC16_KERMIT, buf.nioBuffer())); + buf.writeByte('\r'); + buf.writeByte('\n'); + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + + ByteBuf content = Unpooled.buffer(); + + switch (command.getType()) { + case Command.TYPE_POSITION_SINGLE: + return encodeContent(CityeasyProtocolDecoder.MSG_LOCATION_REQUEST, content); + case Command.TYPE_POSITION_PERIODIC: + content.writeShort(command.getInteger(Command.KEY_FREQUENCY)); + return encodeContent(CityeasyProtocolDecoder.MSG_LOCATION_INTERVAL, content); + case Command.TYPE_POSITION_STOP: + content.writeShort(0); + return encodeContent(CityeasyProtocolDecoder.MSG_LOCATION_INTERVAL, content); + case Command.TYPE_SET_TIMEZONE: + int timezone = TimeZone.getTimeZone(command.getString(Command.KEY_TIMEZONE)).getRawOffset() / 60000; + if (timezone < 0) { + content.writeByte(1); + } else { + content.writeByte(0); + } + content.writeShort(Math.abs(timezone)); + return encodeContent(CityeasyProtocolDecoder.MSG_TIMEZONE, content); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/ContinentalProtocol.java b/src/main/java/org/traccar/protocol/ContinentalProtocol.java new file mode 100644 index 000000000..bc7928fba --- /dev/null +++ b/src/main/java/org/traccar/protocol/ContinentalProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class ContinentalProtocol extends BaseProtocol { + + public ContinentalProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 2, -4, 0)); + pipeline.addLast(new ContinentalProtocolDecoder(ContinentalProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ContinentalProtocolDecoder.java b/src/main/java/org/traccar/protocol/ContinentalProtocolDecoder.java new file mode 100644 index 000000000..471afa0d6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ContinentalProtocolDecoder.java @@ -0,0 +1,114 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.BitUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; + +public class ContinentalProtocolDecoder extends BaseProtocolDecoder { + + public ContinentalProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_KEEPALIVE = 0x00; + public static final int MSG_STATUS = 0x02; + public static final int MSG_ACK = 0x06; + public static final int MSG_NACK = 0x15; + + private double readCoordinate(ByteBuf buf, boolean extended) { + long value = buf.readUnsignedInt(); + if (extended ? (value & 0x08000000) != 0 : (value & 0x00800000) != 0) { + value |= extended ? 0xF0000000 : 0xFF000000; + } + return (int) value / (extended ? 360000.0 : 3600.0); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + buf.readUnsignedShort(); // length + buf.readUnsignedByte(); // software version + + long serialNumber = buf.readUnsignedInt(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(serialNumber)); + if (deviceSession == null) { + return null; + } + + buf.readUnsignedByte(); // product + + int type = buf.readUnsignedByte(); + + if (type == MSG_STATUS) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setFixTime(new Date(buf.readUnsignedInt() * 1000L)); + + boolean extended = buf.getUnsignedByte(buf.readerIndex()) != 0; + position.setLatitude(readCoordinate(buf, extended)); + position.setLongitude(readCoordinate(buf, extended)); + + position.setCourse(buf.readUnsignedShort()); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort())); + + position.setValid(buf.readUnsignedByte() > 0); + + position.setDeviceTime(new Date(buf.readUnsignedInt() * 1000L)); + + position.set(Position.KEY_EVENT, buf.readUnsignedShort()); + + int input = buf.readUnsignedShort(); + position.set(Position.KEY_IGNITION, BitUtil.check(input, 0)); + position.set(Position.KEY_INPUT, input); + + position.set(Position.KEY_OUTPUT, buf.readUnsignedShort()); + position.set(Position.KEY_BATTERY, buf.readUnsignedByte()); + position.set(Position.KEY_DEVICE_TEMP, buf.readByte()); + + buf.readUnsignedShort(); // reserved + + if (buf.readableBytes() > 4) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + } + + if (buf.readableBytes() > 4) { + position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(buf.readUnsignedInt())); + } + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/CradlepointProtocol.java b/src/main/java/org/traccar/protocol/CradlepointProtocol.java new file mode 100644 index 000000000..4a09e0311 --- /dev/null +++ b/src/main/java/org/traccar/protocol/CradlepointProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class CradlepointProtocol extends BaseProtocol { + + public CradlepointProtocol() { + 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 CradlepointProtocolDecoder(CradlepointProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/CradlepointProtocolDecoder.java b/src/main/java/org/traccar/protocol/CradlepointProtocolDecoder.java new file mode 100644 index 000000000..a282131ce --- /dev/null +++ b/src/main/java/org/traccar/protocol/CradlepointProtocolDecoder.java @@ -0,0 +1,95 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; +import java.util.regex.Pattern; + +public class CradlepointProtocolDecoder extends BaseProtocolDecoder { + + public CradlepointProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression("([^,]+),") // id + .number("(d{1,6}),") // time (hhmmss) + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.d+)?,") // speed + .number("(d+.d+)?,") // course + .expression("([^,]+)?,") // carrier + .expression("([^,]+)?,") // serdis + .number("(-?d+)?,") // rsrp + .number("(-?d+)?,") // rssi + .number("(-?d+)?,") // rsrq + .expression("([^,]+)?,") // ecio + .expression("([^,]+)?") // wan ip + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int time = parser.nextInt(); + DateBuilder dateBuilder = new DateBuilder(new Date()); + dateBuilder.setHour(time / 100 / 100); + dateBuilder.setMinute(time / 100 % 100); + dateBuilder.setSecond(time % 100); + position.setTime(dateBuilder.getDate()); + + position.setValid(true); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.set("carrid", parser.next()); + position.set("serdis", parser.next()); + position.set("rsrp", parser.nextInt()); + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set("rsrq", parser.nextInt()); + position.set("ecio", parser.next()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/DishaProtocol.java b/src/main/java/org/traccar/protocol/DishaProtocol.java new file mode 100644 index 000000000..38f49cc05 --- /dev/null +++ b/src/main/java/org/traccar/protocol/DishaProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class DishaProtocol extends BaseProtocol { + + public DishaProtocol() { + 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 DishaProtocolDecoder(DishaProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/DishaProtocolDecoder.java b/src/main/java/org/traccar/protocol/DishaProtocolDecoder.java new file mode 100644 index 000000000..3223988ab --- /dev/null +++ b/src/main/java/org/traccar/protocol/DishaProtocolDecoder.java @@ -0,0 +1,102 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class DishaProtocolDecoder extends BaseProtocolDecoder { + + public DishaProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$A#A#") + .number("(d+)#") // imei + .expression("([AVMX])#") // validity + .number("(dd)(dd)(dd)#") // time (hhmmss) + .number("(dd)(dd)(dd)#") // date (ddmmyy) + .number("(dd)(dd.d+)#") // latitude + .expression("([NS])#") + .number("(ddd)(dd.d+)#") // longitude + .expression("([EW])#") + .number("(d+.d+)#") // speed + .number("(d+.d+)#") // course + .number("(d+)#") // satellites + .number("(d+.d+)#") // hdop + .number("(d+)#") // gsm + .expression("([012])#") // power mode + .number("(d+)#") // battery + .number("(d+)#") // adc 1 + .number("(d+)#") // adc 2 + .number("d+.d+#") // day distance + .number("(d+.d+)#") // odometer + .expression("([01]+)") // digital inputs + .text("*") + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(parser.next().equals("A")); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY)); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_HDOP, parser.nextDouble()); + position.set(Position.KEY_RSSI, parser.nextDouble()); + position.set(Position.KEY_CHARGE, parser.nextInt(0) == 2); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt(0)); + + position.set(Position.PREFIX_ADC + 1, parser.nextInt(0)); + position.set(Position.PREFIX_ADC + 2, parser.nextInt(0)); + + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + position.set(Position.KEY_INPUT, parser.next()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/DmtHttpProtocol.java b/src/main/java/org/traccar/protocol/DmtHttpProtocol.java new file mode 100644 index 000000000..34568128f --- /dev/null +++ b/src/main/java/org/traccar/protocol/DmtHttpProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class DmtHttpProtocol extends BaseProtocol { + + public DmtHttpProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(65535)); + pipeline.addLast(new DmtHttpProtocolDecoder(DmtHttpProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/DmtHttpProtocolDecoder.java b/src/main/java/org/traccar/protocol/DmtHttpProtocolDecoder.java new file mode 100644 index 000000000..987361baf --- /dev/null +++ b/src/main/java/org/traccar/protocol/DmtHttpProtocolDecoder.java @@ -0,0 +1,133 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import java.io.StringReader; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.LinkedList; +import java.util.List; +import java.util.TimeZone; + +public class DmtHttpProtocolDecoder extends BaseHttpProtocolDecoder { + + public DmtHttpProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + JsonObject root = Json.createReader( + new StringReader(request.content().toString(StandardCharsets.US_ASCII))).readObject(); + + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, root.getString("IMEI")); + if (deviceSession == null) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + + List<Position> positions = new LinkedList<>(); + + JsonArray records = root.getJsonArray("Records"); + + for (int i = 0; i < records.size(); i++) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + JsonObject record = records.getJsonObject(i); + + position.set(Position.KEY_INDEX, record.getInt("SeqNo")); + position.set(Position.KEY_EVENT, record.getInt("Reason")); + + position.setDeviceTime(dateFormat.parse(record.getString("DateUTC"))); + + JsonArray fields = record.getJsonArray("Fields"); + + for (int j = 0; j < fields.size(); j++) { + JsonObject field = fields.getJsonObject(j); + switch (field.getInt("FType")) { + case 0: + position.setFixTime(dateFormat.parse(field.getString("GpsUTC"))); + position.setLatitude(field.getJsonNumber("Lat").doubleValue()); + position.setLongitude(field.getJsonNumber("Long").doubleValue()); + position.setAltitude(field.getInt("Alt")); + position.setSpeed(UnitsConverter.knotsFromCps(field.getInt("Spd"))); + position.setCourse(field.getInt("Head")); + position.setAccuracy(field.getInt("PosAcc")); + position.setValid(field.getInt("GpsStat") > 0); + break; + case 2: + int input = field.getInt("DIn"); + int output = field.getInt("DOut"); + + position.set(Position.KEY_IGNITION, BitUtil.check(input, 0)); + + position.set(Position.KEY_INPUT, input); + position.set(Position.KEY_OUTPUT, output); + position.set(Position.KEY_STATUS, field.getInt("DevStat")); + break; + case 6: + JsonObject adc = field.getJsonObject("AnalogueData"); + if (adc.containsKey("1")) { + position.set(Position.KEY_BATTERY, adc.getInt("1") * 0.001); + } + if (adc.containsKey("2")) { + position.set(Position.KEY_POWER, adc.getInt("2") * 0.01); + } + if (adc.containsKey("3")) { + position.set(Position.KEY_DEVICE_TEMP, adc.getInt("3") * 0.01); + } + if (adc.containsKey("4")) { + position.set(Position.KEY_RSSI, adc.getInt("4")); + } + if (adc.containsKey("5")) { + position.set("solarPower", adc.getInt("5") * 0.001); + } + break; + default: + break; + } + } + + positions.add(position); + } + + sendResponse(channel, HttpResponseStatus.OK); + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/DmtProtocol.java b/src/main/java/org/traccar/protocol/DmtProtocol.java new file mode 100644 index 000000000..78a5243c0 --- /dev/null +++ b/src/main/java/org/traccar/protocol/DmtProtocol.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; +public class DmtProtocol extends BaseProtocol { + + public DmtProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 3, 2, 0, 0, true)); + pipeline.addLast(new DmtProtocolDecoder(DmtProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java b/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java new file mode 100644 index 000000000..58a5a88e3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/DmtProtocolDecoder.java @@ -0,0 +1,300 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class DmtProtocolDecoder extends BaseProtocolDecoder { + + public DmtProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_HELLO = 0x00; + public static final int MSG_HELLO_RESPONSE = 0x01; + public static final int MSG_DATA_RECORD = 0x04; + public static final int MSG_COMMIT = 0x05; + public static final int MSG_COMMIT_RESPONSE = 0x06; + public static final int MSG_DATA_RECORD_64 = 0x10; + + public static final int MSG_CANNED_REQUEST_1 = 0x14; + public static final int MSG_CANNED_RESPONSE_1 = 0x15; + public static final int MSG_CANNED_REQUEST_2 = 0x22; + public static final int MSG_CANNED_RESPONSE_2 = 0x23; + + private void sendResponse(Channel channel, int type, ByteBuf content) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(0x02); response.writeByte(0x55); // header + response.writeByte(type); + response.writeShortLE(content != null ? content.readableBytes() : 0); + if (content != null) { + response.writeBytes(content); + content.release(); + } + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + private List<Position> decodeFixed64(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + while (buf.readableBytes() >= 64) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readByte(); // type + + position.set(Position.KEY_INDEX, buf.readUnsignedIntLE()); + + long time = buf.readUnsignedIntLE(); + position.setTime(new DateBuilder() + .setYear((int) (2000 + (time & 0x3F))) + .setMonth((int) (time >> 6) & 0xF) + .setDay((int) (time >> 10) & 0x1F) + .setHour((int) (time >> 15) & 0x1F) + .setMinute((int) (time >> 20) & 0x3F) + .setSecond((int) (time >> 26) & 0x3F) + .getDate()); + + position.setLongitude(buf.readIntLE() * 0.0000001); + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setSpeed(UnitsConverter.knotsFromCps(buf.readUnsignedShortLE())); + position.setCourse(buf.readUnsignedByte() * 2); + position.setAltitude(buf.readShortLE()); + + buf.readUnsignedShortLE(); // position accuracy + buf.readUnsignedByte(); // speed accuracy + + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + + position.setValid(BitUtil.check(buf.readByte(), 0)); + + position.set(Position.KEY_INPUT, buf.readUnsignedIntLE()); + position.set(Position.KEY_OUTPUT, buf.readUnsignedShortLE()); + + for (int i = 1; i <= 5; i++) { + position.set(Position.PREFIX_ADC + i, buf.readShortLE()); + } + + position.set(Position.KEY_DEVICE_TEMP, buf.readByte()); + + buf.readShortLE(); // accelerometer x + buf.readShortLE(); // accelerometer y + buf.readShortLE(); // accelerometer z + + buf.skipBytes(8); // device id + + position.set(Position.KEY_PDOP, buf.readUnsignedShortLE() * 0.01); + + buf.skipBytes(2); // reserved + + buf.readUnsignedShortLE(); // checksum + + positions.add(position); + } + + return positions; + } + + private List<Position> decodeStandard(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + while (buf.isReadable()) { + int recordEnd = buf.readerIndex() + buf.readUnsignedShortLE(); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_INDEX, buf.readUnsignedIntLE()); + + position.setDeviceTime(new Date(1356998400000L + buf.readUnsignedIntLE() * 1000)); // since 1 Jan 2013 + + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + + while (buf.readerIndex() < recordEnd) { + + int fieldId = buf.readUnsignedByte(); + int fieldLength = buf.readUnsignedByte(); + int fieldEnd = buf.readerIndex() + (fieldLength == 255 ? buf.readUnsignedShortLE() : fieldLength); + + if (fieldId == 0) { + + position.setFixTime(new Date(1356998400000L + buf.readUnsignedIntLE() * 1000)); + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setLongitude(buf.readIntLE() * 0.0000001); + position.setAltitude(buf.readShortLE()); + position.setSpeed(UnitsConverter.knotsFromCps(buf.readUnsignedShortLE())); + + buf.readUnsignedByte(); // speed accuracy + + position.setCourse(buf.readUnsignedByte() * 2); + + position.set(Position.KEY_PDOP, buf.readUnsignedByte() * 0.1); + + position.setAccuracy(buf.readUnsignedByte()); + position.setValid(buf.readUnsignedByte() != 0); + + } else if (fieldId == 2) { + + int input = buf.readIntLE(); + int output = buf.readUnsignedShortLE(); + int status = buf.readUnsignedShortLE(); + + position.set(Position.KEY_IGNITION, BitUtil.check(input, 0)); + + position.set(Position.KEY_INPUT, input); + position.set(Position.KEY_OUTPUT, output); + position.set(Position.KEY_STATUS, status); + + } else if (fieldId == 6) { + + while (buf.readerIndex() < fieldEnd) { + switch (buf.readUnsignedByte()) { + case 1: + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); + break; + case 2: + position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.01); + break; + case 3: + position.set(Position.KEY_DEVICE_TEMP, buf.readShortLE() * 0.01); + break; + case 4: + position.set(Position.KEY_RSSI, buf.readUnsignedShortLE()); + break; + case 5: + position.set("solarPower", buf.readUnsignedShortLE() * 0.001); + break; + default: + buf.readUnsignedShortLE(); // other + break; + } + } + + } else if (fieldId == 26) { + + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedIntLE()); + position.set("tripHours", buf.readUnsignedIntLE() * 1000); + + } else if (fieldId == 27) { + + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + position.set(Position.KEY_HOURS, buf.readUnsignedIntLE() * 1000); + + } + + buf.readerIndex(fieldEnd); + + } + + if (position.getFixTime() == null) { + getLastLocation(position, position.getDeviceTime()); + } + + positions.add(position); + } + + return positions; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + + int type = buf.readUnsignedByte(); + int length = buf.readUnsignedShortLE(); + + if (type == MSG_HELLO) { + + buf.readUnsignedIntLE(); // device serial number + + DeviceSession deviceSession = getDeviceSession( + channel, remoteAddress, buf.readSlice(15).toString(StandardCharsets.US_ASCII)); + + ByteBuf response = Unpooled.buffer(); + if (length == 51) { + response.writeByte(0); // reserved + response.writeIntLE(0); // reserved + } else { + response.writeIntLE((int) ((System.currentTimeMillis() - 1356998400000L) / 1000)); + response.writeIntLE(deviceSession != null ? 0 : 1); // flags + } + + sendResponse(channel, MSG_HELLO_RESPONSE, response); + + } else if (type == MSG_COMMIT) { + + ByteBuf response = Unpooled.buffer(0); + response.writeByte(1); // flags (success) + sendResponse(channel, MSG_COMMIT_RESPONSE, response); + + } else if (type == MSG_CANNED_REQUEST_1) { + + ByteBuf response = Unpooled.buffer(0); + response.writeBytes(new byte[12]); + sendResponse(channel, MSG_CANNED_RESPONSE_1, response); + + } else if (type == MSG_CANNED_REQUEST_2) { + + sendResponse(channel, MSG_CANNED_RESPONSE_2, null); + + } else if (type == MSG_DATA_RECORD_64) { + + return decodeFixed64(channel, remoteAddress, buf); + + } else if (type == MSG_DATA_RECORD) { + + return decodeStandard(channel, remoteAddress, buf); + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/DwayProtocol.java b/src/main/java/org/traccar/protocol/DwayProtocol.java new file mode 100644 index 000000000..05fd8b6e7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/DwayProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class DwayProtocol extends BaseProtocol { + + public DwayProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new DwayProtocolDecoder(DwayProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/DwayProtocolDecoder.java b/src/main/java/org/traccar/protocol/DwayProtocolDecoder.java new file mode 100644 index 000000000..9b02c898e --- /dev/null +++ b/src/main/java/org/traccar/protocol/DwayProtocolDecoder.java @@ -0,0 +1,103 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class DwayProtocolDecoder extends BaseProtocolDecoder { + + public DwayProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("AA55,") + .number("d+,") // index + .number("(d+),") // imei + .number("d+,") // type + .number("(dd)(dd)(dd),") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(-?d+),") // altitude + .number(" ?(d+.d+),") // speed + .number("(d+),") // course + .number("([01]{4}),") // input + .number("([01]{4}),") // output + .number("([01]+),") // flags + .number("(d+),") // battery + .number("(d+),") // adc1 + .number("(d+),") // adc2 + .number("(d+)") // driver + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + if (sentence.equals("AA55,HB")) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("55AA,HB,OK\r\n", remoteAddress)); + } + return null; + } + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + position.setTime(parser.nextDateTime()); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setAltitude(parser.nextDouble(0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_INPUT, parser.nextBinInt()); + position.set(Position.KEY_OUTPUT, parser.nextBinInt()); + + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001); + position.set(Position.PREFIX_ADC + 1, parser.nextInt() * 0.001); + position.set(Position.PREFIX_ADC + 2, parser.nextInt() * 0.001); + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/EasyTrackProtocol.java b/src/main/java/org/traccar/protocol/EasyTrackProtocol.java new file mode 100644 index 000000000..74c636d06 --- /dev/null +++ b/src/main/java/org/traccar/protocol/EasyTrackProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class EasyTrackProtocol extends BaseProtocol { + + public EasyTrackProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "#", "\r\n")); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new EasyTrackProtocolDecoder(EasyTrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java new file mode 100644 index 000000000..2ddb24f5c --- /dev/null +++ b/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java @@ -0,0 +1,138 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class EasyTrackProtocolDecoder extends BaseProtocolDecoder { + + public EasyTrackProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("*").expression("..,") // manufacturer + .number("(d+),") // imei + .expression("([^,]{2}),") // command + .expression("([AV]),") // validity + .number("(xx)(xx)(xx),") // date (yymmdd) + .number("(xx)(xx)(xx),") // time (hhmmss) + .number("(x)(x{7}),") // latitude + .number("(x)(x{7}),") // longitude + .number("(x{4}),") // speed + .number("(x{4}),") // course + .number("(x{8}),") // status + .number("(x+),") // signal + .number("(d+),") // power + .number("(x{4}),") // oil + .number("(x+),?") // odometer + .number("(d+)?") // altitude + .any() + .compile(); + + private String decodeAlarm(long status) { + if ((status & 0x02000000) != 0) { + return Position.ALARM_GEOFENCE_ENTER; + } + if ((status & 0x04000000) != 0) { + return Position.ALARM_GEOFENCE_EXIT; + } + if ((status & 0x08000000) != 0) { + return Position.ALARM_LOW_BATTERY; + } + if ((status & 0x20000000) != 0) { + return Position.ALARM_VIBRATION; + } + if ((status & 0x80000000) != 0) { + return Position.ALARM_OVERSPEED; + } + if ((status & 0x00010000) != 0) { + return Position.ALARM_SOS; + } + if ((status & 0x00040000) != 0) { + return Position.ALARM_POWER_CUT; + } + return null; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + 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_COMMAND, parser.next()); + + position.setValid(parser.next().equals("A")); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(parser.nextHexInt(0), parser.nextHexInt(0), parser.nextHexInt(0)) + .setTime(parser.nextHexInt(0), parser.nextHexInt(0), parser.nextHexInt(0)); + position.setTime(dateBuilder.getDate()); + + if (BitUtil.check(parser.nextHexInt(0), 3)) { + position.setLatitude(-parser.nextHexInt(0) / 600000.0); + } else { + position.setLatitude(parser.nextHexInt(0) / 600000.0); + } + + if (BitUtil.check(parser.nextHexInt(0), 3)) { + position.setLongitude(-parser.nextHexInt(0) / 600000.0); + } else { + position.setLongitude(parser.nextHexInt(0) / 600000.0); + } + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextHexInt(0) / 100.0)); + position.setCourse(parser.nextHexInt(0) / 100.0); + + long status = parser.nextHexLong(); + position.set(Position.KEY_STATUS, status); + position.set(Position.KEY_ALARM, decodeAlarm(status)); + + position.set("signal", parser.next()); + position.set(Position.KEY_POWER, parser.nextDouble(0)); + position.set("oil", parser.nextHexInt(0)); + position.set(Position.KEY_ODOMETER, parser.nextHexInt(0) * 100); + + position.setAltitude(parser.nextDouble(0)); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/EelinkProtocol.java b/src/main/java/org/traccar/protocol/EelinkProtocol.java new file mode 100644 index 000000000..8a055d643 --- /dev/null +++ b/src/main/java/org/traccar/protocol/EelinkProtocol.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class EelinkProtocol extends BaseProtocol { + + public EelinkProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_POSITION_SINGLE, + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME, + Command.TYPE_REBOOT_DEVICE); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 2)); + pipeline.addLast(new EelinkProtocolEncoder(EelinkProtocol.this, false)); + pipeline.addLast(new EelinkProtocolDecoder(EelinkProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new EelinkProtocolEncoder(EelinkProtocol.this, true)); + pipeline.addLast(new EelinkProtocolDecoder(EelinkProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java new file mode 100644 index 000000000..41d76f37f --- /dev/null +++ b/src/main/java/org/traccar/protocol/EelinkProtocolDecoder.java @@ -0,0 +1,442 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import 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; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.regex.Pattern; + +public class EelinkProtocolDecoder extends BaseProtocolDecoder { + + public EelinkProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN = 0x01; + public static final int MSG_GPS = 0x02; + public static final int MSG_HEARTBEAT = 0x03; + public static final int MSG_ALARM = 0x04; + public static final int MSG_STATE = 0x05; + public static final int MSG_SMS = 0x06; + public static final int MSG_OBD = 0x07; + public static final int MSG_DOWNLINK = 0x80; + public static final int MSG_DATA = 0x81; + + public static final int MSG_NORMAL = 0x12; + public static final int MSG_WARNING = 0x14; + public static final int MSG_REPORT = 0x15; + public static final int MSG_COMMAND = 0x16; + public static final int MSG_OBD_DATA = 0x17; + public static final int MSG_OBD_BODY = 0x18; + public static final int MSG_OBD_CODE = 0x19; + public static final int MSG_CAMERA_INFO = 0x1E; + public static final int MSG_CAMERA_DATA = 0x1F; + + private String decodeAlarm(Short value) { + switch (value) { + case 0x01: + return Position.ALARM_POWER_OFF; + case 0x02: + return Position.ALARM_SOS; + case 0x03: + return Position.ALARM_LOW_BATTERY; + case 0x04: + return Position.ALARM_VIBRATION; + case 0x08: + case 0x09: + return Position.ALARM_GPS_ANTENNA_CUT; + case 0x81: + return Position.ALARM_LOW_SPEED; + case 0x82: + return Position.ALARM_OVERSPEED; + case 0x83: + return Position.ALARM_GEOFENCE_ENTER; + case 0x84: + return Position.ALARM_GEOFENCE_EXIT; + case 0x85: + return Position.ALARM_ACCIDENT; + case 0x86: + return Position.ALARM_FALL_DOWN; + default: + return null; + } + } + + private void decodeStatus(Position position, int status) { + if (BitUtil.check(status, 1)) { + position.set(Position.KEY_IGNITION, BitUtil.check(status, 2)); + } + if (BitUtil.check(status, 3)) { + position.set(Position.KEY_ARMED, BitUtil.check(status, 4)); + } + if (BitUtil.check(status, 5)) { + position.set(Position.KEY_BLOCKED, !BitUtil.check(status, 6)); + } + if (BitUtil.check(status, 7)) { + position.set(Position.KEY_CHARGE, BitUtil.check(status, 8)); + } + position.set(Position.KEY_STATUS, status); + } + + private Position decodeOld(DeviceSession deviceSession, ByteBuf buf, int type, int index) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_INDEX, index); + + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + position.setLatitude(buf.readInt() / 1800000.0); + position.setLongitude(buf.readInt() / 1800000.0); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.setCourse(buf.readUnsignedShort()); + + position.setNetwork(new Network(CellTower.from( + buf.readUnsignedShort(), buf.readUnsignedShort(), buf.readUnsignedShort(), buf.readUnsignedMedium()))); + + position.setValid((buf.readUnsignedByte() & 0x01) != 0); + + if (type == MSG_GPS) { + + if (buf.readableBytes() >= 2) { + decodeStatus(position, buf.readUnsignedShort()); + } + + if (buf.readableBytes() >= 2 * 4) { + + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.001); + + position.set(Position.KEY_RSSI, buf.readUnsignedShort()); + + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShort()); + + } + + } else if (type == MSG_ALARM) { + + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + + } else if (type == MSG_STATE) { + + int statusType = buf.readUnsignedByte(); + + position.set(Position.KEY_EVENT, statusType); + + if (statusType == 0x01 || statusType == 0x02 || statusType == 0x03) { + buf.readUnsignedInt(); // device time + if (buf.readableBytes() >= 2) { + decodeStatus(position, buf.readUnsignedShort()); + } + } + + } + + return position; + } + + private Position decodeNew(DeviceSession deviceSession, ByteBuf buf, int type, int index) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_INDEX, index); + + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + + int flags = buf.readUnsignedByte(); + + if (BitUtil.check(flags, 0)) { + position.setLatitude(buf.readInt() / 1800000.0); + position.setLongitude(buf.readInt() / 1800000.0); + position.setAltitude(buf.readShort()); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort())); + position.setCourse(buf.readUnsignedShort()); + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + } else { + getLastLocation(position, position.getDeviceTime()); + } + + if (BitUtil.check(flags, 1)) { + position.setNetwork(new Network(CellTower.from( + buf.readUnsignedShort(), buf.readUnsignedShort(), + buf.readUnsignedShort(), buf.readUnsignedInt(), buf.readUnsignedByte()))); + } + + if (BitUtil.check(flags, 2)) { + buf.skipBytes(7); // bsid1 + } + + if (BitUtil.check(flags, 3)) { + buf.skipBytes(7); // bsid2 + } + + if (BitUtil.check(flags, 4)) { + buf.skipBytes(7); // bss0 + } + + if (BitUtil.check(flags, 5)) { + buf.skipBytes(7); // bss1 + } + + if (BitUtil.check(flags, 6)) { + buf.skipBytes(7); // bss2 + } + + if (type == MSG_WARNING) { + + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + + } else if (type == MSG_REPORT) { + + buf.readUnsignedByte(); // report type + + } + + if (type == MSG_NORMAL || type == MSG_WARNING || type == MSG_REPORT) { + + int status = buf.readUnsignedShort(); + position.setValid(BitUtil.check(status, 0)); + if (BitUtil.check(status, 1)) { + position.set(Position.KEY_IGNITION, BitUtil.check(status, 2)); + } + position.set(Position.KEY_STATUS, status); + + } + + if (type == MSG_NORMAL) { + + if (buf.readableBytes() >= 2) { + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.001); + } + + if (buf.readableBytes() >= 4) { + position.set(Position.PREFIX_ADC + 0, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort()); + } + + if (buf.readableBytes() >= 4) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + } + + if (buf.readableBytes() >= 4) { + buf.readUnsignedShort(); // gsm counter + buf.readUnsignedShort(); // gps counter + } + + if (buf.readableBytes() >= 4) { + position.set(Position.KEY_STEPS, buf.readUnsignedShort()); + buf.readUnsignedShort(); // walking time + } + + if (buf.readableBytes() >= 12) { + position.set(Position.PREFIX_TEMP + 1, buf.readUnsignedShort() / 256.0); + position.set("humidity", buf.readUnsignedShort() * 0.1); + position.set("illuminance", buf.readUnsignedInt() / 256.0); + position.set("co2", buf.readUnsignedInt()); + } + + if (buf.readableBytes() >= 2) { + position.set(Position.PREFIX_TEMP + 2, buf.readShort() / 16.0); + } + + } + + return position; + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("Lat:") + .number("([NS])(d+.d+)") // latitude + .any() + .text("Lon:") + .number("([EW])(d+.d+)") // longitude + .any() + .text("Course:") + .number("(d+.d+)") // course + .any() + .text("Speed:") + .number("(d+.d+)") // speed + .any() + .expression("Date ?Time:") + .number("(dddd)-(dd)-(dd) ") // date + .number("(dd):(dd):(dd)") // time + .compile(); + + private Position decodeResult(DeviceSession deviceSession, ByteBuf buf, int index) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_INDEX, index); + + buf.readUnsignedByte(); // type + buf.readUnsignedInt(); // uid + + String sentence = buf.toString(StandardCharsets.UTF_8); + + Parser parser = new Parser(PATTERN, sentence); + if (parser.matches()) { + + position.setValid(true); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setCourse(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setTime(parser.nextDateTime()); + + } else { + + getLastLocation(position, null); + + position.set(Position.KEY_RESULT, sentence); + + } + + return position; + } + + private Position decodeObd(DeviceSession deviceSession, ByteBuf buf) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, new Date(buf.readUnsignedInt() * 1000)); + + while (buf.readableBytes() > 0) { + int pid = buf.readUnsignedByte(); + int value = buf.readInt(); + switch (pid) { + case 0x89: + position.set(Position.KEY_FUEL_CONSUMPTION, value); + break; + case 0x8a: + position.set(Position.KEY_ODOMETER, value * 1000L); + break; + case 0x8b: + position.set(Position.KEY_FUEL_LEVEL, value / 10); + break; + default: + break; + } + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + String uniqueId = null; + DeviceSession deviceSession; + + if (buf.getByte(0) == 'E' && buf.getByte(1) == 'L') { + buf.skipBytes(2 + 2 + 2); // udp header + uniqueId = ByteBufUtil.hexDump(buf.readSlice(8)).substring(1); + deviceSession = getDeviceSession(channel, remoteAddress, uniqueId); + } else { + deviceSession = getDeviceSession(channel, remoteAddress); + } + + buf.skipBytes(2); // header + int type = buf.readUnsignedByte(); + buf.readShort(); // length + int index = buf.readUnsignedShort(); + + if (type != MSG_GPS && type != MSG_DATA) { + ByteBuf content = Unpooled.buffer(); + if (type == MSG_LOGIN) { + content.writeInt((int) (System.currentTimeMillis() / 1000)); + content.writeByte(1); // protocol version + content.writeByte(0); // action mask + } + ByteBuf response = EelinkProtocolEncoder.encodeContent( + channel instanceof DatagramChannel, uniqueId, type, index, content); + content.release(); + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + if (type == MSG_LOGIN) { + + if (deviceSession == null) { + getDeviceSession(channel, remoteAddress, ByteBufUtil.hexDump(buf.readSlice(8)).substring(1)); + } + + } else { + + if (deviceSession == null) { + return null; + } + + if (type == MSG_GPS || type == MSG_ALARM || type == MSG_STATE || type == MSG_SMS) { + + return decodeOld(deviceSession, buf, type, index); + + } else if (type >= MSG_NORMAL && type <= MSG_OBD_CODE) { + + return decodeNew(deviceSession, buf, type, index); + + } else if (type == MSG_HEARTBEAT && buf.readableBytes() >= 2 + || type == MSG_OBD && buf.readableBytes() == 4) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + decodeStatus(position, buf.readUnsignedShort()); + + return position; + + } else if (type == MSG_OBD) { + + return decodeObd(deviceSession, buf); + + } else if (type == MSG_DOWNLINK) { + + return decodeResult(deviceSession, buf, index); + + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/EelinkProtocolEncoder.java b/src/main/java/org/traccar/protocol/EelinkProtocolEncoder.java new file mode 100644 index 000000000..3673c35b3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/EelinkProtocolEncoder.java @@ -0,0 +1,109 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.helper.DataConverter; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +public class EelinkProtocolEncoder extends BaseProtocolEncoder { + + private boolean connectionless; + + public EelinkProtocolEncoder(Protocol protocol, boolean connectionless) { + super(protocol); + this.connectionless = connectionless; + } + + public static int checksum(ByteBuffer buf) { + int sum = 0; + while (buf.hasRemaining()) { + sum = (((sum << 1) | (sum >> 15)) + (buf.get() & 0xFF)) & 0xFFFF; + } + return sum; + } + + public static ByteBuf encodeContent( + boolean connectionless, String uniqueId, int type, int index, ByteBuf content) { + + ByteBuf buf = Unpooled.buffer(); + + if (connectionless) { + buf.writeBytes(DataConverter.parseHex('0' + uniqueId)); + } + + buf.writeByte(0x67); + buf.writeByte(0x67); + buf.writeByte(type); + buf.writeShort(2 + (content != null ? content.readableBytes() : 0)); // length + buf.writeShort(index); + + if (content != null) { + buf.writeBytes(content); + } + + ByteBuf result = Unpooled.buffer(); + + if (connectionless) { + result.writeByte('E'); + result.writeByte('L'); + result.writeShort(2 + buf.readableBytes()); // length + result.writeShort(checksum(buf.nioBuffer())); + } + + result.writeBytes(buf); + buf.release(); + + return result; + } + + private ByteBuf encodeContent(long deviceId, String content) { + + ByteBuf buf = Unpooled.buffer(); + + buf.writeByte(0x01); // command + buf.writeInt(0); // server id + buf.writeBytes(content.getBytes(StandardCharsets.UTF_8)); + + return encodeContent(connectionless, getUniqueId(deviceId), EelinkProtocolDecoder.MSG_DOWNLINK, 0, buf); + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return encodeContent(command.getDeviceId(), command.getString(Command.KEY_DATA)); + case Command.TYPE_POSITION_SINGLE: + return encodeContent(command.getDeviceId(), "WHERE#"); + case Command.TYPE_ENGINE_STOP: + return encodeContent(command.getDeviceId(), "RELAY,1#"); + case Command.TYPE_ENGINE_RESUME: + return encodeContent(command.getDeviceId(), "RELAY,0#"); + case Command.TYPE_REBOOT_DEVICE: + return encodeContent(command.getDeviceId(), "RESET#"); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/EgtsFrameDecoder.java b/src/main/java/org/traccar/protocol/EgtsFrameDecoder.java new file mode 100644 index 000000000..84f1f11a7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/EgtsFrameDecoder.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class EgtsFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 10) { + return null; + } + + int headerLength = buf.getUnsignedByte(buf.readerIndex() + 3); + int frameLength = buf.getUnsignedShortLE(buf.readerIndex() + 5); + + int length = headerLength + frameLength + (frameLength > 0 ? 2 : 0); + + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/EgtsProtocol.java b/src/main/java/org/traccar/protocol/EgtsProtocol.java new file mode 100644 index 000000000..5d4638f37 --- /dev/null +++ b/src/main/java/org/traccar/protocol/EgtsProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class EgtsProtocol extends BaseProtocol { + + public EgtsProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new EgtsFrameDecoder()); + pipeline.addLast(new EgtsProtocolDecoder(EgtsProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/EgtsProtocolDecoder.java b/src/main/java/org/traccar/protocol/EgtsProtocolDecoder.java new file mode 100644 index 000000000..9f0baf6b2 --- /dev/null +++ b/src/main/java/org/traccar/protocol/EgtsProtocolDecoder.java @@ -0,0 +1,283 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class EgtsProtocolDecoder extends BaseProtocolDecoder { + + public EgtsProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private boolean useObjectIdAsDeviceId = true; + + public static final int PT_RESPONSE = 0; + public static final int PT_APPDATA = 1; + public static final int PT_SIGNED_APPDATA = 2; + + public static final int SERVICE_AUTH = 1; + public static final int SERVICE_TELEDATA = 2; + public static final int SERVICE_COMMANDS = 4; + public static final int SERVICE_FIRMWARE = 9; + public static final int SERVICE_ECALL = 10; + + public static final int MSG_RECORD_RESPONSE = 0; + public static final int MSG_TERM_IDENTITY = 1; + public static final int MSG_MODULE_DATA = 2; + public static final int MSG_VEHICLE_DATA = 3; + public static final int MSG_AUTH_PARAMS = 4; + public static final int MSG_AUTH_INFO = 5; + public static final int MSG_SERVICE_INFO = 6; + public static final int MSG_RESULT_CODE = 7; + public static final int MSG_POS_DATA = 16; + public static final int MSG_EXT_POS_DATA = 17; + public static final int MSG_AD_SENSORS_DATA = 18; + public static final int MSG_COUNTERS_DATA = 19; + public static final int MSG_STATE_DATA = 20; + public static final int MSG_LOOPIN_DATA = 22; + public static final int MSG_ABS_DIG_SENS_DATA = 23; + public static final int MSG_ABS_AN_SENS_DATA = 24; + public static final int MSG_ABS_CNTR_DATA = 25; + public static final int MSG_ABS_LOOPIN_DATA = 26; + public static final int MSG_LIQUID_LEVEL_SENSOR = 27; + public static final int MSG_PASSENGERS_COUNTERS = 28; + + private int packetId; + + private void sendResponse( + Channel channel, int packetType, int index, int serviceType, int type, ByteBuf content) { + if (channel != null) { + + ByteBuf data = Unpooled.buffer(); + data.writeByte(type); + data.writeShortLE(content.readableBytes()); + data.writeBytes(content); + content.release(); + + ByteBuf record = Unpooled.buffer(); + if (packetType == PT_RESPONSE) { + record.writeShortLE(index); + record.writeByte(0); // success + } + record.writeShortLE(data.readableBytes()); + record.writeShortLE(0); + record.writeByte(0); // flags (possibly 1 << 6) + record.writeByte(serviceType); + record.writeByte(serviceType); + record.writeBytes(data); + data.release(); + int recordChecksum = Checksum.crc16(Checksum.CRC16_CCITT_FALSE, record.nioBuffer()); + + ByteBuf response = Unpooled.buffer(); + response.writeByte(1); // protocol version + response.writeByte(0); // security key id + response.writeByte(0); // flags + response.writeByte(5 + 2 + 2 + 2); // header length + response.writeByte(0); // encoding + response.writeShortLE(record.readableBytes()); + response.writeShortLE(packetId++); + response.writeByte(packetType); + response.writeByte(Checksum.crc8(Checksum.CRC8_EGTS, response.nioBuffer())); + response.writeBytes(record); + record.release(); + response.writeShortLE(recordChecksum); + + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + List<Position> positions = new LinkedList<>(); + + short headerLength = buf.getUnsignedByte(buf.readerIndex() + 3); + int index = buf.getUnsignedShort(buf.readerIndex() + 5 + 2); + short packetType = buf.getUnsignedByte(buf.readerIndex() + 5 + 2 + 2); + buf.skipBytes(headerLength); + + if (packetType == PT_RESPONSE) { + return null; + } + + long objectId = 0L; + while (buf.readableBytes() > 2) { + + int length = buf.readUnsignedShortLE(); + int recordIndex = buf.readUnsignedShortLE(); + int recordFlags = buf.readUnsignedByte(); + + if (BitUtil.check(recordFlags, 0)) { + objectId = buf.readUnsignedIntLE(); + } + + if (BitUtil.check(recordFlags, 1)) { + buf.readUnsignedIntLE(); // event id + } + if (BitUtil.check(recordFlags, 2)) { + buf.readUnsignedIntLE(); // time + } + + int serviceType = buf.readUnsignedByte(); + buf.readUnsignedByte(); // recipient service type + + int recordEnd = buf.readerIndex() + length; + + Position position = new Position(getProtocolName()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession != null) { + position.setDeviceId(deviceSession.getDeviceId()); + } + + ByteBuf response = Unpooled.buffer(); + response.writeShortLE(recordIndex); + response.writeByte(0); // success + sendResponse(channel, PT_RESPONSE, index, serviceType, MSG_RECORD_RESPONSE, response); + + while (buf.readerIndex() < recordEnd) { + int type = buf.readUnsignedByte(); + int end = buf.readUnsignedShortLE() + buf.readerIndex(); + + if (type == MSG_TERM_IDENTITY) { + useObjectIdAsDeviceId = false; + + buf.readUnsignedIntLE(); // object id + int flags = buf.readUnsignedByte(); + + if (BitUtil.check(flags, 0)) { + buf.readUnsignedShortLE(); // home dispatcher identifier + } + if (BitUtil.check(flags, 1)) { + getDeviceSession( + channel, remoteAddress, buf.readSlice(15).toString(StandardCharsets.US_ASCII).trim()); + } + if (BitUtil.check(flags, 2)) { + getDeviceSession( + channel, remoteAddress, buf.readSlice(16).toString(StandardCharsets.US_ASCII).trim()); + } + if (BitUtil.check(flags, 3)) { + buf.skipBytes(3); // language identifier + } + if (BitUtil.check(flags, 5)) { + buf.skipBytes(3); // network identifier + } + if (BitUtil.check(flags, 6)) { + buf.readUnsignedShortLE(); // buffer size + } + if (BitUtil.check(flags, 7)) { + getDeviceSession( + channel, remoteAddress, buf.readSlice(15).toString(StandardCharsets.US_ASCII).trim()); + } + + response = Unpooled.buffer(); + response.writeByte(0); // success + sendResponse(channel, PT_APPDATA, 0, serviceType, MSG_RESULT_CODE, response); + + } else if (type == MSG_POS_DATA) { + + position.setTime(new Date((buf.readUnsignedIntLE() + 1262304000) * 1000)); // since 2010-01-01 + position.setLatitude(buf.readUnsignedIntLE() * 90.0 / 0xFFFFFFFFL); + position.setLongitude(buf.readUnsignedIntLE() * 180.0 / 0xFFFFFFFFL); + + int flags = buf.readUnsignedByte(); + position.setValid(BitUtil.check(flags, 0)); + if (BitUtil.check(flags, 5)) { + position.setLatitude(-position.getLatitude()); + } + if (BitUtil.check(flags, 6)) { + position.setLongitude(-position.getLongitude()); + } + + int speed = buf.readUnsignedShortLE(); + position.setSpeed(UnitsConverter.knotsFromKph(BitUtil.to(speed, 14) * 0.1)); + position.setCourse(buf.readUnsignedByte() + (BitUtil.check(speed, 15) ? 0x100 : 0)); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedMediumLE() * 100); + position.set(Position.KEY_INPUT, buf.readUnsignedByte()); + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + + if (BitUtil.check(flags, 7)) { + position.setAltitude(buf.readMediumLE()); + } + + } else if (type == MSG_EXT_POS_DATA) { + + int flags = buf.readUnsignedByte(); + + if (BitUtil.check(flags, 0)) { + position.set(Position.KEY_VDOP, buf.readUnsignedShortLE()); + } + if (BitUtil.check(flags, 1)) { + position.set(Position.KEY_HDOP, buf.readUnsignedShortLE()); + } + if (BitUtil.check(flags, 2)) { + position.set(Position.KEY_PDOP, buf.readUnsignedShortLE()); + } + if (BitUtil.check(flags, 3)) { + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + } + + } else if (type == MSG_AD_SENSORS_DATA) { + + buf.readUnsignedByte(); // inputs flags + + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + + buf.readUnsignedByte(); // adc flags + + } + + buf.readerIndex(end); + } + + if (serviceType == SERVICE_TELEDATA && position.getValid()) { + if (useObjectIdAsDeviceId && objectId != 0L) { + deviceSession = getDeviceSession(channel, remoteAddress, true, String.valueOf(objectId)); + if (deviceSession != null) { + position.setDeviceId(deviceSession.getDeviceId()); + } + } + if (deviceSession != null) { + positions.add(position); + } + } + } + + return positions.isEmpty() ? null : positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/EnforaProtocol.java b/src/main/java/org/traccar/protocol/EnforaProtocol.java new file mode 100644 index 000000000..e462ab322 --- /dev/null +++ b/src/main/java/org/traccar/protocol/EnforaProtocol.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class EnforaProtocol extends BaseProtocol { + + public EnforaProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, -2, 2)); + pipeline.addLast(new EnforaProtocolEncoder(EnforaProtocol.this)); + pipeline.addLast(new EnforaProtocolDecoder(EnforaProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new EnforaProtocolEncoder(EnforaProtocol.this)); + pipeline.addLast(new EnforaProtocolDecoder(EnforaProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/EnforaProtocolDecoder.java b/src/main/java/org/traccar/protocol/EnforaProtocolDecoder.java new file mode 100644 index 000000000..bfa7a116b --- /dev/null +++ b/src/main/java/org/traccar/protocol/EnforaProtocolDecoder.java @@ -0,0 +1,115 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.BufferUtil; +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 EnforaProtocolDecoder extends BaseProtocolDecoder { + + public EnforaProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("GPRMC,") + .number("(dd)(dd)(dd).?d*,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.d+)?,") // speed + .number("(d+.d+)?,") // course + .number("(dd)(dd)(dd),") // date (ddmmyy) + .any() + .compile(); + + public static final int IMEI_LENGTH = 15; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + // Find IMEI number + int index = -1; + for (int i = buf.readerIndex(); i < buf.writerIndex() - IMEI_LENGTH; i++) { + index = i; + for (int j = i; j < i + IMEI_LENGTH; j++) { + if (!Character.isDigit((char) buf.getByte(j))) { + index = -1; + break; + } + } + if (index > 0) { + break; + } + } + if (index == -1) { + return null; + } + + String imei = buf.toString(index, IMEI_LENGTH, StandardCharsets.US_ASCII); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + // Find NMEA sentence + int start = BufferUtil.indexOf("GPRMC", buf); + if (start == -1) { + return null; + } + + String sentence = buf.toString(start, buf.readableBytes() - start, StandardCharsets.US_ASCII); + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/EnforaProtocolEncoder.java b/src/main/java/org/traccar/protocol/EnforaProtocolEncoder.java new file mode 100644 index 000000000..8cc24dc0f --- /dev/null +++ b/src/main/java/org/traccar/protocol/EnforaProtocolEncoder.java @@ -0,0 +1,60 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Jose Castellanos + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.nio.charset.StandardCharsets; + +public class EnforaProtocolEncoder extends StringProtocolEncoder { + + public EnforaProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf encodeContent(String content) { + + ByteBuf buf = Unpooled.buffer(); + + buf.writeShort(content.length() + 6); + buf.writeShort(0); // index + buf.writeByte(0x04); // command type + buf.writeByte(0); // optional header + buf.writeBytes(content.getBytes(StandardCharsets.US_ASCII)); + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return encodeContent(command.getString(Command.KEY_DATA)); + case Command.TYPE_ENGINE_STOP: + return encodeContent("AT$IOGP3=1"); + case Command.TYPE_ENGINE_RESUME: + return encodeContent("AT$IOGP3=0"); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/EsealProtocol.java b/src/main/java/org/traccar/protocol/EsealProtocol.java new file mode 100644 index 000000000..fc1d342e1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/EsealProtocol.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class EsealProtocol extends BaseProtocol { + + public EsealProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_ALARM_ARM, + Command.TYPE_ALARM_DISARM); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new EsealProtocolEncoder(EsealProtocol.this)); + pipeline.addLast(new EsealProtocolDecoder(EsealProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/EsealProtocolDecoder.java b/src/main/java/org/traccar/protocol/EsealProtocolDecoder.java new file mode 100644 index 000000000..7a1fd7022 --- /dev/null +++ b/src/main/java/org/traccar/protocol/EsealProtocolDecoder.java @@ -0,0 +1,158 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class EsealProtocolDecoder extends BaseProtocolDecoder { + + private String config; + + public EsealProtocolDecoder(Protocol protocol) { + super(protocol); + config = Context.getConfig().getString(getProtocolName() + ".config"); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("##S,") + .expression("[^,]+,") // device type + .number("(d+),") // device id + .number("d+,") // customer id + .expression("[^,]+,") // firmware version + .expression("([^,]+),") // type + .number("(d+),") // index + .number("(dddd)-(dd)-(dd),") // date + .number("(dd):(dd):(dd),") // time + .number("d+,") // interval + .expression("([AV]),") // validity + .number("(d+.d+)([NS]) ") // latitude + .number("(d+.d+)([EW]),") // longitude + .number("(d+),") // course + .number("(d+),") // speed + .expression("([^,]+),") // door + .number("(d+.d+),") // acceleration + .expression("([^,]+),") // nfc + .number("(d+.d+),") // battery + .number("(-?d+),") // rssi + .text("E##") + .compile(); + + private void sendResponse(Channel channel, String prefix, String type, String payload) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage( + prefix + type + "," + payload + ",E##\r\n", channel.remoteAddress())); + } + } + + private String decodeAlarm(String type) { + switch (type) { + case "Event-Door": + return Position.ALARM_DOOR; + case "Event-Shock": + return Position.ALARM_SHOCK; + case "Event-Drop": + return Position.ALARM_FALL_DOWN; + case "Event-Lock": + return Position.ALARM_LOCK; + case "Event-RC-Unlock": + return Position.ALARM_UNLOCK; + default: + return null; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + String type = parser.next(); + String prefix = sentence.substring(0, sentence.indexOf(type)); + int index = parser.nextInt(); + + position.set(Position.KEY_INDEX, index); + position.set(Position.KEY_ALARM, decodeAlarm(type)); + + switch (type) { + case "Startup": + sendResponse(channel, prefix, type + " ACK", index + "," + config); + break; + case "Normal": + case "Button-Normal": + case "Termination": + case "Event-Door": + case "Event-Shock": + case "Event-Drop": + case "Event-Lock": + case "Event-RC-Unlock": + sendResponse(channel, prefix, type + " ACK", String.valueOf(index)); + break; + default: + break; + } + + position.setTime(parser.nextDateTime()); + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setCourse(parser.nextInt()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + + switch (parser.next()) { + case "Open": + position.set(Position.KEY_DOOR, true); + break; + case "Close": + position.set(Position.KEY_DOOR, false); + break; + default: + break; + } + + position.set(Position.KEY_ACCELERATION, parser.nextDouble()); + position.set("nfc", parser.next()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_RSSI, parser.nextInt()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/EsealProtocolEncoder.java b/src/main/java/org/traccar/protocol/EsealProtocolEncoder.java new file mode 100644 index 000000000..74f9e22ab --- /dev/null +++ b/src/main/java/org/traccar/protocol/EsealProtocolEncoder.java @@ -0,0 +1,46 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class EsealProtocolEncoder extends StringProtocolEncoder { + + public EsealProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return formatCommand( + command, "##S,eSeal,%s,256,3.0.8,%s,E##", Command.KEY_UNIQUE_ID, Command.KEY_DATA); + case Command.TYPE_ALARM_ARM: + return formatCommand( + command, "##S,eSeal,%s,256,3.0.8,RC-Power Control,Power OFF,E##", Command.KEY_UNIQUE_ID); + case Command.TYPE_ALARM_DISARM: + return formatCommand( + command, "##S,eSeal,%s,256,3.0.8,RC-Unlock,E##", Command.KEY_UNIQUE_ID); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/EskyFrameDecoder.java b/src/main/java/org/traccar/protocol/EskyFrameDecoder.java new file mode 100644 index 000000000..da24c1273 --- /dev/null +++ b/src/main/java/org/traccar/protocol/EskyFrameDecoder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class EskyFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + buf.readerIndex(buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 'E')); + + int endIndex = buf.indexOf(buf.readerIndex() + 1, buf.writerIndex(), (byte) 'E'); + if (endIndex > 0) { + return buf.readRetainedSlice(endIndex - buf.readerIndex()); + } else { + return buf.readRetainedSlice(buf.readableBytes()); // assume full frame + } + } + +} diff --git a/src/main/java/org/traccar/protocol/EskyProtocol.java b/src/main/java/org/traccar/protocol/EskyProtocol.java new file mode 100644 index 000000000..aaa92da58 --- /dev/null +++ b/src/main/java/org/traccar/protocol/EskyProtocol.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class EskyProtocol extends BaseProtocol { + + public EskyProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new EskyFrameDecoder()); + 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 new file mode 100644 index 000000000..641b2e28f --- /dev/null +++ b/src/main/java/org/traccar/protocol/EskyProtocolDecoder.java @@ -0,0 +1,94 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class EskyProtocolDecoder extends BaseProtocolDecoder { + + public EskyProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression("..;") // header + .number("d+;") // index + .number("(d+);") // imei + .text("R;") // data type + .number("(d+)[+;]") // satellites + .number("(dd)(dd)(dd)") // date + .number("(dd)(dd)(dd)[+;]") // time + .number("(-?d+.d+)[+;]") // latitude + .number("(-?d+.d+)[+;]") // longitude + .number("(d+.d+)[+;]") // speed + .number("(d+)[+;]") // course + .groupBegin() + .text("0x").number("(d+)[+;]") // input + .number("(d+)[+;]") // message type + .number("(d+)[+;]") // odometer + .groupEnd("?") + .number("(d+)") // voltage + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + + position.setValid(true); + position.setTime(parser.nextDateTime()); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromMps(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + + if (parser.hasNext(3)) { + position.set(Position.KEY_INPUT, parser.nextHexInt()); + position.set(Position.KEY_EVENT, parser.nextInt()); + position.set(Position.KEY_ODOMETER, parser.nextInt()); + } + + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/ExtremTracProtocol.java b/src/main/java/org/traccar/protocol/ExtremTracProtocol.java new file mode 100644 index 000000000..692fd4e99 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ExtremTracProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class ExtremTracProtocol extends BaseProtocol { + + public ExtremTracProtocol() { + 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 ExtremTracProtocolDecoder(ExtremTracProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ExtremTracProtocolDecoder.java b/src/main/java/org/traccar/protocol/ExtremTracProtocolDecoder.java new file mode 100644 index 000000000..9fde6f0a0 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ExtremTracProtocolDecoder.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.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 ExtremTracProtocolDecoder extends BaseProtocolDecoder { + + public ExtremTracProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$GPRMC,") + .number("(d+),") // device id + .number("(dd)(dd)(dd).(ddd),") // time (hhmmss.sss) + .expression("([AV]),") // validity + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .number("(dd)(dd)(dd),") // date (ddmmyy) + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/FifotrackFrameDecoder.java b/src/main/java/org/traccar/protocol/FifotrackFrameDecoder.java new file mode 100644 index 000000000..967b17a64 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FifotrackFrameDecoder.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +import java.nio.charset.StandardCharsets; + +public class FifotrackFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 10) { + return null; + } + + int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ','); + if (index != -1) { + int length = index - buf.readerIndex() + 3 + Integer.parseInt( + buf.toString(buf.readerIndex() + 2, index - buf.readerIndex() - 2, StandardCharsets.US_ASCII)); + if (buf.readableBytes() >= length + 2) { + ByteBuf frame = buf.readRetainedSlice(length); + buf.skipBytes(2); // delimiter + return frame; + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocol.java b/src/main/java/org/traccar/protocol/FifotrackProtocol.java new file mode 100644 index 000000000..4a0a12ed3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FifotrackProtocol.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class FifotrackProtocol extends BaseProtocol { + + public FifotrackProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_REQUEST_PHOTO); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new FifotrackFrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new FifotrackProtocolEncoder(FifotrackProtocol.this)); + pipeline.addLast(new FifotrackProtocolDecoder(FifotrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java new file mode 100644 index 000000000..40e146e0b --- /dev/null +++ b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java @@ -0,0 +1,236 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +public class FifotrackProtocolDecoder extends BaseProtocolDecoder { + + private ByteBuf photo; + + public FifotrackProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$$") + .number("d+,") // length + .number("(d+),") // imei + .number("x+,") // index + .expression("[^,]+,") // type + .number("(d+)?,") // alarm + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("([AV]),") // validity + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(-?d+),") // altitude + .number("(d+),") // odometer + .number("d+,") // runtime + .number("(x{4,8}),") // status + .number("(x+)?,") // input + .number("(x+)?,") // output + .number("(d+)|") // mcc + .number("(d+)|") // mnc + .number("(x+)|") // lac + .number("(x+),") // cid + .number("([x|]+)") // adc + .expression(",([^,]+)") // rfid + .expression(",([^*]*)").optional(2) // sensors + .any() + .compile(); + + private static final Pattern PATTERN_PHOTO = new PatternBuilder() + .text("$$") + .number("d+,") // length + .number("(d+),") // imei + .any() + .number(",(d+),") // length + .expression("([^*]+)") // photo id + .text("*") + .number("xx") + .compile(); + + private static final Pattern PATTERN_PHOTO_DATA = new PatternBuilder() + .text("$$") + .number("d+,") // length + .number("(d+),") // imei + .number("x+,") // index + .expression("[^,]+,") // type + .expression("([^,]+),") // photo id + .number("(d+),") // offset + .number("(d+),") // size + .compile(); + + private void requestPhoto(Channel channel, SocketAddress socketAddress, String imei, String file) { + if (channel != null) { + String content = "1,D06," + file + "," + photo.writerIndex() + "," + Math.min(1024, photo.writableBytes()); + int length = 1 + imei.length() + 1 + content.length(); + String response = String.format("##%02d,%s,%s*", length, imei, content); + response += Checksum.sum(response) + "\r\n"; + channel.writeAndFlush(new NetworkMessage(response, socketAddress)); + } + } + + private String decodeAlarm(Integer alarm) { + if (alarm != null) { + switch (alarm) { + case 2: + return Position.ALARM_SOS; + case 14: + return Position.ALARM_LOW_POWER; + case 15: + return Position.ALARM_POWER_CUT; + case 16: + return Position.ALARM_POWER_RESTORED; + default: + return null; + } + } + return null; + } + + private Object decodeLocation( + Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_ALARM, decodeAlarm(parser.nextInt())); + + position.setTime(parser.nextDateTime()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.setCourse(parser.nextInt()); + position.setAltitude(parser.nextInt()); + + position.set(Position.KEY_ODOMETER, parser.nextLong()); + position.set(Position.KEY_STATUS, parser.nextHexLong()); + position.set(Position.KEY_INPUT, parser.nextHexInt()); + position.set(Position.KEY_OUTPUT, parser.nextHexInt()); + + position.setNetwork(new Network(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt()))); + + String[] adc = parser.next().split("\\|"); + for (int i = 0; i < adc.length; i++) { + position.set(Position.PREFIX_ADC + (i + 1), Integer.parseInt(adc[i], 16)); + } + + if (parser.hasNext()) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(parser.nextHexInt())); + } + + if (parser.hasNext()) { + String[] sensors = parser.next().split("\\|"); + for (int i = 0; i < sensors.length; i++) { + position.set(Position.PREFIX_IO + (i + 1), sensors[i]); + } + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + int typeIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ',') + 1; + typeIndex = buf.indexOf(typeIndex, buf.writerIndex(), (byte) ',') + 1; + typeIndex = buf.indexOf(typeIndex, buf.writerIndex(), (byte) ',') + 1; + String type = buf.toString(typeIndex, 3, StandardCharsets.US_ASCII); + + if (type.equals("D05")) { + String sentence = buf.toString(StandardCharsets.US_ASCII); + Parser parser = new Parser(PATTERN_PHOTO, sentence); + if (parser.matches()) { + String imei = parser.next(); + int length = parser.nextInt(); + String photoId = parser.next(); + photo = Unpooled.buffer(length); + requestPhoto(channel, remoteAddress, imei, photoId); + } + } else if (type.equals("D06")) { + if (photo == null) { + return null; + } + int dataIndex = buf.indexOf(typeIndex + 4, buf.writerIndex(), (byte) ',') + 1; + dataIndex = buf.indexOf(dataIndex, buf.writerIndex(), (byte) ',') + 1; + dataIndex = buf.indexOf(dataIndex, buf.writerIndex(), (byte) ',') + 1; + String sentence = buf.toString(buf.readerIndex(), dataIndex, StandardCharsets.US_ASCII); + Parser parser = new Parser(PATTERN_PHOTO_DATA, sentence); + if (parser.matches()) { + String imei = parser.next(); + String photoId = parser.next(); + parser.nextInt(); // offset + parser.nextInt(); // size + buf.readerIndex(dataIndex); + buf.readBytes(photo, buf.readableBytes() - 3); // ignore checksum + if (photo.isWritable()) { + requestPhoto(channel, remoteAddress, imei, photoId); + } else { + Position position = new Position(getProtocolName()); + position.setDeviceId(getDeviceSession(channel, remoteAddress, imei).getDeviceId()); + getLastLocation(position, null); + position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(imei, photo, "jpg")); + photo.release(); + photo = null; + return position; + } + } + } else { + String sentence = buf.toString(StandardCharsets.US_ASCII); + return decodeLocation(channel, remoteAddress, sentence); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocolEncoder.java b/src/main/java/org/traccar/protocol/FifotrackProtocolEncoder.java new file mode 100644 index 000000000..a4e69b47b --- /dev/null +++ b/src/main/java/org/traccar/protocol/FifotrackProtocolEncoder.java @@ -0,0 +1,50 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.helper.Checksum; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class FifotrackProtocolEncoder extends StringProtocolEncoder { + + public FifotrackProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private Object formatCommand(Command command, String content) { + String uniqueId = getUniqueId(command.getDeviceId()); + int length = 1 + uniqueId.length() + 3 + content.length(); + String result = String.format("##%02d,%s,1,%s*", length, uniqueId, content); + result += Checksum.sum(result) + "\r\n"; + return result; + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return formatCommand(command, command.getString(Command.KEY_DATA)); + case Command.TYPE_REQUEST_PHOTO: + return formatCommand(command, "D05,3"); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/FlespiProtocol.java b/src/main/java/org/traccar/protocol/FlespiProtocol.java new file mode 100644 index 000000000..2c0729b76 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FlespiProtocol.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class FlespiProtocol extends BaseProtocol { + + public FlespiProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE)); + pipeline.addLast(new FlespiProtocolDecoder(FlespiProtocol.this)); + } + }); + } +} diff --git a/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java new file mode 100644 index 000000000..7405fb6ef --- /dev/null +++ b/src/main/java/org/traccar/protocol/FlespiProtocolDecoder.java @@ -0,0 +1,247 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonValue; +import java.io.StringReader; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class FlespiProtocolDecoder extends BaseHttpProtocolDecoder { + + public FlespiProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + JsonArray result = Json.createReader(new StringReader(request.content().toString(StandardCharsets.UTF_8))) + .readArray(); + List<Position> positions = new LinkedList<>(); + for (int i = 0; i < result.size(); i++) { + JsonObject message = result.getJsonObject(i); + JsonString ident = message.getJsonString("ident"); + if (ident == null) { + continue; + } + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, ident.getString()); + if (deviceSession == null) { + continue; + } + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + decodePosition(message, position); + positions.add(position); + } + + sendResponse(channel, HttpResponseStatus.OK); + return positions; + } + + private void decodePosition(JsonObject object, Position position) { + for (Map.Entry<String, JsonValue> param : object.entrySet()) { + String paramName = param.getKey(); + JsonValue paramValue = param.getValue(); + int index = -1; + if (paramName.contains("#")) { + String[] parts = paramName.split("#"); + paramName = parts[0]; + index = Integer.parseInt(parts[1]); + } + if (!decodeParam(paramName, index, paramValue, position)) { + decodeUnknownParam(param.getKey(), param.getValue(), position); + } + } + if (position.getLatitude() == 0 && position.getLongitude() == 0) { + getLastLocation(position, position.getDeviceTime()); + } + } + + private boolean decodeParam(String name, int index, JsonValue value, Position position) { + switch (name) { + case "timestamp": + position.setTime(new Date(((JsonNumber) value).longValue() * 1000)); + return true; + case "position.latitude": + position.setLatitude(((JsonNumber) value).doubleValue()); + return true; + case "position.longitude": + position.setLongitude(((JsonNumber) value).doubleValue()); + return true; + case "position.speed": + position.setSpeed(UnitsConverter.knotsFromKph(((JsonNumber) value).doubleValue())); + return true; + case "position.direction": + position.setCourse(((JsonNumber) value).doubleValue()); + return true; + case "position.altitude": + position.setAltitude(((JsonNumber) value).doubleValue()); + return true; + case "position.satellites": + position.set(Position.KEY_SATELLITES, ((JsonNumber) value).intValue()); + return true; + case "position.valid": + position.setValid(value == JsonValue.TRUE); + return true; + case "position.hdop": + position.set(Position.KEY_HDOP, ((JsonNumber) value).doubleValue()); + return true; + case "position.pdop": + position.set(Position.KEY_PDOP, ((JsonNumber) value).doubleValue()); + return true; + case "din": + case "dout": + position.set(name.equals("din") ? Position.KEY_INPUT : Position.KEY_OUTPUT, + ((JsonNumber) value).intValue()); + return true; + case "gps.vehicle.mileage": + position.set(Position.KEY_ODOMETER, ((JsonNumber) value).doubleValue()); + return true; + case "external.powersource.voltage": + position.set(Position.KEY_POWER, ((JsonNumber) value).doubleValue()); + return true; + case "battery.voltage": + position.set(Position.KEY_BATTERY, ((JsonNumber) value).doubleValue()); + return true; + case "fuel.level": + case "can.fuel.level": + position.set(Position.KEY_FUEL_LEVEL, ((JsonNumber) value).doubleValue()); + return true; + case "engine.rpm": + case "can.engine.rpm": + position.set(Position.KEY_RPM, ((JsonNumber) value).doubleValue()); + return true; + case "can.engine.temperature": + position.set(Position.PREFIX_TEMP + (index > 0 ? index : 0), ((JsonNumber) value).doubleValue()); + return true; + case "engine.ignition.status": + position.set(Position.KEY_IGNITION, value == JsonValue.TRUE); + return true; + case "movement.status": + position.set(Position.KEY_MOTION, value == JsonValue.TRUE); + return true; + case "device.temperature": + position.set(Position.KEY_DEVICE_TEMP, ((JsonNumber) value).doubleValue()); + return true; + case "ibutton.code": + position.set(Position.KEY_DRIVER_UNIQUE_ID, ((JsonString) value).getString()); + return true; + case "vehicle.vin": + position.set(Position.KEY_VIN, ((JsonString) value).getString()); + return true; + case "alarm.event.trigger": + if (value == JsonValue.TRUE) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + return true; + case "towing.event.trigger": + case "towing.alarm.status": + if (value == JsonValue.TRUE) { + position.set(Position.KEY_ALARM, Position.ALARM_TOW); + } + return true; + case "geofence.event.enter": + if (value == JsonValue.TRUE) { + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_ENTER); + } + return true; + case "geofence.event.exit": + if (value == JsonValue.TRUE) { + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_EXIT); + } + return true; + case "shock.event.trigger": + if (value == JsonValue.TRUE) { + position.set(Position.KEY_ALARM, Position.ALARM_SHOCK); + } + return true; + case "overspeeding.event.trigger": + if (value == JsonValue.TRUE) { + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + } + return true; + case "harsh.acceleration.event.trigger": + if (value == JsonValue.TRUE) { + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + } + return true; + case "harsh.braking.event.trigger": + if (value == JsonValue.TRUE) { + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + } + return true; + case "harsh.cornering.event.trigger": + if (value == JsonValue.TRUE) { + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + } + return true; + case "gnss.antenna.cut.status": + if (value == JsonValue.TRUE) { + position.set(Position.KEY_ALARM, Position.ALARM_GPS_ANTENNA_CUT); + } + return true; + case "gsm.jamming.event.trigger": + if (value == JsonValue.TRUE) { + position.set(Position.KEY_ALARM, Position.ALARM_JAMMING); + } + return true; + case "hood.open.status": + if (value == JsonValue.TRUE) { + position.set(Position.KEY_ALARM, Position.ALARM_BONNET); + } + return true; + default: + return false; + } + } + + private void decodeUnknownParam(String name, JsonValue value, Position position) { + if (value instanceof JsonNumber) { + if (((JsonNumber) value).isIntegral()) { + position.set(name, ((JsonNumber) value).longValue()); + } else { + position.set(name, ((JsonNumber) value).doubleValue()); + } + position.set(name, ((JsonNumber) value).doubleValue()); + } else if (value instanceof JsonString) { + position.set(name, ((JsonString) value).getString()); + } else if (value == JsonValue.TRUE || value == JsonValue.FALSE) { + position.set(name, value == JsonValue.TRUE); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/FlexCommProtocol.java b/src/main/java/org/traccar/protocol/FlexCommProtocol.java new file mode 100644 index 000000000..9343ebeb8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FlexCommProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.FixedLengthFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class FlexCommProtocol extends BaseProtocol { + + public FlexCommProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new FixedLengthFrameDecoder(2 + 2 + 101 + 5)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new FlexCommProtocolDecoder(FlexCommProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/FlexCommProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlexCommProtocolDecoder.java new file mode 100644 index 000000000..068c0a05c --- /dev/null +++ b/src/main/java/org/traccar/protocol/FlexCommProtocolDecoder.java @@ -0,0 +1,128 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class FlexCommProtocolDecoder extends BaseProtocolDecoder { + + public FlexCommProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("7E") + .number("(dd)") // status + .number("(d{15})") // imei + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)") // time (hhmmss) + .expression("([01])") // valid + .number("(d{9})") // latitude + .number("(d{10})") // longitude + .number("(d{4})") // altitude + .number("(ddd)") // speed + .number("(ddd)") // course + .number("(dd)") // satellites view + .number("(dd)") // satellites used + .number("(dd)") // rssi + .number("(ddd)") // mcc + .number("(ddd)") // mnc + .number("(x{6})") // lac + .number("(x{6})") // cid + .expression("([01])([01])([01])") // input + .expression("([01])([01])") // output + .number("(ddd)") // fuel + .number("(d{4})") // temperature + .number("(ddd)") // battery + .number("(ddd)") // power + .any() + .compile(); + + private static double parseSignedValue(Parser parser, int decimalPoints) { + String stringValue = parser.next(); + boolean negative = stringValue.charAt(0) == '1'; + double value = Integer.parseInt(stringValue.substring(1)) * Math.pow(10, -decimalPoints); + return negative ? -value : value; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + position.set(Position.KEY_STATUS, parser.nextInt()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime()); + position.setValid(parser.next().equals("1")); + position.setLatitude(parseSignedValue(parser, 6)); + position.setLongitude(parseSignedValue(parser, 6)); + position.setAltitude(parseSignedValue(parser, 0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_SATELLITES_VISIBLE, parser.nextInt()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_RSSI, parser.nextInt()); + + position.setNetwork(new Network(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt()))); + + for (int i = 1; i <= 3; i++) { + position.set(Position.PREFIX_IN + i, parser.nextInt()); + } + + for (int i = 1; i <= 2; i++) { + position.set(Position.PREFIX_OUT + i, parser.nextInt()); + } + + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, parseSignedValue(parser, 0)); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + position.set(Position.KEY_POWER, parser.nextInt() * 0.1); + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("{01}", remoteAddress)); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/FlextrackProtocol.java b/src/main/java/org/traccar/protocol/FlextrackProtocol.java new file mode 100644 index 000000000..ddd1d58f0 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FlextrackProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class FlextrackProtocol extends BaseProtocol { + + public FlextrackProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "\r")); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new FlextrackProtocolDecoder(FlextrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/FlextrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FlextrackProtocolDecoder.java new file mode 100644 index 000000000..9dce22ede --- /dev/null +++ b/src/main/java/org/traccar/protocol/FlextrackProtocolDecoder.java @@ -0,0 +1,144 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class FlextrackProtocolDecoder extends BaseProtocolDecoder { + + public FlextrackProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_LOGON = new PatternBuilder() + .number("(-?d+),") // index + .text("LOGON,") + .number("(d+),") // node id + .number("(d+)") // iccid + .compile(); + + private static final Pattern PATTERN = new PatternBuilder() + .number("(-?d+),") // index + .text("UNITSTAT,") + .number("(dddd)(dd)(dd),") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("d+,") // node id + .number("([NS])(d+).(d+.d+),") // latitude + .number("([EW])(d+).(d+.d+),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(d+),") // satellites + .number("(d+),") // battery + .number("(-?d+),") // gsm + .number("(x+),") // state + .number("(ddd)") // mcc + .number("(dd),") // mnc + .number("(-?d+),") // altitude + .number("(d+),") // hdop + .number("(x+),") // cell + .number("d+,") // gps fix time + .number("(x+),") // lac + .number("(d+)") // odometer + .compile(); + + private void sendAcknowledgement(Channel channel, SocketAddress remoteAddress, String index) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(index + ",ACK\r", remoteAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.contains("LOGON")) { + + Parser parser = new Parser(PATTERN_LOGON, sentence); + if (!parser.matches()) { + return null; + } + + sendAcknowledgement(channel, remoteAddress, parser.next()); + + String id = parser.next(); + String iccid = parser.next(); + + getDeviceSession(channel, remoteAddress, iccid, id); + + } else if (sentence.contains("UNITSTAT")) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + sendAcknowledgement(channel, remoteAddress, parser.next()); + + position.setTime(parser.nextDateTime()); + + position.setValid(true); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt(0))); + position.setCourse(parser.nextInt(0)); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + position.set(Position.KEY_BATTERY, parser.nextInt(0)); + int rssi = parser.nextInt(0); + position.set(Position.KEY_STATUS, parser.nextHexInt(0)); + + int mcc = parser.nextInt(0); + int mnc = parser.nextInt(0); + + position.setAltitude(parser.nextInt(0)); + + position.set(Position.KEY_HDOP, parser.nextInt(0) * 0.1); + + position.setNetwork(new Network(CellTower.from( + mcc, mnc, parser.nextHexInt(0), parser.nextHexInt(0), rssi))); + + position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + + return position; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/FoxProtocol.java b/src/main/java/org/traccar/protocol/FoxProtocol.java new file mode 100644 index 000000000..9bac773b5 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FoxProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class FoxProtocol extends BaseProtocol { + + public FoxProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "</fox>")); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new FoxProtocolDecoder(FoxProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/FoxProtocolDecoder.java b/src/main/java/org/traccar/protocol/FoxProtocolDecoder.java new file mode 100644 index 000000000..449f00022 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FoxProtocolDecoder.java @@ -0,0 +1,124 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class FoxProtocolDecoder extends BaseProtocolDecoder { + + public FoxProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("(d+),") // status id + .expression("([AV]),") // validity + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .expression("[^,]*,") // cell info + .number("([01]+) ") // input + .number("(d+) ") // power + .number("(d+) ") // temperature + .number("(d+) ") // rpm + .number("(d+) ") // fuel + .number("(d+) ") // adc 1 + .number("(d+) ") // adc 2 + .number("([01]+) ") // output + .number("(d+),") // odometer + .expression("(.+)") // status info + .compile(); + + private String getAttribute(String xml, String key) { + int start = xml.indexOf(key + "=\""); + if (start != -1) { + start += key.length() + 2; + int end = xml.indexOf("\"", start); + if (end != -1) { + return xml.substring(start, end); + } + } + return null; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String xml = (String) msg; + String id = getAttribute(xml, "id"); + String data = getAttribute(xml, "data"); + + if (id != null && data != null) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + Parser parser = new Parser(PATTERN, data); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_STATUS, parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_INPUT, parser.nextBinInt(0)); + position.set(Position.KEY_POWER, parser.nextDouble(0) * 0.1); + position.set(Position.PREFIX_TEMP + 1, parser.nextInt(0)); + position.set(Position.KEY_RPM, parser.nextInt(0)); + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt(0)); + position.set(Position.PREFIX_ADC + 1, parser.nextInt(0)); + position.set(Position.PREFIX_ADC + 2, parser.nextInt(0)); + position.set(Position.KEY_OUTPUT, parser.nextBinInt(0)); + position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + + position.set("statusData", parser.next()); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/FreedomProtocol.java b/src/main/java/org/traccar/protocol/FreedomProtocol.java new file mode 100644 index 000000000..bc6b92d5f --- /dev/null +++ b/src/main/java/org/traccar/protocol/FreedomProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class FreedomProtocol extends BaseProtocol { + + public FreedomProtocol() { + 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 FreedomProtocolDecoder(FreedomProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/FreedomProtocolDecoder.java b/src/main/java/org/traccar/protocol/FreedomProtocolDecoder.java new file mode 100644 index 000000000..1d2dd3133 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FreedomProtocolDecoder.java @@ -0,0 +1,77 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class FreedomProtocolDecoder extends BaseProtocolDecoder { + + public FreedomProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("IMEI,") + .number("(d+),") // imei + .number("(dddd)/(dd)/(dd), ") // date (yyyy/dd/mm) + .number("(dd):(dd):(dd), ") // time (hh:mm:ss) + .expression("([NS]), ") + .number("Lat:(dd)(d+.d+), ") // latitude + .expression("([EW]), ") + .number("Lon:(ddd)(d+.d+), ") // longitude + .text("Spd:").number("(d+.d+)") // speed + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + + position.setTime(parser.nextDateTime()); + + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + + position.setSpeed(parser.nextDouble(0)); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/FreematicsProtocol.java b/src/main/java/org/traccar/protocol/FreematicsProtocol.java new file mode 100644 index 000000000..999b075a1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FreematicsProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class FreematicsProtocol extends BaseProtocol { + + public FreematicsProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new FreematicsProtocolDecoder(FreematicsProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java b/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java new file mode 100644 index 000000000..ba47699c3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/FreematicsProtocolDecoder.java @@ -0,0 +1,184 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class FreematicsProtocolDecoder extends BaseProtocolDecoder { + + public FreematicsProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private Object decodeEvent( + Channel channel, SocketAddress remoteAddress, String sentence) { + + DeviceSession deviceSession = null; + String event = null; + String time = null; + + for (String pair : sentence.split(",")) { + String[] data = pair.split("="); + String key = data[0]; + String value = data[1]; + switch (key) { + case "ID": + case "VIN": + if (deviceSession == null) { + deviceSession = getDeviceSession(channel, remoteAddress, value); + } + break; + case "EV": + event = value; + break; + case "TS": + time = value; + break; + default: + break; + } + } + + if (channel != null && deviceSession != null && event != null && time != null) { + String message = String.format("1#EV=%s,RX=1,TS=%s", event, time); + message += '*' + Checksum.sum(message); + channel.writeAndFlush(new NetworkMessage(message, remoteAddress)); + } + + return null; + } + + private Object decodePosition( + Channel channel, SocketAddress remoteAddress, String sentence) throws Exception { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + Position position = null; + DateBuilder dateBuilder = null; + + for (String pair : sentence.split(",")) { + String[] data = pair.split("[=:]"); + int key = Integer.parseInt(data[0], 16); + String value = data[1]; + switch (key) { + case 0x0: + if (position != null) { + position.setTime(dateBuilder.getDate()); + positions.add(position); + } + position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.setValid(true); + dateBuilder = new DateBuilder(new Date()); + break; + case 0x11: + value = ("000000" + value).substring(value.length()); + dateBuilder.setDateReverse( + Integer.parseInt(value.substring(0, 2)), + Integer.parseInt(value.substring(2, 4)), + Integer.parseInt(value.substring(4))); + break; + case 0x10: + value = ("00000000" + value).substring(value.length()); + dateBuilder.setTime( + Integer.parseInt(value.substring(0, 2)), + Integer.parseInt(value.substring(2, 4)), + Integer.parseInt(value.substring(4, 6)), + Integer.parseInt(value.substring(6)) * 10); + break; + case 0xA: + position.setLatitude(Double.parseDouble(value)); + break; + case 0xB: + position.setLongitude(Double.parseDouble(value)); + break; + case 0xC: + position.setAltitude(Double.parseDouble(value)); + break; + case 0xD: + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(value))); + break; + case 0xE: + position.setCourse(Integer.parseInt(value)); + break; + case 0xF: + position.set(Position.KEY_SATELLITES, Integer.parseInt(value)); + break; + case 0x20: + position.set(Position.KEY_ACCELERATION, value); + break; + case 0x24: + position.set(Position.KEY_BATTERY, Integer.parseInt(value) * 0.01); + break; + case 0x81: + position.set(Position.KEY_RSSI, Integer.parseInt(value)); + break; + case 0x82: + position.set(Position.KEY_DEVICE_TEMP, Integer.parseInt(value) * 0.1); + break; + default: + position.set(data[0], value); + break; + } + } + + if (position != null) { + position.setTime(dateBuilder.getDate()); + positions.add(position); + } + + return positions; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + int startIndex = sentence.indexOf('#'); + int endIndex = sentence.indexOf('*'); + + if (startIndex > 0 && endIndex > 0) { + sentence = sentence.substring(startIndex + 1, endIndex); + + if (sentence.startsWith("EV")) { + return decodeEvent(channel, remoteAddress, sentence); + } else { + return decodePosition(channel, remoteAddress, sentence); + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/GalileoFrameDecoder.java b/src/main/java/org/traccar/protocol/GalileoFrameDecoder.java new file mode 100644 index 000000000..c23d26c83 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GalileoFrameDecoder.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class GalileoFrameDecoder extends BaseFrameDecoder { + + private static final int MESSAGE_MINIMUM_LENGTH = 5; + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < MESSAGE_MINIMUM_LENGTH) { + return null; + } + + int length = buf.getUnsignedShortLE(buf.readerIndex() + 1) & 0x7fff; + if (buf.readableBytes() >= (length + MESSAGE_MINIMUM_LENGTH)) { + return buf.readRetainedSlice(length + MESSAGE_MINIMUM_LENGTH); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/GalileoProtocol.java b/src/main/java/org/traccar/protocol/GalileoProtocol.java new file mode 100644 index 000000000..a1570c9b0 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GalileoProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class GalileoProtocol extends BaseProtocol { + + public GalileoProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_OUTPUT_CONTROL); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new GalileoFrameDecoder()); + pipeline.addLast(new GalileoProtocolEncoder(GalileoProtocol.this)); + pipeline.addLast(new GalileoProtocolDecoder(GalileoProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/GalileoProtocolDecoder.java b/src/main/java/org/traccar/protocol/GalileoProtocolDecoder.java new file mode 100644 index 000000000..dfaedd695 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GalileoProtocolDecoder.java @@ -0,0 +1,342 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class GalileoProtocolDecoder extends BaseProtocolDecoder { + + public GalileoProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf photo; + + private static final Map<Integer, Integer> TAG_LENGTH_MAP = new HashMap<>(); + + static { + int[] l1 = { + 0x01, 0x02, 0x35, 0x43, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd5, 0x88, 0x8a, 0x8b, 0x8c, + 0xa0, 0xaf, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, + 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae + }; + int[] l2 = { + 0x04, 0x10, 0x34, 0x40, 0x41, 0x42, 0x45, 0x46, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x60, 0x61, + 0x62, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, + 0x77, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xd6, 0xd7, 0xd8, 0xd9, 0xda + }; + int[] l3 = { + 0x63, 0x64, 0x6f, 0x5d, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e + }; + int[] l4 = { + 0x20, 0x33, 0x44, 0x90, 0xc0, 0xc2, 0xc3, 0xd3, + 0xd4, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xf0, 0xf9, + 0x5a, 0x47, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf7, 0xf8, 0xe2, 0xe9 + }; + for (int i : l1) { + TAG_LENGTH_MAP.put(i, 1); + } + for (int i : l2) { + TAG_LENGTH_MAP.put(i, 2); + } + for (int i : l3) { + TAG_LENGTH_MAP.put(i, 3); + } + for (int i : l4) { + TAG_LENGTH_MAP.put(i, 4); + } + TAG_LENGTH_MAP.put(0x5b, 7); // variable length + TAG_LENGTH_MAP.put(0x5c, 68); + } + + private static int getTagLength(int tag) { + Integer length = TAG_LENGTH_MAP.get(tag); + if (length == null) { + throw new IllegalArgumentException("Unknown tag: " + tag); + } + return length; + } + + private void sendResponse(Channel channel, int header, int checksum) { + if (channel != null) { + ByteBuf reply = Unpooled.buffer(3); + reply.writeByte(header); + reply.writeShortLE((short) checksum); + channel.writeAndFlush(new NetworkMessage(reply, channel.remoteAddress())); + } + } + + private void decodeTag(Position position, ByteBuf buf, int tag) { + if (tag >= 0x50 && tag <= 0x57) { + position.set(Position.PREFIX_ADC + (tag - 0x50), buf.readUnsignedShortLE()); + } else if (tag >= 0x60 && tag <= 0x62) { + position.set("fuel" + (tag - 0x60), buf.readUnsignedShortLE()); + } else if (tag >= 0xa0 && tag <= 0xaf) { + position.set("can8BitR" + (tag - 0xa0 + 15), buf.readUnsignedByte()); + } else if (tag >= 0xb0 && tag <= 0xb9) { + position.set("can16BitR" + (tag - 0xb0 + 5), buf.readUnsignedShortLE()); + } else if (tag >= 0xc4 && tag <= 0xd2) { + position.set("can8BitR" + (tag - 0xc4), buf.readUnsignedByte()); + } else if (tag >= 0xd6 && tag <= 0xda) { + position.set("can16BitR" + (tag - 0xd6), buf.readUnsignedShortLE()); + } else if (tag >= 0xdb && tag <= 0xdf) { + position.set("can32BitR" + (tag - 0xdb), buf.readUnsignedIntLE()); + } else if (tag >= 0xe2 && tag <= 0xe9) { + position.set("userData" + (tag - 0xe2), buf.readUnsignedIntLE()); + } else if (tag >= 0xf0 && tag <= 0xf9) { + position.set("can32BitR" + (tag - 0xf0 + 5), buf.readUnsignedIntLE()); + } else { + decodeTagOther(position, buf, tag); + } + } + + private void decodeTagOther(Position position, ByteBuf buf, int tag) { + switch (tag) { + case 0x01: + position.set(Position.KEY_VERSION_HW, buf.readUnsignedByte()); + break; + case 0x02: + position.set(Position.KEY_VERSION_FW, buf.readUnsignedByte()); + break; + case 0x04: + position.set("deviceId", buf.readUnsignedShortLE()); + break; + case 0x10: + position.set(Position.KEY_INDEX, buf.readUnsignedShortLE()); + break; + case 0x20: + position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); + break; + case 0x33: + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE() * 0.1)); + position.setCourse(buf.readUnsignedShortLE() * 0.1); + break; + case 0x34: + position.setAltitude(buf.readShortLE()); + break; + case 0x35: + position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1); + break; + case 0x40: + position.set(Position.KEY_STATUS, buf.readUnsignedShortLE()); + break; + case 0x41: + position.set(Position.KEY_POWER, buf.readUnsignedShortLE() / 1000.0); + break; + case 0x42: + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() / 1000.0); + break; + case 0x43: + position.set(Position.KEY_DEVICE_TEMP, buf.readByte()); + break; + case 0x44: + position.set(Position.KEY_ACCELERATION, buf.readUnsignedIntLE()); + break; + case 0x45: + position.set(Position.KEY_OUTPUT, buf.readUnsignedShortLE()); + break; + case 0x46: + position.set(Position.KEY_INPUT, buf.readUnsignedShortLE()); + break; + case 0x48: + position.set("statusExtended", buf.readUnsignedShortLE()); + break; + case 0x58: + position.set("rs2320", buf.readUnsignedShortLE()); + break; + case 0x59: + position.set("rs2321", buf.readUnsignedShortLE()); + break; + case 0x90: + position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(buf.readUnsignedIntLE())); + break; + case 0xc0: + position.set("fuelTotal", buf.readUnsignedIntLE() * 0.5); + break; + case 0xc1: + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte() * 0.4); + position.set(Position.PREFIX_TEMP + 1, buf.readUnsignedByte() - 40); + position.set(Position.KEY_RPM, buf.readUnsignedShortLE() * 0.125); + break; + case 0xc2: + position.set("canB0", buf.readUnsignedIntLE()); + break; + case 0xc3: + position.set("canB1", buf.readUnsignedIntLE()); + break; + case 0xd4: + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + break; + case 0xe0: + position.set(Position.KEY_INDEX, buf.readUnsignedIntLE()); + break; + case 0xe1: + position.set(Position.KEY_RESULT, + buf.readSlice(buf.readUnsignedByte()).toString(StandardCharsets.US_ASCII)); + break; + case 0xea: + position.set("userDataArray", ByteBufUtil.hexDump(buf.readSlice(buf.readUnsignedByte()))); + position.set("userDataArray", ByteBufUtil.hexDump(buf.readSlice(buf.readUnsignedByte()))); + break; + default: + buf.skipBytes(getTagLength(tag)); + break; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int header = buf.readUnsignedByte(); + if (header == 0x01) { + return decodePositions(channel, remoteAddress, buf); + } else if (header == 0x07) { + return decodePhoto(channel, remoteAddress, buf); + } + + return null; + } + + private Object decodePositions( + Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws Exception { + + int length = (buf.readUnsignedShortLE() & 0x7fff) + 3; + + List<Position> positions = new LinkedList<>(); + Set<Integer> tags = new HashSet<>(); + boolean hasLocation = false; + + DeviceSession deviceSession = null; + Position position = new Position(getProtocolName()); + + while (buf.readerIndex() < length) { + + int tag = buf.readUnsignedByte(); + if (tags.contains(tag)) { + if (hasLocation && position.getFixTime() != null) { + positions.add(position); + } + tags.clear(); + hasLocation = false; + position = new Position(getProtocolName()); // new position starts + } + tags.add(tag); + + if (tag == 0x03) { + deviceSession = getDeviceSession( + channel, remoteAddress, buf.readSlice(15).toString(StandardCharsets.US_ASCII)); + } else if (tag == 0x30) { + hasLocation = true; + position.setValid((buf.readUnsignedByte() & 0xf0) == 0x00); + position.setLatitude(buf.readIntLE() / 1000000.0); + position.setLongitude(buf.readIntLE() / 1000000.0); + } else { + decodeTag(position, buf, tag); + } + + } + + if (deviceSession == null) { + deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + } + + if (hasLocation && position.getFixTime() != null) { + positions.add(position); + } else if (position.getAttributes().containsKey(Position.KEY_RESULT)) { + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, null); + positions.add(position); + } + + sendResponse(channel, 0x02, buf.readUnsignedShortLE()); + + for (Position p : positions) { + p.setDeviceId(deviceSession.getDeviceId()); + } + + return positions.isEmpty() ? null : positions; + } + + private Object decodePhoto( + Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws Exception { + + int length = buf.readUnsignedShortLE(); + + Position position = null; + + if (length > 1) { + + if (photo == null) { + photo = Unpooled.buffer(); + } + + buf.readUnsignedByte(); // part number + photo.writeBytes(buf, length - 1); + + sendResponse(channel, 0x07, buf.readUnsignedShortLE()); + + } else if (photo != null) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + String uniqueId = Context.getIdentityManager().getById(deviceSession.getDeviceId()).getUniqueId(); + + position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(uniqueId, photo, "jpg")); + photo.release(); + photo = null; + + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/GalileoProtocolEncoder.java b/src/main/java/org/traccar/protocol/GalileoProtocolEncoder.java new file mode 100644 index 000000000..cd068b251 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GalileoProtocolEncoder.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.helper.Checksum; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.nio.charset.StandardCharsets; + +public class GalileoProtocolEncoder extends BaseProtocolEncoder { + + public GalileoProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf encodeText(String uniqueId, String text) { + + ByteBuf buf = Unpooled.buffer(256); + + buf.writeByte(0x01); + buf.writeShortLE(uniqueId.length() + text.length() + 11); + + buf.writeByte(0x03); // imei tag + buf.writeBytes(uniqueId.getBytes(StandardCharsets.US_ASCII)); + + buf.writeByte(0x04); // device id tag + buf.writeShortLE(0); // not needed if imei provided + + buf.writeByte(0xE0); // index tag + buf.writeIntLE(0); // index + + buf.writeByte(0xE1); // command text tag + buf.writeByte(text.length()); + buf.writeBytes(text.getBytes(StandardCharsets.US_ASCII)); + + buf.writeShortLE(Checksum.crc16(Checksum.CRC16_MODBUS, buf.nioBuffer(0, buf.writerIndex()))); + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return encodeText(getUniqueId(command.getDeviceId()), command.getString(Command.KEY_DATA)); + case Command.TYPE_OUTPUT_CONTROL: + return encodeText(getUniqueId(command.getDeviceId()), + "Out " + command.getInteger(Command.KEY_INDEX) + "," + command.getString(Command.KEY_DATA)); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/GatorProtocol.java b/src/main/java/org/traccar/protocol/GatorProtocol.java new file mode 100644 index 000000000..ca81caefb --- /dev/null +++ b/src/main/java/org/traccar/protocol/GatorProtocol.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class GatorProtocol extends BaseProtocol { + + public GatorProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 2)); + pipeline.addLast(new GatorProtocolDecoder(GatorProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new GatorProtocolDecoder(GatorProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java b/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java new file mode 100644 index 000000000..043839be9 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GatorProtocolDecoder.java @@ -0,0 +1,135 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.BcdUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class GatorProtocolDecoder extends BaseProtocolDecoder { + + public GatorProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_HEARTBEAT = 0x21; + public static final int MSG_POSITION_DATA = 0x80; + public static final int MSG_ROLLCALL_RESPONSE = 0x81; + public static final int MSG_ALARM_DATA = 0x82; + public static final int MSG_TERMINAL_STATUS = 0x83; + public static final int MSG_MESSAGE = 0x84; + public static final int MSG_TERMINAL_ANSWER = 0x85; + public static final int MSG_BLIND_AREA = 0x8E; + public static final int MSG_PICTURE_FRAME = 0x54; + public static final int MSG_CAMERA_RESPONSE = 0x56; + public static final int MSG_PICTURE_DATA = 0x57; + + public static String decodeId(int b1, int b2, int b3, int b4) { + + int d1 = 30 + ((b1 >> 7) << 3) + ((b2 >> 7) << 2) + ((b3 >> 7) << 1) + (b4 >> 7); + int d2 = b1 & 0x7f; + int d3 = b2 & 0x7f; + int d4 = b3 & 0x7f; + int d5 = b4 & 0x7f; + + return String.format("%02d%02d%02d%02d%02d", d1, d2, d3, d4, d5); + } + + private void sendResponse(Channel channel, SocketAddress remoteAddress, byte calibration) { + 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.writeByte(0x0D); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + int type = buf.readUnsignedByte(); + buf.readUnsignedShort(); // length + + String id = decodeId( + buf.readUnsignedByte(), buf.readUnsignedByte(), + buf.readUnsignedByte(), buf.readUnsignedByte()); + + sendResponse(channel, remoteAddress, buf.getByte(buf.writerIndex() - 2)); + + if (type == MSG_POSITION_DATA || type == MSG_ROLLCALL_RESPONSE + || type == MSG_ALARM_DATA || type == MSG_BLIND_AREA) { + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, "1" + id, id); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setYear(BcdUtil.readInteger(buf, 2)) + .setMonth(BcdUtil.readInteger(buf, 2)) + .setDay(BcdUtil.readInteger(buf, 2)) + .setHour(BcdUtil.readInteger(buf, 2)) + .setMinute(BcdUtil.readInteger(buf, 2)) + .setSecond(BcdUtil.readInteger(buf, 2)); + position.setTime(dateBuilder.getDate()); + + position.setLatitude(BcdUtil.readCoordinate(buf)); + position.setLongitude(BcdUtil.readCoordinate(buf)); + position.setSpeed(UnitsConverter.knotsFromKph(BcdUtil.readInteger(buf, 4))); + position.setCourse(BcdUtil.readInteger(buf, 4)); + + int flags = buf.readUnsignedByte(); + position.setValid((flags & 0x80) != 0); + position.set(Position.KEY_SATELLITES, flags & 0x0f); + + position.set(Position.KEY_STATUS, buf.readUnsignedByte()); + position.set("key", buf.readUnsignedByte()); + + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedByte() + buf.readUnsignedByte() * 0.01); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedByte() + buf.readUnsignedByte() * 0.01); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + + return position; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/GenxProtocol.java b/src/main/java/org/traccar/protocol/GenxProtocol.java new file mode 100644 index 000000000..c87ba946a --- /dev/null +++ b/src/main/java/org/traccar/protocol/GenxProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class GenxProtocol extends BaseProtocol { + + public GenxProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new GenxProtocolDecoder(GenxProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/GenxProtocolDecoder.java b/src/main/java/org/traccar/protocol/GenxProtocolDecoder.java new file mode 100644 index 000000000..2ae9de7a0 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GenxProtocolDecoder.java @@ -0,0 +1,99 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.text.SimpleDateFormat; + +public class GenxProtocolDecoder extends BaseProtocolDecoder { + + private int[] reportColumns; + + public GenxProtocolDecoder(Protocol protocol) { + super(protocol); + setReportColumns(Context.getConfig().getString(getProtocolName() + ".reportColumns", "1,2,3,4")); + } + + public void setReportColumns(String format) { + String[] columns = format.split(","); + reportColumns = new int[columns.length]; + for (int i = 0; i < columns.length; i++) { + reportColumns[i] = Integer.parseInt(columns[i]); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String[] values = ((String) msg).split(","); + + Position position = new Position(getProtocolName()); + position.setValid(true); + + for (int i = 0; i < Math.min(values.length, reportColumns.length); i++) { + switch (reportColumns[i]) { + case 1: + case 28: + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[i]); + if (deviceSession != null) { + position.setDeviceId(deviceSession.getDeviceId()); + } + break; + case 2: + position.setTime(new SimpleDateFormat("MM/dd/yy HH:mm:ss").parse(values[i])); + break; + case 3: + position.setLatitude(Double.parseDouble(values[i])); + break; + case 4: + position.setLongitude(Double.parseDouble(values[i])); + break; + case 11: + position.set(Position.KEY_IGNITION, values[i].equals("ON")); + break; + case 13: + position.setSpeed(UnitsConverter.knotsFromKph(Integer.parseInt(values[i]))); + break; + case 17: + position.setCourse(Integer.parseInt(values[i])); + break; + case 23: + position.set(Position.KEY_ODOMETER, Double.parseDouble(values[i]) * 1000); + break; + case 27: + position.setAltitude(UnitsConverter.metersFromFeet(Integer.parseInt(values[i]))); + break; + case 46: + position.set(Position.KEY_SATELLITES, Integer.parseInt(values[i])); + break; + default: + break; + } + } + + return position.getDeviceId() != 0 ? position : null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Gl100Protocol.java b/src/main/java/org/traccar/protocol/Gl100Protocol.java new file mode 100644 index 000000000..063e606db --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gl100Protocol.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Gl100Protocol extends BaseProtocol { + + public Gl100Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '\0')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Gl100ProtocolDecoder(Gl100Protocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Gl100ProtocolDecoder(Gl100Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Gl100ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl100ProtocolDecoder.java new file mode 100644 index 000000000..ae0383e5c --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gl100ProtocolDecoder.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class Gl100ProtocolDecoder extends BaseProtocolDecoder { + + public Gl100ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("+RESP:") + .expression("GT...,") + .number("(d{15}),") // imei + .groupBegin() + .number("d+,") // number + .number("d,") // reserved / geofence id + .number("d+") // reserved / geofence alert // battery + .or() + .number("[^,]*") // calling number + .groupEnd(",") + .expression("([01]),") // gps fix + .number("(d+.d),") // speed + .number("(d+),") // course + .number("(-?d+.d),") // altitude + .number("d*,") // gps accuracy + .number("(-?d+.d+),") // longitude + .number("(-?d+.d+),") // latitude + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.contains("AT+GTHBD=") && channel != null) { + String response = "+RESP:GTHBD,GPRS ACTIVE,"; + response += sentence.substring(9, sentence.lastIndexOf(',')); + response += '\0'; + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); // heartbeat response + } + + Parser parser = new Parser(PATTERN, sentence); + 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.setValid(parser.nextInt(0) == 0); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + position.setLatitude(parser.nextDouble(0)); + + position.setTime(parser.nextDateTime()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/Gl200BinaryProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200BinaryProtocolDecoder.java new file mode 100644 index 000000000..c3339bea5 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gl200BinaryProtocolDecoder.java @@ -0,0 +1,403 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.BitBuffer; +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 java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class Gl200BinaryProtocolDecoder extends BaseProtocolDecoder { + + public Gl200BinaryProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private Date decodeTime(ByteBuf buf) { + DateBuilder dateBuilder = new DateBuilder() + .setDate(buf.readUnsignedShort(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + return dateBuilder.getDate(); + } + + public static final int MSG_RSP_LCB = 3; + public static final int MSG_RSP_GEO = 8; + public static final int MSG_RSP_COMPRESSED = 100; + + private List<Position> decodeLocation(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + List<Position> positions = new LinkedList<>(); + + int type = buf.readUnsignedByte(); + + buf.readUnsignedInt(); // mask + buf.readUnsignedShort(); // length + buf.readUnsignedByte(); // device type + buf.readUnsignedShort(); // protocol version + buf.readUnsignedShort(); // firmware version + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.format("%015d", buf.readLong())); + if (deviceSession == null) { + return null; + } + + int battery = buf.readUnsignedByte(); + int power = buf.readUnsignedShort(); + + if (type == MSG_RSP_GEO) { + buf.readUnsignedByte(); // reserved + buf.readUnsignedByte(); // reserved + } + + buf.readUnsignedByte(); // motion status + int satellites = buf.readUnsignedByte(); + + if (type != MSG_RSP_COMPRESSED) { + buf.readUnsignedByte(); // index + } + + if (type == MSG_RSP_LCB) { + buf.readUnsignedByte(); // phone length + for (int b = buf.readUnsignedByte();; b = buf.readUnsignedByte()) { + if ((b & 0xf) == 0xf || (b & 0xf0) == 0xf0) { + break; + } + } + } + + if (type == MSG_RSP_COMPRESSED) { + + int count = buf.readUnsignedShort(); + + BitBuffer bits; + int speed = 0; + int heading = 0; + int latitude = 0; + int longitude = 0; + long time = 0; + + for (int i = 0; i < count; i++) { + + if (time > 0) { + time += 1; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + switch (BitUtil.from(buf.getUnsignedByte(buf.readerIndex()), 8 - 2)) { + case 1: + bits = new BitBuffer(buf.readSlice(3)); + bits.readUnsigned(2); // point attribute + bits.readUnsigned(1); // fix type + speed = bits.readUnsigned(12); + heading = bits.readUnsigned(9); + longitude = buf.readInt(); + latitude = buf.readInt(); + if (time == 0) { + time = buf.readUnsignedInt(); + } + break; + case 2: + bits = new BitBuffer(buf.readSlice(5)); + bits.readUnsigned(2); // point attribute + bits.readUnsigned(1); // fix type + speed += bits.readSigned(7); + heading += bits.readSigned(7); + longitude += bits.readSigned(12); + latitude += bits.readSigned(11); + break; + default: + buf.readUnsignedByte(); // invalid or same + continue; + } + + position.setValid(true); + position.setTime(new Date(time * 1000)); + position.setSpeed(UnitsConverter.knotsFromKph(speed * 0.1)); + position.setCourse(heading); + position.setLongitude(longitude * 0.000001); + position.setLatitude(latitude * 0.000001); + + positions.add(position); + + } + + } else { + + int count = buf.readUnsignedByte(); + + for (int i = 0; i < count; i++) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_BATTERY_LEVEL, battery); + position.set(Position.KEY_POWER, power); + position.set(Position.KEY_SATELLITES, satellites); + + int hdop = buf.readUnsignedByte(); + position.setValid(hdop > 0); + position.set(Position.KEY_HDOP, hdop); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedMedium() * 0.1)); + position.setCourse(buf.readUnsignedShort()); + position.setAltitude(buf.readShort()); + position.setLongitude(buf.readInt() * 0.000001); + position.setLatitude(buf.readInt() * 0.000001); + + position.setTime(decodeTime(buf)); + + position.setNetwork(new Network(CellTower.from( + buf.readUnsignedShort(), buf.readUnsignedShort(), + buf.readUnsignedShort(), buf.readUnsignedShort()))); + + buf.readUnsignedByte(); // reserved + + positions.add(position); + + } + + } + + return positions; + } + + public static final int MSG_EVT_BPL = 6; + public static final int MSG_EVT_VGN = 45; + public static final int MSG_EVT_VGF = 46; + public static final int MSG_EVT_UPD = 15; + public static final int MSG_EVT_IDF = 17; + public static final int MSG_EVT_GSS = 21; + public static final int MSG_EVT_GES = 26; + public static final int MSG_EVT_GPJ = 31; + public static final int MSG_EVT_RMD = 35; + public static final int MSG_EVT_JDS = 33; + public static final int MSG_EVT_CRA = 23; + public static final int MSG_EVT_UPC = 34; + + private Position decodeEvent(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + Position position = new Position(getProtocolName()); + + int type = buf.readUnsignedByte(); + + buf.readUnsignedInt(); // mask + buf.readUnsignedShort(); // length + buf.readUnsignedByte(); // device type + buf.readUnsignedShort(); // protocol version + + position.set(Position.KEY_VERSION_FW, String.valueOf(buf.readUnsignedShort())); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.format("%015d", buf.readLong())); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + position.set(Position.KEY_POWER, buf.readUnsignedShort()); + + buf.readUnsignedByte(); // motion status + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + + switch (type) { + case MSG_EVT_BPL: + buf.readUnsignedShort(); // backup battery voltage + break; + case MSG_EVT_VGN: + case MSG_EVT_VGF: + buf.readUnsignedShort(); // reserved + buf.readUnsignedByte(); // report type + buf.readUnsignedInt(); // ignition duration + break; + case MSG_EVT_UPD: + buf.readUnsignedShort(); // code + buf.readUnsignedByte(); // retry + break; + case MSG_EVT_IDF: + buf.readUnsignedInt(); // idling duration + break; + case MSG_EVT_GSS: + buf.readUnsignedByte(); // gps signal status + buf.readUnsignedInt(); // reserved + break; + case MSG_EVT_GES: + buf.readUnsignedShort(); // trigger geo id + buf.readUnsignedByte(); // trigger geo enable + buf.readUnsignedByte(); // trigger mode + buf.readUnsignedInt(); // radius + buf.readUnsignedInt(); // check interval + break; + case MSG_EVT_GPJ: + buf.readUnsignedByte(); // cw jamming value + buf.readUnsignedByte(); // gps jamming state + break; + case MSG_EVT_RMD: + buf.readUnsignedByte(); // roaming state + break; + case MSG_EVT_JDS: + buf.readUnsignedByte(); // jamming state + break; + case MSG_EVT_CRA: + buf.readUnsignedByte(); // crash counter + break; + case MSG_EVT_UPC: + buf.readUnsignedByte(); // command id + buf.readUnsignedShort(); // result + break; + default: + break; + } + + buf.readUnsignedByte(); // count + + int hdop = buf.readUnsignedByte(); + position.setValid(hdop > 0); + position.set(Position.KEY_HDOP, hdop); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedMedium() * 0.1)); + position.setCourse(buf.readUnsignedShort()); + position.setAltitude(buf.readShort()); + position.setLongitude(buf.readInt() * 0.000001); + position.setLatitude(buf.readInt() * 0.000001); + + position.setTime(decodeTime(buf)); + + position.setNetwork(new Network(CellTower.from( + buf.readUnsignedShort(), buf.readUnsignedShort(), + buf.readUnsignedShort(), buf.readUnsignedShort()))); + + buf.readUnsignedByte(); // reserved + + return position; + } + + public static final int MSG_INF_GPS = 2; + public static final int MSG_INF_CID = 4; + public static final int MSG_INF_CSQ = 5; + public static final int MSG_INF_VER = 6; + public static final int MSG_INF_BAT = 7; + public static final int MSG_INF_TMZ = 9; + public static final int MSG_INF_GIR = 10; + + private Position decodeInformation(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + Position position = new Position(getProtocolName()); + + int type = buf.readUnsignedByte(); + + buf.readUnsignedInt(); // mask + buf.readUnsignedShort(); // length + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.format("%015d", buf.readLong())); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedByte(); // device type + buf.readUnsignedShort(); // protocol version + + position.set(Position.KEY_VERSION_FW, String.valueOf(buf.readUnsignedShort())); + + if (type == MSG_INF_VER) { + buf.readUnsignedShort(); // hardware version + buf.readUnsignedShort(); // mcu version + buf.readUnsignedShort(); // reserved + } + + buf.readUnsignedByte(); // motion status + buf.readUnsignedByte(); // reserved + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + + buf.readUnsignedByte(); // mode + buf.skipBytes(7); // last fix time + buf.readUnsignedByte(); // reserved + buf.readUnsignedByte(); + buf.readUnsignedShort(); // response report mask + buf.readUnsignedShort(); // ign interval + buf.readUnsignedShort(); // igf interval + buf.readUnsignedInt(); // reserved + buf.readUnsignedByte(); // reserved + + if (type == MSG_INF_BAT) { + position.set(Position.KEY_CHARGE, buf.readUnsignedByte() != 0); + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.001); + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.001); + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + } + + buf.skipBytes(10); // iccid + + if (type == MSG_INF_CSQ) { + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + buf.readUnsignedByte(); + } + + buf.readUnsignedByte(); // time zone flags + buf.readUnsignedShort(); // time zone offset + + if (type == MSG_INF_GIR) { + buf.readUnsignedByte(); // gir trigger + buf.readUnsignedByte(); // cell number + position.setNetwork(new Network(CellTower.from( + buf.readUnsignedShort(), buf.readUnsignedShort(), + buf.readUnsignedShort(), buf.readUnsignedShort()))); + buf.readUnsignedByte(); // ta + buf.readUnsignedByte(); // rx level + } + + getLastLocation(position, decodeTime(buf)); + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + switch (buf.readSlice(4).toString(StandardCharsets.US_ASCII)) { + case "+RSP": + return decodeLocation(channel, remoteAddress, buf); + case "+INF": + return decodeInformation(channel, remoteAddress, buf); + case "+EVT": + return decodeEvent(channel, remoteAddress, buf); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Gl200FrameDecoder.java b/src/main/java/org/traccar/protocol/Gl200FrameDecoder.java new file mode 100644 index 000000000..c192cc28d --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gl200FrameDecoder.java @@ -0,0 +1,97 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseFrameDecoder; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class Gl200FrameDecoder extends BaseFrameDecoder { + + private static final int MINIMUM_LENGTH = 11; + + private static final Set<String> BINARY_HEADERS = new HashSet<>( + Arrays.asList("+RSP", "+BSP", "+EVT", "+BVT", "+INF", "+BNF", "+HBD", "+CRD", "+BRD")); + + public static boolean isBinary(ByteBuf buf) { + String header = buf.toString(buf.readerIndex(), 4, StandardCharsets.US_ASCII); + if (header.equals("+ACK")) { + return buf.getByte(buf.readerIndex() + header.length()) != (byte) ':'; + } else { + return BINARY_HEADERS.contains(header); + } + } + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < MINIMUM_LENGTH) { + return null; + } + + if (isBinary(buf)) { + + int length; + switch (buf.toString(buf.readerIndex(), 4, StandardCharsets.US_ASCII)) { + case "+ACK": + length = buf.getUnsignedByte(buf.readerIndex() + 6); + break; + case "+INF": + case "+BNF": + length = buf.getUnsignedShort(buf.readerIndex() + 7); + break; + case "+HBD": + length = buf.getUnsignedByte(buf.readerIndex() + 5); + break; + case "+CRD": + case "+BRD": + length = buf.getUnsignedShort(buf.readerIndex() + 6); + break; + default: + length = buf.getUnsignedShort(buf.readerIndex() + 9); + break; + } + + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + } else { + + int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '$'); + if (endIndex < 0) { + endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0); + } + if (endIndex > 0) { + ByteBuf frame = buf.readRetainedSlice(endIndex - buf.readerIndex()); + buf.readByte(); // delimiter + return frame; + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Gl200Protocol.java b/src/main/java/org/traccar/protocol/Gl200Protocol.java new file mode 100644 index 000000000..e2d0c6d2a --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gl200Protocol.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +import io.netty.handler.codec.string.StringEncoder; + +public class Gl200Protocol extends BaseProtocol { + + public Gl200Protocol() { + setSupportedDataCommands( + Command.TYPE_POSITION_SINGLE, + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME, + Command.TYPE_IDENTIFICATION, + Command.TYPE_REBOOT_DEVICE); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new Gl200FrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new Gl200ProtocolEncoder(Gl200Protocol.this)); + pipeline.addLast(new Gl200ProtocolDecoder(Gl200Protocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new Gl200ProtocolEncoder(Gl200Protocol.this)); + pipeline.addLast(new Gl200ProtocolDecoder(Gl200Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Gl200ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200ProtocolDecoder.java new file mode 100644 index 000000000..ca1df7a13 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gl200ProtocolDecoder.java @@ -0,0 +1,50 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocolDecoder; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import org.traccar.Protocol; + +import java.net.SocketAddress; + +public class Gl200ProtocolDecoder extends BaseProtocolDecoder { + + private final Gl200TextProtocolDecoder textProtocolDecoder; + private final Gl200BinaryProtocolDecoder binaryProtocolDecoder; + + public Gl200ProtocolDecoder(Protocol protocol) { + super(protocol); + textProtocolDecoder = new Gl200TextProtocolDecoder(protocol); + binaryProtocolDecoder = new Gl200BinaryProtocolDecoder(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (Gl200FrameDecoder.isBinary(buf)) { + return binaryProtocolDecoder.decode(channel, remoteAddress, msg); + } else { + return textProtocolDecoder.decode(channel, remoteAddress, msg); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Gl200ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Gl200ProtocolEncoder.java new file mode 100644 index 000000000..dd0672c23 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gl200ProtocolEncoder.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class Gl200ProtocolEncoder extends StringProtocolEncoder { + + public Gl200ProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + initDevicePassword(command, ""); + + switch (command.getType()) { + case Command.TYPE_POSITION_SINGLE: + return formatCommand(command, "AT+GTRTO=%s,1,,,,,,FFFF$", Command.KEY_DEVICE_PASSWORD); + case Command.TYPE_ENGINE_STOP: + return formatCommand(command, "AT+GTOUT=%s,1,,,0,0,0,0,0,0,0,,,,,,,FFFF$", + Command.KEY_DEVICE_PASSWORD); + case Command.TYPE_ENGINE_RESUME: + return formatCommand(command, "AT+GTOUT=%s,0,,,0,0,0,0,0,0,0,,,,,,,FFFF$", + Command.KEY_DEVICE_PASSWORD); + case Command.TYPE_IDENTIFICATION: + return formatCommand(command, "AT+GTRTO=%s,8,,,,,,FFFF$", Command.KEY_DEVICE_PASSWORD); + case Command.TYPE_REBOOT_DEVICE: + return formatCommand(command, "AT+GTRTO=%s,3,,,,,,FFFF$", Command.KEY_DEVICE_PASSWORD); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java new file mode 100644 index 000000000..283dbeb37 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java @@ -0,0 +1,1290 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.LinkedList; +import java.util.List; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { + + private boolean ignoreFixTime; + + public Gl200TextProtocolDecoder(Protocol protocol) { + super(protocol); + + ignoreFixTime = Context.getConfig().getBoolean(getProtocolName() + ".ignoreFixTime"); + } + + private static final Pattern PATTERN_ACK = new PatternBuilder() + .text("+ACK:GT") + .expression("...,") // type + .number("([0-9A-Z]{2}xxxx),") // protocol version + .number("(d{15}|x{14}),") // imei + .any().text(",") + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(xxxx)") // counter + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_INF = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GTINF,") + .number("[0-9A-Z]{2}xxxx,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:[0-9A-Z]{17},)?") // vin + .expression("(?:[^,]+)?,") // device name + .number("(xx),") // state + .expression("(?:[0-9Ff]{20})?,") // iccid + .number("(d{1,2}),") // rssi + .number("d{1,2},") + .expression("[01],") // external power + .number("([d.]+)?,") // odometer or external power + .number("d*,") // backup battery or lightness + .number("(d+.d+),") // battery + .expression("([01]),") // charging + .number("(?:d),") // led + .number("(?:d)?,") // gps on need + .number("(?:d)?,") // gps antenna type + .number("(?:d)?,").optional() // gps antenna state + .number("d{14},") // last fix time + .groupBegin() + .number("(d+),") // battery percentage + .number("[d.]*,") // flash type / power + .number("(-?[d.]+)?,,,") // temperature + .or() + .expression("(?:[01])?,").optional() // pin15 mode + .number("(d+)?,") // adc1 + .number("(d+)?,").optional() // adc2 + .number("(xx)?,") // digital input + .number("(xx)?,") // digital output + .number("[-+]dddd,") // timezone + .expression("[01],") // daylight saving + .groupEnd() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(xxxx)") // counter + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_VER = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GTVER,") + .number("[0-9A-Z]{2}xxxx,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .expression("([^,]*),") // device type + .number("(xxxx),") // firmware version + .number("(xxxx),") // hardware version + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(xxxx)") // counter + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_LOCATION = new PatternBuilder() + .number("(d{1,2}.?d?)?,") // hdop + .number("(d{1,3}.d)?,") // speed + .number("(d{1,3}.?d?)?,") // course + .number("(-?d{1,5}.d)?,") // altitude + .number("(-?d{1,3}.d{6})?,") // longitude + .number("(-?d{1,2}.d{6})?,") // latitude + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(d+)?,") // mcc + .number("(d+)?,") // mnc + .groupBegin() + .number("(d+),") // lac + .number("(d+),") // cid + .or() + .number("(x+)?,") // lac + .number("(x+)?,") // cid + .groupEnd() + .number("(?:d+|(d+.d))?,") // odometer + .compile(); + + private static final Pattern PATTERN_OBD = new PatternBuilder() + .text("+RESP:GTOBD,") + .number("[0-9A-Z]{2}xxxx,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:[0-9A-Z]{17})?,") // vin + .expression("[^,]{0,20},") // device name + .expression("[01],") // report type + .number("x{1,8},") // report mask + .expression("(?:[0-9A-Z]{17})?,") // vin + .number("[01],") // obd connect + .number("(?:d{1,5})?,") // obd voltage + .number("(?:x{8})?,") // support pids + .number("(d{1,5})?,") // engine rpm + .number("(d{1,3})?,") // speed + .number("(-?d{1,3})?,") // coolant temp + .number("(d+.?d*|Inf|NaN)?,") // fuel consumption + .number("(d{1,5})?,") // dtcs cleared distance + .number("(?:d{1,5})?,") + .expression("([01])?,") // obd connect + .number("(d{1,3})?,") // number of dtcs + .number("(x*),") // dtcs + .number("(d{1,3})?,") // throttle + .number("(?:d{1,3})?,") // engine load + .number("(d{1,3})?,") // fuel level + .expression("(?:[0-9A],)?") // obd protocol + .number("(d+),") // odometer + .expression(PATTERN_LOCATION.pattern()) + .number("(d{1,7}.d)?,") // odometer + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_FRI = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GT...,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:([0-9A-Z]{17}),)?") // vin + .expression("[^,]*,") // device name + .number("(d+)?,") // power + .number("d{1,2},").optional() // report type + .number("d{1,2},").optional() // count + .number("d*,").optional() // reserved + .number("(d+),").optional() // battery + .expression("((?:") + .expression(PATTERN_LOCATION.pattern()) + .expression(")+)") + .groupBegin() + .number("(d{1,7}.d)?,") // odometer + .number("(d{5}:dd:dd)?,") // hour meter + .number("(x+)?,") // adc 1 + .number("(x+)?,") // adc 2 + .number("(d{1,3})?,") // battery + .number("(?:(xx)(xx)(xx))?,") // device status + .number("(d+)?,") // rpm + .number("(?:d+.?d*|Inf|NaN)?,") // fuel consumption + .number("(d+)?,") // fuel level + .or() + .number("(d{1,7}.d)?,").optional() // odometer + .number("(d{1,3})?,") // battery + .or() + .number("(-?d),") // rssi + .number("(d{1,3}),") // battery + .groupEnd() + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_ERI = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GTERI,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("(x{8}),") // mask + .number("(d+)?,") // power + .number("d{1,2},") // report type + .number("d{1,2},") // count + .expression("((?:") + .expression(PATTERN_LOCATION.pattern()) + .expression(")+)") + .number("(d{1,7}.d)?,") // odometer + .number("(d{5}:dd:dd)?,") // hour meter + .number("(x+)?,") // adc 1 + .number("(x+)?,").optional() // adc 2 + .number("(d{1,3})?,") // battery + .number("(?:(xx)(xx)(xx))?,") // device status + .expression("(.*)") // additional data + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_IGN = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GTIG[NF],") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("d+,") // ignition off duration + .expression(PATTERN_LOCATION.pattern()) + .number("(d{5}:dd:dd)?,") // hour meter + .number("(d{1,7}.d)?,") // odometer + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_LSW = new PatternBuilder() + .text("+RESP:").expression("GT[LT]SW,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("[01],") // type + .number("([01]),") // state + .expression(PATTERN_LOCATION.pattern()) + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_IDA = new PatternBuilder() + .text("+RESP:GTIDA,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,,") // device name + .number("([^,]+),") // rfid + .expression("[01],") // report type + .number("1,") // count + .expression(PATTERN_LOCATION.pattern()) + .number("(d+.d),") // odometer + .text(",,,,") + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_WIF = new PatternBuilder() + .text("+RESP:GTWIF,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("(d+),") // count + .number("((?:x{12},-?d+,,,,)+),,,,") // wifi + .number("(d{1,3}),") // battery + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_GSM = new PatternBuilder() + .text("+RESP:GTGSM,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:STR|CTN|NMR|RTL),") // fix type + .expression("(.*)") // cells + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_PNA = new PatternBuilder() + .text("+RESP:GT").expression("P[NF]A,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GT...,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("d*,") + .number("(x{1,2}),") // report type + .number("d{1,2},") // count + .expression(PATTERN_LOCATION.pattern()) + .groupBegin() + .number("(d{1,7}.d)?,").optional() // odometer + .number("(d{1,3})?,") // battery + .or() + .number("(d{1,7}.d)?,") // odometer + .groupEnd() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)") // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_BASIC = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF)").text(":") + .expression("GT...,") + .number("(?:[0-9A-Z]{2}xxxx)?,").optional() // protocol version + .number("(d{15}|x{14}),") // imei + .any() + .number("(d{1,2})?,") // hdop + .number("(d{1,3}.d)?,") // speed + .number("(d{1,3})?,") // course + .number("(-?d{1,5}.d)?,") // altitude + .number("(-?d{1,3}.d{6})?,") // longitude + .number("(-?d{1,2}.d{6})?,") // latitude + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(x+),") // lac + .number("(x+),").optional(4) // cell + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private Object decodeAck(Channel channel, SocketAddress remoteAddress, String sentence, String type) { + Parser parser = new Parser(PATTERN_ACK, sentence); + if (parser.matches()) { + String protocolVersion = parser.next(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + if (type.equals("HBD")) { + if (channel != null) { + parser.skip(6); + channel.writeAndFlush(new NetworkMessage( + "+SACK:GTHBD," + protocolVersion + "," + parser.next() + "$", remoteAddress)); + } + } else { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, parser.nextDateTime()); + position.setValid(false); + position.set(Position.KEY_RESULT, "Command " + type + " accepted"); + return position; + } + } + return null; + } + + private Position initPosition(Parser parser, Channel channel, SocketAddress remoteAddress) { + if (parser.matches()) { + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession != null) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + return position; + } + } + return null; + } + + private void decodeDeviceTime(Position position, Parser parser) { + if (parser.hasNext(6)) { + if (ignoreFixTime) { + position.setTime(parser.nextDateTime()); + } else { + position.setDeviceTime(parser.nextDateTime()); + } + } + } + + private Long parseHours(String hoursString) { + if (hoursString != null) { + String[] hours = hoursString.split(":"); + return (long) (Integer.parseInt(hours[0]) * 3600 + + (hours.length > 1 ? Integer.parseInt(hours[1]) * 60 : 0) + + (hours.length > 2 ? Integer.parseInt(hours[2]) : 0)) * 1000; + } + return null; + } + + private Object decodeInf(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_INF, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + switch (parser.nextHexInt()) { + case 0x16: + case 0x1A: + case 0x12: + position.set(Position.KEY_IGNITION, false); + position.set(Position.KEY_MOTION, true); + break; + case 0x11: + position.set(Position.KEY_IGNITION, false); + position.set(Position.KEY_MOTION, false); + break; + case 0x21: + position.set(Position.KEY_IGNITION, true); + position.set(Position.KEY_MOTION, false); + break; + case 0x22: + position.set(Position.KEY_IGNITION, true); + position.set(Position.KEY_MOTION, true); + break; + case 0x41: + position.set(Position.KEY_MOTION, false); + break; + case 0x42: + position.set(Position.KEY_MOTION, true); + break; + default: + break; + } + + position.set(Position.KEY_RSSI, parser.nextInt()); + + parser.next(); // odometer or external power + + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_CHARGE, parser.nextInt() == 1); + + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + + position.set(Position.PREFIX_TEMP + 1, parser.next()); + + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.PREFIX_ADC + 2, parser.next()); + + position.set(Position.KEY_INPUT, parser.next()); + position.set(Position.KEY_OUTPUT, parser.next()); + + getLastLocation(position, parser.nextDateTime()); + + position.set(Position.KEY_INDEX, parser.nextHexInt()); + + return position; + } + + private Object decodeVer(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_VER, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + position.set("deviceType", parser.next()); + position.set(Position.KEY_VERSION_FW, parser.nextHexInt()); + position.set(Position.KEY_VERSION_HW, parser.nextHexInt()); + + getLastLocation(position, parser.nextDateTime()); + + return position; + } + + private void skipLocation(Parser parser) { + parser.skip(19); + } + + private void decodeLocation(Position position, Parser parser) { + Double hdop = parser.nextDouble(); + position.setValid(hdop == null || hdop > 0); + position.set(Position.KEY_HDOP, hdop); + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + if (parser.hasNext(8)) { + position.setValid(true); + position.setLongitude(parser.nextDouble()); + position.setLatitude(parser.nextDouble()); + position.setTime(parser.nextDateTime()); + } else { + getLastLocation(position, null); + } + + if (parser.hasNext(6)) { + int mcc = parser.nextInt(); + int mnc = parser.nextInt(); + if (parser.hasNext(2)) { + position.setNetwork(new Network(CellTower.from(mcc, mnc, parser.nextInt(), parser.nextInt()))); + } + if (parser.hasNext(2)) { + position.setNetwork(new Network(CellTower.from(mcc, mnc, parser.nextHexInt(), parser.nextHexInt()))); + } + } + + if (parser.hasNext()) { + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); + } + } + + private Object decodeObd(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_OBD, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + position.set(Position.KEY_RPM, parser.nextInt()); + position.set(Position.KEY_OBD_SPEED, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, parser.nextInt()); + position.set(Position.KEY_FUEL_CONSUMPTION, parser.next()); + position.set("dtcsClearedDistance", parser.nextInt()); + if (parser.hasNext()) { + position.set("odbConnect", parser.nextInt() == 1); + } + position.set("dtcsNumber", parser.nextInt()); + position.set("dtcsCodes", parser.next()); + position.set(Position.KEY_THROTTLE, parser.nextInt()); + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); + if (parser.hasNext()) { + position.set(Position.KEY_OBD_ODOMETER, parser.nextInt() * 1000); + } + + decodeLocation(position, parser); + + if (parser.hasNext()) { + position.set(Position.KEY_OBD_ODOMETER, (int) (parser.nextDouble() * 1000)); + } + + decodeDeviceTime(position, parser); + + return position; + } + + private Object decodeCan(Channel channel, SocketAddress remoteAddress, String sentence) throws ParseException { + Position position = new Position(getProtocolName()); + + int index = 0; + String[] values = sentence.split(","); + + index += 1; // header + index += 1; // protocol version + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[index++]); + position.setDeviceId(deviceSession.getDeviceId()); + + index += 1; // device name + index += 1; // report type + index += 1; // canbus state + long reportMask = Long.parseLong(values[index++], 16); + long reportMaskExt = 0; + + if (BitUtil.check(reportMask, 0)) { + position.set(Position.KEY_VIN, values[index++]); + } + if (BitUtil.check(reportMask, 1)) { + position.set(Position.KEY_IGNITION, Integer.parseInt(values[index++]) > 0); + } + if (BitUtil.check(reportMask, 2)) { + position.set(Position.KEY_OBD_ODOMETER, values[index++]); + } + if (BitUtil.check(reportMask, 3) && !values[index++].isEmpty()) { + position.set(Position.KEY_FUEL_USED, Double.parseDouble(values[index - 1])); + } + if (BitUtil.check(reportMask, 5) && !values[index++].isEmpty()) { + position.set(Position.KEY_RPM, Integer.parseInt(values[index - 1])); + } + if (BitUtil.check(reportMask, 4) && !values[index++].isEmpty()) { + position.set(Position.KEY_OBD_SPEED, UnitsConverter.knotsFromKph(Integer.parseInt(values[index - 1]))); + } + if (BitUtil.check(reportMask, 6) && !values[index++].isEmpty()) { + position.set(Position.KEY_COOLANT_TEMP, Integer.parseInt(values[index - 1])); + } + if (BitUtil.check(reportMask, 7) && !values[index++].isEmpty()) { + position.set(Position.KEY_FUEL_CONSUMPTION, Double.parseDouble(values[index - 1].substring(1))); + } + if (BitUtil.check(reportMask, 8) && !values[index++].isEmpty()) { + position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(values[index - 1].substring(1))); + } + if (BitUtil.check(reportMask, 9) && !values[index++].isEmpty()) { + position.set("range", Long.parseLong(values[index - 1]) * 100); + } + if (BitUtil.check(reportMask, 10) && !values[index++].isEmpty()) { + position.set(Position.KEY_THROTTLE, Integer.parseInt(values[index - 1])); + } + if (BitUtil.check(reportMask, 11) && !values[index++].isEmpty()) { + position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(Double.parseDouble(values[index - 1]))); + } + if (BitUtil.check(reportMask, 12)) { + position.set("drivingHours", Double.parseDouble(values[index++])); + } + if (BitUtil.check(reportMask, 13)) { + position.set("idleHours", Double.parseDouble(values[index++])); + } + if (BitUtil.check(reportMask, 14) && !values[index++].isEmpty()) { + position.set("idleFuelConsumption", Double.parseDouble(values[index - 1])); + } + if (BitUtil.check(reportMask, 15) && !values[index++].isEmpty()) { + position.set(Position.KEY_AXLE_WEIGHT, Integer.parseInt(values[index - 1])); + } + if (BitUtil.check(reportMask, 16) && !values[index++].isEmpty()) { + position.set("tachographInfo", Integer.parseInt(values[index - 1], 16)); + } + if (BitUtil.check(reportMask, 17) && !values[index++].isEmpty()) { + position.set("indicators", Integer.parseInt(values[index - 1], 16)); + } + if (BitUtil.check(reportMask, 18) && !values[index++].isEmpty()) { + position.set("lights", Integer.parseInt(values[index - 1], 16)); + } + if (BitUtil.check(reportMask, 19) && !values[index++].isEmpty()) { + position.set("doors", Integer.parseInt(values[index - 1], 16)); + } + if (BitUtil.check(reportMask, 20) && !values[index++].isEmpty()) { + position.set("vehicleOverspeed", Double.parseDouble(values[index - 1])); + } + if (BitUtil.check(reportMask, 21) && !values[index++].isEmpty()) { + position.set("engineOverspeed", Double.parseDouble(values[index - 1])); + } + if (BitUtil.check(reportMask, 29)) { + reportMaskExt = Long.parseLong(values[index++], 16); + } + if (BitUtil.check(reportMaskExt, 0) && !values[index++].isEmpty()) { + position.set("adBlueLevel", Integer.parseInt(values[index - 1])); + } + if (BitUtil.check(reportMaskExt, 1) && !values[index++].isEmpty()) { + position.set("axleWeight1", Integer.parseInt(values[index - 1])); + } + if (BitUtil.check(reportMaskExt, 2) && !values[index++].isEmpty()) { + position.set("axleWeight3", Integer.parseInt(values[index - 1])); + } + if (BitUtil.check(reportMaskExt, 3) && !values[index++].isEmpty()) { + position.set("axleWeight4", Integer.parseInt(values[index - 1])); + } + if (BitUtil.check(reportMaskExt, 4)) { + index += 1; // tachograph overspeed + } + if (BitUtil.check(reportMaskExt, 5)) { + index += 1; // tachograph motion + } + if (BitUtil.check(reportMaskExt, 6)) { + index += 1; // tachograph direction + } + if (BitUtil.check(reportMaskExt, 7) && !values[index++].isEmpty()) { + position.set(Position.PREFIX_ADC + 1, Integer.parseInt(values[index - 1]) * 0.001); + } + if (BitUtil.check(reportMaskExt, 8)) { + index += 1; // pedal breaking factor + } + if (BitUtil.check(reportMaskExt, 9)) { + index += 1; // engine breaking factor + } + if (BitUtil.check(reportMaskExt, 10)) { + index += 1; // total accelerator kick-downs + } + if (BitUtil.check(reportMaskExt, 11)) { + index += 1; // total effective engine speed + } + if (BitUtil.check(reportMaskExt, 12)) { + index += 1; // total cruise control time + } + if (BitUtil.check(reportMaskExt, 13)) { + index += 1; // total accelerator kick-down time + } + if (BitUtil.check(reportMaskExt, 14)) { + index += 1; // total brake application + } + if (BitUtil.check(reportMaskExt, 15) && !values[index++].isEmpty()) { + position.set("driver1Card", values[index - 1]); + } + if (BitUtil.check(reportMaskExt, 16) && !values[index++].isEmpty()) { + position.set("driver2Card", values[index - 1]); + } + if (BitUtil.check(reportMaskExt, 17) && !values[index++].isEmpty()) { + position.set("driver1Name", values[index - 1]); + } + if (BitUtil.check(reportMaskExt, 18) && !values[index++].isEmpty()) { + position.set("driver2Name", values[index - 1]); + } + if (BitUtil.check(reportMaskExt, 19) && !values[index++].isEmpty()) { + position.set("registration", values[index - 1]); + } + if (BitUtil.check(reportMaskExt, 20)) { + index += 1; // expansion information + } + if (BitUtil.check(reportMaskExt, 21)) { + index += 1; // rapid brakings + } + if (BitUtil.check(reportMaskExt, 22)) { + index += 1; // rapid accelerations + } + if (BitUtil.check(reportMaskExt, 23)) { + index += 1; // engine torque + } + + DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + + if (BitUtil.check(reportMask, 30)) { + while (values[index].isEmpty()) { + index += 1; + } + position.setValid(Integer.parseInt(values[index++]) > 0); + if (!values[index].isEmpty()) { + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[index++]))); + position.setCourse(Integer.parseInt(values[index++])); + position.setAltitude(Double.parseDouble(values[index++])); + position.setLongitude(Double.parseDouble(values[index++])); + position.setLatitude(Double.parseDouble(values[index++])); + position.setTime(dateFormat.parse(values[index++])); + } else { + index += 6; // no location + getLastLocation(position, null); + } + } else { + getLastLocation(position, null); + } + + if (BitUtil.check(reportMask, 31)) { + index += 4; // cell + index += 1; // reserved + } + + if (ignoreFixTime) { + position.setTime(dateFormat.parse(values[index])); + } else { + position.setDeviceTime(dateFormat.parse(values[index])); + } + + return position; + } + + private void decodeStatus(Position position, Parser parser) { + if (parser.hasNext(3)) { + int ignition = parser.nextHexInt(); + if (BitUtil.check(ignition, 4)) { + position.set(Position.KEY_IGNITION, false); + } else if (BitUtil.check(ignition, 5)) { + position.set(Position.KEY_IGNITION, true); + } + position.set(Position.KEY_INPUT, parser.nextHexInt()); + position.set(Position.KEY_OUTPUT, parser.nextHexInt()); + } + } + + private Object decodeFri(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_FRI, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + LinkedList<Position> positions = new LinkedList<>(); + + String vin = parser.next(); + Integer power = parser.nextInt(); + Integer battery = parser.nextInt(); + + Parser itemParser = new Parser(PATTERN_LOCATION, parser.next()); + while (itemParser.find()) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_VIN, vin); + + decodeLocation(position, itemParser); + + positions.add(position); + } + + Position position = positions.getLast(); + + skipLocation(parser); + + if (power != null && power > 10) { + position.set(Position.KEY_POWER, power * 0.001); // only on some devices + } + if (battery != null) { + position.set(Position.KEY_BATTERY_LEVEL, battery); + } + + if (parser.hasNext()) { + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); + } + position.set(Position.KEY_HOURS, parseHours(parser.next())); + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.PREFIX_ADC + 2, parser.next()); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + + decodeStatus(position, parser); + + position.set(Position.KEY_RPM, parser.nextInt()); + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); + + if (parser.hasNext()) { + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); + } + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + + decodeDeviceTime(position, parser); + if (ignoreFixTime) { + positions.clear(); + positions.add(position); + } + + return positions; + } + + private Object decodeEri(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_ERI, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + long mask = parser.nextHexLong(); + + LinkedList<Position> positions = new LinkedList<>(); + + Integer power = parser.nextInt(); + + Parser itemParser = new Parser(PATTERN_LOCATION, parser.next()); + while (itemParser.find()) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + decodeLocation(position, itemParser); + + positions.add(position); + } + + Position position = positions.getLast(); + + skipLocation(parser); + + if (power != null) { + position.set(Position.KEY_POWER, power * 0.001); + } + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); + position.set(Position.KEY_HOURS, parseHours(parser.next())); + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.PREFIX_ADC + 2, parser.next()); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + + decodeStatus(position, parser); + + int index = 0; + String[] data = parser.next().split(","); + + index += 1; // device type + + if (BitUtil.check(mask, 0)) { + index += 1; // digital fuel sensor data + } + + if (BitUtil.check(mask, 1)) { + int deviceCount = Integer.parseInt(data[index++]); + for (int i = 1; i <= deviceCount; i++) { + index += 1; // id + index += 1; // type + if (!data[index++].isEmpty()) { + position.set(Position.PREFIX_TEMP + i, (short) Integer.parseInt(data[index - 1], 16) * 0.0625); + } + } + } + + if (BitUtil.check(mask, 2)) { + index += 1; // can data + } + + if (BitUtil.check(mask, 3) || BitUtil.check(mask, 4)) { + int deviceCount = Integer.parseInt(data[index++]); + for (int i = 1; i <= deviceCount; i++) { + index += 1; // type + if (BitUtil.check(mask, 3)) { + position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(data[index++])); + } + if (BitUtil.check(mask, 4)) { + index += 1; // volume + } + } + } + + decodeDeviceTime(position, parser); + if (ignoreFixTime) { + positions.clear(); + positions.add(position); + } + + return positions; + } + + private Object decodeIgn(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_IGN, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + decodeLocation(position, parser); + + position.set(Position.KEY_IGNITION, sentence.contains("IGN")); + position.set(Position.KEY_HOURS, parseHours(parser.next())); + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); + + decodeDeviceTime(position, parser); + + return position; + } + + private Object decodeLsw(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_LSW, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + position.set(Position.PREFIX_IN + (sentence.contains("LSW") ? 1 : 2), parser.nextInt() == 1); + + decodeLocation(position, parser); + + decodeDeviceTime(position, parser); + + return position; + } + + private Object decodeIda(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_IDA, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + + decodeLocation(position, parser); + + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); + + decodeDeviceTime(position, parser); + + return position; + } + + private Object decodeWif(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_WIF, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + getLastLocation(position, null); + + Network network = new Network(); + + parser.nextInt(); // count + Matcher matcher = Pattern.compile("([0-9a-fA-F]{12}),(-?\\d+),,,,").matcher(parser.next()); + while (matcher.find()) { + String mac = matcher.group(1).replaceAll("(..)", "$1:"); + network.addWifiAccessPoint(WifiAccessPoint.from( + mac.substring(0, mac.length() - 1), Integer.parseInt(matcher.group(2)))); + } + + position.setNetwork(network); + + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + + return position; + } + + private Object decodeGsm(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_GSM, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + getLastLocation(position, null); + + Network network = new Network(); + + String[] data = parser.next().split(","); + for (int i = 0; i < 6; i++) { + if (!data[i * 6].isEmpty()) { + network.addCellTower(CellTower.from( + Integer.parseInt(data[i * 6]), Integer.parseInt(data[i * 6 + 1]), + Integer.parseInt(data[i * 6 + 2], 16), Integer.parseInt(data[i * 6 + 3], 16), + Integer.parseInt(data[i * 6 + 4]))); + } + } + + position.setNetwork(network); + + return position; + } + + private Object decodePna(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_PNA, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + getLastLocation(position, null); + + position.set(Position.KEY_ALARM, sentence.contains("PNA") ? Position.ALARM_POWER_ON : Position.ALARM_POWER_OFF); + + return position; + } + + private Object decodeOther(Channel channel, SocketAddress remoteAddress, String sentence, String type) { + Parser parser = new Parser(PATTERN, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + int reportType = parser.nextHexInt(); + if (type.equals("NMR")) { + position.set(Position.KEY_MOTION, reportType == 1); + } else if (type.equals("SOS")) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } else if (type.equals("DIS")) { + position.set(Position.PREFIX_IN + reportType / 0x10, reportType % 0x10 == 1); + } else if (type.equals("IGL")) { + position.set(Position.KEY_IGNITION, reportType % 0x10 == 1); + } else if (type.equals("HBM")) { + switch (reportType % 0x10) { + case 0: + case 3: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 1: + case 4: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 2: + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + break; + default: + break; + } + } + + decodeLocation(position, parser); + + if (parser.hasNext()) { + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); + } + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + + if (parser.hasNext()) { + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); + } + + decodeDeviceTime(position, parser); + + if (Context.getConfig().getBoolean(getProtocolName() + ".ack") && channel != null) { + channel.writeAndFlush(new NetworkMessage("+SACK:" + parser.next() + "$", remoteAddress)); + } + + return position; + } + + private Object decodeBasic(Channel channel, SocketAddress remoteAddress, String sentence, String type) { + Parser parser = new Parser(PATTERN_BASIC, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + if (parser.hasNext()) { + int hdop = parser.nextInt(); + position.setValid(hdop > 0); + position.set(Position.KEY_HDOP, hdop); + } + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + if (parser.hasNext(2)) { + position.setLongitude(parser.nextDouble()); + position.setLatitude(parser.nextDouble()); + } else { + getLastLocation(position, null); + } + + if (parser.hasNext(6)) { + position.setTime(parser.nextDateTime()); + } + + if (parser.hasNext(4)) { + position.setNetwork(new Network(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt()))); + } + + decodeDeviceTime(position, parser); + + switch (type) { + case "TOW": + position.set(Position.KEY_ALARM, Position.ALARM_TOW); + break; + case "IDL": + position.set(Position.KEY_ALARM, Position.ALARM_IDLE); + break; + case "PNA": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_ON); + break; + case "PFA": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_OFF); + break; + case "EPN": + case "MPN": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_RESTORED); + break; + case "EPF": + case "MPF": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + break; + case "BPL": + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + case "STT": + position.set(Position.KEY_ALARM, Position.ALARM_MOVEMENT); + break; + case "SWG": + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE); + break; + case "TMP": + case "TEM": + position.set(Position.KEY_ALARM, Position.ALARM_TEMPERATURE); + break; + case "JDR": + case "JDS": + position.set(Position.KEY_ALARM, Position.ALARM_JAMMING); + break; + default: + break; + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = ((ByteBuf) msg).toString(StandardCharsets.US_ASCII); + + int typeIndex = sentence.indexOf(":GT"); + if (typeIndex < 0) { + return null; + } + + Object result; + String type = sentence.substring(typeIndex + 3, typeIndex + 6); + if (sentence.startsWith("+ACK")) { + result = decodeAck(channel, remoteAddress, sentence, type); + } else { + switch (type) { + case "INF": + result = decodeInf(channel, remoteAddress, sentence); + break; + case "OBD": + result = decodeObd(channel, remoteAddress, sentence); + break; + case "CAN": + result = decodeCan(channel, remoteAddress, sentence); + break; + case "CTN": + case "FRI": + case "GEO": + case "RTL": + case "DOG": + case "STR": + result = decodeFri(channel, remoteAddress, sentence); + break; + case "ERI": + result = decodeEri(channel, remoteAddress, sentence); + break; + case "IGN": + case "IGF": + result = decodeIgn(channel, remoteAddress, sentence); + break; + case "LSW": + case "TSW": + result = decodeLsw(channel, remoteAddress, sentence); + break; + case "IDA": + result = decodeIda(channel, remoteAddress, sentence); + break; + case "WIF": + result = decodeWif(channel, remoteAddress, sentence); + break; + case "GSM": + result = decodeGsm(channel, remoteAddress, sentence); + break; + case "VER": + result = decodeVer(channel, remoteAddress, sentence); + break; + case "PNA": + case "PFA": + result = decodePna(channel, remoteAddress, sentence); + break; + default: + result = decodeOther(channel, remoteAddress, sentence, type); + break; + } + + if (result == null) { + result = decodeBasic(channel, remoteAddress, sentence, type); + } + + if (result != null) { + if (result instanceof Position) { + ((Position) result).set(Position.KEY_TYPE, type); + } else { + for (Position p : (List<Position>) result) { + p.set(Position.KEY_TYPE, type); + } + } + } + } + + return result; + } + +} diff --git a/src/main/java/org/traccar/protocol/GlobalSatProtocol.java b/src/main/java/org/traccar/protocol/GlobalSatProtocol.java new file mode 100644 index 000000000..7243be72a --- /dev/null +++ b/src/main/java/org/traccar/protocol/GlobalSatProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class GlobalSatProtocol extends BaseProtocol { + + public GlobalSatProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "!\r\n", "!")); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new GlobalSatProtocolDecoder(GlobalSatProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/GlobalSatProtocolDecoder.java b/src/main/java/org/traccar/protocol/GlobalSatProtocolDecoder.java new file mode 100644 index 000000000..b48df4047 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GlobalSatProtocolDecoder.java @@ -0,0 +1,322 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class GlobalSatProtocolDecoder extends BaseProtocolDecoder { + + private String format0; + private String format1; + + public GlobalSatProtocolDecoder(Protocol protocol) { + super(protocol); + + format0 = Context.getConfig().getString(getProtocolName() + ".format0", "TSPRXAB27GHKLMnaicz*U!"); + format1 = Context.getConfig().getString(getProtocolName() + ".format1", "SARY*U!"); + } + + public void setFormat0(String format) { + format0 = format; + } + + public void setFormat1(String format) { + format1 = format; + } + + private Double decodeVoltage(String value) { + if (value.endsWith("mV")) { + return Integer.parseInt(value.substring(0, value.length() - 2)) / 1000.0; + } else if (value.endsWith("V")) { + return Double.parseDouble(value.substring(0, value.length() - 1)); + } else { + return null; + } + } + + private String decodeAlarm(int value) { + if (BitUtil.check(value, 0)) { + return Position.ALARM_SOS; + } + if (BitUtil.check(value, 3) || BitUtil.check(value, 4)) { + return Position.ALARM_GEOFENCE; + } + if (BitUtil.check(value, 5)) { + return Position.ALARM_OVERSPEED; + } + if (BitUtil.check(value, 6)) { + return Position.ALARM_POWER_CUT; + } + if (BitUtil.check(value, 7)) { + return Position.ALARM_LOW_POWER; + } + return null; + } + + private Position decodeOriginal(Channel channel, SocketAddress remoteAddress, String sentence) { + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("ACK\r", remoteAddress)); + } + + String format; + if (sentence.startsWith("GSr") || sentence.startsWith("GSb")) { + format = format0; + } else if (sentence.startsWith("GSh")) { + format = format1; + } else { + return null; + } + + // Check that message contains required parameters + if (!format.contains("B") || !format.contains("S") || !(format.contains("1") + || format.contains("2") || format.contains("3")) || !(format.contains("6") + || format.contains("7") || format.contains("8"))) { + return null; + } + + if (format.contains("*")) { + format = format.substring(0, format.indexOf('*')); + sentence = sentence.substring(0, sentence.indexOf('*')); + } + String[] values = sentence.split(","); + + Position position = new Position(getProtocolName()); + + CellTower cellTower = new CellTower(); + + for (int formatIndex = 0, valueIndex = 1; formatIndex < format.length() + && valueIndex < values.length; formatIndex++) { + String value = values[valueIndex].replace("\"", ""); + + switch (format.charAt(formatIndex)) { + case 'S': + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, value); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + break; + case 'A': + if (value.isEmpty()) { + position.setValid(false); + } else { + position.setValid(Integer.parseInt(value) != 1); + } + break; + case 'B': + DateBuilder dateBuilder = new DateBuilder() + .setDay(Integer.parseInt(value.substring(0, 2))) + .setMonth(Integer.parseInt(value.substring(2, 4))) + .setYear(Integer.parseInt(value.substring(4))); + value = values[++valueIndex]; + dateBuilder + .setHour(Integer.parseInt(value.substring(0, 2))) + .setMinute(Integer.parseInt(value.substring(2, 4))) + .setSecond(Integer.parseInt(value.substring(4))); + position.setTime(dateBuilder.getDate()); + break; + case 'C': + valueIndex += 1; + break; + case '1': + double longitude = Double.parseDouble(value.substring(1)); + if (value.charAt(0) == 'W') { + longitude = -longitude; + } + position.setLongitude(longitude); + break; + case '2': + longitude = Double.parseDouble(value.substring(4)) / 60; + longitude += Integer.parseInt(value.substring(1, 4)); + if (value.charAt(0) == 'W') { + longitude = -longitude; + } + position.setLongitude(longitude); + break; + case '3': + position.setLongitude(Double.parseDouble(value) * 0.000001); + break; + case '6': + double latitude = Double.parseDouble(value.substring(1)); + if (value.charAt(0) == 'S') { + latitude = -latitude; + } + position.setLatitude(latitude); + break; + case '7': + latitude = Double.parseDouble(value.substring(3)) / 60; + latitude += Integer.parseInt(value.substring(1, 3)); + if (value.charAt(0) == 'S') { + latitude = -latitude; + } + position.setLatitude(latitude); + break; + case '8': + position.setLatitude(Double.parseDouble(value) * 0.000001); + break; + case 'G': + position.setAltitude(Double.parseDouble(value)); + break; + case 'H': + position.setSpeed(Double.parseDouble(value)); + break; + case 'I': + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(value))); + break; + case 'J': + position.setSpeed(UnitsConverter.knotsFromMph(Double.parseDouble(value))); + break; + case 'K': + position.setCourse(Double.parseDouble(value)); + break; + case 'L': + position.set(Position.KEY_SATELLITES, Integer.parseInt(value)); + break; + case 'P': + if (value.length() == 4) { + position.set(Position.KEY_ALARM, decodeAlarm(Integer.parseInt(value, 16))); + } + break; + case 'Z': + if (!value.isEmpty()) { + position.set("geofence", value); + } + break; + case 'Y': + int io = Integer.parseInt(value, 16); + position.set(Position.PREFIX_IN + 1, BitUtil.check(io, 1)); + position.set(Position.KEY_MOTION, BitUtil.check(io, 7)); + position.set(Position.PREFIX_OUT + 1, BitUtil.check(io, 9)); + position.set(Position.KEY_IGNITION, BitUtil.check(io, 13)); + position.set(Position.KEY_CHARGE, BitUtil.check(io, 15)); + break; + case 'c': + cellTower.setSignalStrength(Integer.parseInt(value)); + break; + case 'm': + position.set(Position.KEY_POWER, decodeVoltage(value)); + break; + case 'n': + case 'N': + position.set(Position.KEY_BATTERY, decodeVoltage(value)); + break; + case 't': + cellTower.setMobileCountryCode(Integer.parseInt(value)); + break; + case 'u': + cellTower.setMobileNetworkCode(Integer.parseInt(value)); + break; + case 'v': + cellTower.setLocationAreaCode(Integer.parseInt(value, 16)); + break; + case 'w': + cellTower.setCellId(Long.parseLong(value, 16)); + break; + default: + // Unsupported + break; + } + + valueIndex += 1; + } + + if (cellTower.getCellId() != null) { + position.setNetwork(new Network(cellTower)); + } + + return position; + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$") + .number("(d+),") // imei + .number("d+,") // mode + .number("(d+),") // fix + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([EW])") + .number("(ddd)(dd.d+),") // longitude (dddmm.mmmm) + .expression("([NS])") + .number("(dd)(dd.d+),") // latitude (ddmm.mmmm) + .number("(d+.?d*),") // altitude + .number("(d+.?d*),") // speed + .number("(d+.?d*)?,") // course + .number("(d+)[,*]") // satellites + .number("(d+.?d*)") // hdop + .compile(); + + private Position decodeAlternative(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN, sentence); + 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.setValid(!parser.next().equals("1")); + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setAltitude(parser.nextDouble(0)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + position.set(Position.KEY_HDOP, parser.nextDouble()); + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.startsWith("GS")) { + return decodeOriginal(channel, remoteAddress, sentence); + } else if (sentence.startsWith("$")) { + return decodeAlternative(channel, remoteAddress, sentence); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/GlobalstarProtocol.java b/src/main/java/org/traccar/protocol/GlobalstarProtocol.java new file mode 100644 index 000000000..84cd5565b --- /dev/null +++ b/src/main/java/org/traccar/protocol/GlobalstarProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class GlobalstarProtocol extends BaseProtocol { + + public GlobalstarProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(65535)); + pipeline.addLast(new GlobalstarProtocolDecoder(GlobalstarProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java new file mode 100644 index 000000000..26af8d5af --- /dev/null +++ b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java @@ -0,0 +1,163 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +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 org.traccar.BaseHttpProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.DataConverter; +import org.traccar.model.Position; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import java.net.SocketAddress; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder { + + private DocumentBuilder documentBuilder; + private XPath xPath; + private XPathExpression messageExpression; + + public GlobalstarProtocolDecoder(Protocol protocol) { + super(protocol); + try { + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + builderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + builderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); + builderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + builderFactory.setXIncludeAware(false); + builderFactory.setExpandEntityReferences(false); + documentBuilder = builderFactory.newDocumentBuilder(); + xPath = XPathFactory.newInstance().newXPath(); + messageExpression = xPath.compile("//stuMessages/stuMessage"); + } catch (ParserConfigurationException | XPathExpressionException e) { + throw new RuntimeException(e); + } + } + + private void sendResponse(Channel channel, String messageId) throws TransformerException { + + Document document = documentBuilder.newDocument(); + Element rootElement = document.createElement("stuResponseMsg"); + rootElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + rootElement.setAttribute( + "xsi:noNamespaceSchemaLocation", "http://cody.glpconnect.com/XSD/StuResponse_Rev1_0.xsd"); + rootElement.setAttribute("deliveryTimeStamp", new SimpleDateFormat("dd/MM/yyyy hh:mm:ss z").format(new Date())); + rootElement.setAttribute("messageID", "00000000000000000000000000000000"); + rootElement.setAttribute("correlationID", messageId); + document.appendChild(rootElement); + + Element state = document.createElement("state"); + state.appendChild(document.createTextNode("pass")); + rootElement.appendChild(state); + + Element stateMessage = document.createElement("stateMessage"); + stateMessage.appendChild(document.createTextNode("Messages received and stored successfully")); + 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) { + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + + Document document = documentBuilder.parse(new ByteBufferBackedInputStream(request.content().nioBuffer())); + NodeList nodes = (NodeList) messageExpression.evaluate(document, XPathConstants.NODESET); + + List<Position> positions = new LinkedList<>(); + + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, xPath.evaluate("esn", node)); + if (deviceSession != null) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + position.setTime(new Date(Long.parseLong(xPath.evaluate("unixTime", node)) * 1000)); + + ByteBuf buf = Unpooled.wrappedBuffer( + DataConverter.parseHex(xPath.evaluate("payload", node).substring(2))); + + buf.readUnsignedByte(); // flags + + position.setLatitude(buf.readUnsignedMedium() * 90.0 / (1 << 23)); + if (position.getLatitude() > 90) { + position.setLatitude(position.getLatitude() - 180); + } + + position.setLongitude(buf.readUnsignedMedium() * 180.0 / (1 << 23)); + if (position.getLongitude() > 180) { + position.setLongitude(position.getLongitude() - 360); + } + + buf.readUnsignedByte(); // status + buf.readUnsignedByte(); // status + + positions.add(position); + + } + } + + sendResponse(channel, document.getFirstChild().getAttributes().getNamedItem("messageID").getNodeValue()); + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/GnxProtocol.java b/src/main/java/org/traccar/protocol/GnxProtocol.java new file mode 100644 index 000000000..3576bf805 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GnxProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class GnxProtocol extends BaseProtocol { + + public GnxProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "\n\r")); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new GnxProtocolDecoder(GnxProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/GnxProtocolDecoder.java b/src/main/java/org/traccar/protocol/GnxProtocolDecoder.java new file mode 100644 index 000000000..c9c221a69 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GnxProtocolDecoder.java @@ -0,0 +1,111 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class GnxProtocolDecoder extends BaseProtocolDecoder { + + public GnxProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_LOCATION = new PatternBuilder() + .number("(d+),") // imei + .number("d+,") // length + .expression("([01]),") // history + .number("(dd)(dd)(dd),") // device time (hhmmss) + .number("(dd)(dd)(dd),") // device date (ddmmyy) + .number("(dd)(dd)(dd),") // fix time (hhmmss) + .number("(dd)(dd)(dd),") // fix date (ddmmyy) + .number("(d),") // valid + .number("(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd.d+),") // longitude + .expression("([EW]),") + .compile(); + + private static final Pattern PATTERN_MIF = new PatternBuilder() + .text("$GNX_MIF,") + .expression(PATTERN_LOCATION.pattern()) + .expression("[01],") // valid card + .expression("([^,]+),") // rfid + .any() + .compile(); + + private static final Pattern PATTERN_OTHER = new PatternBuilder() + .text("$GNX_") + .expression("...,") + .expression(PATTERN_LOCATION.pattern()) + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + String type = sentence.substring(5, 8); + + Pattern pattern; + if (type.equals("MIF")) { + pattern = PATTERN_MIF; + } else { + pattern = PATTERN_OTHER; + } + + Parser parser = new Parser(pattern, sentence); + 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()); + + if (parser.nextInt(0) == 1) { + position.set(Position.KEY_ARCHIVE, true); + } + + position.setDeviceTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY, "GMT+5:30")); + position.setFixTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY, "GMT+5:30")); + + position.setValid(parser.nextInt(0) != 0); + + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + + if (type.equals("MIF")) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/GoSafeProtocol.java b/src/main/java/org/traccar/protocol/GoSafeProtocol.java new file mode 100644 index 000000000..853b78a16 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GoSafeProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class GoSafeProtocol extends BaseProtocol { + + public GoSafeProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new GoSafeProtocolDecoder(GoSafeProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java b/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java new file mode 100644 index 000000000..76278070e --- /dev/null +++ b/src/main/java/org/traccar/protocol/GoSafeProtocolDecoder.java @@ -0,0 +1,261 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +public class GoSafeProtocolDecoder extends BaseProtocolDecoder { + + public GoSafeProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("*GS") // header + .number("d+,") // protocol version + .number("(d+),") // imei + .expression("([^#]*)#?") // data + .compile(); + + private static final Pattern PATTERN_OLD = new PatternBuilder() + .text("*GS") // header + .number("d+,") // protocol version + .number("(d+),") // imei + .text("GPS:") + .number("(dd)(dd)(dd);") // time (hhmmss) + .number("d;").optional() // fix type + .expression("([AV]);") // validity + .number("([NS])(d+.d+);") // latitude + .number("([EW])(d+.d+);") // longitude + .number("(d+)?;") // speed + .number("(d+);") // course + .number("(d+.?d*)").optional() // hdop + .number("(dd)(dd)(dd)") // date (ddmmyy) + .any() + .compile(); + + private void decodeFragment(Position position, String fragment) { + + int dataIndex = fragment.indexOf(':'); + int index = 0; + String[] values; + if (fragment.length() == dataIndex + 1) { + values = new String[0]; + } else { + values = fragment.substring(dataIndex + 1).split(";"); + } + + switch (fragment.substring(0, dataIndex)) { + case "GPS": + position.setValid(values[index++].equals("A")); + position.set(Position.KEY_SATELLITES, Integer.parseInt(values[index++])); + position.setLatitude(Double.parseDouble(values[index].substring(1))); + if (values[index++].charAt(0) == 'S') { + position.setLatitude(-position.getLatitude()); + } + position.setLongitude(Double.parseDouble(values[index].substring(1))); + if (values[index++].charAt(0) == 'W') { + position.setLongitude(-position.getLongitude()); + } + if (!values[index++].isEmpty()) { + position.setSpeed(UnitsConverter.knotsFromKph(Integer.parseInt(values[index - 1]))); + } + position.setCourse(Integer.parseInt(values[index++])); + if (index < values.length) { + position.setAltitude(Integer.parseInt(values[index++])); + } + if (index < values.length) { + position.set(Position.KEY_HDOP, Double.parseDouble(values[index++])); + } + if (index < values.length) { + position.set(Position.KEY_VDOP, Double.parseDouble(values[index++])); + } + break; + case "GSM": + index += 1; // registration status + index += 1; // signal strength + position.setNetwork(new Network(CellTower.from( + Integer.parseInt(values[index++]), Integer.parseInt(values[index++]), + Integer.parseInt(values[index++], 16), Integer.parseInt(values[index++], 16), + Integer.parseInt(values[index++])))); + break; + case "COT": + if (index < values.length) { + position.set(Position.KEY_ODOMETER, Long.parseLong(values[index++])); + } + if (index < values.length) { + String[] hours = values[index].split("-"); + position.set(Position.KEY_HOURS, (Integer.parseInt(hours[0]) * 3600 + + (hours.length > 1 ? Integer.parseInt(hours[1]) * 60 : 0) + + (hours.length > 2 ? Integer.parseInt(hours[2]) : 0)) * 1000); + } + break; + case "ADC": + position.set(Position.KEY_POWER, Double.parseDouble(values[index++])); + if (index < values.length) { + position.set(Position.KEY_BATTERY, Double.parseDouble(values[index++])); + } + if (index < values.length) { + position.set(Position.PREFIX_ADC + 1, Double.parseDouble(values[index++])); + } + if (index < values.length) { + position.set(Position.PREFIX_ADC + 2, Double.parseDouble(values[index++])); + } + break; + case "DTT": + position.set(Position.KEY_STATUS, Integer.parseInt(values[index++], 16)); + if (!values[index++].isEmpty()) { + int io = Integer.parseInt(values[index - 1], 16); + position.set(Position.KEY_IGNITION, BitUtil.check(io, 0)); + position.set(Position.PREFIX_IN + 1, BitUtil.check(io, 1)); + position.set(Position.PREFIX_IN + 2, BitUtil.check(io, 2)); + position.set(Position.PREFIX_IN + 3, BitUtil.check(io, 3)); + position.set(Position.PREFIX_IN + 4, BitUtil.check(io, 4)); + position.set(Position.PREFIX_OUT + 1, BitUtil.check(io, 5)); + position.set(Position.PREFIX_OUT + 2, BitUtil.check(io, 6)); + position.set(Position.PREFIX_OUT + 3, BitUtil.check(io, 7)); + } + position.set(Position.KEY_GEOFENCE, values[index++] + values[index++]); + position.set("eventStatus", values[index++]); + if (index < values.length) { + position.set("packetType", values[index++]); + } + break; + case "ETD": + position.set("eventData", values[index++]); + break; + case "OBD": + position.set("obd", values[index++]); + break; + case "TAG": + position.set("tagData", values[index++]); + break; + case "IWD": + while (index < values.length) { + int sensorIndex = Integer.parseInt(values[index++]); + int dataType = Integer.parseInt(values[index++]); + if (dataType == 0) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, values[index++]); + } else if (dataType == 1) { + index += 1; // temperature sensor serial number + position.set(Position.PREFIX_TEMP + sensorIndex, Double.parseDouble(values[index++])); + } + } + break; + default: + break; + } + } + + private Position decodePosition(DeviceSession deviceSession, String sentence) throws ParseException { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int index = 0; + String[] fragments = sentence.split(","); + + position.setTime(new SimpleDateFormat("HHmmssddMMyy").parse(fragments[index++])); + + for (; index < fragments.length; index += 1) { + if (!fragments[index].isEmpty()) { + if (fragments[index].matches("\\p{XDigit}+")) { + position.set(Position.KEY_EVENT, Integer.parseInt(fragments[index], 16)); + } else { + decodeFragment(position, fragments[index]); + } + } + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("1234", remoteAddress)); + } + + String sentence = (String) msg; + Pattern pattern = PATTERN; + if (sentence.startsWith("*GS02")) { + pattern = PATTERN_OLD; + } + + Parser parser = new Parser(pattern, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + if (pattern == PATTERN_OLD) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_HDOP, parser.next()); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + return position; + + } else { + + List<Position> positions = new LinkedList<>(); + for (String item : parser.next().split("\\$")) { + positions.add(decodePosition(deviceSession, item)); + } + return positions; + + } + } + +} diff --git a/src/main/java/org/traccar/protocol/GotopProtocol.java b/src/main/java/org/traccar/protocol/GotopProtocol.java new file mode 100644 index 000000000..07fe02248 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GotopProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class GotopProtocol extends BaseProtocol { + + public GotopProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new GotopProtocolDecoder(GotopProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/GotopProtocolDecoder.java b/src/main/java/org/traccar/protocol/GotopProtocolDecoder.java new file mode 100644 index 000000000..a867451aa --- /dev/null +++ b/src/main/java/org/traccar/protocol/GotopProtocolDecoder.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class GotopProtocolDecoder extends BaseProtocolDecoder { + + public GotopProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("(d+),") // imei + .expression("[^,]+,") // type + .expression("([AV]),") // validity + .number("DATE:(dd)(dd)(dd),") // date (yyddmm) + .number("TIME:(dd)(dd)(dd),") // time (hhmmss) + .number("LAT:(d+.d+)([NS]),") // latitude + .number("LO[NT]:(d+.d+)([EW]),") // longitude + .text("Speed:").number("(d+.d+),") // speed + .expression("([^,]+),") // status + .number("(d+)?") // course + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(parser.next().equals("A")); + + position.setTime(parser.nextDateTime()); + + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + + position.set(Position.KEY_STATUS, parser.next()); + + position.setCourse(parser.nextDouble(0)); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/Gps056FrameDecoder.java b/src/main/java/org/traccar/protocol/Gps056FrameDecoder.java new file mode 100644 index 000000000..0d84bf231 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gps056FrameDecoder.java @@ -0,0 +1,47 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +import java.nio.charset.StandardCharsets; + +public class Gps056FrameDecoder extends BaseFrameDecoder { + + private static final int MESSAGE_HEADER = 4; + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() >= MESSAGE_HEADER) { + int length = Integer.parseInt(buf.toString(2, 2, StandardCharsets.US_ASCII)) + 5; + if (buf.readableBytes() >= length) { + ByteBuf frame = buf.readRetainedSlice(length); + while (buf.isReadable() && buf.getUnsignedByte(buf.readerIndex()) != '$') { + buf.readByte(); + } + return frame; + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Gps056Protocol.java b/src/main/java/org/traccar/protocol/Gps056Protocol.java new file mode 100644 index 000000000..b6ab10a19 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gps056Protocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Gps056Protocol extends BaseProtocol { + + public Gps056Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new Gps056FrameDecoder()); + pipeline.addLast(new Gps056ProtocolDecoder(Gps056Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Gps056ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gps056ProtocolDecoder.java new file mode 100644 index 000000000..0ba79bb51 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gps056ProtocolDecoder.java @@ -0,0 +1,140 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; + +public class Gps056ProtocolDecoder extends BaseProtocolDecoder { + + public Gps056ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static void sendResponse(Channel channel, String type, String imei, ByteBuf content) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + String header = "*" + type + imei; + response.writeBytes(header.getBytes(StandardCharsets.US_ASCII)); + if (content != null) { + response.writeBytes(content); + } + response.writeByte('#'); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + private static double decodeCoordinate(ByteBuf buf) { + double degrees = buf.getUnsignedShort(buf.readerIndex()) / 100; + double minutes = buf.readUnsignedShort() % 100 + buf.readUnsignedShort() * 0.0001; + degrees += minutes / 60; + byte hemisphere = buf.readByte(); + if (hemisphere == 'S' || hemisphere == 'W') { + degrees = -degrees; + } + return degrees; + } + + private static void decodeStatus(ByteBuf buf, Position position) { + + position.set(Position.KEY_INPUT, buf.readUnsignedByte()); + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + + position.set(Position.PREFIX_ADC + 1, buf.readShortLE() * 5.06); // mV + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + buf.skipBytes(2); // length + + String type = buf.readSlice(7).toString(StandardCharsets.US_ASCII); + String imei = buf.readSlice(15).toString(StandardCharsets.US_ASCII); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + if (type.startsWith("LOGN")) { + + ByteBuf content = Unpooled.copiedBuffer("1", StandardCharsets.US_ASCII); + try { + sendResponse(channel, "LGSA" + type.substring(4), imei, content); + } finally { + content.release(); + } + + } else if (type.startsWith("GPSL")) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setDateReverse(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + + position.setValid(true); + position.setTime(dateBuilder.getDate()); + position.setLatitude(decodeCoordinate(buf)); + position.setLongitude(decodeCoordinate(buf)); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.setCourse(buf.readUnsignedShort()); + + decodeStatus(buf, position); + + sendResponse(channel, "GPSA" + type.substring(4), imei, buf.readSlice(2)); + + return position; + + } else if (type.startsWith("SYNC")) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + decodeStatus(buf, position); + + sendResponse(channel, "SYSA" + type.substring(4), imei, null); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Gps103Protocol.java b/src/main/java/org/traccar/protocol/Gps103Protocol.java new file mode 100644 index 000000000..5356387ce --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gps103Protocol.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class Gps103Protocol extends BaseProtocol { + + public Gps103Protocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_POSITION_SINGLE, + Command.TYPE_POSITION_PERIODIC, + Command.TYPE_POSITION_STOP, + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME, + Command.TYPE_ALARM_ARM, + Command.TYPE_ALARM_DISARM, + Command.TYPE_REQUEST_PHOTO); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(2048, false, "\r\n", "\n", ";", "*")); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Gps103ProtocolEncoder(Gps103Protocol.this)); + pipeline.addLast(new Gps103ProtocolDecoder(Gps103Protocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Gps103ProtocolEncoder(Gps103Protocol.this)); + pipeline.addLast(new Gps103ProtocolDecoder(Gps103Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java new file mode 100644 index 000000000..e00d83061 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gps103ProtocolDecoder.java @@ -0,0 +1,422 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.DataConverter; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Gps103ProtocolDecoder extends BaseProtocolDecoder { + + private int photoPackets = 0; + private ByteBuf photo; + + public Gps103ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("imei:") + .number("(d+),") // imei + .expression("([^,]+),") // alarm + .groupBegin() + .number("(dd)/?(dd)/?(dd) ?") // local date (yymmdd) + .number("(dd):?(dd)(?:dd)?,") // local time (hhmmss) + .or() + .number("d*,") + .groupEnd() + .expression("([^,]+)?,") // rfid + .groupBegin() + .text("L,,,") + .number("(x+),,") // lac + .number("(x+),,,") // cid + .or() + .text("F,") + .groupBegin() + .number("(dd)(dd)(dd).d+") // time utc (hhmmss) + .or() + .number("(?:d{1,5}.d+)?") + .groupEnd() + .text(",") + .expression("([AV]),") // validity + .expression("([NS]),").optional() + .number("(d+)(dd.d+),") // latitude (ddmm.mmmm) + .expression("([NS]),").optional() + .expression("([EW]),").optional() + .number("(d+)(dd.d+),") // longitude (dddmm.mmmm) + .expression("([EW])?,").optional() + .number("(d+.?d*)?").optional() // speed + .number(",(d+.?d*)?").optional() // course + .number(",(-?d+.?d*)?").optional() // altitude + .number(",([01])?").optional() // ignition + .number(",([01])?").optional() // door + .groupBegin() + .number(",(?:(d+.d+)%)?") // fuel 1 + .number(",(?:(d+.d+)%|d+)?") // fuel 2 + .groupEnd("?") + .number(",([-+]?d+)?").optional() // temperature + .groupEnd() + .any() + .compile(); + + private static final Pattern PATTERN_OBD = new PatternBuilder() + .text("imei:") + .number("(d+),") // imei + .expression("OBD,") // type + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d+)?,") // odometer + .number("(d+.d+)?,") // fuel instant + .number("(d+.d+)?,") // fuel average + .number("(d+)?,") // hours + .number("(d+),") // speed + .number("(d+.?d*%),") // power load + .number("(?:([-+]?d+)|[-+]?),") // temperature + .number("(d+.?d*%),") // throttle + .number("(d+),") // rpm + .number("(d+.d+),") // battery + .number("([^;]*)") // dtcs + .any() + .compile(); + + private static final Pattern PATTERN_ALT = new PatternBuilder() + .text("imei:") + .number("(d+),") // imei + .expression("[^,]+,") + .expression("(?:-+|(.+)),") // event + .expression("(?:-+|(.+)),") // sensor id + .expression("(?:-+|(.+)),") // sensor voltage + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(d+),") // rssi + .number("(d),") // gps status + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(-?d+),") // altitude + .number("(d+.d+),") // hdop + .number("(d+),") // satellites + .number("([01]),") // ignition + .number("([01]),") // charge + .expression("(?:-+|(.+))") // error + .any() + .compile(); + + private String decodeAlarm(String value) { + if (value.startsWith("T:")) { + return Position.ALARM_TEMPERATURE; + } else if (value.startsWith("oil")) { + return Position.ALARM_FUEL_LEAK; + } + switch (value) { + case "tracker": + return null; + case "help me": + return Position.ALARM_SOS; + case "low battery": + return Position.ALARM_LOW_BATTERY; + case "stockade": + return Position.ALARM_GEOFENCE; + case "move": + return Position.ALARM_MOVEMENT; + case "speed": + return Position.ALARM_OVERSPEED; + case "acc on": + return Position.ALARM_POWER_ON; + case "acc off": + return Position.ALARM_POWER_OFF; + case "door alarm": + return Position.ALARM_DOOR; + case "ac alarm": + return Position.ALARM_POWER_CUT; + case "accident alarm": + return Position.ALARM_ACCIDENT; + case "sensor alarm": + return Position.ALARM_SHOCK; + case "bonnet alarm": + return Position.ALARM_BONNET; + case "footbrake alarm": + return Position.ALARM_FOOT_BRAKE; + case "DTC": + return Position.ALARM_FAULT; + default: + return null; + } + } + + private Position decodeRegular(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + String imei = parser.next(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + String alarm = parser.next(); + position.set(Position.KEY_ALARM, decodeAlarm(alarm)); + if (alarm.equals("help me")) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("**,imei:" + imei + ",E;", remoteAddress)); + } + } else if (alarm.startsWith("vt")) { + photoPackets = Integer.parseInt(alarm.substring(2)); + photo = Unpooled.buffer(); + } else if (alarm.equals("acc on")) { + position.set(Position.KEY_IGNITION, true); + } else if (alarm.equals("acc off")) { + position.set(Position.KEY_IGNITION, false); + } else if (alarm.startsWith("T:")) { + position.set(Position.PREFIX_TEMP + 1, Double.parseDouble(alarm.substring(2))); + } else if (alarm.startsWith("oil ")) { + position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(alarm.substring(4))); + } else if (!position.getAttributes().containsKey(Position.KEY_ALARM) && !alarm.equals("tracker")) { + position.set(Position.KEY_EVENT, alarm); + } + + DateBuilder dateBuilder = new DateBuilder() + .setDate(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + int localHours = parser.nextInt(0); + int localMinutes = parser.nextInt(0); + + String rfid = parser.next(); + if (alarm.equals("rfid")) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, rfid); + } + + if (parser.hasNext(2)) { + + getLastLocation(position, null); + + position.setNetwork(new Network(CellTower.fromLacCid(parser.nextHexInt(0), parser.nextHexInt(0)))); + + } else { + + String utcHours = parser.next(); + String utcMinutes = parser.next(); + + dateBuilder.setTime(localHours, localMinutes, parser.nextInt(0)); + + // Timezone calculation + if (utcHours != null && utcMinutes != null) { + int deltaMinutes = (localHours - Integer.parseInt(utcHours)) * 60; + deltaMinutes += localMinutes - Integer.parseInt(utcMinutes); + if (deltaMinutes <= -12 * 60) { + deltaMinutes += 24 * 60; + } else if (deltaMinutes > 12 * 60) { + deltaMinutes -= 24 * 60; + } + dateBuilder.addMinute(-deltaMinutes); + } + position.setTime(dateBuilder.getDate()); + + position.setValid(parser.next().equals("A")); + position.setFixTime(position.getDeviceTime()); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN_HEM)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + if (parser.hasNext()) { + position.set(Position.KEY_IGNITION, parser.nextInt() == 1); + } + if (parser.hasNext()) { + position.set(Position.KEY_DOOR, parser.nextInt() == 1); + } + position.set("fuel1", parser.nextDouble()); + position.set("fuel2", parser.nextDouble()); + position.set(Position.PREFIX_TEMP + 1, parser.nextInt()); + + } + + return position; + } + + private Position decodeObd(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_OBD, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, parser.nextDateTime()); + + position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + parser.nextDouble(0); // instant fuel consumption + position.set(Position.KEY_FUEL_CONSUMPTION, parser.nextDouble(0)); + if (parser.hasNext()) { + position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(parser.nextInt())); + } + position.set(Position.KEY_OBD_SPEED, parser.nextInt(0)); + position.set(Position.KEY_ENGINE_LOAD, parser.next()); + position.set(Position.KEY_COOLANT_TEMP, parser.nextInt()); + position.set(Position.KEY_THROTTLE, parser.next()); + position.set(Position.KEY_RPM, parser.nextInt(0)); + position.set(Position.KEY_BATTERY, parser.nextDouble(0)); + position.set(Position.KEY_DTCS, parser.next().replace(',', ' ').trim()); + + return position; + } + + + private Position decodeAlternative(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_ALT, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_EVENT, parser.next()); + position.set("sensorId", parser.next()); + position.set("sensorVoltage", parser.nextDouble()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY)); + + position.set(Position.KEY_RSSI, parser.nextInt()); + + position.setValid(parser.nextInt() > 0); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.setCourse(parser.nextInt()); + position.setAltitude(parser.nextInt()); + + position.set(Position.KEY_HDOP, parser.nextDouble()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_IGNITION, parser.nextInt() > 0); + position.set(Position.KEY_CHARGE, parser.nextInt() > 0); + position.set("error", parser.next()); + + return position; + } + + private Position decodePhoto(Channel channel, SocketAddress remoteAddress, String sentence) { + + String imei = sentence.substring(5, 5 + 15); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex( + sentence.substring(24, sentence.endsWith(";") ? sentence.length() - 1 : sentence.length()))); + int index = buf.readUnsignedShortLE(); + photo.writeBytes(buf, buf.readerIndex() + 2, buf.readableBytes() - 4); + + if (index + 1 >= photoPackets) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + try { + position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(imei, photo, "jpg")); + } finally { + photoPackets = 0; + photo.release(); + photo = null; + } + + return position; + } else { + return null; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.contains("imei:") && sentence.length() <= 30) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("LOAD", remoteAddress)); + Matcher matcher = Pattern.compile("imei:(\\d+),").matcher(sentence); + if (matcher.find()) { + getDeviceSession(channel, remoteAddress, matcher.group(1)); + } + } + return null; + } + + if (!sentence.isEmpty() && Character.isDigit(sentence.charAt(0))) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("ON", remoteAddress)); + } + int start = sentence.indexOf("imei:"); + if (start >= 0) { + sentence = sentence.substring(start); + } else { + return null; + } + } + + if (sentence.substring(21, 21 + 2).equals("vr")) { + return decodePhoto(channel, remoteAddress, sentence); + } else if (sentence.substring(21, 21 + 3).contains("OBD")) { + return decodeObd(channel, remoteAddress, sentence); + } else if (sentence.endsWith("*")) { + return decodeAlternative(channel, remoteAddress, sentence); + } else { + return decodeRegular(channel, remoteAddress, sentence); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Gps103ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Gps103ProtocolEncoder.java new file mode 100644 index 000000000..e662e9b04 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gps103ProtocolEncoder.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class Gps103ProtocolEncoder extends StringProtocolEncoder implements StringProtocolEncoder.ValueFormatter { + + public Gps103ProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + public String formatValue(String key, Object value) { + + if (key.equals(Command.KEY_FREQUENCY)) { + long frequency = ((Number) value).longValue(); + if (frequency / 60 / 60 > 0) { + return String.format("%02dh", frequency / 60 / 60); + } else if (frequency / 60 > 0) { + return String.format("%02dm", frequency / 60); + } else { + return String.format("%02ds", frequency); + } + } + + return null; + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return formatCommand(command, "**,imei:%s,%s", Command.KEY_UNIQUE_ID, Command.KEY_DATA); + case Command.TYPE_POSITION_STOP: + return formatCommand(command, "**,imei:%s,A", Command.KEY_UNIQUE_ID); + case Command.TYPE_POSITION_SINGLE: + return formatCommand(command, "**,imei:%s,B", Command.KEY_UNIQUE_ID); + case Command.TYPE_POSITION_PERIODIC: + return formatCommand( + command, "**,imei:%s,C,%s", this, Command.KEY_UNIQUE_ID, Command.KEY_FREQUENCY); + case Command.TYPE_ENGINE_STOP: + return formatCommand(command, "**,imei:%s,J", Command.KEY_UNIQUE_ID); + case Command.TYPE_ENGINE_RESUME: + return formatCommand(command, "**,imei:%s,K", Command.KEY_UNIQUE_ID); + case Command.TYPE_ALARM_ARM: + return formatCommand(command, "**,imei:%s,L", Command.KEY_UNIQUE_ID); + case Command.TYPE_ALARM_DISARM: + return formatCommand(command, "**,imei:%s,M", Command.KEY_UNIQUE_ID); + case Command.TYPE_REQUEST_PHOTO: + return formatCommand(command, "**,imei:%s,160", Command.KEY_UNIQUE_ID); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/GpsGateProtocol.java b/src/main/java/org/traccar/protocol/GpsGateProtocol.java new file mode 100644 index 000000000..a131b6f48 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GpsGateProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class GpsGateProtocol extends BaseProtocol { + + public GpsGateProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "\0", "\n", "\r\n")); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new GpsGateProtocolDecoder(GpsGateProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/GpsGateProtocolDecoder.java b/src/main/java/org/traccar/protocol/GpsGateProtocolDecoder.java new file mode 100644 index 000000000..cc187225b --- /dev/null +++ b/src/main/java/org/traccar/protocol/GpsGateProtocolDecoder.java @@ -0,0 +1,170 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import 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 GpsGateProtocolDecoder extends BaseProtocolDecoder { + + public GpsGateProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_GPRMC = new PatternBuilder() + .text("$GPRMC,") + .number("(dd)(dd)(dd).?d*,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.d+)?,") // speed + .number("(d+.d+)?,") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .any() + .compile(); + + private static final Pattern PATTERN_FRCMD = new PatternBuilder() + .text("$FRCMD,") + .number("(d+),") // imei + .expression("[^,]*,") // command + .expression("[^,]*,") + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*),") // altitude + .number("(d+.?d*),") // speed + .number("(d+.?d*)?,") // course + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd).?d*,") // time (hhmmss) + .expression("([01])") // validity + .any() + .compile(); + + private void send(Channel channel, SocketAddress remoteAddress, String message) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(message + Checksum.nmea(message) + "\r\n", remoteAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.startsWith("$FRLIN,")) { + + int beginIndex = sentence.indexOf(',', 7); + if (beginIndex != -1) { + beginIndex += 1; + int endIndex = sentence.indexOf(',', beginIndex); + if (endIndex != -1) { + String imei = sentence.substring(beginIndex, endIndex); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession != null) { + if (channel != null) { + send(channel, remoteAddress, "$FRSES," + channel.id().asShortText()); + } + } else { + send(channel, remoteAddress, "$FRERR,AuthError,Unknown device"); + } + } else { + send(channel, remoteAddress, "$FRERR,AuthError,Parse error"); + } + } else { + send(channel, remoteAddress, "$FRERR,AuthError,Parse error"); + } + + } else if (sentence.startsWith("$FRVER,")) { + + send(channel, remoteAddress, "$FRVER,1,0,GpsGate Server 1.0"); + + } else if (sentence.startsWith("$GPRMC,")) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Parser parser = new Parser(PATTERN_GPRMC, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + return position; + + } else if (sentence.startsWith("$FRCMD,")) { + + Parser parser = new Parser(PATTERN_FRCMD, sentence); + 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.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setAltitude(parser.nextDouble(0)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setValid(parser.next().equals("1")); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/GpsMarkerProtocol.java b/src/main/java/org/traccar/protocol/GpsMarkerProtocol.java new file mode 100644 index 000000000..ad23ece48 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GpsMarkerProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class GpsMarkerProtocol extends BaseProtocol { + + public GpsMarkerProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "\r")); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new GpsMarkerProtocolDecoder(GpsMarkerProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/GpsMarkerProtocolDecoder.java b/src/main/java/org/traccar/protocol/GpsMarkerProtocolDecoder.java new file mode 100644 index 000000000..bbb2c31e2 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GpsMarkerProtocolDecoder.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class GpsMarkerProtocolDecoder extends BaseProtocolDecoder { + + public GpsMarkerProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$GM") + .number("d") // type + .number("(?:xx)?") // index + .number("(d{15})") // imei + .number("T(dd)(dd)(dd)") // date (ddmmyy) + .number("(dd)(dd)(dd)?") // time (hhmmss) + .expression("([NS])") + .number("(dd)(dd)(dddd)") // latitude + .expression("([EW])") + .number("(ddd)(dd)(dddd)") // longitude + .number("(ddd)") // speed + .number("(ddd)") // course + .number("(x)") // satellites + .number("(dd)") // battery + .number("(d)") // input + .number("(d)") // output + .number("(ddd)") // temperature + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setValid(true); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN_MIN)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN_MIN)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_SATELLITES, parser.nextHexInt(0)); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt(0)); + position.set(Position.KEY_INPUT, parser.next()); + position.set(Position.KEY_OUTPUT, parser.next()); + position.set(Position.PREFIX_TEMP + 1, parser.next()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/GpsmtaProtocol.java b/src/main/java/org/traccar/protocol/GpsmtaProtocol.java new file mode 100644 index 000000000..ce6cc5929 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GpsmtaProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class GpsmtaProtocol extends BaseProtocol { + + public GpsmtaProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new GpsmtaProtocolDecoder(GpsmtaProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/GpsmtaProtocolDecoder.java b/src/main/java/org/traccar/protocol/GpsmtaProtocolDecoder.java new file mode 100644 index 000000000..31f9401b4 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GpsmtaProtocolDecoder.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; +import java.util.regex.Pattern; + +public class GpsmtaProtocolDecoder extends BaseProtocolDecoder { + + public GpsmtaProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression("([^ ]+) ") // uid + .number("(d+) ") // time (unix time) + .number("(-?d+.d+) ") // latitude + .number("(-?d+.d+) ") // longitude + .number("(d+) ") // speed + .number("(d+) ") // course + .number("(d+) ") // accuracy + .number("(d+) ") // altitude + .number("(d+) ") // flags + .number("(d+) ") // battery + .number("(d+) ") // temperature + .number("(d)") // charging status + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + String time = parser.next(); + position.setTime(new Date(Long.parseLong(time) * 1000)); + + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setSpeed(parser.nextInt()); + position.setCourse(parser.nextInt()); + position.setAccuracy(parser.nextInt()); + position.setAltitude(parser.nextInt()); + + position.set(Position.KEY_STATUS, parser.nextInt()); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, parser.nextInt()); + position.set(Position.KEY_CHARGE, parser.nextInt() == 1); + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(time, remoteAddress)); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/GranitFrameDecoder.java b/src/main/java/org/traccar/protocol/GranitFrameDecoder.java new file mode 100644 index 000000000..bb7f4be44 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GranitFrameDecoder.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 GranitFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + int indexEnd = BufferUtil.indexOf("\r\n", buf); + if (indexEnd != -1) { + int indexTilde = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '~'); + if (indexTilde != -1 && indexTilde < indexEnd) { + int length = buf.getUnsignedShortLE(indexTilde + 1); + indexEnd = BufferUtil.indexOf("\r\n", buf, indexTilde + 2 + length, buf.writerIndex()); + if (indexEnd == -1) { + return null; + } + } + ByteBuf frame = buf.readRetainedSlice(indexEnd - buf.readerIndex()); + buf.skipBytes(2); + return frame; + } + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/GranitProtocol.java b/src/main/java/org/traccar/protocol/GranitProtocol.java new file mode 100644 index 000000000..244c3977b --- /dev/null +++ b/src/main/java/org/traccar/protocol/GranitProtocol.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class GranitProtocol extends BaseProtocol { + + public GranitProtocol() { + setSupportedDataCommands( + Command.TYPE_IDENTIFICATION, + Command.TYPE_REBOOT_DEVICE, + Command.TYPE_POSITION_SINGLE); + setTextCommandEncoder(new GranitProtocolSmsEncoder(this)); + setSupportedTextCommands( + Command.TYPE_REBOOT_DEVICE, + Command.TYPE_POSITION_PERIODIC); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new GranitFrameDecoder()); + pipeline.addLast(new GranitProtocolEncoder(GranitProtocol.this)); + pipeline.addLast(new GranitProtocolDecoder(GranitProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/GranitProtocolDecoder.java b/src/main/java/org/traccar/protocol/GranitProtocolDecoder.java new file mode 100644 index 000000000..8900e5b39 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GranitProtocolDecoder.java @@ -0,0 +1,239 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class GranitProtocolDecoder extends BaseProtocolDecoder { + + private static final int HEADER_LENGTH = 6; + + private double adc1Ratio; + private double adc2Ratio; + private double adc3Ratio; + private double adc4Ratio; + + public GranitProtocolDecoder(Protocol protocol) { + super(protocol); + adc1Ratio = Context.getConfig().getDouble("granit.adc1Ratio", 1); + adc2Ratio = Context.getConfig().getDouble("granit.adc2Ratio", 1); + adc3Ratio = Context.getConfig().getDouble("granit.adc3Ratio", 1); + adc4Ratio = Context.getConfig().getDouble("granit.adc4Ratio", 1); + } + + public static void appendChecksum(ByteBuf buffer, int length) { + buffer.writeByte('*'); + int checksum = Checksum.xor(buffer.nioBuffer(0, length)) & 0xFF; + String checksumString = String.format("%02X", checksum); + buffer.writeBytes(checksumString.getBytes(StandardCharsets.US_ASCII)); + buffer.writeByte('\r'); buffer.writeByte('\n'); + } + + private static void sendResponseCurrent(Channel channel, int deviceId, long time) { + ByteBuf response = Unpooled.buffer(); + response.writeBytes("BB+UGRC~".getBytes(StandardCharsets.US_ASCII)); + response.writeShortLE(6); // length + response.writeInt((int) time); + response.writeShortLE(deviceId); + appendChecksum(response, 16); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + + private static void sendResponseArchive(Channel channel, int deviceId, int packNum) { + ByteBuf response = Unpooled.buffer(); + response.writeBytes("BB+ARCF~".getBytes(StandardCharsets.US_ASCII)); + response.writeShortLE(4); // length + response.writeShortLE(packNum); + response.writeShortLE(deviceId); + appendChecksum(response, 14); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + + private void decodeStructure(ByteBuf buf, Position position) { + short flags = buf.readUnsignedByte(); + position.setValid(BitUtil.check(flags, 7)); + if (BitUtil.check(flags, 1)) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + + short satDel = buf.readUnsignedByte(); + position.set(Position.KEY_SATELLITES, BitUtil.from(satDel, 4)); + + int pdop = BitUtil.to(satDel, 4); + position.set(Position.KEY_PDOP, pdop); + + int lonDegrees = buf.readUnsignedByte(); + int latDegrees = buf.readUnsignedByte(); + int lonMinutes = buf.readUnsignedShortLE(); + int latMinutes = buf.readUnsignedShortLE(); + + double latitude = latDegrees + latMinutes / 60000.0; + double longitude = lonDegrees + lonMinutes / 60000.0; + + if (position.getValid()) { + if (!BitUtil.check(flags, 4)) { + latitude = -latitude; + } + if (!BitUtil.check(flags, 5)) { + longitude = -longitude; + } + } + + position.setLongitude(longitude); + position.setLatitude(latitude); + + position.setSpeed(buf.readUnsignedByte()); + + int course = buf.readUnsignedByte(); + if (BitUtil.check(flags, 6)) { + course = course | 0x100; + } + position.setCourse(course); + + position.set(Position.KEY_DISTANCE, buf.readShortLE()); + + int analogIn1 = buf.readUnsignedByte(); + int analogIn2 = buf.readUnsignedByte(); + int analogIn3 = buf.readUnsignedByte(); + int analogIn4 = buf.readUnsignedByte(); + + int analogInHi = buf.readUnsignedByte(); + + analogIn1 = analogInHi << 8 & 0x300 | analogIn1; + analogIn2 = analogInHi << 6 & 0x300 | analogIn2; + analogIn3 = analogInHi << 4 & 0x300 | analogIn3; + analogIn4 = analogInHi << 2 & 0x300 | analogIn4; + + position.set(Position.PREFIX_ADC + 1, analogIn1 * adc1Ratio); + position.set(Position.PREFIX_ADC + 2, analogIn2 * adc2Ratio); + position.set(Position.PREFIX_ADC + 3, analogIn3 * adc3Ratio); + position.set(Position.PREFIX_ADC + 4, analogIn4 * adc4Ratio); + + position.setAltitude(buf.readUnsignedByte() * 10); + + int output = buf.readUnsignedByte(); + for (int i = 0; i < 8; i++) { + position.set(Position.PREFIX_IO + (i + 1), BitUtil.check(output, i)); + } + buf.readUnsignedByte(); // status message buffer + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int indexTilde = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '~'); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + + if (deviceSession != null && indexTilde == -1) { + String bufString = buf.toString(StandardCharsets.US_ASCII); + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date()); + getLastLocation(position, new Date()); + position.setValid(false); + position.set(Position.KEY_RESULT, bufString); + return position; + } + + if (buf.readableBytes() < HEADER_LENGTH) { + return null; + } + String header = buf.readSlice(HEADER_LENGTH).toString(StandardCharsets.US_ASCII); + + if (header.equals("+RRCB~")) { + + buf.skipBytes(2); // binary length 26 + int deviceId = buf.readUnsignedShortLE(); + deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(deviceId)); + if (deviceSession == null) { + return null; + } + long unixTime = buf.readUnsignedIntLE(); + if (channel != null) { + sendResponseCurrent(channel, deviceId, unixTime); + } + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date(unixTime * 1000)); + + decodeStructure(buf, position); + return position; + + } else if (header.equals("+DDAT~")) { + + buf.skipBytes(2); // binary length + int deviceId = buf.readUnsignedShortLE(); + deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(deviceId)); + if (deviceSession == null) { + return null; + } + byte format = buf.readByte(); + if (format != 4) { + return null; + } + byte nblocks = buf.readByte(); + int packNum = buf.readUnsignedShortLE(); + if (channel != null) { + sendResponseArchive(channel, deviceId, packNum); + } + List<Position> positions = new ArrayList<>(); + while (nblocks > 0) { + nblocks--; + long unixTime = buf.readUnsignedIntLE(); + int timeIncrement = buf.getUnsignedShortLE(buf.readerIndex() + 120); + for (int i = 0; i < 6; i++) { + if (buf.getUnsignedByte(buf.readerIndex()) != 0xFE) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.setTime(new Date((unixTime + i * timeIncrement) * 1000)); + decodeStructure(buf, position); + position.set(Position.KEY_ARCHIVE, true); + positions.add(position); + } else { + buf.skipBytes(20); // skip filled 0xFE structure + } + } + buf.skipBytes(2); // increment + } + return positions; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/GranitProtocolEncoder.java b/src/main/java/org/traccar/protocol/GranitProtocolEncoder.java new file mode 100644 index 000000000..66c2a4973 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GranitProtocolEncoder.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import java.nio.charset.StandardCharsets; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class GranitProtocolEncoder extends BaseProtocolEncoder { + + public GranitProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf encodeCommand(String commandString) { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeBytes(commandString.getBytes(StandardCharsets.US_ASCII)); + GranitProtocolDecoder.appendChecksum(buffer, commandString.length()); + return buffer; + } + + @Override + protected Object encodeCommand(Command command) { + switch (command.getType()) { + case Command.TYPE_IDENTIFICATION: + return encodeCommand("BB+IDNT"); + case Command.TYPE_REBOOT_DEVICE: + return encodeCommand("BB+RESET"); + case Command.TYPE_POSITION_SINGLE: + return encodeCommand("BB+RRCD"); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/GranitProtocolSmsEncoder.java b/src/main/java/org/traccar/protocol/GranitProtocolSmsEncoder.java new file mode 100644 index 000000000..be0ab5130 --- /dev/null +++ b/src/main/java/org/traccar/protocol/GranitProtocolSmsEncoder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class GranitProtocolSmsEncoder extends StringProtocolEncoder { + + public GranitProtocolSmsEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected String encodeCommand(Command command) { + switch (command.getType()) { + case Command.TYPE_REBOOT_DEVICE: + return "BB+RESET"; + case Command.TYPE_POSITION_PERIODIC: + return formatCommand(command, "BB+BBMD=%s", Command.KEY_FREQUENCY); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Gt02Protocol.java b/src/main/java/org/traccar/protocol/Gt02Protocol.java new file mode 100644 index 000000000..f412ee720 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gt02Protocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Gt02Protocol extends BaseProtocol { + + public Gt02Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(256, 2, 1, 2, 0)); + pipeline.addLast(new Gt02ProtocolDecoder(Gt02Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Gt02ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt02ProtocolDecoder.java new file mode 100644 index 000000000..78a3fd3ee --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gt02ProtocolDecoder.java @@ -0,0 +1,125 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; + +public class Gt02ProtocolDecoder extends BaseProtocolDecoder { + + public Gt02ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_DATA = 0x10; + public static final int MSG_HEARTBEAT = 0x1A; + public static final int MSG_RESPONSE = 0x1C; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + buf.readByte(); // size + + Position position = new Position(getProtocolName()); + + // Zero for location messages + int power = buf.readUnsignedByte(); + int gsm = buf.readUnsignedByte(); + + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(1); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_INDEX, buf.readUnsignedShort()); + + int type = buf.readUnsignedByte(); + + if (type == MSG_HEARTBEAT) { + + getLastLocation(position, null); + + position.set(Position.KEY_POWER, power); + position.set(Position.KEY_RSSI, gsm); + + if (channel != null) { + byte[] response = {0x54, 0x68, 0x1A, 0x0D, 0x0A}; + channel.writeAndFlush(new NetworkMessage(Unpooled.wrappedBuffer(response), remoteAddress)); + } + + } else if (type == MSG_DATA) { + + DateBuilder dateBuilder = new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + double latitude = buf.readUnsignedInt() / (60.0 * 30000.0); + double longitude = buf.readUnsignedInt() / (60.0 * 30000.0); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.setCourse(buf.readUnsignedShort()); + + buf.skipBytes(3); // reserved + + long flags = buf.readUnsignedInt(); + position.setValid(BitUtil.check(flags, 0)); + if (!BitUtil.check(flags, 1)) { + latitude = -latitude; + } + if (!BitUtil.check(flags, 2)) { + longitude = -longitude; + } + + position.setLatitude(latitude); + position.setLongitude(longitude); + + } else if (type == MSG_RESPONSE) { + + getLastLocation(position, null); + + position.set(Position.KEY_RESULT, + buf.readSlice(buf.readUnsignedByte()).toString(StandardCharsets.US_ASCII)); + + } else { + + return null; + + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/Gt06FrameDecoder.java b/src/main/java/org/traccar/protocol/Gt06FrameDecoder.java new file mode 100644 index 000000000..cc934be42 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gt06FrameDecoder.java @@ -0,0 +1,56 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class Gt06FrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 5) { + return null; + } + + int length = 2 + 2; // head and tail + + if (buf.getByte(buf.readerIndex()) == 0x78) { + length += 1 + buf.getUnsignedByte(buf.readerIndex() + 2); + } else { + length += 2 + buf.getUnsignedShort(buf.readerIndex() + 2); + } + + if (buf.readableBytes() >= length && buf.getUnsignedShort(buf.readerIndex() + length - 2) == 0x0d0a) { + return buf.readRetainedSlice(length); + } + + int endIndex = buf.readerIndex() - 1; + do { + endIndex = buf.indexOf(endIndex + 1, buf.writerIndex(), (byte) 0x0d); + if (endIndex > 0 && buf.writerIndex() > endIndex + 1 && buf.getByte(endIndex + 1) == 0x0a) { + return buf.readRetainedSlice(endIndex + 2 - buf.readerIndex()); + } + } while (endIndex > 0); + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Gt06Protocol.java b/src/main/java/org/traccar/protocol/Gt06Protocol.java new file mode 100644 index 000000000..9ec8de098 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gt06Protocol.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class Gt06Protocol extends BaseProtocol { + + public Gt06Protocol() { + setSupportedDataCommands( + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME, + Command.TYPE_CUSTOM); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new Gt06FrameDecoder()); + pipeline.addLast(new Gt06ProtocolEncoder(Gt06Protocol.this)); + pipeline.addLast(new Gt06ProtocolDecoder(Gt06Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java new file mode 100644 index 000000000..946652b03 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java @@ -0,0 +1,1199 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Device; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; +import java.util.regex.Pattern; + +public class Gt06ProtocolDecoder extends BaseProtocolDecoder { + + private final Map<Integer, ByteBuf> photos = new HashMap<>(); + + public Gt06ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN = 0x01; + public static final int MSG_GPS = 0x10; + public static final int MSG_LBS = 0x11; + public static final int MSG_GPS_LBS_1 = 0x12; + public static final int MSG_GPS_LBS_2 = 0x22; + public static final int MSG_STATUS = 0x13; + public static final int MSG_SATELLITE = 0x14; + public static final int MSG_STRING = 0x15; + public static final int MSG_GPS_LBS_STATUS_1 = 0x16; + public static final int MSG_WIFI = 0x17; + public static final int MSG_GPS_LBS_STATUS_2 = 0x26; + public static final int MSG_GPS_LBS_STATUS_3 = 0x27; + public static final int MSG_LBS_MULTIPLE = 0x28; + public static final int MSG_LBS_WIFI = 0x2C; + public static final int MSG_LBS_EXTEND = 0x18; + public static final int MSG_LBS_STATUS = 0x19; + public static final int MSG_GPS_PHONE = 0x1A; + public static final int MSG_GPS_LBS_EXTEND = 0x1E; + public static final int MSG_HEARTBEAT = 0x23; + public static final int MSG_ADDRESS_REQUEST = 0x2A; + public static final int MSG_ADDRESS_RESPONSE = 0x97; + public static final int MSG_AZ735_GPS = 0x32; + public static final int MSG_AZ735_ALARM = 0x33; + public static final int MSG_X1_GPS = 0x34; + public static final int MSG_X1_PHOTO_INFO = 0x35; + public static final int MSG_X1_PHOTO_DATA = 0x36; + public static final int MSG_WIFI_2 = 0x69; + public static final int MSG_GPS_MODULAR = 0x70; + public static final int MSG_COMMAND_0 = 0x80; + public static final int MSG_COMMAND_1 = 0x81; + public static final int MSG_COMMAND_2 = 0x82; + public static final int MSG_TIME_REQUEST = 0x8A; + public static final int MSG_INFO = 0x94; + public static final int MSG_RFID = 0x9B; + public static final int MSG_STRING_INFO = 0x21; + public static final int MSG_GPS_2 = 0xA0; + public static final int MSG_LBS_2 = 0xA1; + public static final int MSG_WIFI_3 = 0xA2; + public static final int MSG_FENCE_SINGLE = 0xA3; + public static final int MSG_FENCE_MULTI = 0xA4; + public static final int MSG_LBS_ALARM = 0xA5; + public static final int MSG_LBS_ADDRESS = 0xA7; + public static final int MSG_OBD = 0x8C; + public static final int MSG_DTC = 0x65; + public static final int MSG_PID = 0x66; + public static final int MSG_BMS = 0x20; + public static final int MSG_MULTIMEDIA = 0x21; + public static final int MSG_BMS_2 = 0x40; + public static final int MSG_MULTIMEDIA_2 = 0x41; + public static final int MSG_ALARM = 0x95; + + private static boolean isSupported(int type) { + return hasGps(type) || hasLbs(type) || hasStatus(type); + } + + private static boolean hasGps(int type) { + switch (type) { + case MSG_GPS: + case MSG_GPS_LBS_1: + case MSG_GPS_LBS_2: + case MSG_GPS_LBS_STATUS_1: + case MSG_GPS_LBS_STATUS_2: + case MSG_GPS_LBS_STATUS_3: + case MSG_GPS_PHONE: + case MSG_GPS_LBS_EXTEND: + case MSG_GPS_2: + case MSG_FENCE_SINGLE: + case MSG_FENCE_MULTI: + return true; + default: + return false; + } + } + + private static boolean hasLbs(int type) { + switch (type) { + case MSG_LBS: + case MSG_LBS_STATUS: + case MSG_GPS_LBS_1: + case MSG_GPS_LBS_2: + case MSG_GPS_LBS_STATUS_1: + case MSG_GPS_LBS_STATUS_2: + case MSG_GPS_LBS_STATUS_3: + case MSG_GPS_2: + case MSG_FENCE_SINGLE: + case MSG_FENCE_MULTI: + case MSG_LBS_ALARM: + case MSG_LBS_ADDRESS: + return true; + default: + return false; + } + } + + private static boolean hasStatus(int type) { + switch (type) { + case MSG_STATUS: + case MSG_LBS_STATUS: + case MSG_GPS_LBS_STATUS_1: + case MSG_GPS_LBS_STATUS_2: + case MSG_GPS_LBS_STATUS_3: + return true; + default: + return false; + } + } + + private static boolean hasLanguage(int type) { + switch (type) { + case MSG_GPS_PHONE: + case MSG_HEARTBEAT: + case MSG_GPS_LBS_STATUS_3: + case MSG_LBS_MULTIPLE: + case MSG_LBS_2: + case MSG_FENCE_MULTI: + return true; + default: + return false; + } + } + + private void sendResponse(Channel channel, boolean extended, int type, int index, ByteBuf content) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + int length = 5 + (content != null ? content.readableBytes() : 0); + if (extended) { + response.writeShort(0x7979); + response.writeShort(length); + } else { + response.writeShort(0x7878); + response.writeByte(length); + } + response.writeByte(type); + if (content != null) { + response.writeBytes(content); + content.release(); + } + response.writeShort(index); + response.writeShort(Checksum.crc16(Checksum.CRC16_X25, + response.nioBuffer(2, response.writerIndex() - 2))); + response.writeByte('\r'); + response.writeByte('\n'); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + private void sendPhotoRequest(Channel channel, int pictureId) { + ByteBuf photo = photos.get(pictureId); + ByteBuf content = Unpooled.buffer(); + content.writeInt(pictureId); + content.writeInt(photo.writerIndex()); + content.writeShort(Math.min(photo.writableBytes(), 1024)); + sendResponse(channel, false, MSG_X1_PHOTO_DATA, 0, content); + } + + public static boolean decodeGps(Position position, ByteBuf buf, boolean hasLength, TimeZone timezone) { + + DateBuilder dateBuilder = new DateBuilder(timezone) + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + if (hasLength && buf.readUnsignedByte() == 0) { + return false; + } + + position.set(Position.KEY_SATELLITES, BitUtil.to(buf.readUnsignedByte(), 4)); + + double latitude = buf.readUnsignedInt() / 60.0 / 30000.0; + double longitude = buf.readUnsignedInt() / 60.0 / 30000.0; + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + + int flags = buf.readUnsignedShort(); + position.setCourse(BitUtil.to(flags, 10)); + position.setValid(BitUtil.check(flags, 12)); + + if (!BitUtil.check(flags, 10)) { + latitude = -latitude; + } + if (BitUtil.check(flags, 11)) { + longitude = -longitude; + } + + position.setLatitude(latitude); + position.setLongitude(longitude); + + if (BitUtil.check(flags, 14)) { + position.set(Position.KEY_IGNITION, BitUtil.check(flags, 15)); + } + + return true; + } + + private boolean decodeLbs(Position position, ByteBuf buf, boolean hasLength) { + + int length = 0; + if (hasLength) { + length = buf.readUnsignedByte(); + if (length == 0) { + return false; + } + } + + int mcc = buf.readUnsignedShort(); + int mnc = BitUtil.check(mcc, 15) ? buf.readUnsignedShort() : buf.readUnsignedByte(); + + position.setNetwork(new Network(CellTower.from( + BitUtil.to(mcc, 15), mnc, buf.readUnsignedShort(), buf.readUnsignedMedium()))); + + if (length > 9) { + buf.skipBytes(length - 9); + } + + return true; + } + + private boolean decodeStatus(Position position, ByteBuf buf) { + + int status = buf.readUnsignedByte(); + + position.set(Position.KEY_STATUS, status); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 1)); + position.set(Position.KEY_CHARGE, BitUtil.check(status, 2)); + position.set(Position.KEY_BLOCKED, BitUtil.check(status, 7)); + + switch (BitUtil.between(status, 3, 6)) { + case 1: + position.set(Position.KEY_ALARM, Position.ALARM_SHOCK); + break; + case 2: + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + break; + case 3: + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + case 4: + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + break; + case 7: + position.set(Position.KEY_ALARM, Position.ALARM_REMOVING); + break; + default: + break; + } + + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte() * 100 / 6); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + + return true; + } + + private String decodeAlarm(short value) { + switch (value) { + case 0x01: + return Position.ALARM_SOS; + case 0x02: + return Position.ALARM_POWER_CUT; + case 0x03: + case 0x09: + return Position.ALARM_VIBRATION; + case 0x04: + return Position.ALARM_GEOFENCE_ENTER; + case 0x05: + return Position.ALARM_GEOFENCE_EXIT; + case 0x06: + return Position.ALARM_OVERSPEED; + case 0x0E: + case 0x0F: + return Position.ALARM_LOW_BATTERY; + case 0x11: + return Position.ALARM_POWER_OFF; + case 0x13: + return Position.ALARM_TAMPERING; + case 0x14: + return Position.ALARM_DOOR; + case 0x29: + return Position.ALARM_ACCELERATION; + case 0x30: + return Position.ALARM_BRAKING; + case 0x2A: + case 0x2B: + return Position.ALARM_CORNERING; + case 0x2C: + return Position.ALARM_ACCIDENT; + case 0x23: + return Position.ALARM_FALL_DOWN; + default: + return null; + } + } + + private static final Pattern PATTERN_FUEL = new PatternBuilder() + .text("!AIOIL,") + .number("d+,") // device address + .number("d+.d+,") // output value + .number("(d+.d+),") // temperature + .expression("[^,]+,") // version + .number("dd") // back wave + .number("d") // software status code + .number("d,") // hardware status code + .number("(d+.d+),") // measured value + .expression("[01],") // movement status + .number("d+,") // excited wave times + .number("xx") // checksum + .compile(); + + private Position decodeFuelData(Position position, String sentence) { + Parser parser = new Parser(PATTERN_FUEL, sentence); + if (!parser.matches()) { + return null; + } + + position.set(Position.PREFIX_TEMP + 1, parser.nextDouble(0)); + position.set(Position.KEY_FUEL_LEVEL, parser.nextDouble(0)); + + return position; + } + + private static final Pattern PATTERN_LOCATION = new PatternBuilder() + .text("Current position!") + .number("Lat:([NS])(d+.d+),") // latitude + .number("Lon:([EW])(d+.d+),") // longitude + .text("Course:").number("(d+.d+),") // course + .text("Speed:").number("(d+.d+),") // speed + .text("DateTime:") + .number("(dddd)-(dd)-(dd) +") // date + .number("(dd):(dd):(dd)") // time + .compile(); + + private Position decodeLocationString(Position position, String sentence) { + Parser parser = new Parser(PATTERN_LOCATION, sentence); + if (!parser.matches()) { + return null; + } + + position.setValid(true); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setCourse(parser.nextDouble()); + position.setSpeed(parser.nextDouble()); + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.YMD_HMS)); + + return position; + } + + private Object decodeBasic(Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws Exception { + + int length = buf.readUnsignedByte(); + int dataLength = length - 5; + int type = buf.readUnsignedByte(); + + DeviceSession deviceSession = null; + if (type != MSG_LOGIN) { + deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + if (deviceSession.getTimeZone() == null) { + deviceSession.setTimeZone(getTimeZone(deviceSession.getDeviceId())); + } + } + + if (type == MSG_LOGIN) { + + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(1); + buf.readUnsignedShort(); // type + + deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession != null && deviceSession.getTimeZone() == null) { + deviceSession.setTimeZone(getTimeZone(deviceSession.getDeviceId())); + } + + if (dataLength > 10) { + int extensionBits = buf.readUnsignedShort(); + int hours = (extensionBits >> 4) / 100; + int minutes = (extensionBits >> 4) % 100; + int offset = (hours * 60 + minutes) * 60; + if ((extensionBits & 0x8) != 0) { + offset = -offset; + } + if (deviceSession != null) { + TimeZone timeZone = deviceSession.getTimeZone(); + if (timeZone.getRawOffset() == 0) { + timeZone.setRawOffset(offset * 1000); + deviceSession.setTimeZone(timeZone); + } + } + + } + + if (deviceSession != null) { + sendResponse(channel, false, type, buf.getShort(buf.writerIndex() - 6), null); + } + + } else if (type == MSG_HEARTBEAT) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + int status = buf.readUnsignedByte(); + position.set(Position.KEY_ARMED, BitUtil.check(status, 0)); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 1)); + position.set(Position.KEY_CHARGE, BitUtil.check(status, 2)); + + if (buf.readableBytes() >= 2 + 6) { + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); + } + if (buf.readableBytes() >= 1 + 6) { + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + } + + sendResponse(channel, false, type, buf.getShort(buf.writerIndex() - 6), null); + + return position; + + } else if (type == MSG_ADDRESS_REQUEST) { + + String response = "NA&&NA&&0##"; + ByteBuf content = Unpooled.buffer(); + content.writeByte(response.length()); + content.writeInt(0); + content.writeBytes(response.getBytes(StandardCharsets.US_ASCII)); + sendResponse(channel, true, MSG_ADDRESS_RESPONSE, 0, content); + + } else if (type == MSG_TIME_REQUEST) { + + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + ByteBuf content = Unpooled.buffer(); + content.writeByte(calendar.get(Calendar.YEAR) - 2000); + content.writeByte(calendar.get(Calendar.MONTH) + 1); + content.writeByte(calendar.get(Calendar.DAY_OF_MONTH)); + content.writeByte(calendar.get(Calendar.HOUR_OF_DAY)); + content.writeByte(calendar.get(Calendar.MINUTE)); + content.writeByte(calendar.get(Calendar.SECOND)); + sendResponse(channel, false, MSG_TIME_REQUEST, 0, content); + + } else if (type == MSG_X1_GPS || type == MSG_X1_PHOTO_INFO) { + + return decodeX1(channel, buf, deviceSession, type); + + } else if (type == MSG_WIFI || type == MSG_WIFI_2) { + + return decodeWifi(channel, buf, deviceSession, type); + + } else if (type == MSG_INFO) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_POWER, buf.readShort() * 0.01); + + return position; + + } else { + + return decodeBasicOther(channel, buf, deviceSession, type, dataLength); + + } + + return null; + } + + private Object decodeX1(Channel channel, ByteBuf buf, DeviceSession deviceSession, int type) { + + if (type == MSG_X1_GPS) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedInt(); // data and alarm + + decodeGps(position, buf, false, deviceSession.getTimeZone()); + + buf.readUnsignedShort(); // terminal info + + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + + position.setNetwork(new Network(CellTower.from( + buf.readUnsignedShort(), buf.readUnsignedByte(), + buf.readUnsignedShort(), buf.readUnsignedInt()))); + + long driverId = buf.readUnsignedInt(); + if (driverId > 0) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(driverId)); + } + + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); + + long portInfo = buf.readUnsignedInt(); + + position.set(Position.KEY_INPUT, buf.readUnsignedByte()); + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + + for (int i = 1; i <= BitUtil.between(portInfo, 20, 24); i++) { + position.set(Position.PREFIX_ADC + i, buf.readUnsignedShort() * 0.01); + } + + return position; + + } else if (type == MSG_X1_PHOTO_INFO) { + + buf.skipBytes(6); // time + buf.readUnsignedByte(); // fix status + buf.readUnsignedInt(); // latitude + buf.readUnsignedInt(); // longitude + buf.readUnsignedByte(); // camera id + buf.readUnsignedByte(); // photo source + buf.readUnsignedByte(); // picture format + + ByteBuf photo = Unpooled.buffer(buf.readInt()); + int pictureId = buf.readInt(); + photos.put(pictureId, photo); + sendPhotoRequest(channel, pictureId); + + } + + return null; + } + + private Object decodeWifi(Channel channel, ByteBuf buf, DeviceSession deviceSession, int type) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + ByteBuf time = buf.readSlice(6); + DateBuilder dateBuilder = new DateBuilder() + .setYear(BcdUtil.readInteger(time, 2)) + .setMonth(BcdUtil.readInteger(time, 2)) + .setDay(BcdUtil.readInteger(time, 2)) + .setHour(BcdUtil.readInteger(time, 2)) + .setMinute(BcdUtil.readInteger(time, 2)) + .setSecond(BcdUtil.readInteger(time, 2)); + getLastLocation(position, dateBuilder.getDate()); + + Network network = new Network(); + + int wifiCount = buf.getByte(2); + for (int i = 0; i < wifiCount; i++) { + String mac = String.format("%02x:%02x:%02x:%02x:%02x:%02x", + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte(), + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + network.addWifiAccessPoint(WifiAccessPoint.from(mac, buf.readUnsignedByte())); + } + + int cellCount = buf.readUnsignedByte(); + int mcc = buf.readUnsignedShort(); + int mnc = buf.readUnsignedByte(); + for (int i = 0; i < cellCount; i++) { + network.addCellTower(CellTower.from( + mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedShort(), buf.readUnsignedByte())); + } + + position.setNetwork(network); + + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeShort(0x7878); + response.writeByte(0); + response.writeByte(type); + response.writeBytes(time.resetReaderIndex()); + response.writeByte('\r'); + response.writeByte('\n'); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + + return position; + } + + private Object decodeBasicOther( + Channel channel, ByteBuf buf, DeviceSession deviceSession, int type, int dataLength) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (type == MSG_LBS_MULTIPLE || type == MSG_LBS_EXTEND || type == MSG_LBS_WIFI + || type == MSG_LBS_2 || type == MSG_WIFI_3) { + + boolean longFormat = type == MSG_LBS_2 || type == MSG_WIFI_3; + + DateBuilder dateBuilder = new DateBuilder(deviceSession.getTimeZone()) + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + + getLastLocation(position, dateBuilder.getDate()); + + int mcc = buf.readUnsignedShort(); + int mnc = BitUtil.check(mcc, 15) ? buf.readUnsignedShort() : buf.readUnsignedByte(); + Network network = new Network(); + for (int i = 0; i < 7; i++) { + int lac = longFormat ? buf.readInt() : buf.readUnsignedShort(); + int cid = longFormat ? (int) buf.readLong() : buf.readUnsignedMedium(); + int rssi = -buf.readUnsignedByte(); + if (lac > 0) { + network.addCellTower(CellTower.from(BitUtil.to(mcc, 15), mnc, lac, cid, rssi)); + } + } + + buf.readUnsignedByte(); // time leads + + if (type != MSG_LBS_MULTIPLE && type != MSG_LBS_2) { + int wifiCount = buf.readUnsignedByte(); + for (int i = 0; i < wifiCount; i++) { + String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:"); + network.addWifiAccessPoint(WifiAccessPoint.from( + mac.substring(0, mac.length() - 1), buf.readUnsignedByte())); + } + } + + position.setNetwork(network); + + } else if (type == MSG_STRING) { + + getLastLocation(position, null); + + int commandLength = buf.readUnsignedByte(); + + if (commandLength > 0) { + buf.readUnsignedByte(); // server flag (reserved) + position.set(Position.KEY_RESULT, + buf.readSlice(commandLength - 1).toString(StandardCharsets.US_ASCII)); + } + + } else if (type == MSG_BMS || type == MSG_BMS_2) { + + buf.skipBytes(8); // serial number + + getLastLocation(position, new Date(buf.readUnsignedInt() * 1000)); + + position.set("relativeCapacity", buf.readUnsignedByte()); + position.set("remainingCapacity", buf.readUnsignedShort()); + position.set("absoluteCapacity", buf.readUnsignedByte()); + position.set("fullCapacity", buf.readUnsignedShort()); + position.set("batteryHealth", buf.readUnsignedByte()); + position.set("batteryTemp", buf.readUnsignedShort() * 0.1 - 273.1); + position.set("current", buf.readUnsignedShort()); + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.001); + position.set("cycleIndex", buf.readUnsignedShort()); + for (int i = 1; i <= 14; i++) { + position.set("batteryCell" + i, buf.readUnsignedShort() * 0.001); + } + position.set("currentChargeInterval", buf.readUnsignedShort()); + position.set("maxChargeInterval", buf.readUnsignedShort()); + position.set("barcode", buf.readCharSequence(16, StandardCharsets.US_ASCII).toString().trim()); + position.set("batteryVersion", buf.readUnsignedShort()); + position.set("manufacturer", buf.readCharSequence(16, StandardCharsets.US_ASCII).toString().trim()); + position.set("batteryStatus", buf.readUnsignedInt()); + + position.set("controllerStatus", buf.readUnsignedInt()); + position.set("controllerFault", buf.readUnsignedInt()); + + sendResponse(channel, false, type, buf.getShort(buf.writerIndex() - 6), null); + + return position; + + } else if (isSupported(type)) { + + if (hasGps(type)) { + decodeGps(position, buf, false, deviceSession.getTimeZone()); + } else { + getLastLocation(position, null); + } + + if (hasLbs(type)) { + decodeLbs(position, buf, hasStatus(type)); + } + + if (hasStatus(type)) { + decodeStatus(position, buf); + } + + if (type == MSG_GPS_LBS_1 && buf.readableBytes() > 75 + 6) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + String data = buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); + buf.readUnsignedByte(); // alarm + buf.readUnsignedByte(); // swiped + position.set("driverLicense", data.trim()); + } + + if (type == MSG_GPS_LBS_1 && buf.readableBytes() == 2 + 6) { + int mask = buf.readUnsignedShort(); + position.set(Position.KEY_IGNITION, BitUtil.check(mask, 8 + 7)); + position.set(Position.PREFIX_IN + 2, BitUtil.check(mask, 8 + 6)); + if (BitUtil.check(mask, 8 + 4)) { + int value = BitUtil.to(mask, 8 + 1); + if (BitUtil.check(mask, 8 + 1)) { + value = -value; + } + position.set(Position.PREFIX_TEMP + 1, value); + } else { + int value = BitUtil.to(mask, 8 + 2); + if (BitUtil.check(mask, 8 + 5)) { + position.set(Position.PREFIX_ADC + 1, value); + } else { + position.set(Position.PREFIX_ADC + 1, value * 0.1); + } + } + } + + if (type == MSG_GPS_LBS_1 && buf.readableBytes() == 4 + 6) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + } + + if (type == MSG_GPS_LBS_2 && buf.readableBytes() == 3 + 6) { + position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0); + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); // reason + position.set(Position.KEY_ARCHIVE, buf.readUnsignedByte() > 0); + } + + } else if (type == MSG_ALARM) { + + DateBuilder dateBuilder = new DateBuilder(deviceSession.getTimeZone()) + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + + getLastLocation(position, dateBuilder.getDate()); + + short alarmType = buf.readUnsignedByte(); + + switch (alarmType) { + case 0x80: + position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION); + break; + case 0x87: + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + break; + case 0x90: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 0x91: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 0x92: + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + break; + default: + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + break; + } + + position.set("alarmValue", buf.readShort()); + + } else { + + if (dataLength > 0) { + buf.skipBytes(dataLength); + } + if (type != MSG_COMMAND_0 && type != MSG_COMMAND_1 && type != MSG_COMMAND_2) { + sendResponse(channel, false, type, buf.getShort(buf.writerIndex() - 6), null); + } + return null; + + } + + if (hasLanguage(type)) { + buf.readUnsignedShort(); + } + + if (type == MSG_GPS_LBS_STATUS_3 || type == MSG_FENCE_MULTI) { + position.set(Position.KEY_GEOFENCE, buf.readUnsignedByte()); + } + + sendResponse(channel, false, type, buf.getShort(buf.writerIndex() - 6), null); + + return position; + } + + private Object decodeExtended(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + if (deviceSession.getTimeZone() == null) { + deviceSession.setTimeZone(getTimeZone(deviceSession.getDeviceId())); + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedShort(); // length + int type = buf.readUnsignedByte(); + + if (type == MSG_STRING_INFO) { + + buf.readUnsignedInt(); // server flag + String data; + if (buf.readUnsignedByte() == 1) { + data = buf.readSlice(buf.readableBytes() - 6).toString(StandardCharsets.US_ASCII); + } else { + data = buf.readSlice(buf.readableBytes() - 6).toString(StandardCharsets.UTF_16BE); + } + + if (decodeLocationString(position, data) == null) { + getLastLocation(position, null); + position.set(Position.KEY_RESULT, data); + } + + return position; + + } else if (type == MSG_INFO) { + + int subType = buf.readUnsignedByte(); + + getLastLocation(position, null); + + if (subType == 0x00) { + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort() * 0.01); + return position; + } else if (subType == 0x05) { + int flags = buf.readUnsignedByte(); + position.set(Position.KEY_DOOR, BitUtil.check(flags, 0)); + position.set(Position.PREFIX_IO + 1, BitUtil.check(flags, 2)); + return position; + } else if (subType == 0x0a) { + buf.skipBytes(8); // imei + buf.skipBytes(8); // imsi + position.set(Position.KEY_ICCID, ByteBufUtil.hexDump(buf.readSlice(8))); + return position; + } else if (subType == 0x0d) { + if (buf.getByte(buf.readerIndex()) != '!') { + buf.skipBytes(6); + } + return decodeFuelData(position, buf.toString( + buf.readerIndex(), buf.readableBytes() - 4 - 2, StandardCharsets.US_ASCII)); + } + + } else if (type == MSG_X1_PHOTO_DATA) { + + int pictureId = buf.readInt(); + + ByteBuf photo = photos.get(pictureId); + + buf.readUnsignedInt(); // offset + buf.readBytes(photo, buf.readUnsignedShort()); + + if (photo.writableBytes() > 0) { + sendPhotoRequest(channel, pictureId); + } else { + Device device = Context.getDeviceManager().getById(deviceSession.getDeviceId()); + position.set( + Position.KEY_IMAGE, Context.getMediaManager().writeFile(device.getUniqueId(), photo, "jpg")); + photos.remove(pictureId).release(); + } + + } else if (type == MSG_AZ735_GPS || type == MSG_AZ735_ALARM) { + + if (!decodeGps(position, buf, true, deviceSession.getTimeZone())) { + getLastLocation(position, position.getDeviceTime()); + } + + if (decodeLbs(position, buf, true)) { + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + } + + buf.skipBytes(buf.readUnsignedByte()); // additional cell towers + buf.skipBytes(buf.readUnsignedByte()); // wifi access point + + int status = buf.readUnsignedByte(); + position.set(Position.KEY_STATUS, status); + + if (type == MSG_AZ735_ALARM) { + switch (status) { + case 0xA0: + position.set(Position.KEY_ARMED, true); + break; + case 0xA1: + position.set(Position.KEY_ARMED, false); + break; + case 0xA2: + case 0xA3: + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + case 0xA4: + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + break; + case 0xA5: + position.set(Position.KEY_ALARM, Position.ALARM_DOOR); + break; + default: + break; + } + } + + buf.skipBytes(buf.readUnsignedByte()); // reserved extension + + sendResponse(channel, true, type, buf.getShort(buf.writerIndex() - 6), null); + + return position; + + } else if (type == MSG_OBD) { + + DateBuilder dateBuilder = new DateBuilder(deviceSession.getTimeZone()) + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + + getLastLocation(position, dateBuilder.getDate()); + + position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0); + + String data = buf.readCharSequence(buf.readableBytes() - 18, StandardCharsets.US_ASCII).toString(); + for (String pair : data.split(",")) { + String[] values = pair.split("="); + switch (Integer.parseInt(values[0].substring(0, 2), 16)) { + case 40: + position.set(Position.KEY_ODOMETER, Integer.parseInt(values[1], 16) * 0.01); + break; + case 43: + position.set(Position.KEY_FUEL_LEVEL, Integer.parseInt(values[1], 16) * 0.01); + break; + case 45: + position.set(Position.KEY_COOLANT_TEMP, Integer.parseInt(values[1], 16) * 0.01); + break; + case 53: + position.set(Position.KEY_OBD_SPEED, Integer.parseInt(values[1], 16) * 0.01); + break; + case 54: + position.set(Position.KEY_RPM, Integer.parseInt(values[1], 16) * 0.01); + break; + case 71: + position.set(Position.KEY_FUEL_USED, Integer.parseInt(values[1], 16) * 0.01); + break; + case 73: + position.set(Position.KEY_HOURS, Integer.parseInt(values[1], 16) * 0.01); + break; + case 74: + position.set(Position.KEY_VIN, values[1]); + break; + default: + break; + } + } + + return position; + + } else if (type == MSG_RFID) { + + getLastLocation(position, null); + + buf.readUnsignedByte(); // external device type code + buf.readUnsignedByte(); // card type + position.set( + Position.KEY_DRIVER_UNIQUE_ID, + buf.readCharSequence(buf.readableBytes() - 9, StandardCharsets.US_ASCII).toString()); + + return position; + + } else if (type == MSG_GPS_MODULAR) { + + return decodeExtendedModular(buf, deviceSession); + + } else { + + return decodeExtendedOther(channel, buf, deviceSession, type); + + } + + return null; + } + + private Object decodeExtendedModular(ByteBuf buf, DeviceSession deviceSession) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + while (buf.readableBytes() > 6) { + int moduleType = buf.readUnsignedShort(); + int moduleLength = buf.readUnsignedShort(); + + switch (moduleType) { + case 0x03: + position.set(Position.KEY_ICCID, ByteBufUtil.hexDump(buf.readSlice(10))); + break; + case 0x09: + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + break; + case 0x0a: + position.set(Position.KEY_SATELLITES_VISIBLE, buf.readUnsignedByte()); + break; + case 0x11: + CellTower cellTower = CellTower.from( + buf.readUnsignedShort(), + buf.readUnsignedShort(), + buf.readUnsignedShort(), + buf.readUnsignedMedium(), + buf.readUnsignedByte()); + if (cellTower.getCellId() > 0) { + position.setNetwork(new Network(cellTower)); + } + break; + case 0x18: + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); + break; + case 0x28: + position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1); + break; + case 0x29: + position.set(Position.KEY_INDEX, buf.readUnsignedInt()); + break; + case 0x2a: + int input = buf.readUnsignedByte(); + position.set(Position.KEY_DOOR, BitUtil.to(input, 4) > 0); + position.set("tamper", BitUtil.from(input, 4) > 0); + break; + case 0x2b: + int event = buf.readUnsignedByte(); + switch (event) { + case 0x11: + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + case 0x12: + position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); + break; + case 0x13: + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + break; + case 0x14: + position.set(Position.KEY_ALARM, Position.ALARM_REMOVING); + break; + default: + break; + } + position.set(Position.KEY_EVENT, event); + break; + case 0x2e: + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + break; + case 0x33: + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.setAltitude(buf.readShort()); + + double latitude = buf.readUnsignedInt() / 60.0 / 30000.0; + double longitude = buf.readUnsignedInt() / 60.0 / 30000.0; + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + + int flags = buf.readUnsignedShort(); + position.setCourse(BitUtil.to(flags, 10)); + position.setValid(BitUtil.check(flags, 12)); + + if (!BitUtil.check(flags, 10)) { + latitude = -latitude; + } + if (BitUtil.check(flags, 11)) { + longitude = -longitude; + } + + position.setLatitude(latitude); + position.setLongitude(longitude); + break; + case 0x34: + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + buf.readUnsignedIntLE(); // time + buf.skipBytes(buf.readUnsignedByte()); // content + break; + default: + buf.skipBytes(moduleLength); + break; + } + } + + return position; + } + + private Object decodeExtendedOther(Channel channel, ByteBuf buf, DeviceSession deviceSession, int type) { + + Position position = null; + + if (type == MSG_MULTIMEDIA || type == MSG_MULTIMEDIA_2) { + + buf.skipBytes(8); // serial number + long timestamp = buf.readUnsignedInt() * 1000; + buf.skipBytes(4 + 4 + 2 + 1 + 1 + 2); // gps + buf.skipBytes(2 + 2 + 2 + 2); // cell + + int mediaId = buf.readInt(); + int mediaLength = buf.readInt(); + int mediaType = buf.readUnsignedByte(); + int mediaFormat = buf.readUnsignedByte(); + + if (mediaType == 0 && mediaFormat == 0) { + + buf.readUnsignedByte(); // event + + ByteBuf photo; + if (buf.readUnsignedShort() == 0) { + photo = Unpooled.buffer(mediaLength); + if (photos.containsKey(mediaId)) { + photos.remove(mediaId).release(); + } + photos.put(mediaId, photo); + } else { + photo = photos.get(mediaId); + } + + if (photo != null) { + buf.readBytes(photo, buf.readableBytes() - 3 * 2); + if (!photo.isWritable()) { + position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, new Date(timestamp)); + Device device = Context.getDeviceManager().getById(deviceSession.getDeviceId()); + position.set(Position.KEY_IMAGE, + Context.getMediaManager().writeFile(device.getUniqueId(), photo, "jpg")); + photos.remove(mediaId).release(); + } + } + + } + + sendResponse(channel, true, type, buf.getShort(buf.writerIndex() - 6), null); + + return position; + + } + + return null; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int header = buf.readShort(); + + if (header == 0x7878) { + return decodeBasic(channel, remoteAddress, buf); + } else if (header == 0x7979) { + return decodeExtended(channel, remoteAddress, buf); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java new file mode 100644 index 000000000..9115ba10f --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gt06ProtocolEncoder.java @@ -0,0 +1,89 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.Context; +import org.traccar.helper.Checksum; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.nio.charset.StandardCharsets; + +public class Gt06ProtocolEncoder extends BaseProtocolEncoder { + + public Gt06ProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf encodeContent(long deviceId, String content) { + + boolean language = Context.getIdentityManager() + .lookupAttributeBoolean(deviceId, getProtocolName() + ".language", false, false, true); + + ByteBuf buf = Unpooled.buffer(); + + buf.writeByte(0x78); + buf.writeByte(0x78); + + buf.writeByte(1 + 1 + 4 + content.length() + 2 + 2 + (language ? 2 : 0)); // message length + + buf.writeByte(0x80); // message type + + buf.writeByte(4 + content.length()); // command length + buf.writeInt(0); + buf.writeBytes(content.getBytes(StandardCharsets.US_ASCII)); // command + + if (language) { + buf.writeShort(2); // english language + } + + buf.writeShort(0); // message index + + buf.writeShort(Checksum.crc16(Checksum.CRC16_X25, buf.nioBuffer(2, buf.writerIndex() - 2))); + + buf.writeByte('\r'); + buf.writeByte('\n'); + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + + boolean alternative = Context.getIdentityManager().lookupAttributeBoolean( + command.getDeviceId(), getProtocolName() + ".alternative", false, false, true); + + String password = Context.getIdentityManager() + .getDevicePassword(command.getDeviceId(), getProtocolName(), "123456"); + + switch (command.getType()) { + case Command.TYPE_ENGINE_STOP: + return encodeContent(command.getDeviceId(), + alternative ? "DYD," + password + "#" : "Relay,1#"); + case Command.TYPE_ENGINE_RESUME: + return encodeContent(command.getDeviceId(), + alternative ? "HFYD," + password + "#" : "Relay,0#"); + case Command.TYPE_CUSTOM: + return encodeContent(command.getDeviceId(), command.getString(Command.KEY_DATA)); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Gt30Protocol.java b/src/main/java/org/traccar/protocol/Gt30Protocol.java new file mode 100644 index 000000000..aa4ad20b1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gt30Protocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Gt30Protocol extends BaseProtocol { + + public Gt30Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Gt30ProtocolDecoder(Gt30Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Gt30ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt30ProtocolDecoder.java new file mode 100644 index 000000000..abf208a46 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Gt30ProtocolDecoder.java @@ -0,0 +1,114 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.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 Gt30ProtocolDecoder extends BaseProtocolDecoder { + + public Gt30ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$$") + .number("x{4}") // length + .expression("(.{14})") // device id + .number("x{4}") // type + .expression("(.)?") // alarm + .number("(dd)(dd)(dd).(ddd),") // time (hhmmss.sss) + .expression("([AV]),") // validity + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.d+)?,") // speed + .number("(d+.d+)?,") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .expression("[^\\|]*") + .number("|(d+.d+)") // hdop + .number("|(-?d+)") // altitude + .number("x{4}") // checksum + .compile(); + + private String decodeAlarm(int value) { + switch (value) { + case 0x01: + case 0x02: + case 0x03: + return Position.ALARM_SOS; + case 0x10: + return Position.ALARM_LOW_BATTERY; + case 0x11: + return Position.ALARM_OVERSPEED; + case 0x12: + return Position.ALARM_GEOFENCE; + default: + return null; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next().trim()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (parser.hasNext()) { + position.set(Position.KEY_ALARM, decodeAlarm(parser.next().charAt(0))); + } + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + position.set(Position.KEY_HDOP, parser.nextDouble()); + + position.setAltitude(parser.nextDouble(0)); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/H02FrameDecoder.java b/src/main/java/org/traccar/protocol/H02FrameDecoder.java new file mode 100644 index 000000000..583ad599f --- /dev/null +++ b/src/main/java/org/traccar/protocol/H02FrameDecoder.java @@ -0,0 +1,95 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class H02FrameDecoder extends BaseFrameDecoder { + + private static final int MESSAGE_SHORT = 32; + private static final int MESSAGE_LONG = 45; + + private int messageLength; + + public H02FrameDecoder(int messageLength) { + this.messageLength = messageLength; + } + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + char marker = (char) buf.getByte(buf.readerIndex()); + + while (marker != '*' && marker != '$' && marker != 'X' && buf.readableBytes() > 0) { + buf.skipBytes(1); + if (buf.readableBytes() > 0) { + marker = (char) buf.getByte(buf.readerIndex()); + } + } + + switch (marker) { + case '*': + + // Return text message + int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '#'); + if (index != -1) { + ByteBuf result = buf.readRetainedSlice(index + 1 - buf.readerIndex()); + while (buf.isReadable() + && (buf.getByte(buf.readerIndex()) == '\r' || buf.getByte(buf.readerIndex()) == '\n')) { + buf.readByte(); // skip new line + } + return result; + } + + break; + + case '$': + + if (messageLength == 0) { + if (buf.readableBytes() == MESSAGE_LONG) { + messageLength = MESSAGE_LONG; + } else { + messageLength = MESSAGE_SHORT; + } + } + + if (buf.readableBytes() >= messageLength) { + return buf.readRetainedSlice(messageLength); + } + + break; + + case 'X': + + if (buf.readableBytes() >= MESSAGE_SHORT) { + return buf.readRetainedSlice(MESSAGE_SHORT); + } + + break; + + default: + + return null; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/H02Protocol.java b/src/main/java/org/traccar/protocol/H02Protocol.java new file mode 100644 index 000000000..b897d83ad --- /dev/null +++ b/src/main/java/org/traccar/protocol/H02Protocol.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.Context; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class H02Protocol extends BaseProtocol { + + public H02Protocol() { + setSupportedDataCommands( + Command.TYPE_ALARM_ARM, + Command.TYPE_ALARM_DISARM, + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME, + Command.TYPE_POSITION_PERIODIC + ); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + int messageLength = Context.getConfig().getInteger(getName() + ".messageLength"); + pipeline.addLast(new H02FrameDecoder(messageLength)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new H02ProtocolEncoder(H02Protocol.this)); + pipeline.addLast(new H02ProtocolDecoder(H02Protocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new H02ProtocolEncoder(H02Protocol.this)); + pipeline.addLast(new H02ProtocolDecoder(H02Protocol.this)); + } + }); + } +} diff --git a/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java b/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java new file mode 100644 index 000000000..320fe991d --- /dev/null +++ b/src/main/java/org/traccar/protocol/H02ProtocolDecoder.java @@ -0,0 +1,600 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; +import java.util.regex.Pattern; + +public class H02ProtocolDecoder extends BaseProtocolDecoder { + + public H02ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static double readCoordinate(ByteBuf buf, boolean lon) { + + int degrees = BcdUtil.readInteger(buf, 2); + if (lon) { + degrees = degrees * 10 + (buf.getUnsignedByte(buf.readerIndex()) >> 4); + } + + double result = 0; + if (lon) { + result = buf.readUnsignedByte() & 0x0f; + } + + int length = 6; + if (lon) { + length = 5; + } + + result = result * 10 + BcdUtil.readInteger(buf, length) * 0.0001; + + result /= 60; + result += degrees; + + return result; + } + + private void processStatus(Position position, long status) { + + if (!BitUtil.check(status, 0)) { + position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION); + } else if (!BitUtil.check(status, 1) || !BitUtil.check(status, 18)) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } else if (!BitUtil.check(status, 2)) { + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + } else if (!BitUtil.check(status, 19)) { + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + } + + position.set(Position.KEY_IGNITION, BitUtil.check(status, 10)); + position.set(Position.KEY_STATUS, status); + + } + + private Integer decodeBattery(int value) { + if (value == 0) { + return null; + } else if (value <= 3) { + return (value - 1) * 10; + } else if (value <= 6) { + return (value - 1) * 20; + } else if (value <= 100) { + return value; + } else if (value >= 0xF1 && value <= 0xF6) { + return value - 0xF0; + } else { + return null; + } + } + + private Position decodeBinary(ByteBuf buf, Channel channel, SocketAddress remoteAddress) { + + Position position = new Position(getProtocolName()); + + boolean longId = buf.readableBytes() == 42; + + buf.readByte(); // marker + + String id; + if (longId) { + id = ByteBufUtil.hexDump(buf.readSlice(8)).substring(0, 15); + } else { + id = ByteBufUtil.hexDump(buf.readSlice(5)); + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setHour(BcdUtil.readInteger(buf, 2)) + .setMinute(BcdUtil.readInteger(buf, 2)) + .setSecond(BcdUtil.readInteger(buf, 2)) + .setDay(BcdUtil.readInteger(buf, 2)) + .setMonth(BcdUtil.readInteger(buf, 2)) + .setYear(BcdUtil.readInteger(buf, 2)); + position.setTime(dateBuilder.getDate()); + + double latitude = readCoordinate(buf, false); + position.set(Position.KEY_BATTERY_LEVEL, decodeBattery(buf.readUnsignedByte())); + double longitude = readCoordinate(buf, true); + + int flags = buf.readUnsignedByte() & 0x0f; + position.setValid((flags & 0x02) != 0); + if ((flags & 0x04) == 0) { + latitude = -latitude; + } + if ((flags & 0x08) == 0) { + longitude = -longitude; + } + + position.setLatitude(latitude); + position.setLongitude(longitude); + + position.setSpeed(BcdUtil.readInteger(buf, 3)); + position.setCourse((buf.readUnsignedByte() & 0x0f) * 100.0 + BcdUtil.readInteger(buf, 2)); + + processStatus(position, buf.readUnsignedInt()); + + return position; + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("*") + .expression("..,") // manufacturer + .number("(d+)?,") // imei + .groupBegin() + .text("V4,") + .expression("(.*),") // response + .or() + .expression("(V[^,]*),") + .groupEnd() + .number("(?:(dd)(dd)(dd))?,") // time (hhmmss) + .groupBegin() + .expression("([ABV])?,") // validity + .or() + .number("(d+),") // coding scheme + .groupEnd() + .groupBegin() + .number("-(d+)-(d+.d+),") // latitude + .or() + .number("(d+)(dd.d+),") // latitude + .groupEnd() + .expression("([NS]),") + .groupBegin() + .number("-(d+)-(d+.d+),") // longitude + .or() + .number("(d+)(dd.d+),") // longitude + .groupEnd() + .expression("([EW]),") + .number(" *(d+.?d*),") // speed + .number("(d+.?d*)?,") // course + .number("(?:d+,)?") // battery + .number("(?:(dd)(dd)(dd))?") // date (ddmmyy) + .groupBegin() + .expression(",[^,]*,") + .expression("[^,]*,") + .expression("[^,]*") // sim info + .groupEnd("?") + .groupBegin() + .number(",(x{8})") + .groupBegin() + .number(",(d+),") // odometer + .number("(-?d+),") // temperature + .number("(d+.d+),") // fuel + .number("(-?d+),") // altitude + .number("(x+),") // lac + .number("(x+)") // cid + .or() + .text(",") + .expression("(.*)") // data + .or() + .groupEnd() + .or() + .groupEnd() + .text("#") + .compile(); + + private static final Pattern PATTERN_NBR = new PatternBuilder() + .text("*") + .expression("..,") // manufacturer + .number("(d+),") // imei + .text("NBR,") + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("d+,") // gsm delay time + .number("d+,") // count + .number("((?:d+,d+,-?d+,)+)") // cells + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(x{8})") // status + .any() + .compile(); + + private static final Pattern PATTERN_LINK = new PatternBuilder() + .text("*") + .expression("..,") // manufacturer + .number("(d+),") // imei + .text("LINK,") + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d+),") // rssi + .number("(d+),") // satellites + .number("(d+),") // battery + .number("(d+),") // steps + .number("(d+),") // turnovers + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(x{8})") // status + .any() + .compile(); + + private static final Pattern PATTERN_V3 = new PatternBuilder() + .text("*") + .expression("..,") // manufacturer + .number("(d+),") // imei + .text("V3,") + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(ddd)") // mcc + .number("(d+),") // mnc + .number("(d+),") // count + .expression("(.*),") // cell info + .number("(x{4}),") // battery + .number("d+,") // reboot info + .text("X,") + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(x{8})") // status + .text("#").optional() + .compile(); + + private static final Pattern PATTERN_VP1 = new PatternBuilder() + .text("*hq,") + .number("(d{15}),") // imei + .text("VP1,") + .groupBegin() + .text("V,") + .number("(d+),") // mcc + .number("(d+),") // mnc + .expression("([^#]+)") // cells + .or() + .expression("[AB],") // validity + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.d+),") // speed + .number("(d+.d+),") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .groupEnd() + .any() + .compile(); + + private void sendResponse(Channel channel, SocketAddress remoteAddress, String id, String type) { + if (channel != null && id != null) { + String response; + DateFormat dateFormat = new SimpleDateFormat(type.equals("R12") ? "HHmmss" : "yyyyMMddHHmmss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + String time = dateFormat.format(new Date()); + if (type.equals("R12")) { + response = String.format("*HQ,%s,%s,%s#", id, type, time); + } else { + response = String.format("*HQ,%s,V4,%s,%s#", id, type, time); + } + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + private Position decodeText(String sentence, Channel channel, SocketAddress remoteAddress) { + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + String id = parser.next(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (parser.hasNext()) { + position.set(Position.KEY_RESULT, parser.next()); + } + + if (parser.hasNext() && parser.next().equals("V1")) { + sendResponse(channel, remoteAddress, id, "V1"); + } else if (Context.getConfig().getBoolean(getProtocolName() + ".ack")) { + sendResponse(channel, remoteAddress, id, "R12"); + } + + DateBuilder dateBuilder = new DateBuilder(); + if (parser.hasNext(3)) { + dateBuilder.setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + } + + if (parser.hasNext()) { + position.setValid(parser.next().equals("A")); + } + if (parser.hasNext()) { + parser.nextInt(); // coding scheme + position.setValid(true); + } + + if (parser.hasNext(2)) { + position.setLatitude(-parser.nextCoordinate()); + } + if (parser.hasNext(2)) { + position.setLatitude(parser.nextCoordinate()); + } + + if (parser.hasNext(2)) { + position.setLongitude(-parser.nextCoordinate()); + } + if (parser.hasNext(2)) { + position.setLongitude(parser.nextCoordinate()); + } + + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + if (parser.hasNext(3)) { + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + } else { + position.setTime(new Date()); + } + + if (parser.hasNext()) { + processStatus(position, parser.nextLong(16, 0)); + } + + if (parser.hasNext(6)) { + position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + position.set(Position.PREFIX_TEMP + 1, parser.nextInt(0)); + position.set(Position.KEY_FUEL_LEVEL, parser.nextDouble(0)); + + position.setAltitude(parser.nextInt(0)); + + position.setNetwork(new Network(CellTower.fromLacCid(parser.nextHexInt(0), parser.nextHexInt(0)))); + } + + if (parser.hasNext(4)) { + String[] values = parser.next().split(","); + for (int i = 0; i < values.length; i++) { + position.set(Position.PREFIX_IO + (i + 1), values[i].trim()); + } + } + + return position; + } + + private Position decodeLbs(String sentence, Channel channel, SocketAddress remoteAddress) { + + Parser parser = new Parser(PATTERN_NBR, sentence); + if (!parser.matches()) { + return null; + } + + String id = parser.next(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + sendResponse(channel, remoteAddress, id, "NBR"); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + Network network = new Network(); + int mcc = parser.nextInt(0); + int mnc = parser.nextInt(0); + + String[] cells = parser.next().split(","); + for (int i = 0; i < cells.length / 3; i++) { + network.addCellTower(CellTower.from(mcc, mnc, Integer.parseInt(cells[i * 3]), + Integer.parseInt(cells[i * 3 + 1]), Integer.parseInt(cells[i * 3 + 2]))); + } + + position.setNetwork(network); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + getLastLocation(position, dateBuilder.getDate()); + + processStatus(position, parser.nextLong(16, 0)); + + return position; + } + + private Position decodeLink(String sentence, Channel channel, SocketAddress remoteAddress) { + + Parser parser = new Parser(PATTERN_LINK, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + position.set(Position.KEY_STEPS, parser.nextInt()); + position.set("turnovers", parser.nextInt()); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + getLastLocation(position, dateBuilder.getDate()); + + processStatus(position, parser.nextLong(16, 0)); + + return position; + } + + private Position decodeV3(String sentence, Channel channel, SocketAddress remoteAddress) { + + Parser parser = new Parser(PATTERN_V3, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + int mcc = parser.nextInt(); + int mnc = parser.nextInt(); + + int count = parser.nextInt(); + Network network = new Network(); + String[] values = parser.next().split(","); + for (int i = 0; i < count; i++) { + network.addCellTower(CellTower.from( + mcc, mnc, Integer.parseInt(values[i * 4]), Integer.parseInt(values[i * 4 + 1]))); + } + position.setNetwork(network); + + position.set(Position.KEY_BATTERY, parser.nextHexInt()); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + getLastLocation(position, dateBuilder.getDate()); + + processStatus(position, parser.nextLong(16, 0)); + + return position; + } + + private Position decodeVp1(String sentence, Channel channel, SocketAddress remoteAddress) { + + Parser parser = new Parser(PATTERN_VP1, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (parser.hasNext(3)) { + + getLastLocation(position, null); + + int mcc = parser.nextInt(); + int mnc = parser.nextInt(); + + Network network = new Network(); + for (String cell : parser.next().split("Y")) { + String[] values = cell.split(","); + network.addCellTower(CellTower.from(mcc, mnc, + Integer.parseInt(values[0]), Integer.parseInt(values[1]), Integer.parseInt(values[2]))); + } + + position.setNetwork(network); + + } else { + + position.setValid(true); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble()); + position.setCourse(parser.nextDouble()); + + position.setTime(new DateBuilder() + .setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)).getDate()); + + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + String marker = buf.toString(0, 1, StandardCharsets.US_ASCII); + + switch (marker) { + case "*": + String sentence = buf.toString(StandardCharsets.US_ASCII).trim(); + int typeStart = sentence.indexOf(',', sentence.indexOf(',') + 1) + 1; + int typeEnd = sentence.indexOf(',', typeStart); + if (typeEnd < 0) { + typeEnd = sentence.indexOf('#', typeStart); + } + if (typeEnd > 0) { + String type = sentence.substring(typeStart, typeEnd); + switch (type) { + case "V0": + case "HTBT": + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(sentence, remoteAddress)); + } + return null; + case "NBR": + return decodeLbs(sentence, channel, remoteAddress); + case "LINK": + return decodeLink(sentence, channel, remoteAddress); + case "V3": + return decodeV3(sentence, channel, remoteAddress); + case "VP1": + return decodeVp1(sentence, channel, remoteAddress); + default: + return decodeText(sentence, channel, remoteAddress); + } + } else { + return null; + } + case "$": + return decodeBinary(buf, channel, remoteAddress); + case "X": + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/H02ProtocolEncoder.java b/src/main/java/org/traccar/protocol/H02ProtocolEncoder.java new file mode 100644 index 000000000..7a765332c --- /dev/null +++ b/src/main/java/org/traccar/protocol/H02ProtocolEncoder.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Gabor Somogyi (gabor.g.somogyi@gmail.com) + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.Context; +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.util.Date; + +public class H02ProtocolEncoder extends StringProtocolEncoder { + + private static final String MARKER = "HQ"; + + public H02ProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private Object formatCommand(Date time, String uniqueId, String type, String... params) { + + StringBuilder result = new StringBuilder( + String.format("*%s,%s,%s,%4$tH%4$tM%4$tS", MARKER, uniqueId, type, time)); + + for (String param : params) { + result.append(",").append(param); + } + + result.append("#"); + + return result.toString(); + } + + protected Object encodeCommand(Command command, Date time) { + String uniqueId = getUniqueId(command.getDeviceId()); + + switch (command.getType()) { + case Command.TYPE_ALARM_ARM: + return formatCommand(time, uniqueId, "SCF", "0", "0"); + case Command.TYPE_ALARM_DISARM: + return formatCommand(time, uniqueId, "SCF", "1", "1"); + case Command.TYPE_ENGINE_STOP: + return formatCommand(time, uniqueId, "S20", "1", "1"); + case Command.TYPE_ENGINE_RESUME: + return formatCommand(time, uniqueId, "S20", "1", "0"); + case Command.TYPE_POSITION_PERIODIC: + String frequency = command.getAttributes().get(Command.KEY_FREQUENCY).toString(); + if (Context.getIdentityManager().lookupAttributeBoolean( + command.getDeviceId(), getProtocolName() + ".alternative", false, false, true)) { + return formatCommand(time, uniqueId, "D1", frequency); + } else { + return formatCommand(time, uniqueId, "S71", "22", frequency); + } + default: + return null; + } + } + + @Override + protected Object encodeCommand(Command command) { + return encodeCommand(command, new Date()); + } + +} diff --git a/src/main/java/org/traccar/protocol/HaicomProtocol.java b/src/main/java/org/traccar/protocol/HaicomProtocol.java new file mode 100644 index 000000000..6e5760bd4 --- /dev/null +++ b/src/main/java/org/traccar/protocol/HaicomProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class HaicomProtocol extends BaseProtocol { + + public HaicomProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '*')); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new HaicomProtocolDecoder(HaicomProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/HaicomProtocolDecoder.java b/src/main/java/org/traccar/protocol/HaicomProtocolDecoder.java new file mode 100644 index 000000000..dd20f2aeb --- /dev/null +++ b/src/main/java/org/traccar/protocol/HaicomProtocolDecoder.java @@ -0,0 +1,109 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class HaicomProtocolDecoder extends BaseProtocolDecoder { + + public HaicomProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$GPRS") + .number("(d+),") // imei + .expression("([^,]+),") // version + .number("(dd)(dd)(dd),") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d)") // flags + .number("(dd)(d{5})") // latitude + .number("(ddd)(d{5}),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(d+),") // status + .number("(d+)?,") // gprs counting value + .number("(d+)?,") // gps power saving counting value + .number("(d+),") // switch status + .number("(d+)") // relay status + .expression("(?:[LH]{2})?") // power status + .number("#V(d+)") // battery + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_VERSION_FW, parser.next()); + + position.setTime(parser.nextDateTime()); + + int flags = parser.nextInt(0); + + position.setValid(BitUtil.check(flags, 0)); + + double latitude = parser.nextDouble(0) + parser.nextDouble(0) / 60000; + if (BitUtil.check(flags, 2)) { + position.setLatitude(latitude); + } else { + position.setLatitude(-latitude); + } + + double longitude = parser.nextDouble(0) + parser.nextDouble(0) / 60000; + if (BitUtil.check(flags, 1)) { + position.setLongitude(longitude); + } else { + position.setLongitude(-longitude); + } + + position.setSpeed(parser.nextDouble(0) / 10); + position.setCourse(parser.nextDouble(0) / 10); + + position.set(Position.KEY_STATUS, parser.next()); + position.set("gprsCount", parser.next()); + position.set("powersaveCountdown", parser.next()); + position.set(Position.KEY_INPUT, parser.next()); + position.set(Position.KEY_OUTPUT, parser.next()); + position.set(Position.KEY_BATTERY, parser.nextDouble(0) * 0.1); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/HomtecsProtocol.java b/src/main/java/org/traccar/protocol/HomtecsProtocol.java new file mode 100644 index 000000000..34dbf0f51 --- /dev/null +++ b/src/main/java/org/traccar/protocol/HomtecsProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class HomtecsProtocol extends BaseProtocol { + + public HomtecsProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new HomtecsProtocolDecoder(HomtecsProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/HomtecsProtocolDecoder.java b/src/main/java/org/traccar/protocol/HomtecsProtocolDecoder.java new file mode 100644 index 000000000..a93572b5c --- /dev/null +++ b/src/main/java/org/traccar/protocol/HomtecsProtocolDecoder.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class HomtecsProtocolDecoder extends BaseProtocolDecoder { + + public HomtecsProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression("([^_]+)") // id + .text("_R") + .number("(x{8}),") // mac ending + .number("(dd)(dd)(dd),") // date (yymmdd) + .number("(dd)(dd)(dd).d+,") // time (hhmmss) + .number("(d+),") // satellites + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .number("(d),") // fix status + .number("(d+.?d*)?,") // hdop + .number("(d+.?d*)?") // altitude + .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; + } + + String id = parser.next(); + String mac = parser.next(); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id, id + "_R" + mac); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.YMD_HMS)); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.setValid(parser.nextInt(0) > 0); + + position.set(Position.KEY_HDOP, parser.nextDouble(0)); + + position.setAltitude(parser.nextDouble(0)); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/HuaShengFrameDecoder.java b/src/main/java/org/traccar/protocol/HuaShengFrameDecoder.java new file mode 100644 index 000000000..bd52aa9e7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/HuaShengFrameDecoder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class HuaShengFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 2) { + return null; + } + + int index = buf.indexOf(buf.readerIndex() + 1, buf.writerIndex(), (byte) 0xC0); + if (index != -1) { + ByteBuf result = Unpooled.buffer(index + 1 - buf.readerIndex()); + + while (buf.readerIndex() <= index) { + int b = buf.readUnsignedByte(); + if (b == 0xDB) { + int ext = buf.readUnsignedByte(); + if (ext == 0xDC) { + result.writeByte(0xC0); + } else if (ext == 0xDD) { + result.writeByte(0xDB); + } + } else { + result.writeByte(b); + } + } + + return result; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/HuaShengProtocol.java b/src/main/java/org/traccar/protocol/HuaShengProtocol.java new file mode 100644 index 000000000..103f2d501 --- /dev/null +++ b/src/main/java/org/traccar/protocol/HuaShengProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class HuaShengProtocol extends BaseProtocol { + + public HuaShengProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HuaShengFrameDecoder()); + pipeline.addLast(new HuaShengProtocolDecoder(HuaShengProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java new file mode 100644 index 000000000..920898039 --- /dev/null +++ b/src/main/java/org/traccar/protocol/HuaShengProtocolDecoder.java @@ -0,0 +1,193 @@ +/* + * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; + +public class HuaShengProtocolDecoder extends BaseProtocolDecoder { + + public HuaShengProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_POSITION = 0xAA00; + public static final int MSG_POSITION_RSP = 0xFF01; + public static final int MSG_LOGIN = 0xAA02; + public static final int MSG_LOGIN_RSP = 0xFF03; + public static final int MSG_HSO_REQ = 0x0002; + public static final int MSG_HSO_RSP = 0x0003; + + private void sendResponse(Channel channel, int type, int index, ByteBuf content) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(0xC0); + response.writeShort(0x0100); + response.writeShort(12 + (content != null ? content.readableBytes() : 0)); + response.writeShort(type); + response.writeShort(0); + response.writeInt(index); + if (content != null) { + response.writeBytes(content); + content.release(); + } + response.writeByte(0xC0); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(1); // start marker + buf.readUnsignedByte(); // flag + buf.readUnsignedByte(); // reserved + buf.readUnsignedShort(); // length + + int type = buf.readUnsignedShort(); + + buf.readUnsignedShort(); // checksum + int index = buf.readInt(); + + if (type == MSG_LOGIN) { + + while (buf.readableBytes() > 4) { + int subtype = buf.readUnsignedShort(); + int length = buf.readUnsignedShort() - 4; + if (subtype == 0x0003) { + String imei = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession != null && channel != null) { + ByteBuf content = Unpooled.buffer(); + content.writeByte(0); // success + sendResponse(channel, MSG_LOGIN_RSP, index, content); + } + } else { + buf.skipBytes(length); + } + } + + } else if (type == MSG_HSO_REQ) { + + sendResponse(channel, MSG_HSO_RSP, index, null); + + } else if (type == MSG_POSITION) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int status = buf.readUnsignedShort(); + + position.setValid(BitUtil.check(status, 15)); + + position.set(Position.KEY_STATUS, status); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 14)); + position.set(Position.KEY_EVENT, buf.readUnsignedShort()); + + String time = buf.readCharSequence(12, StandardCharsets.US_ASCII).toString(); + + DateBuilder dateBuilder = new DateBuilder() + .setYear(Integer.parseInt(time.substring(0, 2))) + .setMonth(Integer.parseInt(time.substring(2, 4))) + .setDay(Integer.parseInt(time.substring(4, 6))) + .setHour(Integer.parseInt(time.substring(6, 8))) + .setMinute(Integer.parseInt(time.substring(8, 10))) + .setSecond(Integer.parseInt(time.substring(10, 12))); + position.setTime(dateBuilder.getDate()); + + position.setLongitude(buf.readInt() * 0.00001); + position.setLatitude(buf.readInt() * 0.00001); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort())); + position.setCourse(buf.readUnsignedShort()); + position.setAltitude(buf.readUnsignedShort()); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedShort() * 1000); + + while (buf.readableBytes() > 4) { + int subtype = buf.readUnsignedShort(); + int length = buf.readUnsignedShort() - 4; + switch (subtype) { + case 0x0001: + position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedByte() - 40); + position.set(Position.KEY_RPM, buf.readUnsignedShort()); + position.set("averageSpeed", buf.readUnsignedByte()); + buf.readUnsignedShort(); // interval fuel consumption + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort() * 0.01); + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedShort()); + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte() * 0.4); + buf.readUnsignedInt(); // trip id + break; + case 0x0005: + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + position.set(Position.KEY_HDOP, buf.readUnsignedByte()); + buf.readUnsignedInt(); // run time + break; + case 0x0009: + position.set( + Position.KEY_VIN, buf.readCharSequence(length, StandardCharsets.US_ASCII).toString()); + break; + case 0x0020: + Network network = new Network(); + 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))); + } + position.setNetwork(network); + break; + default: + buf.skipBytes(length); + break; + } + } + + sendResponse(channel, MSG_POSITION_RSP, index, null); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/HuabaoFrameDecoder.java b/src/main/java/org/traccar/protocol/HuabaoFrameDecoder.java new file mode 100644 index 000000000..b520f6be9 --- /dev/null +++ b/src/main/java/org/traccar/protocol/HuabaoFrameDecoder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class HuabaoFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 2) { + return null; + } + + int index = buf.indexOf(buf.readerIndex() + 1, buf.writerIndex(), (byte) 0x7e); + if (index != -1) { + ByteBuf result = Unpooled.buffer(index + 1 - buf.readerIndex()); + + while (buf.readerIndex() <= index) { + int b = buf.readUnsignedByte(); + if (b == 0x7d) { + int ext = buf.readUnsignedByte(); + if (ext == 0x01) { + result.writeByte(0x7d); + } else if (ext == 0x02) { + result.writeByte(0x7e); + } + } else { + result.writeByte(b); + } + } + + return result; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocol.java b/src/main/java/org/traccar/protocol/HuabaoProtocol.java new file mode 100644 index 000000000..791672b85 --- /dev/null +++ b/src/main/java/org/traccar/protocol/HuabaoProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class HuabaoProtocol extends BaseProtocol { + + public HuabaoProtocol() { + setSupportedDataCommands( + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HuabaoFrameDecoder()); + pipeline.addLast(new HuabaoProtocolEncoder(HuabaoProtocol.this)); + pipeline.addLast(new HuabaoProtocolDecoder(HuabaoProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java new file mode 100644 index 000000000..bf5b6d89a --- /dev/null +++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java @@ -0,0 +1,299 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; + +public class HuabaoProtocolDecoder extends BaseProtocolDecoder { + + public HuabaoProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_GENERAL_RESPONSE = 0x8001; + public static final int MSG_TERMINAL_REGISTER = 0x0100; + public static final int MSG_TERMINAL_REGISTER_RESPONSE = 0x8100; + public static final int MSG_TERMINAL_CONTROL = 0x8105; + public static final int MSG_TERMINAL_AUTH = 0x0102; + public static final int MSG_LOCATION_REPORT = 0x0200; + public static final int MSG_LOCATION_BATCH = 0x0704; + public static final int MSG_OIL_CONTROL = 0XA006; + + public static final int RESULT_SUCCESS = 0; + + public static ByteBuf formatMessage(int type, ByteBuf id, ByteBuf data) { + ByteBuf buf = Unpooled.buffer(); + buf.writeByte(0x7e); + buf.writeShort(type); + buf.writeShort(data.readableBytes()); + buf.writeBytes(id); + buf.writeShort(0); // index + buf.writeBytes(data); + data.release(); + buf.writeByte(Checksum.xor(buf.nioBuffer(1, buf.readableBytes() - 1))); + buf.writeByte(0x7e); + return buf; + } + + private void sendGeneralResponse( + Channel channel, SocketAddress remoteAddress, ByteBuf id, int type, int index) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeShort(index); + response.writeShort(type); + response.writeByte(RESULT_SUCCESS); + channel.writeAndFlush(new NetworkMessage( + formatMessage(MSG_GENERAL_RESPONSE, id, response), remoteAddress)); + } + } + + private String decodeAlarm(long value) { + if (BitUtil.check(value, 0)) { + return Position.ALARM_SOS; + } + if (BitUtil.check(value, 1)) { + return Position.ALARM_OVERSPEED; + } + if (BitUtil.check(value, 5)) { + return Position.ALARM_GPS_ANTENNA_CUT; + } + if (BitUtil.check(value, 4) || BitUtil.check(value, 9) + || BitUtil.check(value, 10) || BitUtil.check(value, 11)) { + return Position.ALARM_FAULT; + } + if (BitUtil.check(value, 8)) { + return Position.ALARM_POWER_OFF; + } + if (BitUtil.check(value, 20)) { + return Position.ALARM_GEOFENCE; + } + if (BitUtil.check(value, 28)) { + return Position.ALARM_MOVEMENT; + } + if (BitUtil.check(value, 29)) { + return Position.ALARM_ACCIDENT; + } + return null; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // start marker + int type = buf.readUnsignedShort(); + buf.readUnsignedShort(); // body length + ByteBuf id = buf.readSlice(6); // phone number + int index = buf.readUnsignedShort(); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, ByteBufUtil.hexDump(id)); + if (deviceSession == null) { + return null; + } + + if (deviceSession.getTimeZone() == null) { + deviceSession.setTimeZone(getTimeZone(deviceSession.getDeviceId(), "GMT+8")); + } + + if (type == MSG_TERMINAL_REGISTER) { + + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeShort(index); + response.writeByte(RESULT_SUCCESS); + response.writeBytes(ByteBufUtil.hexDump(id).getBytes(StandardCharsets.US_ASCII)); + channel.writeAndFlush(new NetworkMessage( + formatMessage(MSG_TERMINAL_REGISTER_RESPONSE, id, response), remoteAddress)); + } + + } else if (type == MSG_TERMINAL_AUTH) { + + sendGeneralResponse(channel, remoteAddress, id, type, index); + + } else if (type == MSG_LOCATION_REPORT) { + + return decodeLocation(deviceSession, buf); + + } else if (type == MSG_LOCATION_BATCH) { + + return decodeLocationBatch(deviceSession, buf); + + } + + return null; + } + + private Position decodeLocation(DeviceSession deviceSession, ByteBuf buf) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedInt())); + + int status = buf.readInt(); + + position.set(Position.KEY_IGNITION, BitUtil.check(status, 0)); + position.set(Position.KEY_BLOCKED, BitUtil.check(status, 10)); + + position.setValid(BitUtil.check(status, 1)); + + double lat = buf.readUnsignedInt() * 0.000001; + double lon = buf.readUnsignedInt() * 0.000001; + + if (BitUtil.check(status, 2)) { + position.setLatitude(-lat); + } else { + position.setLatitude(lat); + } + + if (BitUtil.check(status, 3)) { + position.setLongitude(-lon); + } else { + position.setLongitude(lon); + } + + position.setAltitude(buf.readShort()); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() * 0.1)); + position.setCourse(buf.readUnsignedShort()); + + DateBuilder dateBuilder = new DateBuilder(deviceSession.getTimeZone()) + .setYear(BcdUtil.readInteger(buf, 2)) + .setMonth(BcdUtil.readInteger(buf, 2)) + .setDay(BcdUtil.readInteger(buf, 2)) + .setHour(BcdUtil.readInteger(buf, 2)) + .setMinute(BcdUtil.readInteger(buf, 2)) + .setSecond(BcdUtil.readInteger(buf, 2)); + position.setTime(dateBuilder.getDate()); + + while (buf.readableBytes() > 2) { + int subtype = buf.readUnsignedByte(); + int length = buf.readUnsignedByte(); + int endIndex = buf.readerIndex() + length; + switch (subtype) { + case 0x01: + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 100); + break; + case 0x02: + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShort() * 0.1); + break; + case 0x30: + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + break; + case 0x31: + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + break; + case 0x33: + String sentence = buf.readCharSequence(length, StandardCharsets.US_ASCII).toString(); + if (sentence.startsWith("*M00")) { + String lockStatus = sentence.substring(8, 8 + 7); + position.set(Position.KEY_BATTERY, Integer.parseInt(lockStatus.substring(2, 5)) * 0.01); + } + break; + case 0x91: + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.1); + position.set(Position.KEY_RPM, buf.readUnsignedShort()); + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedByte()); + position.set(Position.KEY_THROTTLE, buf.readUnsignedByte() * 100 / 255); + position.set(Position.KEY_ENGINE_LOAD, buf.readUnsignedByte() * 100 / 255); + position.set(Position.KEY_COOLANT_TEMP, buf.readUnsignedByte() - 40); + buf.readUnsignedShort(); + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedShort() * 0.01); + buf.readUnsignedShort(); + buf.readUnsignedInt(); + buf.readUnsignedShort(); + position.set(Position.KEY_FUEL_USED, buf.readUnsignedShort() * 0.01); + break; + case 0x94: + if (length > 0) { + position.set( + Position.KEY_VIN, buf.readCharSequence(length, StandardCharsets.US_ASCII).toString()); + } + break; + case 0xD0: + long userStatus = buf.readUnsignedInt(); + if (BitUtil.check(userStatus, 3)) { + position.set(Position.KEY_ALARM, Position.ALARM_VIBRATION); + } + break; + case 0xD3: + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1); + break; + case 0xEB: + while (buf.readerIndex() < endIndex) { + int tenetLength = buf.readUnsignedShort(); + int tenetType = buf.readUnsignedShort(); + switch (tenetType) { + case 0x0001: + position.set("fuel1", buf.readUnsignedShort() * 0.1); + buf.readUnsignedByte(); // unused + break; + case 0x0023: + buf.skipBytes(4); // unused + position.set("fuel2", Double.parseDouble( + buf.readCharSequence(2, StandardCharsets.US_ASCII).toString())); + break; + default: + buf.skipBytes(tenetLength - 2); + break; + } + } + break; + default: + break; + } + buf.readerIndex(endIndex); + } + + return position; + } + + private List<Position> decodeLocationBatch(DeviceSession deviceSession, ByteBuf buf) { + + List<Position> positions = new LinkedList<>(); + + int count = buf.readUnsignedShort(); + buf.readUnsignedByte(); // location type + + for (int i = 0; i < count; i++) { + int endIndex = buf.readUnsignedShort() + buf.readerIndex(); + positions.add(decodeLocation(deviceSession, buf)); + buf.readerIndex(endIndex); + } + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolEncoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolEncoder.java new file mode 100644 index 000000000..40d07230d --- /dev/null +++ b/src/main/java/org/traccar/protocol/HuabaoProtocolEncoder.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.Context; +import org.traccar.helper.DataConverter; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.text.SimpleDateFormat; +import java.util.Date; + +public class HuabaoProtocolEncoder extends BaseProtocolEncoder { + + public HuabaoProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + boolean alternative = Context.getIdentityManager().lookupAttributeBoolean( + command.getDeviceId(), getProtocolName() + ".alternative", false, false, true); + + ByteBuf id = Unpooled.wrappedBuffer( + DataConverter.parseHex(getUniqueId(command.getDeviceId()))); + try { + ByteBuf data = Unpooled.buffer(); + byte[] time = DataConverter.parseHex(new SimpleDateFormat("yyMMddHHmmss").format(new Date())); + + switch (command.getType()) { + case Command.TYPE_ENGINE_STOP: + if (alternative) { + data.writeByte(0x01); + data.writeBytes(time); + return HuabaoProtocolDecoder.formatMessage( + HuabaoProtocolDecoder.MSG_OIL_CONTROL, id, data); + } else { + data.writeByte(0xf0); + return HuabaoProtocolDecoder.formatMessage( + HuabaoProtocolDecoder.MSG_TERMINAL_CONTROL, id, data); + } + case Command.TYPE_ENGINE_RESUME: + if (alternative) { + data.writeByte(0x00); + data.writeBytes(time); + return HuabaoProtocolDecoder.formatMessage( + HuabaoProtocolDecoder.MSG_OIL_CONTROL, id, data); + } else { + data.writeByte(0xf1); + return HuabaoProtocolDecoder.formatMessage( + HuabaoProtocolDecoder.MSG_TERMINAL_CONTROL, id, data); + } + default: + return null; + } + } finally { + id.release(); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/HunterProProtocol.java b/src/main/java/org/traccar/protocol/HunterProProtocol.java new file mode 100644 index 000000000..9f6424a57 --- /dev/null +++ b/src/main/java/org/traccar/protocol/HunterProProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class HunterProProtocol extends BaseProtocol { + + public HunterProProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "\r")); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new HunterProProtocolDecoder(HunterProProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/HunterProProtocolDecoder.java b/src/main/java/org/traccar/protocol/HunterProProtocolDecoder.java new file mode 100644 index 000000000..06bc12d59 --- /dev/null +++ b/src/main/java/org/traccar/protocol/HunterProProtocolDecoder.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.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 HunterProProtocolDecoder extends BaseProtocolDecoder { + + public HunterProProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number(">(d+)<") // identifier + .text("$GPRMC,") + .number("(dd)(dd)(dd).?d*,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + 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()); + + DateBuilder dateBuilder = new DateBuilder(); + dateBuilder.setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/IdplProtocol.java b/src/main/java/org/traccar/protocol/IdplProtocol.java new file mode 100644 index 000000000..418178756 --- /dev/null +++ b/src/main/java/org/traccar/protocol/IdplProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class IdplProtocol extends BaseProtocol { + + public IdplProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new IdplProtocolDecoder(IdplProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/IdplProtocolDecoder.java b/src/main/java/org/traccar/protocol/IdplProtocolDecoder.java new file mode 100644 index 000000000..cf3c03d7f --- /dev/null +++ b/src/main/java/org/traccar/protocol/IdplProtocolDecoder.java @@ -0,0 +1,112 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 java.net.SocketAddress; +import java.util.regex.Pattern; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.Parser.CoordinateFormat; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +public class IdplProtocolDecoder extends BaseProtocolDecoder { + + public IdplProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("*ID") // start of frame + .number("(d+),") // command code + .number("(d+),") // imei + .number("(dd)(dd)(dd),") // current date (ddmmyy) + .number("(dd)(dd)(dd),") // current time (hhmmss) + .expression("([A|V]),") // gps fix + .number("(dd)(dd).?(d+),([NS]),") // latitude + .number("(ddd)(dd).?(d+),([EW]),") // longitude + .number("(d{1,3}.dd),") // speed + .number("(d{1,3}.dd),") // course + .number("(d{1,2}),") // sats + .number("(d{1,3}),") // gsm signal strength + .expression("([A|N|S]),") // vehicle status + .expression("([0|1]),") // main power status + .number("(d.dd),") // internal battery voltage + .expression("([0|1]),") // sos alert + .expression("([0|1]),") // body tamper + .expression("([0|1])([0|1]),") // ac status + ign status + .expression("([0|1|2]),") // output1 status + .number("(d{1,3}),") // adc1 + .number("(d{1,3}),") // adc2 + .expression("([0-9A-Z]{3}),") // software version + .expression("([L|R]),") // message type + .number("(x{4})#") // crc + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + position.set(Position.KEY_TYPE, parser.nextInt(0)); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate(CoordinateFormat.DEG_MIN_MIN_HEM)); + position.setLongitude(parser.nextCoordinate(CoordinateFormat.DEG_MIN_MIN_HEM)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + position.set(Position.KEY_RSSI, parser.nextInt(0)); + position.set("vehicleStatus", parser.next()); + position.set(Position.KEY_POWER, parser.nextInt(0)); + position.set(Position.KEY_BATTERY, parser.nextDouble(0)); + if (parser.nextInt(0) == 1) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + parser.nextInt(0); // body tamper + position.set("acStatus", parser.nextInt(0)); + position.set(Position.KEY_IGNITION, parser.nextInt(0) == 1); + position.set(Position.KEY_OUTPUT, parser.nextInt(0)); + position.set(Position.PREFIX_ADC + 1, parser.nextInt(0)); + position.set(Position.PREFIX_ADC + 2, parser.nextInt(0)); + position.set(Position.KEY_VERSION_FW, parser.next()); + position.set(Position.KEY_ARCHIVE, parser.next().equals("R")); + + parser.next(); // checksum + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/IntellitracFrameDecoder.java b/src/main/java/org/traccar/protocol/IntellitracFrameDecoder.java new file mode 100644 index 000000000..8322e65ba --- /dev/null +++ b/src/main/java/org/traccar/protocol/IntellitracFrameDecoder.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.LineBasedFrameDecoder; +import org.traccar.NetworkMessage; + +public class IntellitracFrameDecoder extends LineBasedFrameDecoder { + + private static final int MESSAGE_MINIMUM_LENGTH = 0; + + public IntellitracFrameDecoder(int maxFrameLength) { + super(maxFrameLength); + } + + // example of sync header: 0xFA 0xF8 0x1B 0x01 0x81 0x60 0x33 0x3C + + @Override + protected Object decode(ChannelHandlerContext ctx, ByteBuf buf) throws Exception { + + // Check minimum length + if (buf.readableBytes() < MESSAGE_MINIMUM_LENGTH) { + return null; + } + + // Check for sync packet + if (buf.getUnsignedShort(buf.readerIndex()) == 0xFAF8) { + ByteBuf syncMessage = buf.readRetainedSlice(8); + if (ctx != null && ctx.channel() != null) { + ctx.channel().writeAndFlush(new NetworkMessage(syncMessage, ctx.channel().remoteAddress())); + } + } + + return super.decode(ctx, buf); + } + +} diff --git a/src/main/java/org/traccar/protocol/IntellitracProtocol.java b/src/main/java/org/traccar/protocol/IntellitracProtocol.java new file mode 100644 index 000000000..3abf40da7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/IntellitracProtocol.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class IntellitracProtocol extends BaseProtocol { + + public IntellitracProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new IntellitracFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new IntellitracProtocolDecoder(IntellitracProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/IntellitracProtocolDecoder.java b/src/main/java/org/traccar/protocol/IntellitracProtocolDecoder.java new file mode 100644 index 000000000..897606270 --- /dev/null +++ b/src/main/java/org/traccar/protocol/IntellitracProtocolDecoder.java @@ -0,0 +1,117 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class IntellitracProtocolDecoder extends BaseProtocolDecoder { + + public IntellitracProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression(".+,").optional() + .number("(d+),") // identifier + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(-?d+.d+),") // longitude + .number("(-?d+.d+),") // latitude + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .number("(-?d+.?d*),") // altitude + .number("(d+),") // satellites + .number("(d+),") // index + .number("(d+),") // input + .number("(d+),?") // output + .number("(d+.d+)?,?") // adc1 + .number("(d+.d+)?,?") // adc2 + .groupBegin() + .number("d{14},d+,") + .number("(d+),") // vss + .number("(d+),") // rpm + .number("(-?d+),") // coolant + .number("(d+),") // fuel + .number("(d+),") // fuel consumption + .number("(-?d+),") // fuel temperature + .number("(d+),") // charger pressure + .number("(d+),") // tpl + .number("(d+),") // axle weight + .number("(d+)") // odometer + .groupEnd("?") + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime()); + + position.setValid(true); + position.setLongitude(parser.nextDouble()); + position.setLatitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + position.setAltitude(parser.nextDouble()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_INDEX, parser.nextLong()); + position.set(Position.KEY_INPUT, parser.nextInt()); + position.set(Position.KEY_OUTPUT, parser.nextInt()); + + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.PREFIX_ADC + 2, parser.nextDouble()); + + // J1939 data + position.set(Position.KEY_OBD_SPEED, parser.nextInt()); + position.set(Position.KEY_RPM, parser.nextInt()); + position.set("coolant", parser.nextInt()); + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); + position.set(Position.KEY_FUEL_CONSUMPTION, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, parser.nextInt()); + position.set("chargerPressure", parser.nextInt()); + position.set("tpl", parser.nextInt()); + position.set(Position.KEY_AXLE_WEIGHT, parser.nextInt()); + position.set(Position.KEY_OBD_ODOMETER, parser.nextInt()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/ItsFrameDecoder.java b/src/main/java/org/traccar/protocol/ItsFrameDecoder.java new file mode 100644 index 000000000..cebfdca5f --- /dev/null +++ b/src/main/java/org/traccar/protocol/ItsFrameDecoder.java @@ -0,0 +1,67 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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 ItsFrameDecoder extends BaseFrameDecoder { + + private static final int MINIMUM_LENGTH = 20; + + private ByteBuf readFrame(ByteBuf buf, int delimiterIndex, int skip) { + int headerIndex = buf.indexOf(buf.readerIndex() + 1, buf.writerIndex(), (byte) '$'); + if (headerIndex > 0 && headerIndex < delimiterIndex) { + return buf.readRetainedSlice(headerIndex - buf.readerIndex()); + } else { + ByteBuf frame = buf.readRetainedSlice(delimiterIndex - buf.readerIndex()); + buf.skipBytes(skip); + return frame; + } + } + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + while (buf.isReadable() && buf.getByte(buf.readerIndex()) != '$') { + buf.skipBytes(1); + } + + int delimiterIndex = BufferUtil.indexOf("\r\n", buf); + if (delimiterIndex > MINIMUM_LENGTH) { + return readFrame(buf, delimiterIndex, 2); + } else { + delimiterIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '*'); + if (delimiterIndex > MINIMUM_LENGTH) { + if (buf.writerIndex() > delimiterIndex + 1 && buf.getByte(delimiterIndex + 1) == '*') { + delimiterIndex += 1; + } + if (buf.getByte(delimiterIndex - 2) == ',') { + return readFrame(buf, delimiterIndex - 1, 2); // skip binary checksum + } else { + return readFrame(buf, delimiterIndex, 1); + } + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/ItsProtocol.java b/src/main/java/org/traccar/protocol/ItsProtocol.java new file mode 100644 index 000000000..45df3da11 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ItsProtocol.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class ItsProtocol extends BaseProtocol { + + public ItsProtocol() { + setSupportedDataCommands( + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new ItsFrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new ItsProtocolDecoder(ItsProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java new file mode 100644 index 000000000..e8d77f1a8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java @@ -0,0 +1,239 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class ItsProtocolDecoder extends BaseProtocolDecoder { + + public ItsProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression("[^$]*") + .text("$") + .expression(",?[^,]+,") // event + .groupBegin() + .expression("[^,]*,") // vendor + .expression("[^,]+,") // firmware version + .expression("(..),") // status + .number("(d+),").optional() // event + .expression("([LH]),") // history + .or() + .expression("([^,]+),") // type + .groupEnd() + .number("(d{15}),") // imei + .groupBegin() + .expression("(..),") // status + .or() + .expression("[^,]*,") // vehicle registration + .number("([01]),").optional() // valid + .groupEnd() + .number("(dd),?(dd),?(d{2,4}),") // date (ddmmyyyy) + .number("(dd),?(dd),?(dd),") // time (hhmmss) + .expression("([01AV]),").optional() // valid + .number("(d+.d+),([NS]),") // latitude + .number("(d+.d+),([EW]),") // longitude + .groupBegin() + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .number("(d+),") // satellites + .groupBegin() + .number("(d+.?d*),") // altitude + .number("d+.?d*,") // pdop + .number("d+.?d*,") // hdop + .expression("[^,]*,") // operator + .number("([01]),") // ignition + .number("([01]),") // charging + .number("(d+.?d*),") // power + .number("(d+.?d*),") // battery + .number("([01]),") // emergency + .expression("[CO]?,") // tamper + .expression("(.*),") // cells + .number("([012]{4}),") // inputs + .number("([01]{2}),") // outputs + .groupBegin() + .number("d+,") // index + .number("(d+.?d*),") // adc1 + .number("(d+.?d*),") // adc2 + .groupEnd("?") + .groupEnd("?") + .or() + .number("(-?d+.d+),") // altitude + .number("(d+.d+),") // speed + .groupEnd() + .any() + .compile(); + + private String decodeAlarm(String status) { + switch (status) { + case "WD": + case "EA": + return Position.ALARM_SOS; + case "BL": + return Position.ALARM_LOW_BATTERY; + case "HB": + return Position.ALARM_BRAKING; + case "HA": + return Position.ALARM_ACCELERATION; + case "RT": + return Position.ALARM_CORNERING; + case "OS": + return Position.ALARM_OVERSPEED; + case "TA": + return Position.ALARM_TAMPERING; + case "BD": + return Position.ALARM_POWER_CUT; + case "BR": + return Position.ALARM_POWER_RESTORED; + default: + return null; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (channel != null && sentence.startsWith("$,01,")) { + channel.writeAndFlush(new NetworkMessage("$,1,*", remoteAddress)); + } + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + String status = parser.next(); + Integer event = parser.nextInt(); + boolean history = "H".equals(parser.next()); + String type = parser.next(); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (type != null && type.equals("EMR")) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + + if (event != null) { + position.set(Position.KEY_EVENT, event); + } + if (history) { + position.set(Position.KEY_ARCHIVE, true); + } + + if (parser.hasNext()) { + status = parser.next(); + } + if (status != null) { + if (status.equals("IN")) { + position.set(Position.KEY_IGNITION, true); + } else if (status.equals("IF")) { + position.set(Position.KEY_IGNITION, false); + } else { + position.set(Position.KEY_ALARM, decodeAlarm(status)); + } + } + + if (parser.hasNext()) { + position.setValid(parser.nextInt() == 1); + } + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + if (parser.hasNext()) { + position.setValid(parser.next().matches("[1A]")); + } + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + + if (parser.hasNext(3)) { + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + } + + if (parser.hasNext(8)) { + position.setAltitude(parser.nextDouble()); + position.set(Position.KEY_IGNITION, parser.nextInt() > 0); + position.set(Position.KEY_CHARGE, parser.nextInt() > 0); + position.set(Position.KEY_POWER, parser.nextDouble()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + + position.set("emergency", parser.nextInt() > 0); + + String cellsString = parser.next(); + if (!cellsString.contains("x")) { + String[] cells = cellsString.split(","); + int mcc = Integer.parseInt(cells[1]); + int mnc = Integer.parseInt(cells[2]); + int lac = Integer.parseInt(cells[3], 16); + int cid = Integer.parseInt(cells[4], 16); + Network network = new Network(CellTower.from(mcc, mnc, lac, cid, Integer.parseInt(cells[0]))); + if (!cells[5].startsWith("(")) { + for (int i = 0; i < 4; i++) { + lac = Integer.parseInt(cells[5 + 3 * i + 1], 16); + cid = Integer.parseInt(cells[5 + 3 * i + 2], 16); + if (lac > 0 && cid > 0) { + network.addCellTower(CellTower.from(mcc, mnc, lac, cid)); + } + } + } + position.setNetwork(network); + } + + String input = parser.next(); + if (input.charAt(input.length() - 1) == '2') { + input = input.substring(0, input.length() - 1) + '0'; + } + position.set(Position.KEY_INPUT, Integer.parseInt(input, 2)); + position.set(Position.KEY_OUTPUT, parser.nextBinInt()); + } + + if (parser.hasNext(2)) { + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.PREFIX_ADC + 2, parser.nextDouble()); + } + + if (parser.hasNext(2)) { + position.setAltitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/ItsProtocolEncoder.java b/src/main/java/org/traccar/protocol/ItsProtocolEncoder.java new file mode 100644 index 000000000..f82b8e3ac --- /dev/null +++ b/src/main/java/org/traccar/protocol/ItsProtocolEncoder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class ItsProtocolEncoder extends StringProtocolEncoder { + + public ItsProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_ENGINE_STOP: + return "@SET#RLP,OP1,"; + case Command.TYPE_ENGINE_RESUME: + return "@CLR#RLP,OP1,"; + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Ivt401Protocol.java b/src/main/java/org/traccar/protocol/Ivt401Protocol.java new file mode 100644 index 000000000..fb44e4fe9 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Ivt401Protocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Ivt401Protocol extends BaseProtocol { + + public Ivt401Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ';')); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Ivt401ProtocolDecoder(Ivt401Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Ivt401ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Ivt401ProtocolDecoder.java new file mode 100644 index 000000000..63556e7a9 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Ivt401ProtocolDecoder.java @@ -0,0 +1,183 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class Ivt401ProtocolDecoder extends BaseProtocolDecoder { + + public Ivt401ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("(") + .expression("TL[ABLN],") // header + .number("(d+),") // imei + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("([-+]d+.d+),") // latitude + .number("([-+]d+.d+),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(-?d+.?d*),") // altitude + .number("d+,") // satellites or battery status + .number("(d),") // gps status + .number("(d+),") // rssi + .number("(d+),") // input + .number("(d+),") // output + .number("(d+.d+),") // adc + .number("(d+.d+),") // power + .number("(d+.d+),") // battery + .number("(-?d+.?d*),") // pcb temp + .expression("([^,]+),") // temp + .number("(d+),") // movement + .number("(d+.d+),") // acceleration + .number("(-?d+),") // tilt + .number("(d+),") // trip + .number("(d+),") // odometer + .groupBegin() + .number("([01]),") // overspeed + .number("[01],") // input 2 misuse + .number("[01],") // immobilizer + .number("[01],") // temperature alert + .number("[0-2]+,") // geofence + .number("([0-3]),") // harsh driving + .number("[01],") // reconnect + .number("([01]),") // low battery + .number("([01]),") // power disconnected + .number("[01],") // gps failure + .number("([01]),") // towing + .number("[01],") // server unreachable + .number("[128],") // sleep mode + .expression("([^,]+)?,") // driver id + .number("d+,") // sms count + .groupEnd("?") + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.setCourse(parser.nextInt()); + position.setAltitude(parser.nextDouble()); + position.setValid(parser.nextInt() > 0); + + position.set(Position.KEY_RSSI, parser.nextInt()); + + String input = parser.next(); + for (int i = 0; i < input.length(); i++) { + int value = Character.getNumericValue(input.charAt(i)); + if (value < 2) { + position.set(Position.PREFIX_IN + (i + 1), value > 0); + } + } + + String output = parser.next(); + for (int i = 0; i < output.length(); i++) { + position.set(Position.PREFIX_OUT + (i + 1), Character.getNumericValue(output.charAt(i)) > 0); + } + + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.KEY_POWER, parser.nextDouble()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_DEVICE_TEMP, parser.nextDouble()); + + String temp = parser.next(); + if (temp.startsWith("M")) { + int index = 1; + int startIndex = 1; + int endIndex; + while (startIndex < temp.length()) { + endIndex = temp.indexOf('-', startIndex + 1); + if (endIndex < 0) { + endIndex = temp.indexOf('+', startIndex + 1); + } + if (endIndex < 0) { + endIndex = temp.length(); + } + if (endIndex > 0) { + double value = Double.parseDouble(temp.substring(startIndex, endIndex)); + position.set(Position.PREFIX_TEMP + index++, value); + } + startIndex = endIndex; + } + } else { + position.set(Position.PREFIX_TEMP + 1, Double.parseDouble(temp)); + } + + position.set(Position.KEY_MOTION, parser.nextInt() > 0); + position.set(Position.KEY_ACCELERATION, parser.nextDouble()); + + parser.nextInt(); // tilt + parser.nextInt(); // trip state + + position.set(Position.KEY_ODOMETER, parser.nextLong()); + + if (parser.hasNext(6)) { + position.set(Position.KEY_ALARM, parser.nextInt() == 1 ? Position.ALARM_OVERSPEED : null); + switch (parser.nextInt()) { + case 1: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 2: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 3: + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + break; + default: + break; + } + position.set(Position.KEY_ALARM, parser.nextInt() == 1 ? Position.ALARM_LOW_BATTERY : null); + position.set(Position.KEY_ALARM, parser.nextInt() == 1 ? Position.ALARM_POWER_CUT : null); + position.set(Position.KEY_ALARM, parser.nextInt() == 1 ? Position.ALARM_TOW : null); + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/JpKorjarFrameDecoder.java b/src/main/java/org/traccar/protocol/JpKorjarFrameDecoder.java new file mode 100644 index 000000000..0eb65c8ef --- /dev/null +++ b/src/main/java/org/traccar/protocol/JpKorjarFrameDecoder.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Nyash (nyashh@gmail.com) + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class JpKorjarFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 80) { + return null; + } + + int spaceIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ' '); + if (spaceIndex == -1) { + return null; + } + + int endIndex = buf.indexOf(spaceIndex, buf.writerIndex(), (byte) ','); + if (endIndex == -1) { + return null; + } + + return buf.readRetainedSlice(endIndex + 1); + } + +} diff --git a/src/main/java/org/traccar/protocol/JpKorjarProtocol.java b/src/main/java/org/traccar/protocol/JpKorjarProtocol.java new file mode 100644 index 000000000..fe5b2480d --- /dev/null +++ b/src/main/java/org/traccar/protocol/JpKorjarProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 Nyash (nyashh@gmail.com) + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class JpKorjarProtocol extends BaseProtocol { + + public JpKorjarProtocol() { + addServer(new TrackerServer(false, this.getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new JpKorjarFrameDecoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new JpKorjarProtocolDecoder(JpKorjarProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/JpKorjarProtocolDecoder.java b/src/main/java/org/traccar/protocol/JpKorjarProtocolDecoder.java new file mode 100644 index 000000000..33026918a --- /dev/null +++ b/src/main/java/org/traccar/protocol/JpKorjarProtocolDecoder.java @@ -0,0 +1,89 @@ +/* + * Copyright 2016 Nyash (nyashh@gmail.com) + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class JpKorjarProtocolDecoder extends BaseProtocolDecoder { + + public JpKorjarProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("KORJAR.PL,") + .number("(d+),") // imei + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d+.d+)([NS]),") // latitude + .number("(d+.d+)([EW]),") // longitude + .number("(d+.d+),") // speed + .number("(d+),") // course + .number("[FL]:(d+.d+)V,") // battery + .number("([01]) ") // valid + .number("(d+) ") // mcc + .number("(d+) ") // mnc + .number("(x+) ") // lac + .number("(x+),") // cid + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime()); + + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_BATTERY, parser.nextDouble(0)); + + position.setValid(parser.nextInt(0) == 1); + + position.setNetwork(new Network(CellTower.from( + parser.nextInt(0), parser.nextInt(0), parser.nextHexInt(0), parser.nextHexInt(0)))); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/Jt600FrameDecoder.java b/src/main/java/org/traccar/protocol/Jt600FrameDecoder.java new file mode 100644 index 000000000..bfefb94a7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Jt600FrameDecoder.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +import java.text.ParseException; + +public class Jt600FrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 10) { + return null; + } + + char type = (char) buf.getByte(buf.readerIndex()); + + if (type == '$') { + boolean longFormat = Jt600ProtocolDecoder.isLongFormat(buf, buf.readerIndex() + 1); + int length = buf.getUnsignedShort(buf.readerIndex() + (longFormat ? 8 : 7)) + 10; + if (length <= buf.readableBytes()) { + return buf.readRetainedSlice(length); + } + } else if (type == '(') { + int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ')'); + if (endIndex != -1) { + return buf.readRetainedSlice(endIndex + 1); + } + } else { + throw new ParseException(null, 0); // unknown message + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Jt600Protocol.java b/src/main/java/org/traccar/protocol/Jt600Protocol.java new file mode 100644 index 000000000..37c82f741 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Jt600Protocol.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class Jt600Protocol extends BaseProtocol { + + public Jt600Protocol() { + setSupportedDataCommands( + Command.TYPE_ENGINE_RESUME, + Command.TYPE_ENGINE_STOP, + Command.TYPE_SET_TIMEZONE, + Command.TYPE_REBOOT_DEVICE); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new Jt600FrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new Jt600ProtocolEncoder(Jt600Protocol.this)); + pipeline.addLast(new Jt600ProtocolDecoder(Jt600Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java new file mode 100644 index 000000000..f456cd1ef --- /dev/null +++ b/src/main/java/org/traccar/protocol/Jt600ProtocolDecoder.java @@ -0,0 +1,380 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitBuffer; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +public class Jt600ProtocolDecoder extends BaseProtocolDecoder { + + public Jt600ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static double convertCoordinate(int raw) { + int degrees = raw / 1000000; + double minutes = (raw % 1000000) / 10000.0; + return degrees + minutes / 60; + } + + private void decodeStatus(Position position, ByteBuf buf) { + + int value = buf.readUnsignedByte(); + + position.set(Position.KEY_IGNITION, BitUtil.check(value, 0)); + position.set(Position.KEY_DOOR, BitUtil.check(value, 6)); + + value = buf.readUnsignedByte(); + + position.set(Position.KEY_CHARGE, BitUtil.check(value, 0)); + position.set(Position.KEY_BLOCKED, BitUtil.check(value, 1)); + + if (BitUtil.check(value, 2)) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + if (BitUtil.check(value, 3) || BitUtil.check(value, 4)) { + position.set(Position.KEY_ALARM, Position.ALARM_GPS_ANTENNA_CUT); + } + if (BitUtil.check(value, 4)) { + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + } + + value = buf.readUnsignedByte(); + + if (BitUtil.check(value, 2)) { + position.set(Position.KEY_ALARM, Position.ALARM_FATIGUE_DRIVING); + } + if (BitUtil.check(value, 3)) { + position.set(Position.KEY_ALARM, Position.ALARM_TOW); + } + + buf.readUnsignedByte(); // reserved + + } + + static boolean isLongFormat(ByteBuf buf, int flagIndex) { + return buf.getUnsignedByte(flagIndex) >> 4 == 0x7; + } + + private List<Position> decodeBinary(ByteBuf buf, Channel channel, SocketAddress remoteAddress) { + + List<Position> positions = new LinkedList<>(); + + buf.readByte(); // header + + boolean longFormat = isLongFormat(buf, buf.readerIndex()); + + String id = String.valueOf(Long.parseLong(ByteBufUtil.hexDump(buf.readSlice(5)))); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + int protocolVersion = 0; + if (longFormat) { + protocolVersion = buf.readUnsignedByte(); + } + + int version = BitUtil.from(buf.readUnsignedByte(), 4); + buf.readUnsignedShort(); // length + + while (buf.readableBytes() > 1) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setDay(BcdUtil.readInteger(buf, 2)) + .setMonth(BcdUtil.readInteger(buf, 2)) + .setYear(BcdUtil.readInteger(buf, 2)) + .setHour(BcdUtil.readInteger(buf, 2)) + .setMinute(BcdUtil.readInteger(buf, 2)) + .setSecond(BcdUtil.readInteger(buf, 2)); + position.setTime(dateBuilder.getDate()); + + double latitude = convertCoordinate(BcdUtil.readInteger(buf, 8)); + double longitude = convertCoordinate(BcdUtil.readInteger(buf, 9)); + + byte flags = buf.readByte(); + position.setValid((flags & 0x1) == 0x1); + if ((flags & 0x2) == 0) { + latitude = -latitude; + } + position.setLatitude(latitude); + if ((flags & 0x4) == 0) { + longitude = -longitude; + } + position.setLongitude(longitude); + + position.setSpeed(BcdUtil.readInteger(buf, 2)); + position.setCourse(buf.readUnsignedByte() * 2.0); + + if (longFormat) { + + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 1000); + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + + buf.readUnsignedInt(); // vehicle id combined + + int status = buf.readUnsignedShort(); + position.set(Position.KEY_ALARM, BitUtil.check(status, 1) ? Position.ALARM_GEOFENCE_ENTER : null); + position.set(Position.KEY_ALARM, BitUtil.check(status, 2) ? Position.ALARM_GEOFENCE_EXIT : null); + position.set(Position.KEY_ALARM, BitUtil.check(status, 3) ? Position.ALARM_POWER_CUT : null); + position.set(Position.KEY_ALARM, BitUtil.check(status, 4) ? Position.ALARM_VIBRATION : null); + position.set(Position.KEY_BLOCKED, BitUtil.check(status, 7)); + position.set(Position.KEY_ALARM, BitUtil.check(status, 8 + 3) ? Position.ALARM_LOW_BATTERY : null); + position.set(Position.KEY_ALARM, BitUtil.check(status, 8 + 6) ? Position.ALARM_FAULT : null); + position.set(Position.KEY_STATUS, status); + + int battery = buf.readUnsignedByte(); + if (battery == 0xff) { + position.set(Position.KEY_CHARGE, true); + } else { + position.set(Position.KEY_BATTERY_LEVEL, battery); + } + + CellTower cellTower = CellTower.fromCidLac(buf.readUnsignedShort(), buf.readUnsignedShort()); + cellTower.setSignalStrength((int) buf.readUnsignedByte()); + position.setNetwork(new Network(cellTower)); + + if (protocolVersion == 0x17) { + buf.readUnsignedByte(); // geofence id + buf.skipBytes(3); // reserved + buf.skipBytes(buf.readableBytes() - 1); + } + + } else if (version == 1) { + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_POWER, buf.readUnsignedByte()); + + buf.readByte(); // other flags and sensors + + position.setAltitude(buf.readUnsignedShort()); + + int cid = buf.readUnsignedShort(); + int lac = buf.readUnsignedShort(); + int rssi = buf.readUnsignedByte(); + + if (cid != 0 && lac != 0) { + CellTower cellTower = CellTower.fromCidLac(cid, lac); + cellTower.setSignalStrength(rssi); + position.setNetwork(new Network(cellTower)); + } else { + position.set(Position.KEY_RSSI, rssi); + } + + } else if (version == 2) { + + int fuel = buf.readUnsignedByte() << 8; + + decodeStatus(position, buf); + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 1000); + + fuel += buf.readUnsignedByte(); + position.set(Position.KEY_FUEL_LEVEL, fuel); + + } else if (version == 3) { + + BitBuffer bitBuffer = new BitBuffer(buf); + + position.set("fuel1", bitBuffer.readUnsigned(12)); + position.set("fuel2", bitBuffer.readUnsigned(12)); + position.set("fuel3", bitBuffer.readUnsigned(12)); + position.set(Position.KEY_ODOMETER, bitBuffer.readUnsigned(20) * 1000); + + int status = bitBuffer.readUnsigned(24); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 0)); + position.set(Position.KEY_STATUS, status); + + } + + positions.add(position); + + } + + buf.readUnsignedByte(); // index + + return positions; + } + + private static final Pattern PATTERN_W01 = new PatternBuilder() + .text("(") + .number("(d+),") // id + .text("W01,") // type + .number("(ddd)(dd.dddd),") // longitude + .expression("([EW]),") + .number("(dd)(dd.dddd),") // latitude + .expression("([NS]),") + .expression("([AV]),") // validity + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d+),") // speed + .number("(d+),") // course + .number("(d+),") // power + .number("(d+),") // gps signal + .number("(d+),") // gsm signal + .number("(d+),") // alert type + .any() + .compile(); + + private Position decodeW01(String sentence, Channel channel, SocketAddress remoteAddress) { + + Parser parser = new Parser(PATTERN_W01, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setLongitude(parser.nextCoordinate()); + position.setLatitude(parser.nextCoordinate()); + position.setValid(parser.next().equals("A")); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_POWER, parser.nextDouble(0)); + position.set(Position.KEY_GPS, parser.nextInt(0)); + position.set(Position.KEY_RSSI, parser.nextInt(0)); + position.set("alertType", parser.nextInt(0)); + + return position; + } + + private static final Pattern PATTERN_U01 = new PatternBuilder() + .text("(") + .number("(d+),") // id + .number("(Udd),") // type + .number("d+,").optional() // alarm + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([TF]),") // validity + .number("(d+.d+),([NS]),") // latitude + .number("(d+.d+),([EW]),") // longitude + .number("(d+.?d*),") // speed + .number("(d+),") // course + .number("(d+),") // satellites + .number("(d+)%,") // battery + .expression("([01]+),") // status + .number("(d+),") // cid + .number("(d+),") // lac + .number("(d+),") // gsm signal + .number("(d+),") // odometer + .number("(d+)") // serial number + .number(",(xx)").optional() // checksum + .any() + .compile(); + + private Position decodeU01(String sentence, Channel channel, SocketAddress remoteAddress) { + + Parser parser = new Parser(PATTERN_U01, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + String type = parser.next(); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setValid(parser.next().equals("T")); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + + position.setSpeed(UnitsConverter.knotsFromMph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt(0)); + position.set(Position.KEY_STATUS, parser.nextBinInt(0)); + + CellTower cellTower = CellTower.fromCidLac(parser.nextInt(0), parser.nextInt(0)); + cellTower.setSignalStrength(parser.nextInt(0)); + position.setNetwork(new Network(cellTower)); + + position.set(Position.KEY_ODOMETER, parser.nextLong(0) * 1000); + position.set(Position.KEY_INDEX, parser.nextInt(0)); + + if (channel != null) { + if (type.equals("U01") || type.equals("U02") || type.equals("U03")) { + channel.writeAndFlush(new NetworkMessage("(S39)", remoteAddress)); + } else if (type.equals("U06")) { + channel.writeAndFlush(new NetworkMessage("(S20)", remoteAddress)); + } + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + char first = (char) buf.getByte(0); + + if (first == '$') { + return decodeBinary(buf, channel, remoteAddress); + } else if (first == '(') { + String sentence = buf.toString(StandardCharsets.US_ASCII); + if (sentence.contains("W01")) { + return decodeW01(sentence, channel, remoteAddress); + } else { + return decodeU01(sentence, channel, remoteAddress); + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Jt600ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Jt600ProtocolEncoder.java new file mode 100644 index 000000000..199467a38 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Jt600ProtocolEncoder.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import java.util.TimeZone; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class Jt600ProtocolEncoder extends StringProtocolEncoder { + + public Jt600ProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_ENGINE_STOP: + return "(S07,0)"; + case Command.TYPE_ENGINE_RESUME: + return "(S07,1)"; + case Command.TYPE_SET_TIMEZONE: + int offset = TimeZone.getTimeZone(command.getString(Command.KEY_TIMEZONE)).getRawOffset() / 60000; + return "(S09,1," + offset + ")"; + case Command.TYPE_REBOOT_DEVICE: + return "(S17)"; + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/KenjiProtocol.java b/src/main/java/org/traccar/protocol/KenjiProtocol.java new file mode 100644 index 000000000..90c0c511c --- /dev/null +++ b/src/main/java/org/traccar/protocol/KenjiProtocol.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Carlos Alvarez (carlos.alvarez.rozas@gmail.com) + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class KenjiProtocol extends BaseProtocol { + + public KenjiProtocol() { + 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 KenjiProtocolDecoder(KenjiProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/KenjiProtocolDecoder.java b/src/main/java/org/traccar/protocol/KenjiProtocolDecoder.java new file mode 100644 index 000000000..63812242a --- /dev/null +++ b/src/main/java/org/traccar/protocol/KenjiProtocolDecoder.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.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 KenjiProtocolDecoder extends BaseProtocolDecoder { + + public KenjiProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text(">") + .number("C(d{6}),") // device id + .number("M(x{6}),") // alarm + .number("O(x{4}),") // output + .number("I(x{4}),") // input + .number("D(dd)(dd)(dd),") // time (hhmmss) + .expression("([AV]),") // valid + .number("([NS])(dd)(dd.d+),") // latitude + .number("([EW])(ddd)(dd.d+),") // longitude + .number("T(d+.d+),") // speed + .number("H(d+.d+),") // course + .number("Y(dd)(dd)(dd),") // date (ddmmyy) + .number("G(d+)") // satellites + .any() + .compile(); + + private String decodeAlarm(int value) { + if (BitUtil.check(value, 2)) { + return Position.ALARM_SOS; + } + if (BitUtil.check(value, 4)) { + return Position.ALARM_LOW_BATTERY; + } + if (BitUtil.check(value, 6)) { + return Position.ALARM_MOVEMENT; + } + if (BitUtil.check(value, 1) || BitUtil.check(value, 10) || BitUtil.check(value, 11)) { + return Position.ALARM_VIBRATION; + } + + return null; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + 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_ALARM, decodeAlarm(parser.nextHexInt(0))); + position.set(Position.KEY_OUTPUT, parser.nextHexInt(0)); + position.set(Position.KEY_INPUT, parser.nextHexInt(0)); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/KhdProtocol.java b/src/main/java/org/traccar/protocol/KhdProtocol.java new file mode 100644 index 000000000..f77f4c311 --- /dev/null +++ b/src/main/java/org/traccar/protocol/KhdProtocol.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class KhdProtocol extends BaseProtocol { + + public KhdProtocol() { + setSupportedDataCommands( + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(512, 3, 2)); + pipeline.addLast(new KhdProtocolEncoder(KhdProtocol.this)); + pipeline.addLast(new KhdProtocolDecoder(KhdProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java new file mode 100644 index 000000000..0dd5b085a --- /dev/null +++ b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java @@ -0,0 +1,157 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.BcdUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class KhdProtocolDecoder extends BaseProtocolDecoder { + + public KhdProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private String readSerialNumber(ByteBuf buf) { + int b1 = buf.readUnsignedByte(); + int b2 = buf.readUnsignedByte() - 0x80; + int b3 = buf.readUnsignedByte() - 0x80; + int b4 = buf.readUnsignedByte(); + return String.format("%02d%02d%02d%02d", b1, b2, b3, b4); + } + + public static final int MSG_LOGIN = 0xB1; + public static final int MSG_CONFIRMATION = 0x21; + public static final int MSG_ON_DEMAND = 0x81; + public static final int MSG_POSITION_UPLOAD = 0x80; + public static final int MSG_POSITION_REUPLOAD = 0x8E; + public static final int MSG_ALARM = 0x82; + public static final int MSG_ADMIN_NUMBER = 0x83; + public static final int MSG_SEND_TEXT = 0x84; + public static final int MSG_REPLY = 0x85; + public static final int MSG_SMS_ALARM_SWITCH = 0x86; + public static final int MSG_PERIPHERAL = 0xA3; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + int type = buf.readUnsignedByte(); + buf.readUnsignedShort(); // size + + if (type == MSG_LOGIN || type == MSG_ADMIN_NUMBER || type == MSG_SEND_TEXT + || type == MSG_SMS_ALARM_SWITCH || type == MSG_POSITION_REUPLOAD) { + + ByteBuf response = Unpooled.buffer(); + response.writeByte(0x29); + response.writeByte(0x29); // header + response.writeByte(MSG_CONFIRMATION); + response.writeShort(5); // size + response.writeByte(buf.getByte(buf.writerIndex() - 2)); + response.writeByte(type); + response.writeByte(buf.writerIndex() > 9 ? buf.getByte(9) : 0); // 10th byte + response.writeByte(Checksum.xor(response.nioBuffer())); + response.writeByte(0x0D); // ending + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + } + + if (type == MSG_ON_DEMAND || type == MSG_POSITION_UPLOAD || type == MSG_POSITION_REUPLOAD + || type == MSG_ALARM || type == MSG_REPLY || type == MSG_PERIPHERAL) { + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, readSerialNumber(buf)); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setYear(BcdUtil.readInteger(buf, 2)) + .setMonth(BcdUtil.readInteger(buf, 2)) + .setDay(BcdUtil.readInteger(buf, 2)) + .setHour(BcdUtil.readInteger(buf, 2)) + .setMinute(BcdUtil.readInteger(buf, 2)) + .setSecond(BcdUtil.readInteger(buf, 2)); + position.setTime(dateBuilder.getDate()); + + position.setLatitude(BcdUtil.readCoordinate(buf)); + position.setLongitude(BcdUtil.readCoordinate(buf)); + position.setSpeed(UnitsConverter.knotsFromKph(BcdUtil.readInteger(buf, 4))); + position.setCourse(BcdUtil.readInteger(buf, 4)); + position.setValid((buf.readUnsignedByte() & 0x80) != 0); + + if (type != MSG_ALARM) { + + position.set(Position.KEY_ODOMETER, buf.readUnsignedMedium()); + position.set(Position.KEY_STATUS, buf.readUnsignedInt()); + position.set(Position.KEY_HDOP, buf.readUnsignedByte()); + position.set(Position.KEY_VDOP, buf.readUnsignedByte()); + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + + buf.skipBytes(5); // other location data + + if (type == MSG_PERIPHERAL) { + + buf.readUnsignedShort(); // data length + + int dataType = buf.readUnsignedByte(); + + buf.readUnsignedByte(); // content length + + switch (dataType) { + case 0x01: + position.set(Position.KEY_FUEL_LEVEL, + buf.readUnsignedByte() * 100 + buf.readUnsignedByte()); + break; + case 0x02: + position.set(Position.PREFIX_TEMP + 1, + buf.readUnsignedByte() * 100 + buf.readUnsignedByte()); + break; + default: + break; + } + + } + + } + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java b/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java new file mode 100644 index 000000000..4a8df26c8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/KhdProtocolEncoder.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.helper.Checksum; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class KhdProtocolEncoder extends BaseProtocolEncoder { + + public static final int MSG_CUT_OIL = 0x39; + public static final int MSG_RESUME_OIL = 0x38; + + public KhdProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf encodeCommand(int command, String uniqueId) { + + ByteBuf buf = Unpooled.buffer(); + + buf.writeByte(0x29); + buf.writeByte(0x29); + + buf.writeByte(command); + buf.writeShort(6); // size + + uniqueId = "00000000".concat(uniqueId); + uniqueId = uniqueId.substring(uniqueId.length() - 8); + buf.writeByte(Integer.parseInt(uniqueId.substring(0, 2))); + buf.writeByte(Integer.parseInt(uniqueId.substring(2, 4)) + 0x80); + buf.writeByte(Integer.parseInt(uniqueId.substring(4, 6)) + 0x80); + buf.writeByte(Integer.parseInt(uniqueId.substring(6, 8))); + + buf.writeByte(Checksum.xor(buf.nioBuffer())); + buf.writeByte(0x0D); // ending + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + + String uniqueId = getUniqueId(command.getDeviceId()); + + switch (command.getType()) { + case Command.TYPE_ENGINE_STOP: + return encodeCommand(MSG_CUT_OIL, uniqueId); + case Command.TYPE_ENGINE_RESUME: + return encodeCommand(MSG_RESUME_OIL, uniqueId); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/L100FrameDecoder.java b/src/main/java/org/traccar/protocol/L100FrameDecoder.java new file mode 100644 index 000000000..158461895 --- /dev/null +++ b/src/main/java/org/traccar/protocol/L100FrameDecoder.java @@ -0,0 +1,90 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +import java.nio.charset.StandardCharsets; + +public class L100FrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 10) { + return null; + } + + if (buf.getCharSequence(buf.readerIndex(), 4, StandardCharsets.US_ASCII).toString().equals("ATL,")) { + return decodeNew(buf); + } else { + return decodeOld(buf); + } + } + + private Object decodeOld(ByteBuf buf) { + + int header = buf.getByte(buf.readerIndex()); + boolean obd = header == 'L' || header == 'H'; + + int index; + if (obd) { + index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '*'); + } else { + index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x02); + if (index < 0) { + index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x04); + if (index < 0) { + return null; + } + } + } + + index += 2; // checksum + + if (buf.writerIndex() >= index) { + if (!obd) { + buf.skipBytes(2); // header + } + ByteBuf frame = buf.readRetainedSlice(index - buf.readerIndex() - 2); + buf.skipBytes(2); // footer + return frame; + } + + return null; + } + + private Object decodeNew(ByteBuf buf) { + + int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '@'); + if (index < 0) { + return null; + } + + if (buf.writerIndex() >= index + 1) { + ByteBuf frame = buf.readRetainedSlice(index - buf.readerIndex()); + buf.skipBytes(1); // delimiter + return frame; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/L100Protocol.java b/src/main/java/org/traccar/protocol/L100Protocol.java new file mode 100644 index 000000000..942029307 --- /dev/null +++ b/src/main/java/org/traccar/protocol/L100Protocol.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class L100Protocol extends BaseProtocol { + + public L100Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new L100FrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new L100ProtocolDecoder(L100Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/L100ProtocolDecoder.java b/src/main/java/org/traccar/protocol/L100ProtocolDecoder.java new file mode 100644 index 000000000..9868de435 --- /dev/null +++ b/src/main/java/org/traccar/protocol/L100ProtocolDecoder.java @@ -0,0 +1,341 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.ObdDecoder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class L100ProtocolDecoder extends BaseProtocolDecoder { + + public L100ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("ATL") + .expression(",[^,]+,").optional() + .number("(d{15}),") // imei + .text("$GPRMC,") + .number("(dd)(dd)(dd)") // time (hhmmss.sss) + .number(".(ddd)").optional() + .expression(",([AV]),") // validity + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .number("(dd)(dd)(dd),") // date (ddmmyy) + .any() + .text("#") + .number("([01]+),") // io status + .number("(d+.?d*|N.C),") // adc + .expression("[^,]*,") // reserved + .expression("[^,]*,") // reserved + .number("(d+.?d*),") // odometer + .number("(d+.?d*),") // temperature + .number("(d+.?d*),") // battery + .number("(d+),") // rssi + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(x+),") // lac + .number("(x+)") // cid + .any() + .text("ATL") + .compile(); + + private static final Pattern PATTERN_OBD_LOCATION = new PatternBuilder() + .expression("[LH],") // archive + .text("ATL,") + .number("(d{15}),") // imei + .number("(d+),") // type + .number("(d+),") // index + .groupBegin() + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(dd)(dd)(dd),") // date (ddmmyy) + .expression("([AV]),") // validity + .number("(d+.d+);([NS]),") // latitude + .number("(d+.d+);([EW]),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(d+.d+),") // odometer + .number("(d+.d+),") // battery + .number("(d+),") // rssi + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(d+),") // lac + .number("(x+),") // cid + .number("#(d)(d)(d)(d),") // status + .number("(d),") // overspeed + .text("ATL,") + .groupEnd("?") + .compile(); + + private static final Pattern PATTERN_OBD_DATA = new PatternBuilder() + .expression("[LH],") // archive + .text("ATLOBD,") + .number("(d{15}),") // imei + .number("d+,") // type + .number("d+,") // index + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(dd)(dd)(dd),") // date (ddmmyy) + .expression("[^,]+,") // obd protocol + .expression("(.+)") // data + .compile(); + + private static final Pattern PATTERN_NEW = new PatternBuilder() + .groupBegin() + .text("ATL,") + .expression("[LH],") // archive + .number("(d{15}),") // imei + .groupEnd("?") + .expression("([NPT]),") // alarm + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([AV]),") // validity + .number("(d+.d+),([NS]),") // latitude + .number("(d+.d+),([EW]),") // longitude + .number("(d+.?d*),") // speed + .expression("(?:GPS|GSM|INV),") + .number("(d+),") // battery + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(d+),") // lac + .number("(d+)") // cid + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.startsWith("L") || sentence.startsWith("H")) { + if (sentence.substring(2, 8).equals("ATLOBD")) { + return decodeObdData(channel, remoteAddress, sentence); + } else { + return decodeObdLocation(channel, remoteAddress, sentence); + } + } else if (!sentence.contains("$GPRMC")) { + return decodeNew(channel, remoteAddress, sentence); + } else { + return decodeNormal(channel, remoteAddress, sentence); + } + } + + private Object decodeNormal(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + position.set(Position.KEY_STATUS, parser.next()); + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.KEY_ODOMETER, parser.nextDouble()); + position.set(Position.PREFIX_TEMP + 1, parser.nextDouble()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + + int rssi = parser.nextInt(); + if (rssi > 0) { + position.setNetwork(new Network(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextHexInt(), parser.nextHexInt(), rssi))); + } + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(String.valueOf((char) 0x01), remoteAddress)); + } + + return position; + } + + private Object decodeObdLocation(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_OBD_LOCATION, sentence); + if (!parser.matches()) { + return null; + } + + String imei = parser.next(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + int type = parser.nextInt(); + int index = parser.nextInt(); + + if (type == 1) { + if (channel != null) { + String response = "@" + imei + ",00," + index + ","; + response += "*" + (char) Checksum.xor(response); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY)); + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setSpeed(parser.nextInt()); + position.setCourse(parser.nextInt()); + + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + + int rssi = parser.nextInt(); + position.setNetwork(new Network(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextHexInt(), rssi))); + + position.set(Position.KEY_IGNITION, parser.nextInt() == 1); + parser.next(); // reserved + + switch (parser.nextInt()) { + case 0: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 2: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 1: + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + break; + default: + break; + } + + position.set(Position.KEY_CHARGE, parser.nextInt() == 1); + + if (parser.nextInt() == 1) { + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + } + + return position; + } + + private Object decodeObdData(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_OBD_DATA, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY)); + + for (String entry : parser.next().split(",")) { + String[] values = entry.split(":"); + if (values.length == 2 && values[1].charAt(0) != 'X') { + position.add(ObdDecoder.decodeData( + Integer.parseInt(values[0].substring(2), 16), Integer.parseInt(values[1], 16), true)); + } + } + + return position; + } + + private Object decodeNew(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_NEW, sentence); + if (!parser.matches()) { + return null; + } + + String imei = parser.next(); + DeviceSession deviceSession; + if (imei != null) { + deviceSession = getDeviceSession(channel, remoteAddress, imei); + } else { + deviceSession = getDeviceSession(channel, remoteAddress); + } + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + switch (parser.next()) { + case "P": + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + break; + case "T": + position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING); + break; + default: + break; + } + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setSpeed(parser.nextDouble()); + + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001); + + position.setNetwork(new Network(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextHexInt()))); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/LaipacProtocol.java b/src/main/java/org/traccar/protocol/LaipacProtocol.java new file mode 100644 index 000000000..1d561dbd2 --- /dev/null +++ b/src/main/java/org/traccar/protocol/LaipacProtocol.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.model.Command; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class LaipacProtocol extends BaseProtocol { + + public LaipacProtocol() { + + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_POSITION_SINGLE, + Command.TYPE_REBOOT_DEVICE); + + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new LaipacProtocolEncoder(LaipacProtocol.this)); + pipeline.addLast(new LaipacProtocolDecoder(LaipacProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java new file mode 100644 index 000000000..0c72568f3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java @@ -0,0 +1,283 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class LaipacProtocolDecoder extends BaseProtocolDecoder { + + public LaipacProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final String DEFAULT_DEVICE_PASSWORD = "00000000"; + + private static final Pattern PATTERN_EAVSYS = new PatternBuilder() + .text("$EAVSYS,") + .expression("([^,]+),") // identifier + .expression("([0-9]+),") // iccid + .expression("(\\+?[0-9]+)?,") // sim phone number + .expression("(?:[^,]*),") // owner name + .expression("([^,]*)?") // firmware version + .text("*") + .number("(xx)") // checksum + .compile(); + + private static final Pattern PATTERN_AVRMC = new PatternBuilder() + .text("$AVRMC,") + .expression("([^,]+),") // identifier + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([AVRPavrp]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") // latitude hemisphere + .number("(ddd)(dd.d+),") // longitude + .number("([EW]),") // longitude hemisphere + .number("(d+.d+),") // speed + .number("(d+.d+),") // course + .number("(dd)(dd)(dd),") // date (ddmmyy) + .expression("([0-9A-Za-z]),") // event code + .expression("([\\d.]+),") // battery voltage + .number("(d+),") // current mileage + .number("(d),") // gps status + .number("(d+),") // adc1 + .number("(d+)") // adc2 + .number(",(xxxx|x)") // lac | lac+cid = 0 + .number("(xxxx),") // cid | nothing + .number("(ddd|d)") // mcc | mcc+mnc = 0 + .number("(ddd)") // mnc | nothing + .optional(4) + .expression(",([^*]*)") // anything remaining (be forward compatible) + .optional(1) + .text("*") + .number("(xx)") // checksum + .compile(); + + private String decodeAlarm(String event) { + switch (event) { + case "Z": + return Position.ALARM_LOW_BATTERY; + case "Y": + return Position.ALARM_TOW; + case "X": + return Position.ALARM_GEOFENCE_ENTER; + case "T": + return Position.ALARM_TAMPERING; + case "H": + return Position.ALARM_POWER_OFF; + case "8": + return Position.ALARM_SHOCK; + case "7": + case "4": + return Position.ALARM_GEOFENCE_EXIT; + case "6": + return Position.ALARM_OVERSPEED; + case "5": + return Position.ALARM_POWER_CUT; + case "3": + return Position.ALARM_SOS; + default: + return null; + } + } + + private String decodeEvent(String event, Position position) { + + if (event.length() == 1) { + char inputStatus = event.charAt(0); + if (inputStatus >= 'A' && inputStatus <= 'D') { + int inputStatusInt = inputStatus - 'A'; + position.set(Position.PREFIX_IN + 1, inputStatusInt & 1); + position.set(Position.PREFIX_IN + 2, inputStatusInt & 2); + return null; + } + } + + return event; + } + + private void sendEventResponse( + String event, String devicePassword, Channel channel, SocketAddress remoteAddress) { + + String responseCode = null; + + switch (event) { + case "3": + responseCode = "d"; + break; + case "S": + case "T": + responseCode = "t"; + break; + case "X": + case "4": + responseCode = "x"; + break; + case "Y": + responseCode = "y"; + break; + case "Z": + responseCode = "z"; + break; + default: + break; + } + + if (responseCode != null) { + String response = "$AVCFG," + devicePassword + "," + responseCode; + response += Checksum.nmea(response) + "\r\n"; + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + } + + private void sendAcknowledge( + String status, String event, String checksum, Channel channel, SocketAddress remoteAddress) { + + if (Character.isLowerCase(status.charAt(0))) { + String response = "$EAVACK," + event + "," + checksum; + response += Checksum.nmea(response) + "\r\n"; + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + } + + protected Object decodeEavsys( + String sentence, Channel channel, SocketAddress remoteAddress) { + + Parser parser = new Parser(PATTERN_EAVSYS, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = + getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_ICCID, parser.next()); + position.set(Position.KEY_PHONE, parser.next()); + position.set(Position.KEY_VERSION_FW, parser.next()); + + return position; + } + + protected Object decodeAvrmc( + String sentence, Channel channel, SocketAddress remoteAddress) { + + Parser parser = new Parser(PATTERN_AVRMC, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = + getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + + position.setDeviceId(deviceSession.getDeviceId()); + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + String status = parser.next(); + String upperCaseStatus = status.toUpperCase(); + position.setValid(upperCaseStatus.equals("A") || upperCaseStatus.equals("R") || upperCaseStatus.equals("P")); + position.set(Position.KEY_STATUS, status); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + String event = parser.next(); + position.set(Position.KEY_ALARM, decodeAlarm(event)); + position.set(Position.KEY_EVENT, decodeEvent(event, position)); + position.set(Position.KEY_BATTERY, Double.parseDouble(parser.next().replaceAll("\\.", "")) * 0.001); + position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); + position.set(Position.KEY_GPS, parser.nextInt()); + position.set(Position.PREFIX_ADC + 1, parser.nextDouble() * 0.001); + position.set(Position.PREFIX_ADC + 2, parser.nextDouble() * 0.001); + + Integer lac = parser.nextHexInt(); + Integer cid = parser.nextHexInt(); + Integer mcc = parser.nextInt(); + Integer mnc = parser.nextInt(); + if (lac != null && cid != null && mcc != null && mnc != null) { + position.setNetwork(new Network(CellTower.from(mcc, mnc, lac, cid))); + } + + parser.next(); // unused + + String checksum = parser.next(); + + if (channel != null) { + + sendAcknowledge(status, event, checksum, channel, remoteAddress); + + String devicePassword = Context.getIdentityManager() + .getDevicePassword(deviceSession.getDeviceId(), getProtocolName(), DEFAULT_DEVICE_PASSWORD); + sendEventResponse(event, devicePassword, channel, remoteAddress); + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.startsWith("$ECHK")) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(sentence + "\r\n", remoteAddress)); + } + } else if (sentence.startsWith("$EAVSYS")) { + return decodeEavsys(sentence, channel, remoteAddress); + } else if (sentence.startsWith("$AVRMC")) { + return decodeAvrmc(sentence, channel, remoteAddress); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java new file mode 100644 index 000000000..0c9f8ebb8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.helper.Checksum; +import org.traccar.Protocol; + +public class LaipacProtocolEncoder extends StringProtocolEncoder { + + public LaipacProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected String formatCommand(Command command, String format, String... keys) { + String sentence = super.formatCommand(command, "$" + format, keys); + sentence += Checksum.nmea(sentence) + "\r\n"; + return sentence; + } + + @Override + protected Object encodeCommand(Command command) { + + initDevicePassword(command, LaipacProtocolDecoder.DEFAULT_DEVICE_PASSWORD); + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return formatCommand(command, "%s", + Command.KEY_DATA); + case Command.TYPE_POSITION_SINGLE: + return formatCommand(command, "AVREQ,%s,1", + Command.KEY_DEVICE_PASSWORD); + case Command.TYPE_REBOOT_DEVICE: + return formatCommand(command, "AVRESET,%s,%s", + Command.KEY_UNIQUE_ID, Command.KEY_DEVICE_PASSWORD); + default: + return null; + } + + } + +} diff --git a/src/main/java/org/traccar/protocol/LeafSpyProtocol.java b/src/main/java/org/traccar/protocol/LeafSpyProtocol.java new file mode 100644 index 000000000..05f63a2d7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/LeafSpyProtocol.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 Jesse Hills (jesserockz@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.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class LeafSpyProtocol extends BaseProtocol { + + public LeafSpyProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(16384)); + pipeline.addLast(new LeafSpyProtocolDecoder(LeafSpyProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/LeafSpyProtocolDecoder.java b/src/main/java/org/traccar/protocol/LeafSpyProtocolDecoder.java new file mode 100644 index 000000000..5b352a961 --- /dev/null +++ b/src/main/java/org/traccar/protocol/LeafSpyProtocolDecoder.java @@ -0,0 +1,139 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 Jesse Hills (jesserockz@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.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; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class LeafSpyProtocolDecoder extends BaseHttpProtocolDecoder { + + public LeafSpyProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + QueryStringDecoder decoder = new QueryStringDecoder(request.uri()); + Map<String, List<String>> params = decoder.parameters(); + if (params.isEmpty()) { + decoder = new QueryStringDecoder(request.content().toString(StandardCharsets.US_ASCII), false); + params = decoder.parameters(); + } + + Position position = new Position(getProtocolName()); + position.setValid(true); + + for (Map.Entry<String, List<String>> entry : params.entrySet()) { + for (String value : entry.getValue()) { + switch (entry.getKey()) { + case "pass": + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, value); + if (deviceSession == null) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + break; + case "Lat": + position.setLatitude(Double.parseDouble(value)); + break; + case "Long": + position.setLongitude(Double.parseDouble(value)); + break; + case "RPM": + position.set(Position.KEY_RPM, Integer.parseInt(value)); + position.setSpeed(convertSpeed(Double.parseDouble(value) / 63, "kmh")); + break; + case "Elv": + position.setAltitude(Double.parseDouble(value)); + break; + case "SOC": + position.set(Position.KEY_BATTERY_LEVEL, Double.parseDouble(value)); + break; + case "user": + position.set(Position.KEY_DRIVER_UNIQUE_ID, value); + break; + case "ChrgMode": + position.set(Position.KEY_CHARGE, Integer.parseInt(value) != 0); + break; + case "Odo": + position.set(Position.KEY_OBD_ODOMETER, Integer.parseInt(value) * 1000); + break; + default: + try { + position.set(entry.getKey(), Double.parseDouble(value)); + } catch (NumberFormatException e) { + switch (value) { + case "true": + position.set(entry.getKey(), true); + break; + case "false": + position.set(entry.getKey(), false); + break; + default: + position.set(entry.getKey(), value); + break; + } + } + break; + } + } + } + + if (position.getFixTime() == null) { + position.setTime(new Date()); + } + + if (position.getLatitude() == 0 && position.getLongitude() == 0) { + getLastLocation(position, position.getDeviceTime()); + } + + if (position.getDeviceId() != 0) { + if (channel != null) { + HttpResponse response = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, + HttpResponseStatus.OK, + Unpooled.copiedBuffer("\"status\":\"0\"", StandardCharsets.US_ASCII)); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + return position; + } else { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/M2cProtocol.java b/src/main/java/org/traccar/protocol/M2cProtocol.java new file mode 100644 index 000000000..9de8526c3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/M2cProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class M2cProtocol extends BaseProtocol { + + public M2cProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(32 * 1024, ']')); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new M2cProtocolDecoder(M2cProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/M2cProtocolDecoder.java b/src/main/java/org/traccar/protocol/M2cProtocolDecoder.java new file mode 100644 index 000000000..1460bb176 --- /dev/null +++ b/src/main/java/org/traccar/protocol/M2cProtocolDecoder.java @@ -0,0 +1,131 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +public class M2cProtocolDecoder extends BaseProtocolDecoder { + + public M2cProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("#M2C,") + .expression("[^,]+,") // model + .expression("[^,]+,") // firmware + .number("d+,") // protocol + .number("(d+),") // imei + .number("(d+),") // index + .expression("([LH]),") // archive + .number("d+,") // priority + .number("(d+),") // event + .number("(dd)(dd)(dd),") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(-?d+),") // altitude + .number("(d+),") // course + .number("(d+.d+),") // speed + .number("(d+),") // satellites + .number("(d+),") // odometer + .number("(d+),") // input + .number("(d+),") // output + .number("(d+),") // power + .number("(d+),") // battery + .number("(d+),") // adc 1 + .number("(d+),") // adc 2 + .number("(d+.?d*),") // temperature + .any() + .compile(); + + private Position decodePosition(Channel channel, SocketAddress remoteAddress, String line) { + + Parser parser = new Parser(PATTERN, line); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_INDEX, parser.nextInt()); + + if (parser.next().equals("H")) { + position.set(Position.KEY_ARCHIVE, true); + } + + position.set(Position.KEY_EVENT, parser.nextInt()); + + position.setValid(true); + position.setTime(parser.nextDateTime()); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setAltitude(parser.nextInt()); + position.setCourse(parser.nextInt()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_ODOMETER, parser.nextLong()); + position.set(Position.KEY_INPUT, parser.nextInt()); + position.set(Position.KEY_OUTPUT, parser.nextInt()); + position.set(Position.KEY_POWER, parser.nextInt() * 0.001); + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001); + position.set(Position.PREFIX_ADC + 1, parser.nextInt()); + position.set(Position.PREFIX_ADC + 2, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, parser.nextDouble()); + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + sentence = sentence.substring(1); // remove start symbol + + List<Position> positions = new LinkedList<>(); + for (String line : sentence.split("\r\n")) { + if (!line.isEmpty()) { + Position position = decodePosition(channel, remoteAddress, line); + if (position != null) { + positions.add(position); + } + } + } + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/M2mProtocol.java b/src/main/java/org/traccar/protocol/M2mProtocol.java new file mode 100644 index 000000000..dda328a59 --- /dev/null +++ b/src/main/java/org/traccar/protocol/M2mProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.FixedLengthFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class M2mProtocol extends BaseProtocol { + + public M2mProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new FixedLengthFrameDecoder(23)); + pipeline.addLast(new M2mProtocolDecoder(M2mProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/M2mProtocolDecoder.java b/src/main/java/org/traccar/protocol/M2mProtocolDecoder.java new file mode 100644 index 000000000..21e4a2fd0 --- /dev/null +++ b/src/main/java/org/traccar/protocol/M2mProtocolDecoder.java @@ -0,0 +1,127 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.model.Position; + +import java.net.SocketAddress; + +public class M2mProtocolDecoder extends BaseProtocolDecoder { + + public M2mProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private boolean firstPacket = true; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + // Remove offset + for (int i = 0; i < buf.readableBytes(); i++) { + int b = buf.getByte(i); + if (b != 0x0b) { + buf.setByte(i, b - 0x20); + } + } + + if (firstPacket) { + + firstPacket = false; + + StringBuilder imei = new StringBuilder(); + for (int i = 0; i < 8; i++) { + int b = buf.readByte(); + if (i != 0) { + imei.append(b / 10); + } + imei.append(b % 10); + } + + getDeviceSession(channel, remoteAddress, imei.toString()); + + } else { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setDay(buf.readUnsignedByte() & 0x3f) + .setMonth(buf.readUnsignedByte() & 0x3f) + .setYear(buf.readUnsignedByte()) + .setHour(buf.readUnsignedByte() & 0x3f) + .setMinute(buf.readUnsignedByte() & 0x7f) + .setSecond(buf.readUnsignedByte() & 0x7f); + position.setTime(dateBuilder.getDate()); + + int degrees = buf.readUnsignedByte(); + double latitude = buf.readUnsignedByte(); + latitude += buf.readUnsignedByte() / 100.0; + latitude += buf.readUnsignedByte() / 10000.0; + latitude /= 60; + latitude += degrees; + + int b = buf.readUnsignedByte(); + + degrees = (b & 0x7f) * 100 + buf.readUnsignedByte(); + double longitude = buf.readUnsignedByte(); + longitude += buf.readUnsignedByte() / 100.0; + longitude += buf.readUnsignedByte() / 10000.0; + longitude /= 60; + longitude += degrees; + + if ((b & 0x80) != 0) { + longitude = -longitude; + } + if ((b & 0x40) != 0) { + latitude = -latitude; + } + + position.setValid(true); + position.setLatitude(latitude); + position.setLongitude(longitude); + position.setSpeed(buf.readUnsignedByte()); + + int satellites = buf.readUnsignedByte(); + if (satellites == 0) { + return null; // cell information + } + position.set(Position.KEY_SATELLITES, satellites); + + // decode other data + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/MaestroProtocol.java b/src/main/java/org/traccar/protocol/MaestroProtocol.java new file mode 100644 index 000000000..87453ce7d --- /dev/null +++ b/src/main/java/org/traccar/protocol/MaestroProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.FixedLengthFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class MaestroProtocol extends BaseProtocol { + + public MaestroProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new FixedLengthFrameDecoder(160)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new MaestroProtocolDecoder(MaestroProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/MaestroProtocolDecoder.java b/src/main/java/org/traccar/protocol/MaestroProtocolDecoder.java new file mode 100644 index 000000000..37b097414 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MaestroProtocolDecoder.java @@ -0,0 +1,103 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class MaestroProtocolDecoder extends BaseProtocolDecoder { + + public MaestroProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("@") + .number("(d+),") // imei + .number("d+,") // index + .expression("[^,]+,") // profile + .expression("([01]),") // validity + .number("(d+.d+),") // battery + .number("(d+),") // gsm + .expression("([01]),") // starter + .expression("([01]),") // ignition + .number("(dd)/(dd)/(dd),") // date (yy/mm/dd) + .number("(dd):(dd):(dd),") // time (hh:mm:ss) + .number("(-?d+.d+),") // longitude + .number("(-?d+.d+),") // latitude + .number("(d+.?d*),") // altitude + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .number("(d+),") // satellites + .number("(d+.?d*),") // hdop + .number("(d+.?d*)") // odometer + .number(",(d+)").optional() // adc + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(parser.nextInt(0) == 1); + + position.set(Position.KEY_BATTERY, parser.nextDouble(0)); + position.set(Position.KEY_RSSI, parser.nextInt(0)); + position.set(Position.KEY_CHARGE, parser.nextInt(0) == 1); + position.set(Position.KEY_IGNITION, parser.nextInt(0) == 1); + + position.setTime(parser.nextDateTime()); + + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + position.setSpeed(UnitsConverter.knotsFromMph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + position.set(Position.KEY_HDOP, parser.nextDouble(0)); + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1609.34); + + if (parser.hasNext()) { + position.set(Position.PREFIX_ADC + 1, parser.nextInt(0)); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/ManPowerProtocol.java b/src/main/java/org/traccar/protocol/ManPowerProtocol.java new file mode 100644 index 000000000..49d8b1e9f --- /dev/null +++ b/src/main/java/org/traccar/protocol/ManPowerProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class ManPowerProtocol extends BaseProtocol { + + public ManPowerProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ';')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new ManPowerProtocolDecoder(ManPowerProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ManPowerProtocolDecoder.java b/src/main/java/org/traccar/protocol/ManPowerProtocolDecoder.java new file mode 100644 index 000000000..2c7b7eb40 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ManPowerProtocolDecoder.java @@ -0,0 +1,81 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class ManPowerProtocolDecoder extends BaseProtocolDecoder { + + public ManPowerProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("simei:") + .number("(d+),") // imei + .expression("[^,]*,[^,]*,") + .expression("([^,]*),") // status + .number("d+,d+,d+.?d*,") + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([AV]),") // validity + .number("(dd)(dd.dddd),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.dddd),") // longitude + .expression("([EW])?,") + .number("(d+.?d*),") // speed + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_STATUS, parser.next()); + + position.setTime(parser.nextDateTime()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/MegastekFrameDecoder.java b/src/main/java/org/traccar/protocol/MegastekFrameDecoder.java new file mode 100644 index 000000000..347fa24b1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MegastekFrameDecoder.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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; + +import java.nio.charset.StandardCharsets; + +public class MegastekFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 10) { + return null; + } + + if (Character.isDigit(buf.getByte(buf.readerIndex()))) { + int length = 4 + Integer.parseInt(buf.toString(buf.readerIndex(), 4, StandardCharsets.US_ASCII)); + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + } else { + while (buf.getByte(buf.readerIndex()) == '\r' || buf.getByte(buf.readerIndex()) == '\n') { + buf.skipBytes(1); + } + int delimiter = BufferUtil.indexOf("\r\n", buf); + if (delimiter == -1) { + delimiter = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '!'); + } + if (delimiter != -1) { + ByteBuf result = buf.readRetainedSlice(delimiter - buf.readerIndex()); + buf.skipBytes(1); + return result; + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/MegastekProtocol.java b/src/main/java/org/traccar/protocol/MegastekProtocol.java new file mode 100644 index 000000000..e9f5f9fde --- /dev/null +++ b/src/main/java/org/traccar/protocol/MegastekProtocol.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class MegastekProtocol extends BaseProtocol { + + public MegastekProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new MegastekFrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new MegastekProtocolDecoder(MegastekProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/MegastekProtocolDecoder.java b/src/main/java/org/traccar/protocol/MegastekProtocolDecoder.java new file mode 100644 index 000000000..d81cc0eda --- /dev/null +++ b/src/main/java/org/traccar/protocol/MegastekProtocolDecoder.java @@ -0,0 +1,419 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class MegastekProtocolDecoder extends BaseProtocolDecoder { + + public MegastekProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_GPRMC = new PatternBuilder() + .text("$GPRMC,") + .number("(dd)(dd)(dd).(ddd),") // time (hhmmss.sss) + .expression("([AV]),") // validity + .number("(d+)(dd.d+),([NS]),") // latitude + .number("(d+)(dd.d+),([EW]),") // longitude + .number("(d+.d+)?,") // speed + .number("(d+.d+)?,") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .any() // checksum + .compile(); + + private static final Pattern PATTERN_SIMPLE = new PatternBuilder() + .expression("[FL],") // flag + .expression("([^,]*),") // alarm + .number("imei:(d+),") // imei + .number("(d+/?d*)?,") // satellites + .number("(d+.d+)?,") // altitude + .number("Battery=(d+)%,,?") // battery + .number("(d)?,") // charger + .number("(d+)?,") // mcc + .number("(d+)?,") // mnc + .number("(xxxx),") // lac + .number("(xxxx);") // cid + .any() // checksum + .compile(); + + private static final Pattern PATTERN_ALTERNATIVE = new PatternBuilder() + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(xxxx),") // lac + .number("(xxxx),") // cid + .number("(d+),") // gsm signal + .number("(d+),") // battery + .number("(d+),") // flags + .number("(d+),") // inputs + .number("(?:(d+),)?") // outputs + .number("(d.?d*),") // adc 1 + .groupBegin() + .number("(d.dd),") // adc 2 + .number("(d.dd),") // adc 3 + .groupEnd("?") + .expression("([^;]+);") // alarm + .any() // checksum + .compile(); + + private boolean parseLocation(String location, Position position) { + + Parser parser = new Parser(PATTERN_GPRMC, location); + if (!parser.matches()) { + return false; + } + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + return true; + } + + private Position decodeOld(Channel channel, SocketAddress remoteAddress, String sentence) { + + // Detect type + boolean simple = sentence.charAt(3) == ',' || sentence.charAt(6) == ','; + + // Split message + String id; + String location; + String status; + if (simple) { + + int beginIndex = sentence.indexOf(',') + 1; + int endIndex = sentence.indexOf(',', beginIndex); + id = sentence.substring(beginIndex, endIndex); + + beginIndex = endIndex + 1; + endIndex = sentence.indexOf('*', beginIndex); + if (endIndex != -1) { + endIndex += 3; + } else { + endIndex = sentence.length(); + } + location = sentence.substring(beginIndex, endIndex); + + beginIndex = endIndex + 1; + if (beginIndex > sentence.length()) { + beginIndex = endIndex; + } + status = sentence.substring(beginIndex); + + } else { + + int beginIndex = 3; + int endIndex = beginIndex + 16; + id = sentence.substring(beginIndex, endIndex).trim(); + + beginIndex = endIndex + 2; + endIndex = sentence.indexOf('*', beginIndex) + 3; + location = sentence.substring(beginIndex, endIndex); + + beginIndex = endIndex + 1; + status = sentence.substring(beginIndex); + + } + + Position position = new Position(getProtocolName()); + if (!parseLocation(location, position)) { + return null; + } + + if (simple) { + + Parser parser = new Parser(PATTERN_SIMPLE, status); + if (parser.matches()) { + + position.set(Position.KEY_ALARM, decodeAlarm(parser.next())); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next(), id); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + String sat = parser.next(); + if (sat.contains("/")) { + position.set(Position.KEY_SATELLITES, Integer.parseInt(sat.split("/")[0])); + position.set(Position.KEY_SATELLITES_VISIBLE, Integer.parseInt(sat.split("/")[1])); + } else { + position.set(Position.KEY_SATELLITES, Integer.parseInt(sat)); + } + + position.setAltitude(parser.nextDouble(0)); + + position.set(Position.KEY_BATTERY_LEVEL, parser.nextDouble(0)); + + String charger = parser.next(); + if (charger != null) { + position.set(Position.KEY_CHARGE, Integer.parseInt(charger) == 1); + } + + if (parser.hasNext(4)) { + position.setNetwork(new Network(CellTower.from( + parser.nextInt(0), parser.nextInt(0), parser.nextHexInt(0), parser.nextHexInt(0)))); + } + + } else { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + } + + } else { + + Parser parser = new Parser(PATTERN_ALTERNATIVE, status); + if (parser.matches()) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setNetwork(new Network(CellTower.from(parser.nextInt(0), parser.nextInt(0), + parser.nextHexInt(0), parser.nextHexInt(0), parser.nextInt(0)))); + + position.set(Position.KEY_BATTERY_LEVEL, parser.nextDouble()); + + position.set(Position.KEY_FLAGS, parser.next()); + position.set(Position.KEY_INPUT, parser.next()); + position.set(Position.KEY_OUTPUT, parser.next()); + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.PREFIX_ADC + 2, parser.next()); + position.set(Position.PREFIX_ADC + 3, parser.next()); + position.set(Position.KEY_ALARM, decodeAlarm(parser.next())); + + } + } + + return position; + } + + private static final Pattern PATTERN_NEW = new PatternBuilder() + .number("dddd").optional() + .text("$MGV") + .number("ddd,") + .number("(d+),") // imei + .expression("[^,]*,") // name + .expression("([RS]),") + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([AV]),") // validity + .number("(d+)(dd.d+),([NS]),") // latitude + .number("(d+)(dd.d+),([EW]),") // longitude + .number("dd,") + .number("(dd),") // satellites + .number("dd,") + .number("(d+.d+),") // hdop + .number("(d+.d+)?,") // speed + .number("(d+.d+)?,") // course + .number("(-?d+.d+),") // altitude + .number("(d+.d+)?,") // odometer + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(xxxx)?,") // lac + .number("(x+)?,") // cid + .number("(d+)?,") // gsm + .groupBegin() + .number("([01]{4})?,") // input + .number("([01]{4})?,") // output + .number("(d+)?,") // adc1 + .number("(d+)?,") // adc2 + .number("(d+)?,") // adc3 + .or() + .number("(d+),") // input + .number("(d+),") // output + .number("(d+),") // adc1 + .number("(d+),") // adc2 + .number("(d+),") // adc3 + .groupEnd() + .groupBegin() + .number("(-?d+.?d*)") // temperature 1 + .or().text(" ") + .groupEnd("?").text(",") + .groupBegin() + .number("(-?d+.?d*)") // temperature 2 + .or().text(" ") + .groupEnd("?").text(",") + .number("(d+)?,") // rfid + .expression("[^,]*,") + .number("(d+)?,") // battery + .expression("([^,]*)") // alert + .any() + .compile(); + + private Position decodeNew(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_NEW, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (parser.next().equals("S")) { + position.set(Position.KEY_ARCHIVE, true); + } + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + position.set(Position.KEY_HDOP, parser.nextDouble(0)); + + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + if (parser.hasNext()) { + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + } + + int mcc = parser.nextInt(); + int mnc = parser.nextInt(); + Integer lac = parser.nextHexInt(); + Integer cid = parser.nextHexInt(); + Integer rssi = parser.nextInt(); + if (lac != null && cid != null) { + CellTower tower = CellTower.from(mcc, mnc, lac, cid); + if (rssi != null) { + tower.setSignalStrength(rssi); + } + position.setNetwork(new Network(tower)); + } + + if (parser.hasNext(5)) { + position.set(Position.KEY_INPUT, parser.nextBinInt(0)); + position.set(Position.KEY_OUTPUT, parser.nextBinInt(0)); + for (int i = 1; i <= 3; i++) { + position.set(Position.PREFIX_ADC + i, parser.nextInt(0)); + } + } + + if (parser.hasNext(5)) { + position.set(Position.KEY_HEART_RATE, parser.nextInt()); + position.set(Position.KEY_STEPS, parser.nextInt()); + position.set("activityTime", parser.nextInt()); + position.set("lightSleepTime", parser.nextInt()); + position.set("deepSleepTime", parser.nextInt()); + } + + for (int i = 1; i <= 2; i++) { + String adc = parser.next(); + if (adc != null) { + position.set(Position.PREFIX_TEMP + i, Double.parseDouble(adc)); + } + } + + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + + String battery = parser.next(); + if (battery != null) { + position.set(Position.KEY_BATTERY, Integer.parseInt(battery)); + } + + position.set(Position.KEY_ALARM, decodeAlarm(parser.next())); + + return position; + } + + private String decodeAlarm(String value) { + value = value.toLowerCase(); + if (value.startsWith("geo")) { + if (value.endsWith("in")) { + return Position.ALARM_GEOFENCE_ENTER; + } else if (value.endsWith("out")) { + return Position.ALARM_GEOFENCE_EXIT; + } + } + switch (value) { + case "poweron": + return Position.ALARM_POWER_ON; + case "poweroff": + return Position.ALARM_POWER_ON; + case "sos": + case "help": + return Position.ALARM_SOS; + case "over speed": + case "overspeed": + return Position.ALARM_OVERSPEED; + case "lowspeed": + return Position.ALARM_LOW_SPEED; + case "low battery": + case "lowbattery": + return Position.ALARM_LOW_BATTERY; + case "vib": + return Position.ALARM_VIBRATION; + case "move in": + return Position.ALARM_GEOFENCE_ENTER; + case "move out": + return Position.ALARM_GEOFENCE_EXIT; + case "error": + return Position.ALARM_FAULT; + default: + return null; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.contains("$MG")) { + return decodeNew(channel, remoteAddress, sentence); + } else { + return decodeOld(channel, remoteAddress, sentence); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/MeiligaoFrameDecoder.java b/src/main/java/org/traccar/protocol/MeiligaoFrameDecoder.java new file mode 100644 index 000000000..52f9ae26d --- /dev/null +++ b/src/main/java/org/traccar/protocol/MeiligaoFrameDecoder.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class MeiligaoFrameDecoder extends BaseFrameDecoder { + + private static final int MESSAGE_HEADER = 4; + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + // Strip not '$' (0x24) bytes from the beginning + while (buf.isReadable() && buf.getUnsignedByte(buf.readerIndex()) != 0x24) { + buf.readByte(); + } + + // Check length and return buffer + if (buf.readableBytes() >= MESSAGE_HEADER) { + int length = buf.getUnsignedShort(buf.readerIndex() + 2); + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/MeiligaoProtocol.java b/src/main/java/org/traccar/protocol/MeiligaoProtocol.java new file mode 100644 index 000000000..e8a66e49f --- /dev/null +++ b/src/main/java/org/traccar/protocol/MeiligaoProtocol.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class MeiligaoProtocol extends BaseProtocol { + + public MeiligaoProtocol() { + setSupportedDataCommands( + Command.TYPE_POSITION_SINGLE, + Command.TYPE_POSITION_PERIODIC, + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME, + Command.TYPE_ALARM_GEOFENCE, + Command.TYPE_SET_TIMEZONE, + Command.TYPE_REQUEST_PHOTO, + Command.TYPE_REBOOT_DEVICE); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new MeiligaoFrameDecoder()); + pipeline.addLast(new MeiligaoProtocolEncoder(MeiligaoProtocol.this)); + pipeline.addLast(new MeiligaoProtocolDecoder(MeiligaoProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new MeiligaoProtocolEncoder(MeiligaoProtocol.this)); + pipeline.addLast(new MeiligaoProtocolDecoder(MeiligaoProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java new file mode 100644 index 000000000..bd66cdc4b --- /dev/null +++ b/src/main/java/org/traccar/protocol/MeiligaoProtocolDecoder.java @@ -0,0 +1,499 @@ +/* + * 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. + * 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.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.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.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class MeiligaoProtocolDecoder extends BaseProtocolDecoder { + + private Map<Byte, ByteBuf> photos = new HashMap<>(); + + public MeiligaoProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("(d+)(dd)(dd).?d*,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .expression("[^\\|]*") + .groupBegin() + .number("|(d+.d+)?") // hdop + .number("|(-?d+.?d*)?") // altitude + .number("|(xxxx)?") // state + .groupBegin() + .number("|(xxxx),(xxxx)") // adc + .number(",(xxxx)").optional() + .number(",(xxxx)").optional() + .number(",(xxxx)").optional() + .number(",(xxxx)").optional() + .number(",(xxxx)").optional() + .number(",(xxxx)").optional() + .groupBegin() + .number("|x{16,20}") // cell + .number("|(xx)") // rssi + .number("|(x{8})") // odometer + .groupBegin() + .number("|(xx)") // satellites + .text("|") + .expression("(.*)") // driver + .groupEnd("?") + .or() + .number("|(d{1,9})") // odometer + .groupBegin() + .number("|(x{5,})") // rfid + .groupEnd("?") + .groupEnd("?") + .groupEnd("?") + .groupEnd("?") + .any() + .compile(); + + private static final Pattern PATTERN_RFID = new PatternBuilder() + .number("|(dd)(dd)(dd),") // time (hhmmss) + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .expression("([EW])") + .compile(); + + private static final Pattern PATTERN_OBD = new PatternBuilder() + .number("(d+.d+),") // battery + .number("(d+),") // rpm + .number("(d+),") // speed + .number("(d+.d+),") // throttle + .number("(d+.d+),") // engine load + .number("(-?d+),") // coolant temp + .number("(d+.d+),") // instantaneous fuel + .number("(d+.d+),") // average fuel + .number("(d+.d+),") // driving range + .number("(d+.?d*),") // odometer + .number("(d+.d+),") // single fuel consumption + .number("(d+.d+),") // total fuel consumption + .number("(d+),") // error code count + .number("(d+),") // hard acceleration count + .number("(d+)") // hard brake count + .compile(); + + private static final Pattern PATTERN_OBDA = new PatternBuilder() + .number("(d+),") // total ignition + .number("(d+.d+),") // total driving time + .number("(d+.d+),") // total idling time + .number("(d+),") // average hot start time + .number("(d+),") // average speed + .number("(d+),") // history highest speed + .number("(d+),") // history highest rpm + .number("(d+),") // total hard acceleration + .number("(d+)") // total hard brake + .compile(); + + public static final int MSG_HEARTBEAT = 0x0001; + public static final int MSG_SERVER = 0x0002; + public static final int MSG_LOGIN = 0x5000; + public static final int MSG_LOGIN_RESPONSE = 0x4000; + public static final int MSG_POSITION = 0x9955; + public static final int MSG_POSITION_LOGGED = 0x9016; + public static final int MSG_ALARM = 0x9999; + public static final int MSG_RFID = 0x9966; + public static final int MSG_RETRANSMISSION = 0x6688; + + public static final int MSG_OBD_RT = 0x9901; + public static final int MSG_OBD_RTA = 0x9902; + + public static final int MSG_TRACK_ON_DEMAND = 0x4101; + public static final int MSG_TRACK_BY_INTERVAL = 0x4102; + public static final int MSG_MOVEMENT_ALARM = 0x4106; + public static final int MSG_OUTPUT_CONTROL = 0x4115; + public static final int MSG_TIME_ZONE = 0x4132; + public static final int MSG_TAKE_PHOTO = 0x4151; + public static final int MSG_UPLOAD_PHOTO = 0x0800; + public static final int MSG_UPLOAD_PHOTO_RESPONSE = 0x8801; + public static final int MSG_DATA_PHOTO = 0x9988; + public static final int MSG_POSITION_IMAGE = 0x9977; + public static final int MSG_UPLOAD_COMPLETE = 0x0f80; + public static final int MSG_REBOOT_GPS = 0x4902; + + private DeviceSession identify(ByteBuf buf, Channel channel, SocketAddress remoteAddress) { + StringBuilder builder = new StringBuilder(); + + for (int i = 0; i < 7; i++) { + int b = buf.readUnsignedByte(); + + // First digit + int d1 = (b & 0xf0) >> 4; + if (d1 == 0xf) { + break; + } + builder.append(d1); + + // Second digit + int d2 = b & 0x0f; + if (d2 == 0xf) { + break; + } + builder.append(d2); + } + + String id = builder.toString(); + + if (id.length() == 14) { + return getDeviceSession(channel, remoteAddress, id, id + Checksum.luhn(Long.parseLong(id))); + } else { + return getDeviceSession(channel, remoteAddress, id); + } + } + + private static void sendResponse( + Channel channel, SocketAddress remoteAddress, ByteBuf id, int type, ByteBuf msg) { + + if (channel != null) { + ByteBuf buf = Unpooled.buffer( + 2 + 2 + id.readableBytes() + 2 + msg.readableBytes() + 2 + 2); + + buf.writeByte('@'); + buf.writeByte('@'); + buf.writeShort(buf.capacity()); + buf.writeBytes(id); + buf.writeShort(type); + buf.writeBytes(msg); + msg.release(); + buf.writeShort(Checksum.crc16(Checksum.CRC16_CCITT_FALSE, buf.nioBuffer())); + buf.writeByte('\r'); + buf.writeByte('\n'); + + channel.writeAndFlush(new NetworkMessage(buf, remoteAddress)); + } + } + + private String decodeAlarm(short value) { + switch (value) { + case 0x01: + return Position.ALARM_SOS; + case 0x10: + return Position.ALARM_LOW_BATTERY; + case 0x11: + return Position.ALARM_OVERSPEED; + case 0x12: + return Position.ALARM_MOVEMENT; + case 0x13: + return Position.ALARM_GEOFENCE_ENTER; + case 0x14: + return Position.ALARM_ACCIDENT; + case 0x50: + return Position.ALARM_POWER_OFF; + case 0x53: + return Position.ALARM_GPS_ANTENNA_CUT; + case 0x72: + return Position.ALARM_BRAKING; + case 0x73: + return Position.ALARM_ACCELERATION; + default: + return null; + } + } + + private Position decodeRegular(Position position, String sentence) { + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + if (parser.hasNext()) { + position.setSpeed(parser.nextDouble(0)); + } + + if (parser.hasNext()) { + position.setCourse(parser.nextDouble(0)); + } + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + position.set(Position.KEY_HDOP, parser.nextDouble()); + + if (parser.hasNext()) { + position.setAltitude(parser.nextDouble(0)); + } + + if (parser.hasNext()) { + int status = parser.nextHexInt(); + for (int i = 1; i <= 5; i++) { + position.set(Position.PREFIX_OUT + i, BitUtil.check(status, i - 1)); + } + for (int i = 1; i <= 5; i++) { + position.set(Position.PREFIX_IN + i, BitUtil.check(status, i - 1 + 8)); + } + } + + for (int i = 1; i <= 8; i++) { + position.set(Position.PREFIX_ADC + i, parser.nextHexInt()); + } + + position.set(Position.KEY_RSSI, parser.nextHexInt()); + position.set(Position.KEY_ODOMETER, parser.nextHexLong()); + position.set(Position.KEY_SATELLITES, parser.nextHexInt()); + position.set("driverLicense", parser.next()); + position.set(Position.KEY_ODOMETER, parser.nextLong()); + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + + return position; + } + + private Position decodeRfid(Position position, String sentence) { + Parser parser = new Parser(PATTERN_RFID, sentence); + if (!parser.matches()) { + return null; + } + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY)); + + position.setValid(true); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + return position; + } + + private Position decodeObd(Position position, String sentence) { + Parser parser = new Parser(PATTERN_OBD, sentence); + if (!parser.matches()) { + return null; + } + + getLastLocation(position, null); + + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_RPM, parser.nextInt()); + position.set(Position.KEY_OBD_SPEED, parser.nextInt()); + position.set(Position.KEY_THROTTLE, parser.nextDouble()); + position.set(Position.KEY_ENGINE_LOAD, parser.nextDouble()); + position.set(Position.KEY_COOLANT_TEMP, parser.nextInt()); + position.set(Position.KEY_FUEL_CONSUMPTION, parser.nextDouble()); + position.set("averageFuelConsumption", parser.nextDouble()); + position.set("drivingRange", parser.nextDouble()); + position.set(Position.KEY_ODOMETER, parser.nextDouble()); + position.set("singleFuelConsumption", parser.nextDouble()); + position.set(Position.KEY_FUEL_USED, parser.nextDouble()); + position.set(Position.KEY_DTCS, parser.nextInt()); + position.set("hardAccelerationCount", parser.nextInt()); + position.set("hardBrakingCount", parser.nextInt()); + + return position; + } + + private Position decodeObdA(Position position, String sentence) { + Parser parser = new Parser(PATTERN_OBDA, sentence); + if (!parser.matches()) { + return null; + } + + getLastLocation(position, null); + + position.set("totalIgnitionNo", parser.nextInt(0)); + position.set("totalDrivingTime", parser.nextDouble(0)); + position.set("totalIdlingTime", parser.nextDouble(0)); + position.set("averageHotStartTime", parser.nextInt(0)); + position.set("averageSpeed", parser.nextInt(0)); + position.set("historyHighestSpeed", parser.nextInt(0)); + position.set("historyHighestRpm", parser.nextInt(0)); + position.set("totalHarshAccerleration", parser.nextInt(0)); + position.set("totalHarshBrake", parser.nextInt(0)); + + return position; + } + + private List<Position> decodeRetransmission(ByteBuf buf, DeviceSession deviceSession) { + List<Position> positions = new LinkedList<>(); + + int count = buf.readUnsignedByte(); + for (int i = 0; i < count; i++) { + + buf.readUnsignedByte(); // alarm + + int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '\\'); + if (endIndex < 0) { + endIndex = buf.writerIndex() - 4; + } + + String sentence = buf.readSlice(endIndex - buf.readerIndex()).toString(StandardCharsets.US_ASCII); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position = decodeRegular(position, sentence); + + if (position != null) { + positions.add(position); + } + + if (buf.readableBytes() > 4) { + buf.readUnsignedByte(); // delimiter + } + + } + + return positions; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + buf.skipBytes(2); // header + buf.readShort(); // length + ByteBuf id = buf.readSlice(7); + int command = buf.readUnsignedShort(); + + if (command == MSG_LOGIN) { + ByteBuf response = Unpooled.wrappedBuffer(new byte[]{0x01}); + sendResponse(channel, remoteAddress, id, MSG_LOGIN_RESPONSE, response); + return null; + } else if (command == MSG_HEARTBEAT) { + ByteBuf response = Unpooled.wrappedBuffer(new byte[]{0x01}); + sendResponse(channel, remoteAddress, id, MSG_HEARTBEAT, response); + return null; + } else if (command == MSG_SERVER) { + ByteBuf response = Unpooled.copiedBuffer(getServer(channel, ':'), StandardCharsets.US_ASCII); + sendResponse(channel, remoteAddress, id, MSG_SERVER, response); + return null; + } else if (command == MSG_UPLOAD_PHOTO) { + byte imageIndex = buf.readByte(); + photos.put(imageIndex, Unpooled.buffer()); + ByteBuf response = Unpooled.copiedBuffer(new byte[]{imageIndex}); + sendResponse(channel, remoteAddress, id, MSG_UPLOAD_PHOTO_RESPONSE, response); + return null; + } else if (command == MSG_UPLOAD_COMPLETE) { + byte imageIndex = buf.readByte(); + ByteBuf response = Unpooled.copiedBuffer(new byte[]{imageIndex, 0, 0}); + sendResponse(channel, remoteAddress, id, MSG_RETRANSMISSION, response); + return null; + } + + DeviceSession deviceSession = identify(id, channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + if (command == MSG_DATA_PHOTO) { + + byte imageIndex = buf.readByte(); + buf.readUnsignedShort(); // image footage + buf.readUnsignedByte(); // total packets + buf.readUnsignedByte(); // packet index + + photos.get(imageIndex).writeBytes(buf, buf.readableBytes() - 2 - 2); + + return null; + + } else if (command == MSG_RETRANSMISSION) { + + return decodeRetransmission(buf, deviceSession); + + } else { + + Position position = new Position(getProtocolName()); + + position.setDeviceId(deviceSession.getDeviceId()); + + if (command == MSG_ALARM) { + short alarmCode = buf.readUnsignedByte(); + position.set(Position.KEY_ALARM, decodeAlarm(alarmCode)); + if (alarmCode >= 0x02 && alarmCode <= 0x05) { + position.set(Position.PREFIX_IN + alarmCode, 1); + } else if (alarmCode >= 0x32 && alarmCode <= 0x35) { + position.set(Position.PREFIX_IN + (alarmCode - 0x30), 0); + } + } else if (command == MSG_POSITION_LOGGED) { + buf.skipBytes(6); + } else if (command == MSG_RFID) { + for (int i = 0; i < 15; i++) { + long rfid = buf.readUnsignedInt(); + if (rfid != 0) { + String card = String.format("%010d", rfid); + position.set("card" + (i + 1), card); + position.set(Position.KEY_DRIVER_UNIQUE_ID, card); + } + } + } else if (command == MSG_POSITION_IMAGE) { + byte imageIndex = buf.readByte(); + buf.readUnsignedByte(); // image upload type + String uniqueId = Context.getIdentityManager().getById(deviceSession.getDeviceId()).getUniqueId(); + ByteBuf photo = photos.remove(imageIndex); + try { + position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(uniqueId, photo, "jpg")); + } finally { + photo.release(); + } + } + + String sentence = buf.toString(buf.readerIndex(), buf.readableBytes() - 4, StandardCharsets.US_ASCII); + + switch (command) { + case MSG_POSITION: + case MSG_POSITION_LOGGED: + case MSG_ALARM: + case MSG_POSITION_IMAGE: + return decodeRegular(position, sentence); + case MSG_RFID: + return decodeRfid(position, sentence); + case MSG_OBD_RT: + return decodeObd(position, sentence); + case MSG_OBD_RTA: + return decodeObdA(position, sentence); + default: + return null; + } + + } + } + +} diff --git a/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java b/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java new file mode 100644 index 000000000..36e94195c --- /dev/null +++ b/src/main/java/org/traccar/protocol/MeiligaoProtocolEncoder.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.helper.Checksum; +import org.traccar.helper.DataConverter; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.nio.charset.StandardCharsets; +import java.util.TimeZone; + +public class MeiligaoProtocolEncoder extends BaseProtocolEncoder { + + public MeiligaoProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf encodeContent(long deviceId, int type, ByteBuf content) { + + ByteBuf buf = Unpooled.buffer(); + + buf.writeByte('@'); + buf.writeByte('@'); + + buf.writeShort(2 + 2 + 7 + 2 + content.readableBytes() + 2 + 2); // message length + + buf.writeBytes(DataConverter.parseHex((getUniqueId(deviceId) + "FFFFFFFFFFFFFF").substring(0, 14))); + + buf.writeShort(type); + + buf.writeBytes(content); + + buf.writeShort(Checksum.crc16(Checksum.CRC16_CCITT_FALSE, buf.nioBuffer())); + + buf.writeByte('\r'); + buf.writeByte('\n'); + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + + ByteBuf content = Unpooled.buffer(); + + switch (command.getType()) { + case Command.TYPE_POSITION_SINGLE: + return encodeContent(command.getDeviceId(), MeiligaoProtocolDecoder.MSG_TRACK_ON_DEMAND, content); + case Command.TYPE_POSITION_PERIODIC: + content.writeShort(command.getInteger(Command.KEY_FREQUENCY) / 10); + return encodeContent(command.getDeviceId(), MeiligaoProtocolDecoder.MSG_TRACK_BY_INTERVAL, content); + case Command.TYPE_ENGINE_STOP: + content.writeByte(0x01); + return encodeContent(command.getDeviceId(), MeiligaoProtocolDecoder.MSG_OUTPUT_CONTROL, content); + case Command.TYPE_ENGINE_RESUME: + content.writeByte(0x00); + return encodeContent(command.getDeviceId(), MeiligaoProtocolDecoder.MSG_OUTPUT_CONTROL, content); + case Command.TYPE_ALARM_GEOFENCE: + content.writeShort(command.getInteger(Command.KEY_RADIUS)); + return encodeContent(command.getDeviceId(), MeiligaoProtocolDecoder.MSG_MOVEMENT_ALARM, content); + case Command.TYPE_SET_TIMEZONE: + int offset = TimeZone.getTimeZone(command.getString(Command.KEY_TIMEZONE)).getRawOffset() / 60000; + content.writeBytes(String.valueOf(offset).getBytes(StandardCharsets.US_ASCII)); + return encodeContent(command.getDeviceId(), MeiligaoProtocolDecoder.MSG_TIME_ZONE, content); + case Command.TYPE_REQUEST_PHOTO: + return encodeContent(command.getDeviceId(), MeiligaoProtocolDecoder.MSG_TAKE_PHOTO, content); + case Command.TYPE_REBOOT_DEVICE: + return encodeContent(command.getDeviceId(), MeiligaoProtocolDecoder.MSG_REBOOT_GPS, content); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/MeitrackFrameDecoder.java b/src/main/java/org/traccar/protocol/MeitrackFrameDecoder.java new file mode 100644 index 000000000..d122bca0c --- /dev/null +++ b/src/main/java/org/traccar/protocol/MeitrackFrameDecoder.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +import java.nio.charset.StandardCharsets; + +public class MeitrackFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 10) { + return null; + } + + int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ','); + if (index != -1) { + int length = index - buf.readerIndex() + Integer.parseInt( + buf.toString(buf.readerIndex() + 3, index - buf.readerIndex() - 3, StandardCharsets.US_ASCII)); + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocol.java b/src/main/java/org/traccar/protocol/MeitrackProtocol.java new file mode 100644 index 000000000..7439ea611 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MeitrackProtocol.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class MeitrackProtocol extends BaseProtocol { + + public MeitrackProtocol() { + setSupportedDataCommands( + Command.TYPE_POSITION_SINGLE, + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME, + Command.TYPE_ALARM_ARM, + Command.TYPE_ALARM_DISARM, + Command.TYPE_REQUEST_PHOTO, + Command.TYPE_SEND_SMS); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new MeitrackFrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new MeitrackProtocolEncoder(MeitrackProtocol.this)); + pipeline.addLast(new MeitrackProtocolDecoder(MeitrackProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new MeitrackProtocolEncoder(MeitrackProtocol.this)); + pipeline.addLast(new MeitrackProtocolDecoder(MeitrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java new file mode 100644 index 000000000..55260ef0c --- /dev/null +++ b/src/main/java/org/traccar/protocol/MeitrackProtocolDecoder.java @@ -0,0 +1,534 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +public class MeitrackProtocolDecoder extends BaseProtocolDecoder { + + private ByteBuf photo; + + public MeitrackProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$$").expression(".") // flag + .number("d+,") // length + .number("(d+),") // imei + .number("xxx,") // command + .number("d+,").optional() + .number("(d+),") // event + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("([AV]),") // validity + .number("(d+),") // satellites + .number("(d+),") // rssi + .number("(d+.?d*),") // speed + .number("(d+),") // course + .number("(d+.?d*),") // hdop + .number("(-?d+),") // altitude + .number("(d+),") // odometer + .number("(d+),") // runtime + .number("(d+)|") // mcc + .number("(d+)|") // mnc + .number("(x+)|") // lac + .number("(x+),") // cid + .number("(x+),") // state + .number("(x+)?|") // adc1 + .number("(x+)?|") // adc2 + .number("(x+)?|") // adc3 + .number("(x+)|") // battery + .number("(x+)?,") // power + .groupBegin() + .expression("([^,]+)?,").optional() // event specific + .expression("[^,]*,") // reserved + .number("(d+)?,") // protocol + .number("(x{4})?") // fuel + .groupBegin() + .number(",(x{6}(?:|x{6})*)?") // temperature + .groupBegin() + .number(",(d+)") // data count + .expression(",([^*]*)") // data + .groupEnd("?") + .groupEnd("?") + .or() + .any() + .groupEnd() + .text("*") + .number("xx") + .text("\r\n").optional() + .compile(); + + private String decodeAlarm(int event) { + switch (event) { + case 1: + return Position.ALARM_SOS; + case 17: + return Position.ALARM_LOW_BATTERY; + case 18: + return Position.ALARM_LOW_POWER; + case 19: + return Position.ALARM_OVERSPEED; + case 20: + return Position.ALARM_GEOFENCE_ENTER; + case 21: + return Position.ALARM_GEOFENCE_EXIT; + case 22: + return Position.ALARM_POWER_RESTORED; + case 23: + return Position.ALARM_POWER_CUT; + case 36: + return Position.ALARM_TOW; + case 44: + return Position.ALARM_JAMMING; + case 78: + return Position.ALARM_ACCIDENT; + case 90: + case 91: + return Position.ALARM_CORNERING; + case 129: + return Position.ALARM_BRAKING; + case 130: + return Position.ALARM_ACCELERATION; + case 135: + return Position.ALARM_FATIGUE_DRIVING; + default: + return null; + } + } + + private Position decodeRegular(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + 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()); + + int event = parser.nextInt(0); + position.set(Position.KEY_EVENT, event); + position.set(Position.KEY_ALARM, decodeAlarm(event)); + + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + + position.setTime(parser.nextDateTime()); + + position.setValid(parser.next().equals("A")); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + int rssi = parser.nextInt(0); + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_HDOP, parser.nextDouble()); + + position.setAltitude(parser.nextDouble(0)); + + position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + position.set("runtime", parser.next()); + + position.setNetwork(new Network(CellTower.from( + parser.nextInt(0), parser.nextInt(0), parser.nextHexInt(0), parser.nextHexInt(0), rssi))); + + position.set(Position.KEY_STATUS, parser.next()); + + for (int i = 1; i <= 3; i++) { + if (parser.hasNext()) { + position.set(Position.PREFIX_ADC + i, parser.nextHexInt(0)); + } + } + + String deviceModel = Context.getIdentityManager().getById(deviceSession.getDeviceId()).getModel(); + if (deviceModel == null) { + deviceModel = ""; + } + switch (deviceModel.toUpperCase()) { + case "MVT340": + case "MVT380": + position.set(Position.KEY_BATTERY, parser.nextHexInt(0) * 3.0 * 2.0 / 1024.0); + position.set(Position.KEY_POWER, parser.nextHexInt(0) * 3.0 * 16.0 / 1024.0); + break; + case "MT90": + position.set(Position.KEY_BATTERY, parser.nextHexInt(0) * 3.3 * 2.0 / 4096.0); + position.set(Position.KEY_POWER, parser.nextHexInt(0)); + break; + case "T1": + case "T3": + case "MVT100": + case "MVT600": + case "MVT800": + case "TC68": + case "TC68S": + position.set(Position.KEY_BATTERY, parser.nextHexInt(0) * 3.3 * 2.0 / 4096.0); + position.set(Position.KEY_POWER, parser.nextHexInt(0) * 3.3 * 16.0 / 4096.0); + break; + case "T311": + case "T322X": + case "T333": + case "T355": + position.set(Position.KEY_BATTERY, parser.nextHexInt(0) / 100.0); + position.set(Position.KEY_POWER, parser.nextHexInt(0) / 100.0); + break; + default: + position.set(Position.KEY_BATTERY, parser.nextHexInt(0)); + position.set(Position.KEY_POWER, parser.nextHexInt(0)); + break; + } + + String eventData = parser.next(); + if (eventData != null && !eventData.isEmpty()) { + switch (event) { + case 37: + position.set(Position.KEY_DRIVER_UNIQUE_ID, eventData); + break; + default: + position.set("eventData", eventData); + break; + } + } + + int protocol = parser.nextInt(0); + + if (parser.hasNext()) { + String fuel = parser.next(); + position.set(Position.KEY_FUEL_LEVEL, + Integer.parseInt(fuel.substring(0, 2), 16) + Integer.parseInt(fuel.substring(2), 16) * 0.01); + } + + if (parser.hasNext()) { + for (String temp : parser.next().split("\\|")) { + int index = Integer.parseInt(temp.substring(0, 2), 16); + if (protocol >= 3) { + double value = (short) Integer.parseInt(temp.substring(2), 16); + position.set(Position.PREFIX_TEMP + index, value * 0.01); + } else { + double value = Byte.parseByte(temp.substring(2, 4), 16); + value += (value < 0 ? -0.01 : 0.01) * Integer.parseInt(temp.substring(4), 16); + position.set(Position.PREFIX_TEMP + index, value); + } + } + } + + if (parser.hasNext(2)) { + parser.nextInt(); // count + decodeDataFields(position, parser.next().split(",")); + } + + return position; + } + + private void decodeDataFields(Position position, String[] values) { + + if (values.length > 1 && !values[1].isEmpty()) { + position.set("tempData", values[1]); + } + + if (values.length > 5 && !values[5].isEmpty()) { + String[] data = values[5].split("\\|"); + boolean started = data[0].charAt(1) == '0'; + position.set("taximeterOn", started); + position.set("taximeterStart", data[1]); + if (data.length > 2) { + position.set("taximeterEnd", data[2]); + position.set("taximeterDistance", Integer.parseInt(data[3])); + position.set("taximeterFare", Integer.parseInt(data[4])); + position.set("taximeterTrip", data[5]); + position.set("taximeterWait", data[6]); + } + } + + } + + private List<Position> decodeBinaryC(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + List<Position> positions = new LinkedList<>(); + + String flag = buf.toString(2, 1, StandardCharsets.US_ASCII); + int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ','); + + String imei = buf.toString(index + 1, 15, StandardCharsets.US_ASCII); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + buf.skipBytes(index + 1 + 15 + 1 + 3 + 1 + 2 + 2 + 4); + + while (buf.readableBytes() >= 0x34) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + + position.setLatitude(buf.readIntLE() * 0.000001); + position.setLongitude(buf.readIntLE() * 0.000001); + + position.setTime(new Date((946684800 + buf.readUnsignedIntLE()) * 1000)); // 946684800 = 2000-01-01 + + position.setValid(buf.readUnsignedByte() == 1); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + int rssi = buf.readUnsignedByte(); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE())); + position.setCourse(buf.readUnsignedShortLE()); + + position.set(Position.KEY_HDOP, buf.readUnsignedShortLE() * 0.1); + + position.setAltitude(buf.readUnsignedShortLE()); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + position.set("runtime", buf.readUnsignedIntLE()); + + position.setNetwork(new Network(CellTower.from( + buf.readUnsignedShortLE(), buf.readUnsignedShortLE(), + buf.readUnsignedShortLE(), buf.readUnsignedShortLE(), + rssi))); + + position.set(Position.KEY_STATUS, buf.readUnsignedShortLE()); + + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShortLE()); + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.01); + position.set(Position.KEY_POWER, buf.readUnsignedShortLE()); + + buf.readUnsignedIntLE(); // geo-fence + + positions.add(position); + } + + if (channel != null) { + StringBuilder command = new StringBuilder("@@"); + command.append(flag).append(27 + positions.size() / 10).append(","); + command.append(imei).append(",CCC,").append(positions.size()).append("*"); + int checksum = 0; + for (int i = 0; i < command.length(); i += 1) { + checksum += command.charAt(i); + } + command.append(String.format("%02x", checksum & 0xff).toUpperCase()); + command.append("\r\n"); + channel.writeAndFlush(new NetworkMessage(command.toString(), remoteAddress)); // delete processed data + } + + return positions; + } + + private List<Position> decodeBinaryE(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + List<Position> positions = new LinkedList<>(); + + buf.readerIndex(buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ',') + 1); + String imei = buf.readSlice(15).toString(StandardCharsets.US_ASCII); + buf.skipBytes(1 + 3 + 1); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + buf.readUnsignedIntLE(); // remaining cache + int count = buf.readUnsignedShortLE(); + + for (int i = 0; i < count; i++) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedShortLE(); // length + buf.readUnsignedShortLE(); // index + + int paramCount = buf.readUnsignedByte(); + for (int j = 0; j < paramCount; j++) { + int id = buf.readUnsignedByte(); + switch (id) { + case 0x01: + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + break; + case 0x05: + position.setValid(buf.readUnsignedByte() > 0); + break; + case 0x06: + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + break; + case 0x07: + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + break; + default: + buf.readUnsignedByte(); + break; + } + } + + paramCount = buf.readUnsignedByte(); + for (int j = 0; j < paramCount; j++) { + int id = buf.readUnsignedByte(); + switch (id) { + case 0x08: + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE())); + break; + case 0x09: + position.setCourse(buf.readUnsignedShortLE()); + break; + case 0x0B: + position.setAltitude(buf.readShortLE()); + break; + case 0x19: + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.01); + break; + case 0x1A: + position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.01); + break; + default: + buf.readUnsignedShortLE(); + break; + } + } + + paramCount = buf.readUnsignedByte(); + for (int j = 0; j < paramCount; j++) { + int id = buf.readUnsignedByte(); + switch (id) { + case 0x02: + position.setLatitude(buf.readIntLE() * 0.000001); + break; + case 0x03: + position.setLongitude(buf.readIntLE() * 0.000001); + break; + case 0x04: + position.setTime(new Date((946684800 + buf.readUnsignedIntLE()) * 1000)); // 2000-01-01 + break; + case 0x0D: + position.set("runtime", buf.readUnsignedIntLE()); + break; + default: + buf.readUnsignedIntLE(); + break; + } + } + + paramCount = buf.readUnsignedByte(); + for (int j = 0; j < paramCount; j++) { + buf.readUnsignedByte(); // id + buf.skipBytes(buf.readUnsignedByte()); // value + } + + positions.add(position); + } + + return positions; + } + + private void requestPhotoPacket(Channel channel, SocketAddress socketAddress, String imei, String file, int index) { + if (channel != null) { + String content = "D00," + file + "," + index; + int length = 1 + imei.length() + 1 + content.length() + 5; + String response = String.format("@@O%02d,%s,%s*", length, imei, content); + response += Checksum.sum(response) + "\r\n"; + channel.writeAndFlush(new NetworkMessage(response, socketAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ','); + String imei = buf.toString(index + 1, 15, StandardCharsets.US_ASCII); + index = buf.indexOf(index + 1, buf.writerIndex(), (byte) ','); + String type = buf.toString(index + 1, 3, StandardCharsets.US_ASCII); + + switch (type) { + case "D00": + if (photo == null) { + photo = Unpooled.buffer(); + } + + index = index + 1 + type.length() + 1; + int endIndex = buf.indexOf(index, buf.writerIndex(), (byte) ','); + String file = buf.toString(index, endIndex - index, StandardCharsets.US_ASCII); + index = endIndex + 1; + endIndex = buf.indexOf(index, buf.writerIndex(), (byte) ','); + int total = Integer.parseInt(buf.toString(index, endIndex - index, StandardCharsets.US_ASCII)); + index = endIndex + 1; + endIndex = buf.indexOf(index, buf.writerIndex(), (byte) ','); + int current = Integer.parseInt(buf.toString(index, endIndex - index, StandardCharsets.US_ASCII)); + + buf.readerIndex(endIndex + 1); + photo.writeBytes(buf.readSlice(buf.readableBytes() - 1 - 2 - 2)); + + if (current == total - 1) { + Position position = new Position(getProtocolName()); + position.setDeviceId(getDeviceSession(channel, remoteAddress, imei).getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(imei, photo, "jpg")); + photo.release(); + photo = null; + + return position; + } else { + if ((current + 1) % 8 == 0) { + requestPhotoPacket(channel, remoteAddress, imei, file, current + 1); + } + return null; + } + case "D03": + photo = Unpooled.buffer(); + requestPhotoPacket(channel, remoteAddress, imei, "camera_picture.jpg", 0); + return null; + case "CCC": + return decodeBinaryC(channel, remoteAddress, buf); + case "CCE": + return decodeBinaryE(channel, remoteAddress, buf); + default: + return decodeRegular(channel, remoteAddress, buf); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/MeitrackProtocolEncoder.java b/src/main/java/org/traccar/protocol/MeitrackProtocolEncoder.java new file mode 100644 index 000000000..354e81434 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MeitrackProtocolEncoder.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.Context; +import org.traccar.StringProtocolEncoder; +import org.traccar.helper.Checksum; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.util.Map; + +public class MeitrackProtocolEncoder extends StringProtocolEncoder { + + public MeitrackProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private Object formatCommand(Command command, char dataId, String content) { + String uniqueId = getUniqueId(command.getDeviceId()); + int length = 1 + uniqueId.length() + 1 + content.length() + 5; + String result = String.format("@@%c%02d,%s,%s*", dataId, length, uniqueId, content); + result += Checksum.sum(result) + "\r\n"; + return result; + } + + @Override + protected Object encodeCommand(Command command) { + + Map<String, Object> attributes = command.getAttributes(); + + boolean alternative = Context.getIdentityManager().lookupAttributeBoolean( + command.getDeviceId(), getProtocolName() + ".alternative", false, false, true); + + switch (command.getType()) { + case Command.TYPE_POSITION_SINGLE: + return formatCommand(command, 'Q', "A10"); + case Command.TYPE_ENGINE_STOP: + return formatCommand(command, 'M', "C01,0,12222"); + case Command.TYPE_ENGINE_RESUME: + return formatCommand(command, 'M', "C01,0,02222"); + case Command.TYPE_ALARM_ARM: + return formatCommand(command, 'M', alternative ? "B21,1" : "C01,0,22122"); + case Command.TYPE_ALARM_DISARM: + return formatCommand(command, 'M', alternative ? "B21,0" : "C01,0,22022"); + case Command.TYPE_REQUEST_PHOTO: + int index = command.getInteger(Command.KEY_INDEX); + return formatCommand(command, 'D', "D03," + (index > 0 ? index : 1) + ",camera_picture.jpg"); + case Command.TYPE_SEND_SMS: + return formatCommand(command, 'f', "C02,0," + + attributes.get(Command.KEY_PHONE) + "," + attributes.get(Command.KEY_MESSAGE)); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/MictrackProtocol.java b/src/main/java/org/traccar/protocol/MictrackProtocol.java new file mode 100644 index 000000000..c8d64fd81 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MictrackProtocol.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class MictrackProtocol extends BaseProtocol { + + public MictrackProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new MictrackProtocolDecoder(MictrackProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new MictrackProtocolDecoder(MictrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java new file mode 100644 index 000000000..a2fccb707 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MictrackProtocolDecoder.java @@ -0,0 +1,181 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +public class MictrackProtocolDecoder extends BaseProtocolDecoder { + + public MictrackProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private Date decodeTime(String data) throws ParseException { + DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + return dateFormat.parse(data); + } + + private String decodeAlarm(int event) { + switch (event) { + case 5: + return Position.ALARM_SOS; + case 8: + return Position.ALARM_LOW_BATTERY; + case 9: + return Position.ALARM_GEOFENCE_ENTER; + case 10: + return Position.ALARM_GEOFENCE_EXIT; + case 12: + return Position.ALARM_POWER_OFF; + default: + return null; + } + } + + private void decodeLocation(Position position, String data) throws ParseException { + int index = 0; + String[] values = data.split("\\+"); + + position.set(Position.KEY_SATELLITES, Integer.parseInt(values[index++])); + + position.setValid(true); + position.setTime(decodeTime(values[index++])); + position.setLatitude(Double.parseDouble(values[index++])); + position.setLongitude(Double.parseDouble(values[index++])); + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[index++]))); + position.setCourse(Integer.parseInt(values[index++])); + + int event = Integer.parseInt(values[index++]); + position.set(Position.KEY_ALARM, decodeAlarm(event)); + position.set(Position.KEY_EVENT, event); + position.set(Position.KEY_BATTERY, Integer.parseInt(values[index++]) * 0.001); + } + + private void decodeCell(Network network, String data) { + String[] values = data.split(","); + int length = values.length % 5 == 0 ? 5 : 4; + for (int i = 0; i < values.length / length; i++) { + int mnc = Integer.parseInt(values[i * length]); + int cid = Integer.parseInt(values[i * length + 1]); + int lac = Integer.parseInt(values[i * length + 2]); + int mcc = Integer.parseInt(values[i * length + 3]); + network.addCellTower(CellTower.from(mcc, mnc, lac, cid)); + } + } + + private void decodeWifi(Network network, String data) { + String[] values = data.split(","); + for (int i = 0; i < values.length / 2; i++) { + network.addWifiAccessPoint(WifiAccessPoint.from(values[i * 2], Integer.parseInt(values[i * 2 + 1]))); + } + } + + private void decodeNetwork(Position position, String data, boolean hasWifi, boolean hasCell) throws ParseException { + int index = 0; + String[] values = data.split("\\+"); + + getLastLocation(position, decodeTime(values[index++])); + + Network network = new Network(); + + if (hasWifi) { + decodeWifi(network, values[index++]); + } + + if (hasCell) { + decodeCell(network, values[index++]); + } + + position.setNetwork(network); + + int event = Integer.parseInt(values[index++]); + position.set(Position.KEY_ALARM, decodeAlarm(event)); + position.set(Position.KEY_EVENT, event); + position.set(Position.KEY_BATTERY, Integer.parseInt(values[index++]) * 0.001); + } + + private void decodeStatus(Position position, String data) throws ParseException { + int index = 0; + String[] values = data.split("\\+"); + + position.set(Position.KEY_SATELLITES, Integer.parseInt(values[index++])); + + getLastLocation(position, decodeTime(values[index++])); + + index += 4; // fix values + + int event = Integer.parseInt(values[index++]); + position.set(Position.KEY_ALARM, decodeAlarm(event)); + position.set(Position.KEY_EVENT, event); + position.set(Position.KEY_BATTERY, Integer.parseInt(values[index++]) * 0.001); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String[] fragments = ((String) msg).split(";"); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, fragments[2]); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + switch (fragments[3]) { + case "R0": + decodeLocation(position, fragments[4]); + break; + case "R1": + decodeNetwork(position, fragments[4], true, false); + break; + case "R2": + case "R3": + decodeNetwork(position, fragments[4], false, true); + break; + case "R12": + case "R13": + decodeNetwork(position, fragments[4], true, true); + break; + case "RH": + decodeStatus(position, fragments[4]); + break; + default: + return null; + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/MilesmateProtocol.java b/src/main/java/org/traccar/protocol/MilesmateProtocol.java new file mode 100644 index 000000000..822711603 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MilesmateProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class MilesmateProtocol extends BaseProtocol { + + public MilesmateProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new MilesmateProtocolDecoder(MilesmateProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/MilesmateProtocolDecoder.java b/src/main/java/org/traccar/protocol/MilesmateProtocolDecoder.java new file mode 100644 index 000000000..901ceb8f7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MilesmateProtocolDecoder.java @@ -0,0 +1,108 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class MilesmateProtocolDecoder extends BaseProtocolDecoder { + + public MilesmateProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("ApiString={") + .number("A:(d+),") // imei + .number("B:(d+.d+),") // battery + .number("C:(d+.d+),") // adc + .number("D:(dd)(dd)(dd),") // time (hhmmss) + .number("E:(dd)(dd.d+)([NS]),") // latitude + .number("F:(ddd)(dd.d+)([EW]),") // longitude + .number("G:(d+.d+),") // speed + .number("H:(dd)(dd)(dd),") // date (ddmmyy) + .expression("I:[GL],") // location source + .number("J:(d{8}),") // flags + .number("K:(d{7})") // flags + .expression("([AV]),") // validity + .number("L:d{4},") // pin + .number("M:(d+.d+)") // course + .text("}") + .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; + } + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("+##Received OK\n", remoteAddress)); + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + String flags = parser.next(); + position.set(Position.KEY_IGNITION, flags.charAt(0) == '1'); + position.set(Position.KEY_ALARM, flags.charAt(1) == '1' ? Position.ALARM_SOS : null); + position.set(Position.KEY_CHARGE, flags.charAt(5) == '1'); + position.set(Position.KEY_ALARM, flags.charAt(7) == '1' ? Position.ALARM_OVERSPEED : null); + + flags = parser.next(); + position.set(Position.KEY_BLOCKED, flags.charAt(0) == '1'); + position.set(Position.KEY_ALARM, flags.charAt(1) == '1' ? Position.ALARM_TOW : null); + + position.setValid(parser.next().equals("A")); + + position.setCourse(parser.nextDouble()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/MiniFinderProtocol.java b/src/main/java/org/traccar/protocol/MiniFinderProtocol.java new file mode 100644 index 000000000..82534ecd8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MiniFinderProtocol.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class MiniFinderProtocol extends BaseProtocol { + + public MiniFinderProtocol() { + setSupportedDataCommands( + Command.TYPE_SET_TIMEZONE, + Command.TYPE_VOICE_MONITORING, + Command.TYPE_ALARM_SPEED, + Command.TYPE_ALARM_GEOFENCE, + Command.TYPE_ALARM_VIBRATION, + Command.TYPE_SET_AGPS, + Command.TYPE_ALARM_FALL, + Command.TYPE_MODE_POWER_SAVING, + Command.TYPE_MODE_DEEP_SLEEP, + Command.TYPE_SOS_NUMBER, + Command.TYPE_SET_INDICATOR); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ';')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new MiniFinderProtocolEncoder(MiniFinderProtocol.this)); + pipeline.addLast(new MiniFinderProtocolDecoder(MiniFinderProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java b/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java new file mode 100644 index 000000000..2b7a960c4 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MiniFinderProtocolDecoder.java @@ -0,0 +1,208 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class MiniFinderProtocolDecoder extends BaseProtocolDecoder { + + public MiniFinderProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_FIX = new PatternBuilder() + .number("(d+)/(d+)/(d+),") // date (dd/mm/yy) + .number("(d+):(d+):(d+),") // time (hh:mm:ss) + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .compile(); + + private static final Pattern PATTERN_STATE = new PatternBuilder() + .number("(d+.?d*),") // speed (km/h) + .number("(d+.?d*),") // course + .number("(x+),") // flags + .number("(-?d+.d+),") // altitude (meters) + .number("(d+),") // battery (percentage) + .compile(); + + private static final Pattern PATTERN_A = new PatternBuilder() + .text("!A,") + .expression(PATTERN_FIX.pattern()) + .any() // unknown 3 fields + .compile(); + + private static final Pattern PATTERN_C = new PatternBuilder() + .text("!C,") + .expression(PATTERN_FIX.pattern()) + .expression(PATTERN_STATE.pattern()) + .any() // unknown 3 fields + .compile(); + + private static final Pattern PATTERN_BD = new PatternBuilder() + .expression("![BD],") // B - buffered, D - live + .expression(PATTERN_FIX.pattern()) + .expression(PATTERN_STATE.pattern()) + .number("(d+),") // satellites in use + .number("(d+),") // satellites in view + .number("(d+.?d*)") // hdop + .compile(); + + private void decodeFix(Position position, Parser parser) { + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + } + + private void decodeFlags(Position position, int flags) { + + position.setValid(BitUtil.to(flags, 2) > 0); + if (BitUtil.check(flags, 1)) { + position.set(Position.KEY_APPROXIMATE, true); + } + + if (BitUtil.check(flags, 2)) { + position.set(Position.KEY_ALARM, Position.ALARM_FAULT); + } + if (BitUtil.check(flags, 6)) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + if (BitUtil.check(flags, 7)) { + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + } + if (BitUtil.check(flags, 8)) { + position.set(Position.KEY_ALARM, Position.ALARM_FALL_DOWN); + } + if (BitUtil.check(flags, 9) || BitUtil.check(flags, 10) || BitUtil.check(flags, 11)) { + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE); + } + if (BitUtil.check(flags, 12)) { + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + } + if (BitUtil.check(flags, 15) || BitUtil.check(flags, 14)) { + position.set(Position.KEY_ALARM, Position.ALARM_MOVEMENT); + } + + position.set(Position.KEY_RSSI, BitUtil.between(flags, 16, 21)); + position.set(Position.KEY_CHARGE, BitUtil.check(flags, 22)); + } + + private void decodeState(Position position, Parser parser) { + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + + position.setCourse(parser.nextDouble(0)); + if (position.getCourse() > 360) { + position.setCourse(0); + } + + decodeFlags(position, parser.nextHexInt(0)); + + position.setAltitude(parser.nextDouble(0)); + + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt(0)); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.startsWith("!1,")) { + int index = sentence.indexOf(',', 3); + if (index < 0) { + index = sentence.length(); + } + getDeviceSession(channel, remoteAddress, sentence.substring(3, index)); + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null || !sentence.matches("![3A-D],.*")) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + String type = sentence.substring(1, 2); + position.set(Position.KEY_TYPE, type); + + if (type.equals("3")) { + + getLastLocation(position, null); + + position.set(Position.KEY_RESULT, sentence.substring(3)); + + return position; + + } else if (type.equals("B") || type.equals("D")) { + + Parser parser = new Parser(PATTERN_BD, sentence); + if (!parser.matches()) { + return null; + } + + decodeFix(position, parser); + decodeState(position, parser); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + position.set(Position.KEY_SATELLITES_VISIBLE, parser.nextInt(0)); + position.set(Position.KEY_HDOP, parser.nextDouble(0)); + + return position; + + } else if (type.equals("C")) { + + Parser parser = new Parser(PATTERN_C, sentence); + if (!parser.matches()) { + return null; + } + + decodeFix(position, parser); + decodeState(position, parser); + + return position; + + } else if (type.equals("A")) { + + Parser parser = new Parser(PATTERN_A, sentence); + if (!parser.matches()) { + return null; + } + + decodeFix(position, parser); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/MiniFinderProtocolEncoder.java b/src/main/java/org/traccar/protocol/MiniFinderProtocolEncoder.java new file mode 100644 index 000000000..059f688f7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MiniFinderProtocolEncoder.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import java.util.TimeZone; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class MiniFinderProtocolEncoder extends StringProtocolEncoder implements StringProtocolEncoder.ValueFormatter { + + public MiniFinderProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + public String formatValue(String key, Object value) { + switch (key) { + case Command.KEY_ENABLE: + return (Boolean) value ? "1" : "0"; + case Command.KEY_TIMEZONE: + return String.format("%+03d", TimeZone.getTimeZone((String) value).getRawOffset() / 3600000); + case Command.KEY_INDEX: + switch (((Number) value).intValue()) { + case 0: + return "A"; + case 1: + return "B"; + case 2: + return "C"; + default: + return null; + } + default: + return null; + } + } + + @Override + protected Object encodeCommand(Command command) { + + initDevicePassword(command, "123456"); + + switch (command.getType()) { + case Command.TYPE_SET_TIMEZONE: + return formatCommand(command, "%sL%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_TIMEZONE); + case Command.TYPE_VOICE_MONITORING: + return formatCommand(command, "%sP%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); + case Command.TYPE_ALARM_SPEED: + return formatCommand(command, "%sJ1%s", Command.KEY_DEVICE_PASSWORD, Command.KEY_DATA); + case Command.TYPE_ALARM_GEOFENCE: + return formatCommand(command, "%sR1%s", Command.KEY_DEVICE_PASSWORD, Command.KEY_RADIUS); + case Command.TYPE_ALARM_VIBRATION: + return formatCommand(command, "%sW1,%s", Command.KEY_DEVICE_PASSWORD, Command.KEY_DATA); + case Command.TYPE_SET_AGPS: + return formatCommand(command, "%sAGPS%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); + case Command.TYPE_ALARM_FALL: + return formatCommand(command, "%sF%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); + case Command.TYPE_MODE_POWER_SAVING: + return formatCommand(command, "%sSP%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); + case Command.TYPE_MODE_DEEP_SLEEP: + return formatCommand(command, "%sDS%s", this, Command.KEY_DEVICE_PASSWORD, Command.KEY_ENABLE); + case Command.TYPE_SOS_NUMBER: + return formatCommand(command, "%s%s1,%s", this, + Command.KEY_DEVICE_PASSWORD, Command.KEY_INDEX, Command.KEY_PHONE); + case Command.TYPE_SET_INDICATOR: + return formatCommand(command, "%sLED%s", Command.KEY_DEVICE_PASSWORD, Command.KEY_DATA); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Minifinder2Protocol.java b/src/main/java/org/traccar/protocol/Minifinder2Protocol.java new file mode 100644 index 000000000..957b8f4d0 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Minifinder2Protocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; + +public class Minifinder2Protocol extends BaseProtocol { + + public Minifinder2Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 2, 4, 0, true)); + pipeline.addLast(new Minifinder2ProtocolDecoder(Minifinder2Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java new file mode 100644 index 000000000..b8ab134c5 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Minifinder2ProtocolDecoder.java @@ -0,0 +1,196 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class Minifinder2ProtocolDecoder extends BaseProtocolDecoder { + + public Minifinder2ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_DATA = 0x01; + public static final int MSG_RESPONSE = 0x7F; + + private String decodeAlarm(int code) { + if (BitUtil.check(code, 0)) { + return Position.ALARM_LOW_BATTERY; + } + if (BitUtil.check(code, 1)) { + return Position.ALARM_OVERSPEED; + } + if (BitUtil.check(code, 2)) { + return Position.ALARM_FALL_DOWN; + } + if (BitUtil.check(code, 8)) { + return Position.ALARM_POWER_OFF; + } + if (BitUtil.check(code, 9)) { + return Position.ALARM_POWER_ON; + } + if (BitUtil.check(code, 12)) { + return Position.ALARM_SOS; + } + return null; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // header + int flags = buf.readUnsignedByte(); + buf.readUnsignedShortLE(); // length + buf.readUnsignedShortLE(); // checksum + int index = buf.readUnsignedShortLE(); + int type = buf.readUnsignedByte(); + + if (BitUtil.check(flags, 4) && channel != null) { + + ByteBuf content = Unpooled.buffer(); + content.writeByte(MSG_RESPONSE); + content.writeByte(1); // key length + content.writeByte(0); // success + + ByteBuf response = Unpooled.buffer(); + response.writeByte(0xAB); // header + response.writeByte(0x00); // properties + response.writeShortLE(content.readableBytes()); + response.writeShortLE(Checksum.crc16(Checksum.CRC16_XMODEM, content.nioBuffer())); + response.writeShortLE(index); + response.writeBytes(content); + content.release(); + + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + if (type == MSG_DATA) { + + Position position = new Position(getProtocolName()); + + while (buf.isReadable()) { + int endIndex = buf.readUnsignedByte() + buf.readerIndex(); + int key = buf.readUnsignedByte(); + switch (key) { + case 0x01: + DeviceSession deviceSession = getDeviceSession( + channel, remoteAddress, buf.readCharSequence(15, StandardCharsets.US_ASCII).toString()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + break; + case 0x02: + position.set(Position.KEY_ALARM, decodeAlarm(buf.readIntLE())); + break; + case 0x14: + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); + break; + case 0x20: + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setLongitude(buf.readIntLE() * 0.0000001); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE())); + position.setCourse(buf.readUnsignedShortLE()); + position.setAltitude(buf.readShortLE()); + position.setValid(buf.readUnsignedShortLE() > 0); + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + break; + case 0x21: + int mcc = buf.readUnsignedShortLE(); + int mnc = buf.readUnsignedByte(); + if (position.getNetwork() == null) { + position.setNetwork(new Network()); + } + while (buf.readerIndex() < endIndex) { + int rssi = buf.readByte(); + position.getNetwork().addCellTower(CellTower.from( + mcc, mnc, buf.readUnsignedShortLE(), buf.readUnsignedShortLE(), rssi)); + } + break; + case 0x22: + if (position.getNetwork() == null) { + position.setNetwork(new Network()); + } + while (buf.readerIndex() < endIndex) { + int rssi = buf.readByte(); + String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:"); + position.getNetwork().addWifiAccessPoint(WifiAccessPoint.from( + 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(); + position.set(Position.KEY_BATTERY_LEVEL, BitUtil.from(status, 24)); + position.set(Position.KEY_STATUS, status); + break; + case 0x40: + buf.readUnsignedIntLE(); // timestamp + int heartRate = buf.readUnsignedByte(); + if (heartRate > 1) { + position.set(Position.KEY_HEART_RATE, heartRate); + } + break; + default: + break; + } + buf.readerIndex(endIndex); + } + + if (!position.getAttributes().containsKey(Position.KEY_SATELLITES)) { + getLastLocation(position, null); + } + + return position.getDeviceId() > 0 ? position : null; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/MotorProtocol.java b/src/main/java/org/traccar/protocol/MotorProtocol.java new file mode 100644 index 000000000..680687e15 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MotorProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class MotorProtocol extends BaseProtocol { + + public MotorProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new MotorProtocolDecoder(MotorProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/MotorProtocolDecoder.java b/src/main/java/org/traccar/protocol/MotorProtocolDecoder.java new file mode 100644 index 000000000..8ce4fe8b1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MotorProtocolDecoder.java @@ -0,0 +1,85 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DataConverter; +import org.traccar.helper.DateBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class MotorProtocolDecoder extends BaseProtocolDecoder { + + public MotorProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(sentence)); + + String id = String.format("%08x", buf.readUnsignedIntLE()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.skipBytes(2); // divider + + position.set(Position.KEY_STATUS, buf.readUnsignedShortLE()); + + buf.skipBytes(2); // divider + buf.readUnsignedMediumLE(); // command + + int flags = buf.readUnsignedByte(); + position.setValid(BitUtil.check(flags, 7)); + if (BitUtil.check(flags, 0)) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + + position.setLatitude(BcdUtil.readInteger(buf, 2) + BcdUtil.readInteger(buf, 6) * 0.0001 / 60); + position.setLongitude(BcdUtil.readInteger(buf, 4) + BcdUtil.readInteger(buf, 6) * 0.0001 / 60); + position.setSpeed(BcdUtil.readInteger(buf, 4) * 0.1); + position.setCourse(BcdUtil.readInteger(buf, 4) * 0.1); + + position.setTime(new DateBuilder() + .setYear(BcdUtil.readInteger(buf, 2)) + .setMonth(BcdUtil.readInteger(buf, 2)) + .setDay(BcdUtil.readInteger(buf, 2)) + .setHour(BcdUtil.readInteger(buf, 2)) + .setMinute(BcdUtil.readInteger(buf, 2)) + .setSecond(BcdUtil.readInteger(buf, 2)).getDate()); + + position.set(Position.KEY_RSSI, BcdUtil.readInteger(buf, 2)); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/Mta6Protocol.java b/src/main/java/org/traccar/protocol/Mta6Protocol.java new file mode 100644 index 000000000..632a7df80 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Mta6Protocol.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.Context; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Mta6Protocol extends BaseProtocol { + + public Mta6Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(65535)); + pipeline.addLast(new Mta6ProtocolDecoder( + Mta6Protocol.this, !Context.getConfig().getBoolean(getName() + ".can"))); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java new file mode 100644 index 000000000..88419b871 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Mta6ProtocolDecoder.java @@ -0,0 +1,319 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class Mta6ProtocolDecoder extends BaseProtocolDecoder { + + private static final Logger LOGGER = LoggerFactory.getLogger(Mta6ProtocolDecoder.class); + + private final boolean simple; + + public Mta6ProtocolDecoder(Protocol protocol, boolean simple) { + super(protocol); + this.simple = simple; + } + + private void sendContinue(Channel channel) { + FullHttpResponse response = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + + private void sendResponse(Channel channel, short packetId, short packetCount) { + ByteBuf begin = Unpooled.copiedBuffer("#ACK#", StandardCharsets.US_ASCII); + ByteBuf end = Unpooled.buffer(3); + end.writeByte(packetId); + end.writeByte(packetCount); + end.writeByte(0); + + FullHttpResponse response = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(begin, end)); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + + private static class FloatReader { + + private int previousFloat; + + public float readFloat(ByteBuf buf) { + switch (buf.getUnsignedByte(buf.readerIndex()) >> 6) { + case 0: + previousFloat = buf.readInt() << 2; + break; + case 1: + previousFloat = (previousFloat & 0xffffff00) + ((buf.readUnsignedByte() & 0x3f) << 2); + break; + case 2: + previousFloat = (previousFloat & 0xffff0000) + ((buf.readUnsignedShort() & 0x3fff) << 2); + break; + case 3: + previousFloat = (previousFloat & 0xff000000) + ((buf.readUnsignedMedium() & 0x3fffff) << 2); + break; + default: + LOGGER.warn("MTA6 float decoding error", new IllegalArgumentException()); + break; + } + return Float.intBitsToFloat(previousFloat); + } + + } + + private static class TimeReader extends FloatReader { + + private long weekNumber; + + public Date readTime(ByteBuf buf) { + long weekTime = (long) (readFloat(buf) * 1000); + if (weekNumber == 0) { + weekNumber = buf.readUnsignedShort(); + } + + DateBuilder dateBuilder = new DateBuilder().setDate(1980, 1, 6); + dateBuilder.addMillis(weekNumber * 7 * 24 * 60 * 60 * 1000 + weekTime); + + return dateBuilder.getDate(); + } + + } + + private List<Position> parseFormatA(DeviceSession deviceSession, ByteBuf buf) { + List<Position> positions = new LinkedList<>(); + + FloatReader latitudeReader = new FloatReader(); + FloatReader longitudeReader = new FloatReader(); + TimeReader timeReader = new TimeReader(); + + try { + while (buf.isReadable()) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + short flags = buf.readUnsignedByte(); + + short event = buf.readUnsignedByte(); + if (BitUtil.check(event, 7)) { + if (BitUtil.check(event, 6)) { + buf.skipBytes(8); + } else { + while (BitUtil.check(event, 7)) { + event = buf.readUnsignedByte(); + } + } + } + + position.setLatitude(latitudeReader.readFloat(buf) / Math.PI * 180); + position.setLongitude(longitudeReader.readFloat(buf) / Math.PI * 180); + position.setTime(timeReader.readTime(buf)); + + if (BitUtil.check(flags, 0)) { + buf.readUnsignedByte(); // status + } + + if (BitUtil.check(flags, 1)) { + position.setAltitude(buf.readUnsignedShort()); + } + + if (BitUtil.check(flags, 2)) { + position.setSpeed(buf.readUnsignedShort() & 0x03ff); + position.setCourse(buf.readUnsignedByte()); + } + + if (BitUtil.check(flags, 3)) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedShort() * 1000); + } + + if (BitUtil.check(flags, 4)) { + position.set(Position.KEY_FUEL_CONSUMPTION + "Accumulator1", buf.readUnsignedInt()); + position.set(Position.KEY_FUEL_CONSUMPTION + "Accumulator2", buf.readUnsignedInt()); + position.set("hours1", buf.readUnsignedShort()); + position.set("hours2", buf.readUnsignedShort()); + } + + if (BitUtil.check(flags, 5)) { + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort() & 0x03ff); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShort() & 0x03ff); + position.set(Position.PREFIX_ADC + 3, buf.readUnsignedShort() & 0x03ff); + position.set(Position.PREFIX_ADC + 4, buf.readUnsignedShort() & 0x03ff); + } + + if (BitUtil.check(flags, 6)) { + position.set(Position.PREFIX_TEMP + 1, buf.readByte()); + buf.getUnsignedByte(buf.readerIndex()); // control (>> 4) + position.set(Position.KEY_INPUT, buf.readUnsignedShort() & 0x0fff); + buf.readUnsignedShort(); // old sensor state (& 0x0fff) + } + + if (BitUtil.check(flags, 7)) { + position.set(Position.KEY_BATTERY, buf.getUnsignedByte(buf.readerIndex()) >> 2); + position.set(Position.KEY_POWER, buf.readUnsignedShort() & 0x03ff); + position.set(Position.KEY_DEVICE_TEMP, buf.readByte()); + + position.set(Position.KEY_RSSI, (buf.getUnsignedByte(buf.readerIndex()) >> 4) & 0x07); + + int satellites = buf.readUnsignedByte() & 0x0f; + position.setValid(satellites >= 3); + position.set(Position.KEY_SATELLITES, satellites); + } + positions.add(position); + } + } catch (IndexOutOfBoundsException error) { + LOGGER.warn("MTA6 parsing error", error); + } + + return positions; + } + + private Position parseFormatA1(DeviceSession deviceSession, ByteBuf buf) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + short flags = buf.readUnsignedByte(); + + // Skip events + short event = buf.readUnsignedByte(); + if (BitUtil.check(event, 7)) { + if (BitUtil.check(event, 6)) { + buf.skipBytes(8); + } else { + while (BitUtil.check(event, 7)) { + event = buf.readUnsignedByte(); + } + } + } + + position.setLatitude(new FloatReader().readFloat(buf) / Math.PI * 180); + position.setLongitude(new FloatReader().readFloat(buf) / Math.PI * 180); + position.setTime(new TimeReader().readTime(buf)); + + position.set(Position.KEY_STATUS, buf.readUnsignedByte()); + + if (BitUtil.check(flags, 0)) { + position.setAltitude(buf.readUnsignedShort()); + position.setSpeed(buf.readUnsignedByte()); + position.setCourse(buf.readByte()); + position.set(Position.KEY_ODOMETER, new FloatReader().readFloat(buf)); + } + + if (BitUtil.check(flags, 1)) { + position.set(Position.KEY_FUEL_CONSUMPTION, new FloatReader().readFloat(buf)); + position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(new FloatReader().readFloat(buf))); + position.set("tank", buf.readUnsignedByte() * 0.4); + } + + if (BitUtil.check(flags, 2)) { + position.set("engine", buf.readUnsignedShort() * 0.125); + position.set("pedals", buf.readUnsignedByte()); + position.set(Position.PREFIX_TEMP + 1, buf.readUnsignedByte() - 40); + position.set(Position.KEY_ODOMETER_SERVICE, buf.readUnsignedShort()); + } + + if (BitUtil.check(flags, 3)) { + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 3, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 4, buf.readUnsignedShort()); + } + + if (BitUtil.check(flags, 4)) { + position.set(Position.PREFIX_TEMP + 1, buf.readByte()); + buf.getUnsignedByte(buf.readerIndex()); // control (>> 4) + position.set(Position.KEY_INPUT, buf.readUnsignedShort() & 0x0fff); + buf.readUnsignedShort(); // old sensor state (& 0x0fff) + } + + if (BitUtil.check(flags, 5)) { + position.set(Position.KEY_BATTERY, buf.getUnsignedByte(buf.readerIndex()) >> 2); + position.set(Position.KEY_POWER, buf.readUnsignedShort() & 0x03ff); + position.set(Position.KEY_DEVICE_TEMP, buf.readByte()); + + position.set(Position.KEY_RSSI, buf.getUnsignedByte(buf.readerIndex()) >> 5); + + int satellites = buf.readUnsignedByte() & 0x1f; + position.setValid(satellites >= 3); + position.set(Position.KEY_SATELLITES, satellites); + } + + // other data + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + ByteBuf buf = request.content(); + + buf.skipBytes("id=".length()); + int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '&'); + String uniqueId = buf.toString(buf.readerIndex(), index - buf.readerIndex(), StandardCharsets.US_ASCII); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, uniqueId); + if (deviceSession == null) { + return null; + } + buf.skipBytes(uniqueId.length()); + buf.skipBytes("&bin=".length()); + + short packetId = buf.readUnsignedByte(); + short offset = buf.readUnsignedByte(); // dataOffset + short packetCount = buf.readUnsignedByte(); + buf.readUnsignedByte(); // reserved + buf.readUnsignedByte(); // timezone + buf.skipBytes(offset - 5); + + if (channel != null) { + sendContinue(channel); + sendResponse(channel, packetId, packetCount); + } + + if (packetId == 0x31 || packetId == 0x32 || packetId == 0x36) { + if (simple) { + return parseFormatA1(deviceSession, buf); + } else { + return parseFormatA(deviceSession, buf); + } + } // else if (0x34 0x38 0x4F 0x59) + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/MtxProtocol.java b/src/main/java/org/traccar/protocol/MtxProtocol.java new file mode 100644 index 000000000..44372ce83 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MtxProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class MtxProtocol extends BaseProtocol { + + public MtxProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new MtxProtocolDecoder(MtxProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/MtxProtocolDecoder.java b/src/main/java/org/traccar/protocol/MtxProtocolDecoder.java new file mode 100644 index 000000000..d1207bedf --- /dev/null +++ b/src/main/java/org/traccar/protocol/MtxProtocolDecoder.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class MtxProtocolDecoder extends BaseProtocolDecoder { + + public MtxProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("#MTX,") + .number("(d+),") // imei + .number("(dddd)(dd)(dd),") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(d+.?d*),") // speed + .number("(d+),") // course + .number("(d+.?d*),") // odometer + .groupBegin() + .number("d+") + .or() + .text("X") + .groupEnd() + .text(",") + .expression("(?:[01]|X),") + .expression("([01]+),") // input + .expression("([01]+),") // output + .number("(d+),") // adc1 + .number("(d+)") // adc2 + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("#ACK", remoteAddress)); + } + + Parser parser = new Parser(PATTERN, (String) msg); + 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.setTime(parser.nextDateTime()); + + position.setValid(true); + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + position.set(Position.KEY_INPUT, parser.next()); + position.set(Position.KEY_OUTPUT, parser.next()); + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.PREFIX_ADC + 2, parser.next()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/MxtFrameDecoder.java b/src/main/java/org/traccar/protocol/MxtFrameDecoder.java new file mode 100644 index 000000000..d70e92da1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MxtFrameDecoder.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class MxtFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 2) { + return null; + } + + int index = buf.indexOf(buf.readerIndex() + 1, buf.writerIndex(), (byte) 0x04); + if (index != -1) { + ByteBuf result = Unpooled.buffer(index + 1 - buf.readerIndex()); + + while (buf.readerIndex() <= index) { + int b = buf.readUnsignedByte(); + if (b == 0x10) { + result.writeByte(buf.readUnsignedByte() - 0x20); + } else { + result.writeByte(b); + } + } + + return result; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/MxtProtocol.java b/src/main/java/org/traccar/protocol/MxtProtocol.java new file mode 100644 index 000000000..dbe43fe45 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MxtProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class MxtProtocol extends BaseProtocol { + + public MxtProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new MxtFrameDecoder()); + pipeline.addLast(new MxtProtocolDecoder(MxtProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/MxtProtocolDecoder.java b/src/main/java/org/traccar/protocol/MxtProtocolDecoder.java new file mode 100644 index 000000000..7bde85f87 --- /dev/null +++ b/src/main/java/org/traccar/protocol/MxtProtocolDecoder.java @@ -0,0 +1,175 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class MxtProtocolDecoder extends BaseProtocolDecoder { + + public MxtProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_ACK = 0x02; + public static final int MSG_NACK = 0x03; + public static final int MSG_POSITION = 0x31; + + private static void sendResponse(Channel channel, int device, long id, int crc) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(device); + response.writeByte(MSG_ACK); + response.writeIntLE((int) id); + response.writeShortLE(crc); + response.writeShortLE(Checksum.crc16( + Checksum.CRC16_XMODEM, response.nioBuffer())); + + ByteBuf encoded = Unpooled.buffer(); + encoded.writeByte(0x01); // header + while (response.isReadable()) { + int b = response.readByte(); + if (b == 0x01 || b == 0x04 || b == 0x10 || b == 0x11 || b == 0x13) { + encoded.writeByte(0x10); + b += 0x20; + } + encoded.writeByte(b); + } + response.release(); + encoded.writeByte(0x04); // ending + channel.writeAndFlush(new NetworkMessage(encoded, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // start + int device = buf.readUnsignedByte(); // device descriptor + int type = buf.readUnsignedByte(); + + long id = buf.readUnsignedIntLE(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(id)); + if (deviceSession == null) { + return null; + } + + if (type == MSG_POSITION) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedByte(); // protocol + int infoGroups = buf.readUnsignedByte(); + + position.set(Position.KEY_INDEX, buf.readUnsignedShortLE()); + + DateBuilder dateBuilder = new DateBuilder().setDate(2000, 1, 1); + + long date = buf.readUnsignedIntLE(); + + long days = BitUtil.from(date, 6 + 6 + 5); + long hours = BitUtil.between(date, 6 + 6, 6 + 6 + 5); + long minutes = BitUtil.between(date, 6, 6 + 6); + long seconds = BitUtil.to(date, 6); + + dateBuilder.addMillis((((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000); + + position.setTime(dateBuilder.getDate()); + + position.setValid(true); + position.setLatitude(buf.readIntLE() / 1000000.0); + position.setLongitude(buf.readIntLE() / 1000000.0); + + long flags = buf.readUnsignedIntLE(); + position.set(Position.KEY_IGNITION, BitUtil.check(flags, 0)); + if (BitUtil.check(flags, 1)) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + position.set(Position.KEY_INPUT, BitUtil.between(flags, 2, 7)); + position.set(Position.KEY_OUTPUT, BitUtil.between(flags, 7, 10)); + position.setCourse(BitUtil.between(flags, 10, 13) * 45); + // position.setValid(BitUtil.check(flags, 15)); + position.set(Position.KEY_CHARGE, BitUtil.check(flags, 20)); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + + buf.readUnsignedByte(); // input mask + + if (BitUtil.check(infoGroups, 0)) { + buf.skipBytes(8); // waypoints + } + + if (BitUtil.check(infoGroups, 1)) { + buf.skipBytes(8); // wireless accessory + } + + if (BitUtil.check(infoGroups, 2)) { + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_HDOP, buf.readUnsignedByte()); + position.setAccuracy(buf.readUnsignedByte()); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + buf.readUnsignedShortLE(); // time since boot + position.set(Position.KEY_POWER, buf.readUnsignedByte()); + position.set(Position.PREFIX_TEMP + 1, buf.readByte()); + } + + if (BitUtil.check(infoGroups, 3)) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + } + + if (BitUtil.check(infoGroups, 4)) { + position.set(Position.KEY_HOURS, UnitsConverter.msFromMinutes(buf.readUnsignedIntLE())); + } + + if (BitUtil.check(infoGroups, 5)) { + buf.readUnsignedIntLE(); // reason + } + + if (BitUtil.check(infoGroups, 6)) { + position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.001); + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE()); + } + + if (BitUtil.check(infoGroups, 7)) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(buf.readUnsignedIntLE())); + } + + buf.readerIndex(buf.writerIndex() - 3); + sendResponse(channel, device, id, buf.readUnsignedShortLE()); + + return position; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/NavigilFrameDecoder.java b/src/main/java/org/traccar/protocol/NavigilFrameDecoder.java new file mode 100644 index 000000000..e8b6bea52 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NavigilFrameDecoder.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class NavigilFrameDecoder extends BaseFrameDecoder { + + private static final int MESSAGE_HEADER = 20; + private static final long PREAMBLE = 0x2477F5F6; + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + // Check minimum length + if (buf.readableBytes() < MESSAGE_HEADER) { + return null; + } + + // Check for preamble + boolean hasPreamble = false; + if (buf.getUnsignedIntLE(buf.readerIndex()) == PREAMBLE) { + hasPreamble = true; + } + + // Check length and return buffer + int length = buf.getUnsignedShortLE(buf.readerIndex() + 6); + if (buf.readableBytes() >= length) { + if (hasPreamble) { + buf.readUnsignedIntLE(); + length -= 4; + } + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/NavigilProtocol.java b/src/main/java/org/traccar/protocol/NavigilProtocol.java new file mode 100644 index 000000000..2c946c39f --- /dev/null +++ b/src/main/java/org/traccar/protocol/NavigilProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class NavigilProtocol extends BaseProtocol { + + public NavigilProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new NavigilFrameDecoder()); + pipeline.addLast(new NavigilProtocolDecoder(NavigilProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/NavigilProtocolDecoder.java b/src/main/java/org/traccar/protocol/NavigilProtocolDecoder.java new file mode 100644 index 000000000..db5521201 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NavigilProtocolDecoder.java @@ -0,0 +1,308 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; + +public class NavigilProtocolDecoder extends BaseProtocolDecoder { + + public NavigilProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final int LEAP_SECONDS_DELTA = 25; + + public static final int MSG_ERROR = 2; + public static final int MSG_INDICATION = 4; + public static final int MSG_CONN_OPEN = 5; + public static final int MSG_CONN_CLOSE = 6; + public static final int MSG_SYSTEM_REPORT = 7; + public static final int MSG_UNIT_REPORT = 8; + public static final int MSG_GEOFENCE_ALARM = 10; + public static final int MSG_INPUT_ALARM = 11; + public static final int MSG_TG2_REPORT = 12; + public static final int MSG_POSITION_REPORT = 13; + public static final int MSG_POSITION_REPORT_2 = 15; + public static final int MSG_SNAPSHOT4 = 17; + public static final int MSG_TRACKING_DATA = 18; + public static final int MSG_MOTION_ALARM = 19; + public static final int MSG_ACKNOWLEDGEMENT = 255; + + private static Date convertTimestamp(long timestamp) { + return new Date((timestamp - LEAP_SECONDS_DELTA) * 1000); + } + + private int senderSequenceNumber = 1; + + private void sendAcknowledgment(Channel channel, int sequenceNumber) { + ByteBuf data = Unpooled.buffer(4); + data.writeShortLE(sequenceNumber); + data.writeShortLE(0); // OK + + ByteBuf header = Unpooled.buffer(20); + header.writeByte(1); header.writeByte(0); + header.writeShortLE(senderSequenceNumber++); + header.writeShortLE(MSG_ACKNOWLEDGEMENT); + header.writeShortLE(header.capacity() + data.capacity()); + header.writeShortLE(0); + header.writeShortLE(Checksum.crc16(Checksum.CRC16_CCITT_FALSE, data.nioBuffer())); + header.writeIntLE(0); + header.writeIntLE((int) (System.currentTimeMillis() / 1000) + LEAP_SECONDS_DELTA); + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(Unpooled.wrappedBuffer(header, data), channel.remoteAddress())); + } + } + + private Position parseUnitReport( + DeviceSession deviceSession, ByteBuf buf, int sequenceNumber) { + Position position = new Position(getProtocolName()); + + position.setValid(true); + position.set(Position.KEY_INDEX, sequenceNumber); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedShortLE(); // report trigger + position.set(Position.KEY_FLAGS, buf.readUnsignedShortLE()); + + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setLongitude(buf.readIntLE() * 0.0000001); + position.setAltitude(buf.readUnsignedShortLE()); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedShortLE()); + position.set(Position.KEY_SATELLITES_VISIBLE, buf.readUnsignedShortLE()); + position.set("gpsAntennaState", buf.readUnsignedShortLE()); + + position.setSpeed(buf.readUnsignedShortLE() * 0.194384); + position.setCourse(buf.readUnsignedShortLE()); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + position.set(Position.KEY_DISTANCE, buf.readUnsignedIntLE()); + + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); + + position.set(Position.KEY_CHARGE, buf.readUnsignedShortLE()); + + position.setTime(convertTimestamp(buf.readUnsignedIntLE())); + + return position; + } + + private Position parseTg2Report( + DeviceSession deviceSession, ByteBuf buf, int sequenceNumber) { + Position position = new Position(getProtocolName()); + + position.setValid(true); + position.set(Position.KEY_INDEX, sequenceNumber); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedShortLE(); // report trigger + buf.readUnsignedByte(); // reserved + buf.readUnsignedByte(); // assisted GPS age + + position.setTime(convertTimestamp(buf.readUnsignedIntLE())); + + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setLongitude(buf.readIntLE() * 0.0000001); + position.setAltitude(buf.readUnsignedShortLE()); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_SATELLITES_VISIBLE, buf.readUnsignedByte()); + + position.setSpeed(buf.readUnsignedShortLE() * 0.194384); + position.setCourse(buf.readUnsignedShortLE()); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + position.set("maximumSpeed", buf.readUnsignedShortLE()); + position.set("minimumSpeed", buf.readUnsignedShortLE()); + + position.set(Position.PREFIX_IO + 1, buf.readUnsignedShortLE()); // VSAUT1 voltage + position.set(Position.PREFIX_IO + 2, buf.readUnsignedShortLE()); // VSAUT2 voltage + position.set(Position.PREFIX_IO + 3, buf.readUnsignedShortLE()); // solar voltage + + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); + + return position; + } + + private Position parsePositionReport( + DeviceSession deviceSession, ByteBuf buf, int sequenceNumber, long timestamp) { + Position position = new Position(getProtocolName()); + + position.set(Position.KEY_INDEX, sequenceNumber); + position.setDeviceId(deviceSession.getDeviceId()); + position.setTime(convertTimestamp(timestamp)); + + position.setLatitude(buf.readMediumLE() * 0.00002); + position.setLongitude(buf.readMediumLE() * 0.00002); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.setCourse(buf.readUnsignedByte() * 2); + + short flags = buf.readUnsignedByte(); + position.setValid((flags & 0x80) == 0x80 && (flags & 0x40) == 0x40); + + buf.readUnsignedByte(); // reserved + + return position; + } + + private Position parsePositionReport2( + DeviceSession deviceSession, ByteBuf buf, int sequenceNumber, long timestamp) { + Position position = new Position(getProtocolName()); + + position.set(Position.KEY_INDEX, sequenceNumber); + position.setDeviceId(deviceSession.getDeviceId()); + position.setTime(convertTimestamp(timestamp)); + + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setLongitude(buf.readIntLE() * 0.0000001); + + buf.readUnsignedByte(); // report trigger + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + + short flags = buf.readUnsignedByte(); + position.setValid((flags & 0x80) == 0x80 && (flags & 0x40) == 0x40); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + + return position; + } + + private Position parseSnapshot4( + DeviceSession deviceSession, ByteBuf buf, int sequenceNumber) { + Position position = new Position(getProtocolName()); + + position.set(Position.KEY_INDEX, sequenceNumber); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedByte(); // report trigger + buf.readUnsignedByte(); // position fix source + buf.readUnsignedByte(); // GNSS fix quality + buf.readUnsignedByte(); // GNSS assistance age + + long flags = buf.readUnsignedIntLE(); + position.setValid((flags & 0x0400) == 0x0400); + + position.setTime(convertTimestamp(buf.readUnsignedIntLE())); + + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setLongitude(buf.readIntLE() * 0.0000001); + position.setAltitude(buf.readUnsignedShortLE()); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_SATELLITES_VISIBLE, buf.readUnsignedByte()); + + position.setSpeed(buf.readUnsignedShortLE() * 0.194384); + position.setCourse(buf.readUnsignedShortLE() * 0.1); + + position.set("maximumSpeed", buf.readUnsignedByte()); + position.set("minimumSpeed", buf.readUnsignedByte()); + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + + position.set(Position.PREFIX_IO + 1, buf.readUnsignedByte()); // supply voltage 1 + position.set(Position.PREFIX_IO + 2, buf.readUnsignedByte()); // supply voltage 2 + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); + + return position; + } + + private Position parseTrackingData( + DeviceSession deviceSession, ByteBuf buf, int sequenceNumber, long timestamp) { + Position position = new Position(getProtocolName()); + + position.set(Position.KEY_INDEX, sequenceNumber); + position.setDeviceId(deviceSession.getDeviceId()); + position.setTime(convertTimestamp(timestamp)); + + buf.readUnsignedByte(); // tracking mode + + short flags = buf.readUnsignedByte(); + position.setValid((flags & 0x01) == 0x01); + + buf.readUnsignedShortLE(); // duration + + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setLongitude(buf.readIntLE() * 0.0000001); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.setCourse(buf.readUnsignedByte() * 2.0); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // protocol version + buf.readUnsignedByte(); // version id + int sequenceNumber = buf.readUnsignedShortLE(); + int messageId = buf.readUnsignedShortLE(); + buf.readUnsignedShortLE(); // length + int flags = buf.readUnsignedShortLE(); + buf.readUnsignedShortLE(); // checksum + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(buf.readUnsignedIntLE())); + if (deviceSession == null) { + return null; + } + + long timestamp = buf.readUnsignedIntLE(); + + if ((flags & 0x1) == 0x0) { + sendAcknowledgment(channel, sequenceNumber); + } + + switch (messageId) { + case MSG_UNIT_REPORT: + return parseUnitReport(deviceSession, buf, sequenceNumber); + case MSG_TG2_REPORT: + return parseTg2Report(deviceSession, buf, sequenceNumber); + case MSG_POSITION_REPORT: + return parsePositionReport(deviceSession, buf, sequenceNumber, timestamp); + case MSG_POSITION_REPORT_2: + return parsePositionReport2(deviceSession, buf, sequenceNumber, timestamp); + case MSG_SNAPSHOT4: + return parseSnapshot4(deviceSession, buf, sequenceNumber); + case MSG_TRACKING_DATA: + return parseTrackingData(deviceSession, buf, sequenceNumber, timestamp); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/NavisFrameDecoder.java b/src/main/java/org/traccar/protocol/NavisFrameDecoder.java new file mode 100644 index 000000000..8a0bb0b9a --- /dev/null +++ b/src/main/java/org/traccar/protocol/NavisFrameDecoder.java @@ -0,0 +1,109 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import java.nio.charset.StandardCharsets; +import org.traccar.BaseFrameDecoder; +import org.traccar.BasePipelineFactory; + +public class NavisFrameDecoder extends BaseFrameDecoder { + + private static final int NTCB_HEADER_LENGTH = 16; + private static final int NTCB_LENGTH_OFFSET = 12; + private static final int FLEX_HEADER_LENGTH = 2; + + private int flexDataSize; + + public void setFlexDataSize(int flexDataSize) { + this.flexDataSize = flexDataSize; + } + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.getByte(buf.readerIndex()) == 0x7F) { + return buf.readRetainedSlice(1); // keep alive + } + + if (ctx != null && flexDataSize == 0) { + NavisProtocolDecoder protocolDecoder = + BasePipelineFactory.getHandler(ctx.pipeline(), NavisProtocolDecoder.class); + if (protocolDecoder != null) { + flexDataSize = protocolDecoder.getFlexDataSize(); + } + } + + if (flexDataSize > 0) { + + if (buf.readableBytes() > FLEX_HEADER_LENGTH) { + int length = 0; + String type = buf.toString(buf.readerIndex(), 2, StandardCharsets.US_ASCII); + switch (type) { + // FLEX 1.0 + case "~A": + length = flexDataSize * buf.getByte(buf.readerIndex() + FLEX_HEADER_LENGTH) + 1 + 1; + break; + case "~T": + length = flexDataSize + 4 + 1; + break; + case "~C": + length = flexDataSize + 1; + break; + // FLEX 2.0 (Extra packages) + case "~E": + length++; + for (int i = 0; i < buf.getByte(buf.readerIndex() + FLEX_HEADER_LENGTH); i++) { + if (buf.readableBytes() > FLEX_HEADER_LENGTH + length + 1) { + length += buf.getUnsignedShort(length + FLEX_HEADER_LENGTH) + 2; + } else { + return null; + } + } + length++; + break; + case "~X": + length = buf.getUnsignedShortLE(buf.readerIndex() + FLEX_HEADER_LENGTH) + 4 + 1; + break; + default: + return null; + } + + if (buf.readableBytes() >= FLEX_HEADER_LENGTH + length) { + return buf.readRetainedSlice(buf.readableBytes()); + } + } + + } else { + + if (buf.readableBytes() < NTCB_HEADER_LENGTH) { + return null; + } + + int length = NTCB_HEADER_LENGTH + buf.getUnsignedShortLE(buf.readerIndex() + NTCB_LENGTH_OFFSET); + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/NavisProtocol.java b/src/main/java/org/traccar/protocol/NavisProtocol.java new file mode 100644 index 000000000..d5af6838d --- /dev/null +++ b/src/main/java/org/traccar/protocol/NavisProtocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class NavisProtocol extends BaseProtocol { + + public NavisProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new NavisFrameDecoder()); + pipeline.addLast(new NavisProtocolDecoder(NavisProtocol.this)); + } + }); + } +} diff --git a/src/main/java/org/traccar/protocol/NavisProtocolDecoder.java b/src/main/java/org/traccar/protocol/NavisProtocolDecoder.java new file mode 100644 index 000000000..7ba474ae0 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NavisProtocolDecoder.java @@ -0,0 +1,683 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.Checksum.Algorithm; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; +import java.util.Date; + +public class NavisProtocolDecoder extends BaseProtocolDecoder { + + private static final int[] FLEX_FIELDS_SIZES = { + 4, 2, 4, 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 2, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 4, 4, 2, 2, + 4, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 1, 4, 2, 2, 2, 2, 2, 1, 1, 1, 2, 4, 2, 1, + /* FLEX 2.0 */ + 8, 2, 1, 16, 4, 2, 4, 37, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 12, 24, 48, 1, 1, 1, 1, 4, 4, + 1, 4, 2, 6, 2, 6, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 1 + }; + + private String prefix; + private long deviceUniqueId, serverId; + private int flexDataSize; + private int flexBitFieldSize; + private final byte[] flexBitField = new byte[16]; + + public NavisProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int F10 = 0x01; + public static final int F20 = 0x02; + public static final int F30 = 0x03; + public static final int F40 = 0x04; + public static final int F50 = 0x05; + public static final int F51 = 0x15; + public static final int F52 = 0x25; + public static final int F60 = 0x06; + + public int getFlexDataSize() { + return flexDataSize; + } + + private static boolean isFormat(int type, int... types) { + for (int i : types) { + if (type == i) { + return true; + } + } + return false; + } + + private Position parseNtcbPosition(DeviceSession deviceSession, ByteBuf buf) { + Position position = new Position(getProtocolName()); + + position.setDeviceId(deviceSession.getDeviceId()); + + int format; + if (buf.getUnsignedByte(buf.readerIndex()) == 0) { + format = buf.readUnsignedShortLE(); + } else { + format = buf.readUnsignedByte(); + } + position.set("format", format); + + position.set(Position.KEY_INDEX, buf.readUnsignedIntLE()); + position.set(Position.KEY_EVENT, buf.readUnsignedShortLE()); + + buf.skipBytes(6); // event time + + short armedStatus = buf.readUnsignedByte(); + if (isFormat(format, F10, F20, F30, F40, F50, F51, F52)) { + position.set(Position.KEY_ARMED, BitUtil.to(armedStatus, 7)); + if (BitUtil.check(armedStatus, 7)) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + } else if (isFormat(format, F60)) { + position.set(Position.KEY_ARMED, BitUtil.check(armedStatus, 0)); + if (BitUtil.check(armedStatus, 1)) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + } + + position.set(Position.KEY_STATUS, buf.readUnsignedByte()); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + + if (isFormat(format, F10, F20, F30)) { + int output = buf.readUnsignedShortLE(); + position.set(Position.KEY_OUTPUT, output); + for (int i = 0; i < 16; i++) { + position.set(Position.PREFIX_OUT + (i + 1), BitUtil.check(output, i)); + } + } else if (isFormat(format, F50, F51, F52)) { + short extField = buf.readUnsignedByte(); + position.set(Position.KEY_OUTPUT, BitUtil.to(extField, 2)); + position.set(Position.PREFIX_OUT + 1, BitUtil.check(extField, 0)); + position.set(Position.PREFIX_OUT + 2, BitUtil.check(extField, 1)); + position.set(Position.KEY_SATELLITES, BitUtil.from(extField, 2)); + } else if (isFormat(format, F40, F60)) { + short output = buf.readUnsignedByte(); + position.set(Position.KEY_OUTPUT, BitUtil.to(output, 4)); + for (int i = 0; i < 4; i++) { + position.set(Position.PREFIX_OUT + (i + 1), BitUtil.check(output, i)); + } + } + + if (isFormat(format, F10, F20, F30, F40)) { + int input = buf.readUnsignedShortLE(); + position.set(Position.KEY_INPUT, input); + if (!isFormat(format, F40)) { + for (int i = 0; i < 16; i++) { + position.set(Position.PREFIX_IN + (i + 1), BitUtil.check(input, i)); + } + } else { + position.set(Position.PREFIX_IN + 1, BitUtil.check(input, 0)); + position.set(Position.PREFIX_IN + 2, BitUtil.check(input, 1)); + position.set(Position.PREFIX_IN + 3, BitUtil.check(input, 2)); + position.set(Position.PREFIX_IN + 4, BitUtil.check(input, 3)); + position.set(Position.PREFIX_IN + 5, BitUtil.between(input, 4, 7)); + position.set(Position.PREFIX_IN + 6, BitUtil.between(input, 7, 10)); + position.set(Position.PREFIX_IN + 7, BitUtil.between(input, 10, 12)); + position.set(Position.PREFIX_IN + 8, BitUtil.between(input, 12, 14)); + } + } else if (isFormat(format, F50, F51, F52, F60)) { + short input = buf.readUnsignedByte(); + position.set(Position.KEY_INPUT, input); + for (int i = 0; i < 8; i++) { + position.set(Position.PREFIX_IN + (i + 1), BitUtil.check(input, i)); + } + } + + position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.001); + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); + + if (isFormat(format, F10, F20, F30)) { + position.set(Position.PREFIX_TEMP + 1, buf.readShortLE()); + } + + if (isFormat(format, F10, F20, F50, F51, F52, F60)) { + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShortLE()); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShortLE()); + } + if (isFormat(format, F60)) { + position.set(Position.PREFIX_ADC + 3, buf.readUnsignedShortLE()); + } + + // Impulse counters + if (isFormat(format, F20, F50, F51, F52, F60)) { + buf.readUnsignedIntLE(); + buf.readUnsignedIntLE(); + } + + if (isFormat(format, F60)) { + // Fuel + buf.readUnsignedShortLE(); + buf.readUnsignedShortLE(); + buf.readByte(); + buf.readShortLE(); + buf.readByte(); + buf.readUnsignedShortLE(); + buf.readByte(); + buf.readUnsignedShortLE(); + buf.readByte(); + buf.readUnsignedShortLE(); + buf.readByte(); + buf.readUnsignedShortLE(); + buf.readByte(); + buf.readUnsignedShortLE(); + buf.readByte(); + buf.readUnsignedShortLE(); + buf.readByte(); + buf.readUnsignedShortLE(); + + position.set(Position.PREFIX_TEMP + 1, buf.readByte()); + position.set(Position.PREFIX_TEMP + 2, buf.readByte()); + position.set(Position.PREFIX_TEMP + 3, buf.readByte()); + position.set(Position.PREFIX_TEMP + 4, buf.readByte()); + position.set(Position.KEY_AXLE_WEIGHT, buf.readIntLE()); + position.set(Position.KEY_RPM, buf.readUnsignedShortLE()); + } + + if (isFormat(format, F20, F50, F51, F52, F60)) { + int navSensorState = buf.readUnsignedByte(); + position.setValid(BitUtil.check(navSensorState, 1)); + if (isFormat(format, F60)) { + position.set(Position.KEY_SATELLITES, BitUtil.from(navSensorState, 2)); + } + + DateBuilder dateBuilder = new DateBuilder() + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setDateReverse(buf.readUnsignedByte(), buf.readUnsignedByte() + 1, buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + if (isFormat(format, F60)) { + position.setLatitude(buf.readIntLE() / 600000.0); + position.setLongitude(buf.readIntLE() / 600000.0); + position.setAltitude(buf.readIntLE() * 0.1); + } else { + position.setLatitude(buf.readFloatLE() / Math.PI * 180); + position.setLongitude(buf.readFloatLE() / Math.PI * 180); + } + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readFloatLE())); + position.setCourse(buf.readUnsignedShortLE()); + + position.set(Position.KEY_ODOMETER, buf.readFloatLE() * 1000); + position.set(Position.KEY_DISTANCE, buf.readFloatLE() * 1000); + + // Segment times + buf.readUnsignedShortLE(); + buf.readUnsignedShortLE(); + } + + // Other + if (isFormat(format, F51, F52)) { + buf.readUnsignedShortLE(); + buf.readByte(); + buf.readUnsignedShortLE(); + buf.readUnsignedShortLE(); + buf.readByte(); + buf.readUnsignedShortLE(); + buf.readUnsignedShortLE(); + buf.readByte(); + buf.readUnsignedShortLE(); + } + + // Four temperature sensors + if (isFormat(format, F40, F52)) { + position.set(Position.PREFIX_TEMP + 1, buf.readByte()); + position.set(Position.PREFIX_TEMP + 2, buf.readByte()); + position.set(Position.PREFIX_TEMP + 3, buf.readByte()); + position.set(Position.PREFIX_TEMP + 4, buf.readByte()); + } + + return position; + } + + private Object processNtcbSingle(DeviceSession deviceSession, Channel channel, ByteBuf buf) { + Position position = parseNtcbPosition(deviceSession, buf); + + ByteBuf response = Unpooled.buffer(7); + response.writeCharSequence("*<T", StandardCharsets.US_ASCII); + response.writeIntLE((int) position.getLong(Position.KEY_INDEX)); + sendNtcbReply(channel, response); + + return position.getFixTime() != null ? position : null; + } + + private Object processNtcbArray(DeviceSession deviceSession, Channel channel, ByteBuf buf) { + List<Position> positions = new LinkedList<>(); + int count = buf.readUnsignedByte(); + + for (int i = 0; i < count; i++) { + Position position = parseNtcbPosition(deviceSession, buf); + if (position.getFixTime() != null) { + positions.add(position); + } + } + + ByteBuf response = Unpooled.buffer(7); + response.writeCharSequence("*<A", StandardCharsets.US_ASCII); + response.writeByte(count); + sendNtcbReply(channel, response); + + if (positions.isEmpty()) { + return null; + } + + return positions; + } + + private boolean checkFlexBitfield(int index) { + int byteIndex = Math.floorDiv(index, 8); + int bitIndex = Math.floorMod(index, 8); + return BitUtil.check(flexBitField[byteIndex], 7 - bitIndex); + } + + private Position parseFlexPosition(DeviceSession deviceSession, ByteBuf buf) { + + Position position = new Position(getProtocolName()); + + position.setDeviceId(deviceSession.getDeviceId()); + + int status = 0; + short input = 0; + short output = 0; + + for (int i = 0; i < flexBitFieldSize; i++) { + if (!checkFlexBitfield(i)) { + continue; + } + + switch (i) { + case 0: + position.set(Position.KEY_INDEX, buf.readUnsignedIntLE()); + break; + case 1: + position.set(Position.KEY_EVENT, buf.readUnsignedShortLE()); + break; + case 3: + short armedStatus = buf.readUnsignedByte(); + position.set(Position.KEY_ARMED, BitUtil.check(armedStatus, 0)); + if (BitUtil.check(armedStatus, 1)) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + break; + case 4: + status = buf.readUnsignedByte(); + position.set(Position.KEY_STATUS, status); + break; + case 5: + int status2 = buf.readUnsignedByte(); + position.set(Position.KEY_STATUS, (short) (BitUtil.to(status, 8) | (status2 << 8))); + break; + case 6: + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + break; + case 7: + int navSensorState = buf.readUnsignedByte(); + position.setValid(BitUtil.check(navSensorState, 1)); + position.set(Position.KEY_SATELLITES, BitUtil.from(navSensorState, 2)); + break; + case 8: + position.setTime(new DateBuilder(new Date(buf.readUnsignedIntLE() * 1000)).getDate()); + break; + case 9: + position.setLatitude(buf.readIntLE() / 600000.0); + break; + case 10: + position.setLongitude(buf.readIntLE() / 600000.0); + break; + case 11: + position.setAltitude(buf.readIntLE() * 0.1); + break; + case 12: + position.setSpeed(UnitsConverter.knotsFromKph(buf.readFloatLE())); + break; + case 13: + position.setCourse(buf.readUnsignedShortLE()); + break; + case 14: + position.set(Position.KEY_ODOMETER, buf.readFloatLE() * 1000); + break; + case 15: + position.set(Position.KEY_DISTANCE, buf.readFloatLE() * 1000); + break; + case 18: + position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.001); + break; + case 19: + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); + break; + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + position.set(Position.PREFIX_ADC + (i - 19), buf.readUnsignedShortLE()); + break; + case 28: + input = buf.readUnsignedByte(); + position.set(Position.KEY_INPUT, input); + for (int k = 0; k < 8; k++) { + position.set(Position.PREFIX_IN + (k + 1), BitUtil.check(input, k)); + } + break; + case 29: + short input2 = buf.readUnsignedByte(); + position.set(Position.KEY_INPUT, (short) (BitUtil.to(input, 8) | (input2 << 8))); + for (int k = 0; k < 8; k++) { + position.set(Position.PREFIX_IN + (k + 9), BitUtil.check(input2, k)); + } + break; + case 30: + output = buf.readUnsignedByte(); + position.set(Position.KEY_OUTPUT, output); + for (int k = 0; k < 8; k++) { + position.set(Position.PREFIX_OUT + (k + 1), BitUtil.check(output, k)); + } + break; + case 31: + short output2 = buf.readUnsignedByte(); + position.set(Position.KEY_OUTPUT, (short) (BitUtil.to(output, 8) | (output2 << 8))); + for (int k = 0; k < 8; k++) { + position.set(Position.PREFIX_OUT + (k + 9), BitUtil.check(output2, k)); + } + break; + case 36: + position.set(Position.KEY_HOURS, buf.readUnsignedIntLE() * 1000); + break; + case 44: + case 45: + case 46: + case 47: + case 48: + case 49: + case 50: + case 51: + position.set(Position.PREFIX_TEMP + (i - 43), buf.readByte()); + break; + case 68: + position.set("can-speed", buf.readUnsignedByte()); + break; + // FLEX 2.0 + case 69: + int satVisible = 0; + for (int k = 0; k < 8; k++) { + satVisible += buf.readUnsignedByte(); + } + position.set(Position.KEY_SATELLITES_VISIBLE, satVisible); + break; + case 70: + position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1); + position.set(Position.KEY_PDOP, buf.readUnsignedByte() * 0.1); + break; + default: + if (i < FLEX_FIELDS_SIZES.length) { + buf.skipBytes(FLEX_FIELDS_SIZES[i]); + } + break; + } + } + + return position; + } + + private Position parseFlex20Position(DeviceSession deviceSession, ByteBuf buf) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int length = buf.readUnsignedShort(); + if (length <= buf.readableBytes() && buf.readUnsignedByte() == 0x0A) { + + buf.readUnsignedByte(); // length of static part + + position.set(Position.KEY_INDEX, buf.readUnsignedIntLE()); + + position.set(Position.KEY_EVENT, buf.readUnsignedShortLE()); + buf.readUnsignedInt(); // event time + + int navSensorState = buf.readUnsignedByte(); + position.setValid(BitUtil.check(navSensorState, 1)); + position.set(Position.KEY_SATELLITES, BitUtil.from(navSensorState, 2)); + + position.setTime(new DateBuilder(new Date(buf.readUnsignedIntLE() * 1000)).getDate()); + position.setLatitude(buf.readIntLE() / 600000.0); + position.setLongitude(buf.readIntLE() / 600000.0); + position.setAltitude(buf.readIntLE() * 0.1); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readFloatLE())); + position.setCourse(buf.readUnsignedShortLE()); + position.set(Position.KEY_ODOMETER, buf.readFloatLE() * 1000); + + buf.skipBytes(length - buf.readerIndex() - 1); // skip unused part + } + + return position; + } + + private interface FlexPositionParser { + Position parsePosition(DeviceSession deviceSession, ByteBuf buf); + } + + private Object processFlexSingle( + FlexPositionParser parser, String flexHeader, DeviceSession deviceSession, Channel channel, ByteBuf buf) { + + if (!flexHeader.equals("~C")) { + buf.readUnsignedInt(); // event index + } + + Position position = parser.parsePosition(deviceSession, buf); + + ByteBuf response = Unpooled.buffer(); + response.writeCharSequence(flexHeader, StandardCharsets.US_ASCII); + response.writeIntLE((int) position.getLong(Position.KEY_INDEX)); + sendFlexReply(channel, response); + + return position.getFixTime() != null ? position : null; + } + + private Object processFlexArray( + FlexPositionParser parser, String flexHeader, DeviceSession deviceSession, Channel channel, ByteBuf buf) { + + List<Position> positions = new LinkedList<>(); + int count = buf.readUnsignedByte(); + + for (int i = 0; i < count; i++) { + Position position = parser.parsePosition(deviceSession, buf); + if (position.getFixTime() != null) { + positions.add(position); + } + } + + ByteBuf response = Unpooled.buffer(); + response.writeCharSequence(flexHeader, StandardCharsets.US_ASCII); + response.writeByte(count); + sendFlexReply(channel, response); + + return !positions.isEmpty() ? positions : null; + } + + private Object processFlexNegotiation(Channel channel, ByteBuf buf) { + if ((byte) buf.readUnsignedByte() != (byte) 0xB0) { + return null; + } + + short flexProtocolVersion = buf.readUnsignedByte(); + short flexStructVersion = buf.readUnsignedByte(); + if ((flexProtocolVersion == 0x0A || flexProtocolVersion == 0x14) + && (flexStructVersion == 0x0A || flexStructVersion == 0x14)) { + + flexBitFieldSize = buf.readUnsignedByte(); + if (flexBitFieldSize > 122) { + return null; + } + + buf.readBytes(flexBitField, 0, (int) Math.ceil((double) flexBitFieldSize / 8)); + + flexDataSize = 0; + for (int i = 0; i < flexBitFieldSize; i++) { + if (checkFlexBitfield(i)) { + flexDataSize += FLEX_FIELDS_SIZES[i]; + } + } + } else { + flexProtocolVersion = 0x14; + flexStructVersion = 0x14; + } + + ByteBuf response = Unpooled.buffer(9); + response.writeCharSequence("*<FLEX", StandardCharsets.US_ASCII); + response.writeByte(0xB0); + response.writeByte(flexProtocolVersion); + response.writeByte(flexStructVersion); + sendNtcbReply(channel, response); + + return null; + } + + private Object processHandshake(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + buf.readByte(); // colon + if (getDeviceSession(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII)) != null) { + sendNtcbReply(channel, Unpooled.copiedBuffer("*<S", StandardCharsets.US_ASCII)); + } + return null; + } + + private void sendNtcbReply(Channel channel, ByteBuf data) { + if (channel != null) { + ByteBuf header = Unpooled.buffer(16); + header.writeCharSequence(prefix, StandardCharsets.US_ASCII); + header.writeIntLE((int) deviceUniqueId); + header.writeIntLE((int) serverId); + header.writeShortLE(data.readableBytes()); + header.writeByte(Checksum.xor(data.nioBuffer())); + header.writeByte(Checksum.xor(header.nioBuffer())); + + channel.writeAndFlush(new NetworkMessage(Unpooled.wrappedBuffer(header, data), channel.remoteAddress())); + } + } + + private void sendFlexReply(Channel channel, ByteBuf data) { + if (channel != null) { + ByteBuf cs = Unpooled.buffer(1); + cs.writeByte(Checksum.crc8(new Algorithm(8, 0x31, 0xFF, false, false, 0x00), data.nioBuffer())); + + channel.writeAndFlush(new NetworkMessage(Unpooled.wrappedBuffer(data, cs), channel.remoteAddress())); + } + } + + private Object decodeNtcb(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + prefix = buf.toString(buf.readerIndex(), 4, StandardCharsets.US_ASCII); + buf.skipBytes(prefix.length()); // prefix @NTC by default + serverId = buf.readUnsignedIntLE(); + deviceUniqueId = buf.readUnsignedIntLE(); + int length = buf.readUnsignedShortLE(); + buf.skipBytes(2); // header and data XOR checksum + + if (length == 0) { + return null; // keep alive message + } + + String type = buf.toString(buf.readerIndex(), 3, StandardCharsets.US_ASCII); + buf.skipBytes(type.length()); + + if (type.equals("*>S")) { + return processHandshake(channel, remoteAddress, buf); + } else { + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession != null) { + switch (type) { + case "*>A": + return processNtcbArray(deviceSession, channel, buf); + case "*>T": + return processNtcbSingle(deviceSession, channel, buf); + case "*>F": + buf.skipBytes(3); + return processFlexNegotiation(channel, buf); + default: + break; + } + } + } + + return null; + } + + private Object decodeFlex(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + if (buf.getByte(buf.readerIndex()) == 0x7F) { + return null; // keep alive + } + + String type = buf.toString(buf.readerIndex(), 2, StandardCharsets.US_ASCII); + buf.skipBytes(type.length()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession != null) { + switch (type) { + // FLEX 1.0 + case "~A": + return processFlexArray(this::parseFlexPosition, type, deviceSession, channel, buf); + case "~T": + case "~C": + return processFlexSingle(this::parseFlexPosition, type, deviceSession, channel, buf); + // FLEX 2.0 (extra packages) + case "~E": + return processFlexArray(this::parseFlex20Position, type, deviceSession, channel, buf); + case "~X": + return processFlexSingle(this::parseFlex20Position, type, deviceSession, channel, buf); + default: + break; + } + } + + return null; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (flexDataSize > 0) { + return decodeFlex(channel, remoteAddress, buf); + } else { + return decodeNtcb(channel, remoteAddress, buf); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/NavisetFrameDecoder.java b/src/main/java/org/traccar/protocol/NavisetFrameDecoder.java new file mode 100644 index 000000000..e5e13b305 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NavisetFrameDecoder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; +import org.traccar.helper.BitUtil; + +public class NavisetFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + int length = 2 + BitUtil.to(buf.getUnsignedShortLE(buf.readerIndex()), 12) + 2; + + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/NavisetProtocol.java b/src/main/java/org/traccar/protocol/NavisetProtocol.java new file mode 100644 index 000000000..78755ea4d --- /dev/null +++ b/src/main/java/org/traccar/protocol/NavisetProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class NavisetProtocol extends BaseProtocol { + + public NavisetProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new NavisetFrameDecoder()); + pipeline.addLast(new NavisetProtocolDecoder(NavisetProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/NavisetProtocolDecoder.java b/src/main/java/org/traccar/protocol/NavisetProtocolDecoder.java new file mode 100644 index 000000000..10d71d76c --- /dev/null +++ b/src/main/java/org/traccar/protocol/NavisetProtocolDecoder.java @@ -0,0 +1,249 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class NavisetProtocolDecoder extends BaseProtocolDecoder { + + public NavisetProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_HEADER = 0b00; + public static final int MSG_DATA = 0b01; + public static final int MSG_RESPONSE = 0b10; + public static final int MSG_RESERVE = 0b11; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(0x01); + response.writeShortLE(buf.getUnsignedShortLE(buf.writerIndex() - 2)); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + + int length = buf.readUnsignedShortLE(); + int type = BitUtil.between(length, 14, 16); + buf.readUnsignedShortLE(); // device number + + if (type == MSG_HEADER) { + + getDeviceSession(channel, remoteAddress, buf.readCharSequence(15, StandardCharsets.US_ASCII).toString()); + + } else if (type == MSG_DATA) { + + List<Position> positions = new LinkedList<>(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + int blockMask = buf.readUnsignedByte(); + + while (buf.readableBytes() > 2) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_INDEX, buf.readUnsignedShortLE()); + position.set(Position.KEY_STATUS, buf.readUnsignedByte()); + position.setValid(true); + position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); + position.setLatitude(buf.readUnsignedIntLE() * 0.000001); + position.setLongitude(buf.readUnsignedIntLE() * 0.000001); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE() * 0.1)); + + if (BitUtil.check(blockMask, 0)) { + int dataMask = buf.readUnsignedByte(); + if (BitUtil.check(dataMask, 0)) { + int satellites = buf.readUnsignedByte(); + position.setValid(BitUtil.check(satellites, 7)); + position.set(Position.KEY_SATELLITES, BitUtil.to(satellites, 7)); + } + if (BitUtil.check(dataMask, 1)) { + position.setCourse(buf.readUnsignedShortLE() * 0.1); + } + if (BitUtil.check(dataMask, 2)) { + position.setAltitude(buf.readShortLE()); + } + if (BitUtil.check(dataMask, 3)) { + position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1); + } + if (BitUtil.check(dataMask, 4)) { + position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.001); + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE() * 0.001); + } + if (BitUtil.check(dataMask, 5)) { + position.set(Position.KEY_INPUT, buf.readUnsignedByte()); + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + } + if (BitUtil.check(dataMask, 6)) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + } + if (BitUtil.check(dataMask, 7)) { + buf.skipBytes(6); // accelerometer + } + } + + if (BitUtil.check(blockMask, 1)) { + int dataMask = buf.readUnsignedByte(); + for (int i = 0; i < 8; i++) { + if (BitUtil.check(dataMask, i)) { + position.set(Position.PREFIX_ADC + (i + 1), buf.readUnsignedShortLE()); + } + } + } + + if (BitUtil.check(blockMask, 2)) { + int dataMask = buf.readUnsignedByte(); + if (BitUtil.check(dataMask, 0)) { + position.set(Position.KEY_DEVICE_TEMP, (int) buf.readByte()); + } + if (BitUtil.check(dataMask, 1)) { + buf.skipBytes(6); // key code + } + if (BitUtil.check(dataMask, 2)) { + position.set(Position.PREFIX_TEMP + 1, (int) buf.readByte()); + position.set(Position.PREFIX_TEMP + 2, (int) buf.readByte()); + } + if (BitUtil.check(dataMask, 3)) { + position.set(Position.PREFIX_TEMP + 3, (int) buf.readByte()); + position.set(Position.PREFIX_TEMP + 4, (int) buf.readByte()); + } + if (BitUtil.check(dataMask, 4)) { + position.set(Position.PREFIX_TEMP + 5, (int) buf.readByte()); + position.set(Position.PREFIX_TEMP + 6, (int) buf.readByte()); + position.set(Position.PREFIX_TEMP + 7, (int) buf.readByte()); + position.set(Position.PREFIX_TEMP + 8, (int) buf.readByte()); + } + if (BitUtil.check(dataMask, 5)) { + position.set(Position.KEY_HOURS, buf.readUnsignedShortLE() / 60.0); + } + if (BitUtil.check(dataMask, 6)) { + buf.readUnsignedByte(); // extra status + } + if (BitUtil.check(dataMask, 7)) { + buf.readUnsignedByte(); // geofence + } + } + + if (BitUtil.check(blockMask, 3)) { + int dataMask = buf.readUnsignedByte(); + if (BitUtil.check(dataMask, 0)) { + position.set("fuel1", buf.readUnsignedShortLE()); + } + if (BitUtil.check(dataMask, 1)) { + position.set("fuel2", buf.readUnsignedShortLE()); + } + if (BitUtil.check(dataMask, 2)) { + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShortLE()); + } + if (BitUtil.check(dataMask, 3)) { + buf.skipBytes(18); + } + if (BitUtil.check(dataMask, 4)) { + buf.readUnsignedByte(); // fuel 1 temperature + } + if (BitUtil.check(dataMask, 5)) { + buf.readUnsignedByte(); // fuel 2 temperature + } + if (BitUtil.check(dataMask, 6)) { + buf.readUnsignedShortLE(); // fuel 1 frequency + } + if (BitUtil.check(dataMask, 7)) { + buf.readUnsignedShortLE(); // fuel 2 frequency + } + } + + if (BitUtil.check(blockMask, 4)) { + int dataMask = buf.readUnsignedByte(); + if (BitUtil.check(dataMask, 0)) { + buf.readUnsignedByte(); // fuel level (percentage) + position.set(Position.KEY_RPM, buf.readUnsignedShortLE()); + position.set(Position.KEY_COOLANT_TEMP, (int) buf.readByte()); + } + if (BitUtil.check(dataMask, 1)) { + buf.readUnsignedIntLE(); // fuel consumption + } + if (BitUtil.check(dataMask, 2)) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + } + for (int i = 3; i < 8; i++) { + if (BitUtil.check(dataMask, i)) { + buf.readUnsignedShortLE(); // axle weight + } + } + } + + if (BitUtil.check(blockMask, 4)) { + int dataMask = buf.readUnsignedByte(); + if (BitUtil.check(dataMask, 0)) { + buf.readUnsignedByte(); // speed + } + if (BitUtil.check(dataMask, 1)) { + buf.readUnsignedMediumLE(); // prefix S + } + if (BitUtil.check(dataMask, 2)) { + buf.readUnsignedIntLE(); // prefix P + } + if (BitUtil.check(dataMask, 3)) { + buf.readUnsignedIntLE(); // prefix A or B + } + if (BitUtil.check(dataMask, 4)) { + buf.readUnsignedShortLE(); // prefix R + } + if (BitUtil.check(dataMask, 5)) { + buf.skipBytes(26); + } + if (BitUtil.check(dataMask, 6)) { + buf.readUnsignedIntLE(); // reserved + } + if (BitUtil.check(dataMask, 7)) { + buf.readUnsignedIntLE(); // reserved + } + } + + positions.add(position); + } + + return positions; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/NeosProtocol.java b/src/main/java/org/traccar/protocol/NeosProtocol.java new file mode 100644 index 000000000..e545a9969 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NeosProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class NeosProtocol extends BaseProtocol { + + public NeosProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new NeosProtocolDecoder(NeosProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/NeosProtocolDecoder.java b/src/main/java/org/traccar/protocol/NeosProtocolDecoder.java new file mode 100644 index 000000000..6b5596dba --- /dev/null +++ b/src/main/java/org/traccar/protocol/NeosProtocolDecoder.java @@ -0,0 +1,98 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class NeosProtocolDecoder extends BaseProtocolDecoder { + + public NeosProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text(">") + .number("(d{8}),") // id + .number("d+,") // status + .number("([01]),") // valid + .number("(dd)(dd)(dd),") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([EW])") + .number("(d+)(dd.d+),") // longitude + .expression("([NS])") + .number("(d+)(dd.d+),") // latitude + .expression("[^,]*,") // response + .number("(d+),") // speed + .number("(d+),") // course + .number("(d+),") // rssi + .expression("[^,]*,") // event data + .number("(d+)-") // adc + .number("(d+),") // battery + .number("0,") + .number("d,") + .number("([01]{8})") // input + .text("*") + .number("xx!") + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("$OK!", remoteAddress)); + } + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(parser.nextInt() > 0); + position.setTime(parser.nextDateTime()); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setSpeed(parser.nextInt()); + position.setCourse(parser.nextInt()); + + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.PREFIX_ADC + 1, parser.nextInt()); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + position.set(Position.KEY_INPUT, parser.nextBinInt()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/NoranProtocol.java b/src/main/java/org/traccar/protocol/NoranProtocol.java new file mode 100644 index 000000000..3df364c30 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NoranProtocol.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class NoranProtocol extends BaseProtocol { + + public NoranProtocol() { + setSupportedDataCommands( + Command.TYPE_POSITION_SINGLE, + Command.TYPE_POSITION_PERIODIC, + Command.TYPE_POSITION_STOP, + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new NoranProtocolEncoder(NoranProtocol.this)); + pipeline.addLast(new NoranProtocolDecoder(NoranProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/NoranProtocolDecoder.java b/src/main/java/org/traccar/protocol/NoranProtocolDecoder.java new file mode 100644 index 000000000..53dae7fd6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NoranProtocolDecoder.java @@ -0,0 +1,164 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +public class NoranProtocolDecoder extends BaseProtocolDecoder { + + public NoranProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_UPLOAD_POSITION = 0x0008; + public static final int MSG_UPLOAD_POSITION_NEW = 0x0032; + public static final int MSG_CONTROL = 0x0002; + public static final int MSG_CONTROL_RESPONSE = 0x8009; + public static final int MSG_ALARM = 0x0003; + public static final int MSG_SHAKE_HAND = 0x0000; + public static final int MSG_SHAKE_HAND_RESPONSE = 0x8000; + public static final int MSG_IMAGE_SIZE = 0x0200; + public static final int MSG_IMAGE_PACKET = 0x0201; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedShortLE(); // length + int type = buf.readUnsignedShortLE(); + + if (type == MSG_SHAKE_HAND && channel != null) { + + ByteBuf response = Unpooled.buffer(13); + response.writeCharSequence("\r\n*KW", StandardCharsets.US_ASCII); + response.writeByte(0); + response.writeShortLE(response.capacity()); + response.writeShortLE(MSG_SHAKE_HAND_RESPONSE); + response.writeByte(1); // status + response.writeCharSequence("\r\n", StandardCharsets.US_ASCII); + + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + + } else if (type == MSG_UPLOAD_POSITION || type == MSG_UPLOAD_POSITION_NEW + || type == MSG_CONTROL_RESPONSE || type == MSG_ALARM) { + + boolean newFormat = false; + if (type == MSG_UPLOAD_POSITION && buf.readableBytes() == 48 + || type == MSG_ALARM && buf.readableBytes() == 48 + || type == MSG_CONTROL_RESPONSE && buf.readableBytes() == 57) { + newFormat = true; + } + + Position position = new Position(getProtocolName()); + + if (type == MSG_CONTROL_RESPONSE) { + buf.readUnsignedIntLE(); // GIS ip + buf.readUnsignedIntLE(); // GIS port + } + + position.setValid(BitUtil.check(buf.readUnsignedByte(), 0)); + + short alarm = buf.readUnsignedByte(); + switch (alarm) { + case 1: + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + break; + case 2: + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + break; + case 3: + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_EXIT); + break; + case 9: + position.set(Position.KEY_ALARM, Position.ALARM_POWER_OFF); + break; + default: + break; + } + + if (newFormat) { + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedIntLE())); + position.setCourse(buf.readFloatLE()); + } else { + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.setCourse(buf.readUnsignedShortLE()); + } + position.setLongitude(buf.readFloatLE()); + position.setLatitude(buf.readFloatLE()); + + if (!newFormat) { + long timeValue = buf.readUnsignedIntLE(); + DateBuilder dateBuilder = new DateBuilder() + .setYear((int) BitUtil.from(timeValue, 26)) + .setMonth((int) BitUtil.between(timeValue, 22, 26)) + .setDay((int) BitUtil.between(timeValue, 17, 22)) + .setHour((int) BitUtil.between(timeValue, 12, 17)) + .setMinute((int) BitUtil.between(timeValue, 6, 12)) + .setSecond((int) BitUtil.to(timeValue, 6)); + position.setTime(dateBuilder.getDate()); + } + + ByteBuf rawId; + if (newFormat) { + rawId = buf.readSlice(12); + } else { + rawId = buf.readSlice(11); + } + String id = rawId.toString(StandardCharsets.US_ASCII).replaceAll("[^\\p{Print}]", ""); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + if (newFormat) { + DateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss"); + position.setTime(dateFormat.parse(buf.readSlice(17).toString(StandardCharsets.US_ASCII))); + buf.readByte(); + } + + if (!newFormat) { + position.set(Position.PREFIX_IO + 1, buf.readUnsignedByte()); + position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedByte()); + } else if (type == MSG_UPLOAD_POSITION_NEW) { + position.set(Position.PREFIX_TEMP + 1, buf.readShortLE()); + position.set(Position.KEY_ODOMETER, buf.readFloatLE()); + } + + return position; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/NoranProtocolEncoder.java b/src/main/java/org/traccar/protocol/NoranProtocolEncoder.java new file mode 100644 index 000000000..e02a1313c --- /dev/null +++ b/src/main/java/org/traccar/protocol/NoranProtocolEncoder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.nio.charset.StandardCharsets; + +public class NoranProtocolEncoder extends BaseProtocolEncoder { + + public NoranProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf encodeContent(String content) { + + ByteBuf buf = Unpooled.buffer(12 + 56); + + buf.writeCharSequence("\r\n*KW", StandardCharsets.US_ASCII); + buf.writeByte(0); + buf.writeShortLE(buf.capacity()); + buf.writeShortLE(NoranProtocolDecoder.MSG_CONTROL); + buf.writeInt(0); // gis ip + buf.writeShortLE(0); // gis port + buf.writeBytes(content.getBytes(StandardCharsets.US_ASCII)); + buf.writerIndex(buf.writerIndex() + 50 - content.length()); + buf.writeCharSequence("\r\n", StandardCharsets.US_ASCII); + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_POSITION_SINGLE: + return encodeContent("*KW,000,000,000000#"); + case Command.TYPE_POSITION_PERIODIC: + int interval = command.getInteger(Command.KEY_FREQUENCY); + return encodeContent("*KW,000,002,000000," + interval + "#"); + case Command.TYPE_POSITION_STOP: + return encodeContent("*KW,000,002,000000,0#"); + case Command.TYPE_ENGINE_STOP: + return encodeContent("*KW,000,007,000000,0#"); + case Command.TYPE_ENGINE_RESUME: + return encodeContent("*KW,000,007,000000,1#"); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/NvsFrameDecoder.java b/src/main/java/org/traccar/protocol/NvsFrameDecoder.java new file mode 100644 index 000000000..e93a58cf6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NvsFrameDecoder.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class NvsFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 4 + 2) { + return null; + } + + int length; + if (buf.getUnsignedByte(buf.readerIndex()) == 0) { + length = 2 + buf.getUnsignedShort(buf.readerIndex()); + } else { + length = 4 + 2 + buf.getUnsignedShort(buf.readerIndex() + 4) + 2; + } + + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/NvsProtocol.java b/src/main/java/org/traccar/protocol/NvsProtocol.java new file mode 100644 index 000000000..d319b22f3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/NvsProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class NvsProtocol extends BaseProtocol { + + public NvsProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new NvsFrameDecoder()); + pipeline.addLast(new NvsProtocolDecoder(NvsProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/NvsProtocolDecoder.java b/src/main/java/org/traccar/protocol/NvsProtocolDecoder.java new file mode 100644 index 000000000..5d1159f7d --- /dev/null +++ b/src/main/java/org/traccar/protocol/NvsProtocolDecoder.java @@ -0,0 +1,137 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class NvsProtocolDecoder extends BaseProtocolDecoder { + + public NvsProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private void sendResponse(Channel channel, SocketAddress remoteAddress, String response) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage( + Unpooled.copiedBuffer(response, StandardCharsets.US_ASCII), remoteAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + + if (buf.getUnsignedByte(buf.readerIndex()) == 0) { + + buf.readUnsignedShort(); // length + + String imei = buf.toString(buf.readerIndex(), 15, StandardCharsets.US_ASCII); + + if (getDeviceSession(channel, remoteAddress, imei) != null) { + sendResponse(channel, remoteAddress, "OK"); + } else { + sendResponse(channel, remoteAddress, "NO01"); + } + + } else { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + buf.skipBytes(4); // marker + buf.readUnsignedShort(); // length + buf.readLong(); // imei + buf.readUnsignedByte(); // codec + int count = buf.readUnsignedByte(); + + for (int i = 0; i < count; i++) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + + position.set("reason", buf.readUnsignedByte()); + + position.setLongitude(buf.readInt() / 10000000.0); + position.setLatitude(buf.readInt() / 10000000.0); + position.setAltitude(buf.readShort()); + position.setCourse(buf.readUnsignedShort()); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort())); + position.setValid(buf.readUnsignedByte() != 0); + + buf.readUnsignedByte(); // used systems + + buf.readUnsignedByte(); // cause element id + + // Read 1 byte data + int cnt = buf.readUnsignedByte(); + for (int j = 0; j < cnt; j++) { + position.set(Position.PREFIX_IO + buf.readUnsignedByte(), buf.readUnsignedByte()); + } + + // Read 2 byte data + cnt = buf.readUnsignedByte(); + for (int j = 0; j < cnt; j++) { + position.set(Position.PREFIX_IO + buf.readUnsignedByte(), buf.readUnsignedShort()); + } + + // Read 4 byte data + cnt = buf.readUnsignedByte(); + for (int j = 0; j < cnt; j++) { + position.set(Position.PREFIX_IO + buf.readUnsignedByte(), buf.readUnsignedInt()); + } + + // Read 8 byte data + cnt = buf.readUnsignedByte(); + for (int j = 0; j < cnt; j++) { + position.set(Position.PREFIX_IO + buf.readUnsignedByte(), buf.readLong()); + } + + positions.add(position); + } + + return positions; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/NyitechProtocol.java b/src/main/java/org/traccar/protocol/NyitechProtocol.java new file mode 100644 index 000000000..58974be5c --- /dev/null +++ b/src/main/java/org/traccar/protocol/NyitechProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; + +public class NyitechProtocol extends BaseProtocol { + + public NyitechProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 2, -4, 0, true)); + pipeline.addLast(new NyitechProtocolDecoder(NyitechProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/NyitechProtocolDecoder.java b/src/main/java/org/traccar/protocol/NyitechProtocolDecoder.java new file mode 100644 index 000000000..62b41a2ea --- /dev/null +++ b/src/main/java/org/traccar/protocol/NyitechProtocolDecoder.java @@ -0,0 +1,170 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.ObdDecoder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; + +public class NyitechProtocolDecoder extends BaseProtocolDecoder { + + public NyitechProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final short MSG_LOGIN = 0x1001; + public static final short MSG_COMPREHENSIVE_LIVE = 0x2001; + public static final short MSG_COMPREHENSIVE_HISTORY = 0x2002; + public static final short MSG_ALARM = 0x2003; + public static final short MSG_FIXED = 0x2004; + + private void decodeLocation(Position position, ByteBuf buf) { + + DateBuilder dateBuilder = new DateBuilder() + .setDateReverse(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + int flags = buf.readUnsignedByte(); + position.setValid(BitUtil.to(flags, 2) > 0); + + double lat = buf.readUnsignedIntLE() / 3600000.0; + double lon = buf.readUnsignedIntLE() / 3600000.0; + + position.setLatitude(BitUtil.check(flags, 2) ? lat : -lat); + position.setLongitude(BitUtil.check(flags, 3) ? lon : -lon); + + position.setSpeed(UnitsConverter.knotsFromCps(buf.readUnsignedShortLE())); + position.setCourse(buf.readUnsignedShortLE() * 0.1); + position.setAltitude(buf.readShortLE() * 0.1); + } + + private String decodeAlarm(int type) { + switch (type) { + case 0x09: + return Position.ALARM_ACCELERATION; + case 0x0a: + return Position.ALARM_BRAKING; + case 0x0b: + return Position.ALARM_CORNERING; + case 0x0e: + return Position.ALARM_SOS; + default: + return null; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + buf.readUnsignedShortLE(); // length + + String id = buf.readCharSequence(12, StandardCharsets.US_ASCII).toString(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + int type = buf.readUnsignedShortLE(); + + if (type != MSG_LOGIN && type != MSG_COMPREHENSIVE_LIVE + && type != MSG_COMPREHENSIVE_HISTORY && type != MSG_ALARM && type != MSG_FIXED) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (type == MSG_COMPREHENSIVE_LIVE || type == MSG_COMPREHENSIVE_HISTORY) { + + buf.skipBytes(6); // time + boolean includeLocation = buf.readUnsignedByte() > 0; + boolean includeObd = buf.readUnsignedByte() > 0; + buf.readUnsignedByte(); // include sensor + + if (includeLocation) { + decodeLocation(position, buf); + } else { + getLastLocation(position, null); + } + + if (includeObd) { + int count = buf.readUnsignedByte(); + for (int i = 0; i < count; i++) { + int pid = buf.readUnsignedShortLE(); + int length = buf.readUnsignedByte(); + switch (length) { + case 1: + position.add(ObdDecoder.decodeData(pid, buf.readByte(), true)); + break; + case 2: + position.add(ObdDecoder.decodeData(pid, buf.readShortLE(), true)); + break; + case 4: + position.add(ObdDecoder.decodeData(pid, buf.readIntLE(), true)); + break; + default: + buf.skipBytes(length); + break; + } + } + } + + position.set(Position.KEY_FUEL_USED, buf.readUnsignedInt() * 0.01); + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedInt()); + + + } else if (type == MSG_ALARM) { + + buf.readUnsignedShortLE(); // random number + buf.readUnsignedByte(); // tag + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + buf.readUnsignedShortLE(); // threshold + buf.readUnsignedShortLE(); // value + buf.skipBytes(6); // time + + decodeLocation(position, buf); + + } else if (type == MSG_FIXED) { + + buf.skipBytes(6); // time + + decodeLocation(position, buf); + + } else { + + decodeLocation(position, buf); + + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/ObdDongleProtocol.java b/src/main/java/org/traccar/protocol/ObdDongleProtocol.java new file mode 100644 index 000000000..10a55759b --- /dev/null +++ b/src/main/java/org/traccar/protocol/ObdDongleProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class ObdDongleProtocol extends BaseProtocol { + + public ObdDongleProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1099, 20, 2, 3, 0)); + pipeline.addLast(new ObdDongleProtocolDecoder(ObdDongleProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ObdDongleProtocolDecoder.java b/src/main/java/org/traccar/protocol/ObdDongleProtocolDecoder.java new file mode 100644 index 000000000..1c9771ce9 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ObdDongleProtocolDecoder.java @@ -0,0 +1,130 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class ObdDongleProtocolDecoder extends BaseProtocolDecoder { + + public ObdDongleProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_TYPE_CONNECT = 0x01; + public static final int MSG_TYPE_CONNACK = 0x02; + public static final int MSG_TYPE_PUBLISH = 0x03; + public static final int MSG_TYPE_PUBACK = 0x04; + public static final int MSG_TYPE_PINGREQ = 0x0C; + public static final int MSG_TYPE_PINGRESP = 0x0D; + public static final int MSG_TYPE_DISCONNECT = 0x0E; + + private static void sendResponse(Channel channel, int type, int index, String imei, ByteBuf content) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeShort(0x5555); // header + response.writeShort(index); + response.writeBytes(imei.getBytes(StandardCharsets.US_ASCII)); + response.writeByte(type); + response.writeShort(content.readableBytes()); + response.writeBytes(content); + content.release(); + response.writeByte(0); // checksum + response.writeShort(0xAAAA); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + int index = buf.readUnsignedShort(); + + String imei = buf.readSlice(15).toString(StandardCharsets.US_ASCII); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + int type = buf.readUnsignedByte(); + + buf.readUnsignedShort(); // data length + + if (type == MSG_TYPE_CONNECT) { + + ByteBuf response = Unpooled.buffer(); + response.writeByte(1); + response.writeShort(0); + response.writeInt(0); + sendResponse(channel, MSG_TYPE_CONNACK, index, imei, response); + + } else if (type == MSG_TYPE_PUBLISH) { + + int typeMajor = buf.readUnsignedByte(); + int typeMinor = buf.readUnsignedByte(); + + buf.readUnsignedByte(); // event id + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + + int flags = buf.readUnsignedByte(); + + position.setValid(!BitUtil.check(flags, 6)); + + position.set(Position.KEY_SATELLITES, BitUtil.to(flags, 4)); + + double longitude = ((BitUtil.to(buf.readUnsignedShort(), 1) << 24) + buf.readUnsignedMedium()) * 0.00001; + position.setLongitude(BitUtil.check(flags, 5) ? longitude : -longitude); + + double latitude = buf.readUnsignedMedium() * 0.00001; + position.setLatitude(BitUtil.check(flags, 4) ? latitude : -latitude); + + int speedCourse = buf.readUnsignedMedium(); + position.setSpeed(UnitsConverter.knotsFromMph(BitUtil.from(speedCourse, 10) * 0.1)); + position.setCourse(BitUtil.to(speedCourse, 10)); + + ByteBuf response = Unpooled.buffer(); + response.writeByte(typeMajor); + response.writeByte(typeMinor); + sendResponse(channel, MSG_TYPE_PUBACK, index, imei, response); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/OigoProtocol.java b/src/main/java/org/traccar/protocol/OigoProtocol.java new file mode 100644 index 000000000..5056f68aa --- /dev/null +++ b/src/main/java/org/traccar/protocol/OigoProtocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class OigoProtocol extends BaseProtocol { + + public OigoProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new OigoProtocolDecoder(OigoProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/OigoProtocolDecoder.java b/src/main/java/org/traccar/protocol/OigoProtocolDecoder.java new file mode 100644 index 000000000..b9cc71e8c --- /dev/null +++ b/src/main/java/org/traccar/protocol/OigoProtocolDecoder.java @@ -0,0 +1,240 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; + +public class OigoProtocolDecoder extends BaseProtocolDecoder { + + public OigoProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_AR_LOCATION = 0x00; + public static final int MSG_AR_REMOTE_START = 0x10; + + public static final int MSG_ACKNOWLEDGEMENT = 0xE0; + + private Position decodeArMessage(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + buf.skipBytes(1); // header + buf.readUnsignedShort(); // length + + int type = buf.readUnsignedByte(); + + int tag = buf.readUnsignedByte(); + + DeviceSession deviceSession; + switch (BitUtil.to(tag, 3)) { + case 0: + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(1); + deviceSession = getDeviceSession(channel, remoteAddress, imei); + break; + case 1: + buf.skipBytes(1); + String meid = buf.readSlice(14).toString(StandardCharsets.US_ASCII); + deviceSession = getDeviceSession(channel, remoteAddress, meid); + break; + default: + deviceSession = getDeviceSession(channel, remoteAddress); + break; + } + + if (deviceSession == null || type != MSG_AR_LOCATION) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + + int mask = buf.readInt(); + + if (BitUtil.check(mask, 0)) { + position.set(Position.KEY_INDEX, buf.readUnsignedShort()); + } + + if (BitUtil.check(mask, 1)) { + int date = buf.readUnsignedByte(); + DateBuilder dateBuilder = new DateBuilder() + .setDate(BitUtil.between(date, 4, 8) + 2010, BitUtil.to(date, 4), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + } + + if (BitUtil.check(mask, 2)) { + buf.skipBytes(5); // device time + } + + if (BitUtil.check(mask, 3)) { + position.setLatitude(buf.readUnsignedInt() * 0.000001 - 90); + position.setLongitude(buf.readUnsignedInt() * 0.000001 - 180.0); + } + + if (BitUtil.check(mask, 4)) { + int status = buf.readUnsignedByte(); + position.setValid(BitUtil.between(status, 4, 8) != 0); + position.set(Position.KEY_SATELLITES, BitUtil.to(status, 4)); + position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1); + } + + if (BitUtil.check(mask, 5)) { + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + } + + if (BitUtil.check(mask, 6)) { + position.setCourse(buf.readUnsignedShort()); + } + + if (BitUtil.check(mask, 7)) { + position.setAltitude(buf.readShort()); + } + + if (BitUtil.check(mask, 8)) { + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + } + + if (BitUtil.check(mask, 9)) { + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.001); + } + + if (BitUtil.check(mask, 10)) { + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.001); + } + + if (BitUtil.check(mask, 11)) { + buf.skipBytes(2); // gpio + } + + if (BitUtil.check(mask, 12)) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 1000); + } + + if (BitUtil.check(mask, 13)) { + buf.skipBytes(6); // software version + } + + if (BitUtil.check(mask, 14)) { + buf.skipBytes(5); // hardware version + } + + if (BitUtil.check(mask, 15)) { + buf.readUnsignedShort(); // device config + } + + return position; + } + + private double convertCoordinate(long value) { + boolean negative = value < 0; + value = Math.abs(value); + double minutes = (value % 100000) * 0.001; + value /= 100000; + double degrees = value + minutes / 60; + return negative ? -degrees : degrees; + } + + private Position decodeMgMessage(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + buf.readUnsignedByte(); // tag + int flags = buf.getUnsignedByte(buf.readerIndex()); + + DeviceSession deviceSession; + if (BitUtil.check(flags, 6)) { + buf.readUnsignedByte(); // flags + deviceSession = getDeviceSession(channel, remoteAddress); + } else { + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(1); + deviceSession = getDeviceSession(channel, remoteAddress, imei); + } + + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.skipBytes(8); // imsi + + int date = buf.readUnsignedShort(); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(2010 + BitUtil.from(date, 12), BitUtil.between(date, 8, 12), BitUtil.to(date, 8)) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), 0); + + position.setValid(true); + position.setLatitude(convertCoordinate(buf.readInt())); + position.setLongitude(convertCoordinate(buf.readInt())); + + position.setAltitude(UnitsConverter.metersFromFeet(buf.readShort())); + position.setCourse(buf.readUnsignedShort()); + position.setSpeed(UnitsConverter.knotsFromMph(buf.readUnsignedByte())); + + position.set(Position.KEY_POWER, buf.readUnsignedByte() * 0.1); + position.set(Position.PREFIX_IO + 1, buf.readUnsignedByte()); + + dateBuilder.setSecond(buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + + int index = buf.readUnsignedByte(); + + position.set(Position.KEY_VERSION_FW, buf.readUnsignedByte()); + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_ODOMETER, (long) (buf.readUnsignedInt() * 1609.34)); + + if (channel != null && BitUtil.check(flags, 7)) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(MSG_ACKNOWLEDGEMENT); + response.writeByte(index); + response.writeByte(0x00); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (buf.getUnsignedByte(buf.readerIndex()) == 0x7e) { + return decodeArMessage(channel, remoteAddress, buf); + } else { + return decodeMgMessage(channel, remoteAddress, buf); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/OkoProtocol.java b/src/main/java/org/traccar/protocol/OkoProtocol.java new file mode 100644 index 000000000..9571ccc48 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OkoProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class OkoProtocol extends BaseProtocol { + + public OkoProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '}')); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new OkoProtocolDecoder(OkoProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/OkoProtocolDecoder.java b/src/main/java/org/traccar/protocol/OkoProtocolDecoder.java new file mode 100644 index 000000000..5adf61494 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OkoProtocolDecoder.java @@ -0,0 +1,100 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.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 OkoProtocolDecoder extends BaseProtocolDecoder { + + public OkoProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("{") + .number("(d{15}),").optional() // imei + .number("(dd)(dd)(dd).d+,") // time + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(d+),") // satellites + .number("(d+.d+),") // adc + .number("(xx),") // event + .number("(d+.d+),") // power + .number("d,") // memory status + .number("(xx)") // io + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession; + if (parser.hasNext()) { + deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + } else { + deviceSession = getDeviceSession(channel, remoteAddress); + } + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.KEY_EVENT, parser.next()); + position.set(Position.KEY_POWER, parser.nextDouble()); + 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 new file mode 100644 index 000000000..314f19757 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OmnicommFrameDecoder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class OmnicommFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 6) { + return null; + } + + int endIndex = buf.getUnsignedShortLE(buf.readerIndex() + 2) + buf.readerIndex() + 6; + if (buf.writerIndex() < endIndex) { + return null; + } + + ByteBuf result = Unpooled.buffer(); + result.writeByte(buf.readUnsignedByte()); + while (buf.readerIndex() < endIndex) { + int b = buf.readUnsignedByte(); + if (b == 0xDB) { + int ext = buf.readUnsignedByte(); + if (ext == 0xDC) { + result.writeByte(0xC0); + } else if (ext == 0xDD) { + result.writeByte(0xDB); + } + endIndex += 1; + } else { + result.writeByte(b); + } + } + return result; + } + +} diff --git a/src/main/java/org/traccar/protocol/OmnicommProtocol.java b/src/main/java/org/traccar/protocol/OmnicommProtocol.java new file mode 100644 index 000000000..96660cb59 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OmnicommProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class OmnicommProtocol extends BaseProtocol { + + public OmnicommProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new OmnicommFrameDecoder()); + pipeline.addLast(new OmnicommProtocolDecoder(OmnicommProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java b/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java new file mode 100644 index 000000000..cd8b74c9a --- /dev/null +++ b/src/main/java/org/traccar/protocol/OmnicommProtocolDecoder.java @@ -0,0 +1,142 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import com.google.protobuf.InvalidProtocolBufferException; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Checksum; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; +import org.traccar.protobuf.OmnicommMessageOuterClass; + +import java.net.SocketAddress; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class OmnicommProtocolDecoder extends BaseProtocolDecoder { + + public OmnicommProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_IDENTIFICATION = 0x80; + public static final int MSG_ARCHIVE_INQUIRY = 0x85; + public static final int MSG_ARCHIVE_DATA = 0x86; + public static final int MSG_REMOVE_ARCHIVE_INQUIRY = 0x87; + + private OmnicommMessageOuterClass.OmnicommMessage parseProto( + ByteBuf buf, int length) throws InvalidProtocolBufferException { + + final byte[] array; + final int offset; + if (buf.hasArray()) { + array = buf.array(); + offset = buf.arrayOffset() + buf.readerIndex(); + } else { + array = ByteBufUtil.getBytes(buf, buf.readerIndex(), length, false); + offset = 0; + } + buf.skipBytes(length); + + return OmnicommMessageOuterClass.OmnicommMessage + .getDefaultInstance().getParserForType().parseFrom(array, offset, length); + } + + private void sendResponse(Channel channel, int type, long index) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(0xC0); + response.writeByte(type); + response.writeShortLE(4); + response.writeIntLE((int) index); + response.writeShortLE(Checksum.crc16(Checksum.CRC16_CCITT_FALSE, + response.nioBuffer(1, response.writerIndex() - 1))); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // prefix + int type = buf.readUnsignedByte(); + buf.readUnsignedShortLE(); // length + + if (type == MSG_IDENTIFICATION) { + + getDeviceSession(channel, remoteAddress, String.valueOf(buf.readUnsignedIntLE())); + sendResponse(channel, MSG_ARCHIVE_INQUIRY, 0); + + } else if (type == MSG_ARCHIVE_DATA) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + long index = buf.readUnsignedIntLE(); + buf.readUnsignedIntLE(); // time + buf.readUnsignedByte(); // priority + + List<Position> positions = new LinkedList<>(); + + while (buf.readableBytes() > 2) { + + OmnicommMessageOuterClass.OmnicommMessage message = parseProto(buf, buf.readUnsignedShortLE()); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (message.hasNAV()) { + OmnicommMessageOuterClass.OmnicommMessage.NAV nav = 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()); + } + + if (position.getFixTime() != null) { + positions.add(position); + } + } + + if (positions.isEmpty()) { + sendResponse(channel, MSG_REMOVE_ARCHIVE_INQUIRY, index + 1); + return null; + } else { + return positions; + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/OpenGtsProtocol.java b/src/main/java/org/traccar/protocol/OpenGtsProtocol.java new file mode 100644 index 000000000..5ef3260c6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OpenGtsProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class OpenGtsProtocol extends BaseProtocol { + + public OpenGtsProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(16384)); + pipeline.addLast(new OpenGtsProtocolDecoder(OpenGtsProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/OpenGtsProtocolDecoder.java b/src/main/java/org/traccar/protocol/OpenGtsProtocolDecoder.java new file mode 100644 index 000000000..b76cbfa85 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OpenGtsProtocolDecoder.java @@ -0,0 +1,115 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.QueryStringDecoder; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.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.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class OpenGtsProtocolDecoder extends BaseHttpProtocolDecoder { + + private static final Pattern PATTERN = new PatternBuilder() + .text("$GPRMC,") + .number("(dd)(dd)(dd)(?:.d+)?,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.d+),") // speed + .number("(d+.d+)?,") // course + .number("(dd)(dd)(dd),") // date (ddmmyy) + .any() + .compile(); + + public OpenGtsProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + QueryStringDecoder decoder = new QueryStringDecoder(request.uri()); + Map<String, List<String>> params = decoder.parameters(); + + Position position = new Position(getProtocolName()); + + for (Map.Entry<String, List<String>> entry : params.entrySet()) { + String value = entry.getValue().get(0); + switch (entry.getKey()) { + case "id": + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, value); + if (deviceSession == null) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + break; + case "gprmc": + Parser parser = new Parser(PATTERN, value); + if (!parser.matches()) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble()); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + break; + case "alt": + position.setAltitude(Double.parseDouble(value)); + break; + case "batt": + position.set(Position.KEY_BATTERY_LEVEL, Double.parseDouble(value)); + break; + default: + break; + } + } + + if (position.getDeviceId() != 0) { + sendResponse(channel, HttpResponseStatus.OK); + return position; + } else { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/OrionFrameDecoder.java b/src/main/java/org/traccar/protocol/OrionFrameDecoder.java new file mode 100644 index 000000000..948806609 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OrionFrameDecoder.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class OrionFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + int length = 6; + + if (buf.readableBytes() >= length) { + + int type = buf.getUnsignedByte(buf.readerIndex() + 2) & 0x0f; + + if (type == OrionProtocolDecoder.MSG_USERLOG && buf.readableBytes() >= length + 5) { + + int index = buf.readerIndex() + 3; + int count = buf.getUnsignedByte(index) & 0x0f; + index += 5; + length += 5; + + for (int i = 0; i < count; i++) { + if (buf.readableBytes() < length) { + return null; + } + int logLength = buf.getUnsignedByte(index + 1); + index += logLength; + length += logLength; + } + + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + } else if (type == OrionProtocolDecoder.MSG_SYSLOG && buf.readableBytes() >= length + 12) { + + length += buf.getUnsignedShortLE(buf.readerIndex() + 8); + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/OrionProtocol.java b/src/main/java/org/traccar/protocol/OrionProtocol.java new file mode 100644 index 000000000..8485ae638 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OrionProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class OrionProtocol extends BaseProtocol { + + public OrionProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new OrionFrameDecoder()); + pipeline.addLast(new OrionProtocolDecoder(OrionProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/OrionProtocolDecoder.java b/src/main/java/org/traccar/protocol/OrionProtocolDecoder.java new file mode 100644 index 000000000..af819989e --- /dev/null +++ b/src/main/java/org/traccar/protocol/OrionProtocolDecoder.java @@ -0,0 +1,115 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.DateBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.LinkedList; +import java.util.List; + +public class OrionProtocolDecoder extends BaseProtocolDecoder { + + public OrionProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_USERLOG = 0; + public static final int MSG_SYSLOG = 3; + + private static void sendResponse(Channel channel, ByteBuf buf) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(4); + response.writeByte('*'); + response.writeShort(buf.getUnsignedShort(buf.writerIndex() - 2)); + response.writeByte(buf.getUnsignedByte(buf.writerIndex() - 3)); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + private static double convertCoordinate(int raw) { + int degrees = raw / 1000000; + double minutes = (raw % 1000000) / 10000.0; + return degrees + minutes / 60; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + int type = buf.readUnsignedByte() & 0x0f; + + if (type == MSG_USERLOG) { + + int header = buf.readUnsignedByte(); + + if ((header & 0x40) != 0) { + sendResponse(channel, buf); + } + + DeviceSession deviceSession = getDeviceSession( + channel, remoteAddress, String.valueOf(buf.readUnsignedInt())); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + for (int i = 0; i < (header & 0x0f); i++) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + buf.readUnsignedByte(); // length + position.set(Position.KEY_FLAGS, buf.readUnsignedShortLE()); + + position.setLatitude(convertCoordinate(buf.readIntLE())); + position.setLongitude(convertCoordinate(buf.readIntLE())); + position.setAltitude(buf.readShortLE() / 10.0); + position.setCourse(buf.readUnsignedShortLE()); + position.setSpeed(buf.readUnsignedShortLE() * 0.0539957); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + int satellites = buf.readUnsignedByte(); + position.setValid(satellites >= 3); + position.set(Position.KEY_SATELLITES, satellites); + + positions.add(position); + } + + return positions; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/OsmAndProtocol.java b/src/main/java/org/traccar/protocol/OsmAndProtocol.java new file mode 100644 index 000000000..d3aa2fd6f --- /dev/null +++ b/src/main/java/org/traccar/protocol/OsmAndProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class OsmAndProtocol extends BaseProtocol { + + public OsmAndProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(16384)); + pipeline.addLast(new OsmAndProtocolDecoder(OsmAndProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/OsmAndProtocolDecoder.java b/src/main/java/org/traccar/protocol/OsmAndProtocolDecoder.java new file mode 100644 index 000000000..3bc71de81 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OsmAndProtocolDecoder.java @@ -0,0 +1,184 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.QueryStringDecoder; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.DateUtil; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class OsmAndProtocolDecoder extends BaseHttpProtocolDecoder { + + public OsmAndProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + QueryStringDecoder decoder = new QueryStringDecoder(request.uri()); + Map<String, List<String>> params = decoder.parameters(); + if (params.isEmpty()) { + decoder = new QueryStringDecoder(request.content().toString(StandardCharsets.US_ASCII), false); + params = decoder.parameters(); + } + + Position position = new Position(getProtocolName()); + position.setValid(true); + + Network network = new Network(); + + for (Map.Entry<String, List<String>> entry : params.entrySet()) { + for (String value : entry.getValue()) { + switch (entry.getKey()) { + case "id": + case "deviceid": + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, value); + if (deviceSession == null) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + break; + case "valid": + position.setValid(Boolean.parseBoolean(value) || "1".equals(value)); + break; + case "timestamp": + try { + long timestamp = Long.parseLong(value); + if (timestamp < Integer.MAX_VALUE) { + timestamp *= 1000; + } + position.setTime(new Date(timestamp)); + } catch (NumberFormatException error) { + if (value.contains("T")) { + position.setTime(DateUtil.parseDate(value)); + } else { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + position.setTime(dateFormat.parse(value)); + } + } + break; + case "lat": + position.setLatitude(Double.parseDouble(value)); + break; + case "lon": + position.setLongitude(Double.parseDouble(value)); + break; + case "location": + String[] location = value.split(","); + position.setLatitude(Double.parseDouble(location[0])); + position.setLongitude(Double.parseDouble(location[1])); + break; + case "cell": + String[] cell = value.split(","); + if (cell.length > 4) { + network.addCellTower(CellTower.from( + Integer.parseInt(cell[0]), Integer.parseInt(cell[1]), + Integer.parseInt(cell[2]), Integer.parseInt(cell[3]), Integer.parseInt(cell[4]))); + } else { + network.addCellTower(CellTower.from( + Integer.parseInt(cell[0]), Integer.parseInt(cell[1]), + Integer.parseInt(cell[2]), Integer.parseInt(cell[3]))); + } + break; + case "wifi": + String[] wifi = value.split(","); + network.addWifiAccessPoint(WifiAccessPoint.from( + wifi[0].replace('-', ':'), Integer.parseInt(wifi[1]))); + break; + case "speed": + position.setSpeed(convertSpeed(Double.parseDouble(value), "kn")); + break; + case "bearing": + case "heading": + position.setCourse(Double.parseDouble(value)); + break; + case "altitude": + position.setAltitude(Double.parseDouble(value)); + break; + case "accuracy": + position.setAccuracy(Double.parseDouble(value)); + break; + case "hdop": + position.set(Position.KEY_HDOP, Double.parseDouble(value)); + break; + case "batt": + position.set(Position.KEY_BATTERY_LEVEL, Double.parseDouble(value)); + break; + case "driverUniqueId": + position.set(Position.KEY_DRIVER_UNIQUE_ID, value); + break; + default: + try { + position.set(entry.getKey(), Double.parseDouble(value)); + } catch (NumberFormatException e) { + switch (value) { + case "true": + position.set(entry.getKey(), true); + break; + case "false": + position.set(entry.getKey(), false); + break; + default: + position.set(entry.getKey(), value); + break; + } + } + break; + } + } + } + + if (position.getFixTime() == null) { + position.setTime(new Date()); + } + + if (network.getCellTowers() != null || network.getWifiAccessPoints() != null) { + position.setNetwork(network); + } + + if (position.getLatitude() == 0 && position.getLongitude() == 0) { + getLastLocation(position, position.getDeviceTime()); + } + + if (position.getDeviceId() != 0) { + sendResponse(channel, HttpResponseStatus.OK); + return position; + } else { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/OutsafeProtocol.java b/src/main/java/org/traccar/protocol/OutsafeProtocol.java new file mode 100644 index 000000000..c728a404d --- /dev/null +++ b/src/main/java/org/traccar/protocol/OutsafeProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class OutsafeProtocol extends BaseProtocol { + + public OutsafeProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(65535)); + pipeline.addLast(new OutsafeProtocolDecoder(OutsafeProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java b/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java new file mode 100644 index 000000000..5e95270e8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OutsafeProtocolDecoder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.model.Position; + +import javax.json.Json; +import javax.json.JsonObject; +import java.io.StringReader; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class OutsafeProtocolDecoder extends BaseHttpProtocolDecoder { + + public OutsafeProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + String content = request.content().toString(StandardCharsets.UTF_8); + JsonObject json = Json.createReader(new StringReader(content)).readObject(); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, json.getString("device")); + if (deviceSession == null) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date()); + position.setValid(true); + position.setLatitude(json.getJsonNumber("latitude").doubleValue()); + position.setLongitude(json.getJsonNumber("longitude").doubleValue()); + position.setAltitude(json.getJsonNumber("altitude").doubleValue()); + position.setCourse(json.getJsonNumber("heading").intValue()); + + position.set(Position.KEY_RSSI, json.getJsonNumber("rssi").intValue()); + + sendResponse(channel, HttpResponseStatus.OK); + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/OwnTracksProtocol.java b/src/main/java/org/traccar/protocol/OwnTracksProtocol.java new file mode 100644 index 000000000..0086371d8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OwnTracksProtocol.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Jan-Piet Mens (jpmens@gmail.com) + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class OwnTracksProtocol extends BaseProtocol { + + public OwnTracksProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(16384)); + pipeline.addLast(new OwnTracksProtocolDecoder(OwnTracksProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java b/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java new file mode 100644 index 000000000..323d97fa3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/OwnTracksProtocolDecoder.java @@ -0,0 +1,215 @@ +/* + * Copyright 2017 Jan-Piet Mens (jpmens@gmail.com) + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import javax.json.Json; +import javax.json.JsonObject; +import java.io.StringReader; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class OwnTracksProtocolDecoder extends BaseHttpProtocolDecoder { + + public OwnTracksProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + JsonObject root = Json.createReader( + new StringReader(request.content().toString(StandardCharsets.US_ASCII))).readObject(); + + if (!root.containsKey("_type")) { + sendResponse(channel, HttpResponseStatus.OK); + return null; + } + if (!root.getString("_type").equals("location") + && !root.getString("_type").equals("lwt")) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + + Position position = new Position(getProtocolName()); + String uniqueId; + + if (root.containsKey("topic")) { + uniqueId = root.getString("topic"); + if (root.containsKey("tid")) { + position.set("tid", root.getString("tid")); + } + } else { + uniqueId = root.getString("tid"); + } + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, uniqueId); + if (deviceSession == null) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + + if (root.getString("_type").equals("lwt")) { + sendResponse(channel, HttpResponseStatus.OK); + return null; + } + + if (root.containsKey("t") && root.getString("t").equals("p")) { + sendResponse(channel, HttpResponseStatus.OK); + return null; + } + + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date(root.getJsonNumber("tst").longValue() * 1000)); + if (root.containsKey("sent")) { + position.setDeviceTime(new Date(root.getJsonNumber("sent").longValue() * 1000)); + } + + position.setValid(true); + + position.setLatitude(root.getJsonNumber("lat").doubleValue()); + position.setLongitude(root.getJsonNumber("lon").doubleValue()); + + if (root.containsKey("vel")) { + position.setSpeed(UnitsConverter.knotsFromKph(root.getInt("vel"))); + } + if (root.containsKey("alt")) { + position.setAltitude(root.getInt("alt")); + } + if (root.containsKey("cog")) { + position.setCourse(root.getInt("cog")); + } + if (root.containsKey("acc")) { + position.setAccuracy(root.getInt("acc")); + } + if (root.containsKey("t")) { + String trigger = root.getString("t"); + position.set("t", trigger); + Integer reportType = -1; + if (root.containsKey("rty")) { + reportType = root.getInt("rty"); + } + setEventOrAlarm(position, trigger, reportType); + } + if (root.containsKey("batt")) { + position.set(Position.KEY_BATTERY_LEVEL, root.getInt("batt")); + } + if (root.containsKey("uext")) { + position.set(Position.KEY_POWER, root.getJsonNumber("uext").doubleValue()); + } + if (root.containsKey("ubatt")) { + position.set(Position.KEY_BATTERY, root.getJsonNumber("ubatt").doubleValue()); + } + if (root.containsKey("vin")) { + position.set(Position.KEY_VIN, root.getString("vin")); + } + if (root.containsKey("name")) { + position.set(Position.KEY_VIN, root.getString("name")); + } + if (root.containsKey("rpm")) { + position.set(Position.KEY_RPM, root.getInt("rpm")); + } + if (root.containsKey("ign")) { + position.set(Position.KEY_IGNITION, root.getBoolean("ign")); + } + if (root.containsKey("motion")) { + position.set(Position.KEY_MOTION, root.getBoolean("motion")); + } + if (root.containsKey("odometer")) { + position.set(Position.KEY_ODOMETER, root.getJsonNumber("odometer").doubleValue() * 1000.0); + } + if (root.containsKey("hmc")) { + position.set(Position.KEY_HOURS, root.getJsonNumber("hmc").doubleValue() / 3600.0); + } + + if (root.containsKey("anum")) { + Integer numberOfAnalogueInputs = root.getInt("anum"); + for (Integer i = 0; i < numberOfAnalogueInputs; i++) { + String indexString = String.format("%02d", i); + if (root.containsKey("adda-" + indexString)) { + position.set(Position.PREFIX_ADC + (i + 1), root.getString("adda-" + indexString)); + } + if (root.containsKey("temp_c-" + indexString)) { + position.set(Position.PREFIX_TEMP + (i + 1), + root.getJsonNumber("temp_c-" + indexString).doubleValue()); + } + } + } + + sendResponse(channel, HttpResponseStatus.OK); + return position; + } + + private void setEventOrAlarm(Position position, String trigger, Integer reportType) { + switch (trigger) { + case "9": + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + case "1": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_ON); + break; + case "i": + position.set(Position.KEY_IGNITION, true); + break; + case "I": + position.set(Position.KEY_IGNITION, false); + break; + case "E": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_RESTORED); + break; + case "e": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + break; + case "!": + position.set(Position.KEY_ALARM, Position.ALARM_TOW); + break; + case "s": + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + break; + case "h": + switch (reportType) { + case 0: + case 3: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 1: + case 4: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 2: + case 5: + default: + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + break; + } + break; + default: + break; + } + } +} diff --git a/src/main/java/org/traccar/protocol/PacificTrackProtocol.java b/src/main/java/org/traccar/protocol/PacificTrackProtocol.java new file mode 100644 index 000000000..08991ab64 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PacificTrackProtocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class PacificTrackProtocol extends BaseProtocol { + + public PacificTrackProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new PacificTrackProtocolDecoder(PacificTrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java new file mode 100644 index 000000000..d49a73a86 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PacificTrackProtocolDecoder.java @@ -0,0 +1,152 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class PacificTrackProtocolDecoder extends BaseProtocolDecoder { + + public PacificTrackProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static int readBitExt(ByteBuf buf) { + int result = 0; + while (buf.isReadable()) { + int b = buf.readUnsignedByte(); + result <<= 7; + result += BitUtil.to(b, 7); + if (BitUtil.check(b, 7)) { + break; + } + } + return result; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readByte(); // frame start + readBitExt(buf); // frame control + readBitExt(buf); // frame length + + DeviceSession deviceSession = null; + Position position = new Position(getProtocolName()); + + while (buf.isReadable()) { + + int segmentId = readBitExt(buf); + int segmentEnd = readBitExt(buf) + buf.readerIndex(); + + switch (segmentId) { + case 0x01: + position.set(Position.KEY_EVENT, readBitExt(buf)); + break; + case 0x10: + position.setValid(BitUtil.check(buf.readUnsignedByte(), 4)); + int date = buf.readUnsignedByte(); + DateBuilder dateBuilder = new DateBuilder() + .setDate(2010 + BitUtil.from(date, 4), BitUtil.to(date, 4), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + position.setLatitude(buf.readUnsignedInt() / 1000000.0 - 90.0); + position.setLongitude(buf.readUnsignedInt() / 1000000.0 - 180.0); + int speedAndCourse = buf.readUnsignedMedium(); + position.setCourse(BitUtil.from(speedAndCourse, 12)); + 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.readerIndex(segmentEnd); + break; + } + } + + if (deviceSession != null) { + position.setDeviceId(deviceSession.getDeviceId()); + return position; + } else { + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/PathAwayProtocol.java b/src/main/java/org/traccar/protocol/PathAwayProtocol.java new file mode 100644 index 000000000..6b5d75c5e --- /dev/null +++ b/src/main/java/org/traccar/protocol/PathAwayProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class PathAwayProtocol extends BaseProtocol { + + public PathAwayProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(16384)); + pipeline.addLast(new PathAwayProtocolDecoder(PathAwayProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/PathAwayProtocolDecoder.java b/src/main/java/org/traccar/protocol/PathAwayProtocolDecoder.java new file mode 100644 index 000000000..02a15e34a --- /dev/null +++ b/src/main/java/org/traccar/protocol/PathAwayProtocolDecoder.java @@ -0,0 +1,97 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFutureListener; +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.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class PathAwayProtocolDecoder extends BaseProtocolDecoder { + + public PathAwayProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$PWS,") + .number("d+,") // version + .expression("[^,]*,") // name + .expression("[^,]*,") // icon + .expression("[^,]*,") // color + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(-?d+.?d*),") // altitude + .number("(-?d+.?d*),") // speed + .number("(-?d+.?d*),") // course + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + QueryStringDecoder decoder = new QueryStringDecoder(request.uri()); + + DeviceSession deviceSession = getDeviceSession( + channel, remoteAddress, decoder.parameters().get("UserName").get(0)); + if (deviceSession == null) { + return null; + } + + Parser parser = new Parser(PATTERN, decoder.parameters().get("LOC").get(0)); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setValid(true); + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + if (channel != null) { + FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)).addListener(ChannelFutureListener.CLOSE); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/PiligrimProtocol.java b/src/main/java/org/traccar/protocol/PiligrimProtocol.java new file mode 100644 index 000000000..d88c1ab72 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PiligrimProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class PiligrimProtocol extends BaseProtocol { + + public PiligrimProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(16384)); + pipeline.addLast(new PiligrimProtocolDecoder(PiligrimProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java b/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java new file mode 100644 index 000000000..47aa86da7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PiligrimProtocolDecoder.java @@ -0,0 +1,167 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.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.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; + +public class PiligrimProtocolDecoder extends BaseProtocolDecoder { + + 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())); + } + } + + public static final int MSG_GPS = 0xF1; + public static final int MSG_GPS_SENSORS = 0xF2; + public static final int MSG_EVENTS = 0xF3; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + String uri = request.uri(); + + if (uri.startsWith("/config")) { + + sendResponse(channel, "CONFIG: OK"); + + } else if (uri.startsWith("/addlog")) { + + sendResponse(channel, "ADDLOG: OK"); + + } else if (uri.startsWith("/inform")) { + + sendResponse(channel, "INFORM: OK"); + + } else if (uri.startsWith("/bingps")) { + + sendResponse(channel, "BINGPS: OK"); + + QueryStringDecoder decoder = new QueryStringDecoder(request.uri()); + DeviceSession deviceSession = getDeviceSession( + channel, remoteAddress, decoder.parameters().get("imei").get(0)); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + ByteBuf buf = request.content(); + + while (buf.readableBytes() > 2) { + + buf.readUnsignedByte(); // header + int type = buf.readUnsignedByte(); + buf.readUnsignedByte(); // length + + if (type == MSG_GPS || type == MSG_GPS_SENSORS) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setDay(buf.readUnsignedByte()) + .setMonth(buf.getByte(buf.readerIndex()) & 0x0f) + .setYear(2010 + (buf.readUnsignedByte() >> 4)) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + double latitude = buf.readUnsignedByte(); + latitude += buf.readUnsignedByte() / 60.0; + latitude += buf.readUnsignedByte() / 6000.0; + latitude += buf.readUnsignedByte() / 600000.0; + + double longitude = buf.readUnsignedByte(); + longitude += buf.readUnsignedByte() / 60.0; + longitude += buf.readUnsignedByte() / 6000.0; + longitude += buf.readUnsignedByte() / 600000.0; + + int flags = buf.readUnsignedByte(); + if (BitUtil.check(flags, 0)) { + latitude = -latitude; + } + if (BitUtil.check(flags, 1)) { + longitude = -longitude; + } + position.setLatitude(latitude); + position.setLongitude(longitude); + + int satellites = buf.readUnsignedByte(); + position.set(Position.KEY_SATELLITES, satellites); + position.setValid(satellites >= 3); + + position.setSpeed(buf.readUnsignedByte()); + + double course = buf.readUnsignedByte() << 1; + course += (flags >> 2) & 1; + course += buf.readUnsignedByte() / 100.0; + position.setCourse(course); + + if (type == MSG_GPS_SENSORS) { + double power = buf.readUnsignedByte(); + power += buf.readUnsignedByte() << 8; + position.set(Position.KEY_POWER, power * 0.01); + + double battery = buf.readUnsignedByte(); + battery += buf.readUnsignedByte() << 8; + position.set(Position.KEY_BATTERY, battery * 0.01); + + buf.skipBytes(6); + } + + positions.add(position); + + } else if (type == MSG_EVENTS) { + + buf.skipBytes(13); + } + } + + return positions; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/PluginProtocol.java b/src/main/java/org/traccar/protocol/PluginProtocol.java new file mode 100644 index 000000000..d5f28da9d --- /dev/null +++ b/src/main/java/org/traccar/protocol/PluginProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class PluginProtocol extends BaseProtocol { + + public PluginProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new PluginProtocolDecoder(PluginProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java b/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java new file mode 100644 index 000000000..949c994ee --- /dev/null +++ b/src/main/java/org/traccar/protocol/PluginProtocolDecoder.java @@ -0,0 +1,139 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class PluginProtocolDecoder extends BaseProtocolDecoder { + + public PluginProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression("[^0-9,]*,?") + .number("([^,]+),") // device id + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(-?d+.d+),") // longitude + .number("(-?d+.d+),") // latitude + .number("(d+.?d*),") // speed + .number("(d+),") // course + .number("(-?d+),") // altitude + .number("(-?d+),") // satellites + .number("d+,") // type + .number("(d+.?d*),") // odometer + .number("(d+),") // status + .number("(d+.?d*),") // fuel + .expression("[^,]*,") + .text("0") + .groupBegin() + .number(",(-?d+.?d*)") // temperature 1 + .number(",(-?d+.?d*)") // temperature 2 + .number(",d+") // oil level + .number(",(d+)") // rpm + .number(",(d+)") // obd speed + .number(",d+") // people up + .number(",d+") // people down + .number(",d+") // obd status + .number(",d+") // fuel intake air temperature + .number(",(d+)") // throttle + .number(",(d+)") // battery + .groupEnd("?") + .groupBegin() + .text(",+,") + .number("(d+),") // event + .groupEnd("?") + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime()); + position.setLongitude(parser.nextDouble()); + position.setLatitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextInt()); + position.setAltitude(parser.nextInt()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_ODOMETER, (long) (parser.nextDouble() * 1000)); + + int status = parser.nextInt(); + position.setValid(BitUtil.check(status, 0)); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 1)); + for (int i = 0; i < 4; i++) { + position.set(Position.PREFIX_IN + (i + 1), BitUtil.check(status, 20 + i)); + } + position.set(Position.KEY_STATUS, status); + + position.set(Position.KEY_FUEL_LEVEL, parser.nextDouble()); + + if (parser.hasNext(6)) { + position.set(Position.PREFIX_TEMP + 1, parser.nextDouble()); + position.set(Position.PREFIX_TEMP + 2, parser.nextDouble()); + position.set(Position.KEY_RPM, parser.nextInt()); + position.set(Position.KEY_OBD_SPEED, parser.nextInt()); + position.set(Position.KEY_THROTTLE, parser.nextInt() * 0.1); + position.set(Position.KEY_POWER, parser.nextInt() * 0.1); + } + + if (parser.hasNext()) { + int event = parser.nextInt(); + switch (event) { + case 11317: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 11319: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + default: + break; + + } + position.set(Position.KEY_EVENT, event); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/PretraceProtocol.java b/src/main/java/org/traccar/protocol/PretraceProtocol.java new file mode 100644 index 000000000..9d35c1c2f --- /dev/null +++ b/src/main/java/org/traccar/protocol/PretraceProtocol.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class PretraceProtocol extends BaseProtocol { + + public PretraceProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_POSITION_PERIODIC); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ')')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new PretraceProtocolEncoder(PretraceProtocol.this)); + pipeline.addLast(new PretraceProtocolDecoder(PretraceProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/PretraceProtocolDecoder.java b/src/main/java/org/traccar/protocol/PretraceProtocolDecoder.java new file mode 100644 index 000000000..a19384e62 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PretraceProtocolDecoder.java @@ -0,0 +1,132 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class PretraceProtocolDecoder extends BaseProtocolDecoder { + + public PretraceProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("(") + .number("(d{15})") // imei + .number("Uddd") // type + .number("d") // gps type + .expression("([AV])") // validity + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd)") // time (hhmmss) + .number("(dd)(dd.dddd)") // latitude + .expression("([NS])") + .number("(ddd)(dd.dddd)") // longitude + .expression("([EW])") + .number("(ddd)") // speed + .number("(ddd)") // course + .number("(xxx)") // altitude + .number("(x{8})") // odometer + .number("(x)") // satellites + .number("(dd)") // hdop + .number("(dd)") // gsm + .expression("(.{8}),&") // state + .expression("(.+)?") // optional data + .text("^") + .number("xx") // checksum + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(parser.next().equals("A")); + + position.setTime(parser.nextDateTime()); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt(0))); + position.setCourse(parser.nextInt(0)); + position.setAltitude(parser.nextHexInt(0)); + + position.set(Position.KEY_ODOMETER, parser.nextHexInt(0)); + position.set(Position.KEY_SATELLITES, parser.nextHexInt(0)); + position.set(Position.KEY_HDOP, parser.nextInt(0)); + position.set(Position.KEY_RSSI, parser.nextInt(0)); + + parser.next(); // state + + if (parser.hasNext()) { + for (String value : parser.next().split(",")) { + switch (value.charAt(0)) { + case 'P': + if (value.charAt(1) == '1') { + if (value.charAt(4) == '%') { + position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(value.substring(2, 4))); + } else { + position.set(Position.KEY_BATTERY, Integer.parseInt(value.substring(2), 16) * 0.01); + } + } else { + position.set(Position.KEY_POWER, Integer.parseInt(value.substring(2), 16) * 0.01); + } + break; + case 'T': + double temperature = Integer.parseInt(value.substring(2), 16) * 0.25; + if (value.charAt(1) == '1') { + position.set(Position.KEY_DEVICE_TEMP, temperature); + } else { + position.set(Position.PREFIX_TEMP + (value.charAt(1) - '0'), temperature); + } + break; + case 'F': + position.set("fuel" + (value.charAt(1) - '0'), Integer.parseInt(value.substring(2), 16) * 0.01); + break; + case 'R': + position.set(Position.KEY_DRIVER_UNIQUE_ID, value.substring(3)); + break; + default: + break; + } + } + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/PretraceProtocolEncoder.java b/src/main/java/org/traccar/protocol/PretraceProtocolEncoder.java new file mode 100644 index 000000000..1083a252e --- /dev/null +++ b/src/main/java/org/traccar/protocol/PretraceProtocolEncoder.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocolEncoder; +import org.traccar.Context; +import org.traccar.helper.Checksum; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class PretraceProtocolEncoder extends BaseProtocolEncoder { + + public PretraceProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private String formatCommand(String uniqueId, String data) { + String content = uniqueId + data; + return String.format("(%s^%02X)", content, Checksum.xor(content)); + } + + @Override + protected Object encodeCommand(Command command) { + + String uniqueId = Context.getIdentityManager().getById(command.getDeviceId()).getUniqueId(); + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return formatCommand(uniqueId, command.getString(Command.KEY_DATA)); + case Command.TYPE_POSITION_PERIODIC: + return formatCommand( + uniqueId, String.format("D221%1$d,%1$d,,", command.getInteger(Command.KEY_FREQUENCY))); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/PricolProtocol.java b/src/main/java/org/traccar/protocol/PricolProtocol.java new file mode 100644 index 000000000..6821cd949 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PricolProtocol.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.FixedLengthFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class PricolProtocol extends BaseProtocol { + + public PricolProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new FixedLengthFrameDecoder(64)); + pipeline.addLast(new PricolProtocolDecoder(PricolProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new PricolProtocolDecoder(PricolProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/PricolProtocolDecoder.java b/src/main/java/org/traccar/protocol/PricolProtocolDecoder.java new file mode 100644 index 000000000..190c68258 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PricolProtocolDecoder.java @@ -0,0 +1,98 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; + +public class PricolProtocolDecoder extends BaseProtocolDecoder { + + public PricolProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // header + + DeviceSession deviceSession = getDeviceSession( + channel, remoteAddress, buf.readSlice(7).toString(StandardCharsets.US_ASCII)); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set("eventType", buf.readUnsignedByte()); + position.set("packetVersion", buf.readUnsignedByte()); + position.set(Position.KEY_STATUS, buf.readUnsignedByte()); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + position.set(Position.KEY_GPS, buf.readUnsignedByte()); + + position.setTime(new DateBuilder() + .setDateReverse(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()).getDate()); + + position.setValid(true); + + double lat = buf.getUnsignedShort(buf.readerIndex()) / 100; + lat += (buf.readUnsignedShort() % 100 * 10000 + buf.readUnsignedShort()) / 600000.0; + position.setLatitude(buf.readUnsignedByte() == 'S' ? -lat : lat); + + double lon = buf.getUnsignedMedium(buf.readerIndex()) / 100; + lon += (buf.readUnsignedMedium() % 100 * 10000 + buf.readUnsignedShort()) / 600000.0; + position.setLongitude(buf.readUnsignedByte() == 'W' ? -lon : lon); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + + position.set(Position.KEY_INPUT, buf.readUnsignedShort()); + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + + position.set("analogAlerts", buf.readUnsignedByte()); + position.set("customAlertTypes", buf.readUnsignedShort()); + + for (int i = 1; i <= 5; i++) { + position.set(Position.PREFIX_ADC + i, buf.readUnsignedShort()); + } + + position.set(Position.KEY_ODOMETER, buf.readUnsignedMedium()); + position.set(Position.KEY_RPM, buf.readUnsignedShort()); + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage( + Unpooled.copiedBuffer("ACK", StandardCharsets.US_ASCII), remoteAddress)); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/ProgressProtocol.java b/src/main/java/org/traccar/protocol/ProgressProtocol.java new file mode 100644 index 000000000..aac84205d --- /dev/null +++ b/src/main/java/org/traccar/protocol/ProgressProtocol.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; +public class ProgressProtocol extends BaseProtocol { + + public ProgressProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 2, 4, 0, true)); + pipeline.addLast(new ProgressProtocolDecoder(ProgressProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ProgressProtocolDecoder.java b/src/main/java/org/traccar/protocol/ProgressProtocolDecoder.java new file mode 100644 index 000000000..0025cd9e7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ProgressProtocolDecoder.java @@ -0,0 +1,172 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class ProgressProtocolDecoder extends BaseProtocolDecoder { + + private long lastIndex; + private long newIndex; + + public ProgressProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_NULL = 0; + public static final int MSG_IDENT = 1; + public static final int MSG_IDENT_FULL = 2; + public static final int MSG_POINT = 10; + public static final int MSG_LOG_SYNC = 100; + public static final int MSG_LOGMSG = 101; + public static final int MSG_TEXT = 102; + public static final int MSG_ALARM = 200; + public static final int MSG_ALARM_RECIEVED = 201; + + private void requestArchive(Channel channel) { + if (lastIndex == 0) { + lastIndex = newIndex; + } else if (newIndex > lastIndex) { + ByteBuf request = Unpooled.buffer(12); + request.writeShortLE(MSG_LOG_SYNC); + request.writeShortLE(4); + request.writeIntLE((int) lastIndex); + request.writeIntLE(0); + channel.writeAndFlush(new NetworkMessage(request, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + int type = buf.readUnsignedShortLE(); + buf.readUnsignedShortLE(); // length + + if (type == MSG_IDENT || type == MSG_IDENT_FULL) { + + buf.readUnsignedIntLE(); // id + int length = buf.readUnsignedShortLE(); + buf.skipBytes(length); + length = buf.readUnsignedShortLE(); + buf.skipBytes(length); + length = buf.readUnsignedShortLE(); + String imei = buf.readSlice(length).toString(StandardCharsets.US_ASCII); + getDeviceSession(channel, remoteAddress, imei); + + } else if (type == MSG_POINT || type == MSG_ALARM || type == MSG_LOGMSG) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + int recordCount = 1; + if (type == MSG_LOGMSG) { + recordCount = buf.readUnsignedShortLE(); + } + + for (int j = 0; j < recordCount; j++) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (type == MSG_LOGMSG) { + position.set(Position.KEY_ARCHIVE, true); + int subtype = buf.readUnsignedShortLE(); + if (subtype == MSG_ALARM) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + if (buf.readUnsignedShortLE() > buf.readableBytes()) { + lastIndex += 1; + break; // workaround for device bug + } + lastIndex = buf.readUnsignedIntLE(); + position.set(Position.KEY_INDEX, lastIndex); + } else { + newIndex = buf.readUnsignedIntLE(); + } + + position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); + position.setLatitude(buf.readIntLE() * 180.0 / 0x7FFFFFFF); + position.setLongitude(buf.readIntLE() * 180.0 / 0x7FFFFFFF); + position.setSpeed(buf.readUnsignedIntLE() * 0.01); + position.setCourse(buf.readUnsignedShortLE() * 0.01); + position.setAltitude(buf.readUnsignedShortLE() * 0.01); + + int satellites = buf.readUnsignedByte(); + position.setValid(satellites >= 3); + position.set(Position.KEY_SATELLITES, satellites); + + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + + long extraFlags = buf.readLongLE(); + + if (BitUtil.check(extraFlags, 0)) { + int count = buf.readUnsignedShortLE(); + for (int i = 1; i <= count; i++) { + position.set(Position.PREFIX_ADC + i, buf.readUnsignedShortLE()); + } + } + + if (BitUtil.check(extraFlags, 1)) { + int size = buf.readUnsignedShortLE(); + position.set("can", buf.toString(buf.readerIndex(), size, StandardCharsets.US_ASCII)); + buf.skipBytes(size); + } + + if (BitUtil.check(extraFlags, 2)) { + position.set("passenger", ByteBufUtil.hexDump(buf.readSlice(buf.readUnsignedShortLE()))); + } + + if (type == MSG_ALARM) { + position.set(Position.KEY_ALARM, true); + byte[] response = {(byte) 0xC9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + channel.writeAndFlush(new NetworkMessage(Unpooled.wrappedBuffer(response), remoteAddress)); + } + + buf.readUnsignedIntLE(); // crc + + positions.add(position); + } + + requestArchive(channel); + + return positions; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/PstFrameDecoder.java b/src/main/java/org/traccar/protocol/PstFrameDecoder.java new file mode 100644 index 000000000..dbafd1476 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PstFrameDecoder.java @@ -0,0 +1,51 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class PstFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + while (buf.isReadable() && buf.getByte(buf.readerIndex()) == 0x28) { + buf.skipBytes(1); + } + + int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x29); + if (endIndex > 0) { + ByteBuf result = Unpooled.buffer(endIndex - buf.readerIndex()); + while (buf.readerIndex() < endIndex) { + int b = buf.readUnsignedByte(); + if (b == 0x27) { + b = buf.readUnsignedByte() ^ 0x40; + } + result.writeByte(b); + } + buf.skipBytes(1); + return result; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/PstProtocol.java b/src/main/java/org/traccar/protocol/PstProtocol.java new file mode 100644 index 000000000..0ed9affd8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/PstProtocol.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class PstProtocol extends BaseProtocol { + + public PstProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new PstProtocolDecoder(PstProtocol.this)); + } + }); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new PstFrameDecoder()); + pipeline.addLast(new PstProtocolDecoder(PstProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/PstProtocolDecoder.java b/src/main/java/org/traccar/protocol/PstProtocolDecoder.java new file mode 100644 index 000000000..23e2afbbd --- /dev/null +++ b/src/main/java/org/traccar/protocol/PstProtocolDecoder.java @@ -0,0 +1,116 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; + +public class PstProtocolDecoder extends BaseProtocolDecoder { + + public PstProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_STATUS = 0x05; + + private Date readDate(ByteBuf buf) { + long value = buf.readUnsignedInt(); + return new DateBuilder() + .setYear((int) BitUtil.between(value, 26, 32)) + .setMonth((int) BitUtil.between(value, 22, 26)) + .setDay((int) BitUtil.between(value, 17, 22)) + .setHour((int) BitUtil.between(value, 12, 17)) + .setMinute((int) BitUtil.between(value, 6, 12)) + .setSecond((int) BitUtil.between(value, 0, 6)).getDate(); + } + + private double readCoordinate(ByteBuf buf) { + long value = buf.readUnsignedInt(); + int sign = BitUtil.check(value, 31) ? -1 : 1; + value = BitUtil.to(value, 31); + return sign * (BitUtil.from(value, 16) + BitUtil.to(value, 16) * 0.00001) / 60; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + String id = String.valueOf(buf.readUnsignedInt()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + buf.readUnsignedByte(); // version + buf.readUnsignedInt(); // index + + int type = buf.readUnsignedByte(); + + if (type == MSG_STATUS) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setDeviceTime(readDate(buf)); + + buf.readUnsignedByte(); + + int count = buf.readUnsignedByte(); + for (int i = 0; i < count; i++) { + + int tag = buf.readUnsignedByte(); + int length = buf.readUnsignedByte(); + + switch (tag) { + case 0x0D: + int battery = buf.readUnsignedByte(); + if (battery <= 20) { + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte() * 5); + } + break; + case 0x10: + position.setFixTime(readDate(buf)); + position.setLatitude(readCoordinate(buf)); + position.setLongitude(readCoordinate(buf)); + position.setSpeed(buf.readUnsignedByte()); + position.setCourse(buf.readUnsignedByte() * 2); + position.setAltitude(buf.readShort()); + buf.readUnsignedInt(); // gps condition + break; + default: + buf.skipBytes(length); + break; + } + } + + return position.getFixTime() != null ? position : null; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt215Protocol.java b/src/main/java/org/traccar/protocol/Pt215Protocol.java new file mode 100644 index 000000000..09bd9b121 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Pt215Protocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Pt215Protocol extends BaseProtocol { + + public Pt215Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2, 1, -1, 0)); + pipeline.addLast(new Pt215ProtocolDecoder(Pt215Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt215ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Pt215ProtocolDecoder.java new file mode 100644 index 000000000..48ce7dede --- /dev/null +++ b/src/main/java/org/traccar/protocol/Pt215ProtocolDecoder.java @@ -0,0 +1,118 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class Pt215ProtocolDecoder extends BaseProtocolDecoder { + + public Pt215ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN = 0x01; + public static final int MSG_HEARTBEAT = 0x08; + public static final int MSG_GPS_REALTIME = 0x10; + public static final int MSG_GPS_OFFLINE = 0x11; + public static final int MSG_STATUS = 0x13; + + private void sendResponse( + Channel channel, SocketAddress remoteAddress, int type, ByteBuf content) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte('X'); + response.writeByte('X'); + response.writeByte(content != null ? 1 + content.readableBytes() : 1); + response.writeByte(type); + if (content != null) { + response.writeBytes(content); + content.release(); + } + response.writeByte('\r'); + response.writeByte('\n'); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + buf.readUnsignedByte(); // length + int type = buf.readUnsignedByte(); + + if (type == MSG_LOGIN) { + + getDeviceSession(channel, remoteAddress, ByteBufUtil.hexDump(buf.readSlice(8)).substring(1)); + sendResponse(channel, remoteAddress, type, null); + + } else if (type == MSG_GPS_OFFLINE || type == MSG_GPS_REALTIME) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + sendResponse(channel, remoteAddress, type, buf.retainedSlice(buf.readerIndex(), 6)); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + double latitude = buf.readUnsignedInt() / 60.0 / 30000.0; + double longitude = buf.readUnsignedInt() / 60.0 / 30000.0; + + int flags = buf.readUnsignedShort(); + position.setCourse(BitUtil.to(flags, 10)); + position.setValid(BitUtil.check(flags, 12)); + + if (!BitUtil.check(flags, 10)) { + latitude = -latitude; + } + if (BitUtil.check(flags, 11)) { + longitude = -longitude; + } + + position.setLatitude(latitude); + position.setLongitude(longitude); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt3000Protocol.java b/src/main/java/org/traccar/protocol/Pt3000Protocol.java new file mode 100644 index 000000000..1ad0026a3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Pt3000Protocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Pt3000Protocol extends BaseProtocol { + + public Pt3000Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, 'd')); // probably wrong + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Pt3000ProtocolDecoder(Pt3000Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt3000ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Pt3000ProtocolDecoder.java new file mode 100644 index 000000000..e7f9e062a --- /dev/null +++ b/src/main/java/org/traccar/protocol/Pt3000ProtocolDecoder.java @@ -0,0 +1,83 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.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 Pt3000ProtocolDecoder extends BaseProtocolDecoder { + + public Pt3000ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("%(d+),") // imei + .text("$GPRMC,") + .number("(dd)(dd)(dd).?d*,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + 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()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt502FrameDecoder.java b/src/main/java/org/traccar/protocol/Pt502FrameDecoder.java new file mode 100644 index 000000000..316cd987f --- /dev/null +++ b/src/main/java/org/traccar/protocol/Pt502FrameDecoder.java @@ -0,0 +1,73 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +import java.nio.charset.StandardCharsets; + +public class Pt502FrameDecoder extends BaseFrameDecoder { + + private static final int BINARY_HEADER = 5; + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 10) { + return null; + } + + if (buf.getUnsignedByte(buf.readerIndex()) == 0xbf + && buf.toString(buf.readerIndex() + BINARY_HEADER, 4, StandardCharsets.US_ASCII).equals("$PHD")) { + + int length = buf.getUnsignedShortLE(buf.readerIndex() + 3); + if (buf.readableBytes() >= length) { + buf.skipBytes(BINARY_HEADER); + ByteBuf result = buf.readRetainedSlice(length - BINARY_HEADER - 2); + buf.skipBytes(2); // line break + return result; + } + + } else { + + if (buf.getUnsignedByte(buf.readerIndex()) == 0xbf) { + buf.skipBytes(BINARY_HEADER); + } + + int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '\r'); + if (index < 0) { + index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '\n'); + } + + if (index > 0) { + ByteBuf result = buf.readRetainedSlice(index - buf.readerIndex()); + while (buf.isReadable() + && (buf.getByte(buf.readerIndex()) == '\r' || buf.getByte(buf.readerIndex()) == '\n')) { + buf.skipBytes(1); + } + return result; + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt502Protocol.java b/src/main/java/org/traccar/protocol/Pt502Protocol.java new file mode 100644 index 000000000..56444fb42 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Pt502Protocol.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class Pt502Protocol extends BaseProtocol { + + public Pt502Protocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_SET_TIMEZONE, + Command.TYPE_ALARM_SPEED, + Command.TYPE_OUTPUT_CONTROL, + Command.TYPE_REQUEST_PHOTO); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new Pt502FrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new Pt502ProtocolEncoder(Pt502Protocol.this)); + pipeline.addLast(new Pt502ProtocolDecoder(Pt502Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt502ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Pt502ProtocolDecoder.java new file mode 100644 index 000000000..0afec67ad --- /dev/null +++ b/src/main/java/org/traccar/protocol/Pt502ProtocolDecoder.java @@ -0,0 +1,212 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2012 Luis Parada (luis.parada@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.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +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 Pt502ProtocolDecoder extends BaseProtocolDecoder { + + private static final int MAX_CHUNK_SIZE = 960; + + private ByteBuf photo; + + public Pt502ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .any().text("$") + .expression("([^,]+),") // type + .number("(d+),") // id + .number("(dd)(dd)(dd).(ddd),") // time (hhmmss.sss) + .expression("([AV]),") // validity + .number("(d+)(dd.dddd),") // latitude + .expression("([NS]),") + .number("(d+)(dd.dddd),") // longitude + .expression("([EW]),") + .number("(d+.d+)?,") // speed + .number("(d+.d+)?,") // course + .number("(dd)(dd)(dd),,,") // date (ddmmyy) + .expression("./") + .expression("([01])+,") // input + .expression("([01])+/") // output + .expression("([^/]+)?/") // adc + .number("(d+)") // odometer + .expression("/([^/]+)?/") // rfid + .number("(xxx)").optional(2) // state + .any() + .compile(); + + private String decodeAlarm(String value) { + switch (value) { + case "IN1": + return Position.ALARM_SOS; + case "GOF": + return Position.ALARM_GEOFENCE; + case "TOW": + return Position.ALARM_TOW; + case "HDA": + return Position.ALARM_ACCELERATION; + case "HDB": + return Position.ALARM_BRAKING; + case "FDA": + return Position.ALARM_FATIGUE_DRIVING; + case "SKA": + return Position.ALARM_VIBRATION; + case "PMA": + return Position.ALARM_MOVEMENT; + case "CPA": + return Position.ALARM_POWER_CUT; + default: + return null; + } + } + + private Position decodePosition(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.set(Position.KEY_ALARM, decodeAlarm(parser.next())); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + position.set(Position.KEY_INPUT, parser.next()); + position.set(Position.KEY_OUTPUT, parser.next()); + + if (parser.hasNext()) { + String[] values = parser.next().split(","); + for (int i = 0; i < values.length; i++) { + position.set(Position.PREFIX_ADC + (i + 1), Integer.parseInt(values[i], 16)); + } + } + + position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + + if (parser.hasNext()) { + int value = parser.nextHexInt(0); + position.set(Position.KEY_BATTERY, value >> 8); + position.set(Position.KEY_RSSI, (value >> 4) & 0xf); + position.set(Position.KEY_SATELLITES, value & 0xf); + } + + return position; + } + + private void requestPhotoFragment(Channel channel) { + if (channel != null) { + int offset = photo.writerIndex(); + int size = Math.min(photo.writableBytes(), MAX_CHUNK_SIZE); + channel.writeAndFlush(new NetworkMessage("#PHD" + offset + "," + size + "\r\n", channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int typeEndIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ','); + String type = buf.toString(buf.readerIndex(), typeEndIndex - buf.readerIndex(), StandardCharsets.US_ASCII); + + if (type.startsWith("$PHD")) { + + int dataIndex = buf.indexOf(typeEndIndex + 1, buf.writerIndex(), (byte) ',') + 1; + buf.readerIndex(dataIndex); + + if (photo != null) { + + photo.writeBytes(buf.readSlice(buf.readableBytes())); + + if (photo.writableBytes() > 0) { + + requestPhotoFragment(channel); + + } else { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + String uniqueId = Context.getIdentityManager().getById(deviceSession.getDeviceId()).getUniqueId(); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(uniqueId, photo, "jpg")); + photo.release(); + photo = null; + + return position; + + } + + } + + } else { + + if (type.startsWith("$PHO")) { + int size = Integer.parseInt(type.split("-")[0].substring(4)); + if (size > 0) { + photo = Unpooled.buffer(size); + requestPhotoFragment(channel); + } + } + + return decodePosition(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII)); + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt502ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Pt502ProtocolEncoder.java new file mode 100644 index 000000000..364ecae86 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Pt502ProtocolEncoder.java @@ -0,0 +1,63 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import java.util.TimeZone; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class Pt502ProtocolEncoder extends StringProtocolEncoder implements StringProtocolEncoder.ValueFormatter { + + public Pt502ProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + public String formatValue(String key, Object value) { + if (key.equals(Command.KEY_TIMEZONE)) { + return String.valueOf(TimeZone.getTimeZone((String) value).getRawOffset() / 3600000); + } + + return null; + } + + @Override + protected String formatCommand(Command command, String format, String... keys) { + return formatCommand(command, format, this, keys); + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return formatCommand(command, "%s\r\n", Command.KEY_DATA); + case Command.TYPE_OUTPUT_CONTROL: + return formatCommand(command, "#OPC%s,%s\r\n", Command.KEY_INDEX, Command.KEY_DATA); + case Command.TYPE_SET_TIMEZONE: + return formatCommand(command, "#TMZ%s\r\n", Command.KEY_TIMEZONE); + case Command.TYPE_ALARM_SPEED: + return formatCommand(command, "#SPD%s\r\n", Command.KEY_DATA); + case Command.TYPE_REQUEST_PHOTO: + return formatCommand(command, "#PHO\r\n"); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt60Protocol.java b/src/main/java/org/traccar/protocol/Pt60Protocol.java new file mode 100644 index 000000000..c502426c5 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Pt60Protocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Pt60Protocol extends BaseProtocol { + + public Pt60Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "@R#@", "@E#@")); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Pt60ProtocolDecoder(Pt60Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Pt60ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Pt60ProtocolDecoder.java new file mode 100644 index 000000000..6a3fe2734 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Pt60ProtocolDecoder.java @@ -0,0 +1,184 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.regex.Pattern; + +public class Pt60ProtocolDecoder extends BaseProtocolDecoder { + + public Pt60ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_G_TRACK = 6; + public static final int MSG_G_STEP_COUNT = 13; + public static final int MSG_G_HEART_RATE = 14; + + public static final int MSG_B_POSITION = 1; + + private static final Pattern PATTERN = new PatternBuilder() + .expression("@(.)#@[,|]") // header + .number("V?dd[,|]") // protocol version + .number("(d+)[,|]") // type + .number("(d+)[,|]") // imei + .number("d+[,|]") // imsi + .groupBegin() + .expression("[^,|]+[,|]").optional() // firmware version + .number("[01][,|]") // state + .number("d+[,|]") // battery + .groupEnd("?") + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)[,|]") // time (hhmmss) + .expression("(.*)") // data + .expression("[,|]") + .compile(); + + private void sendResponse(Channel channel, SocketAddress remoteAddress, String format, int type, String imei) { + if (channel != null) { + String message; + String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); + if (format.equals("G")) { + message = String.format("@G#@,V01,38,%s,@R#@", time); + } else { + message = String.format("@B#@|01|%03d|%s|0|%s|@E#@", type + 1, imei, time); + } + channel.writeAndFlush(new NetworkMessage(message, remoteAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + String format = parser.next(); + int type = parser.nextInt(); + String imei = parser.next(); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + sendResponse(channel, remoteAddress, format, type, imei); + + if (format.equals("G")) { + + if (type != MSG_G_TRACK && type != MSG_G_STEP_COUNT && type != MSG_G_HEART_RATE) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.setDeviceTime(parser.nextDateTime()); + + String[] values = parser.next().split(","); + + if (type == MSG_G_TRACK) { + + position.setValid(true); + position.setFixTime(position.getDeviceTime()); + + String[] coordinates = values[0].split(";"); + position.setLatitude(Double.parseDouble(coordinates[0])); + position.setLongitude(Double.parseDouble(coordinates[1])); + + } else { + + getLastLocation(position, position.getDeviceTime()); + + switch (type) { + case MSG_G_STEP_COUNT: + position.set(Position.KEY_STEPS, Integer.parseInt(values[0])); + break; + case MSG_G_HEART_RATE: + position.set(Position.KEY_HEART_RATE, Integer.parseInt(values[0])); + position.set(Position.KEY_BATTERY, Integer.parseInt(values[1])); + break; + default: + break; + } + + } + + return position; + + } else { + + if (type != MSG_B_POSITION) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.setDeviceTime(parser.nextDateTime()); + + String[] values = parser.next().split("\\|"); + + if (Integer.parseInt(values[values.length - 1]) == 2) { + + getLastLocation(position, position.getDeviceTime()); + + Network network = new Network(); + + for (int i = 0; i < values.length - 1; i++) { + String[] cellValues = values[i].split(","); + CellTower tower = new CellTower(); + tower.setCellId(Long.parseLong(cellValues[0])); + tower.setLocationAreaCode(Integer.parseInt(cellValues[1])); + tower.setMobileNetworkCode(Integer.parseInt(cellValues[2])); + tower.setMobileCountryCode(Integer.parseInt(cellValues[3])); + tower.setSignalStrength(Integer.parseInt(cellValues[4])); + network.addCellTower(tower); + } + + position.setNetwork(network); + + + } else { + + position.setValid(true); + position.setFixTime(position.getDeviceTime()); + + position.setLatitude(Double.parseDouble(values[0])); + position.setLongitude(Double.parseDouble(values[1])); + + } + + return position; + + } + } + +} diff --git a/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java b/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java new file mode 100644 index 000000000..c9db10610 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RaceDynamicsProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class RaceDynamicsProtocol extends BaseProtocol { + + public RaceDynamicsProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1500)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new RaceDynamicsProtocolDecoder(RaceDynamicsProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/RaceDynamicsProtocolDecoder.java b/src/main/java/org/traccar/protocol/RaceDynamicsProtocolDecoder.java new file mode 100644 index 000000000..f441bf8ed --- /dev/null +++ b/src/main/java/org/traccar/protocol/RaceDynamicsProtocolDecoder.java @@ -0,0 +1,171 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +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.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +public class RaceDynamicsProtocolDecoder extends BaseProtocolDecoder { + + public RaceDynamicsProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN = 12; + public static final int MSG_LOCATION = 15; + + private static final Pattern PATTERN_LOGIN = new PatternBuilder() + .text("$GPRMC,") + .number("d+,") // type + .number("d{6},") // date + .number("d{6},") // time + .number("(d{15}),") + .compile(); + + private static final Pattern PATTERN_LOCATION = new PatternBuilder() + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+),") // speed + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(-?d+),") // altitude + .number("(d+),") // satellites + .number("([01]),") // ignition + .number("(d+),") // index + .text("%,") + .number("([^,]+),") // ibutton + .number("d+,") // acceleration + .number("d+,") // deceleration + .number("[01],") // cruise control + .number("[01],") // seat belt + .number("[01],") // wrong ibutton + .number("(d+),") // power + .number("[01],") // power status + .number("(d+),") // battery + .number("([01]),") // panic + .number("d+,") + .number("d+,") + .number("(d),") // overspeed + .number("d+,") // speed limit + .number("d+,") // tachometer + .number("d+,d+,d+,") // aux + .number("d+,") // geofence id + .number("d+,") // road speed type + .number("d+,") // ibutton count + .number("(d),") // overdriver alert + .any() + .compile(); + + private String imei; + + private void sendResponse(Channel channel, SocketAddress remoteAddress, int type) { + if (channel != null) { + String response = String.format( + "$GPRMC,%1$d,%2$td%2$tm%2$ty,%2$tH%2$tM%2$tS,%3$s,\r\n", type, new Date(), imei); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + int type = Integer.parseInt(sentence.substring(7, 9)); + + if (type == MSG_LOGIN) { + + Parser parser = new Parser(PATTERN_LOGIN, sentence); + if (parser.matches()) { + imei = parser.next(); + getDeviceSession(channel, remoteAddress, imei); + sendResponse(channel, remoteAddress, type); + } + + } else if (type == MSG_LOCATION) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + for (String data : sentence.substring(17, sentence.length() - 3).split(",#,#,")) { + Parser parser = new Parser(PATTERN_LOCATION, data); + if (parser.matches()) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble()); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + position.setAltitude(parser.nextInt()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_IGNITION, parser.nextInt() == 1); + position.set(Position.KEY_INDEX, parser.nextInt()); + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + position.set(Position.KEY_POWER, parser.nextInt() * 0.01); + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.01); + position.set(Position.KEY_ALARM, parser.nextInt() > 0 ? Position.ALARM_SOS : null); + position.set(Position.KEY_ALARM, parser.nextInt() > 0 ? Position.ALARM_OVERSPEED : null); + + int overDriver = parser.nextInt(); + if (overDriver > 0) { + position.set("overDriver", overDriver); + } + + positions.add(position); + + } + } + + sendResponse(channel, remoteAddress, type); + + return positions; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/RadarProtocol.java b/src/main/java/org/traccar/protocol/RadarProtocol.java new file mode 100644 index 000000000..9783778f0 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RadarProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class RadarProtocol extends BaseProtocol { + + public RadarProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 12, 2, -14, 0)); + pipeline.addLast(new RadarProtocolDecoder(RadarProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/RadarProtocolDecoder.java b/src/main/java/org/traccar/protocol/RadarProtocolDecoder.java new file mode 100644 index 000000000..b56a081c7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RadarProtocolDecoder.java @@ -0,0 +1,197 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.BitUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.BitSet; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class RadarProtocolDecoder extends BaseProtocolDecoder { + + public RadarProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_TRACKING = 0x4C; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // product id + buf.readUnsignedByte(); // product version + + String serialNumber = String.valueOf(buf.readUnsignedInt()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, serialNumber); + if (deviceSession == null) { + return null; + } + + buf.readUnsignedByte(); // index + buf.readUnsignedInt(); // timestamp + int type = buf.readUnsignedByte(); + buf.readUnsignedShort(); // length + + if (type == MSG_TRACKING) { + + buf.readUnsignedShort(); // memory count + buf.readUnsignedShort(); // memory index + int count = buf.readUnsignedShort(); + buf.readUnsignedShort(); // first index + + List<Position> positions = new LinkedList<>(); + + for (int index = 0; index < count; index++) { + + Position position = new Position(getProtocolName()); + + position.set(Position.KEY_EVENT, buf.readUnsignedShort()); + + int maskLength = buf.readUnsignedByte(); + BitSet mask = BitSet.valueOf(buf.nioBuffer(buf.readerIndex(), maskLength)); + buf.skipBytes(maskLength); + + buf.readUnsignedShort(); // length + + if (mask.get(0)) { + position.setDeviceTime(new Date(buf.readUnsignedInt() * 1000)); + } + if (mask.get(1)) { + position.setFixTime(new Date(buf.readUnsignedInt() * 1000)); + } + if (mask.get(2)) { + position.setLatitude(buf.readInt() / 360000.0); + } + if (mask.get(3)) { + position.setLongitude(buf.readInt() / 360000.0); + } + if (mask.get(4)) { + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort())); + } + if (mask.get(5)) { + position.setCourse(buf.readUnsignedShort() * 0.1); + } + if (mask.get(6)) { + position.setAltitude(buf.readShort()); + } + if (mask.get(7)) { + int flags = buf.readUnsignedByte(); + position.setValid(BitUtil.check(flags, 0)); + position.set(Position.KEY_SATELLITES, BitUtil.from(flags, 4)); + } + if (mask.get(8)) { + long flags = buf.readUnsignedInt(); + position.set(Position.KEY_IGNITION, BitUtil.check(flags, 0)); + position.set(Position.KEY_CHARGE, BitUtil.check(flags, 1)); + position.set(Position.KEY_MOTION, BitUtil.check(flags, 2)); + for (int i = 0; i < 3; i++) { + position.set(Position.PREFIX_IN + i, BitUtil.check(flags, 4 + i)); + } + } + if (mask.get(9)) { + int flags = buf.readUnsignedShort(); + position.set(Position.KEY_BLOCKED, BitUtil.check(flags, 0)); + position.set(Position.PREFIX_IN + 0, BitUtil.check(flags, 4)); + } + for (int i = 10; i <= 14; i++) { + if (mask.get(i)) { + buf.readUnsignedShort(); // reserved + } + } + if (mask.get(15)) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 100); + } + if (mask.get(16)) { + buf.readUnsignedInt(); // reserved + } + for (int i = 17; i <= 27; i++) { + if (mask.get(i)) { + buf.readUnsignedByte(); // reserved + } + } + for (int i = 28; i <= 37; i += 2) { + if (mask.get(i)) { + buf.skipBytes(12); // reserved + } + if (mask.get(i + 1)) { + buf.readUnsignedByte(); // reserved + } + } + if (mask.get(38)) { + buf.skipBytes(6); // driver id + } + if (mask.get(39)) { + buf.readUnsignedShort(); // hardware problems + } + if (mask.get(40)) { + buf.readShort(); // acceleration x + } + if (mask.get(41)) { + buf.readShort(); // acceleration y + } + if (mask.get(42)) { + buf.readShort(); // acceleration z + } + if (mask.get(43)) { + buf.skipBytes(10); // operator + } + if (mask.get(44)) { + buf.readUnsignedShort(); // power + } + for (int i = 45; i <= 49; i++) { + if (mask.get(i)) { + buf.readUnsignedByte(); // reserved + } + } + if (mask.get(50)) { + buf.readShort(); // tilt + } + if (mask.get(51)) { + buf.readUnsignedInt(); // partial hours + } + if (mask.get(52)) { + buf.readUnsignedInt(); // accumulated hours + } + + if (position.getDeviceTime() != null && position.getFixTime() != null) { + positions.add(position); + } + + } + + // ACK 0x9C + + return positions.isEmpty() ? null : positions; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/RaveonProtocol.java b/src/main/java/org/traccar/protocol/RaveonProtocol.java new file mode 100644 index 000000000..44faadb3b --- /dev/null +++ b/src/main/java/org/traccar/protocol/RaveonProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class RaveonProtocol extends BaseProtocol { + + public RaveonProtocol() { + 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 RaveonProtocolDecoder(RaveonProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/RaveonProtocolDecoder.java b/src/main/java/org/traccar/protocol/RaveonProtocolDecoder.java new file mode 100644 index 000000000..50acd20a1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RaveonProtocolDecoder.java @@ -0,0 +1,99 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class RaveonProtocolDecoder extends BaseProtocolDecoder { + + public RaveonProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$PRAVE,") + .number("(d+),") // id + .number("d+,") + .number("(-?)(d+)(dd.d+),") // latitude + .number("(-?)(d+)(dd.d+),") // longitude + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d),") // validity + .number("(d+),") // satellites + .number("(-?d+),") // altitude + .number("(-?d+),") // temperature + .number("(d+.d+),") // power + .number("(d+),") // inputs + .number("(-?d+),") // gsm + .number("(d+),") // speed + .number("(d+),") // course + .expression("([PMACIVSX])?,") // status + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS)); + + position.setValid(parser.nextInt(0) != 0); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + + position.setAltitude(parser.nextInt(0)); + + position.set(Position.PREFIX_TEMP + 1, parser.nextInt(0)); + position.set(Position.KEY_POWER, parser.nextDouble(0)); + position.set(Position.KEY_INPUT, parser.nextInt(0)); + position.set(Position.KEY_RSSI, parser.nextInt(0)); + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt(0))); + position.setCourse(parser.nextInt(0)); + + position.set(Position.KEY_ALARM, parser.next()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/RecodaProtocol.java b/src/main/java/org/traccar/protocol/RecodaProtocol.java new file mode 100644 index 000000000..0bc9870bc --- /dev/null +++ b/src/main/java/org/traccar/protocol/RecodaProtocol.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; +public class RecodaProtocol extends BaseProtocol { + + public RecodaProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 4, 4, -8, 0, true)); + pipeline.addLast(new RecodaProtocolDecoder(RecodaProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/RecodaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RecodaProtocolDecoder.java new file mode 100644 index 000000000..04098225f --- /dev/null +++ b/src/main/java/org/traccar/protocol/RecodaProtocolDecoder.java @@ -0,0 +1,110 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.BitUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class RecodaProtocolDecoder extends BaseProtocolDecoder { + + public RecodaProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_HEARTBEAT = 0x00001001; + public static final int MSG_REQUEST_RESPONSE = 0x20000001; + public static final int MSG_SIGNAL_LINK_REGISTRATION = 0x20001001; + public static final int MSG_EVENT_NOTICE = 0x20002001; + public static final int MSG_GPS_DATA = 0x20001011; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int type = buf.readIntLE(); + buf.readUnsignedIntLE(); // length + + if (type != MSG_HEARTBEAT) { + buf.readUnsignedShortLE(); // version + buf.readUnsignedShortLE(); // index + } + + if (type == MSG_SIGNAL_LINK_REGISTRATION) { + + getDeviceSession(channel, remoteAddress, buf.readSlice(12).toString(StandardCharsets.US_ASCII)); + + } else if (type == MSG_GPS_DATA) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date(buf.readLongLE())); + + int flags = buf.readUnsignedByte(); + + if (BitUtil.check(flags, 0)) { + + buf.readUnsignedShortLE(); // declination + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE())); + + position.setLongitude(buf.readUnsignedByte() + buf.readUnsignedByte() / 60.0); + position.setLatitude(buf.readUnsignedByte() + buf.readUnsignedByte() / 60.0); + + position.setLongitude(position.getLongitude() + buf.readUnsignedIntLE() / 3600.0); + position.setLatitude(position.getLatitude() + buf.readUnsignedIntLE() / 3600.0); + + int status = buf.readUnsignedByte(); + + position.setValid(BitUtil.check(status, 0)); + if (BitUtil.check(status, 1)) { + position.setLongitude(-position.getLongitude()); + } + if (!BitUtil.check(status, 2)) { + position.setLatitude(-position.getLatitude()); + } + + } else { + + getLastLocation(position, position.getDeviceTime()); + + } + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/RetranslatorFrameDecoder.java b/src/main/java/org/traccar/protocol/RetranslatorFrameDecoder.java new file mode 100644 index 000000000..4edd09418 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RetranslatorFrameDecoder.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class RetranslatorFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + int length = 4 + buf.getIntLE(buf.readerIndex()); + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } else { + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/RetranslatorProtocol.java b/src/main/java/org/traccar/protocol/RetranslatorProtocol.java new file mode 100644 index 000000000..fae81f7d2 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RetranslatorProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class RetranslatorProtocol extends BaseProtocol { + + public RetranslatorProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new RetranslatorFrameDecoder()); + pipeline.addLast(new RetranslatorProtocolDecoder(RetranslatorProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/RetranslatorProtocolDecoder.java b/src/main/java/org/traccar/protocol/RetranslatorProtocolDecoder.java new file mode 100644 index 000000000..5bf6cef50 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RetranslatorProtocolDecoder.java @@ -0,0 +1,114 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class RetranslatorProtocolDecoder extends BaseProtocolDecoder { + + public RetranslatorProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(Unpooled.wrappedBuffer(new byte[]{0x11}), remoteAddress)); + } + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedInt(); // length + + int idLength = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x00) - buf.readerIndex(); + String id = buf.readCharSequence(idLength, StandardCharsets.US_ASCII).toString(); + buf.readByte(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + + buf.readUnsignedInt(); // bit flags + + while (buf.isReadable()) { + + buf.readUnsignedShort(); // block type + int blockEnd = buf.readInt() + buf.readerIndex(); + buf.readUnsignedByte(); // security attribute + int dataType = buf.readUnsignedByte(); + + int nameLength = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x00) - buf.readerIndex(); + String name = buf.readCharSequence(nameLength, StandardCharsets.US_ASCII).toString(); + buf.readByte(); + + if (name.equals("posinfo")) { + position.setValid(true); + position.setLongitude(buf.readDoubleLE()); + position.setLatitude(buf.readDoubleLE()); + position.setAltitude(buf.readDoubleLE()); + position.setSpeed(buf.readShort()); + position.setCourse(buf.readShort()); + position.set(Position.KEY_SATELLITES, buf.readByte()); + } else { + switch (dataType) { + case 1: + int len = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x00) - buf.readerIndex(); + position.set(name, buf.readCharSequence(len, StandardCharsets.US_ASCII).toString()); + buf.readByte(); + break; + case 3: + position.set(name, buf.readInt()); + break; + case 4: + position.set(name, buf.readDoubleLE()); + break; + case 5: + position.set(name, buf.readLong()); + break; + default: + break; + } + } + + buf.readerIndex(blockEnd); + + } + + if (position.getLatitude() == 0 && position.getLongitude() == 0) { + getLastLocation(position, position.getDeviceTime()); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/RitiProtocol.java b/src/main/java/org/traccar/protocol/RitiProtocol.java new file mode 100644 index 000000000..de1026672 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RitiProtocol.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; +public class RitiProtocol extends BaseProtocol { + + public RitiProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 105, 2, 3, 0, true)); + pipeline.addLast(new RitiProtocolDecoder(RitiProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/RitiProtocolDecoder.java b/src/main/java/org/traccar/protocol/RitiProtocolDecoder.java new file mode 100644 index 000000000..46267ca90 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RitiProtocolDecoder.java @@ -0,0 +1,102 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 RitiProtocolDecoder extends BaseProtocolDecoder { + + public RitiProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$GPRMC,") + .number("(dd)(dd)(dd).?d*,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(buf.readUnsignedShort())); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.set("mode", buf.readUnsignedByte()); + position.set(Position.KEY_COMMAND, buf.readUnsignedByte()); + position.set(Position.KEY_POWER, buf.readUnsignedShortLE() * 0.001); + + buf.skipBytes(5); // status + buf.readUnsignedShortLE(); // idleCount + buf.readUnsignedShortLE(); // idleTime in seconds + + position.set(Position.KEY_DISTANCE, buf.readUnsignedIntLE()); + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedIntLE()); + + // Parse GPRMC + int end = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '*'); + String gprmc = buf.toString(buf.readerIndex(), end - buf.readerIndex(), StandardCharsets.US_ASCII); + Parser parser = new Parser(PATTERN, gprmc); + if (!parser.matches()) { + return null; + } + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/RoboTrackFrameDecoder.java b/src/main/java/org/traccar/protocol/RoboTrackFrameDecoder.java new file mode 100644 index 000000000..85ed6c76f --- /dev/null +++ b/src/main/java/org/traccar/protocol/RoboTrackFrameDecoder.java @@ -0,0 +1,57 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class RoboTrackFrameDecoder extends BaseFrameDecoder { + + private int messageLength(ByteBuf buf) { + switch (buf.getUnsignedByte(buf.readerIndex())) { + case RoboTrackProtocolDecoder.MSG_ID: + return 69; + case RoboTrackProtocolDecoder.MSG_ACK: + return 3; + case RoboTrackProtocolDecoder.MSG_GPS: + case RoboTrackProtocolDecoder.MSG_GSM: + case RoboTrackProtocolDecoder.MSG_IMAGE_START: + return 24; + case RoboTrackProtocolDecoder.MSG_IMAGE_DATA: + return 8 + buf.getUnsignedShortLE(buf.readerIndex() + 1); + case RoboTrackProtocolDecoder.MSG_IMAGE_END: + return 6; + default: + return Integer.MAX_VALUE; + } + } + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + int length = messageLength(buf); + + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/RoboTrackProtocol.java b/src/main/java/org/traccar/protocol/RoboTrackProtocol.java new file mode 100644 index 000000000..c2c531293 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RoboTrackProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class RoboTrackProtocol extends BaseProtocol { + + public RoboTrackProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new RoboTrackFrameDecoder()); + pipeline.addLast(new RoboTrackProtocolDecoder(RoboTrackProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/RoboTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/RoboTrackProtocolDecoder.java new file mode 100644 index 000000000..b613f31d7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RoboTrackProtocolDecoder.java @@ -0,0 +1,131 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class RoboTrackProtocolDecoder extends BaseProtocolDecoder { + + public RoboTrackProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_ID = 0x00; + public static final int MSG_ACK = 0x80; + public static final int MSG_GPS = 0x03; + public static final int MSG_GSM = 0x04; + public static final int MSG_IMAGE_START = 0x06; + public static final int MSG_IMAGE_DATA = 0x07; + public static final int MSG_IMAGE_END = 0x08; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int type = buf.readUnsignedByte(); + + if (type == MSG_ID) { + + buf.skipBytes(16); // name + + String imei = buf.readSlice(15).toString(StandardCharsets.US_ASCII); + + if (getDeviceSession(channel, remoteAddress, imei) != null && channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(MSG_ACK); + response.writeByte(0x01); // success + response.writeByte(0x66); // checksum + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + } else if (type == MSG_GPS || type == MSG_GSM) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setDeviceTime(new Date(buf.readUnsignedIntLE() * 1000)); + + if (type == MSG_GPS) { + + position.setValid(true); + position.setFixTime(position.getDeviceTime()); + position.setLatitude(buf.readIntLE() * 0.000001); + position.setLongitude(buf.readIntLE() * 0.000001); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readByte())); + + } else { + + getLastLocation(position, position.getDeviceTime()); + + position.setNetwork(new Network(CellTower.from( + buf.readUnsignedShortLE(), buf.readUnsignedShortLE(), + buf.readUnsignedShortLE(), buf.readUnsignedShortLE()))); + + buf.readUnsignedByte(); // reserved + + } + + int value = buf.readUnsignedByte(); + + position.set(Position.KEY_SATELLITES, BitUtil.to(value, 4)); + position.set(Position.KEY_RSSI, BitUtil.between(value, 4, 7)); + position.set(Position.KEY_MOTION, BitUtil.check(value, 7)); + + value = buf.readUnsignedByte(); + + position.set(Position.KEY_CHARGE, BitUtil.check(value, 0)); + + for (int i = 1; i <= 4; i++) { + position.set(Position.PREFIX_IN + i, BitUtil.check(value, i)); + } + + position.set(Position.KEY_BATTERY_LEVEL, BitUtil.from(value, 5) * 100 / 7); + position.set(Position.KEY_DEVICE_TEMP, buf.readByte()); + + for (int i = 1; i <= 3; i++) { + position.set(Position.PREFIX_ADC + i, buf.readUnsignedShortLE()); + } + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/RstProtocol.java b/src/main/java/org/traccar/protocol/RstProtocol.java new file mode 100644 index 000000000..10d11d493 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RstProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class RstProtocol extends BaseProtocol { + + public RstProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new RstProtocolDecoder(RstProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/RstProtocolDecoder.java b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java new file mode 100644 index 000000000..05601ed51 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RstProtocolDecoder.java @@ -0,0 +1,132 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class RstProtocolDecoder extends BaseProtocolDecoder { + + public RstProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("RST;") + .expression("([AL]);") // archive + .expression("([^,]+);") // model + .expression("(.{5});") // firmware + .number("(d{9});") // serial number + .number("(d+);") // index + .number("(d+);") // type + .number("(dd)-(dd)-(dddd) ") // event date + .number("(dd):(dd):(dd);") // event time + .number("(dd)-(dd)-(dddd) ") // fix date + .number("(dd):(dd):(dd);") // fix time + .number("(-?d+.d+);") // latitude + .number("(-?d+.d+);") // longitude + .number("(d+);") // speed + .number("(d+);") // course + .number("(-?d+);") // altitude + .number("([01]);") // valid + .number("(d+);") // satellites + .number("(d+);") // hdop + .number("(xx);") // inputs 1 + .number("(xx);") // inputs 2 + .number("(xx);") // inputs 3 + .number("(xx);") // outputs 1 + .number("(xx);") // outputs 2 + .number("(d+.d+);") // power + .number("(d+.d+);") // battery + .number("(d+);") // odometer + .number("(d+);") // rssi + .number("(xx);") // temperature + .number("x{4};") // sensors + .number("(xx);") // status 1 + .number("(xx);") // status 2 + .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; + } + + String archive = parser.next(); + String model = parser.next(); + String firmware = parser.next(); + String serial = parser.next(); + int index = parser.nextInt(); + parser.nextInt(); // type + + if (channel != null && archive.equals("A")) { + String response = "RST;A;" + model + ";" + firmware + ";" + serial + ";" + index + ";6;FIM;"; + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, serial); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setDeviceTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setFixTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.setCourse(parser.nextInt()); + position.setAltitude(parser.nextInt()); + position.setValid(parser.nextInt() > 0); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_HDOP, parser.nextInt()); + position.set(Position.PREFIX_IN + 1, parser.nextHexInt()); + position.set(Position.PREFIX_IN + 2, parser.nextHexInt()); + position.set(Position.PREFIX_IN + 3, parser.nextHexInt()); + position.set(Position.PREFIX_OUT + 1, parser.nextHexInt()); + position.set(Position.PREFIX_OUT + 2, parser.nextHexInt()); + position.set(Position.KEY_POWER, parser.nextDouble()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_ODOMETER, parser.nextInt()); + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, (int) parser.nextHexInt().byteValue()); + + int status = (parser.nextHexInt() << 8) + parser.nextHexInt(); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 7)); + position.set(Position.KEY_STATUS, status); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocol.java b/src/main/java/org/traccar/protocol/RuptelaProtocol.java new file mode 100644 index 000000000..b8f72336b --- /dev/null +++ b/src/main/java/org/traccar/protocol/RuptelaProtocol.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class RuptelaProtocol extends BaseProtocol { + + public RuptelaProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_REQUEST_PHOTO, + Command.TYPE_CONFIGURATION, + Command.TYPE_GET_VERSION, + Command.TYPE_FIRMWARE_UPDATE, + Command.TYPE_OUTPUT_CONTROL, + Command.TYPE_SET_CONNECTION, + Command.TYPE_SET_ODOMETER); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 2, 0)); + pipeline.addLast(new RuptelaProtocolEncoder(RuptelaProtocol.this)); + pipeline.addLast(new RuptelaProtocolDecoder(RuptelaProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java new file mode 100644 index 000000000..227a9ac91 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java @@ -0,0 +1,297 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.DataConverter; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class RuptelaProtocolDecoder extends BaseProtocolDecoder { + + private ByteBuf photo; + + public RuptelaProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_RECORDS = 1; + public static final int MSG_DEVICE_CONFIGURATION = 2; + public static final int MSG_DEVICE_VERSION = 3; + public static final int MSG_FIRMWARE_UPDATE = 4; + public static final int MSG_SET_CONNECTION = 5; + public static final int MSG_SET_ODOMETER = 6; + public static final int MSG_SMS_VIA_GPRS_RESPONSE = 7; + public static final int MSG_SMS_VIA_GPRS = 8; + public static final int MSG_DTCS = 9; + public static final int MSG_SET_IO = 17; + public static final int MSG_FILES = 37; + public static final int MSG_EXTENDED_RECORDS = 68; + + private Position decodeCommandResponse(DeviceSession deviceSession, int type, ByteBuf buf) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_TYPE, type); + + switch (type) { + case MSG_DEVICE_CONFIGURATION: + case MSG_DEVICE_VERSION: + case MSG_FIRMWARE_UPDATE: + case MSG_SMS_VIA_GPRS_RESPONSE: + position.set(Position.KEY_RESULT, + buf.toString(buf.readerIndex(), buf.readableBytes() - 2, StandardCharsets.US_ASCII).trim()); + return position; + case MSG_SET_IO: + position.set(Position.KEY_RESULT, + String.valueOf(buf.readUnsignedByte())); + return position; + default: + return null; + } + } + + private long readValue(ByteBuf buf, int length, boolean signed) { + switch (length) { + case 1: + return signed ? buf.readByte() : buf.readUnsignedByte(); + case 2: + return signed ? buf.readShort() : buf.readUnsignedShort(); + case 4: + return signed ? buf.readInt() : buf.readUnsignedInt(); + default: + return buf.readLong(); + } + } + + private void decodeParameter(Position position, int id, ByteBuf buf, int length) { + switch (id) { + case 2: + case 3: + case 4: + position.set("di" + (id - 1), readValue(buf, length, false)); + break; + case 5: + position.set(Position.KEY_IGNITION, readValue(buf, length, false) == 1); + break; + case 74: + position.set(Position.PREFIX_TEMP + 3, readValue(buf, length, true) * 0.1); + break; + case 78: + case 79: + case 80: + position.set(Position.PREFIX_TEMP + (id - 78), readValue(buf, length, true) * 0.1); + break; + default: + position.set(Position.PREFIX_IO + id, readValue(buf, length, false)); + break; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedShort(); // data length + + String imei = String.format("%015d", buf.readLong()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + int type = buf.readUnsignedByte(); + + if (type == MSG_RECORDS || type == MSG_EXTENDED_RECORDS) { + + List<Position> positions = new LinkedList<>(); + + buf.readUnsignedByte(); // records left + int count = buf.readUnsignedByte(); + + for (int i = 0; i < count; i++) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + buf.readUnsignedByte(); // timestamp extension + + if (type == MSG_EXTENDED_RECORDS) { + buf.readUnsignedByte(); // record extension + } + + buf.readUnsignedByte(); // priority (reserved) + + position.setValid(true); + position.setLongitude(buf.readInt() / 10000000.0); + position.setLatitude(buf.readInt() / 10000000.0); + position.setAltitude(buf.readUnsignedShort() / 10.0); + position.setCourse(buf.readUnsignedShort() / 100.0); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort())); + + position.set(Position.KEY_HDOP, buf.readUnsignedByte() / 10.0); + + if (type == MSG_EXTENDED_RECORDS) { + position.set(Position.KEY_EVENT, buf.readUnsignedShort()); + } else { + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + } + + // Read 1 byte data + int cnt = buf.readUnsignedByte(); + for (int j = 0; j < cnt; j++) { + int id = type == MSG_EXTENDED_RECORDS ? buf.readUnsignedShort() : buf.readUnsignedByte(); + decodeParameter(position, id, buf, 1); + } + + // Read 2 byte data + cnt = buf.readUnsignedByte(); + for (int j = 0; j < cnt; j++) { + int id = type == MSG_EXTENDED_RECORDS ? buf.readUnsignedShort() : buf.readUnsignedByte(); + decodeParameter(position, id, buf, 2); + } + + // Read 4 byte data + cnt = buf.readUnsignedByte(); + for (int j = 0; j < cnt; j++) { + int id = type == MSG_EXTENDED_RECORDS ? buf.readUnsignedShort() : buf.readUnsignedByte(); + decodeParameter(position, id, buf, 4); + } + + // Read 8 byte data + cnt = buf.readUnsignedByte(); + for (int j = 0; j < cnt; j++) { + int id = type == MSG_EXTENDED_RECORDS ? buf.readUnsignedShort() : buf.readUnsignedByte(); + decodeParameter(position, id, buf, 8); + } + + Long driverIdPart1 = (Long) position.getAttributes().remove(Position.PREFIX_IO + 126); + Long driverIdPart2 = (Long) position.getAttributes().remove(Position.PREFIX_IO + 127); + if (driverIdPart1 != null && driverIdPart2 != null) { + ByteBuf driverId = Unpooled.copyLong(driverIdPart1, driverIdPart2); + position.set(Position.KEY_DRIVER_UNIQUE_ID, driverId.toString(StandardCharsets.US_ASCII)); + driverId.release(); + } + + positions.add(position); + } + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage( + Unpooled.wrappedBuffer(DataConverter.parseHex("0002640113bc")), remoteAddress)); + } + + return positions; + + } else if (type == MSG_DTCS) { + + List<Position> positions = new LinkedList<>(); + + int count = buf.readUnsignedByte(); + + for (int i = 0; i < count; i++) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedByte(); // reserved + + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + + position.setValid(true); + position.setLongitude(buf.readInt() / 10000000.0); + position.setLatitude(buf.readInt() / 10000000.0); + + if (buf.readUnsignedByte() == 2) { + position.set(Position.KEY_ARCHIVE, true); + } + + position.set(Position.KEY_DTCS, buf.readSlice(5).toString(StandardCharsets.US_ASCII)); + + positions.add(position); + } + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage( + Unpooled.wrappedBuffer(DataConverter.parseHex("00026d01c4a4")), remoteAddress)); + } + + return positions; + + } else if (type == MSG_FILES) { + + int subtype = buf.readUnsignedByte(); + int source = buf.readUnsignedByte(); + + if (subtype == 2) { + ByteBuf filename = buf.readSlice(8); + int total = buf.readUnsignedShort(); + int current = buf.readUnsignedShort(); + if (photo == null) { + photo = Unpooled.buffer(); + } + photo.writeBytes(buf.readSlice(buf.readableBytes() - 2)); + if (current < total - 1) { + ByteBuf content = Unpooled.buffer(); + content.writeByte(subtype); + content.writeByte(source); + content.writeBytes(filename); + content.writeShort(current + 1); + ByteBuf response = RuptelaProtocolEncoder.encodeContent(type, content); + content.release(); + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } else { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, null); + position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(imei, photo, "jpg")); + photo.release(); + photo = null; + return position; + } + } + + return null; + + } else { + + return decodeCommandResponse(deviceSession, type, buf); + + } + } + +} diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java new file mode 100644 index 000000000..fb0dcf690 --- /dev/null +++ b/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.traccar.BaseProtocolEncoder; +import org.traccar.helper.Checksum; +import org.traccar.helper.DataConverter; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.nio.charset.StandardCharsets; + +public class RuptelaProtocolEncoder extends BaseProtocolEncoder { + + public RuptelaProtocolEncoder(Protocol protocol) { + super(protocol); + } + + public static ByteBuf encodeContent(int type, ByteBuf content) { + + ByteBuf buf = Unpooled.buffer(); + + buf.writeShort(1 + content.readableBytes()); + buf.writeByte(100 + type); + buf.writeBytes(content); + buf.writeShort(Checksum.crc16(Checksum.CRC16_KERMIT, buf.nioBuffer(2, buf.writerIndex() - 2))); + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + + ByteBuf content = Unpooled.buffer(); + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + String data = command.getString(Command.KEY_DATA); + if (data.matches("(\\p{XDigit}{2})+")) { + content.writeBytes(DataConverter.parseHex(data)); + return content; + } else { + content.writeBytes(data.getBytes(StandardCharsets.US_ASCII)); + return encodeContent(RuptelaProtocolDecoder.MSG_SMS_VIA_GPRS, content); + } + case Command.TYPE_REQUEST_PHOTO: + content.writeByte(1); // sub-command + content.writeByte(0); // source + content.writeInt(0); // start timestamp + content.writeInt(Integer.MAX_VALUE); // end timestamp + return encodeContent(RuptelaProtocolDecoder.MSG_FILES, content); + case Command.TYPE_CONFIGURATION: + content.writeBytes((command.getString(Command.KEY_DATA) + "\r\n").getBytes(StandardCharsets.US_ASCII)); + return encodeContent(RuptelaProtocolDecoder.MSG_DEVICE_CONFIGURATION, content); + case Command.TYPE_GET_VERSION: + return encodeContent(RuptelaProtocolDecoder.MSG_DEVICE_VERSION, content); + case Command.TYPE_FIRMWARE_UPDATE: + content.writeBytes("|FU_STRT*\r\n".getBytes(StandardCharsets.US_ASCII)); + return encodeContent(RuptelaProtocolDecoder.MSG_FIRMWARE_UPDATE, content); + case Command.TYPE_OUTPUT_CONTROL: + content.writeInt(command.getInteger(Command.KEY_INDEX)); + content.writeInt(Integer.parseInt(command.getString(Command.KEY_DATA))); + return encodeContent(RuptelaProtocolDecoder.MSG_SET_IO, content); + case Command.TYPE_SET_CONNECTION: + String c = command.getString(Command.KEY_SERVER) + "," + command.getInteger(Command.KEY_PORT) + ",TCP"; + content.writeBytes(c.getBytes(StandardCharsets.US_ASCII)); + return encodeContent(RuptelaProtocolDecoder.MSG_SET_CONNECTION, content); + case Command.TYPE_SET_ODOMETER: + content.writeInt(Integer.parseInt(command.getString(Command.KEY_DATA))); + return encodeContent(RuptelaProtocolDecoder.MSG_SET_ODOMETER, content); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/S168Protocol.java b/src/main/java/org/traccar/protocol/S168Protocol.java new file mode 100644 index 000000000..e78664c40 --- /dev/null +++ b/src/main/java/org/traccar/protocol/S168Protocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class S168Protocol extends BaseProtocol { + + public S168Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '$')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new S168ProtocolDecoder(S168Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java b/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java new file mode 100644 index 000000000..71aff1a65 --- /dev/null +++ b/src/main/java/org/traccar/protocol/S168ProtocolDecoder.java @@ -0,0 +1,81 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +public class S168ProtocolDecoder extends BaseProtocolDecoder { + + public S168ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + String[] values = sentence.split("#"); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[1]); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + String content = values[4]; + String[] fragments = content.split(";"); + for (String fragment : fragments) { + + int dataIndex = fragment.indexOf(':'); + String type = fragment.substring(0, dataIndex); + values = fragment.substring(dataIndex + 1).split(","); + int index = 0; + + switch (type) { + case "GDATA": + position.setValid(values[index++].equals("A")); + position.set(Position.KEY_SATELLITES, Integer.parseInt(values[index++])); + DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + position.setTime(dateFormat.parse(values[index++])); + position.setLatitude(Double.parseDouble(values[index++])); + position.setLongitude(Double.parseDouble(values[index++])); + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[index++]))); + position.setCourse(Integer.parseInt(values[index++])); + position.setAltitude(Integer.parseInt(values[index++])); + break; + default: + break; + } + } + + return position.getAttributes().containsKey(Position.KEY_SATELLITES) ? position : null; + } + +} diff --git a/src/main/java/org/traccar/protocol/SabertekFrameDecoder.java b/src/main/java/org/traccar/protocol/SabertekFrameDecoder.java new file mode 100644 index 000000000..ad5000bf8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SabertekFrameDecoder.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class SabertekFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + int beginIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x02); + if (beginIndex >= 0) { + int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x03); + if (endIndex >= 0) { + buf.readerIndex(beginIndex + 1); + ByteBuf frame = buf.readRetainedSlice(endIndex - beginIndex - 1); + buf.readerIndex(endIndex + 1); + buf.skipBytes(2); // end line + return frame; + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/SabertekProtocol.java b/src/main/java/org/traccar/protocol/SabertekProtocol.java new file mode 100644 index 000000000..0ec847b60 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SabertekProtocol.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class SabertekProtocol extends BaseProtocol { + + public SabertekProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new SabertekFrameDecoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new SabertekProtocolDecoder(SabertekProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SabertekProtocolDecoder.java b/src/main/java/org/traccar/protocol/SabertekProtocolDecoder.java new file mode 100644 index 000000000..3033aa2cc --- /dev/null +++ b/src/main/java/org/traccar/protocol/SabertekProtocolDecoder.java @@ -0,0 +1,135 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import 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; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; +import java.util.regex.Pattern; + +public class SabertekProtocolDecoder extends BaseProtocolDecoder { + + public SabertekProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text(",") + .number("(d+),") // id + .number("d,") // type + .groupBegin() + .number("d+,") // imei + .number("d+,") // scid + .expression("[^,]*,") // phone + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .groupEnd("?") + .number("(d+),") // battery + .number("(d+),") // rssi + .number("(d+),") // state + .number("(d+),") // events + .number("(d),") // valid + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(d+),") // altitude + .number("(d+),") // satellites + .number("(d+),") // odometer + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (channel != null) { + channel.writeAndFlush(new NetworkMessage( + Unpooled.wrappedBuffer(new byte[]{(byte) (deviceSession != null ? 0x06 : 0x15)}), remoteAddress)); + } + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (parser.hasNext(6)) { + position.setTime(parser.nextDateTime()); + } else { + position.setTime(new Date()); + } + + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + position.set(Position.KEY_RSSI, parser.nextInt()); + + int state = parser.nextInt(); + + position.set(Position.KEY_IGNITION, BitUtil.check(state, 0)); + position.set(Position.KEY_CHARGE, BitUtil.check(state, 1)); + + if (BitUtil.check(state, 2)) { + position.set(Position.KEY_ALARM, Position.ALARM_JAMMING); + } + if (BitUtil.check(state, 3)) { + position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING); + } + + int events = parser.nextInt(); + + if (BitUtil.check(events, 0)) { + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + } + if (BitUtil.check(events, 1)) { + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + } + if (BitUtil.check(events, 2)) { + position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT); + } + if (BitUtil.check(events, 3)) { + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + } + + position.setValid(parser.nextInt() == 1); + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.setCourse(parser.nextInt()); + position.setAltitude(parser.nextInt()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_ODOMETER, parser.nextInt() * 1000L); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/SanavProtocol.java b/src/main/java/org/traccar/protocol/SanavProtocol.java new file mode 100644 index 000000000..6799c57e6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SanavProtocol.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class SanavProtocol extends BaseProtocol { + + public SanavProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new SanavProtocolDecoder(SanavProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new SanavProtocolDecoder(SanavProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SanavProtocolDecoder.java b/src/main/java/org/traccar/protocol/SanavProtocolDecoder.java new file mode 100644 index 000000000..7e1c158e6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SanavProtocolDecoder.java @@ -0,0 +1,107 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.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 SanavProtocolDecoder extends BaseProtocolDecoder { + + public SanavProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression("imei[:=]") + .number("(d+)") // imei + .expression("&?rmc[:=]") + .text("$GPRMC,") + .number("(dd)(dd)(dd).d+,") // time (hhmmss.sss) + .expression("([AV]),") // validity + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.d+),") // speed + .number("(d+.d+)?,") // course + .number("(dd)(dd)(dd),") // date (ddmmyy) + .groupBegin() + .expression("[^*]*") + .text("*") + .number("xx,") + .expression("[^,]+,") // status + .number("(d+),") // io + .groupEnd("?") + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble()); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + if (parser.hasNext()) { + int io = parser.nextHexInt(); + for (int i = 0; i < 5; i++) { + position.set(Position.PREFIX_IN + (i + 1), BitUtil.check(io, i)); + } + position.set(Position.KEY_IGNITION, BitUtil.check(io, 5)); + position.set(Position.PREFIX_OUT + 1, BitUtil.check(io, 6)); + position.set(Position.PREFIX_OUT + 2, BitUtil.check(io, 7)); + position.set(Position.KEY_CHARGE, BitUtil.check(io, 8)); + if (!BitUtil.check(io, 9)) { + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + } + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/SanulProtocol.java b/src/main/java/org/traccar/protocol/SanulProtocol.java new file mode 100644 index 000000000..3104e9366 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SanulProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; + +public class SanulProtocol extends BaseProtocol { + + public SanulProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 3, 2, 0, 0, true)); + pipeline.addLast(new SanulProtocolDecoder(SanulProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SanulProtocolDecoder.java b/src/main/java/org/traccar/protocol/SanulProtocolDecoder.java new file mode 100644 index 000000000..036d1ee51 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SanulProtocolDecoder.java @@ -0,0 +1,97 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; + +public class SanulProtocolDecoder extends BaseProtocolDecoder { + + public static final int MSG_LOGIN = 0; + public static final int MSG_LOCATION = 1; + public static final int MSG_RESPONSE = 5; + + public SanulProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private void sendResponse(Channel channel, int type) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(0xaa); // header + response.writeShortLE(0x85da); // reserved + response.writeShortLE(15); // length + response.writeByte(1); // edition + response.writeShortLE(MSG_RESPONSE); + response.writeShortLE(type); + response.writeIntLE(0); // command id + response.writeByte(0); // status + response.writeByte(0); // result length + response.writeIntLE(0x20000); // result data ? + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // header + buf.readUnsignedShortLE(); // reserved + buf.readUnsignedShortLE(); // length + buf.readUnsignedByte(); // edition + + int type = buf.readUnsignedShortLE(); + + buf.readUnsignedIntLE(); // command id + + sendResponse(channel, type); + + if (type == MSG_LOGIN) { + + getDeviceSession(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII).trim()); + + } else if (type == MSG_LOCATION) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/SatsolProtocol.java b/src/main/java/org/traccar/protocol/SatsolProtocol.java new file mode 100644 index 000000000..b69fdd1fe --- /dev/null +++ b/src/main/java/org/traccar/protocol/SatsolProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; + +public class SatsolProtocol extends BaseProtocol { + + public SatsolProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1400, 8, 2, 0, 0, true)); + pipeline.addLast(new SatsolProtocolDecoder(SatsolProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SatsolProtocolDecoder.java b/src/main/java/org/traccar/protocol/SatsolProtocolDecoder.java new file mode 100644 index 000000000..c457d5620 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SatsolProtocolDecoder.java @@ -0,0 +1,104 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class SatsolProtocolDecoder extends BaseProtocolDecoder { + + public SatsolProtocolDecoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedShortLE(); // checksum + buf.readUnsignedShortLE(); // preamble + long id = buf.readUnsignedIntLE(); + buf.readUnsignedShortLE(); // length + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(id)); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + while (buf.isReadable()) { + + buf.readUnsignedShortLE(); // checksum + buf.readUnsignedShortLE(); // checksum + buf.readUnsignedShortLE(); // type + int length = buf.readUnsignedShortLE(); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); + position.setLatitude(buf.readUnsignedIntLE() * 0.000001); + position.setLongitude(buf.readUnsignedIntLE() * 0.000001); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShortLE() * 0.01)); + position.setAltitude(buf.readShortLE()); + position.setCourse(buf.readUnsignedShortLE()); + position.setValid(buf.readUnsignedByte() > 0); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + + if (BitUtil.check(buf.readUnsignedByte(), 0)) { + position.set(Position.KEY_ARCHIVE, true); + } + + positions.add(position); + + buf.skipBytes(length); + + } + + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeShortLE(0); + response.writeShortLE(0x4CBF); // preamble + response.writeIntLE((int) id); + response.writeShortLE(0); + response.setShortLE(0, Checksum.crc16( + Checksum.CRC16_CCITT_FALSE, response.nioBuffer(2, response.readableBytes() - 2))); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/SigfoxProtocol.java b/src/main/java/org/traccar/protocol/SigfoxProtocol.java new file mode 100644 index 000000000..e2f2cbe1f --- /dev/null +++ b/src/main/java/org/traccar/protocol/SigfoxProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class SigfoxProtocol extends BaseProtocol { + + public SigfoxProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(65535)); + pipeline.addLast(new SigfoxProtocolDecoder(SigfoxProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java new file mode 100644 index 000000000..304f61836 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SigfoxProtocolDecoder.java @@ -0,0 +1,219 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +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 org.traccar.BaseHttpProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DataConverter; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import javax.json.Json; +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonValue; +import java.io.StringReader; +import java.net.SocketAddress; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class SigfoxProtocolDecoder extends BaseHttpProtocolDecoder { + + public SigfoxProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private int getJsonInt(JsonObject json, String key) { + JsonValue value = json.get(key); + if (value != null) { + if (value.getValueType() == JsonValue.ValueType.NUMBER) { + return ((JsonNumber) value).intValue(); + } else if (value.getValueType() == JsonValue.ValueType.STRING) { + return Integer.parseInt(((JsonString) value).getString()); + } + } + return 0; + } + + private double getJsonDouble(JsonObject json, String key) { + JsonValue value = json.get(key); + if (value != null) { + if (value.getValueType() == JsonValue.ValueType.NUMBER) { + return ((JsonNumber) value).doubleValue(); + } else if (value.getValueType() == JsonValue.ValueType.STRING) { + return Double.parseDouble(((JsonString) value).getString()); + } + } + return 0; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + String content = request.content().toString(StandardCharsets.UTF_8); + if (!content.startsWith("{")) { + content = URLDecoder.decode(content.split("=")[0], "UTF-8"); + } + JsonObject json = Json.createReader(new StringReader(content)).readObject(); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, json.getString("device")); + if (deviceSession == null) { + sendResponse(channel, HttpResponseStatus.BAD_REQUEST); + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (json.containsKey("time")) { + position.setTime(new Date(getJsonInt(json, "time") * 1000L)); + } else { + position.setTime(new Date()); + } + + if (json.containsKey("location") + || json.containsKey("lat") && json.containsKey("lng") && !json.containsKey("data")) { + + JsonObject location; + if (json.containsKey("location")) { + location = json.getJsonObject("location"); + } else { + location = json; + } + + position.setValid(true); + position.setLatitude(getJsonDouble(location, "lat")); + position.setLongitude(getJsonDouble(location, "lng")); + + } else { + + String data = json.getString(json.containsKey("data") ? "data" : "payload"); + ByteBuf buf = Unpooled.wrappedBuffer(DataConverter.parseHex(data)); + try { + int event = buf.readUnsignedByte(); + if (event == 0x0f || event == 0x1f) { + + position.setValid(event >> 4 > 0); + + long value; + value = buf.readUnsignedInt(); + position.setLatitude(BitUtil.to(value, 31) * 0.000001); + if (BitUtil.check(value, 31)) { + position.setLatitude(-position.getLatitude()); + } + value = buf.readUnsignedInt(); + position.setLongitude(BitUtil.to(value, 31) * 0.000001); + if (BitUtil.check(value, 31)) { + position.setLongitude(-position.getLongitude()); + } + + position.set(Position.KEY_BATTERY, (int) buf.readUnsignedByte()); + + } else if (event >> 4 == 0) { + + position.setValid(true); + position.setLatitude(buf.readIntLE() * 0.0000001); + position.setLongitude(buf.readIntLE() * 0.0000001); + position.setCourse(buf.readUnsignedByte() * 2); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + + position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 0.025); + + } else { + + position.set(Position.KEY_EVENT, event); + if (event == 0x22 || event == 0x62) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + + while (buf.isReadable()) { + int type = buf.readUnsignedByte(); + switch (type) { + case 0x01: + position.setValid(true); + position.setLatitude(buf.readMedium()); + position.setLongitude(buf.readMedium()); + break; + case 0x02: + position.setValid(true); + position.setLatitude(buf.readFloat()); + position.setLongitude(buf.readFloat()); + break; + case 0x03: + position.set(Position.PREFIX_TEMP + 1, buf.readByte() * 0.5); + break; + case 0x04: + position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 0.1); + break; + case 0x05: + position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); + break; + case 0x06: + String mac = ByteBufUtil.hexDump(buf.readSlice(6)).replaceAll("(..)", "$1:"); + position.setNetwork(new Network(WifiAccessPoint.from( + mac.substring(0, mac.length() - 1), buf.readUnsignedByte()))); + break; + case 0x07: + buf.skipBytes(10); // wifi extended + break; + case 0x08: + buf.skipBytes(6); // accelerometer + break; + case 0x09: + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + break; + default: + buf.readUnsignedByte(); // fence number + break; + } + } + + } + } finally { + buf.release(); + } + } + + if (position.getLatitude() == 0 && position.getLongitude() == 0) { + getLastLocation(position, position.getDeviceTime()); + } + + if (json.containsKey("rssi")) { + position.set(Position.KEY_RSSI, getJsonDouble(json, "rssi")); + } + if (json.containsKey("seqNumber")) { + position.set(Position.KEY_INDEX, getJsonInt(json, "seqNumber")); + } + + sendResponse(channel, HttpResponseStatus.OK); + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/SiwiProtocol.java b/src/main/java/org/traccar/protocol/SiwiProtocol.java new file mode 100644 index 000000000..8963721c8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SiwiProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class SiwiProtocol extends BaseProtocol { + + public SiwiProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new SiwiProtocolDecoder(SiwiProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java b/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java new file mode 100644 index 000000000..6b97f5fe0 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SiwiProtocolDecoder.java @@ -0,0 +1,96 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class SiwiProtocolDecoder extends BaseProtocolDecoder { + + public SiwiProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$").expression("[A-Z]+,") // header + .number("(d+),") // device id + .number("d+,") // unit no + .expression("([A-Z]),") // reason + .number("d+,") // command code + .number("[^,]*,") // command value + .expression("([01]),") // ignition + .expression("[01],") // power cut + .expression("[01],") // box open + .number("d+,") // message key + .number("(d+),") // odometer + .number("(d+),") // speed + .number("(d+),") // satellites + .expression("([AV]),") // valid + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(-?d+),") // altitude + .number("(d+),") // course + .number("(dd)(dd)(dd),") // time (hhmmss) + .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; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_EVENT, parser.next()); + position.set(Position.KEY_IGNITION, parser.next().equals("1")); + position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt(0))); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + position.setCourse(parser.nextInt(0)); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY, "IST")); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/SkypatrolProtocol.java b/src/main/java/org/traccar/protocol/SkypatrolProtocol.java new file mode 100644 index 000000000..7c6203d86 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SkypatrolProtocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class SkypatrolProtocol extends BaseProtocol { + + public SkypatrolProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new SkypatrolProtocolDecoder(SkypatrolProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SkypatrolProtocolDecoder.java b/src/main/java/org/traccar/protocol/SkypatrolProtocolDecoder.java new file mode 100644 index 000000000..3c7ca6dc5 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SkypatrolProtocolDecoder.java @@ -0,0 +1,193 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; + +public class SkypatrolProtocolDecoder extends BaseProtocolDecoder { + + private static final Logger LOGGER = LoggerFactory.getLogger(SkypatrolProtocolDecoder.class); + + private final long defaultMask; + + public SkypatrolProtocolDecoder(Protocol protocol) { + super(protocol); + defaultMask = Context.getConfig().getInteger(getProtocolName() + ".mask"); + } + + private static double convertCoordinate(long coordinate) { + int sign = 1; + if (coordinate > 0x7fffffffL) { + sign = -1; + coordinate = 0xffffffffL - coordinate; + } + + long degrees = coordinate / 1000000; + double minutes = (coordinate % 1000000) / 10000.0; + + return sign * (degrees + minutes / 60); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int apiNumber = buf.readUnsignedShort(); + int commandType = buf.readUnsignedByte(); + int messageType = BitUtil.from(buf.readUnsignedByte(), 4); + long mask = defaultMask; + if (buf.readUnsignedByte() == 4) { + mask = buf.readUnsignedInt(); + } + + // Binary position report + if (apiNumber == 5 && commandType == 2 && messageType == 1 && BitUtil.check(mask, 0)) { + + Position position = new Position(getProtocolName()); + + if (BitUtil.check(mask, 1)) { + position.set(Position.KEY_STATUS, buf.readUnsignedInt()); + } + + String id; + if (BitUtil.check(mask, 23)) { + id = buf.toString(buf.readerIndex(), 8, StandardCharsets.US_ASCII).trim(); + buf.skipBytes(8); + } else if (BitUtil.check(mask, 2)) { + id = buf.toString(buf.readerIndex(), 22, StandardCharsets.US_ASCII).trim(); + buf.skipBytes(22); + } else { + LOGGER.warn("No device id field"); + return null; + } + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + if (BitUtil.check(mask, 3)) { + position.set(Position.PREFIX_IO + 1, buf.readUnsignedShort()); + } + + if (BitUtil.check(mask, 4)) { + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort()); + } + + if (BitUtil.check(mask, 5)) { + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShort()); + } + + if (BitUtil.check(mask, 7)) { + buf.readUnsignedByte(); // function category + } + + DateBuilder dateBuilder = new DateBuilder(); + + if (BitUtil.check(mask, 8)) { + dateBuilder.setDateReverse( + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + } + + if (BitUtil.check(mask, 9)) { + position.setValid(buf.readUnsignedByte() == 1); // gps status + } + + if (BitUtil.check(mask, 10)) { + position.setLatitude(convertCoordinate(buf.readUnsignedInt())); + } + + if (BitUtil.check(mask, 11)) { + position.setLongitude(convertCoordinate(buf.readUnsignedInt())); + } + + if (BitUtil.check(mask, 12)) { + position.setSpeed(buf.readUnsignedShort() / 10.0); + } + + if (BitUtil.check(mask, 13)) { + position.setCourse(buf.readUnsignedShort() / 10.0); + } + + if (BitUtil.check(mask, 14)) { + dateBuilder.setTime( + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + } + + position.setTime(dateBuilder.getDate()); + + if (BitUtil.check(mask, 15)) { + position.setAltitude(buf.readMedium()); + } + + if (BitUtil.check(mask, 16)) { + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + } + + if (BitUtil.check(mask, 17)) { + position.set(Position.KEY_BATTERY, buf.readUnsignedShort()); + } + + if (BitUtil.check(mask, 20)) { + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedInt()); + } + + if (BitUtil.check(mask, 21)) { + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + } + + if (BitUtil.check(mask, 22)) { + buf.skipBytes(6); // time of message generation + } + + if (BitUtil.check(mask, 24)) { + position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.001); + } + + if (BitUtil.check(mask, 25)) { + buf.skipBytes(18); // gps overspeed + } + + if (BitUtil.check(mask, 26)) { + buf.skipBytes(54); // cell information + } + + if (BitUtil.check(mask, 28)) { + position.set(Position.KEY_INDEX, buf.readUnsignedShort()); + } + + return position; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/SmartSoleProtocol.java b/src/main/java/org/traccar/protocol/SmartSoleProtocol.java new file mode 100644 index 000000000..bcf43f68b --- /dev/null +++ b/src/main/java/org/traccar/protocol/SmartSoleProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class SmartSoleProtocol extends BaseProtocol { + + public SmartSoleProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '$')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new SmartSoleProtocolDecoder(SmartSoleProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SmartSoleProtocolDecoder.java b/src/main/java/org/traccar/protocol/SmartSoleProtocolDecoder.java new file mode 100644 index 000000000..04920c969 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SmartSoleProtocolDecoder.java @@ -0,0 +1,91 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class SmartSoleProtocolDecoder extends BaseProtocolDecoder { + + public SmartSoleProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("#GTXRP=") + .number("(d+),") // imei + .number("d+,") // report type + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(-?d+.d+),") // longitude + .number("(-?d+.d+),") // latitude + .number("(-?d+),") // altitude + .number("(d+),") // speed + .number("([01]),") // valid + .number("(d+),") // satellites + .number("(d+.d+),") // hdop + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d+.d+),") // battery + .number("(d+)") // status + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setFixTime(parser.nextDateTime()); + + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setAltitude(parser.nextInt()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextInt())); + position.setValid(parser.nextInt() == 1); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_HDOP, parser.nextDouble()); + + position.setDeviceTime(parser.nextDateTime()); + + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_STATUS, parser.nextInt()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/SmokeyProtocol.java b/src/main/java/org/traccar/protocol/SmokeyProtocol.java new file mode 100644 index 000000000..482c8347c --- /dev/null +++ b/src/main/java/org/traccar/protocol/SmokeyProtocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class SmokeyProtocol extends BaseProtocol { + + public SmokeyProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new SmokeyProtocolDecoder(SmokeyProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SmokeyProtocolDecoder.java b/src/main/java/org/traccar/protocol/SmokeyProtocolDecoder.java new file mode 100644 index 000000000..9da52e97a --- /dev/null +++ b/src/main/java/org/traccar/protocol/SmokeyProtocolDecoder.java @@ -0,0 +1,161 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.DateBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +public class SmokeyProtocolDecoder extends BaseProtocolDecoder { + + public SmokeyProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_DATE_RECORD = 0; + public static final int MSG_DATE_RECORD_ACK = 1; + + private static void sendResponse( + Channel channel, SocketAddress remoteAddress, ByteBuf id, int index, int report) { + + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeBytes("SM".getBytes(StandardCharsets.US_ASCII)); + response.writeByte(3); // protocol version + response.writeByte(MSG_DATE_RECORD_ACK); + response.writeBytes(id); + response.writeInt( + (int) ChronoUnit.SECONDS.between(Instant.parse("2000-01-01T00:00:00.00Z"), Instant.now())); + response.writeByte(index); + response.writeByte(report - 0x200); + + short checksum = (short) 0xF5A0; + for (int i = 0; i < response.readableBytes(); i += 2) { + checksum ^= response.getShortLE(i); + } + response.writeShort(checksum); + + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + buf.readUnsignedByte(); // protocol version + + int type = buf.readUnsignedByte(); + + ByteBuf id = buf.readSlice(8); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, ByteBufUtil.hexDump(id)); + if (deviceSession == null) { + return null; + } + + if (type == MSG_DATE_RECORD) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_VERSION_FW, buf.readUnsignedShort()); + + int status = buf.readUnsignedShort(); + position.set(Position.KEY_STATUS, status); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(2000, 1, 1).addSeconds(buf.readUnsignedInt()); + + getLastLocation(position, dateBuilder.getDate()); + + int index = buf.readUnsignedByte(); + position.set(Position.KEY_INDEX, index); + + int report = buf.readUnsignedShort(); + + buf.readUnsignedShort(); // length + + position.set(Position.KEY_BATTERY, buf.readUnsignedShort()); + + Network network = new Network(); + + if (report != 0x0203) { + + int count = 1; + if (report != 0x0200) { + count = buf.readUnsignedByte(); + } + + for (int i = 0; i < count; i++) { + int mcc = buf.readUnsignedShort(); + int mnc = buf.readUnsignedShort(); + int lac = buf.readUnsignedShort(); + int cid = buf.readUnsignedShort(); + if (i == 0) { + buf.readByte(); // timing advance + } + int rssi = buf.readByte(); + network.addCellTower(CellTower.from(mcc, mnc, lac, cid, rssi)); + } + + } + + if (report == 0x0202 || report == 0x0203) { + + int count = buf.readUnsignedByte(); + + for (int i = 0; i < count; i++) { + buf.readerIndex(buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0) + 1); // ssid + + String mac = String.format("%02x:%02x:%02x:%02x:%02x:%02x", + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte(), + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + + network.addWifiAccessPoint(WifiAccessPoint.from(mac, buf.readByte())); + } + + } + + position.setNetwork(network); + + sendResponse(channel, remoteAddress, id, index, report); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java b/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java new file mode 100644 index 000000000..53a948cdc --- /dev/null +++ b/src/main/java/org/traccar/protocol/SolarPoweredProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class SolarPoweredProtocol extends BaseProtocol { + + public SolarPoweredProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HuabaoFrameDecoder()); + pipeline.addLast(new SolarPoweredProtocolDecoder(SolarPoweredProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java b/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java new file mode 100644 index 000000000..eae37386a --- /dev/null +++ b/src/main/java/org/traccar/protocol/SolarPoweredProtocolDecoder.java @@ -0,0 +1,97 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class SolarPoweredProtocolDecoder extends BaseProtocolDecoder { + + public SolarPoweredProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_ACTIVE_REPORTING = 0x11; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // start marker + + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(0, 15); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + int type = buf.readUnsignedByte(); + buf.readUnsignedShort(); // attributes + + if (type == MSG_ACTIVE_REPORTING) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + while (buf.readableBytes() > 2) { + int tag = buf.readUnsignedByte(); + int length = buf.readUnsignedByte(); + switch (tag) { + case 0x81: + int status = buf.readUnsignedByte(); + DateBuilder dateBuilder = new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + position.setLatitude(buf.readUnsignedInt() * 0.000001); + if (BitUtil.check(status, 3)) { + position.setLatitude(-position.getLatitude()); + } + position.setLongitude(buf.readUnsignedInt() * 0.000001); + if (BitUtil.check(status, 2)) { + position.setLongitude(-position.getLongitude()); + } + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.set(Position.KEY_DEVICE_TEMP, (int) buf.readByte()); + position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 0.02); + position.setCourse(buf.readUnsignedByte()); + break; + default: + buf.skipBytes(length); + break; + } + } + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/SpotProtocol.java b/src/main/java/org/traccar/protocol/SpotProtocol.java new file mode 100644 index 000000000..bbf0e8d8a --- /dev/null +++ b/src/main/java/org/traccar/protocol/SpotProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class SpotProtocol extends BaseProtocol { + + public SpotProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpRequestDecoder()); + pipeline.addLast(new HttpObjectAggregator(65535)); + pipeline.addLast(new SpotProtocolDecoder(SpotProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SpotProtocolDecoder.java b/src/main/java/org/traccar/protocol/SpotProtocolDecoder.java new file mode 100644 index 000000000..da36c2048 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SpotProtocolDecoder.java @@ -0,0 +1,102 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.traccar.BaseHttpProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.DateUtil; +import org.traccar.model.Position; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import java.net.SocketAddress; +import java.util.LinkedList; +import java.util.List; + +public class SpotProtocolDecoder extends BaseHttpProtocolDecoder { + + private DocumentBuilder documentBuilder; + private XPath xPath; + private XPathExpression messageExpression; + + public SpotProtocolDecoder(Protocol protocol) { + super(protocol); + try { + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + builderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + builderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); + builderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + builderFactory.setXIncludeAware(false); + builderFactory.setExpandEntityReferences(false); + documentBuilder = builderFactory.newDocumentBuilder(); + xPath = XPathFactory.newInstance().newXPath(); + messageExpression = xPath.compile("//messageList/message"); + } catch (ParserConfigurationException | XPathExpressionException e) { + throw new RuntimeException(e); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + FullHttpRequest request = (FullHttpRequest) msg; + + Document document = documentBuilder.parse(new ByteBufferBackedInputStream(request.content().nioBuffer())); + NodeList nodes = (NodeList) messageExpression.evaluate(document, XPathConstants.NODESET); + + List<Position> positions = new LinkedList<>(); + + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, xPath.evaluate("esnName", node)); + if (deviceSession != null) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + position.setTime(DateUtil.parseDate(xPath.evaluate("timestamp", node))); + position.setLatitude(Double.parseDouble(xPath.evaluate("latitude", node))); + position.setLongitude(Double.parseDouble(xPath.evaluate("longitude", node))); + + position.set(Position.KEY_EVENT, xPath.evaluate("messageType", node)); + + positions.add(position); + + } + } + + sendResponse(channel, HttpResponseStatus.OK); + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/StarLinkProtocol.java b/src/main/java/org/traccar/protocol/StarLinkProtocol.java new file mode 100644 index 000000000..5630722ee --- /dev/null +++ b/src/main/java/org/traccar/protocol/StarLinkProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class StarLinkProtocol extends BaseProtocol { + + public StarLinkProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StarLinkProtocolDecoder(StarLinkProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java new file mode 100644 index 000000000..bad6f03a9 --- /dev/null +++ b/src/main/java/org/traccar/protocol/StarLinkProtocolDecoder.java @@ -0,0 +1,223 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.TimeZone; +import java.util.regex.Pattern; + +public class StarLinkProtocolDecoder extends BaseProtocolDecoder { + + public static final int MSG_EVENT_REPORT = 6; + + private static final Pattern PATTERN = new PatternBuilder() + .expression(".") // protocol head + .text("SLU") // message head + .number("(x{6}|d{15}),") // id + .number("(d+),") // type + .number("(d+),") // index + .expression("(.+)") // data + .text("*") + .number("xx") // checksum + .compile(); + + private String[] dataTags; + private DateFormat dateFormat; + + public StarLinkProtocolDecoder(Protocol protocol) { + super(protocol); + + setFormat(Context.getConfig().getString( + getProtocolName() + ".format", "#EDT#,#EID#,#PDT#,#LAT#,#LONG#,#SPD#,#HEAD#,#ODO#," + + "#IN1#,#IN2#,#IN3#,#IN4#,#OUT1#,#OUT2#,#OUT3#,#OUT4#,#LAC#,#CID#,#VIN#,#VBAT#,#DEST#,#IGN#,#ENG#")); + + setDateFormat(Context.getConfig().getString(getProtocolName() + ".dateFormat", "yyMMddHHmmss")); + } + + public void setFormat(String format) { + dataTags = format.split(","); + } + + public void setDateFormat(String dateFormat) { + this.dateFormat = new SimpleDateFormat(dateFormat); + this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + private double parseCoordinate(String value) { + int minutesIndex = value.indexOf('.') - 2; + double result = Double.parseDouble(value.substring(1, minutesIndex)); + result += Double.parseDouble(value.substring(minutesIndex)) / 60; + return value.charAt(0) == '+' ? result : -result; + } + + private String decodeAlarm(int event) { + switch (event) { + case 6: + return Position.ALARM_OVERSPEED; + case 7: + return Position.ALARM_GEOFENCE_ENTER; + case 8: + return Position.ALARM_GEOFENCE_EXIT; + case 9: + return Position.ALARM_POWER_CUT; + case 11: + return Position.ALARM_LOW_BATTERY; + case 26: + return Position.ALARM_TOW; + case 36: + return Position.ALARM_SOS; + case 42: + return Position.ALARM_JAMMING; + default: + return null; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + int type = parser.nextInt(0); + if (type != MSG_EVENT_REPORT) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.setValid(true); + + position.set(Position.KEY_INDEX, parser.nextInt(0)); + + String[] data = parser.next().split(","); + Integer lac = null, cid = null; + int event = 0; + + for (int i = 0; i < Math.min(data.length, dataTags.length); i++) { + if (data[i].isEmpty()) { + continue; + } + switch (dataTags[i]) { + case "#EDT#": + position.setDeviceTime(dateFormat.parse(data[i])); + break; + case "#EID#": + event = Integer.parseInt(data[i]); + position.set(Position.KEY_ALARM, decodeAlarm(event)); + position.set(Position.KEY_EVENT, event); + break; + case "#PDT#": + position.setFixTime(dateFormat.parse(data[i])); + break; + case "#LAT#": + position.setLatitude(parseCoordinate(data[i])); + break; + case "#LONG#": + position.setLongitude(parseCoordinate(data[i])); + break; + case "#SPD#": + position.setSpeed(Double.parseDouble(data[i])); + break; + case "#HEAD#": + position.setCourse(Integer.parseInt(data[i])); + break; + case "#ODO#": + position.set(Position.KEY_ODOMETER, (long) (Double.parseDouble(data[i]) * 1000)); + break; + case "#IN1#": + case "#IN2#": + case "#IN3#": + case "#IN4#": + position.set(Position.PREFIX_IN + dataTags[i].charAt(3), Integer.parseInt(data[i])); + break; + case "#OUT1#": + case "#OUT2#": + case "#OUT3#": + case "#OUT4#": + position.set(Position.PREFIX_OUT + dataTags[i].charAt(3), Integer.parseInt(data[i])); + break; + case "#LAC#": + if (!data[i].isEmpty()) { + lac = Integer.parseInt(data[i]); + } + break; + case "#CID#": + if (!data[i].isEmpty()) { + cid = Integer.parseInt(data[i]); + } + break; + case "#VIN#": + position.set(Position.KEY_POWER, Double.parseDouble(data[i])); + break; + case "#VBAT#": + position.set(Position.KEY_BATTERY, Double.parseDouble(data[i])); + break; + case "#DEST#": + position.set("destination", data[i]); + break; + case "#IGN#": + position.set(Position.KEY_IGNITION, data[i].equals("1")); + break; + case "#ENG#": + position.set("engine", data[i].equals("1")); + break; + default: + break; + } + } + + if (position.getFixTime() == null) { + getLastLocation(position, null); + } + + if (lac != null && cid != null) { + position.setNetwork(new Network(CellTower.fromLacCid(lac, cid))); + } + + if (event == 20) { + String rfid = data[data.length - 1]; + if (rfid.matches("0+")) { + rfid = data[data.length - 2]; + } + position.set(Position.KEY_DRIVER_UNIQUE_ID, rfid); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/StarcomProtocol.java b/src/main/java/org/traccar/protocol/StarcomProtocol.java new file mode 100644 index 000000000..63a6143a6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/StarcomProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class StarcomProtocol extends BaseProtocol { + + public StarcomProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StarcomProtocolDecoder(StarcomProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java b/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java new file mode 100644 index 000000000..5ffddb318 --- /dev/null +++ b/src/main/java/org/traccar/protocol/StarcomProtocolDecoder.java @@ -0,0 +1,122 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.text.SimpleDateFormat; + +public class StarcomProtocolDecoder extends BaseProtocolDecoder { + + public StarcomProtocolDecoder(StarcomProtocol protocol) { + super(protocol); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + sentence = sentence.substring(sentence.indexOf('|') + 1, sentence.lastIndexOf('|')); + + Position position = new Position(); + position.setProtocol(getProtocolName()); + + for (String entry : sentence.split(",")) { + int delimiter = entry.indexOf('='); + String key = entry.substring(0, delimiter); + String value = entry.substring(delimiter + 1); + switch (key) { + case "unit": + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, value); + if (deviceSession != null) { + position.setDeviceId(deviceSession.getDeviceId()); + } + break; + case "gps_valid": + position.setValid(Integer.parseInt(value) != 0); + break; + case "datetime_actual": + position.setTime(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(value)); + break; + case "latitude": + position.setLatitude(Double.parseDouble(value)); + break; + case "longitude": + position.setLongitude(Double.parseDouble(value)); + break; + case "altitude": + position.setAltitude(Double.parseDouble(value)); + break; + case "velocity": + position.setSpeed(UnitsConverter.knotsFromKph(Integer.parseInt(value))); + break; + case "heading": + position.setCourse(Integer.parseInt(value)); + break; + case "eventid": + position.set(Position.KEY_EVENT, Integer.parseInt(value)); + break; + case "mileage": + position.set(Position.KEY_ODOMETER, (long) (Double.parseDouble(value) * 1000)); + break; + case "satellites": + position.set(Position.KEY_SATELLITES, Integer.parseInt(value)); + break; + case "ignition": + position.set(Position.KEY_IGNITION, Integer.parseInt(value) != 0); + break; + case "door": + position.set(Position.KEY_DOOR, Integer.parseInt(value) != 0); + break; + case "arm": + position.set(Position.KEY_ARMED, Integer.parseInt(value) != 0); + break; + case "fuel": + position.set(Position.KEY_FUEL_LEVEL, Integer.parseInt(value)); + break; + case "rpm": + position.set(Position.KEY_RPM, Integer.parseInt(value)); + break; + case "main_voltage": + position.set(Position.KEY_POWER, Double.parseDouble(value)); + break; + case "backup_voltage": + position.set(Position.KEY_BATTERY, Double.parseDouble(value)); + break; + case "analog1": + case "analog2": + case "analog3": + position.set(Position.PREFIX_ADC + (key.charAt(key.length() - 1) - '0'), Double.parseDouble(value)); + break; + case "extra1": + case "extra2": + case "extra3": + position.set(key, value); + default: + break; + } + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/Stl060FrameDecoder.java b/src/main/java/org/traccar/protocol/Stl060FrameDecoder.java new file mode 100644 index 000000000..f72474e2b --- /dev/null +++ b/src/main/java/org/traccar/protocol/Stl060FrameDecoder.java @@ -0,0 +1,48 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.CharacterDelimiterFrameDecoder; + +public class Stl060FrameDecoder extends CharacterDelimiterFrameDecoder { + + public Stl060FrameDecoder(int maxFrameLength) { + super(maxFrameLength, '#'); + } + + @Override + protected Object decode(ChannelHandlerContext ctx, ByteBuf buf) throws Exception { + + ByteBuf result = (ByteBuf) super.decode(ctx, buf); + + if (result != null) { + + int index = result.indexOf(result.readerIndex(), result.writerIndex(), (byte) '$'); + if (index == -1) { + return result; + } else { + result.skipBytes(index); + return result.readRetainedSlice(result.readableBytes()); + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Stl060Protocol.java b/src/main/java/org/traccar/protocol/Stl060Protocol.java new file mode 100644 index 000000000..2711e936b --- /dev/null +++ b/src/main/java/org/traccar/protocol/Stl060Protocol.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Stl060Protocol extends BaseProtocol { + + public Stl060Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new Stl060FrameDecoder(1024)); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new Stl060ProtocolDecoder(Stl060Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Stl060ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Stl060ProtocolDecoder.java new file mode 100644 index 000000000..7b0055aa1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Stl060ProtocolDecoder.java @@ -0,0 +1,120 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class Stl060ProtocolDecoder extends BaseProtocolDecoder { + + public Stl060ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .any() + .text("$1,") + .number("(d+),") // imei + .text("D001,") // type + .expression("[^,]*,") // vehicle + .number("(dd)/(dd)/(dd),") // date (dd/mm/yy) + .number("(dd):(dd):(dd),") // time (hh:mm:ss) + .number("(dd)(dd).?(d+)([NS]),") // latitude + .number("(ddd)(dd).?(d+)([EW]),") // longitude + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .groupBegin() + .number("(d+),") // odometer + .number("(d+),") // Ignition + .number("(d+),") // di1 + .number("(d+),") // di2 + .number("(d+),") // fuel + .or() + .expression("([01]),") // charging + .expression("([01]),") // ignition + .expression("0,0,") // reserved + .number("(d+),") // di + .expression("([^,]+),") // rfid + .number("(d+),") // odometer + .number("(d+),") // temperature + .number("(d+),") // fuel + .expression("([01]),") // accelerometer + .expression("([01]),") // do1 + .expression("([01]),") // do2 + .groupEnd() + .expression("([AV])") // validity + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN_HEM)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + // Old format + if (parser.hasNext(5)) { + position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + position.set(Position.KEY_IGNITION, parser.nextInt(0) == 1); + position.set(Position.KEY_INPUT, parser.nextInt(0) + parser.nextInt(0) << 1); + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt(0)); + } + + // New format + if (parser.hasNext(10)) { + position.set(Position.KEY_CHARGE, parser.nextInt(0) == 1); + position.set(Position.KEY_IGNITION, parser.nextInt(0) == 1); + position.set(Position.KEY_INPUT, parser.nextInt(0)); + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + position.set(Position.KEY_ODOMETER, parser.nextInt(0)); + position.set(Position.PREFIX_TEMP + 1, parser.nextInt(0)); + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt(0)); + position.set(Position.KEY_ACCELERATION, parser.nextInt(0) == 1); + position.set(Position.KEY_OUTPUT, parser.nextInt(0) + parser.nextInt(0) << 1); + } + + position.setValid(parser.next().equals("A")); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/SuntechFrameDecoder.java b/src/main/java/org/traccar/protocol/SuntechFrameDecoder.java new file mode 100644 index 000000000..8748ecc61 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SuntechFrameDecoder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class SuntechFrameDecoder extends BaseFrameDecoder { + + private ByteBuf readFrame(ByteBuf buf, int delimiterIndex) { + ByteBuf frame = buf.readRetainedSlice(delimiterIndex - buf.readerIndex()); + buf.skipBytes(1); // delimiter + return frame; + } + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.getByte(buf.readerIndex() + 1) == 0) { + + int length = 1 + 2 + buf.getShort(buf.readerIndex() + 1); + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + } else { + + int delimiterIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '\r'); + while (delimiterIndex > 0) { + if (delimiterIndex + 1 < buf.writerIndex() && buf.getByte(delimiterIndex + 1) == '\n') { + delimiterIndex = buf.indexOf(delimiterIndex + 1, buf.writerIndex(), (byte) '\r'); + } else { + return readFrame(buf, delimiterIndex); + } + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/SuntechProtocol.java b/src/main/java/org/traccar/protocol/SuntechProtocol.java new file mode 100644 index 000000000..199885537 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SuntechProtocol.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class SuntechProtocol extends BaseProtocol { + + public SuntechProtocol() { + setSupportedDataCommands( + Command.TYPE_OUTPUT_CONTROL, + Command.TYPE_REBOOT_DEVICE, + Command.TYPE_POSITION_SINGLE, + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME, + Command.TYPE_ALARM_ARM, + Command.TYPE_ALARM_DISARM); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new SuntechFrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new SuntechProtocolEncoder(SuntechProtocol.this)); + pipeline.addLast(new SuntechProtocolDecoder(SuntechProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java new file mode 100644 index 000000000..915f764e1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java @@ -0,0 +1,654 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +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 java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +public class SuntechProtocolDecoder extends BaseProtocolDecoder { + + private String prefix; + + private int protocolType; + private boolean hbm; + private boolean includeAdc; + private boolean includeRpm; + private boolean includeTemp; + + public SuntechProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public String getPrefix() { + return prefix; + } + + public void setProtocolType(int protocolType) { + this.protocolType = protocolType; + } + + public int getProtocolType(long deviceId) { + return Context.getIdentityManager().lookupAttributeInteger( + deviceId, getProtocolName() + ".protocolType", protocolType, false, true); + } + + public void setHbm(boolean hbm) { + this.hbm = hbm; + } + + public boolean isHbm(long deviceId) { + return Context.getIdentityManager().lookupAttributeBoolean( + deviceId, getProtocolName() + ".hbm", hbm, false, true); + } + + public void setIncludeAdc(boolean includeAdc) { + this.includeAdc = includeAdc; + } + + public boolean isIncludeAdc(long deviceId) { + return Context.getIdentityManager().lookupAttributeBoolean( + deviceId, getProtocolName() + ".includeAdc", includeAdc, false, true); + } + + public void setIncludeRpm(boolean includeRpm) { + this.includeRpm = includeRpm; + } + + public boolean isIncludeRpm(long deviceId) { + return Context.getIdentityManager().lookupAttributeBoolean( + deviceId, getProtocolName() + ".includeRpm", includeRpm, false, true); + } + + public void setIncludeTemp(boolean includeTemp) { + this.includeTemp = includeTemp; + } + + public boolean isIncludeTemp(long deviceId) { + return Context.getIdentityManager().lookupAttributeBoolean( + deviceId, getProtocolName() + ".includeTemp", includeTemp, false, true); + } + + private Position decode9( + Channel channel, SocketAddress remoteAddress, String[] values) throws ParseException { + int index = 1; + + String type = values[index++]; + + if (!type.equals("Location") && !type.equals("Emergency") && !type.equals("Alert")) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[index++]); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (type.equals("Emergency") || type.equals("Alert")) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + + if (!type.equals("Alert") || getProtocolType(deviceSession.getDeviceId()) == 0) { + position.set(Position.KEY_VERSION_FW, values[index++]); + } + + DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + position.setTime(dateFormat.parse(values[index++] + values[index++])); + + if (getProtocolType(deviceSession.getDeviceId()) == 1) { + index += 1; // cell + } + + position.setLatitude(Double.parseDouble(values[index++])); + position.setLongitude(Double.parseDouble(values[index++])); + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[index++]))); + position.setCourse(Double.parseDouble(values[index++])); + + position.setValid(values[index++].equals("1")); + + if (getProtocolType(deviceSession.getDeviceId()) == 1) { + position.set(Position.KEY_ODOMETER, Integer.parseInt(values[index++])); + } + + return position; + } + + private String decodeEmergency(int value) { + switch (value) { + case 1: + return Position.ALARM_SOS; + case 2: + return Position.ALARM_PARKING; + case 3: + return Position.ALARM_POWER_CUT; + case 5: + case 6: + return Position.ALARM_DOOR; + case 7: + return Position.ALARM_MOVEMENT; + case 8: + return Position.ALARM_SHOCK; + default: + return null; + } + } + + private String decodeAlert(int value) { + switch (value) { + case 1: + return Position.ALARM_OVERSPEED; + case 5: + return Position.ALARM_GEOFENCE_EXIT; + case 6: + return Position.ALARM_GEOFENCE_ENTER; + case 14: + return Position.ALARM_LOW_BATTERY; + case 15: + return Position.ALARM_SHOCK; + case 16: + return Position.ALARM_ACCIDENT; + case 46: + return Position.ALARM_ACCELERATION; + case 47: + return Position.ALARM_BRAKING; + case 48: + return Position.ALARM_ACCIDENT; + case 50: + return Position.ALARM_JAMMING; + default: + return null; + } + } + private Position decode4( + Channel channel, SocketAddress remoteAddress, String[] values) throws ParseException { + int index = 0; + + String type = values[index++].substring(5); + + if (!type.equals("STT") && !type.equals("ALT")) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[index++]); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.set(Position.KEY_TYPE, type); + + position.set(Position.KEY_VERSION_FW, values[index++]); + index += 1; // model + + Network network = new Network(); + + for (int i = 0; i < 7; i++) { + int cid = Integer.parseInt(values[index++]); + int mcc = Integer.parseInt(values[index++]); + int mnc = Integer.parseInt(values[index++]); + int lac, rssi; + if (i == 0) { + rssi = Integer.parseInt(values[index++]); + lac = Integer.parseInt(values[index++]); + } else { + lac = Integer.parseInt(values[index++]); + rssi = Integer.parseInt(values[index++]); + } + index += 1; // timing advance + if (cid > 0) { + network.addCellTower(CellTower.from(mcc, mnc, lac, cid, rssi)); + } + } + + position.setNetwork(network); + + position.set(Position.KEY_BATTERY, Double.parseDouble(values[index++])); + position.set(Position.KEY_ARCHIVE, values[index++].equals("0") ? true : null); + position.set(Position.KEY_INDEX, Integer.parseInt(values[index++])); + position.set(Position.KEY_STATUS, Integer.parseInt(values[index++])); + + if (values[index].length() == 3) { + index += 1; // collaborative network + } + + DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + position.setTime(dateFormat.parse(values[index++] + values[index++])); + + position.setLatitude(Double.parseDouble(values[index++])); + position.setLongitude(Double.parseDouble(values[index++])); + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[index++]))); + position.setCourse(Double.parseDouble(values[index++])); + + position.set(Position.KEY_SATELLITES, Integer.parseInt(values[index++])); + + position.setValid(values[index++].equals("1")); + + return position; + } + + private Position decode2356( + Channel channel, SocketAddress remoteAddress, String protocol, String[] values) throws ParseException { + int index = 0; + + String type = values[index++].substring(5); + + if (!type.equals("STT") && !type.equals("EMG") && !type.equals("EVT") + && !type.equals("ALT") && !type.equals("UEX")) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[index++]); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.set(Position.KEY_TYPE, type); + + if (protocol.startsWith("ST3") || protocol.equals("ST500") || protocol.equals("ST600")) { + index += 1; // model + } + + position.set(Position.KEY_VERSION_FW, values[index++]); + + DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + position.setTime(dateFormat.parse(values[index++] + values[index++])); + + if (!protocol.equals("ST500")) { + long cid = Long.parseLong(values[index++], 16); + if (protocol.equals("ST600")) { + position.setNetwork(new Network(CellTower.from( + Integer.parseInt(values[index++]), Integer.parseInt(values[index++]), + Integer.parseInt(values[index++], 16), cid, Integer.parseInt(values[index++])))); + } + } + + position.setLatitude(Double.parseDouble(values[index++])); + position.setLongitude(Double.parseDouble(values[index++])); + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[index++]))); + position.setCourse(Double.parseDouble(values[index++])); + + position.set(Position.KEY_SATELLITES, Integer.parseInt(values[index++])); + + position.setValid(values[index++].equals("1")); + + position.set(Position.KEY_ODOMETER, Integer.parseInt(values[index++])); + position.set(Position.KEY_POWER, Double.parseDouble(values[index++])); + + String io = values[index++]; + if (io.length() >= 6) { + position.set(Position.KEY_IGNITION, io.charAt(0) == '1'); + position.set(Position.PREFIX_IN + 1, io.charAt(1) == '1'); + position.set(Position.PREFIX_IN + 2, io.charAt(2) == '1'); + position.set(Position.PREFIX_IN + 3, io.charAt(3) == '1'); + position.set(Position.PREFIX_OUT + 1, io.charAt(4) == '1'); + position.set(Position.PREFIX_OUT + 2, io.charAt(5) == '1'); + } + + switch (type) { + case "STT": + position.set(Position.KEY_STATUS, Integer.parseInt(values[index++])); + position.set(Position.KEY_INDEX, Integer.parseInt(values[index++])); + break; + case "EMG": + position.set(Position.KEY_ALARM, decodeEmergency(Integer.parseInt(values[index++]))); + break; + case "EVT": + position.set(Position.KEY_EVENT, Integer.parseInt(values[index++])); + break; + case "ALT": + position.set(Position.KEY_ALARM, decodeAlert(Integer.parseInt(values[index++]))); + break; + case "UEX": + int remaining = Integer.parseInt(values[index++]); + while (remaining > 0) { + String attribute = values[index++]; + if (attribute.startsWith("CabAVL")) { + String[] data = attribute.split(","); + double fuel1 = Double.parseDouble(data[2]); + if (fuel1 > 0) { + position.set("fuel1", fuel1); + } + double fuel2 = Double.parseDouble(data[3]); + if (fuel2 > 0) { + position.set("fuel2", fuel2); + } + } else { + String[] pair = attribute.split("="); + if (pair.length >= 2) { + String value = pair[1].trim(); + if (value.contains(".")) { + value = value.substring(0, value.indexOf('.')); + } + switch (pair[0].charAt(0)) { + case 't': + position.set(Position.PREFIX_TEMP + pair[0].charAt(2), Integer.parseInt(value, 16)); + break; + case 'N': + position.set("fuel" + pair[0].charAt(2), Integer.parseInt(value, 16)); + break; + case 'Q': + position.set("drivingQuality", Integer.parseInt(value, 16)); + break; + default: + break; + } + } + } + remaining -= attribute.length() + 1; + } + index += 1; // checksum + break; + default: + break; + } + + if (isHbm(deviceSession.getDeviceId())) { + + if (index < values.length) { + position.set(Position.KEY_HOURS, UnitsConverter.msFromMinutes(Integer.parseInt(values[index++]))); + } + + if (index < values.length) { + position.set(Position.KEY_BATTERY, Double.parseDouble(values[index++])); + } + + if (index < values.length && values[index++].equals("0")) { + position.set(Position.KEY_ARCHIVE, true); + } + + if (isIncludeAdc(deviceSession.getDeviceId())) { + for (int i = 1; i <= 3; i++) { + if (!values[index++].isEmpty()) { + position.set(Position.PREFIX_ADC + i, Double.parseDouble(values[index - 1])); + } + } + } + + if (isIncludeRpm(deviceSession.getDeviceId()) && index < values.length) { + position.set(Position.KEY_RPM, Integer.parseInt(values[index++])); + } + + if (values.length - index >= 2) { + String driverUniqueId = values[index++]; + if (values[index++].equals("1") && !driverUniqueId.isEmpty()) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, driverUniqueId); + } + } + + if (isIncludeTemp(deviceSession.getDeviceId())) { + for (int i = 1; i <= 3; i++) { + String temperature = values[index++]; + String value = temperature.substring(temperature.indexOf(':') + 1); + if (!value.isEmpty()) { + position.set(Position.PREFIX_TEMP + i, Double.parseDouble(value)); + } + } + + } + + } + + return position; + } + + private Position decodeUniversal( + Channel channel, SocketAddress remoteAddress, String[] values) throws ParseException { + int index = 0; + + String type = values[index++]; + + if (!type.equals("STT") && !type.equals("ALT")) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[index++]); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.set(Position.KEY_TYPE, type); + + int mask = Integer.parseInt(values[index++], 16); + + if (BitUtil.check(mask, 1)) { + index += 1; // model + } + + if (BitUtil.check(mask, 2)) { + position.set(Position.KEY_VERSION_FW, values[index++]); + } + + if (BitUtil.check(mask, 3) && values[index++].equals("0")) { + position.set(Position.KEY_ARCHIVE, true); + } + + if (BitUtil.check(mask, 4) && BitUtil.check(mask, 5)) { + DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + position.setTime(dateFormat.parse(values[index++] + values[index++])); + } + + if (BitUtil.check(mask, 6)) { + index += 1; // cell + } + + if (BitUtil.check(mask, 7)) { + index += 1; // mcc + } + + if (BitUtil.check(mask, 8)) { + index += 1; // mnc + } + + if (BitUtil.check(mask, 9)) { + index += 1; // lac + } + + if (BitUtil.check(mask, 10)) { + position.set(Position.KEY_RSSI, Integer.parseInt(values[index++])); + } + + if (BitUtil.check(mask, 11)) { + position.setLatitude(Double.parseDouble(values[index++])); + } + + if (BitUtil.check(mask, 12)) { + position.setLongitude(Double.parseDouble(values[index++])); + } + + if (BitUtil.check(mask, 13)) { + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[index++]))); + } + + if (BitUtil.check(mask, 14)) { + position.setCourse(Double.parseDouble(values[index++])); + } + + if (BitUtil.check(mask, 15)) { + position.set(Position.KEY_SATELLITES, Integer.parseInt(values[index++])); + } + + if (BitUtil.check(mask, 16)) { + position.setValid(values[index++].equals("1")); + } + + return position; + } + + private Position decodeBinary( + Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + int type = buf.readUnsignedByte(); + buf.readUnsignedShort(); // length + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, ByteBufUtil.hexDump(buf.readSlice(5))); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int mask = buf.readUnsignedMedium(); + + if (BitUtil.check(mask, 1)) { + buf.readUnsignedByte(); // model + } + + if (BitUtil.check(mask, 2)) { + position.set(Position.KEY_VERSION_FW, String.format("%d.%d.%d", + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte())); + } + + if (BitUtil.check(mask, 3) && buf.readUnsignedByte() == 0) { + position.set(Position.KEY_ARCHIVE, true); + } + + if (BitUtil.check(mask, 4) && BitUtil.check(mask, 5)) { + position.setTime(new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .getDate()); + } + + if (BitUtil.check(mask, 6)) { + buf.readUnsignedInt(); // cell + } + + if (BitUtil.check(mask, 7)) { + buf.readUnsignedShort(); // mcc + } + + if (BitUtil.check(mask, 8)) { + buf.readUnsignedShort(); // mnc + } + + if (BitUtil.check(mask, 9)) { + buf.readUnsignedShort(); // lac + } + + if (BitUtil.check(mask, 10)) { + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + } + + if (BitUtil.check(mask, 11)) { + long value = buf.readUnsignedInt(); + if (BitUtil.check(value, 31)) { + value = -BitUtil.to(value, 31); + } + position.setLatitude(value / 1000000.0); + } + + if (BitUtil.check(mask, 12)) { + long value = buf.readUnsignedInt(); + if (BitUtil.check(value, 31)) { + value = -BitUtil.to(value, 31); + } + position.setLongitude(value / 1000000.0); + } + + if (BitUtil.check(mask, 13)) { + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() / 100.0)); + } + + if (BitUtil.check(mask, 14)) { + position.setCourse(buf.readUnsignedShort() / 100.0); + } + + if (BitUtil.check(mask, 15)) { + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + } + + if (BitUtil.check(mask, 16)) { + position.setValid(buf.readUnsignedByte() > 0); + } + + if (BitUtil.check(mask, 17)) { + int input = buf.readUnsignedByte(); + position.set(Position.KEY_IGNITION, BitUtil.check(input, 0)); + position.set(Position.KEY_INPUT, input); + } + + if (BitUtil.check(mask, 18)) { + position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); + } + + if (BitUtil.check(mask, 19)) { + int value = buf.readUnsignedByte(); + if (type == 0x82) { + position.set(Position.KEY_ALARM, decodeAlert(value)); + } + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (buf.getByte(buf.readerIndex() + 1) == 0) { + + return decodeBinary(channel, remoteAddress, buf); + + } else { + + String[] values = buf.toString(StandardCharsets.US_ASCII).split(";"); + prefix = values[0]; + + if (prefix.length() < 5) { + return decodeUniversal(channel, remoteAddress, values); + } else if (prefix.startsWith("ST9")) { + return decode9(channel, remoteAddress, values); + } else if (prefix.startsWith("ST4")) { + return decode4(channel, remoteAddress, values); + } else { + return decode2356(channel, remoteAddress, prefix.substring(0, 5), values); + } + } + } + +} diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java new file mode 100644 index 000000000..3b4995110 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SuntechProtocolEncoder.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BasePipelineFactory; +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class SuntechProtocolEncoder extends StringProtocolEncoder { + + public SuntechProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private String getPrefix(Channel channel) { + String prefix = "SA200CMD"; + if (channel != null) { + SuntechProtocolDecoder protocolDecoder = + BasePipelineFactory.getHandler(channel.pipeline(), SuntechProtocolDecoder.class); + if (protocolDecoder != null) { + String decoderPrefix = protocolDecoder.getPrefix(); + if (decoderPrefix != null && decoderPrefix.length() > 5) { + prefix = decoderPrefix.substring(0, decoderPrefix.length() - 3) + "CMD"; + } + } + } + return prefix; + } + + @Override + protected Object encodeCommand(Channel channel, Command command) { + + String prefix = getPrefix(channel); + + switch (command.getType()) { + case Command.TYPE_REBOOT_DEVICE: + return formatCommand(command, prefix + ";%s;02;Reboot\r", Command.KEY_UNIQUE_ID); + case Command.TYPE_POSITION_SINGLE: + return formatCommand(command, prefix + ";%s;02;\r", Command.KEY_UNIQUE_ID); + case Command.TYPE_OUTPUT_CONTROL: + if (command.getAttributes().containsKey(Command.KEY_DATA)) { + if (command.getAttributes().get(Command.KEY_DATA).equals("1")) { + return formatCommand(command, prefix + ";%s;02;Enable%s\r", + Command.KEY_UNIQUE_ID, Command.KEY_INDEX); + } else { + return formatCommand(command, prefix + ";%s;02;Disable%s\r", + Command.KEY_UNIQUE_ID, Command.KEY_INDEX); + } + } + case Command.TYPE_ENGINE_STOP: + return formatCommand(command, prefix + ";%s;02;Enable1\r", Command.KEY_UNIQUE_ID); + case Command.TYPE_ENGINE_RESUME: + return formatCommand(command, prefix + ";%s;02;Disable1\r", Command.KEY_UNIQUE_ID); + case Command.TYPE_ALARM_ARM: + return formatCommand(command, prefix + ";%s;02;Enable2\r", Command.KEY_UNIQUE_ID); + case Command.TYPE_ALARM_DISARM: + return formatCommand(command, prefix + ";%s;02;Disable2\r", Command.KEY_UNIQUE_ID); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/SupermateProtocol.java b/src/main/java/org/traccar/protocol/SupermateProtocol.java new file mode 100644 index 000000000..46625ddc7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SupermateProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class SupermateProtocol extends BaseProtocol { + + public SupermateProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "#")); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new SupermateProtocolDecoder(SupermateProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SupermateProtocolDecoder.java b/src/main/java/org/traccar/protocol/SupermateProtocolDecoder.java new file mode 100644 index 000000000..40a25bb91 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SupermateProtocolDecoder.java @@ -0,0 +1,122 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +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.Calendar; +import java.util.regex.Pattern; + +public class SupermateProtocolDecoder extends BaseProtocolDecoder { + + public SupermateProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("d+:") // header + .number("(d+):") // imei + .number("d+:").text("*,") + .number("(d+),") // command id + .expression("([^,]{2}),") // command + .expression("([AV]),") // validity + .number("(xx)(xx)(xx),") // date (yymmdd) + .number("(xx)(xx)(xx),") // time (hhmmss) + .number("(x)(x{7}),") // latitude + .number("(x)(x{7}),") // longitude + .number("(x{4}),") // speed + .number("(x{4}),") // course + .number("(x{12}),") // status + .number("(x+),") // signal + .number("(d+),") // power + .number("(x{4}),") // oil + .number("(x+)?") // odometer + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + String imei = parser.next(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.set("commandId", parser.next()); + position.set(Position.KEY_COMMAND, parser.next()); + + position.setValid(parser.next().equals("A")); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(parser.nextHexInt(0), parser.nextHexInt(0), parser.nextHexInt(0)) + .setTime(parser.nextHexInt(0), parser.nextHexInt(0), parser.nextHexInt(0)); + position.setTime(dateBuilder.getDate()); + + if (parser.nextHexInt(0) == 8) { + position.setLatitude(-parser.nextHexInt(0) / 600000.0); + } else { + position.setLatitude(parser.nextHexInt(0) / 600000.0); + } + + if (parser.nextHexInt(0) == 8) { + position.setLongitude(-parser.nextHexInt(0) / 600000.0); + } else { + position.setLongitude(parser.nextHexInt(0) / 600000.0); + } + + position.setSpeed(parser.nextHexInt(0) / 100.0); + position.setCourse(parser.nextHexInt(0) / 100.0); + + position.set(Position.KEY_STATUS, parser.next()); + position.set("signal", parser.next()); + position.set(Position.KEY_POWER, parser.nextDouble(0)); + position.set("oil", parser.nextHexInt(0)); + position.set(Position.KEY_ODOMETER, parser.nextHexInt(0)); + + if (channel != null) { + Calendar calendar = Calendar.getInstance(); + String content = String.format("#1:%s:1:*,00000000,UP,%02x%02x%02x,%02x%02x%02x#", imei, + calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH), + calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND)); + channel.writeAndFlush(new NetworkMessage( + Unpooled.copiedBuffer(content, StandardCharsets.US_ASCII), remoteAddress)); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/SviasProtocol.java b/src/main/java/org/traccar/protocol/SviasProtocol.java new file mode 100644 index 000000000..accfa173f --- /dev/null +++ b/src/main/java/org/traccar/protocol/SviasProtocol.java @@ -0,0 +1,51 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import org.traccar.model.Command; + +public class SviasProtocol extends BaseProtocol { + + public SviasProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_POSITION_SINGLE, + Command.TYPE_SET_ODOMETER, + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME, + Command.TYPE_ALARM_ARM, + Command.TYPE_ALARM_DISARM, + Command.TYPE_ALARM_REMOVE); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "]")); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new SviasProtocolEncoder(SviasProtocol.this)); + pipeline.addLast(new SviasProtocolDecoder(SviasProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SviasProtocolDecoder.java b/src/main/java/org/traccar/protocol/SviasProtocolDecoder.java new file mode 100644 index 000000000..7e783f6cd --- /dev/null +++ b/src/main/java/org/traccar/protocol/SviasProtocolDecoder.java @@ -0,0 +1,105 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.PatternBuilder; + +import java.net.SocketAddress; +import java.util.regex.Pattern; +import org.traccar.DeviceSession; +import org.traccar.helper.Parser; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +public class SviasProtocolDecoder extends BaseProtocolDecoder { + + public SviasProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("[") // delimiter + .number("d{4},") // hardware version + .number("d{4},") // software version + .number("d+,") // index + .number("(d+),") // imei + .number("d+,") // hour meter + .number("(d+)(dd)(dd),") // date (dmmyy) + .number("(d+)(dd)(dd),") // time (hmmss) + .number("(-?)(d+)(dd)(d{5}),") // latitude + .number("(-?)(d+)(dd)(d{5}),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(d+),") // odometer + .number("(d+),") // input + .number("(d+),") // output / status + .number("(d),") + .number("(d),") + .number("(d+),") // power + .number("(d+),") // battery level + .number("(d+),") // rssi + .any() + .compile(); + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) + throws Exception { + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("@", remoteAddress)); + } + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN_MIN)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN_MIN)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble() * 0.01)); + position.setCourse(parser.nextDouble() * 0.01); + + position.set(Position.KEY_ODOMETER, parser.nextInt() * 100); + + int input = parser.nextInt(); + int output = parser.nextInt(); + + position.set(Position.KEY_ALARM, BitUtil.check(input, 0) ? Position.ALARM_SOS : null); + position.set(Position.KEY_IGNITION, BitUtil.check(input, 4)); + position.setValid(BitUtil.check(output, 0)); + + position.set(Position.KEY_POWER, parser.nextInt() * 0.001); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + position.set(Position.KEY_RSSI, parser.nextInt()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/SviasProtocolEncoder.java b/src/main/java/org/traccar/protocol/SviasProtocolEncoder.java new file mode 100644 index 000000000..d218f63ce --- /dev/null +++ b/src/main/java/org/traccar/protocol/SviasProtocolEncoder.java @@ -0,0 +1,53 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class SviasProtocolEncoder extends StringProtocolEncoder { + + public SviasProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return formatCommand(command, "%s", Command.KEY_DATA); + case Command.TYPE_POSITION_SINGLE: + return formatCommand(command, "AT+STR=1*"); + case Command.TYPE_SET_ODOMETER: + return formatCommand(command, "AT+ODT=%s*", Command.KEY_DATA); + case Command.TYPE_ENGINE_STOP: + return formatCommand(command, "AT+OUT=1,1*"); + case Command.TYPE_ENGINE_RESUME: + return formatCommand(command, "AT+OUT=1,0*"); + case Command.TYPE_ALARM_ARM: + return formatCommand(command, "AT+OUT=2,1*"); + case Command.TYPE_ALARM_DISARM: + return formatCommand(command, "AT+OUT=2,0*"); + case Command.TYPE_ALARM_REMOVE: + return formatCommand(command, "AT+PNC=600*"); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/T55Protocol.java b/src/main/java/org/traccar/protocol/T55Protocol.java new file mode 100644 index 000000000..f5ec19094 --- /dev/null +++ b/src/main/java/org/traccar/protocol/T55Protocol.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class T55Protocol extends BaseProtocol { + + public T55Protocol() { + 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 T55ProtocolDecoder(T55Protocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new T55ProtocolDecoder(T55Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java b/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java new file mode 100644 index 000000000..b75addfae --- /dev/null +++ b/src/main/java/org/traccar/protocol/T55ProtocolDecoder.java @@ -0,0 +1,316 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +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.channels.DatagramChannel; +import java.util.Date; +import java.util.regex.Pattern; + +public class T55ProtocolDecoder extends BaseProtocolDecoder { + + public T55ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_GPRMC = new PatternBuilder() + .text("$GPRMC,") + .number("(dd)(dd)(dd).?d*,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d{2,3})(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .number("(dd)(dd)(dd),") // date (ddmmyy) + .expression("[^*]+") + .text("*") + .expression("[^,]+") + .number(",(d+)") // satellites + .number(",(d+)") // imei + .expression(",([01])") // ignition + .number(",(d+)") // fuel + .number(",(d+)").optional(7) // battery + .number("((?:,d+)+)?") // parameters + .any() + .compile(); + + private static final Pattern PATTERN_GPGGA = new PatternBuilder() + .text("$GPGGA,") + .number("(dd)(dd)(dd).?d*,") // time (hhmmss) + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .expression("([EW]),") + .any() + .compile(); + + private static final Pattern PATTERN_GPRMA = new PatternBuilder() + .text("$GPRMA,") + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),,,") + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .any() + .compile(); + + private static final Pattern PATTERN_TRCCR = new PatternBuilder() + .text("$TRCCR,") + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd).?d*,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(d+.d+),") // speed + .number("(d+.d+),") // course + .number("(-?d+.d+),") // altitude + .number("(d+.?d*),") // battery + .any() + .compile(); + + private static final Pattern PATTERN_GPIOP = new PatternBuilder() + .text("$GPIOP,") + .number("[01]{8},") // inputs + .number("[01]{8},") // outputs + .number("d+.d+,") // adc 1 + .number("d+.d+,") // adc 2 + .number("d+.d+,") // adc 3 + .number("d+.d+,") // adc 4 + .number("(d+.d+),") // power + .number("(d+.d+)") // battery + .any() + .compile(); + + private Position position = null; + + private Position decodeGprmc( + DeviceSession deviceSession, String sentence, SocketAddress remoteAddress, Channel channel) { + + if (deviceSession != null && channel != null && !(channel instanceof DatagramChannel) + && Context.getIdentityManager().lookupAttributeBoolean( + deviceSession.getDeviceId(), getProtocolName() + ".ack", false, false, true)) { + channel.writeAndFlush(new NetworkMessage("OK1\r\n", remoteAddress)); + } + + Parser parser = new Parser(PATTERN_GPRMC, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + if (deviceSession != null) { + position.setDeviceId(deviceSession.getDeviceId()); + } + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + if (parser.hasNext(5)) { + position.set(Position.KEY_SATELLITES, parser.nextInt()); + + deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_IGNITION, parser.hasNext() && parser.next().equals("1")); + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt(0)); + position.set(Position.KEY_BATTERY, parser.nextInt()); + } + + if (parser.hasNext()) { + String[] parameters = parser.next().split(","); + for (int i = 1; i < parameters.length; i++) { + position.set(Position.PREFIX_IO + i, parameters[i]); + } + } + + if (deviceSession != null) { + return position; + } else { + this.position = position; // save position + return null; + } + } + + private Position decodeGpgga(DeviceSession deviceSession, String sentence) { + + Parser parser = new Parser(PATTERN_GPGGA, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setCurrentDate() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + position.setValid(true); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + return position; + } + + private Position decodeGprma(DeviceSession deviceSession, String sentence) { + + Parser parser = new Parser(PATTERN_GPRMA, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date()); + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + return position; + } + + private Position decodeTrccr(DeviceSession deviceSession, String sentence) { + + Parser parser = new Parser(PATTERN_TRCCR, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + position.set(Position.KEY_BATTERY, parser.nextDouble(0)); + + return position; + } + + private Position decodeGpiop(DeviceSession deviceSession, String sentence) { + + Parser parser = new Parser(PATTERN_GPIOP, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_POWER, parser.nextDouble()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + DeviceSession deviceSession; + + if (!sentence.startsWith("$") && sentence.contains("$")) { + int index = sentence.indexOf("$"); + String id = sentence.substring(0, index); + if (id.endsWith(",")) { + id = id.substring(0, id.length() - 1); + } else if (id.endsWith("/")) { + id = id.substring(id.indexOf('/') + 1, id.length() - 1); + } + deviceSession = getDeviceSession(channel, remoteAddress, id); + sentence = sentence.substring(index); + } else { + deviceSession = getDeviceSession(channel, remoteAddress); + } + + if (sentence.startsWith("$PGID")) { + getDeviceSession(channel, remoteAddress, sentence.substring(6, sentence.length() - 3)); + } else if (sentence.startsWith("$DEVID")) { + getDeviceSession(channel, remoteAddress, sentence.substring(7, sentence.lastIndexOf('*'))); + } else if (sentence.startsWith("$PCPTI")) { + getDeviceSession(channel, remoteAddress, sentence.substring(7, sentence.indexOf(",", 7))); + } else if (sentence.startsWith("IMEI")) { + getDeviceSession(channel, remoteAddress, sentence.substring(5)); + } else if (sentence.startsWith("$IMEI")) { + getDeviceSession(channel, remoteAddress, sentence.substring(6)); + } else if (sentence.startsWith("$GPFID")) { + deviceSession = getDeviceSession(channel, remoteAddress, sentence.substring(7)); + if (deviceSession != null && position != null) { + Position position = this.position; + position.setDeviceId(deviceSession.getDeviceId()); + this.position = null; + return position; + } + } else if (sentence.matches("^[0-9A-F]+$")) { + getDeviceSession(channel, remoteAddress, sentence); + } else if (sentence.startsWith("$GPRMC")) { + return decodeGprmc(deviceSession, sentence, remoteAddress, channel); + } else if (sentence.startsWith("$GPGGA") && deviceSession != null) { + return decodeGpgga(deviceSession, sentence); + } else if (sentence.startsWith("$GPRMA") && deviceSession != null) { + return decodeGprma(deviceSession, sentence); + } else if (sentence.startsWith("$TRCCR") && deviceSession != null) { + return decodeTrccr(deviceSession, sentence); + } else if (sentence.startsWith("$GPIOP")) { + return decodeGpiop(deviceSession, sentence); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/T57FrameDecoder.java b/src/main/java/org/traccar/protocol/T57FrameDecoder.java new file mode 100644 index 000000000..14ba31453 --- /dev/null +++ b/src/main/java/org/traccar/protocol/T57FrameDecoder.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +import java.nio.charset.StandardCharsets; + +public class T57FrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 10) { + return null; + } + + String type = buf.toString(buf.readerIndex() + 5, 2, StandardCharsets.US_ASCII); + int count = type.equals("F3") ? 12 : 14; + + int index = 0; + while (index >= 0 && count > 0) { + index = buf.indexOf(index + 1, buf.writerIndex(), (byte) '#'); + if (index > 0) { + count -= 1; + } + } + + return index > 0 ? buf.readRetainedSlice(index + 1 - buf.readerIndex()) : null; + } + +} diff --git a/src/main/java/org/traccar/protocol/T57Protocol.java b/src/main/java/org/traccar/protocol/T57Protocol.java new file mode 100644 index 000000000..f67f82318 --- /dev/null +++ b/src/main/java/org/traccar/protocol/T57Protocol.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class T57Protocol extends BaseProtocol { + + public T57Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new T57FrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new T57ProtocolDecoder(T57Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/T57ProtocolDecoder.java b/src/main/java/org/traccar/protocol/T57ProtocolDecoder.java new file mode 100644 index 000000000..2a3cca3e4 --- /dev/null +++ b/src/main/java/org/traccar/protocol/T57ProtocolDecoder.java @@ -0,0 +1,84 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class T57ProtocolDecoder extends BaseProtocolDecoder { + + public T57ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("*T57#") + .number("Fd#") // type + .number("([^#]+)#") // device id + .number("(dd)(dd)(dd)#") // date (ddmmyy) + .number("(dd)(dd)(dd)#") // time (hhmmss) + .number("(dd)(dd.d+)#") // latitude + .expression("([NS])#") + .number("(ddd)(dd.d+)#") // longitude + .expression("([EW])#") + .expression("[^#]+#") + .number("(d+.d+)#") // speed + .number("(d+.d+)#") // altitude + .expression("([AV])") // valid + .number("d#") // fix type + .number("(d+.d+)#") // battery + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble()); + position.setAltitude(parser.nextDouble()); + + position.setValid(parser.next().equals("A")); + + position.set(Position.KEY_BATTERY, parser.nextDouble()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/T800xProtocol.java b/src/main/java/org/traccar/protocol/T800xProtocol.java new file mode 100644 index 000000000..8b91265cb --- /dev/null +++ b/src/main/java/org/traccar/protocol/T800xProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class T800xProtocol extends BaseProtocol { + + public T800xProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 2, -5, 0)); + pipeline.addLast(new T800xProtocolEncoder(T800xProtocol.this)); + pipeline.addLast(new T800xProtocolDecoder(T800xProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java new file mode 100644 index 000000000..9b146ec90 --- /dev/null +++ b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java @@ -0,0 +1,314 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.math.BigInteger; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class T800xProtocolDecoder extends BaseProtocolDecoder { + + private short header = DEFAULT_HEADER; + + public short getHeader() { + return header; + } + + public T800xProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final short DEFAULT_HEADER = 0x2323; + + public static final int MSG_LOGIN = 0x01; + public static final int MSG_GPS = 0x02; + public static final int MSG_HEARTBEAT = 0x03; + public static final int MSG_ALARM = 0x04; + public static final int MSG_NETWORK = 0x05; + public static final int MSG_COMMAND = 0x81; + + private void sendResponse(Channel channel, short header, int type, int index, ByteBuf imei, int alarm) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(alarm > 0 ? 16 : 15); + response.writeShort(header); + response.writeByte(type); + response.writeShort(response.capacity()); // length + response.writeShort(index); + response.writeBytes(imei); + if (alarm > 0) { + response.writeByte(alarm); + } + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + private String decodeAlarm(int value) { + switch (value) { + case 1: + return Position.ALARM_POWER_CUT; + case 2: + return Position.ALARM_LOW_BATTERY; + case 3: + return Position.ALARM_SOS; + case 4: + return Position.ALARM_OVERSPEED; + case 5: + return Position.ALARM_GEOFENCE_ENTER; + case 6: + return Position.ALARM_GEOFENCE_EXIT; + case 7: + return Position.ALARM_TOW; + case 8: + case 10: + return Position.ALARM_VIBRATION; + case 21: + return Position.ALARM_JAMMING; + case 23: + return Position.ALARM_POWER_RESTORED; + case 24: + return Position.ALARM_LOW_POWER; + default: + return null; + } + } + + private Date readDate(ByteBuf buf) { + return new DateBuilder() + .setYear(BcdUtil.readInteger(buf, 2)) + .setMonth(BcdUtil.readInteger(buf, 2)) + .setDay(BcdUtil.readInteger(buf, 2)) + .setHour(BcdUtil.readInteger(buf, 2)) + .setMinute(BcdUtil.readInteger(buf, 2)) + .setSecond(BcdUtil.readInteger(buf, 2)) + .getDate(); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + header = buf.readShort(); + int type = buf.readUnsignedByte(); + buf.readUnsignedShort(); // length + int index = buf.readUnsignedShort(); + ByteBuf imei = buf.readSlice(8); + + DeviceSession deviceSession = getDeviceSession( + channel, remoteAddress, ByteBufUtil.hexDump(imei).substring(1)); + if (deviceSession == null) { + return null; + } + + if (type != MSG_GPS && type != MSG_ALARM) { + sendResponse(channel, header, type, index, imei, 0); + } + + if (type == MSG_GPS || type == MSG_ALARM) { + + return decodePosition(channel, deviceSession, buf, type, index, imei); + + } else if (type == MSG_NETWORK) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, readDate(buf)); + + position.set(Position.KEY_OPERATOR, buf.readCharSequence( + buf.readUnsignedByte(), StandardCharsets.UTF_16LE).toString()); + position.set("networkTechnology", buf.readCharSequence( + buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString()); + position.set("networkBand", buf.readCharSequence( + buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString()); + buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII); // imsi + position.set(Position.KEY_ICCID, buf.readCharSequence( + buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString()); + + return position; + + } else if (type == MSG_COMMAND) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + buf.readUnsignedByte(); // protocol number + + position.set(Position.KEY_RESULT, buf.toString(StandardCharsets.UTF_16LE)); + + return position; + + } + + return null; + } + + private Position decodePosition( + Channel channel, DeviceSession deviceSession, + ByteBuf buf, int type, int index, ByteBuf imei) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_INDEX, index); + + if (header != 0x2727) { + + buf.readUnsignedShort(); // acc on interval + buf.readUnsignedShort(); // acc off interval + buf.readUnsignedByte(); // angle compensation + buf.readUnsignedShort(); // distance compensation + + position.set(Position.KEY_RSSI, BitUtil.to(buf.readUnsignedShort(), 7)); + + } + + int status = buf.readUnsignedByte(); + position.set(Position.KEY_SATELLITES, BitUtil.to(status, 5)); + + if (header != 0x2727) { + + buf.readUnsignedByte(); // gsensor manager status + buf.readUnsignedByte(); // other flags + buf.readUnsignedByte(); // heartbeat + buf.readUnsignedByte(); // relay status + buf.readUnsignedShort(); // drag alarm setting + + int io = buf.readUnsignedShort(); + position.set(Position.KEY_IGNITION, BitUtil.check(io, 14)); + position.set("ac", BitUtil.check(io, 13)); + for (int i = 0; i <= 2; i++) { + 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()); + + } + + int alarm = buf.readUnsignedByte(); + position.set(Position.KEY_ALARM, decodeAlarm(alarm)); + + if (header != 0x2727) { + + buf.readUnsignedByte(); // reserved + + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + + int battery = BcdUtil.readInteger(buf, 2); + if (battery == 0) { + battery = 100; + } + position.set(Position.KEY_BATTERY, battery); + + } + + if (BitUtil.check(status, 6)) { + + position.setValid(!BitUtil.check(status, 7)); + position.setTime(readDate(buf)); + position.setAltitude(buf.readFloatLE()); + position.setLongitude(buf.readFloatLE()); + position.setLatitude(buf.readFloatLE()); + position.setSpeed(UnitsConverter.knotsFromKph(BcdUtil.readInteger(buf, 4) * 0.1)); + position.setCourse(buf.readUnsignedShort()); + + } else { + + getLastLocation(position, readDate(buf)); + + int mcc = buf.readUnsignedShortLE(); + int mnc = buf.readUnsignedShortLE(); + + if (mcc != 0xffff && mnc != 0xffff) { + Network network = new Network(); + for (int i = 0; i < 3; i++) { + network.addCellTower(CellTower.from( + mcc, mnc, buf.readUnsignedShortLE(), buf.readUnsignedShortLE())); + } + position.setNetwork(network); + } + + } + + if (header == 0x2727) { + + byte[] accelerationBytes = new byte[5]; + buf.readBytes(accelerationBytes); + long acceleration = new BigInteger(accelerationBytes).longValue(); + double accelerationZ = BitUtil.between(acceleration, 8, 15) + BitUtil.between(acceleration, 4, 8) * 0.1; + if (!BitUtil.check(acceleration, 15)) { + accelerationZ = -accelerationZ; + } + double accelerationY = BitUtil.between(acceleration, 20, 27) + BitUtil.between(acceleration, 16, 20) * 0.1; + if (!BitUtil.check(acceleration, 27)) { + accelerationY = -accelerationY; + } + double accelerationX = BitUtil.between(acceleration, 28, 32) + BitUtil.between(acceleration, 32, 39) * 0.1; + if (!BitUtil.check(acceleration, 39)) { + accelerationX = -accelerationX; + } + position.set(Position.KEY_G_SENSOR, "[" + accelerationX + "," + accelerationY + "," + accelerationZ + "]"); + + position.set(Position.KEY_BATTERY_LEVEL, BcdUtil.readInteger(buf, 2)); + position.set(Position.KEY_DEVICE_TEMP, (int) buf.readByte()); + position.set("lightSensor", BcdUtil.readInteger(buf, 2) * 0.1); + position.set(Position.KEY_BATTERY, BcdUtil.readInteger(buf, 2) * 0.1); + position.set("solarPanel", BcdUtil.readInteger(buf, 2) * 0.1); + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + + int inputStatus = buf.readUnsignedShort(); + position.set(Position.KEY_IGNITION, BitUtil.check(inputStatus, 2)); + position.set(Position.KEY_RSSI, BitUtil.between(inputStatus, 4, 11)); + + buf.readUnsignedShort(); // ignition on upload interval + buf.readUnsignedInt(); // ignition off upload interval + buf.readUnsignedByte(); // angle upload interval + buf.readUnsignedShort(); // distance upload interval + buf.readUnsignedByte(); // heartbeat + + } else if (buf.readableBytes() >= 2) { + + position.set(Position.KEY_POWER, BcdUtil.readInteger(buf, 4) * 0.01); + + } + + sendResponse(channel, header, type, index, imei, alarm); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/T800xProtocolEncoder.java b/src/main/java/org/traccar/protocol/T800xProtocolEncoder.java new file mode 100644 index 000000000..74587c8b1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/T800xProtocolEncoder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolEncoder; +import org.traccar.helper.DataConverter; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.nio.charset.StandardCharsets; + +public class T800xProtocolEncoder extends BaseProtocolEncoder { + + public static final int MODE_SETTING = 0x01; + public static final int MODE_BROADCAST = 0x02; + public static final int MODE_FORWARD = 0x03; + + public T800xProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf encodeContent(Command command, short header, String content) { + + ByteBuf buf = Unpooled.buffer(); + + buf.writeShort(header); + buf.writeByte(T800xProtocolDecoder.MSG_COMMAND); + buf.writeShort(7 + 8 + 1 + content.length()); + buf.writeShort(1); // serial number + buf.writeBytes(DataConverter.parseHex("0" + getUniqueId(command.getDeviceId()))); + buf.writeByte(MODE_SETTING); + buf.writeBytes(content.getBytes(StandardCharsets.US_ASCII)); + + return buf; + } + + @Override + protected Object encodeCommand(Channel channel, Command command) { + + short header = T800xProtocolDecoder.DEFAULT_HEADER; + if (channel != null) { + header = channel.pipeline().get(T800xProtocolDecoder.class).getHeader(); + } + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return encodeContent(command, header, command.getString(Command.KEY_DATA)); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/TaipProtocol.java b/src/main/java/org/traccar/protocol/TaipProtocol.java new file mode 100644 index 000000000..b8f40a183 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TaipProtocol.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TaipProtocol extends BaseProtocol { + + public TaipProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '<')); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new TaipProtocolDecoder(TaipProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new TaipProtocolDecoder(TaipProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java b/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java new file mode 100644 index 000000000..8a0cb870b --- /dev/null +++ b/src/main/java/org/traccar/protocol/TaipProtocolDecoder.java @@ -0,0 +1,287 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.DateUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; +import java.util.regex.Pattern; + +public class TaipProtocolDecoder extends BaseProtocolDecoder { + + public TaipProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .groupBegin() + .expression("R[EP]V") // type + .groupBegin() + .number("(dd)") // event + .number("(dddd)") // week + .number("(d)") // day + .groupEnd("?") + .number("(d{5})") // seconds + .or() + .expression("(?:RGP|RCQ|RCV|RBR)") // type + .number("(dd)?") // event + .number("(dd)(dd)(dd)") // date (mmddyy) + .number("(dd)(dd)(dd)") // time (hhmmss) + .groupEnd() + .groupBegin() + .number("([-+]dd)(d{5})") // latitude + .number("([-+]ddd)(d{5})") // longitude + .or() + .number("([-+])(dd)(dd.dddd)") // latitude + .number("([-+])(ddd)(dd.dddd)") // longitude + .groupEnd() + .number("(ddd)") // speed + .number("(ddd)") // course + .groupBegin() + .number("([023])") // fix mode + .number("xx") // data age + .number("(xx)") // input + .number("(dd)") // event + .number("(dd)") // hdop + .or() + .groupBegin() + .number("(xx)") // input + .number("(xx)") // satellites + .number("(ddd)") // battery + .number("(x{8})") // odometer + .number("[01]") // gps power + .groupBegin() + .number("([023])") // fix mode + .number("(dd)") // pdop + .number("dd") // satellites + .number("xxxx") // data age + .number("[01]") // modem power + .number("[0-5]") // gsm status + .number("(dd)") // rssi + .number("([-+]dddd)") // temperature 1 + .number("xx") // seconds from last + .number("([-+]dddd)") // temperature 2 + .number("xx") // seconds from last + .groupEnd("?") + .groupEnd("?") + .groupEnd() + .any() + .compile(); + + private Date getTime(long week, long day, long seconds) { + DateBuilder dateBuilder = new DateBuilder() + .setDate(1980, 1, 6) + .addMillis(((week * 7 + day) * 24 * 60 * 60 + seconds) * 1000); + return dateBuilder.getDate(); + } + + private Date getTime(long seconds) { + DateBuilder dateBuilder = new DateBuilder(new Date()) + .setTime(0, 0, 0, 0) + .addMillis(seconds * 1000); + return DateUtil.correctDay(dateBuilder.getDate()); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + int beginIndex = sentence.indexOf('>'); + if (beginIndex != -1) { + sentence = sentence.substring(beginIndex + 1); + } + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + Boolean valid = null; + Integer event = null; + + if (parser.hasNext(3)) { + event = parser.nextInt(); + position.setTime(getTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0))); + } else if (parser.hasNext()) { + position.setTime(getTime(parser.nextInt(0))); + } + + if (parser.hasNext()) { + event = parser.nextInt(); + } + + if (parser.hasNext(6)) { + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + } + + if (parser.hasNext(4)) { + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_DEG)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_DEG)); + } + if (parser.hasNext(6)) { + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + } + + position.setSpeed(UnitsConverter.knotsFromMph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + + if (parser.hasNext(4)) { + valid = parser.nextInt() > 0; + int input = parser.nextHexInt(); + position.set(Position.KEY_IGNITION, BitUtil.check(input, 7)); + position.set(Position.KEY_INPUT, input); + event = parser.nextInt(); + position.set(Position.KEY_HDOP, parser.nextInt()); + } + + if (parser.hasNext(4)) { + position.set(Position.KEY_INPUT, parser.nextHexInt(0)); + position.set(Position.KEY_SATELLITES, parser.nextHexInt(0)); + position.set(Position.KEY_BATTERY, parser.nextInt(0)); + position.set(Position.KEY_ODOMETER, parser.nextLong(16, 0)); + } + + if (parser.hasNext(4)) { + valid = parser.nextInt() > 0; + position.set(Position.KEY_PDOP, parser.nextInt()); + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, parser.nextInt() * 0.01); + position.set(Position.PREFIX_TEMP + 2, parser.nextInt() * 0.01); + } + + position.setValid(valid == null || valid); + + if (event != null) { + position.set(Position.KEY_EVENT, event); + switch (event) { + case 22: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 23: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 24: + position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT); + break; + case 26: + case 28: + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + break; + default: + break; + } + } + + String[] attributes = null; + beginIndex = sentence.indexOf(';'); + if (beginIndex != -1) { + int endIndex = sentence.indexOf('<', beginIndex); + if (endIndex == -1) { + endIndex = sentence.length(); + } + attributes = sentence.substring(beginIndex, endIndex).split(";"); + } + + return decodeAttributes(channel, remoteAddress, position, attributes); + } + + private Position decodeAttributes( + Channel channel, SocketAddress remoteAddress, Position position, String[] attributes) { + + String uniqueId = null; + DeviceSession deviceSession = null; + String messageIndex = null; + + if (attributes != null) { + for (String attribute : attributes) { + int index = attribute.indexOf('='); + if (index != -1) { + String key = attribute.substring(0, index).toLowerCase(); + String value = attribute.substring(index + 1); + switch (key) { + case "id": + uniqueId = value; + deviceSession = getDeviceSession(channel, remoteAddress, value); + if (deviceSession != null) { + position.setDeviceId(deviceSession.getDeviceId()); + } + break; + case "io": + position.set(Position.KEY_IGNITION, BitUtil.check(value.charAt(0) - '0', 0)); + position.set(Position.KEY_CHARGE, BitUtil.check(value.charAt(0) - '0', 1)); + position.set(Position.KEY_OUTPUT, value.charAt(1) - '0'); + position.set(Position.KEY_INPUT, value.charAt(2) - '0'); + break; + case "ix": + position.set(Position.PREFIX_IO + 1, value); + break; + case "ad": + position.set(Position.PREFIX_ADC + 1, Integer.parseInt(value)); + break; + case "sv": + position.set(Position.KEY_SATELLITES, Integer.parseInt(value)); + break; + case "bl": + position.set(Position.KEY_BATTERY, Integer.parseInt(value) * 0.001); + break; + case "vo": + position.set(Position.KEY_ODOMETER, Long.parseLong(value)); + break; + default: + position.set(key, value); + break; + } + } else if (attribute.startsWith("#")) { + messageIndex = attribute; + } + } + } + + if (deviceSession != null) { + if (channel != null) { + if (messageIndex != null) { + String response = ">ACK;ID=" + uniqueId + ";" + messageIndex + ";*"; + response += String.format("%02X", Checksum.xor(response)) + "<"; + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } else { + channel.writeAndFlush(new NetworkMessage(uniqueId, remoteAddress)); + } + } + return position; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TechTltProtocol.java b/src/main/java/org/traccar/protocol/TechTltProtocol.java new file mode 100644 index 000000000..0cffb452d --- /dev/null +++ b/src/main/java/org/traccar/protocol/TechTltProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TechTltProtocol extends BaseProtocol { + + public TechTltProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new TechTltProtocolDecoder(TechTltProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TechTltProtocolDecoder.java b/src/main/java/org/traccar/protocol/TechTltProtocolDecoder.java new file mode 100644 index 000000000..17f5c80fa --- /dev/null +++ b/src/main/java/org/traccar/protocol/TechTltProtocolDecoder.java @@ -0,0 +1,132 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class TechTltProtocolDecoder extends BaseProtocolDecoder { + + public TechTltProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_STATUS = new PatternBuilder() + .number("(d+),") // id + .text("INFOGPRS,") + .number("V Bat=(d+.d),") // battery + .number("TEMP=(d+),") // temperature + .expression("[^,]*,") + .number("(d+)") // rssi + .compile(); + + private static final Pattern PATTERN_POSITION = new PatternBuilder() + .number("(d+)") // id + .text("*POS=Y,") + .number("(dd):(dd):(dd),") // time + .number("(dd)/(dd)/(dd),") // date + .number("(dd)(dd.d+)") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+)") // longitude + .expression("([EW]),") + .number("(d+.d+),") // speed + .number("(d+.d+),") // course + .number("(d+.d+),") // altitude + .number("(d+),") // satellites + .number("(d+),") // lac + .number("(d+)") // cid + .compile(); + + private Position decodeStatus(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_STATUS, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_DEVICE_TEMP, parser.nextInt()); + position.set(Position.KEY_RSSI, parser.nextInt()); + + return position; + } + + private Position decodeLocation(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_POSITION, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY)); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + position.setAltitude(parser.nextDouble()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + + position.setNetwork(new Network(CellTower.fromLacCid(parser.nextInt(), parser.nextInt()))); + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = ((String) msg).trim(); + if (sentence.contains("INFO")) { + return decodeStatus(channel, remoteAddress, sentence); + } else if (sentence.contains("POS")) { + return decodeLocation(channel, remoteAddress, sentence); + } else { + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/TekFrameDecoder.java b/src/main/java/org/traccar/protocol/TekFrameDecoder.java new file mode 100644 index 000000000..c56e1f8f2 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TekFrameDecoder.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.BitUtil; + +public class TekFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 17) { + return null; + } + + int length = 17 + buf.getUnsignedByte(16) + (BitUtil.from(buf.getUnsignedByte(15), 6) << 6); + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TekProtocol.java b/src/main/java/org/traccar/protocol/TekProtocol.java new file mode 100644 index 000000000..c1d78e6f5 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TekProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TekProtocol extends BaseProtocol { + + public TekProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new TekFrameDecoder()); + pipeline.addLast(new TekProtocolDecoder(TekProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TekProtocolDecoder.java b/src/main/java/org/traccar/protocol/TekProtocolDecoder.java new file mode 100644 index 000000000..33ff51d2d --- /dev/null +++ b/src/main/java/org/traccar/protocol/TekProtocolDecoder.java @@ -0,0 +1,139 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +public class TekProtocolDecoder extends BaseProtocolDecoder { + + public TekProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number(",d+,") + .number("(dd)(dd)(dd).d,") // time (hhmmss) + .number("(dd)(dd.d+)") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+)") // longitude + .expression("([EW]),") + .number("(d+.d+),") // hdop + .number("(d+.d+),") // altitude + .number("(d+),") // fix mode + .number("(d+.d+),") // course + .number("d+.d+,") // speed km + .number("(d+.d+),") // speed kn + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(d+),") // satellites + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // product type + buf.readUnsignedByte(); // hardware version + buf.readUnsignedByte(); // firmware version + buf.readUnsignedByte(); // contact reason + buf.readUnsignedByte(); // alarm / status + buf.readUnsignedByte(); // rssi + buf.readUnsignedByte(); // battery / status + + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(1); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + int type = BitUtil.to(buf.readUnsignedByte(), 6); + buf.readUnsignedByte(); // length + + if (type == 4 || type == 8) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int count = buf.readUnsignedShort(); + buf.readUnsignedByte(); // hours / tickets + buf.readUnsignedByte(); // error code + buf.readUnsignedByte(); // reserved + buf.readUnsignedByte(); // logger speed + buf.readUnsignedByte(); // login time + buf.readUnsignedByte(); // minutes + + for (int i = 0; i < count; i++) { + position.set("rssi" + (i + 1), buf.readUnsignedByte()); + position.set("temp" + (i + 1), buf.readUnsignedByte() - 30); + int data = buf.readUnsignedShort(); + position.set("src" + (i + 1), BitUtil.from(data, 10)); + position.set("ullage" + (i + 1), BitUtil.to(data, 10)); + } + + return position; + + } else if (type == 17) { + + String sentence = buf.toString(StandardCharsets.US_ASCII); + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + position.set(Position.KEY_HDOP, parser.nextDouble()); + + position.setAltitude(parser.nextDouble()); + position.setValid(parser.nextInt() > 0); + position.setCourse(parser.nextDouble()); + position.setSpeed(parser.nextDouble()); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TelemaxProtocol.java b/src/main/java/org/traccar/protocol/TelemaxProtocol.java new file mode 100644 index 000000000..838da9df1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TelemaxProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TelemaxProtocol extends BaseProtocol { + + public TelemaxProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new TelemaxProtocolDecoder(TelemaxProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TelemaxProtocolDecoder.java b/src/main/java/org/traccar/protocol/TelemaxProtocolDecoder.java new file mode 100644 index 000000000..9369ab101 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TelemaxProtocolDecoder.java @@ -0,0 +1,112 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class TelemaxProtocolDecoder extends BaseProtocolDecoder { + + public TelemaxProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private String readValue(String sentence, int[] index, int length) { + String value = sentence.substring(index[0], index[0] + length); + index[0] += length; + return value; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.startsWith("%")) { + int length = Integer.parseInt(sentence.substring(1, 3)); + getDeviceSession(channel, remoteAddress, sentence.substring(3, 3 + length)); + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + int[] index = {0}; + + if (!readValue(sentence, index, 1).equals("Y")) { + return null; + } + + readValue(sentence, index, 8); // command id + readValue(sentence, index, 6); // password + readValue(sentence, index, Integer.parseInt(readValue(sentence, index, 2), 16)); // unit id + readValue(sentence, index, 2); // frame count + + readValue(sentence, index, 2); // data format + + int interval = Integer.parseInt(readValue(sentence, index, 4), 16); + + readValue(sentence, index, 2); // info flags + readValue(sentence, index, 2); // version + + int count = Integer.parseInt(readValue(sentence, index, 2), 16); + + Date time = null; + List<Position> positions = new LinkedList<>(); + + for (int i = 0; i < count; i++) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int speed = Integer.parseInt(readValue(sentence, index, 2), 16); + + position.setValid(BitUtil.check(speed, 7)); + position.setSpeed(BitUtil.to(speed, 7)); + + position.setLongitude((Integer.parseInt(readValue(sentence, index, 6), 16) - 5400000) / 30000.0); + position.setLatitude((Integer.parseInt(readValue(sentence, index, 6), 16) - 5400000) / 30000.0); + + if (i == 0 | i == count - 1) { + time = new SimpleDateFormat("yyMMddHHmmss").parse(readValue(sentence, index, 12)); + position.set(Position.KEY_STATUS, readValue(sentence, index, 8)); + } else { + time = new Date(time.getTime() + interval * 1000); + } + + position.setTime(time); + + positions.add(position); + + } + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/TelicFrameDecoder.java b/src/main/java/org/traccar/protocol/TelicFrameDecoder.java new file mode 100644 index 000000000..d1fef1b5b --- /dev/null +++ b/src/main/java/org/traccar/protocol/TelicFrameDecoder.java @@ -0,0 +1,55 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseFrameDecoder; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; + +public class TelicFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 4) { + return null; + } + + long length = buf.getUnsignedIntLE(buf.readerIndex()); + + if (length < 1024) { + if (buf.readableBytes() >= length + 4) { + buf.readUnsignedIntLE(); + return buf.readRetainedSlice((int) length); + } + } else { + int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0); + if (endIndex >= 0) { + ByteBuf frame = buf.readRetainedSlice(endIndex - buf.readerIndex()); + buf.readByte(); + if (frame.readableBytes() > 0) { + return frame; + } + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TelicProtocol.java b/src/main/java/org/traccar/protocol/TelicProtocol.java new file mode 100644 index 000000000..991befa19 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TelicProtocol.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TelicProtocol extends BaseProtocol { + + public TelicProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new TelicFrameDecoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new TelicProtocolDecoder(TelicProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TelicProtocolDecoder.java b/src/main/java/org/traccar/protocol/TelicProtocolDecoder.java new file mode 100644 index 000000000..457687b2e --- /dev/null +++ b/src/main/java/org/traccar/protocol/TelicProtocolDecoder.java @@ -0,0 +1,131 @@ +/* + * Copyright 2014 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class TelicProtocolDecoder extends BaseProtocolDecoder { + + public TelicProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("dddd") + .number("(d{6}|d{15})") // device id + .number("(d{1,2}),") // type + .number("d{12},") // event time + .number("d+,") + .number("(dd)(dd)(dd)") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .groupBegin() + .number("(-?d{9}),") // longitude + .number("(-?d{8}),") // latitude + .or() + .number("(-?d+),") // longitude + .number("(-?d+),") // latitude + .groupEnd() + .number("(d),") // validity + .number("(d+),") // speed + .number("(d+),") // course + .number("(d+)?,") // satellites + .expression("(?:[^,]*,){7}") + .number("(d+),") // battery + .any() + .compile(); + + private String decodeAlarm(int eventId) { + + switch (eventId) { + case 1: + return Position.ALARM_POWER_ON; + case 2: + return Position.ALARM_SOS; + case 5: + return Position.ALARM_POWER_OFF; + case 7: + return Position.ALARM_GEOFENCE_ENTER; + case 8: + return Position.ALARM_GEOFENCE_EXIT; + case 22: + return Position.ALARM_LOW_BATTERY; + case 25: + return Position.ALARM_MOVEMENT; + default: + return null; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int event = parser.nextInt(); + position.set(Position.KEY_EVENT, event); + position.set(Position.KEY_ALARM, decodeAlarm(event)); + + if (event == 11) { + position.set(Position.KEY_IGNITION, true); + } else if (event == 12) { + position.set(Position.KEY_IGNITION, false); + } + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + if (parser.hasNext(2)) { + position.setLongitude(parser.nextDouble() / 1000000); + position.setLatitude(parser.nextDouble() / 1000000); + } + + if (parser.hasNext(2)) { + position.setLongitude(parser.nextDouble() / 10000); + position.setLatitude(parser.nextDouble() / 10000); + } + + position.setValid(parser.nextInt() != 1); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_BATTERY, parser.nextInt()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java new file mode 100644 index 000000000..c30fee6e3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TeltonikaFrameDecoder.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseFrameDecoder; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; + +public class TeltonikaFrameDecoder extends BaseFrameDecoder { + + private static final int MESSAGE_MINIMUM_LENGTH = 12; + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + while (buf.isReadable() && buf.getByte(buf.readerIndex()) == (byte) 0xff) { + buf.skipBytes(1); + } + + if (buf.readableBytes() < MESSAGE_MINIMUM_LENGTH) { + return null; + } + + int length = buf.getUnsignedShort(buf.readerIndex()); + if (length > 0) { + if (buf.readableBytes() >= (length + 2)) { + return buf.readRetainedSlice(length + 2); + } + } else { + int dataLength = buf.getInt(buf.readerIndex() + 4); + if (buf.readableBytes() >= (dataLength + 12)) { + return buf.readRetainedSlice(dataLength + 12); + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocol.java b/src/main/java/org/traccar/protocol/TeltonikaProtocol.java new file mode 100644 index 000000000..5817b86be --- /dev/null +++ b/src/main/java/org/traccar/protocol/TeltonikaProtocol.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class TeltonikaProtocol extends BaseProtocol { + + public TeltonikaProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new TeltonikaFrameDecoder()); + pipeline.addLast(new TeltonikaProtocolEncoder(TeltonikaProtocol.this)); + pipeline.addLast(new TeltonikaProtocolDecoder(TeltonikaProtocol.this, false)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new TeltonikaProtocolEncoder(TeltonikaProtocol.this)); + pipeline.addLast(new TeltonikaProtocolDecoder(TeltonikaProtocol.this, true)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java new file mode 100644 index 000000000..cc2868a20 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java @@ -0,0 +1,626 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { + + private static final int IMAGE_PACKET_MAX = 2048; + + private boolean connectionless; + private boolean extended; + private Map<Long, ByteBuf> photos = new HashMap<>(); + + public void setExtended(boolean extended) { + this.extended = extended; + } + + public TeltonikaProtocolDecoder(Protocol protocol, boolean connectionless) { + super(protocol); + this.connectionless = connectionless; + this.extended = Context.getConfig().getBoolean(getProtocolName() + ".extended"); + } + + private void parseIdentification(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + int length = buf.readUnsignedShort(); + String imei = buf.toString(buf.readerIndex(), length, StandardCharsets.US_ASCII); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + + if (channel != null) { + ByteBuf response = Unpooled.buffer(1); + if (deviceSession != null) { + response.writeByte(1); + } else { + response.writeByte(0); + } + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + public static final int CODEC_GH3000 = 0x07; + public static final int CODEC_8 = 0x08; + public static final int CODEC_8_EXT = 0x8E; + public static final int CODEC_12 = 0x0C; + public static final int CODEC_16 = 0x10; + + private void sendImageRequest(Channel channel, SocketAddress remoteAddress, long id, int offset, int size) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeInt(0); + response.writeShort(0); + response.writeShort(19); // length + response.writeByte(CODEC_12); + response.writeByte(1); // nod + response.writeByte(0x0D); // camera + response.writeInt(11); // payload length + response.writeByte(2); // command + response.writeInt((int) id); + response.writeInt(offset); + response.writeShort(size); + response.writeByte(1); // nod + response.writeShort(0); + response.writeShort(Checksum.crc16( + Checksum.CRC16_IBM, response.nioBuffer(8, response.readableBytes() - 10))); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + private void decodeSerial(Channel channel, SocketAddress remoteAddress, Position position, ByteBuf buf) { + + getLastLocation(position, null); + + int type = buf.readUnsignedByte(); + if (type == 0x0D) { + + buf.readInt(); // length + int subtype = buf.readUnsignedByte(); + if (subtype == 0x01) { + + long photoId = buf.readUnsignedInt(); + ByteBuf photo = Unpooled.buffer(buf.readInt()); + photos.put(photoId, photo); + sendImageRequest( + channel, remoteAddress, photoId, + 0, Math.min(IMAGE_PACKET_MAX, photo.capacity())); + + } else if (subtype == 0x02) { + + long photoId = buf.readUnsignedInt(); + buf.readInt(); // offset + ByteBuf photo = photos.get(photoId); + photo.writeBytes(buf, buf.readUnsignedShort()); + if (photo.writableBytes() > 0) { + sendImageRequest( + channel, remoteAddress, photoId, + photo.writerIndex(), Math.min(IMAGE_PACKET_MAX, photo.writableBytes())); + } else { + String uniqueId = Context.getIdentityManager().getById(position.getDeviceId()).getUniqueId(); + photos.remove(photoId); + try { + position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(uniqueId, photo, "jpg")); + } finally { + photo.release(); + } + } + + } + + } else { + + position.set(Position.KEY_TYPE, type); + + int length = buf.readInt(); + boolean readable = true; + for (int i = 0; i < length; i++) { + byte b = buf.getByte(buf.readerIndex() + i); + if (b < 32 && b != '\r' && b != '\n') { + readable = false; + break; + } + } + + if (readable) { + String data = buf.readSlice(length).toString(StandardCharsets.US_ASCII).trim(); + if (data.startsWith("UUUUww") && data.endsWith("SSS")) { + String[] values = data.substring(6, data.length() - 4).split(";"); + for (int i = 0; i < 8; i++) { + position.set("axle" + (i + 1), Double.parseDouble(values[i])); + } + position.set("loadTruck", Double.parseDouble(values[8])); + position.set("loadTrailer", Double.parseDouble(values[9])); + position.set("totalTruck", Double.parseDouble(values[10])); + position.set("totalTrailer", Double.parseDouble(values[11])); + } else { + position.set(Position.KEY_RESULT, data); + } + } else { + position.set(Position.KEY_RESULT, ByteBufUtil.hexDump(buf.readSlice(length))); + } + } + } + + private long readValue(ByteBuf buf, int length, boolean signed) { + switch (length) { + case 1: + return signed ? buf.readByte() : buf.readUnsignedByte(); + case 2: + return signed ? buf.readShort() : buf.readUnsignedShort(); + case 4: + return signed ? buf.readInt() : buf.readUnsignedInt(); + default: + return buf.readLong(); + } + } + + private void decodeOtherParameter(Position position, int id, ByteBuf buf, int length) { + switch (id) { + case 1: + case 2: + case 3: + case 4: + position.set("di" + id, readValue(buf, length, false)); + break; + case 9: + position.set(Position.PREFIX_ADC + 1, readValue(buf, length, false)); + break; + case 10: + position.set(Position.PREFIX_ADC + 2, readValue(buf, length, false)); + break; + case 17: + position.set("axisX", readValue(buf, length, true)); + break; + case 18: + position.set("axisY", readValue(buf, length, true)); + break; + case 19: + position.set("axisZ", readValue(buf, length, true)); + break; + case 21: + position.set(Position.KEY_RSSI, readValue(buf, length, false)); + break; + case 25: + case 26: + case 27: + case 28: + position.set(Position.PREFIX_TEMP + (id - 24), readValue(buf, length, true) * 0.1); + break; + case 66: + position.set(Position.KEY_POWER, readValue(buf, length, false) * 0.001); + break; + case 67: + position.set(Position.KEY_BATTERY, readValue(buf, length, false) * 0.001); + break; + case 69: + position.set("gpsStatus", readValue(buf, length, false)); + break; + case 72: + case 73: + case 74: + position.set(Position.PREFIX_TEMP + (id - 71), readValue(buf, length, true) * 0.1); + break; + case 78: + long driverUniqueId = readValue(buf, length, false); + if (driverUniqueId != 0) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, String.format("%016X", driverUniqueId)); + } + break; + case 80: + position.set("workMode", readValue(buf, length, false)); + break; + case 129: + case 130: + case 131: + case 132: + case 133: + case 134: + String driver = id == 129 || id == 132 ? "" : position.getString("driver1"); + position.set("driver" + (id >= 132 ? 2 : 1), + driver + buf.readSlice(length).toString(StandardCharsets.US_ASCII).trim()); + break; + case 179: + position.set(Position.PREFIX_OUT + 1, readValue(buf, length, false) == 1); + break; + case 180: + position.set(Position.PREFIX_OUT + 2, readValue(buf, length, false) == 1); + break; + case 181: + position.set(Position.KEY_PDOP, readValue(buf, length, false) * 0.1); + break; + case 182: + position.set(Position.KEY_HDOP, readValue(buf, length, false) * 0.1); + break; + case 236: + if (readValue(buf, length, false) == 1) { + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + } + break; + case 237: + position.set(Position.KEY_MOTION, readValue(buf, length, false) == 0); + break; + case 238: + switch ((int) readValue(buf, length, false)) { + case 1: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 2: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 3: + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + break; + default: + break; + } + break; + case 239: + position.set(Position.KEY_IGNITION, readValue(buf, length, false) == 1); + break; + case 240: + position.set(Position.KEY_MOTION, readValue(buf, length, false) == 1); + break; + case 241: + position.set(Position.KEY_OPERATOR, readValue(buf, length, false)); + break; + default: + position.set(Position.PREFIX_IO + id, readValue(buf, length, false)); + break; + } + } + + private void decodeGh3000Parameter(Position position, int id, ByteBuf buf, int length) { + switch (id) { + case 1: + position.set(Position.KEY_BATTERY_LEVEL, readValue(buf, length, false)); + break; + case 2: + position.set("usbConnected", readValue(buf, length, false) == 1); + break; + case 5: + position.set("uptime", readValue(buf, length, false)); + break; + case 20: + position.set(Position.KEY_HDOP, readValue(buf, length, false) * 0.1); + break; + case 21: + position.set(Position.KEY_VDOP, readValue(buf, length, false) * 0.1); + break; + case 22: + position.set(Position.KEY_PDOP, readValue(buf, length, false) * 0.1); + break; + case 67: + position.set(Position.KEY_BATTERY, readValue(buf, length, false) * 0.001); + break; + case 221: + position.set("button", readValue(buf, length, false)); + break; + case 222: + if (readValue(buf, length, false) == 1) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + break; + case 240: + position.set(Position.KEY_MOTION, readValue(buf, length, false) == 1); + break; + case 244: + position.set(Position.KEY_ROAMING, readValue(buf, length, false) == 1); + break; + default: + position.set(Position.PREFIX_IO + id, readValue(buf, length, false)); + break; + } + } + + private void decodeParameter(Position position, int id, ByteBuf buf, int length, int codec) { + if (codec == CODEC_GH3000) { + decodeGh3000Parameter(position, id, buf, length); + } else { + decodeOtherParameter(position, id, buf, length); + } + } + + private void decodeNetwork(Position position) { + long cid = position.getLong(Position.PREFIX_IO + 205); + int lac = position.getInteger(Position.PREFIX_IO + 206); + if (cid != 0 && lac != 0) { + CellTower cellTower = CellTower.fromLacCid(lac, cid); + long operator = position.getInteger(Position.KEY_OPERATOR); + if (operator != 0) { + cellTower.setOperator(operator); + } + position.setNetwork(new Network(cellTower)); + } + } + + private int readExtByte(ByteBuf buf, int codec, int... codecs) { + boolean ext = false; + for (int c : codecs) { + if (codec == c) { + ext = true; + break; + } + } + if (ext) { + return buf.readUnsignedShort(); + } else { + return buf.readUnsignedByte(); + } + } + + private void decodeLocation(Position position, ByteBuf buf, int codec) { + + int globalMask = 0x0f; + + if (codec == CODEC_GH3000) { + + long time = buf.readUnsignedInt() & 0x3fffffff; + time += 1167609600; // 2007-01-01 00:00:00 + + globalMask = buf.readUnsignedByte(); + if (BitUtil.check(globalMask, 0)) { + + position.setTime(new Date(time * 1000)); + + int locationMask = buf.readUnsignedByte(); + + if (BitUtil.check(locationMask, 0)) { + position.setLatitude(buf.readFloat()); + position.setLongitude(buf.readFloat()); + } + + if (BitUtil.check(locationMask, 1)) { + position.setAltitude(buf.readUnsignedShort()); + } + + if (BitUtil.check(locationMask, 2)) { + position.setCourse(buf.readUnsignedByte() * 360.0 / 256); + } + + if (BitUtil.check(locationMask, 3)) { + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + } + + if (BitUtil.check(locationMask, 4)) { + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + } + + if (BitUtil.check(locationMask, 5)) { + CellTower cellTower = CellTower.fromLacCid(buf.readUnsignedShort(), buf.readUnsignedShort()); + + if (BitUtil.check(locationMask, 6)) { + cellTower.setSignalStrength((int) buf.readUnsignedByte()); + } + + if (BitUtil.check(locationMask, 7)) { + cellTower.setOperator(buf.readUnsignedInt()); + } + + position.setNetwork(new Network(cellTower)); + + } else { + if (BitUtil.check(locationMask, 6)) { + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + } + if (BitUtil.check(locationMask, 7)) { + position.set(Position.KEY_OPERATOR, buf.readUnsignedInt()); + } + } + + } else { + + getLastLocation(position, new Date(time * 1000)); + + } + + } else { + + position.setTime(new Date(buf.readLong())); + + position.set("priority", buf.readUnsignedByte()); + + position.setLongitude(buf.readInt() / 10000000.0); + position.setLatitude(buf.readInt() / 10000000.0); + position.setAltitude(buf.readShort()); + position.setCourse(buf.readUnsignedShort()); + + int satellites = buf.readUnsignedByte(); + position.set(Position.KEY_SATELLITES, satellites); + + position.setValid(satellites != 0); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort())); + + position.set(Position.KEY_EVENT, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16)); + if (codec == CODEC_16) { + buf.readUnsignedByte(); // generation type + } + + readExtByte(buf, codec, CODEC_8_EXT); // total IO data records + + } + + // Read 1 byte data + if (BitUtil.check(globalMask, 1)) { + int cnt = readExtByte(buf, codec, CODEC_8_EXT); + for (int j = 0; j < cnt; j++) { + decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 1, codec); + } + } + + // Read 2 byte data + if (BitUtil.check(globalMask, 2)) { + int cnt = readExtByte(buf, codec, CODEC_8_EXT); + for (int j = 0; j < cnt; j++) { + decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 2, codec); + } + } + + // Read 4 byte data + if (BitUtil.check(globalMask, 3)) { + int cnt = readExtByte(buf, codec, CODEC_8_EXT); + for (int j = 0; j < cnt; j++) { + decodeParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 4, codec); + } + } + + // Read 8 byte data + if (codec == CODEC_8 || codec == CODEC_8_EXT || codec == CODEC_16) { + int cnt = readExtByte(buf, codec, CODEC_8_EXT); + for (int j = 0; j < cnt; j++) { + decodeOtherParameter(position, readExtByte(buf, codec, CODEC_8_EXT, CODEC_16), buf, 8); + } + } + + // Read 16 byte data + if (extended) { + int cnt = readExtByte(buf, codec, CODEC_8_EXT); + for (int j = 0; j < cnt; j++) { + int id = readExtByte(buf, codec, CODEC_8_EXT, CODEC_16); + position.set(Position.PREFIX_IO + id, ByteBufUtil.hexDump(buf.readSlice(16))); + } + } + + // Read X byte data + if (codec == CODEC_8_EXT) { + int cnt = buf.readUnsignedShort(); + for (int j = 0; j < cnt; j++) { + int id = buf.readUnsignedShort(); + int length = buf.readUnsignedShort(); + if (id == 256) { + position.set(Position.KEY_VIN, buf.readSlice(length).toString(StandardCharsets.US_ASCII)); + } else { + position.set(Position.PREFIX_IO + id, ByteBufUtil.hexDump(buf.readSlice(length))); + } + } + } + + decodeNetwork(position); + + } + + private List<Position> parseData( + Channel channel, SocketAddress remoteAddress, ByteBuf buf, int locationPacketId, String... imei) { + List<Position> positions = new LinkedList<>(); + + if (!connectionless) { + buf.readUnsignedInt(); // data length + } + + int codec = buf.readUnsignedByte(); + int count = buf.readUnsignedByte(); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + + if (deviceSession == null) { + return null; + } + + for (int i = 0; i < count; i++) { + Position position = new Position(getProtocolName()); + + position.setDeviceId(deviceSession.getDeviceId()); + position.setValid(true); + + if (codec == CODEC_12) { + decodeSerial(channel, remoteAddress, position, buf); + } else { + decodeLocation(position, buf, codec); + } + + if (!position.getOutdated() || !position.getAttributes().isEmpty()) { + positions.add(position); + } + } + + if (channel != null) { + if (connectionless) { + ByteBuf response = Unpooled.buffer(); + response.writeShort(5); + response.writeShort(0); + response.writeByte(0x01); + response.writeByte(locationPacketId); + response.writeByte(count); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } else { + ByteBuf response = Unpooled.buffer(); + response.writeInt(count); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + return positions.isEmpty() ? null : positions; + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (connectionless) { + return decodeUdp(channel, remoteAddress, buf); + } else { + return decodeTcp(channel, remoteAddress, buf); + } + } + + private Object decodeTcp(Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws Exception { + + if (buf.getUnsignedShort(0) > 0) { + parseIdentification(channel, remoteAddress, buf); + } else { + buf.skipBytes(4); + return parseData(channel, remoteAddress, buf, 0); + } + + return null; + } + + private Object decodeUdp(Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws Exception { + + buf.readUnsignedShort(); // length + buf.readUnsignedShort(); // packet id + buf.readUnsignedByte(); // packet type + int locationPacketId = buf.readUnsignedByte(); + String imei = buf.readSlice(buf.readUnsignedShort()).toString(StandardCharsets.US_ASCII); + + return parseData(channel, remoteAddress, buf, locationPacketId, imei); + + } + +} diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolEncoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolEncoder.java new file mode 100644 index 000000000..8a614618a --- /dev/null +++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolEncoder.java @@ -0,0 +1,67 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocolEncoder; +import org.traccar.helper.Checksum; +import org.traccar.helper.DataConverter; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import java.nio.charset.StandardCharsets; + +public class TeltonikaProtocolEncoder extends BaseProtocolEncoder { + + public TeltonikaProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private ByteBuf encodeContent(byte[] content) { + + ByteBuf buf = Unpooled.buffer(); + + buf.writeInt(0); + buf.writeInt(content.length + 8); + buf.writeByte(TeltonikaProtocolDecoder.CODEC_12); + buf.writeByte(1); // quantity + buf.writeByte(5); // type + buf.writeInt(content.length); + buf.writeBytes(content); + buf.writeByte(1); // quantity + buf.writeInt(Checksum.crc16(Checksum.CRC16_IBM, buf.nioBuffer(8, buf.writerIndex() - 8))); + + return buf; + } + + @Override + protected Object encodeCommand(Command command) { + + if (command.getType().equals(Command.TYPE_CUSTOM)) { + String data = command.getString(Command.KEY_DATA); + if (data.matches("(\\p{XDigit}{2})+")) { + return encodeContent(DataConverter.parseHex(data)); + } else { + return encodeContent((data + "\r\n").getBytes(StandardCharsets.US_ASCII)); + } + } else { + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/ThinkRaceProtocol.java b/src/main/java/org/traccar/protocol/ThinkRaceProtocol.java new file mode 100644 index 000000000..ca1237cef --- /dev/null +++ b/src/main/java/org/traccar/protocol/ThinkRaceProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class ThinkRaceProtocol extends BaseProtocol { + + public ThinkRaceProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 2 + 12 + 1 + 1, 2, 2, 0)); + pipeline.addLast(new ThinkRaceProtocolDecoder(ThinkRaceProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/ThinkRaceProtocolDecoder.java b/src/main/java/org/traccar/protocol/ThinkRaceProtocolDecoder.java new file mode 100644 index 000000000..0928b25e0 --- /dev/null +++ b/src/main/java/org/traccar/protocol/ThinkRaceProtocolDecoder.java @@ -0,0 +1,116 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class ThinkRaceProtocolDecoder extends BaseProtocolDecoder { + + public ThinkRaceProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN = 0x80; + public static final int MSG_GPS = 0x90; + + private static double convertCoordinate(long raw, boolean negative) { + long degrees = raw / 1000000; + double minutes = (raw % 1000000) * 0.0001; + double result = degrees + minutes / 60; + if (negative) { + result = -result; + } + return result; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + ByteBuf id = buf.readSlice(12); + buf.readUnsignedByte(); // separator + int type = buf.readUnsignedByte(); + buf.readUnsignedShort(); // length + + if (type == MSG_LOGIN) { + + int command = buf.readUnsignedByte(); // 0x00 - heartbeat + + if (command == 0x01) { + String imei = buf.toString(buf.readerIndex(), 15, StandardCharsets.US_ASCII); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession != null && channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(0x48); response.writeByte(0x52); // header + response.writeBytes(id); + response.writeByte(0x2c); // separator + response.writeByte(type); + response.writeShort(0x0002); // length + response.writeShort(0x8000); + response.writeShort(0x0000); // checksum + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + } else if (type == MSG_GPS) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + + int flags = buf.readUnsignedByte(); + + position.setValid(true); + position.setLatitude(convertCoordinate(buf.readUnsignedInt(), !BitUtil.check(flags, 0))); + position.setLongitude(convertCoordinate(buf.readUnsignedInt(), !BitUtil.check(flags, 1))); + + position.setSpeed(buf.readUnsignedByte()); + position.setCourse(buf.readUnsignedByte()); + + position.setNetwork(new Network( + CellTower.fromLacCid(buf.readUnsignedShort(), buf.readUnsignedShort()))); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Tk102Protocol.java b/src/main/java/org/traccar/protocol/Tk102Protocol.java new file mode 100644 index 000000000..9f2463cd6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tk102Protocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Tk102Protocol extends BaseProtocol { + + public Tk102Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 1 + 1 + 10, 1, 1, 0)); + pipeline.addLast(new Tk102ProtocolDecoder(Tk102Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Tk102ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tk102ProtocolDecoder.java new file mode 100644 index 000000000..da0c6928b --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tk102ProtocolDecoder.java @@ -0,0 +1,144 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.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 Tk102ProtocolDecoder extends BaseProtocolDecoder { + + public Tk102ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN_REQUEST = 0x80; + public static final int MSG_LOGIN_REQUEST_2 = 0x21; + public static final int MSG_LOGIN_RESPONSE = 0x00; + public static final int MSG_HEARTBEAT_REQUEST = 0xF0; + public static final int MSG_HEARTBEAT_RESPONSE = 0xFF; + public static final int MSG_REPORT_ONCE = 0x90; + public static final int MSG_REPORT_INTERVAL = 0x93; + + public static final int MODE_GPRS = 0x30; + public static final int MODE_GPRS_SMS = 0x33; + + private static final Pattern PATTERN = new PatternBuilder() + .text("(") + .expression("[A-Z]+") + .number("(dd)(dd)(dd)") // time (hhmmss) + .expression("([AV])") // validity + .number("(dd)(dd.dddd)([NS])") // latitude + .number("(ddd)(dd.dddd)([EW])") // longitude + .number("(ddd.ddd)") // speed + .number("(dd)(dd)(dd)") // date (ddmmyy) + .any() + .text(")") + .compile(); + + private void sendResponse(Channel channel, int type, ByteBuf dataSequence, ByteBuf content) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte('['); + response.writeByte(type); + response.writeBytes(dataSequence); + response.writeByte(content.readableBytes()); + response.writeBytes(content); + content.release(); + response.writeByte(']'); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(1); // header + int type = buf.readUnsignedByte(); + ByteBuf dataSequence = buf.readSlice(10); + int length = buf.readUnsignedByte(); + + if (type == MSG_LOGIN_REQUEST || type == MSG_LOGIN_REQUEST_2) { + + ByteBuf data = buf.readSlice(length); + + String id; + if (type == MSG_LOGIN_REQUEST) { + id = data.toString(StandardCharsets.US_ASCII); + } else { + id = data.copy(1, 15).toString(StandardCharsets.US_ASCII); + } + + if (getDeviceSession(channel, remoteAddress, id) != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(MODE_GPRS); + response.writeBytes(data); + sendResponse(channel, MSG_LOGIN_RESPONSE, dataSequence, response); + } + + } else if (type == MSG_HEARTBEAT_REQUEST) { + + sendResponse(channel, MSG_HEARTBEAT_RESPONSE, dataSequence, buf.readRetainedSlice(length)); + + } else { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Parser parser = new Parser(PATTERN, buf.readSlice(length).toString(StandardCharsets.US_ASCII)); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Tk103FrameDecoder.java b/src/main/java/org/traccar/protocol/Tk103FrameDecoder.java new file mode 100644 index 000000000..b61a42563 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tk103FrameDecoder.java @@ -0,0 +1,75 @@ +/* + * Copyright 2017 Valerii Vyshniak (val@val.one) + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class Tk103FrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 2) { + return null; + } + + int frameStartIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '('); + if (frameStartIndex == -1) { + buf.clear(); + return null; + } + + int frameEndIndex, freeTextSymbolCounter; + for (frameEndIndex = frameStartIndex, freeTextSymbolCounter = 0;; frameEndIndex++) { + int freeTextIndex = frameEndIndex; + frameEndIndex = buf.indexOf(frameEndIndex, buf.writerIndex(), (byte) ')'); + if (frameEndIndex == -1) { + break; + } + for (;; freeTextIndex++, freeTextSymbolCounter++) { + freeTextIndex = buf.indexOf(freeTextIndex, frameEndIndex, (byte) '$'); + if (freeTextIndex == -1 || freeTextIndex >= frameEndIndex) { + break; + } + } + if (freeTextSymbolCounter % 2 == 0) { + break; + } + } + + if (frameEndIndex == -1) { + while (buf.readableBytes() > 1024) { + int discardUntilIndex = buf.indexOf(buf.readerIndex() + 1, buf.writerIndex(), (byte) '('); + if (discardUntilIndex == -1) { + buf.clear(); + } else { + buf.readerIndex(discardUntilIndex); + } + } + return null; + } + + buf.readerIndex(frameStartIndex); + + return buf.readRetainedSlice(frameEndIndex + 1 - frameStartIndex); + } + +} diff --git a/src/main/java/org/traccar/protocol/Tk103Protocol.java b/src/main/java/org/traccar/protocol/Tk103Protocol.java new file mode 100644 index 000000000..ff0bedfb7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tk103Protocol.java @@ -0,0 +1,69 @@ +/* + * Copyright 2017 Christoph Krey (c@ckrey.de) + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class Tk103Protocol extends BaseProtocol { + + public Tk103Protocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_GET_DEVICE_STATUS, + Command.TYPE_IDENTIFICATION, + Command.TYPE_MODE_DEEP_SLEEP, + Command.TYPE_MODE_POWER_SAVING, + Command.TYPE_ALARM_SOS, + Command.TYPE_SET_CONNECTION, + Command.TYPE_SOS_NUMBER, + Command.TYPE_POSITION_SINGLE, + Command.TYPE_POSITION_PERIODIC, + Command.TYPE_POSITION_STOP, + Command.TYPE_GET_VERSION, + Command.TYPE_POWER_OFF, + Command.TYPE_REBOOT_DEVICE, + Command.TYPE_SET_ODOMETER, + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME, + Command.TYPE_OUTPUT_CONTROL); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new Tk103FrameDecoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new Tk103ProtocolEncoder(Tk103Protocol.this)); + pipeline.addLast(new Tk103ProtocolDecoder(Tk103Protocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new Tk103ProtocolEncoder(Tk103Protocol.this)); + pipeline.addLast(new Tk103ProtocolDecoder(Tk103Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java new file mode 100644 index 000000000..feedb2c5e --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tk103ProtocolDecoder.java @@ -0,0 +1,465 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class Tk103ProtocolDecoder extends BaseProtocolDecoder { + + private boolean decodeLow; + + public Tk103ProtocolDecoder(Protocol protocol) { + super(protocol); + decodeLow = Context.getConfig().getBoolean(getProtocolName() + ".decodeLow"); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("(").optional() + .groupBegin() + .expression("(.{12})") // device id + .or() + .expression("(.+),") // device id + .groupEnd() + .expression("(.{4}),?") // command + .number("(d*)") + .number("(dd)(dd)(dd),?") // date (mmddyy if comma-delimited, otherwise yyddmm) + .expression("([AV]),?") // validity + .number("(d+)(dd.d+)") // latitude + .expression("([NS]),?") + .number("(d+)(dd.d+)") // longitude + .expression("([EW]),?") + .number("(d+.d)(?:d*,)?") // speed + .number("(dd)(dd)(dd),?") // time (hhmmss) + .groupBegin() + .number("(?:([d.]{6})|(dd)),?") // course + .number("([01])") // charge + .number("([01])") // ignition + .number("(x)") // io + .number("(x)") // io + .number("(x)") // io + .number("(xxx)") // fuel + .number("L(x+)") // odometer + .or() + .number("(d+.d+)") // course + .groupEnd() + .any() + .number("([+-]ddd.d)?") // temperature + .text(")").optional() + .compile(); + + private static final Pattern PATTERN_BATTERY = new PatternBuilder() + .text("(").optional() + .number("(d+),") // device id + .text("ZC20,") + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d+),") // battery level + .number("(d+),") // battery voltage + .number("(d+),") // power voltage + .number("d+") // installed + .any() + .compile(); + + private static final Pattern PATTERN_NETWORK = new PatternBuilder() + .text("(").optional() + .number("(d{12})") // device id + .text("BZ00,") + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(x+),") // lac + .number("(x+),") // cid + .any() + .compile(); + + private static final Pattern PATTERN_LBSWIFI = new PatternBuilder() + .text("(").optional() + .number("(d+),") // device id + .expression("(.{4}),") // command + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(d+),") // lac + .number("(d+),") // cid + .number("(d+),") // number of wifi macs + .number("((?:(?:xx:){5}(?:xx)\\*[-+]?d+\\*d+,)*)") + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd)") // time (hhmmss) + .any() + .compile(); + + private static final Pattern PATTERN_COMMAND_RESULT = new PatternBuilder() + .text("(").optional() + .number("(d+),") // device id + .expression(".{4},") // command + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("\\$([\\s\\S]*?)(?:\\$|$)") // message + .any() + .compile(); + + private String decodeAlarm(int value) { + switch (value) { + case 1: + return Position.ALARM_ACCIDENT; + case 2: + return Position.ALARM_SOS; + case 3: + return Position.ALARM_VIBRATION; + case 4: + return Position.ALARM_LOW_SPEED; + case 5: + return Position.ALARM_OVERSPEED; + case 6: + return Position.ALARM_GEOFENCE_EXIT; + default: + return null; + } + } + + private void decodeType(Position position, String type, String data) { + switch (type) { + case "BO01": + position.set(Position.KEY_ALARM, decodeAlarm(data.charAt(0) - '0')); + break; + case "ZC11": + case "DW31": + case "DW51": + position.set(Position.KEY_ALARM, Position.ALARM_MOVEMENT); + break; + case "ZC12": + case "DW32": + case "DW52": + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + case "ZC13": + case "DW33": + case "DW53": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + break; + case "ZC15": + case "DW35": + case "DW55": + position.set(Position.KEY_IGNITION, true); + break; + case "ZC16": + case "DW36": + case "DW56": + position.set(Position.KEY_IGNITION, false); + break; + case "ZC29": + case "DW42": + case "DW62": + position.set(Position.KEY_IGNITION, true); + break; + case "ZC17": + case "DW37": + case "DW57": + position.set(Position.KEY_ALARM, Position.ALARM_REMOVING); + break; + case "ZC25": + case "DW3E": + case "DW5E": + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + break; + case "ZC26": + case "DW3F": + case "DW5F": + position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING); + break; + case "ZC27": + case "DW40": + case "DW60": + position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); + break; + default: + break; + } + } + + private Integer decodeBattery(int value) { + switch (value) { + case 6: + return 100; + case 5: + return 80; + case 4: + return 50; + case 3: + return 20; + case 2: + return 10; + default: + return null; + } + } + + private Position decodeBattery(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_BATTERY, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + int batterylevel = parser.nextInt(0); + if (batterylevel != 255) { + position.set(Position.KEY_BATTERY_LEVEL, decodeBattery(batterylevel)); + } + + int battery = parser.nextInt(0); + if (battery != 65535) { + position.set(Position.KEY_BATTERY, battery * 0.01); + } + + int power = parser.nextInt(0); + if (power != 65535) { + position.set(Position.KEY_POWER, power * 0.1); + } + + return position; + } + + private Position decodeNetwork(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_NETWORK, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.setNetwork(new Network(CellTower.from( + parser.nextInt(0), parser.nextInt(0), parser.nextHexInt(0), parser.nextHexInt(0)))); + + return position; + } + + private Position decodeLbsWifi(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_LBSWIFI, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + decodeType(position, parser.next(), "0"); + + getLastLocation(position, null); + + Network network = new Network(); + + network.addCellTower(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextInt())); + + int wifiCount = parser.nextInt(); + if (parser.hasNext()) { + String[] wifimacs = parser.next().split(","); + if (wifimacs.length == wifiCount) { + for (int i = 0; i < wifiCount; i++) { + String[] wifiinfo = wifimacs[i].split("\\*"); + network.addWifiAccessPoint(WifiAccessPoint.from( + wifiinfo[0], Integer.parseInt(wifiinfo[1]), Integer.parseInt(wifiinfo[2]))); + } + } + } + + if (network.getCellTowers() != null || network.getWifiAccessPoints() != null) { + position.setNetwork(network); + } + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + return position; + } + + private Position decodeCommandResult(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_COMMAND_RESULT, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.set(Position.KEY_RESULT, parser.next()); + + return position; + + } + +@Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (channel != null) { + String id = sentence.substring(1, 13); + String type = sentence.substring(13, 17); + if (type.equals("BP00")) { + channel.writeAndFlush(new NetworkMessage("(" + id + "AP01HSO)", remoteAddress)); + return null; + } else if (type.equals("BP05")) { + channel.writeAndFlush(new NetworkMessage("(" + id + "AP05)", remoteAddress)); + } + } + + if (sentence.contains("ZC20")) { + return decodeBattery(channel, remoteAddress, sentence); + } else if (sentence.contains("BZ00")) { + return decodeNetwork(channel, remoteAddress, sentence); + } else if (sentence.contains("ZC03")) { + return decodeCommandResult(channel, remoteAddress, sentence); + } else if (sentence.contains("DW5")) { + return decodeLbsWifi(channel, remoteAddress, sentence); + } + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + String id = null; + boolean alternative = false; + if (parser.hasNext()) { + id = parser.next(); + } + if (parser.hasNext()) { + id = parser.next(); + alternative = true; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + decodeType(position, parser.next(), parser.next()); + + DateBuilder dateBuilder = new DateBuilder(); + if (alternative) { + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + } else { + dateBuilder.setDate(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + } + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + position.setSpeed(convertSpeed(parser.nextDouble(0), "kmh")); + + dateBuilder.setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + if (parser.hasNext()) { + position.setCourse(parser.nextDouble()); + } + if (parser.hasNext()) { + position.setCourse(parser.nextDouble()); + } + + if (parser.hasNext(7)) { + position.set(Position.KEY_CHARGE, parser.nextInt() == 0); + position.set(Position.KEY_IGNITION, parser.nextInt() == 1); + + int mask1 = parser.nextHexInt(); + position.set(Position.PREFIX_IN + 2, BitUtil.check(mask1, 0) ? 1 : 0); + position.set("panic", BitUtil.check(mask1, 1) ? 1 : 0); + position.set(Position.PREFIX_OUT + 2, BitUtil.check(mask1, 2) ? 1 : 0); + if (decodeLow || BitUtil.check(mask1, 3)) { + position.set(Position.KEY_BLOCKED, BitUtil.check(mask1, 3) ? 1 : 0); + } + + int mask2 = parser.nextHexInt(); + for (int i = 0; i < 3; i++) { + if (decodeLow || BitUtil.check(mask2, i)) { + position.set("hs" + (3 - i), BitUtil.check(mask2, i) ? 1 : 0); + } + } + if (decodeLow || BitUtil.check(mask2, 3)) { + position.set(Position.KEY_DOOR, BitUtil.check(mask2, 3) ? 1 : 0); + } + + int mask3 = parser.nextHexInt(); + for (int i = 1; i <= 3; i++) { + if (decodeLow || BitUtil.check(mask3, i)) { + position.set("ls" + (3 - i + 1), BitUtil.check(mask3, i) ? 1 : 0); + } + } + + position.set(Position.KEY_FUEL_LEVEL, parser.nextHexInt()); + position.set(Position.KEY_ODOMETER, parser.nextLong(16, 0)); + } + + if (parser.hasNext()) { + position.setCourse(parser.nextDouble()); + } + + if (parser.hasNext()) { + position.set(Position.PREFIX_TEMP + 1, parser.nextDouble(0)); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/Tk103ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Tk103ProtocolEncoder.java new file mode 100644 index 000000000..5d7e63920 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tk103ProtocolEncoder.java @@ -0,0 +1,113 @@ +/* + * Copyright 2017 Christoph Krey (c@ckrey.de) + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.Context; +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class Tk103ProtocolEncoder extends StringProtocolEncoder { + + private final boolean forceAlternative; + + public Tk103ProtocolEncoder(Protocol protocol) { + super(protocol); + this.forceAlternative = false; + } + + public Tk103ProtocolEncoder(Protocol protocol, boolean forceAlternative) { + super(protocol); + this.forceAlternative = forceAlternative; + } + + private String formatAlt(Command command, String format, String... keys) { + return formatCommand(command, "[begin]sms2," + format + ",[end]", keys); + } + + @Override + protected Object encodeCommand(Command command) { + + boolean alternative = forceAlternative || Context.getIdentityManager().lookupAttributeBoolean( + command.getDeviceId(), getProtocolName() + ".alternative", false, false, true); + + initDevicePassword(command, "123456"); + + if (alternative) { + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return formatAlt(command, "%s", Command.KEY_DATA); + case Command.TYPE_GET_VERSION: + return formatAlt(command, "*about*"); + case Command.TYPE_POWER_OFF: + return formatAlt(command, "*turnoff*"); + case Command.TYPE_REBOOT_DEVICE: + return formatAlt(command, "88888888"); + case Command.TYPE_POSITION_SINGLE: + return formatAlt(command, "*getposl*"); + case Command.TYPE_POSITION_PERIODIC: + return formatAlt(command, "*routetrack*99*"); + case Command.TYPE_POSITION_STOP: + return formatAlt(command, "*routetrackoff*"); + case Command.TYPE_GET_DEVICE_STATUS: + return formatAlt(command, "*status*"); + case Command.TYPE_IDENTIFICATION: + return formatAlt(command, "999999"); + case Command.TYPE_MODE_DEEP_SLEEP: + return formatAlt(command, command.getBoolean(Command.KEY_ENABLE) ? "*sleep*2*" : "*sleepoff*"); + case Command.TYPE_MODE_POWER_SAVING: + return formatAlt(command, command.getBoolean(Command.KEY_ENABLE) ? "*sleepv*" : "*sleepoff*"); + case Command.TYPE_ALARM_SOS: + return formatAlt(command, command.getBoolean(Command.KEY_ENABLE) ? "*soson*" : "*sosoff*"); + case Command.TYPE_SET_CONNECTION: + String server = command.getString(Command.KEY_SERVER).replace(".", "*"); + return formatAlt(command, "*setip*" + server + "*%s*", Command.KEY_PORT); + case Command.TYPE_SOS_NUMBER: + return formatAlt(command, "*master*%s*%s*", Command.KEY_DEVICE_PASSWORD, Command.KEY_PHONE); + default: + return null; + } + } else { + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return formatCommand(command, "(%s%s)", Command.KEY_UNIQUE_ID, Command.KEY_DATA); + case Command.TYPE_GET_VERSION: + return formatCommand(command, "(%sAP07)", Command.KEY_UNIQUE_ID); + case Command.TYPE_REBOOT_DEVICE: + return formatCommand(command, "(%sAT00)", Command.KEY_UNIQUE_ID); + case Command.TYPE_SET_ODOMETER: + return formatCommand(command, "(%sAX01)", Command.KEY_UNIQUE_ID); + case Command.TYPE_POSITION_SINGLE: + return formatCommand(command, "(%sAP00)", Command.KEY_UNIQUE_ID); + case Command.TYPE_POSITION_PERIODIC: + String frequency = String.format("%04X", command.getInteger(Command.KEY_FREQUENCY)); + return formatCommand(command, "(%sAR00" + frequency + "0000)", Command.KEY_UNIQUE_ID); + case Command.TYPE_POSITION_STOP: + return formatCommand(command, "(%sAR0000000000)", Command.KEY_UNIQUE_ID); + case Command.TYPE_ENGINE_STOP: + return formatCommand(command, "(%sAV010)", Command.KEY_UNIQUE_ID); + case Command.TYPE_ENGINE_RESUME: + return formatCommand(command, "(%sAV011)", Command.KEY_UNIQUE_ID); + case Command.TYPE_OUTPUT_CONTROL: + return formatCommand(command, "(%sAV00%s)", Command.KEY_UNIQUE_ID, Command.KEY_DATA); + default: + return null; + } + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Tlt2hProtocol.java b/src/main/java/org/traccar/protocol/Tlt2hProtocol.java new file mode 100644 index 000000000..12fd92afa --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tlt2hProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Tlt2hProtocol extends BaseProtocol { + + public Tlt2hProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(32 * 1024, "##\r\n")); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new Tlt2hProtocolDecoder(Tlt2hProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java new file mode 100644 index 000000000..7ad99b37e --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tlt2hProtocolDecoder.java @@ -0,0 +1,178 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +public class Tlt2hProtocolDecoder extends BaseProtocolDecoder { + + public Tlt2hProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_HEADER = new PatternBuilder() + .number("#(d+)") // imei + .expression("#[^#]*") // user + .number("#d+") // password + .groupBegin() + .number("#([01])") // door + .number("#(d+)") // fuel voltage + .number("#(d+)") // power + .number("#(d+)") // battery + .number("#(d+)") // temperature + .groupEnd("?") + .expression("#([^#]+)") // status + .number("#d+") // number of records + .compile(); + + private static final Pattern PATTERN_POSITION = new PatternBuilder() + .number("#(x+)?") // cell info + .text("$GPRMC,") + .number("(dd)(dd)(dd).d+,") // time (hhmmss.sss) + .expression("([AV]),") // validity + .number("(d+)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d+)(dd.d+),") // longitude + .number("([EW]),") + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .any() + .compile(); + + private void decodeStatus(Position position, String status) { + switch (status) { + case "AUTOSTART": + case "AUTO": + position.set(Position.KEY_IGNITION, true); + break; + case "AUTOSTOP": + case "AUTOLOW": + position.set(Position.KEY_IGNITION, false); + break; + case "TOWED": + position.set(Position.KEY_ALARM, Position.ALARM_TOW); + break; + case "SOS": + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + break; + case "DEF": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + break; + case "BLP": + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + case "CLP": + position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); + break; + case "OS": + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_EXIT); + break; + case "RS": + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE_ENTER); + break; + case "OVERSPEED": + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + break; + default: + break; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + sentence = sentence.trim(); + + String header = sentence.substring(0, sentence.indexOf('\r')); + Parser parser = new Parser(PATTERN_HEADER, header); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Boolean door = null; + Double adc = null; + Double power = null; + Double battery = null; + Double temperature = null; + if (parser.hasNext(5)) { + door = parser.nextInt() == 1; + adc = parser.nextInt() * 0.1; + power = parser.nextInt() * 0.1; + battery = parser.nextInt() * 0.1; + temperature = parser.nextInt() * 0.1; + } + + String status = parser.next(); + + String[] messages = sentence.substring(sentence.indexOf('\n') + 1).split("\r\n"); + List<Position> positions = new LinkedList<>(); + + for (String message : messages) { + parser = new Parser(PATTERN_POSITION, message); + if (parser.matches()) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + parser.next(); // base station info + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + position.set(Position.KEY_DOOR, door); + position.set(Position.PREFIX_ADC + 1, adc); + position.set(Position.KEY_POWER, power); + position.set(Position.KEY_BATTERY, battery); + position.set(Position.PREFIX_TEMP + 1, temperature); + decodeStatus(position, status); + + positions.add(position); + } + } + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/TlvProtocol.java b/src/main/java/org/traccar/protocol/TlvProtocol.java new file mode 100644 index 000000000..94f5da94f --- /dev/null +++ b/src/main/java/org/traccar/protocol/TlvProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TlvProtocol extends BaseProtocol { + + public TlvProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '\0')); + pipeline.addLast(new TlvProtocolDecoder(TlvProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TlvProtocolDecoder.java b/src/main/java/org/traccar/protocol/TlvProtocolDecoder.java new file mode 100644 index 000000000..36cf7859f --- /dev/null +++ b/src/main/java/org/traccar/protocol/TlvProtocolDecoder.java @@ -0,0 +1,110 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +public class TlvProtocolDecoder extends BaseProtocolDecoder { + + public TlvProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private void sendResponse(Channel channel, SocketAddress remoteAddress, String type, String... arguments) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeCharSequence(type, StandardCharsets.US_ASCII); + for (String argument : arguments) { + response.writeByte(argument.length()); + response.writeCharSequence(argument, StandardCharsets.US_ASCII); + } + response.writeByte(0); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + private String readArgument(ByteBuf buf) { + return buf.readSlice(buf.readUnsignedByte()).toString(StandardCharsets.US_ASCII); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + String type = buf.readSlice(2).toString(StandardCharsets.US_ASCII); + + if (channel != null) { + switch (type) { + case "0A": + case "0C": + sendResponse(channel, remoteAddress, type); + break; + case "0B": + sendResponse(channel, remoteAddress, type, "1482202689", "10", "20", "15"); + break; + case "0E": + case "0F": + sendResponse(channel, remoteAddress, type, "30", "Unknown"); + break; + default: + break; + } + } + + if (type.equals("0E")) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, readArgument(buf)); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + position.setTime(new Date(Long.parseLong(readArgument(buf)) * 1000)); + + readArgument(buf); // location identifier + + position.setLongitude(Double.parseDouble(readArgument(buf))); + position.setLatitude(Double.parseDouble(readArgument(buf))); + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(readArgument(buf)))); + position.setCourse(Double.parseDouble(readArgument(buf))); + + position.set(Position.KEY_SATELLITES, Integer.parseInt(readArgument(buf))); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TmgFrameDecoder.java b/src/main/java/org/traccar/protocol/TmgFrameDecoder.java new file mode 100644 index 000000000..205adaa51 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TmgFrameDecoder.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseFrameDecoder; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; + +public class TmgFrameDecoder extends BaseFrameDecoder { + + private boolean isLetter(byte c) { + return c >= 'a' && c <= 'z'; + } + + private int findHeader(ByteBuf buffer) { + int guessedIndex = buffer.indexOf(buffer.readerIndex(), buffer.writerIndex(), (byte) '$'); + while (guessedIndex != -1 && buffer.writerIndex() - guessedIndex >= 5) { + if (buffer.getByte(guessedIndex + 4) == ',' + && isLetter(buffer.getByte(guessedIndex + 1)) + && isLetter(buffer.getByte(guessedIndex + 2)) + && isLetter(buffer.getByte(guessedIndex + 3))) { + return guessedIndex; + } + guessedIndex = buffer.indexOf(guessedIndex, buffer.writerIndex(), (byte) '$'); + } + return -1; + } + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + int beginIndex = findHeader(buf); + + if (beginIndex >= 0) { + + buf.readerIndex(beginIndex); + + int endIndex = buf.indexOf(beginIndex, buf.writerIndex(), (byte) '\n'); + + if (endIndex >= 0) { + ByteBuf frame = buf.readRetainedSlice(endIndex - beginIndex); + buf.readByte(); // delimiter + return frame; + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TmgProtocol.java b/src/main/java/org/traccar/protocol/TmgProtocol.java new file mode 100644 index 000000000..020332ce7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TmgProtocol.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TmgProtocol extends BaseProtocol { + + public TmgProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new TmgFrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new TmgProtocolDecoder(TmgProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TmgProtocolDecoder.java b/src/main/java/org/traccar/protocol/TmgProtocolDecoder.java new file mode 100644 index 000000000..d27849f8c --- /dev/null +++ b/src/main/java/org/traccar/protocol/TmgProtocolDecoder.java @@ -0,0 +1,194 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class TmgProtocolDecoder extends BaseProtocolDecoder { + + public TmgProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$") + .expression("(...),") // type + .expression("[LH],").optional() // history + .number("(d+),") // imei + .number("(dd)(dd)(dddd),") // date (ddmmyyyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(d),") // status + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .groupBegin() + .number("(-?d+.?d*),") // altitude + .number("(d+.d+),") // hdop + .number("(d+),") // satellites + .number("(d+),") // visible satellites + .number("([^,]*),") // operator + .number("(d+),") // rssi + .number("[^,]*,") // cid + .expression("([01]),") // ignition + .number("(d+.?d*),") // battery + .number("(d+.?d*),") // power + .expression("([01]+),") // input + .expression("([01]+),") // output + .expression("[01]+,") // temper status + .number("(d+.?d*)[^,]*,") // adc1 + .number("(d+.?d*)[^,]*,") // adc2 + .number("d+.?d*,") // trip meter + .expression("([^,]*),") // software version + .expression("([^,]*),").optional() // rfid + .or() + .number("[^,]*,") // cid + .number("(d+),") // rssi + .number("(d+),") // satellites + .number("[^,]*,") // battery level + .expression("([01]),") // ignition + .expression("([LH]{4}),") // input + .expression("[NT]{4},") // tamper status + .expression("([LH]{2}),") // output + .number("(d+.d+),") // adc1 + .number("(d+.d+),") // adc1 + .number("[^,]*,") // device id + .number("(d+),") // odometer + .groupEnd() + .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; + } + + String type = parser.next(); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + switch (type) { + case "rmv": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + break; + case "ebl": + position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); + break; + case "ibl": + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + case "tmp": + case "smt": + case "btt": + position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING); + break; + case "ion": + position.set(Position.KEY_IGNITION, true); + break; + case "iof": + position.set(Position.KEY_IGNITION, false); + break; + default: + break; + } + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setValid(parser.nextInt() > 0); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + + if (parser.hasNext(15)) { + + position.setAltitude(parser.nextDouble()); + + position.set(Position.KEY_HDOP, parser.nextDouble()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_SATELLITES_VISIBLE, parser.nextInt()); + position.set(Position.KEY_OPERATOR, parser.next()); + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.KEY_IGNITION, parser.nextInt() == 1); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + position.set(Position.KEY_POWER, parser.nextDouble()); + + int input = parser.nextBinInt(); + int output = parser.nextBinInt(); + + if (!BitUtil.check(input, 0)) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + + position.set(Position.KEY_INPUT, input); + position.set(Position.KEY_OUTPUT, output); + + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.PREFIX_ADC + 2, parser.nextDouble()); + position.set(Position.KEY_VERSION_FW, parser.next()); + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + + } + + if (parser.hasNext(6)) { + + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_IGNITION, parser.nextInt() == 1); + + char[] input = parser.next().toCharArray(); + for (int i = 0; i < input.length; i++) { + position.set(Position.PREFIX_IN + (i + 1), input[i] == 'H'); + } + + char[] output = parser.next().toCharArray(); + for (int i = 0; i < output.length; i++) { + position.set(Position.PREFIX_OUT + (i + 1), output[i] == 'H'); + } + + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.PREFIX_ADC + 2, parser.nextDouble()); + position.set(Position.KEY_ODOMETER, parser.nextInt()); + + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/TopflytechProtocol.java b/src/main/java/org/traccar/protocol/TopflytechProtocol.java new file mode 100644 index 000000000..303072bdb --- /dev/null +++ b/src/main/java/org/traccar/protocol/TopflytechProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TopflytechProtocol extends BaseProtocol { + + public TopflytechProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, ')')); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new TopflytechProtocolDecoder(TopflytechProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TopflytechProtocolDecoder.java b/src/main/java/org/traccar/protocol/TopflytechProtocolDecoder.java new file mode 100644 index 000000000..6de053c32 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TopflytechProtocolDecoder.java @@ -0,0 +1,76 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class TopflytechProtocolDecoder extends BaseProtocolDecoder { + + public TopflytechProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("(") + .number("(d+)") // imei + .any() + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd)") // time (hhmmss) + .expression("([AV])") + .number("(dd)(dd.dddd)([NS])") // latitude + .number("(ddd)(dd.dddd)([EW])") // longitude + .number("(ddd.d)") // speed + .number("(d+.d+)") // course + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/TopinProtocol.java b/src/main/java/org/traccar/protocol/TopinProtocol.java new file mode 100644 index 000000000..844dd7518 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TopinProtocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TopinProtocol extends BaseProtocol { + + public TopinProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new TopinProtocolDecoder(TopinProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java new file mode 100644 index 000000000..eee0e9ae8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TopinProtocolDecoder.java @@ -0,0 +1,182 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.util.TimeZone; + +public class TopinProtocolDecoder extends BaseProtocolDecoder { + + public TopinProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN = 0x01; + public static final int MSG_GPS = 0x10; + public static final int MSG_GPS_OFFLINE = 0x11; + public static final int MSG_STATUS = 0x13; + public static final int MSG_WIFI_OFFLINE = 0x17; + public static final int MSG_WIFI = 0x69; + + private void sendResponse(Channel channel, int length, int type, ByteBuf content) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeShort(0x7878); + response.writeByte(length); + response.writeByte(type); + response.writeBytes(content); + response.writeByte('\r'); + response.writeByte('\n'); + content.release(); + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + int length = buf.readUnsignedByte(); + + int type = buf.readUnsignedByte(); + + DeviceSession deviceSession; + if (type == MSG_LOGIN) { + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(1); + deviceSession = getDeviceSession(channel, remoteAddress, imei); + ByteBuf content = Unpooled.buffer(); + content.writeByte(deviceSession != null ? 0x01 : 0x44); + sendResponse(channel, length, type, content); + return null; + } else { + deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + } + + if (type == MSG_GPS || type == MSG_GPS_OFFLINE) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + ByteBuf time = buf.slice(buf.readerIndex(), 6); + + Gt06ProtocolDecoder.decodeGps(position, buf, false, TimeZone.getTimeZone("UTC")); + + ByteBuf content = Unpooled.buffer(); + content.writeBytes(time); + sendResponse(channel, length, type, content); + + return position; + + } else if (type == MSG_STATUS) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + int battery = buf.readUnsignedByte(); + int firmware = buf.readUnsignedByte(); + int timezone = buf.readUnsignedByte(); + int interval = buf.readUnsignedByte(); + int signal = 0; + if (length >= 7) { + signal = buf.readUnsignedByte(); + position.set(Position.KEY_RSSI, signal); + } + + position.set(Position.KEY_BATTERY_LEVEL, battery); + position.set(Position.KEY_VERSION_FW, firmware); + + ByteBuf content = Unpooled.buffer(); + content.writeByte(battery); + content.writeByte(firmware); + content.writeByte(timezone); + content.writeByte(interval); + if (length >= 7) { + content.writeByte(signal); + } + sendResponse(channel, length, type, content); + + return position; + + } else if (type == MSG_WIFI || type == MSG_WIFI_OFFLINE) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + ByteBuf time = buf.readSlice(6); + DateBuilder dateBuilder = new DateBuilder() + .setYear(BcdUtil.readInteger(time, 2)) + .setMonth(BcdUtil.readInteger(time, 2)) + .setDay(BcdUtil.readInteger(time, 2)) + .setHour(BcdUtil.readInteger(time, 2)) + .setMinute(BcdUtil.readInteger(time, 2)) + .setSecond(BcdUtil.readInteger(time, 2)); + time.resetReaderIndex(); + + getLastLocation(position, dateBuilder.getDate()); + + Network network = new Network(); + for (int i = 0; i < length; i++) { + String mac = String.format("%02x:%02x:%02x:%02x:%02x:%02x", + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte(), + buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + network.addWifiAccessPoint(WifiAccessPoint.from(mac, buf.readUnsignedByte())); + } + + int cellCount = buf.readUnsignedByte(); + int mcc = buf.readUnsignedShort(); + int mnc = buf.readUnsignedByte(); + for (int i = 0; i < cellCount; i++) { + network.addCellTower(CellTower.from( + mcc, mnc, buf.readUnsignedShort(), buf.readUnsignedShort(), buf.readUnsignedByte())); + } + + position.setNetwork(network); + + ByteBuf content = Unpooled.buffer(); + content.writeBytes(time); + sendResponse(channel, length, type, content); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TotemFrameDecoder.java b/src/main/java/org/traccar/protocol/TotemFrameDecoder.java new file mode 100644 index 000000000..3fa5abc7a --- /dev/null +++ b/src/main/java/org/traccar/protocol/TotemFrameDecoder.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 java.nio.charset.StandardCharsets; + +import org.traccar.BaseFrameDecoder; +import org.traccar.helper.BufferUtil; + +public class TotemFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 10) { + return null; + } + + int beginIndex = BufferUtil.indexOf("$$", buf); + if (beginIndex == -1) { + return null; + } else if (beginIndex > buf.readerIndex()) { + buf.readerIndex(beginIndex); + } + + int length; + + if (buf.getByte(buf.readerIndex() + 2) == (byte) '0') { + length = Integer.parseInt(buf.toString(buf.readerIndex() + 2, 4, StandardCharsets.US_ASCII)); + } else { + length = Integer.parseInt(buf.toString(buf.readerIndex() + 2, 2, StandardCharsets.US_ASCII), 16); + } + + if (length <= buf.readableBytes()) { + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TotemProtocol.java b/src/main/java/org/traccar/protocol/TotemProtocol.java new file mode 100644 index 000000000..f8cda8358 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TotemProtocol.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class TotemProtocol extends BaseProtocol { + + public TotemProtocol() { + setSupportedDataCommands( + Command.TYPE_ENGINE_RESUME, + Command.TYPE_ENGINE_STOP + ); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new TotemFrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new TotemProtocolEncoder(TotemProtocol.this)); + pipeline.addLast(new TotemProtocolDecoder(TotemProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java b/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java new file mode 100644 index 000000000..cd7f684b8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java @@ -0,0 +1,444 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import 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.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class TotemProtocolDecoder extends BaseProtocolDecoder { + + public TotemProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN1 = new PatternBuilder() + .text("$$") // header + .number("xx") // length + .number("(d+)|") // imei + .expression("(..)") // alarm + .text("$GPRMC,") + .number("(dd)(dd)(dd).d+,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(d+)(dd.d+),([NS]),") // latitude + .number("(d+)(dd.d+),([EW]),") // longitude + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .expression("[^*]*").text("*") + .number("xx|") // checksum + .number("(d+.d+)|") // pdop + .number("(d+.d+)|") // hdop + .number("(d+.d+)|") // vdop + .number("(d+)|") // io status + .number("d+|") // battery time + .number("d") // charged + .number("(ddd)") // battery + .number("(dddd)|") // power + .number("(d+)|").optional() // adc + .number("x*(xxxx)") // lac + .number("(xxxx)|") // cid + .number("(d+)|") // temperature + .number("(d+.d+)|") // odometer + .number("d+|") // serial number + .any() + .number("xxxx") // checksum + .any() + .compile(); + + private static final Pattern PATTERN2 = new PatternBuilder() + .text("$$") // header + .number("xx") // length + .number("(d+)|") // imei + .expression("(..)") // alarm type + .number("(dd)(dd)(dd)") // date (ddmmyy) + .number("(dd)(dd)(dd)|") // time (hhmmss) + .expression("([AV])|") // validity + .number("(d+)(dd.d+)|") // latitude + .expression("([NS])|") + .number("(d+)(dd.d+)|") // longitude + .expression("([EW])|") + .number("(d+.d+)?|") // speed + .number("(d+)?|") // course + .number("(d+.d+)|") // hdop + .number("(d+)|") // io status + .number("d") // charged + .number("(dd)") // battery + .number("(dd)|") // external power + .number("(d+)|") // adc + .number("(xxxx)") // lac + .number("(xxxx)|") // cid + .number("(d+)|") // temperature + .number("(d+.d+)|") // odometer + .number("d+|") // serial number + .number("xxxx") // checksum + .any() + .compile(); + + private static final Pattern PATTERN3 = new PatternBuilder() + .text("$$") // header + .number("xx") // length + .number("(d+)|") // imei + .expression("(..)") // alarm type + .number("(dd)(dd)(dd)") // date (ddmmyy) + .number("(dd)(dd)(dd)") // time (hhmmss) + .number("(xxxx)") // io status + .expression("[01]") // charging + .number("(dd)") // battery + .number("(dd)") // external power + .number("(dddd)") // adc 1 + .number("(dddd)") // adc 2 + .number("(ddd)") // temperature 1 + .number("(ddd)") // temperature 2 + .number("(xxxx)") // lac + .number("(xxxx)") // cid + .expression("([AV])") // validity + .number("(dd)") // satellites + .number("(ddd)") // course + .number("(ddd)") // speed + .number("(dd.d)") // pdop + .number("(d{7})") // odometer + .number("(dd)(dd.dddd)([NS])") // latitude + .number("(ddd)(dd.dddd)([EW])") // longitude + .number("dddd") // serial number + .number("xxxx") // checksum + .any() + .compile(); + + private static final Pattern PATTERN4 = new PatternBuilder() + .text("$$") // header + .number("dddd") // length + .number("(xx)") // type + .number("(d+)|") // imei + .number("(x{8})") // status + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd)") // time (hhmmss) + .number("(dd)") // battery + .number("(dd)") // external power + .number("(dddd)") // adc 1 + .groupBegin() + .groupBegin() + .number("(dddd)") // adc 2 + .number("(dddd)") // adc 3 + .number("(dddd)") // adc 4 + .groupEnd("?") + .number("(dddd)") // temperature 1 + .number("(dddd)?") // temperature 2 + .groupEnd("?") + .number("(xxxx)") // lac + .number("(xxxx)") // cid + .groupBegin() + .number("(dd)") // mcc + .number("(ddd)") // mnc + .groupEnd("?") + .number("(dd)") // satellites + .number("(dd)") // gsm (rssi) + .number("(ddd)") // course + .number("(ddd)") // speed + .number("(dd.d)") // hdop + .number("(d{7})") // odometer + .number("(dd)(dd.dddd)([NS])") // latitude + .number("(ddd)(dd.dddd)([EW])") // longitude + .number("dddd") // serial number + .number("xx") // checksum + .any() + .compile(); + + private String decodeAlarm123(int value) { + switch (value) { + case 0x01: + return Position.ALARM_SOS; + case 0x10: + return Position.ALARM_LOW_BATTERY; + case 0x11: + return Position.ALARM_OVERSPEED; + case 0x30: + return Position.ALARM_PARKING; + case 0x42: + return Position.ALARM_GEOFENCE_EXIT; + case 0x43: + return Position.ALARM_GEOFENCE_ENTER; + default: + return null; + } + } + + private String decodeAlarm4(int value) { + switch (value) { + case 0x01: + return Position.ALARM_SOS; + case 0x02: + return Position.ALARM_OVERSPEED; + case 0x04: + return Position.ALARM_GEOFENCE_EXIT; + case 0x05: + return Position.ALARM_GEOFENCE_ENTER; + case 0x40: + return Position.ALARM_SHOCK; + case 0x42: + return Position.ALARM_ACCELERATION; + case 0x43: + return Position.ALARM_BRAKING; + default: + return null; + } + } + + private boolean decode12(Position position, Parser parser, Pattern pattern) { + + if (parser.hasNext()) { + position.set(Position.KEY_ALARM, decodeAlarm123(Short.parseShort(parser.next(), 16))); + } + DateBuilder dateBuilder = new DateBuilder(); + int year = 0, month = 0, day = 0; + if (pattern == PATTERN2) { + day = parser.nextInt(0); + month = parser.nextInt(0); + year = parser.nextInt(0); + } + dateBuilder.setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + if (pattern == PATTERN1) { + day = parser.nextInt(0); + month = parser.nextInt(0); + year = parser.nextInt(0); + } + if (year == 0) { + return false; // ignore invalid data + } + dateBuilder.setDate(year, month, day); + position.setTime(dateBuilder.getDate()); + + if (pattern == PATTERN1) { + position.set(Position.KEY_PDOP, parser.nextDouble()); + position.set(Position.KEY_HDOP, parser.nextDouble()); + position.set(Position.KEY_VDOP, parser.nextDouble()); + } else { + position.set(Position.KEY_HDOP, parser.nextDouble()); + } + + int io = parser.nextBinInt(); + position.set(Position.KEY_STATUS, io); + if (pattern == PATTERN1) { + position.set(Position.KEY_ALARM, BitUtil.check(io, 0) ? Position.ALARM_SOS : null); + position.set(Position.PREFIX_IN + 3, BitUtil.check(io, 4)); + position.set(Position.PREFIX_IN + 4, BitUtil.check(io, 5)); + position.set(Position.PREFIX_IN + 1, BitUtil.check(io, 6)); + position.set(Position.PREFIX_IN + 2, BitUtil.check(io, 7)); + position.set(Position.PREFIX_OUT + 1, BitUtil.check(io, 8)); + position.set(Position.PREFIX_OUT + 2, BitUtil.check(io, 9)); + position.set(Position.KEY_BATTERY, parser.nextDouble(0) * 0.01); + } else { + position.set(Position.KEY_ANTENNA, BitUtil.check(io, 0)); + position.set(Position.KEY_CHARGE, BitUtil.check(io, 1)); + for (int i = 1; i <= 6; i++) { + position.set(Position.PREFIX_IN + i, BitUtil.check(io, 1 + i)); + } + for (int i = 1; i <= 4; i++) { + position.set(Position.PREFIX_OUT + i, BitUtil.check(io, 7 + i)); + } + position.set(Position.KEY_BATTERY, parser.nextDouble(0) * 0.1); + } + + position.set(Position.KEY_POWER, parser.nextDouble(0)); + position.set(Position.PREFIX_ADC + 1, parser.next()); + + int lac = parser.nextHexInt(0); + int cid = parser.nextHexInt(0); + if (lac != 0 && cid != 0) { + position.setNetwork(new Network(CellTower.fromLacCid(lac, cid))); + } + + position.set(Position.PREFIX_TEMP + 1, parser.next()); + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + + return true; + } + + private boolean decode3(Position position, Parser parser) { + + if (parser.hasNext()) { + position.set(Position.KEY_ALARM, decodeAlarm123(Short.parseShort(parser.next(), 16))); + } + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.set(Position.PREFIX_IO + 1, parser.next()); + position.set(Position.KEY_BATTERY, parser.nextDouble(0) * 0.1); + position.set(Position.KEY_POWER, parser.nextDouble(0)); + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.PREFIX_ADC + 2, parser.next()); + position.set(Position.PREFIX_TEMP + 1, parser.next()); + position.set(Position.PREFIX_TEMP + 2, parser.next()); + + position.setNetwork(new Network( + CellTower.fromLacCid(parser.nextHexInt(0), parser.nextHexInt(0)))); + + position.setValid(parser.next().equals("A")); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.setCourse(parser.nextDouble(0)); + position.setSpeed(parser.nextDouble(0)); + position.set(Position.KEY_PDOP, parser.nextDouble()); + position.set(Position.KEY_ODOMETER, parser.nextInt(0) * 1000); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + return true; + } + + private boolean decode4(Position position, Parser parser) { + + long status = parser.nextHexLong(); + + position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 1) ? Position.ALARM_SOS : null); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 32 - 2)); + position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 3) ? Position.ALARM_OVERSPEED : null); + position.set(Position.KEY_CHARGE, BitUtil.check(status, 32 - 4)); + position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 5) ? Position.ALARM_GEOFENCE_EXIT : null); + position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 6) ? Position.ALARM_GEOFENCE_ENTER : null); + position.set(Position.PREFIX_OUT + 1, BitUtil.check(status, 32 - 9)); + position.set(Position.PREFIX_OUT + 2, BitUtil.check(status, 32 - 10)); + position.set(Position.PREFIX_OUT + 3, BitUtil.check(status, 32 - 11)); + position.set(Position.PREFIX_OUT + 4, BitUtil.check(status, 32 - 12)); + position.set(Position.PREFIX_IN + 2, BitUtil.check(status, 32 - 13)); + position.set(Position.PREFIX_IN + 3, BitUtil.check(status, 32 - 14)); + position.set(Position.PREFIX_IN + 4, BitUtil.check(status, 32 - 15)); + position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 16) ? Position.ALARM_SHOCK : null); + position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 18) ? Position.ALARM_LOW_BATTERY : null); + position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 22) ? Position.ALARM_JAMMING : null); + + + position.setTime(parser.nextDateTime()); + + position.set(Position.KEY_BATTERY, parser.nextDouble() * 0.1); + position.set(Position.KEY_POWER, parser.nextDouble()); + + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.PREFIX_ADC + 2, parser.next()); + position.set(Position.PREFIX_ADC + 3, parser.next()); + position.set(Position.PREFIX_ADC + 4, parser.next()); + position.set(Position.PREFIX_TEMP + 1, parser.next()); + + if (parser.hasNext()) { + position.set(Position.PREFIX_TEMP + 2, parser.next()); + position.setValid(BitUtil.check(status, 32 - 20)); + } else { + position.setValid(BitUtil.check(status, 32 - 18)); + } + + int lac = parser.nextHexInt(); + int cid = parser.nextHexInt(); + CellTower cellTower; + if (parser.hasNext(2)) { + int mnc = parser.nextInt(); + int mcc = parser.nextInt(); + cellTower = CellTower.from(mcc, mnc, lac, cid); + } else { + cellTower = CellTower.fromLacCid(lac, cid); + } + position.set(Position.KEY_SATELLITES, parser.nextInt()); + cellTower.setSignalStrength(parser.nextInt()); + position.setNetwork(new Network(cellTower)); + + position.setCourse(parser.nextDouble()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.set(Position.KEY_HDOP, parser.nextDouble()); + position.set(Position.KEY_ODOMETER, parser.nextInt() * 1000); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + return true; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + Pattern pattern = PATTERN3; + if (sentence.charAt(2) == '0') { + pattern = PATTERN4; + } else if (sentence.contains("$GPRMC")) { + pattern = PATTERN1; + } else { + int index = sentence.indexOf('|'); + if (index != -1 && sentence.indexOf('|', index + 1) != -1) { + pattern = PATTERN2; + } + } + + Parser parser = new Parser(pattern, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + if (pattern == PATTERN4) { + position.set(Position.KEY_ALARM, decodeAlarm4(parser.nextHexInt())); + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + boolean result; + if (pattern == PATTERN1 || pattern == PATTERN2) { + result = decode12(position, parser, pattern); + } else if (pattern == PATTERN3) { + result = decode3(position, parser); + } else { + result = decode4(position, parser); + } + + if (channel != null) { + if (pattern == PATTERN4) { + String response = "$$0014AA" + sentence.substring(sentence.length() - 6, sentence.length() - 2); + response += String.format("%02X", Checksum.xor(response)).toUpperCase(); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } else { + channel.writeAndFlush(new NetworkMessage("ACK OK\r\n", remoteAddress)); + } + } + + return result ? position : null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java b/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java new file mode 100644 index 000000000..a96dd1ee3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TotemProtocolEncoder.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 Irving Gonzalez + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class TotemProtocolEncoder extends StringProtocolEncoder { + + public TotemProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + initDevicePassword(command, "000000"); + + switch (command.getType()) { + // Assuming PIN 8 (Output C) is the power wire, like manual says but it can be PIN 5,7,8 + case Command.TYPE_ENGINE_STOP: + return formatCommand(command, "*%s,025,C,1#", Command.KEY_DEVICE_PASSWORD); + case Command.TYPE_ENGINE_RESUME: + return formatCommand(command, "*%s,025,C,0#", Command.KEY_DEVICE_PASSWORD); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Tr20Protocol.java b/src/main/java/org/traccar/protocol/Tr20Protocol.java new file mode 100644 index 000000000..3eee9d9c3 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tr20Protocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Tr20Protocol extends BaseProtocol { + + public Tr20Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Tr20ProtocolDecoder(Tr20Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Tr20ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tr20ProtocolDecoder.java new file mode 100644 index 000000000..c2e6c381f --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tr20ProtocolDecoder.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class Tr20ProtocolDecoder extends BaseProtocolDecoder { + + public Tr20ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_PING = new PatternBuilder() + .text("%%") + .expression("[^,]+,") + .number("(d+)") + .compile(); + + private static final Pattern PATTERN_DATA = new PatternBuilder() + .text("%%") + .expression("([^,]+),") // id + .expression("([AL]),") // validity + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([NS])") + .number("(dd)(dd.d+)") // latitude + .expression("([EW])") + .number("(ddd)(dd.d+),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(?:NA|[FC]?(-?d+)),") // temperature + .number("(x{8}),") // status + .number("(d+)") // event + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN_PING, (String) msg); + if (parser.matches()) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage( + "&&" + parser.next() + "\r\n", remoteAddress)); // keep-alive response + } + return null; + } + + parser = new Parser(PATTERN_DATA, (String) msg); + 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.setValid(parser.next().equals("A")); + + position.setTime(parser.nextDateTime()); + + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + + position.set(Position.PREFIX_TEMP + 1, parser.nextInt()); + position.set(Position.KEY_STATUS, parser.nextHexLong()); + position.set(Position.KEY_EVENT, parser.nextInt()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/Tr900Protocol.java b/src/main/java/org/traccar/protocol/Tr900Protocol.java new file mode 100644 index 000000000..b70521b35 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tr900Protocol.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Tr900Protocol extends BaseProtocol { + + public Tr900Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Tr900ProtocolDecoder(Tr900Protocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Tr900ProtocolDecoder(Tr900Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Tr900ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tr900ProtocolDecoder.java new file mode 100644 index 000000000..319194c21 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tr900ProtocolDecoder.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015 -2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class Tr900ProtocolDecoder extends BaseProtocolDecoder { + + public Tr900ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number(">(d+),") // id + .number("d+,") // period + .number("(d),") // fix + .number("(dd)(dd)(dd),") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([EW])") + .number("(ddd)(dd.d+),") // longitude + .expression("([NS])") + .number("(dd)(dd.d+),") // latitude + .expression("[^,]*,") // command + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .number("(d+),") // gsm + .number("(d+),") // event + .number("(d+)-") // adc + .number("(d+),") // battery + .number("d+,") // impulses + .number("(d+),") // input + .number("(d+)") // status + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(parser.nextInt(0) == 1); + + position.setTime(parser.nextDateTime()); + + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG_MIN)); + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_RSSI, parser.nextDouble()); + position.set(Position.KEY_EVENT, parser.nextInt(0)); + position.set(Position.PREFIX_ADC + 1, parser.nextInt(0)); + position.set(Position.KEY_BATTERY, parser.nextInt(0)); + position.set(Position.KEY_INPUT, parser.next()); + position.set(Position.KEY_STATUS, parser.next()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/TrackboxProtocol.java b/src/main/java/org/traccar/protocol/TrackboxProtocol.java new file mode 100644 index 000000000..5da5abd64 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TrackboxProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TrackboxProtocol extends BaseProtocol { + + public TrackboxProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new TrackboxProtocolDecoder(TrackboxProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TrackboxProtocolDecoder.java b/src/main/java/org/traccar/protocol/TrackboxProtocolDecoder.java new file mode 100644 index 000000000..db8022738 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TrackboxProtocolDecoder.java @@ -0,0 +1,110 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.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 TrackboxProtocolDecoder extends BaseProtocolDecoder { + + public TrackboxProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("(dd)(dd)(dd).(ddd),") // time (hhmmss.sss) + .number("(dd)(dd.dddd)([NS]),") // latitude + .number("(ddd)(dd.dddd)([EW]),") // longitude + .number("(d+.d),") // hdop + .number("(-?d+.?d*),") // altitude + .number("(d),") // fix type + .number("(d+.d+),") // course + .number("d+.d+,") // speed (kph) + .number("(d+.d+),") // speed (knots) + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(d+)") // satellites + .compile(); + + private void sendResponse(Channel channel, SocketAddress remoteAddress) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage("=OK=\r\n", remoteAddress)); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (sentence.startsWith("a=connect")) { + String id = sentence.substring(sentence.indexOf("i=") + 2); + if (getDeviceSession(channel, remoteAddress, id) != null) { + sendResponse(channel, remoteAddress); + } + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + sendResponse(channel, remoteAddress); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + position.set(Position.KEY_HDOP, parser.nextDouble()); + + position.setAltitude(parser.nextDouble(0)); + + int fix = parser.nextInt(0); + position.set(Position.KEY_GPS, fix); + position.setValid(fix > 0); + + position.setCourse(parser.nextDouble(0)); + position.setSpeed(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/TrakMateProtocol.java b/src/main/java/org/traccar/protocol/TrakMateProtocol.java new file mode 100644 index 000000000..bda5df10f --- /dev/null +++ b/src/main/java/org/traccar/protocol/TrakMateProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TrakMateProtocol extends BaseProtocol { + + public TrakMateProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new TrakMateProtocolDecoder(TrakMateProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TrakMateProtocolDecoder.java b/src/main/java/org/traccar/protocol/TrakMateProtocolDecoder.java new file mode 100644 index 000000000..4d5cb18f5 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TrakMateProtocolDecoder.java @@ -0,0 +1,233 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class TrakMateProtocolDecoder extends BaseProtocolDecoder { + + public TrakMateProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_SRT = new PatternBuilder() + .text("^TMSRT|") + .expression("([^ ]+)|") // uid + .number("(d+.d+)|") // latitude + .number("(d+.d+)|") // longitude + .number("(dd)(dd)(dd)|") // time (hhmmss) + .number("(dd)(dd)(dd)|") // date (ddmmyy) + .number("(d+.d+)|") // software ver + .number("(d+.d+)|") // Hardware ver + .any() + .compile(); + + private static final Pattern PATTERN_PER = new PatternBuilder() + .text("^TM") + .expression("...|") // type + .expression("([^ ]+)|") // uid + .number("(d+)|") // seq + .number("(d+.d+)|") // latitude + .number("(d+.d+)|") // longitude + .number("(dd)(dd)(dd)|") // time (hhmmss) + .number("(dd)(dd)(dd)|") // date (ddmmyy) + .number("(d+.d+)|") // speed + .number("(d+.d+)|") // heading + .number("(d+)|").optional() // satellites + .number("([01])|") // ignition + .groupBegin() + .number("(d+)|") // dop1 + .number("(d+)|") // dop2 + .number("(d+.d+)|") // analog + .number("(d+.d+)|") // internal battery + .or() + .number("-?d+ -?d+ -?d+|") // accelerometer + .number("([01])|") // movement + .groupEnd() + .number("(d+.d+)|") // vehicle battery + .number("(d+.d+)|") // gps odometer + .number("(d+.d+)|").optional() // pulse odometer + .number("([01])|") // main power status + .number("([01])|") // gps data validity + .number("([01])|") // live or cache + .any() + .compile(); + + private static final Pattern PATTERN_ALT = new PatternBuilder() + .text("^TMALT|") + .expression("([^ ]+)|") // uid + .number("(d+)|") // seq + .number("(d+)|") // Alert type + .number("(d+)|") // Alert status + .number("(d+.d+)|") // latitude + .number("(d+.d+)|") // longitude + .number("(dd)(dd)(dd)|") // time (hhmmss) + .number("(dd)(dd)(dd)|") // date (ddmmyy) + .number("(d+.d+)|") // speed + .number("(d+.d+)|") // heading + .any() + .compile(); + + private String decodeAlarm(int value) { + switch (value) { + case 1: + return Position.ALARM_SOS; + case 3: + return Position.ALARM_GEOFENCE; + case 4: + return Position.ALARM_POWER_CUT; + default: + return null; + } + } + + private Object decodeSrt(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_SRT, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY)); + + position.set(Position.KEY_VERSION_FW, parser.next()); + position.set(Position.KEY_VERSION_HW, parser.next()); + + return position; + } + + private Object decodeAlt(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_ALT, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + parser.next(); // seq + position.set(Position.KEY_ALARM, decodeAlarm(parser.nextInt())); + parser.next(); // alert status or data + + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY)); + + position.setSpeed(parser.nextDouble()); + position.setCourse(parser.nextDouble()); + + return position; + } + + private Object decodePer(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN_PER, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + parser.next(); // seq + + position.setLatitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY)); + + position.setSpeed(parser.nextDouble()); + position.setCourse(parser.nextDouble()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_IGNITION, parser.nextInt() > 0); + + if (parser.hasNext(4)) { + position.set("dop1", parser.nextInt()); + position.set("dop2", parser.nextInt()); + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + } + + if (parser.hasNext()) { + position.set(Position.KEY_MOTION, parser.nextInt(0) > 0); + } + + position.set(Position.KEY_POWER, parser.nextDouble()); + position.set(Position.KEY_ODOMETER, parser.nextDouble()); + position.set("pulseOdometer", parser.nextDouble()); + position.set(Position.KEY_STATUS, parser.nextInt()); + + position.setValid(parser.nextInt() > 0); + + position.set(Position.KEY_ARCHIVE, parser.nextInt() > 0); + + return position; + } + + @Override + protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + int typeIndex = sentence.indexOf("^TM"); + if (typeIndex < 0) { + return null; + } + + String type = sentence.substring(typeIndex + 3, typeIndex + 6); + switch (type) { + case "ALT": + return decodeAlt(channel, remoteAddress, sentence); + case "SRT": + return decodeSrt(channel, remoteAddress, sentence); + default: + return decodePer(channel, remoteAddress, sentence); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/TramigoFrameDecoder.java b/src/main/java/org/traccar/protocol/TramigoFrameDecoder.java new file mode 100644 index 000000000..e4c94dc77 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TramigoFrameDecoder.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class TramigoFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 20) { + return null; + } + + int length; + if (buf.getUnsignedByte(buf.readerIndex()) == 0x80) { + length = buf.getUnsignedShortLE(buf.readerIndex() + 6); + } else { + length = buf.getUnsignedShort(buf.readerIndex() + 6); + } + + if (length <= buf.readableBytes()) { + return buf.readRetainedSlice(length); + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TramigoProtocol.java b/src/main/java/org/traccar/protocol/TramigoProtocol.java new file mode 100644 index 000000000..f683ccc5d --- /dev/null +++ b/src/main/java/org/traccar/protocol/TramigoProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TramigoProtocol extends BaseProtocol { + + public TramigoProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new TramigoFrameDecoder()); + pipeline.addLast(new TramigoProtocolDecoder(TramigoProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TramigoProtocolDecoder.java b/src/main/java/org/traccar/protocol/TramigoProtocolDecoder.java new file mode 100644 index 000000000..e42e2f670 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TramigoProtocolDecoder.java @@ -0,0 +1,160 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.DateUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TramigoProtocolDecoder extends BaseProtocolDecoder { + + public TramigoProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_COMPACT = 0x0100; + public static final int MSG_FULL = 0x00FE; + + private static final String[] DIRECTIONS = new String[] {"N", "NE", "E", "SE", "S", "SW", "W", "NW"}; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + int protocol = buf.readUnsignedByte(); + boolean legacy = protocol == 0x80; + + buf.readUnsignedByte(); // version id + int index = legacy ? buf.readUnsignedShort() : buf.readUnsignedShortLE(); + int type = legacy ? buf.readUnsignedShort() : buf.readUnsignedShortLE(); + buf.readUnsignedShort(); // length + buf.readUnsignedShort(); // mask + buf.readUnsignedShort(); // checksum + long id = legacy ? buf.readUnsignedInt() : buf.readUnsignedIntLE(); + buf.readUnsignedInt(); // time + + Position position = new Position(getProtocolName()); + position.set(Position.KEY_INDEX, index); + position.setValid(true); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(id)); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + if (protocol == 0x01 && (type == MSG_COMPACT || type == MSG_FULL)) { + + // need to send ack? + + buf.readUnsignedShortLE(); // report trigger + buf.readUnsignedShortLE(); // state flag + + position.setLatitude(buf.readUnsignedIntLE() * 0.0000001); + position.setLongitude(buf.readUnsignedIntLE() * 0.0000001); + + position.set(Position.KEY_RSSI, buf.readUnsignedShortLE()); + position.set(Position.KEY_SATELLITES, buf.readUnsignedShortLE()); + position.set(Position.KEY_SATELLITES_VISIBLE, buf.readUnsignedShortLE()); + position.set("gpsAntennaStatus", buf.readUnsignedShortLE()); + + position.setSpeed(buf.readUnsignedShortLE() * 0.194384); + position.setCourse(buf.readUnsignedShortLE()); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + + position.set(Position.KEY_BATTERY, buf.readUnsignedShortLE()); + + position.set(Position.KEY_CHARGE, buf.readUnsignedShortLE()); + + position.setTime(new Date(buf.readUnsignedIntLE() * 1000)); + + // parse other data + + return position; + + } else if (legacy) { + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage( + Unpooled.copiedBuffer("gprs,ack," + index, StandardCharsets.US_ASCII), remoteAddress)); + } + + String sentence = buf.toString(StandardCharsets.US_ASCII); + + Pattern pattern = Pattern.compile("(-?\\d+\\.\\d+), (-?\\d+\\.\\d+)"); + Matcher matcher = pattern.matcher(sentence); + if (!matcher.find()) { + return null; + } + position.setLatitude(Double.parseDouble(matcher.group(1))); + position.setLongitude(Double.parseDouble(matcher.group(2))); + + pattern = Pattern.compile("([NSWE]{1,2}) with speed (\\d+) km/h"); + matcher = pattern.matcher(sentence); + if (matcher.find()) { + for (int i = 0; i < DIRECTIONS.length; i++) { + if (matcher.group(1).equals(DIRECTIONS[i])) { + position.setCourse(i * 45.0); + break; + } + } + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(matcher.group(2)))); + } + + pattern = Pattern.compile("(\\d{1,2}:\\d{2}(:\\d{2})? \\w{3} \\d{1,2})"); + matcher = pattern.matcher(sentence); + if (!matcher.find()) { + return null; + } + DateFormat dateFormat = new SimpleDateFormat( + matcher.group(2) != null ? "HH:mm:ss MMM d yyyy" : "HH:mm MMM d yyyy", Locale.ENGLISH); + position.setTime(DateUtil.correctYear( + dateFormat.parse(matcher.group(1) + " " + Calendar.getInstance().get(Calendar.YEAR)))); + + if (sentence.contains("Ignition on detected")) { + position.set(Position.KEY_IGNITION, true); + } else if (sentence.contains("Ignition off detected")) { + position.set(Position.KEY_IGNITION, false); + } + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/TrvProtocol.java b/src/main/java/org/traccar/protocol/TrvProtocol.java new file mode 100644 index 000000000..99a164cf1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TrvProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TrvProtocol extends BaseProtocol { + + public TrvProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new TrvProtocolDecoder(TrvProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java b/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java new file mode 100644 index 000000000..05312b820 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java @@ -0,0 +1,260 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.regex.Pattern; + +public class TrvProtocolDecoder extends BaseProtocolDecoder { + + public TrvProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression("[A-Z]{2,3}") + .expression("[A-Z]P") + .number("dd") + .number("(dd)(dd)(dd)") // date (yymmdd) + .expression("([AV])") // validity + .number("(dd)(dd.d+)") // latitude + .expression("([NS])") + .number("(ddd)(dd.d+)") // longitude + .expression("([EW])") + .number("(ddd.d)") // speed + .number("(dd)(dd)(dd)") // time (hhmmss) + .number("([d.]{6})") // course + .number("(ddd)") // gsm + .number("(ddd)") // satellites + .number("(ddd)") // battery + .number("(d)") // acc + .number("(dd)") // arm status + .number("(dd)") // working mode + .number("(?:[0-2]{3})?,") + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(d+),") // lac + .number("(d+)") // cell + .any() + .compile(); + + private static final Pattern PATTERN_HEATRBEAT = new PatternBuilder() + .expression("[A-Z]{2,3}") + .text("CP01,") + .number("(ddd)") // gsm + .number("(ddd)") // gps + .number("(ddd)") // battery + .number("(d)") // acc + .number("(dd)") // arm status + .number("(dd)") // working mode + .groupBegin() + .number("(ddd)") // interval + .number("d") // vibration alarm + .number("ddd") // vibration sensitivity + .number("d") // automatic arm + .number("dddd") // automatic arm time + .number("(d)") // blocked + .number("(d)") // power status + .number("(d)") // movement status + .groupEnd("?") + .any() + .compile(); + + private static final Pattern PATTERN_LBS = new PatternBuilder() + .expression("[A-Z]{2,3}") + .text("AP02,") + .expression("[^,]+,") // language + .number("[01],") // reply + .number("d+,") // cell count + .number("(d+),") // mcc + .number("(d+),") // mnc + .expression("(") + .groupBegin() + .number("d+|") // lac + .number("d+|") // cid + .number("d+,") // rssi + .groupEnd("+") + .expression(")") + .number("d+,") // wifi count + .expression("(.*)") // wifi + .compile(); + + private Boolean decodeOptionalValue(Parser parser, int activeValue) { + int value = parser.nextInt(); + if (value != 0) { + return value == activeValue; + } + return null; + } + + private void decodeCommon(Position position, Parser parser) { + + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_BATTERY, parser.nextInt()); + position.set(Position.KEY_IGNITION, decodeOptionalValue(parser, 1)); + position.set(Position.KEY_ARMED, decodeOptionalValue(parser, 1)); + + int mode = parser.nextInt(); + if (mode != 0) { + position.set("mode", mode); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + String id = sentence.startsWith("TRV") ? sentence.substring(0, 3) : sentence.substring(0, 2); + String type = sentence.substring(id.length(), id.length() + 4); + + if (channel != null) { + String responseHeader = id + (char) (type.charAt(0) + 1) + type.substring(1); + if (type.equals("AP00") && id.equals("IW")) { + String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); + channel.writeAndFlush(new NetworkMessage(responseHeader + "," + time + ",0#", remoteAddress)); + } else if (type.equals("AP14")) { + channel.writeAndFlush(new NetworkMessage(responseHeader + ",0.000,0.000#", remoteAddress)); + } else { + channel.writeAndFlush(new NetworkMessage(responseHeader + "#", remoteAddress)); + } + } + + if (type.equals("AP00")) { + getDeviceSession(channel, remoteAddress, sentence.substring(id.length() + type.length())); + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + if (type.equals("CP01")) { + + Parser parser = new Parser(PATTERN_HEATRBEAT, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + decodeCommon(position, parser); + + if (parser.hasNext(3)) { + position.set(Position.KEY_BLOCKED, decodeOptionalValue(parser, 2)); + position.set(Position.KEY_CHARGE, decodeOptionalValue(parser, 1)); + position.set(Position.KEY_MOTION, decodeOptionalValue(parser, 1)); + } + + return position; + + } else if (type.equals("AP01") || type.equals("AP10") || type.equals("YP03")) { + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + + dateBuilder.setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + position.setCourse(parser.nextDouble()); + + decodeCommon(position, parser); + + position.setNetwork(new Network(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextInt()))); + + return position; + + } else if (type.equals("AP02")) { + + Parser parser = new Parser(PATTERN_LBS, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + int mcc = parser.nextInt(); + int mnc = parser.nextInt(); + + Network network = new Network(); + + for (String cell : parser.next().split(",")) { + if (!cell.isEmpty()) { + String[] values = cell.split("\\|"); + network.addCellTower(CellTower.from( + mcc, mnc, + Integer.parseInt(values[0]), + Integer.parseInt(values[1]), + Integer.parseInt(values[2]))); + } + } + + for (String wifi : parser.next().split("&")) { + if (!wifi.isEmpty()) { + String[] values = wifi.split("\\|"); + network.addWifiAccessPoint(WifiAccessPoint.from(values[1], Integer.parseInt(values[2]))); + } + } + + position.setNetwork(network); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Tt8850Protocol.java b/src/main/java/org/traccar/protocol/Tt8850Protocol.java new file mode 100644 index 000000000..66a13da9e --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tt8850Protocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Tt8850Protocol extends BaseProtocol { + + public Tt8850Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "$")); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new Tt8850ProtocolDecoder(Tt8850Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Tt8850ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Tt8850ProtocolDecoder.java new file mode 100644 index 000000000..1010528c4 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Tt8850ProtocolDecoder.java @@ -0,0 +1,99 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class Tt8850ProtocolDecoder extends BaseProtocolDecoder { + + public Tt8850ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .binary("0004,") + .number("xxxx,") + .expression("[01],") + .expression("GT...,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("([^,]+),") // imei + .any() + .number("(d{1,2})?,") // gps accuracy + .number("(d{1,3}.d)?,") // speed + .number("(d{1,3})?,") // course + .number("(-?d{1,5}.d)?,") // altitude + .number("(-?d{1,3}.d{6}),") // longitude + .number("(-?d{1,2}.d{6}),") // latitude + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(0ddd)?,") // mcc + .number("(0ddd)?,") // mnc + .number("(xxxx)?,") // lac + .number("(xxxx)?,") // cell + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(xxxx)") + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setValid(true); + position.setAccuracy(parser.nextInt(0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + position.setLatitude(parser.nextDouble(0)); + + position.setTime(parser.nextDateTime()); + + if (parser.hasNext(4)) { + position.setNetwork(new Network( + CellTower.from(parser.nextInt(0), parser.nextInt(0), parser.nextHexInt(0), parser.nextHexInt(0)))); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/TytanProtocol.java b/src/main/java/org/traccar/protocol/TytanProtocol.java new file mode 100644 index 000000000..32e9acae1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TytanProtocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class TytanProtocol extends BaseProtocol { + + public TytanProtocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new TytanProtocolDecoder(TytanProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TytanProtocolDecoder.java b/src/main/java/org/traccar/protocol/TytanProtocolDecoder.java new file mode 100644 index 000000000..93d3a63d2 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TytanProtocolDecoder.java @@ -0,0 +1,192 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class TytanProtocolDecoder extends BaseProtocolDecoder { + + public TytanProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private void decodeExtraData(Position position, ByteBuf buf, int end) { + while (buf.readerIndex() < end) { + + int type = buf.readUnsignedByte(); + int length = buf.readUnsignedByte(); + if (length == 255) { + length += buf.readUnsignedByte(); + } + + int n; + + switch (type) { + case 2: + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedMedium()); + break; + case 5: + position.set(Position.KEY_INPUT, buf.readUnsignedByte()); + break; + case 6: + n = buf.readUnsignedByte() >> 4; + if (n < 2) { + position.set(Position.PREFIX_ADC + n, buf.readFloat()); + } else { + position.set("di" + (n - 2), buf.readFloat()); + } + break; + case 7: + int alarm = buf.readUnsignedByte(); + buf.readUnsignedByte(); + if (BitUtil.check(alarm, 5)) { + position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); + } + break; + case 8: + position.set("antihijack", buf.readUnsignedByte()); + break; + case 9: + position.set("unauthorized", ByteBufUtil.hexDump(buf.readSlice(8))); + break; + case 10: + position.set("authorized", ByteBufUtil.hexDump(buf.readSlice(8))); + break; + case 24: + for (int i = 0; i < length / 2; i++) { + position.set(Position.PREFIX_TEMP + buf.readUnsignedByte(), buf.readByte()); + } + break; + case 28: + position.set(Position.KEY_AXLE_WEIGHT, buf.readUnsignedShort()); + buf.readUnsignedByte(); + break; + case 90: + position.set(Position.KEY_POWER, buf.readFloat()); + break; + case 101: + position.set(Position.KEY_OBD_SPEED, buf.readUnsignedByte()); + break; + case 102: + position.set(Position.KEY_RPM, buf.readUnsignedByte() * 50); + break; + case 107: + int fuel = buf.readUnsignedShort(); + int fuelFormat = fuel >> 14; + if (fuelFormat == 1) { + position.set("fuelValue", (fuel & 0x3fff) * 0.4 + "%"); + } else if (fuelFormat == 2) { + position.set("fuelValue", (fuel & 0x3fff) * 0.5 + " l"); + } else if (fuelFormat == 3) { + position.set("fuelValue", (fuel & 0x3fff) * -0.5 + " l"); + } + break; + case 108: + position.set(Position.KEY_OBD_ODOMETER, buf.readUnsignedInt() * 5); + break; + case 150: + position.set(Position.KEY_DOOR, buf.readUnsignedByte()); + break; + default: + buf.skipBytes(length); + break; + } + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // protocol + buf.readUnsignedShort(); // length + int index = buf.readUnsignedByte() >> 3; + + if (channel != null) { + ByteBuf response = Unpooled.copiedBuffer("^" + index, StandardCharsets.US_ASCII); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + String id = String.valueOf(buf.readUnsignedInt()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + while (buf.readableBytes() > 2) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + int end = buf.readerIndex() + buf.readUnsignedByte(); + + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + + int flags = buf.readUnsignedByte(); + position.set(Position.KEY_SATELLITES, BitUtil.from(flags, 2)); + position.setValid(BitUtil.to(flags, 2) > 0); + + // Latitude + double lat = buf.readUnsignedMedium(); + lat = lat * -180 / 16777216 + 90; + position.setLatitude(lat); + + // Longitude + double lon = buf.readUnsignedMedium(); + lon = lon * 360 / 16777216 - 180; + position.setLongitude(lon); + + // Status + flags = buf.readUnsignedByte(); + position.set(Position.KEY_IGNITION, BitUtil.check(flags, 0)); + position.set(Position.KEY_RSSI, BitUtil.between(flags, 2, 5)); + position.setCourse((BitUtil.from(flags, 5) * 45 + 180) % 360); + + // Speed + int speed = buf.readUnsignedByte(); + if (speed < 250) { + position.setSpeed(UnitsConverter.knotsFromKph(speed)); + } + + decodeExtraData(position, buf, end); + + positions.add(position); + } + + return positions; + } + +} diff --git a/src/main/java/org/traccar/protocol/TzoneProtocol.java b/src/main/java/org/traccar/protocol/TzoneProtocol.java new file mode 100644 index 000000000..6e855d138 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TzoneProtocol.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; + +public class TzoneProtocol extends BaseProtocol { + + public TzoneProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(256, 2, 2, 2, 0)); + pipeline.addLast(new TzoneProtocolDecoder(TzoneProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java b/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java new file mode 100644 index 000000000..4f6854098 --- /dev/null +++ b/src/main/java/org/traccar/protocol/TzoneProtocolDecoder.java @@ -0,0 +1,318 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.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 java.net.SocketAddress; + +public class TzoneProtocolDecoder extends BaseProtocolDecoder { + + public TzoneProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private String decodeAlarm(Short value) { + switch (value) { + case 0x01: + return Position.ALARM_SOS; + case 0x10: + return Position.ALARM_LOW_BATTERY; + case 0x11: + return Position.ALARM_OVERSPEED; + case 0x14: + return Position.ALARM_BRAKING; + case 0x15: + return Position.ALARM_ACCELERATION; + case 0x30: + return Position.ALARM_PARKING; + case 0x42: + return Position.ALARM_GEOFENCE_EXIT; + case 0x43: + return Position.ALARM_GEOFENCE_ENTER; + default: + return null; + } + } + + private boolean decodeGps(Position position, ByteBuf buf, int hardware) { + + int blockLength = buf.readUnsignedShort(); + int blockEnd = buf.readerIndex() + blockLength; + + if (blockLength < 22) { + return false; + } + + if (hardware == 0x413) { + buf.readUnsignedByte(); // status + } else { + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + } + + if (hardware == 0x413) { + position.setFixTime(new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()).getDate()); + } + + double lat; + double lon; + + if (hardware == 0x10A || hardware == 0x10B) { + lat = buf.readUnsignedInt() / 600000.0; + lon = buf.readUnsignedInt() / 600000.0; + } else { + lat = buf.readUnsignedInt() / 100000.0 / 60.0; + lon = buf.readUnsignedInt() / 100000.0 / 60.0; + } + + if (hardware == 0x413) { + + position.set(Position.KEY_HDOP, buf.readUnsignedShort() * 0.1); + + position.setAltitude(buf.readUnsignedShort()); + position.setCourse(buf.readUnsignedShort()); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort() * 0.1)); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + + } else { + + position.setFixTime(new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()).getDate()); + + position.setSpeed(buf.readUnsignedShort() * 0.01); + + position.set(Position.KEY_ODOMETER, buf.readUnsignedMedium()); + + int flags = buf.readUnsignedShort(); + position.setCourse(BitUtil.to(flags, 9)); + if (!BitUtil.check(flags, 10)) { + lat = -lat; + } + position.setLatitude(lat); + if (BitUtil.check(flags, 9)) { + lon = -lon; + } + position.setLongitude(lon); + position.setValid(BitUtil.check(flags, 11)); + + } + + buf.readerIndex(blockEnd); + + return true; + } + + private void decodeCards(Position position, ByteBuf buf) { + + int index = 1; + for (int i = 0; i < 4; i++) { + + int blockLength = buf.readUnsignedShort(); + int blockEnd = buf.readerIndex() + blockLength; + + if (blockLength > 0) { + + int count = buf.readUnsignedByte(); + for (int j = 0; j < count; j++) { + + int length = buf.readUnsignedByte(); + + boolean odd = length % 2 != 0; + if (odd) { + length += 1; + } + + String num = ByteBufUtil.hexDump(buf.readSlice(length / 2)); + + if (odd) { + num = num.substring(1); + } + + position.set("card" + index, num); + } + } + + buf.readerIndex(blockEnd); + } + + } + + private void decodePassengers(Position position, ByteBuf buf) { + + int blockLength = buf.readUnsignedShort(); + int blockEnd = buf.readerIndex() + blockLength; + + if (blockLength > 0) { + + position.set("passengersOn", buf.readUnsignedMedium()); + position.set("passengersOff", buf.readUnsignedMedium()); + + } + + buf.readerIndex(blockEnd); + + } + + private void decodeTags(Position position, ByteBuf buf) { + + int blockLength = buf.readUnsignedShort(); + int blockEnd = buf.readerIndex() + blockLength; + + if (blockLength > 0) { + + buf.readUnsignedByte(); // tag type + + int count = buf.readUnsignedByte(); + int tagLength = buf.readUnsignedByte(); + + for (int i = 1; i <= count; i++) { + int tagEnd = buf.readerIndex() + tagLength; + + buf.readUnsignedByte(); // status + buf.readUnsignedShortLE(); // battery voltage + + position.set(Position.PREFIX_TEMP + i, (buf.readShortLE() & 0x3fff) * 0.1); + + buf.readUnsignedByte(); // humidity + buf.readUnsignedByte(); // rssi + + buf.readerIndex(tagEnd); + } + + } + + buf.readerIndex(blockEnd); + + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + buf.readUnsignedShort(); // length + if (buf.readUnsignedShort() != 0x2424) { + return null; + } + int hardware = buf.readUnsignedShort(); + long firmware = buf.readUnsignedInt(); + + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(1); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_VERSION_HW, hardware); + position.set(Position.KEY_VERSION_FW, firmware); + + position.setDeviceTime(new DateBuilder() + .setDate(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()).getDate()); + + // GPS info + + if (hardware == 0x406 || !decodeGps(position, buf, hardware)) { + + getLastLocation(position, position.getDeviceTime()); + + } + + // LBS info + + int blockLength = buf.readUnsignedShort(); + int blockEnd = buf.readerIndex() + blockLength; + + if (blockLength > 0 && (hardware == 0x10A || hardware == 0x10B || hardware == 0x406)) { + position.setNetwork(new Network( + CellTower.fromLacCid(buf.readUnsignedShort(), buf.readUnsignedShort()))); + } + + buf.readerIndex(blockEnd); + + // Status info + + blockLength = buf.readUnsignedShort(); + blockEnd = buf.readerIndex() + blockLength; + + if (blockLength >= 13) { + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + position.set("terminalInfo", buf.readUnsignedByte()); + + int status = buf.readUnsignedByte(); + position.set(Position.PREFIX_OUT + 1, BitUtil.check(status, 0)); + position.set(Position.PREFIX_OUT + 2, BitUtil.check(status, 1)); + status = buf.readUnsignedByte(); + position.set(Position.PREFIX_IN + 1, BitUtil.check(status, 4)); + if (BitUtil.check(status, 0)) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + position.set("gsmStatus", buf.readUnsignedByte()); + position.set(Position.KEY_BATTERY, buf.readUnsignedShort()); + position.set(Position.KEY_POWER, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShort()); + } + + if (blockLength >= 15) { + position.set(Position.PREFIX_TEMP + 1, buf.readUnsignedShort()); + } + + buf.readerIndex(blockEnd); + + if (hardware == 0x10B) { + + decodeCards(position, buf); + + buf.skipBytes(buf.readUnsignedShort()); // temperature + buf.skipBytes(buf.readUnsignedShort()); // lock + + decodePassengers(position, buf); + + } + + if (hardware == 0x406) { + + decodeTags(position, buf); + + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/UlbotechFrameDecoder.java b/src/main/java/org/traccar/protocol/UlbotechFrameDecoder.java new file mode 100644 index 000000000..f141dc9b7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/UlbotechFrameDecoder.java @@ -0,0 +1,70 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseFrameDecoder; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; + +public class UlbotechFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 2) { + return null; + } + + if (buf.getUnsignedByte(buf.readerIndex()) == 0xF8) { + + int index = buf.indexOf(buf.readerIndex() + 1, buf.writerIndex(), (byte) 0xF8); + if (index != -1) { + ByteBuf result = Unpooled.buffer(index + 1 - buf.readerIndex()); + + while (buf.readerIndex() <= index) { + int b = buf.readUnsignedByte(); + if (b == 0xF7) { + int ext = buf.readUnsignedByte(); + if (ext == 0x00) { + result.writeByte(0xF7); + } else if (ext == 0x0F) { + result.writeByte(0xF8); + } + } else { + result.writeByte(b); + } + } + + return result; + } + + } else { + + int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '#'); + if (index != -1) { + return buf.readRetainedSlice(index + 1 - buf.readerIndex()); + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/UlbotechProtocol.java b/src/main/java/org/traccar/protocol/UlbotechProtocol.java new file mode 100644 index 000000000..b99ec1cc6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/UlbotechProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class UlbotechProtocol extends BaseProtocol { + + public UlbotechProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new UlbotechFrameDecoder()); + pipeline.addLast(new UlbotechProtocolDecoder(UlbotechProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/UlbotechProtocolDecoder.java b/src/main/java/org/traccar/protocol/UlbotechProtocolDecoder.java new file mode 100644 index 000000000..7fec0bf8b --- /dev/null +++ b/src/main/java/org/traccar/protocol/UlbotechProtocolDecoder.java @@ -0,0 +1,372 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.ObdDecoder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.regex.Pattern; + +public class UlbotechProtocolDecoder extends BaseProtocolDecoder { + + public UlbotechProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final short DATA_GPS = 0x01; + private static final short DATA_LBS = 0x02; + private static final short DATA_STATUS = 0x03; + private static final short DATA_ODOMETER = 0x04; + private static final short DATA_ADC = 0x05; + private static final short DATA_GEOFENCE = 0x06; + private static final short DATA_OBD2 = 0x07; + private static final short DATA_FUEL = 0x08; + private static final short DATA_OBD2_ALARM = 0x09; + private static final short DATA_HARSH_DRIVER = 0x0A; + private static final short DATA_CANBUS = 0x0B; + private static final short DATA_J1708 = 0x0C; + private static final short DATA_VIN = 0x0D; + private static final short DATA_RFID = 0x0E; + private static final short DATA_EVENT = 0x10; + + private void decodeObd(Position position, ByteBuf buf, int length) { + + int end = buf.readerIndex() + length; + + while (buf.readerIndex() < end) { + int parameterLength = buf.getUnsignedByte(buf.readerIndex()) >> 4; + int mode = buf.readUnsignedByte() & 0x0F; + position.add(ObdDecoder.decode(mode, ByteBufUtil.hexDump(buf.readSlice(parameterLength - 1)))); + } + } + + private void decodeJ1708(Position position, ByteBuf buf, int length) { + + int end = buf.readerIndex() + length; + + while (buf.readerIndex() < end) { + int mark = buf.readUnsignedByte(); + int len = BitUtil.between(mark, 0, 6); + int type = BitUtil.between(mark, 6, 8); + int id = buf.readUnsignedByte(); + if (type == 3) { + id += 256; + } + String value = ByteBufUtil.hexDump(buf.readSlice(len - 1)); + if (type == 2 || type == 3) { + position.set("pid" + id, value); + } + } + } + + private void decodeDriverBehavior(Position position, ByteBuf buf) { + + int value = buf.readUnsignedByte(); + + if (BitUtil.check(value, 0)) { + position.set("rapidAcceleration", true); + } + if (BitUtil.check(value, 1)) { + position.set("roughBraking", true); + } + if (BitUtil.check(value, 2)) { + position.set("harshCourse", true); + } + if (BitUtil.check(value, 3)) { + position.set("noWarmUp", true); + } + if (BitUtil.check(value, 4)) { + position.set("longIdle", true); + } + if (BitUtil.check(value, 5)) { + position.set("fatigueDriving", true); + } + if (BitUtil.check(value, 6)) { + position.set("roughTerrain", true); + } + if (BitUtil.check(value, 7)) { + position.set("highRpm", true); + } + } + + private String decodeAlarm(int alarm) { + if (BitUtil.check(alarm, 0)) { + return Position.ALARM_POWER_OFF; + } + if (BitUtil.check(alarm, 1)) { + return Position.ALARM_MOVEMENT; + } + if (BitUtil.check(alarm, 2)) { + return Position.ALARM_OVERSPEED; + } + if (BitUtil.check(alarm, 4)) { + return Position.ALARM_GEOFENCE; + } + if (BitUtil.check(alarm, 10)) { + return Position.ALARM_SOS; + } + return null; + } + + private void decodeAdc(Position position, ByteBuf buf, int length) { + for (int i = 0; i < length / 2; i++) { + int value = buf.readUnsignedShort(); + int id = BitUtil.from(value, 12); + value = BitUtil.to(value, 12); + switch (id) { + case 0: + position.set(Position.KEY_POWER, value * (100 + 10) / 4096.0 - 10); + break; + case 1: + position.set(Position.PREFIX_TEMP + 1, value * (125 + 55) / 4096.0 - 55); + break; + case 2: + position.set(Position.KEY_BATTERY, value * (100 + 10) / 4096.0 - 10); + break; + case 3: + position.set(Position.PREFIX_ADC + 1, value * (100 + 10) / 4096.0 - 10); + break; + default: + position.set(Position.PREFIX_IO + id, value); + break; + } + } + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("*TS") + .number("dd,") // protocol version + .number("(d{15}),") // device id + .number("(dd)(dd)(dd)") // time + .number("(dd)(dd)(dd),") // date + .expression("([^#]+)") // command + .text("#") + .compile(); + + private Object decodeText(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)) + .setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + getLastLocation(position, dateBuilder.getDate()); + + position.set(Position.KEY_RESULT, parser.next()); + + return position; + } + + private Object decodeBinary(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { + + buf.readUnsignedByte(); // header + buf.readUnsignedByte(); // version + buf.readUnsignedByte(); // type + + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(1); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + if (deviceSession.getTimeZone() == null) { + deviceSession.setTimeZone(getTimeZone(deviceSession.getDeviceId())); + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + long seconds = buf.readUnsignedInt() & 0x7fffffffL; + seconds += 946684800L; // 2000-01-01 00:00 + seconds -= deviceSession.getTimeZone().getRawOffset() / 1000; + Date time = new Date(seconds * 1000); + + boolean hasLocation = false; + + while (buf.readableBytes() > 3) { + + int type = buf.readUnsignedByte(); + int length = type == DATA_CANBUS ? buf.readUnsignedShort() : buf.readUnsignedByte(); + + switch (type) { + + case DATA_GPS: + hasLocation = true; + position.setLatitude(buf.readInt() / 1000000.0); + position.setLongitude(buf.readInt() / 1000000.0); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort())); + position.setCourse(buf.readUnsignedShort()); + int hdop = buf.readUnsignedShort(); + position.setValid(hdop < 9999); + position.set(Position.KEY_HDOP, hdop * 0.01); + break; + + case DATA_LBS: + if (length == 11) { + position.setNetwork(new Network(CellTower.from( + buf.readUnsignedShort(), buf.readUnsignedShort(), + buf.readUnsignedShort(), buf.readUnsignedInt(), -buf.readUnsignedByte()))); + } else { + position.setNetwork(new Network(CellTower.from( + buf.readUnsignedShort(), buf.readUnsignedShort(), + buf.readUnsignedShort(), buf.readUnsignedShort(), -buf.readUnsignedByte()))); + } + if (length > 9 && length != 11) { + buf.skipBytes(length - 9); + } + break; + + case DATA_STATUS: + int status = buf.readUnsignedShort(); + position.set(Position.KEY_IGNITION, BitUtil.check(status, 9)); + position.set(Position.KEY_STATUS, status); + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedShort())); + break; + + case DATA_ODOMETER: + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + break; + + case DATA_ADC: + decodeAdc(position, buf, length); + break; + + case DATA_GEOFENCE: + position.set("geofenceIn", buf.readUnsignedInt()); + position.set("geofenceAlarm", buf.readUnsignedInt()); + break; + + case DATA_OBD2: + decodeObd(position, buf, length); + break; + + case DATA_FUEL: + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedInt() / 10000.0); + break; + + case DATA_OBD2_ALARM: + decodeObd(position, buf, length); + break; + + case DATA_HARSH_DRIVER: + decodeDriverBehavior(position, buf); + break; + + case DATA_CANBUS: + position.set("can", ByteBufUtil.hexDump(buf.readSlice(length))); + break; + + case DATA_J1708: + decodeJ1708(position, buf, length); + break; + + case DATA_VIN: + position.set(Position.KEY_VIN, buf.readSlice(length).toString(StandardCharsets.US_ASCII)); + break; + + case DATA_RFID: + position.set(Position.KEY_DRIVER_UNIQUE_ID, + buf.readSlice(length - 1).toString(StandardCharsets.US_ASCII)); + position.set("authorized", buf.readUnsignedByte() != 0); + break; + + case DATA_EVENT: + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + if (length > 1) { + position.set("eventMask", buf.readUnsignedInt()); + } + break; + + default: + buf.skipBytes(length); + break; + } + } + + if (!hasLocation) { + getLastLocation(position, time); + } else { + position.setTime(time); + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (buf.getUnsignedByte(buf.readerIndex()) == 0xF8) { + + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(0xF8); + response.writeByte(DATA_GPS); + response.writeByte(0xFE); + response.writeShort(buf.getShort(response.writerIndex() - 1 - 2)); + response.writeShort(Checksum.crc16(Checksum.CRC16_XMODEM, response.nioBuffer(1, 4))); + response.writeByte(0xF8); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + + return decodeBinary(channel, remoteAddress, buf); + } else { + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(Unpooled.copiedBuffer(String.format("*TS01,ACK:%04X#", + Checksum.crc16(Checksum.CRC16_XMODEM, buf.nioBuffer(1, buf.writerIndex() - 2))), + StandardCharsets.US_ASCII), remoteAddress)); + } + + return decodeText(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII)); + } + } + +} diff --git a/src/main/java/org/traccar/protocol/UproProtocol.java b/src/main/java/org/traccar/protocol/UproProtocol.java new file mode 100644 index 000000000..4e60ffeb6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/UproProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class UproProtocol extends BaseProtocol { + + public UproProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new UproProtocolDecoder(UproProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/UproProtocolDecoder.java b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java new file mode 100644 index 000000000..a17003fc8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/UproProtocolDecoder.java @@ -0,0 +1,282 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +public class UproProtocolDecoder extends BaseProtocolDecoder { + + public UproProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_HEADER = new PatternBuilder() + .text("*") + .expression("(..20)") // head + .expression("([01])") // ack + .number("(d+),") // device id + .expression("(.)") // type + .expression("(.)") // subtype + .any() + .compile(); + + private static final Pattern PATTERN_LOCATION = new PatternBuilder() + .number("(dd)(dd)(dd)") // time (hhmmss) + .number("(dd)(dd)(dddd)") // latitude + .number("(ddd)(dd)(dddd)") // longitude + .number("(d)") // flags + .number("(dd)") // speed + .number("(dd)") // course + .number("(dd)(dd)(dd)") // date (ddmmyy) + .compile(); + + private void decodeLocation(Position position, String data) { + Parser parser = new Parser(PATTERN_LOCATION, data); + if (parser.matches()) { + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(true); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN)); + + int flags = parser.nextInt(0); + position.setValid(BitUtil.check(flags, 0)); + if (!BitUtil.check(flags, 1)) { + position.setLatitude(-position.getLatitude()); + } + if (!BitUtil.check(flags, 2)) { + position.setLongitude(-position.getLongitude()); + } + + position.setSpeed(parser.nextInt(0) * 2); + position.setCourse(parser.nextInt(0) * 10); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + } + } + + private String decodeAlarm(int alarm) { + if (BitUtil.check(alarm, 2)) { + return Position.ALARM_TAMPERING; + } + return null; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (buf.getByte(buf.readerIndex()) != '*') { + return null; + } + + int headerIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '&'); + if (headerIndex < 0) { + headerIndex = buf.writerIndex(); + } + String header = buf.readSlice(headerIndex - buf.readerIndex()).toString(StandardCharsets.US_ASCII); + + Parser parser = new Parser(PATTERN_HEADER, header); + if (!parser.matches()) { + return null; + } + + String head = parser.next(); + boolean reply = parser.next().equals("1"); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + String type = parser.next(); + String subtype = parser.next(); + + if (reply && channel != null) { + channel.writeAndFlush(new NetworkMessage("*" + head + "Y" + type + subtype + "#", remoteAddress)); + } + + while (buf.isReadable()) { + + buf.readByte(); // skip delimiter + + byte dataType = buf.readByte(); + + int delimiterIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '&'); + if (delimiterIndex < 0) { + delimiterIndex = buf.writerIndex(); + } + + ByteBuf data = buf.readSlice(delimiterIndex - buf.readerIndex()); + + switch (dataType) { + case 'A': + decodeLocation(position, data.toString(StandardCharsets.US_ASCII)); + break; + case 'B': + position.set(Position.KEY_STATUS, data.toString(StandardCharsets.US_ASCII)); + break; + case 'C': + long odometer = 0; + while (data.isReadable()) { + odometer <<= 4; + odometer += data.readByte() - (byte) '0'; + } + position.set(Position.KEY_ODOMETER, odometer * 2 * 1852 / 3600); + break; + case 'F': + position.setSpeed( + Integer.parseInt(data.readSlice(4).toString(StandardCharsets.US_ASCII)) * 0.1); + break; + case 'G': + position.setAltitude( + Integer.parseInt(data.readSlice(6).toString(StandardCharsets.US_ASCII)) * 0.1); + break; + case 'J': + if (data.readableBytes() == 6) { + char index = (char) data.readUnsignedByte(); + int status = data.readUnsignedByte(); + double value = Integer.parseInt(data.readSlice(4).toString(StandardCharsets.US_ASCII)) * 0.1; + if (BitUtil.check(status, 0)) { + value = -value; + } + position.set(Position.PREFIX_TEMP + index, value); + } + break; + case 'K': + position.set("statusExtended", data.toString(StandardCharsets.US_ASCII)); + break; + case 'M': + if (data.readableBytes() == 3) { + position.set(Position.KEY_BATTERY_LEVEL, + Integer.parseInt(data.readSlice(3).toString(StandardCharsets.US_ASCII)) * 0.1); + } else if (data.readableBytes() == 4) { + char index = (char) data.readUnsignedByte(); + data.readUnsignedByte(); // status + position.set( + "humidity" + index, + Integer.parseInt(data.readSlice(2).toString(StandardCharsets.US_ASCII))); + } + break; + case 'N': + position.set(Position.KEY_RSSI, + Integer.parseInt(data.readSlice(2).toString(StandardCharsets.US_ASCII))); + break; + case 'O': + position.set(Position.KEY_SATELLITES, + Integer.parseInt(data.readSlice(2).toString(StandardCharsets.US_ASCII))); + break; + case 'P': + if (data.readableBytes() >= 16) { + position.setNetwork(new Network(CellTower.from( + Integer.parseInt(data.readSlice(4).toString(StandardCharsets.US_ASCII)), + Integer.parseInt(data.readSlice(4).toString(StandardCharsets.US_ASCII)), + Integer.parseInt(data.readSlice(4).toString(StandardCharsets.US_ASCII), 16), + Integer.parseInt(data.readSlice(4).toString(StandardCharsets.US_ASCII), 16)))); + } + break; + case 'Q': + position.set("obdPid", ByteBufUtil.hexDump(data)); + break; + case 'R': + if (head.startsWith("HQ")) { + position.set(Position.KEY_RSSI, + Integer.parseInt(data.readSlice(2).toString(StandardCharsets.US_ASCII))); + position.set(Position.KEY_SATELLITES, + Integer.parseInt(data.readSlice(2).toString(StandardCharsets.US_ASCII))); + } else { + position.set("odbTravel", ByteBufUtil.hexDump(data)); + } + break; + case 'S': + position.set("obdTraffic", ByteBufUtil.hexDump(data)); + break; + case 'V': + position.set(Position.KEY_POWER, + Integer.parseInt(data.readSlice(4).toString(StandardCharsets.US_ASCII)) * 0.1); + break; + case 'W': + position.set(Position.KEY_ALARM, + decodeAlarm(Integer.parseInt(data.readSlice(2).toString(StandardCharsets.US_ASCII)))); + break; + case 'X': + Network network = new Network(); + int mcc = 0, mnc = 0; + String[] cells = data.toString(StandardCharsets.US_ASCII).split(";"); + if (!cells[0].startsWith("(")) { + for (int i = 0; i < cells.length; i++) { + String[] values = cells[i].split(","); + int index = 0; + if (i == 0) { + mcc = Integer.parseInt(values[index++]); + mnc = Integer.parseInt(values[index++]); + } + network.addCellTower(CellTower.from( + mcc, mnc, + Integer.parseInt(values[index++]), + Integer.parseInt(values[index++]), + Integer.parseInt(values[index]))); + } + position.setNetwork(network); + } + break; + case 'Y': + position.set(Position.KEY_POWER, + Integer.parseInt(data.readSlice(5).toString(StandardCharsets.US_ASCII)) * 0.001); + break; + default: + break; + } + + } + + if (position.getLatitude() == 0 || position.getLongitude() == 0) { + if (position.getAttributes().isEmpty()) { + return null; + } + getLastLocation(position, position.getDeviceTime()); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/V680Protocol.java b/src/main/java/org/traccar/protocol/V680Protocol.java new file mode 100644 index 000000000..dc0922cd4 --- /dev/null +++ b/src/main/java/org/traccar/protocol/V680Protocol.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class V680Protocol extends BaseProtocol { + + public V680Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "##")); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new V680ProtocolDecoder(V680Protocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new V680ProtocolDecoder(V680Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/V680ProtocolDecoder.java b/src/main/java/org/traccar/protocol/V680ProtocolDecoder.java new file mode 100644 index 000000000..0342404a6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/V680ProtocolDecoder.java @@ -0,0 +1,132 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.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 V680ProtocolDecoder extends BaseProtocolDecoder { + + public V680ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .groupBegin() + .number("#(d+)#") // imei + .expression("([^#]*)#") // user + .groupEnd("?") + .number("(d+)#") // fix + .expression("([^#]+)#") // password + .expression("([^#]+)#") // event + .number("(d+)#") // packet number + .expression("([^#]+)?#?") // gsm base station + .expression("(?:[^#]+#)?") + .number("(d+.d+),([EW]),") // longitude + .number("(d+.d+),([NS]),") // latitude + .number("(d+.d+),") // speed + .number("(d+.?d*)?#") // course + .number("(dd)(dd)(dd)#") // date (ddmmyy) + .number("(dd)(dd)(dd)") // time (hhmmss) + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + sentence = sentence.trim(); + + if (sentence.length() == 16) { + + getDeviceSession(channel, remoteAddress, sentence.substring(1, sentence.length())); + + } else { + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession; + if (parser.hasNext()) { + deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + } else { + deviceSession = getDeviceSession(channel, remoteAddress); + } + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.set("user", parser.next()); + position.setValid(parser.nextInt(0) > 0); + position.set("password", parser.next()); + position.set(Position.KEY_EVENT, parser.next()); + position.set("packet", parser.next()); + position.set("lbsData", parser.next()); + + double lon = parser.nextDouble(0); + boolean west = parser.next().equals("W"); + double lat = parser.nextDouble(0); + boolean south = parser.next().equals("S"); + + if (lat > 90 || lon > 180) { + int lonDegrees = (int) (lon * 0.01); + lon = (lon - lonDegrees * 100) / 60.0; + lon += lonDegrees; + + int latDegrees = (int) (lat * 0.01); + lat = (lat - latDegrees * 100) / 60.0; + lat += latDegrees; + } + + position.setLongitude(west ? -lon : lon); + position.setLatitude(south ? -lat : lat); + + position.setSpeed(parser.nextDouble(0)); + position.setCourse(parser.nextDouble(0)); + + int day = parser.nextInt(0); + int month = parser.nextInt(0); + if (day == 0 && month == 0) { + return null; // invalid date + } + + DateBuilder dateBuilder = new DateBuilder() + .setDate(parser.nextInt(0), month, day) + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + return position; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/VisiontekProtocol.java b/src/main/java/org/traccar/protocol/VisiontekProtocol.java new file mode 100644 index 000000000..2c6af45a8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/VisiontekProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class VisiontekProtocol extends BaseProtocol { + + public VisiontekProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '#')); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new VisiontekProtocolDecoder(VisiontekProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/VisiontekProtocolDecoder.java b/src/main/java/org/traccar/protocol/VisiontekProtocolDecoder.java new file mode 100644 index 000000000..c4787bda2 --- /dev/null +++ b/src/main/java/org/traccar/protocol/VisiontekProtocolDecoder.java @@ -0,0 +1,138 @@ +/* + * Copyright 2014 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class VisiontekProtocolDecoder extends BaseProtocolDecoder { + + public VisiontekProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("$1,") + .expression("([^,]+),") // identifier + .number("(d+),").optional() // imei + .number("(dd),(dd),(dd),") // date (dd,mm,yy) + .number("(dd),(dd),(dd),") // time (hh,mm,ss) + .groupBegin() + .number("(dd)(dd).?(d+)([NS]),") // latitude + .number("(ddd)(dd).?(d+)([EW]),") // longitude + .or() + .number("(dd.d+)([NS]),") // latitude + .number("(ddd.d+)([EW]),") // longitude + .groupEnd() + .number("(d+.?d+),") // speed + .number("(d+),") // course + .groupBegin() + .number("(d+),") // altitude + .number("(d+),") // satellites + .number("(d+),") // odometer + .number("([01]),") // ignition + .number("([01]),") // input 1 + .number("([01]),") // input 2 + .number("([01]),") // immobilizer + .number("([01]),") // external battery status + .number("(d+),") // gsm + .or() + .number("(d+.d),") // hdop + .number("(d+),") // altitude + .number("(d+),") // odometer + .number("([01],[01],[01],[01]),") // input + .number("([01],[01],[01],[01]),") // output + .number("(d+.?d*),") // adc 1 + .number("(d+.?d*),") // adc 2 + .groupEnd("?") + .any() + .expression("([AV])") // validity + .number(",(d{10})").optional() // rfid + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next(), parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + if (parser.hasNext(8)) { + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN_HEM)); + } + if (parser.hasNext(4)) { + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + } + + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble( + parser.next().replace(".", "")) / 10)); + + position.setCourse(parser.nextDouble(0)); + + if (parser.hasNext(9)) { + position.setAltitude(parser.nextDouble(0)); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_ODOMETER, parser.nextInt(0) * 1000); + position.set(Position.KEY_IGNITION, parser.next().equals("1")); + position.set(Position.PREFIX_IO + 1, parser.next()); + position.set(Position.PREFIX_IO + 2, parser.next()); + position.set("immobilizer", parser.next()); + position.set(Position.KEY_CHARGE, parser.next().equals("1")); + position.set(Position.KEY_RSSI, parser.nextDouble()); + } + + if (parser.hasNext(7)) { + position.set(Position.KEY_HDOP, parser.nextDouble()); + position.setAltitude(parser.nextDouble(0)); + position.set(Position.KEY_ODOMETER, parser.nextInt(0) * 1000); + position.set(Position.KEY_INPUT, parser.next()); + position.set(Position.KEY_OUTPUT, parser.next()); + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.PREFIX_ADC + 2, parser.next()); + } + + position.setValid(parser.next().equals("A")); + + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/VnetProtocol.java b/src/main/java/org/traccar/protocol/VnetProtocol.java new file mode 100644 index 000000000..0fed30e13 --- /dev/null +++ b/src/main/java/org/traccar/protocol/VnetProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; + +public class VnetProtocol extends BaseProtocol { + + public VnetProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1500, 4, 2, 12, 0, true)); + pipeline.addLast(new VnetProtocolDecoder(VnetProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/VnetProtocolDecoder.java b/src/main/java/org/traccar/protocol/VnetProtocolDecoder.java new file mode 100644 index 000000000..1ee00bd3f --- /dev/null +++ b/src/main/java/org/traccar/protocol/VnetProtocolDecoder.java @@ -0,0 +1,105 @@ +/* + * Copyright 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; + +public class VnetProtocolDecoder extends BaseProtocolDecoder { + + public VnetProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_LOGIN = 0x0000; + public static final int MSG_LBS = 0x32; + public static final int MSG_GPS = 0x33; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + int type = BitUtil.to(buf.readUnsignedShortLE(), 15); + buf.readUnsignedShortLE(); // length + + DateBuilder dateBuilder = new DateBuilder() + .setDateReverse(BcdUtil.readInteger(buf, 2), BcdUtil.readInteger(buf, 2), BcdUtil.readInteger(buf, 2)) + .setTime(BcdUtil.readInteger(buf, 2), BcdUtil.readInteger(buf, 2), BcdUtil.readInteger(buf, 2)); + + if (type == MSG_LOGIN) { + + String imei = ByteBufUtil.hexDump(buf.readSlice(8)).substring(0, 15); + getDeviceSession(channel, remoteAddress, imei); + if (channel != null) { + channel.writeAndFlush(new NetworkMessage( + buf.retainedSlice(0, buf.writerIndex()), channel.remoteAddress())); + } + + } else if (type == MSG_GPS) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + position.setTime(dateBuilder.getDate()); + + int value; + int degrees; + + value = BcdUtil.readInteger(buf, 8); + degrees = value / 1000000; + double lat = degrees + value % 1000000 * 0.0001 / 60; + + value = BcdUtil.readInteger(buf, 10); + degrees = value / 10000000; + double lon = degrees + value % 10000000 * 0.00001 / 60; + + int flags = buf.readUnsignedByte(); + position.setValid(BitUtil.check(flags, 0)); + position.setLatitude(BitUtil.check(flags, 1) ? lat : -lat); + position.setLongitude(BitUtil.check(flags, 2) ? lon : -lon); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.set(Position.KEY_ODOMETER, buf.readUnsignedIntLE()); + position.setCourse(buf.readUnsignedByte() * 2); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Vt200FrameDecoder.java b/src/main/java/org/traccar/protocol/Vt200FrameDecoder.java new file mode 100644 index 000000000..0fd83e715 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Vt200FrameDecoder.java @@ -0,0 +1,52 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseFrameDecoder; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; + +public class Vt200FrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ')') + 1; + if (endIndex > 0) { + + ByteBuf frame = Unpooled.buffer(); + + while (buf.readerIndex() < endIndex) { + int b = buf.readByte(); + if (b == '=') { + frame.writeByte(buf.readByte() ^ '='); + } else { + frame.writeByte(b); + } + } + + return frame; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/Vt200Protocol.java b/src/main/java/org/traccar/protocol/Vt200Protocol.java new file mode 100644 index 000000000..2a9ef6ab5 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Vt200Protocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Vt200Protocol extends BaseProtocol { + + public Vt200Protocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new Vt200FrameDecoder()); + pipeline.addLast(new Vt200ProtocolDecoder(Vt200Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Vt200ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Vt200ProtocolDecoder.java new file mode 100644 index 000000000..b1564abd9 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Vt200ProtocolDecoder.java @@ -0,0 +1,150 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BcdUtil; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Arrays; +import java.util.Date; + +public class Vt200ProtocolDecoder extends BaseProtocolDecoder { + + public Vt200ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static double decodeCoordinate(int value) { + int degrees = value / 1000000; + int minutes = value % 1000000; + return degrees + minutes * 0.0001 / 60; + } + + protected Date decodeDate(ByteBuf buf) { + DateBuilder dateBuilder = new DateBuilder() + .setDateReverse(BcdUtil.readInteger(buf, 2), BcdUtil.readInteger(buf, 2), BcdUtil.readInteger(buf, 2)) + .setTime(BcdUtil.readInteger(buf, 2), BcdUtil.readInteger(buf, 2), BcdUtil.readInteger(buf, 2)); + return dateBuilder.getDate(); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(1); // header + + String id = ByteBufUtil.hexDump(buf.readSlice(6)); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + int type = buf.readUnsignedShort(); + buf.readUnsignedShort(); // length + + if (type == 0x2086 || type == 0x2084 || type == 0x2082) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + buf.readUnsignedByte(); // data type + buf.readUnsignedShort(); // trip id + + position.setTime(decodeDate(buf)); + + position.setLatitude(decodeCoordinate(BcdUtil.readInteger(buf, 8))); + position.setLongitude(decodeCoordinate(BcdUtil.readInteger(buf, 9))); + + int flags = buf.readUnsignedByte(); + position.setValid(BitUtil.check(flags, 0)); + if (!BitUtil.check(flags, 1)) { + position.setLatitude(-position.getLatitude()); + } + if (!BitUtil.check(flags, 2)) { + position.setLongitude(-position.getLongitude()); + } + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.setCourse(buf.readUnsignedByte() * 2); + + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt() * 1000); + position.set(Position.KEY_STATUS, buf.readUnsignedInt()); + + // additional data + + return position; + + } else if (type == 0x3088) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + buf.readUnsignedShort(); // trip id + buf.skipBytes(8); // imei + buf.skipBytes(8); // imsi + + position.set("tripStart", decodeDate(buf).getTime()); + position.set("tripEnd", decodeDate(buf).getTime()); + position.set("drivingTime", buf.readUnsignedShort()); + + position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedInt()); + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedInt()); + + position.set("maxSpeed", UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + position.set("maxRpm", buf.readUnsignedShort()); + position.set("maxTemp", buf.readUnsignedByte() - 40); + position.set("hardAccelerationCount", buf.readUnsignedByte()); + position.set("hardBrakingCount", buf.readUnsignedByte()); + + for (String speedType : Arrays.asList("over", "high", "normal", "low")) { + position.set(speedType + "SpeedTime", buf.readUnsignedShort()); + position.set(speedType + "SpeedDistance", buf.readUnsignedInt()); + position.set(speedType + "SpeedFuel", buf.readUnsignedInt()); + } + + position.set("idleTime", buf.readUnsignedShort()); + position.set("idleFuel", buf.readUnsignedInt()); + + position.set("hardCorneringCount", buf.readUnsignedByte()); + position.set("overspeedCount", buf.readUnsignedByte()); + position.set("overheatCount", buf.readUnsignedShort()); + position.set("laneChangeCount", buf.readUnsignedByte()); + position.set("emergencyRefueling", buf.readUnsignedByte()); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/VtfmsFrameDecoder.java b/src/main/java/org/traccar/protocol/VtfmsFrameDecoder.java new file mode 100644 index 000000000..62a189960 --- /dev/null +++ b/src/main/java/org/traccar/protocol/VtfmsFrameDecoder.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseFrameDecoder; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; + +public class VtfmsFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ')'); + if (endIndex > 0) { + endIndex += 1 + 3; + if (buf.writerIndex() >= endIndex) { + return buf.readRetainedSlice(endIndex - buf.readerIndex()); + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/VtfmsProtocol.java b/src/main/java/org/traccar/protocol/VtfmsProtocol.java new file mode 100644 index 000000000..2826a86e6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/VtfmsProtocol.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import io.netty.handler.codec.string.StringDecoder; + +public class VtfmsProtocol extends BaseProtocol { + + public VtfmsProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new VtfmsFrameDecoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new VtfmsProtocolDecoder(VtfmsProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new VtfmsProtocolDecoder(VtfmsProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/VtfmsProtocolDecoder.java b/src/main/java/org/traccar/protocol/VtfmsProtocolDecoder.java new file mode 100644 index 000000000..17fac4311 --- /dev/null +++ b/src/main/java/org/traccar/protocol/VtfmsProtocolDecoder.java @@ -0,0 +1,167 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class VtfmsProtocolDecoder extends BaseProtocolDecoder { + + private static final String[] DIRECTIONS = new String[] {"N", "NE", "E", "SE", "S", "SW", "W", "NW"}; + + public VtfmsProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("(") + .number("(d{15}),") // imei + .number("[0-9A-Z]{3}dd,") // packet count + .number("(dd),") // packet id + .number("[^,]*,") // reserved + .number("(d+)?,") // rssi + .number("(?:d+)?,") // fix status + .number("(d+)?,") // satellites + .number("[^,]*,") // reserved + .expression("([AV]),") // validity + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(dd)(dd)(dd),") // time (ddmmyy) + .number("(-?d+.d+),") // latitude + .number("(-?d+.d+),") // longitude + .number("(?:(d+)|([NESW]{1,2})),") // course + .number("(d+),") // speed + .number("(d+),") // hours + .number("(d+),") // idle hours + .expression("[KNT],") // antenna status + .number("(d+),") // odometer + .expression("([01]),") // power status + .number("(d+.d+),") // power voltage + .number("[^,]*,") // reserved + .number("(d+)?,") // fuel level + .number("(d+.d+)?,") // adc 1 + .number("[^,]*,") // reserved + .number("(d+.d+)?,") // adc 2 + .expression("([01]),") // di 1 + .expression("([01]),") // di 2 + .expression("([01]),") // di 3 + .expression("([01]),") // di 4 + .expression("([01]),") // do 1 + .expression("([01]),") // do 2 + .expression("([01]),") // do 3 + .number("[^,]*,") // reserved + .number("[^,]*") // reserved + .text(")") + .number("ddd") // checksum + .compile(); + + private String decodeAlarm(int value) { + switch (value) { + case 10: + return Position.ALARM_OVERSPEED; + case 14: + return Position.ALARM_POWER_CUT; + case 15: + return Position.ALARM_POWER_RESTORED; + case 32: + return Position.ALARM_BRAKING; + case 33: + return Position.ALARM_ACCELERATION; + default: + return null; + } + } + + private double convertToDegrees(double value) { + double degrees = Math.floor(value / 100); + return degrees + (value - degrees * 100) / 60; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_ALARM, decodeAlarm(parser.nextInt())); + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + + position.setValid(parser.next().equals("A")); + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY)); + + double latitude = parser.nextDouble(); + double longitude = parser.nextDouble(); + if (Math.abs(latitude) > 90 || Math.abs(longitude) > 180) { + position.setLatitude(convertToDegrees(latitude)); + position.setLongitude(convertToDegrees(longitude)); + } else { + position.setLatitude(latitude); + position.setLongitude(longitude); + } + + position.setCourse(parser.nextDouble(0)); + if (parser.hasNext()) { + String direction = parser.next(); + for (int i = 0; i < DIRECTIONS.length; i++) { + if (direction.equals(DIRECTIONS[i])) { + position.setCourse(i * 45.0); + break; + } + } + } + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + + position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(parser.nextInt())); + position.set("idleHours", parser.nextInt()); + position.set(Position.KEY_ODOMETER, parser.nextInt() * 100); + position.set(Position.KEY_CHARGE, parser.next().equals("1")); + position.set(Position.KEY_POWER, parser.nextDouble()); + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.PREFIX_ADC + 2, parser.nextDouble()); + position.set(Position.PREFIX_IN + 1, parser.nextInt()); + position.set(Position.PREFIX_IN + 2, parser.nextInt()); + position.set(Position.PREFIX_IN + 3, parser.nextInt()); + position.set(Position.PREFIX_IN + 4, parser.nextInt()); + position.set(Position.PREFIX_OUT + 1, parser.nextInt()); + position.set(Position.PREFIX_OUT + 2, parser.nextInt()); + position.set(Position.PREFIX_OUT + 3, parser.nextInt()); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/WatchFrameDecoder.java b/src/main/java/org/traccar/protocol/WatchFrameDecoder.java new file mode 100644 index 000000000..f99bd52e2 --- /dev/null +++ b/src/main/java/org/traccar/protocol/WatchFrameDecoder.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class WatchFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ']') + 1; + if (endIndex > 0) { + ByteBuf frame = Unpooled.buffer(); + while (buf.readerIndex() < endIndex) { + byte b1 = buf.readByte(); + if (b1 == '}') { + byte b2 = buf.readByte(); + switch (b2) { + case 0x01: + frame.writeByte('}'); + break; + case 0x02: + frame.writeByte('['); + break; + case 0x03: + frame.writeByte(']'); + break; + case 0x04: + frame.writeByte(','); + break; + case 0x05: + frame.writeByte('*'); + break; + default: + throw new IllegalArgumentException(String.format( + "unexpected byte at %d: 0x%02x", buf.readerIndex() - 1, b2)); + } + } else { + frame.writeByte(b1); + } + } + return frame; + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/WatchProtocol.java b/src/main/java/org/traccar/protocol/WatchProtocol.java new file mode 100644 index 000000000..6dc3bf9fb --- /dev/null +++ b/src/main/java/org/traccar/protocol/WatchProtocol.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +public class WatchProtocol extends BaseProtocol { + + public WatchProtocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_POSITION_SINGLE, + Command.TYPE_POSITION_PERIODIC, + Command.TYPE_SOS_NUMBER, + Command.TYPE_ALARM_SOS, + Command.TYPE_ALARM_BATTERY, + Command.TYPE_REBOOT_DEVICE, + Command.TYPE_POWER_OFF, + Command.TYPE_ALARM_REMOVE, + Command.TYPE_SILENCE_TIME, + Command.TYPE_ALARM_CLOCK, + Command.TYPE_SET_PHONEBOOK, + Command.TYPE_MESSAGE, + Command.TYPE_VOICE_MESSAGE, + Command.TYPE_SET_TIMEZONE, + Command.TYPE_SET_INDICATOR); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new WatchFrameDecoder()); + pipeline.addLast(new WatchProtocolEncoder(WatchProtocol.this)); + pipeline.addLast(new WatchProtocolDecoder(WatchProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java b/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java new file mode 100644 index 000000000..0647afdee --- /dev/null +++ b/src/main/java/org/traccar/protocol/WatchProtocolDecoder.java @@ -0,0 +1,334 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.regex.Pattern; + +public class WatchProtocolDecoder extends BaseProtocolDecoder { + + private static final Logger LOGGER = LoggerFactory.getLogger(WatchProtocolDecoder.class); + + public WatchProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_POSITION = new PatternBuilder() + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([AV]),") // validity + .number(" *(-?d+.d+),") // latitude + .expression("([NS]),") + .number(" *(-?d+.d+),") // longitude + .expression("([EW])?,") + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .number("(d+.?d*),") // altitude + .number("(d+),") // satellites + .number("(d+),") // rssi + .number("(d+),") // battery + .number("(d+),") // steps + .number("d+,") // tumbles + .number("(x+),") // status + .expression("(.*)") // cell and wifi + .compile(); + + private void sendResponse(Channel channel, String id, String index, String content) { + if (channel != null) { + String response; + if (index != null) { + response = String.format("[%s*%s*%s*%04x*%s]", + manufacturer, id, index, content.length(), content); + } else { + response = String.format("[%s*%s*%04x*%s]", + manufacturer, id, content.length(), content); + } + ByteBuf buf = Unpooled.copiedBuffer(response, StandardCharsets.US_ASCII); + channel.writeAndFlush(new NetworkMessage(buf, channel.remoteAddress())); + } + } + + private String decodeAlarm(int status) { + if (BitUtil.check(status, 0)) { + return Position.ALARM_LOW_BATTERY; + } else if (BitUtil.check(status, 1)) { + return Position.ALARM_GEOFENCE_EXIT; + } else if (BitUtil.check(status, 2)) { + return Position.ALARM_GEOFENCE_ENTER; + } else if (BitUtil.check(status, 3)) { + return Position.ALARM_OVERSPEED; + } else if (BitUtil.check(status, 16)) { + return Position.ALARM_SOS; + } else if (BitUtil.check(status, 17)) { + return Position.ALARM_LOW_BATTERY; + } else if (BitUtil.check(status, 18)) { + return Position.ALARM_GEOFENCE_EXIT; + } else if (BitUtil.check(status, 19)) { + return Position.ALARM_GEOFENCE_ENTER; + } else if (BitUtil.check(status, 20)) { + return Position.ALARM_REMOVING; + } else if (BitUtil.check(status, 21)) { + return Position.ALARM_FALL_DOWN; + } + return null; + } + + private Position decodePosition(DeviceSession deviceSession, String data) { + + Parser parser = new Parser(PATTERN_POSITION, data); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + position.set(Position.KEY_SATELLITES, parser.nextInt(0)); + position.set(Position.KEY_RSSI, parser.nextInt(0)); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt(0)); + + position.set(Position.KEY_STEPS, parser.nextInt(0)); + + int status = parser.nextHexInt(0); + position.set(Position.KEY_ALARM, decodeAlarm(status)); + if (BitUtil.check(status, 4)) { + position.set(Position.KEY_MOTION, true); + } + + String[] values = parser.next().split(","); + int index = 0; + + Network network = new Network(); + + int cellCount = Integer.parseInt(values[index++]); + index += 1; // timing advance + int mcc = Integer.parseInt(values[index++]); + int mnc = Integer.parseInt(values[index++]); + + for (int i = 0; i < cellCount; i++) { + network.addCellTower(CellTower.from(mcc, mnc, + Integer.parseInt(values[index++]), Integer.parseInt(values[index++]), + Integer.parseInt(values[index++]))); + } + + if (index < values.length && !values[index].isEmpty()) { + int wifiCount = Integer.parseInt(values[index++]); + + for (int i = 0; i < wifiCount; i++) { + index += 1; // wifi name + network.addWifiAccessPoint(WifiAccessPoint.from( + values[index++], Integer.parseInt(values[index++]))); + } + } + + if (network.getCellTowers() != null || network.getWifiAccessPoints() != null) { + position.setNetwork(network); + } + + return position; + } + + private boolean hasIndex; + private String manufacturer; + + public boolean getHasIndex() { + return hasIndex; + } + + public String getManufacturer() { + return manufacturer; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(1); // '[' header + manufacturer = buf.readSlice(2).toString(StandardCharsets.US_ASCII); + buf.skipBytes(1); // '*' delimiter + + int idIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '*'); + String id = buf.readSlice(idIndex - buf.readerIndex()).toString(StandardCharsets.US_ASCII); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + buf.skipBytes(1); // '*' delimiter + + String index = null; + int contentIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '*'); + if (contentIndex + 5 < buf.writerIndex() && buf.getByte(contentIndex + 5) == '*' + && buf.toString(contentIndex + 1, 4, StandardCharsets.US_ASCII).matches("\\p{XDigit}+")) { + int indexLength = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '*') - buf.readerIndex(); + hasIndex = true; + index = buf.readSlice(indexLength).toString(StandardCharsets.US_ASCII); + buf.skipBytes(1); // '*' delimiter + } + + buf.skipBytes(4); // length + buf.skipBytes(1); // '*' delimiter + + buf.writerIndex(buf.writerIndex() - 1); // ']' ignore ending + + contentIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ','); + if (contentIndex < 0) { + contentIndex = buf.writerIndex(); + } + + String type = buf.readSlice(contentIndex - buf.readerIndex()).toString(StandardCharsets.US_ASCII); + + if (contentIndex < buf.writerIndex()) { + buf.readerIndex(contentIndex + 1); + } + + if (type.equals("INIT")) { + + sendResponse(channel, id, index, "INIT,1"); + + } else if (type.equals("LK")) { + + sendResponse(channel, id, index, "LK"); + + if (buf.isReadable()) { + String[] values = buf.toString(StandardCharsets.US_ASCII).split(","); + if (values.length >= 3) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(values[2])); + + return position; + } + } + + } else if (type.startsWith("UD") || type.equals("AL") || type.equals("WT")) { + + Position position = decodePosition(deviceSession, buf.toString(StandardCharsets.US_ASCII)); + + if (type.equals("AL")) { + if (position != null) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + sendResponse(channel, id, index, "AL"); + } + + return position; + + } else if (type.equals("TKQ")) { + + sendResponse(channel, id, index, "TKQ"); + + } else if (type.equalsIgnoreCase("PULSE") + || type.equalsIgnoreCase("HEART") + || type.equalsIgnoreCase("BLOOD") + || type.equalsIgnoreCase("BPHRT")) { + + if (buf.isReadable()) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, new Date()); + + String[] values = buf.toString(StandardCharsets.US_ASCII).split(","); + int valueIndex = 0; + + if (type.equalsIgnoreCase("BPHRT") || type.equalsIgnoreCase("BLOOD")) { + position.set("pressureHigh", values[valueIndex++]); + position.set("pressureLow", values[valueIndex++]); + } + + if (valueIndex <= values.length - 1) { + position.set(Position.KEY_HEART_RATE, Integer.parseInt(values[valueIndex])); + } + + return position; + + } + + } else if (type.equals("img")) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + int timeIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) ','); + buf.readerIndex(timeIndex + 12 + 2); + position.set(Position.KEY_IMAGE, Context.getMediaManager().writeFile(id, buf, "jpg")); + + return position; + + } else if (type.equals("TK")) { + + if (buf.readableBytes() == 1) { + byte result = buf.readByte(); + if (result != '1') { + LOGGER.warn(type + "," + result); + } + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_AUDIO, Context.getMediaManager().writeFile(id, buf, "amr")); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/WatchProtocolEncoder.java b/src/main/java/org/traccar/protocol/WatchProtocolEncoder.java new file mode 100644 index 000000000..f285267ba --- /dev/null +++ b/src/main/java/org/traccar/protocol/WatchProtocolEncoder.java @@ -0,0 +1,172 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.StringProtocolEncoder; +import org.traccar.helper.DataConverter; +import org.traccar.model.Command; +import org.traccar.Protocol; + +import java.nio.charset.StandardCharsets; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +public class WatchProtocolEncoder extends StringProtocolEncoder implements StringProtocolEncoder.ValueFormatter { + + public WatchProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + public String formatValue(String key, Object value) { + if (key.equals(Command.KEY_TIMEZONE)) { + double offset = TimeZone.getTimeZone((String) value).getRawOffset() / 3600000.0; + DecimalFormat fmt = new DecimalFormat("+#.##;-#.##", DecimalFormatSymbols.getInstance(Locale.US)); + return fmt.format(offset); + } else if (key.equals(Command.KEY_MESSAGE)) { + return DataConverter.printHex(value.toString().getBytes(StandardCharsets.UTF_16BE)); + } else if (key.equals(Command.KEY_ENABLE)) { + return (boolean) value ? "1" : "0"; + } + + return null; + } + + protected ByteBuf formatTextCommand(Channel channel, Command command, String format, String... keys) { + String content = formatCommand(command, format, this, keys); + ByteBuf buf = Unpooled.copiedBuffer(content, StandardCharsets.US_ASCII); + + return formatBinaryCommand(channel, command, "", buf); + } + + protected ByteBuf formatBinaryCommand(Channel channel, Command command, String textPrefix, ByteBuf data) { + boolean hasIndex = false; + String manufacturer = "CS"; + if (channel != null) { + WatchProtocolDecoder decoder = channel.pipeline().get(WatchProtocolDecoder.class); + if (decoder != null) { + hasIndex = decoder.getHasIndex(); + manufacturer = decoder.getManufacturer(); + } + } + + ByteBuf buf = Unpooled.buffer(); + buf.writeByte('['); + buf.writeCharSequence(manufacturer, StandardCharsets.US_ASCII); + buf.writeByte('*'); + buf.writeCharSequence(getUniqueId(command.getDeviceId()), StandardCharsets.US_ASCII); + buf.writeByte('*'); + if (hasIndex) { + buf.writeCharSequence("0001", StandardCharsets.US_ASCII); + buf.writeByte('*'); + } + buf.writeCharSequence(String.format("%04x", data.readableBytes() + textPrefix.length()), + StandardCharsets.US_ASCII); + buf.writeByte('*'); + buf.writeCharSequence(textPrefix, StandardCharsets.US_ASCII); + buf.writeBytes(data); + buf.writeByte(']'); + + return buf; + } + + private static Map<Byte, Byte> mapping = new HashMap<>(); + + static { + mapping.put((byte) 0x7d, (byte) 0x01); + mapping.put((byte) 0x5B, (byte) 0x02); + mapping.put((byte) 0x5D, (byte) 0x03); + mapping.put((byte) 0x2C, (byte) 0x04); + mapping.put((byte) 0x2A, (byte) 0x05); + } + + private ByteBuf getBinaryData(Command command) { + byte[] data = DataConverter.parseHex(command.getString(Command.KEY_DATA)); + + int encodedLength = data.length; + for (byte b : data) { + if (mapping.containsKey(b)) { + encodedLength += 1; + } + } + + int index = 0; + byte[] encodedData = new byte[encodedLength]; + + for (byte b : data) { + Byte replacement = mapping.get(b); + if (replacement != null) { + encodedData[index] = 0x7D; + index += 1; + encodedData[index] = replacement; + } else { + encodedData[index] = b; + } + index += 1; + } + + return Unpooled.copiedBuffer(encodedData); + } + + @Override + protected Object encodeCommand(Channel channel, Command command) { + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return formatTextCommand(channel, command, command.getString(Command.KEY_DATA)); + case Command.TYPE_POSITION_SINGLE: + return formatTextCommand(channel, command, "RG"); + case Command.TYPE_SOS_NUMBER: + return formatTextCommand(channel, command, "SOS%s,%s", Command.KEY_INDEX, Command.KEY_PHONE); + case Command.TYPE_ALARM_SOS: + return formatTextCommand(channel, command, "SOSSMS,%s", Command.KEY_ENABLE); + case Command.TYPE_ALARM_BATTERY: + return formatTextCommand(channel, command, "LOWBAT,%s", Command.KEY_ENABLE); + case Command.TYPE_REBOOT_DEVICE: + return formatTextCommand(channel, command, "RESET"); + case Command.TYPE_POWER_OFF: + return formatTextCommand(channel, command, "POWEROFF"); + case Command.TYPE_ALARM_REMOVE: + return formatTextCommand(channel, command, "REMOVE,%s", Command.KEY_ENABLE); + case Command.TYPE_SILENCE_TIME: + return formatTextCommand(channel, command, "SILENCETIME,%s", Command.KEY_DATA); + case Command.TYPE_ALARM_CLOCK: + return formatTextCommand(channel, command, "REMIND,%s", Command.KEY_DATA); + case Command.TYPE_SET_PHONEBOOK: + return formatTextCommand(channel, command, "PHB,%s", Command.KEY_DATA); + case Command.TYPE_MESSAGE: + return formatTextCommand(channel, command, "MESSAGE,%s", Command.KEY_MESSAGE); + case Command.TYPE_VOICE_MESSAGE: + return formatBinaryCommand(channel, command, "TK,", getBinaryData(command)); + case Command.TYPE_POSITION_PERIODIC: + return formatTextCommand(channel, command, "UPLOAD,%s", Command.KEY_FREQUENCY); + case Command.TYPE_SET_TIMEZONE: + return formatTextCommand(channel, command, "LZ,%s,%s", Command.KEY_LANGUAGE, Command.KEY_TIMEZONE); + case Command.TYPE_SET_INDICATOR: + return formatTextCommand(channel, command, "FLOWER,%s", Command.KEY_DATA); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/WialonProtocol.java b/src/main/java/org/traccar/protocol/WialonProtocol.java new file mode 100644 index 000000000..fd183dd2c --- /dev/null +++ b/src/main/java/org/traccar/protocol/WialonProtocol.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.Context; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; + +import java.nio.charset.StandardCharsets; +public class WialonProtocol extends BaseProtocol { + + public WialonProtocol() { + setSupportedDataCommands( + Command.TYPE_REBOOT_DEVICE, + Command.TYPE_SEND_USSD, + Command.TYPE_IDENTIFICATION, + Command.TYPE_OUTPUT_CONTROL); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(4 * 1024)); + boolean utf8 = Context.getConfig().getBoolean(getName() + ".utf8"); + if (utf8) { + pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8)); + pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8)); + } else { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + } + pipeline.addLast(new WialonProtocolEncoder(WialonProtocol.this)); + pipeline.addLast(new WialonProtocolDecoder(WialonProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(4 * 1024)); + boolean utf8 = Context.getConfig().getBoolean(getName() + ".utf8"); + if (utf8) { + pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8)); + pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8)); + } else { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + } + pipeline.addLast(new WialonProtocolEncoder(WialonProtocol.this)); + pipeline.addLast(new WialonProtocolDecoder(WialonProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java b/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java new file mode 100644 index 000000000..8488ca2d1 --- /dev/null +++ b/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java @@ -0,0 +1,223 @@ +/* + * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class WialonProtocolDecoder extends BaseProtocolDecoder { + + public WialonProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN_ANY = new PatternBuilder() + .expression("([^#]*)?") // imei + .text("#") // start byte + .expression("([^#]+)") // type + .text("#") // separator + .expression("(.*)") // message + .compile(); + + private static final Pattern PATTERN = new PatternBuilder() + .number("(dd)(dd)(dd);") // date (ddmmyy) + .number("(dd)(dd)(dd);") // time (hhmmss) + .number("(dd)(dd.d+);") // latitude + .expression("([NS]);") + .number("(ddd)(dd.d+);") // longitude + .expression("([EW]);") + .number("(d+.?d*)?;") // speed + .number("(d+.?d*)?;") // course + .number("(?:NA|(-?d+.?d*));") // altitude + .number("(?:NA|(d+))") // satellites + .groupBegin().text(";") + .number("(?:NA|(d+.?d*));") // hdop + .number("(?:NA|(d+));") // inputs + .number("(?:NA|(d+));") // outputs + .expression("(?:NA|([^;]*));") // adc + .expression("(?:NA|([^;]*));") // ibutton + .expression("(?:NA|(.*))") // params + .groupEnd("?") + .compile(); + + private void sendResponse(Channel channel, SocketAddress remoteAddress, String type, Integer number) { + if (channel != null) { + StringBuilder response = new StringBuilder("#A"); + response.append(type); + response.append("#"); + if (number != null) { + response.append(number); + } + response.append("\r\n"); + channel.writeAndFlush(new NetworkMessage(response.toString(), remoteAddress)); + } + } + + private Position decodePosition(Channel channel, SocketAddress remoteAddress, String id, String substring) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + Parser parser = new Parser(PATTERN, substring); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + if (parser.hasNext()) { + int satellites = parser.nextInt(0); + position.setValid(satellites >= 3); + position.set(Position.KEY_SATELLITES, satellites); + } + + position.set(Position.KEY_HDOP, parser.nextDouble()); + position.set(Position.KEY_INPUT, parser.next()); + position.set(Position.KEY_OUTPUT, parser.next()); + + if (parser.hasNext()) { + String[] values = parser.next().split(","); + for (int i = 0; i < values.length; i++) { + position.set(Position.PREFIX_ADC + (i + 1), values[i]); + } + } + + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + + if (parser.hasNext()) { + String[] values = parser.next().split(","); + for (String param : values) { + Matcher paramParser = Pattern.compile("(.*):[1-3]:(.*)").matcher(param); + if (paramParser.matches()) { + try { + position.set(paramParser.group(1).toLowerCase(), Double.parseDouble(paramParser.group(2))); + } catch (NumberFormatException e) { + position.set(paramParser.group(1).toLowerCase(), paramParser.group(2)); + } + } + } + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + Parser parser = new Parser(PATTERN_ANY, sentence); + if (!parser.matches()) { + return null; + } + + String id = parser.next(); + String type = parser.next(); + String data = parser.next(); + + switch (type) { + + case "L": + String[] values = data.split(";"); + + String imei = values[0].indexOf('.') >= 0 ? values[1] : values[0]; + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession != null) { + sendResponse(channel, remoteAddress, type, 1); + } + break; + + case "P": + sendResponse(channel, remoteAddress, type, null); // heartbeat + break; + + case "D": + case "SD": + Position position = decodePosition( + channel, remoteAddress, id, data); + + if (position != null) { + sendResponse(channel, remoteAddress, type, 1); + return position; + } + break; + + case "B": + String[] messages = data.split("\\|"); + List<Position> positions = new LinkedList<>(); + + for (String message : messages) { + position = decodePosition(channel, remoteAddress, id, message); + if (position != null) { + position.set(Position.KEY_ARCHIVE, true); + positions.add(position); + } + } + + sendResponse(channel, remoteAddress, type, messages.length); + if (!positions.isEmpty()) { + return positions; + } + break; + + case "M": + deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession != null) { + position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, new Date()); + position.setValid(false); + position.set(Position.KEY_RESULT, data); + sendResponse(channel, remoteAddress, type, 1); + return position; + } + break; + + default: + break; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/WialonProtocolEncoder.java b/src/main/java/org/traccar/protocol/WialonProtocolEncoder.java new file mode 100644 index 000000000..93086bf8a --- /dev/null +++ b/src/main/java/org/traccar/protocol/WialonProtocolEncoder.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class WialonProtocolEncoder extends StringProtocolEncoder { + + public WialonProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + switch (command.getType()) { + case Command.TYPE_REBOOT_DEVICE: + return formatCommand(command, "reboot\r\n"); + case Command.TYPE_SEND_USSD: + return formatCommand(command, "USSD:%s\r\n", Command.KEY_PHONE); + case Command.TYPE_IDENTIFICATION: + return formatCommand(command, "VER?\r\n"); + case Command.TYPE_OUTPUT_CONTROL: + return formatCommand(command, "L%s=%s\r\n", Command.KEY_INDEX, Command.KEY_DATA); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/WondexFrameDecoder.java b/src/main/java/org/traccar/protocol/WondexFrameDecoder.java new file mode 100644 index 000000000..39d83d761 --- /dev/null +++ b/src/main/java/org/traccar/protocol/WondexFrameDecoder.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseFrameDecoder; +import org.traccar.NetworkMessage; +import org.traccar.helper.BufferUtil; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; + +public class WondexFrameDecoder extends BaseFrameDecoder { + + private static final int KEEP_ALIVE_LENGTH = 8; + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < KEEP_ALIVE_LENGTH) { + return null; + } + + if (buf.getUnsignedByte(buf.readerIndex()) == 0xD0) { + + // Send response + ByteBuf frame = buf.readRetainedSlice(KEEP_ALIVE_LENGTH); + if (channel != null) { + frame.retain(); + channel.writeAndFlush(new NetworkMessage(frame, channel.remoteAddress())); + } + return frame; + + } else { + + int index = BufferUtil.indexOf("\r\n", buf); + if (index != -1) { + ByteBuf frame = buf.readRetainedSlice(index - buf.readerIndex()); + buf.skipBytes(2); + return frame; + } + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/WondexProtocol.java b/src/main/java/org/traccar/protocol/WondexProtocol.java new file mode 100644 index 000000000..035dd9160 --- /dev/null +++ b/src/main/java/org/traccar/protocol/WondexProtocol.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +import io.netty.handler.codec.string.StringEncoder; + +public class WondexProtocol extends BaseProtocol { + + public WondexProtocol() { + setSupportedCommands( + 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); + setTextCommandEncoder(new WondexProtocolEncoder(this)); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new WondexFrameDecoder()); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new WondexProtocolEncoder(WondexProtocol.this)); + pipeline.addLast(new WondexProtocolDecoder(WondexProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new WondexProtocolEncoder(WondexProtocol.this)); + pipeline.addLast(new WondexProtocolDecoder(WondexProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/WondexProtocolDecoder.java b/src/main/java/org/traccar/protocol/WondexProtocolDecoder.java new file mode 100644 index 000000000..b85ae2656 --- /dev/null +++ b/src/main/java/org/traccar/protocol/WondexProtocolDecoder.java @@ -0,0 +1,128 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.regex.Pattern; + +public class WondexProtocolDecoder extends BaseProtocolDecoder { + + public WondexProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("[^d]*") // header + .number("(d+),") // device identifier + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(-?d+.d+),") // longitude + .number("(-?d+.d+),") // latitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(-?d+.?d*),") // altitude + .number("(d+),") // satellites + .number("(d+),?") // event + .number("(d+.d+)V,").optional() // battery + .number("(d+.d+)?,?") // odometer + .number("(d+)?,?") // input + .number("(d+.d+)?,?") // adc1 + .number("(d+.d+)?,?") // adc2 + .number("(d+)?") // output + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + if (buf.getUnsignedByte(0) == 0xD0) { + + long deviceId = ((Long.reverseBytes(buf.getLong(0))) >> 32) & 0xFFFFFFFFL; + getDeviceSession(channel, remoteAddress, String.valueOf(deviceId)); + + return null; + } else if (buf.toString(StandardCharsets.US_ASCII).startsWith("$OK:") + || buf.toString(StandardCharsets.US_ASCII).startsWith("$ERR:") + || buf.toString(StandardCharsets.US_ASCII).startsWith("$MSG:")) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, new Date()); + position.set(Position.KEY_RESULT, buf.toString(StandardCharsets.US_ASCII)); + + return position; + + } else { + + 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.setTime(parser.nextDateTime()); + + position.setLongitude(parser.nextDouble(0)); + position.setLatitude(parser.nextDouble(0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + int satellites = parser.nextInt(0); + position.setValid(satellites != 0); + position.set(Position.KEY_SATELLITES, satellites); + + position.set(Position.KEY_EVENT, parser.next()); + position.set(Position.KEY_BATTERY, parser.nextDouble()); + if (parser.hasNext()) { + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + } + position.set(Position.KEY_INPUT, parser.next()); + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.PREFIX_ADC + 2, parser.next()); + position.set(Position.KEY_OUTPUT, parser.next()); + + return position; + + } + + } + +} diff --git a/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java b/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java new file mode 100644 index 000000000..21f1ee321 --- /dev/null +++ b/src/main/java/org/traccar/protocol/WondexProtocolEncoder.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class WondexProtocolEncoder extends StringProtocolEncoder { + + public WondexProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + initDevicePassword(command, "0000"); + + switch (command.getType()) { + case Command.TYPE_REBOOT_DEVICE: + return formatCommand(command, "$WP+REBOOT=%s", Command.KEY_DEVICE_PASSWORD); + case Command.TYPE_GET_DEVICE_STATUS: + return formatCommand(command, "$WP+TEST=%s", Command.KEY_DEVICE_PASSWORD); + case Command.TYPE_GET_MODEM_STATUS: + return formatCommand(command, "$WP+GSMINFO=%s", Command.KEY_DEVICE_PASSWORD); + case Command.TYPE_IDENTIFICATION: + return formatCommand(command, "$WP+IMEI=%s", Command.KEY_DEVICE_PASSWORD); + case Command.TYPE_POSITION_SINGLE: + return formatCommand(command, "$WP+GETLOCATION=%s", Command.KEY_DEVICE_PASSWORD); + case Command.TYPE_GET_VERSION: + return formatCommand(command, "$WP+VER=%s", Command.KEY_DEVICE_PASSWORD); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/WristbandProtocol.java b/src/main/java/org/traccar/protocol/WristbandProtocol.java new file mode 100644 index 000000000..1e5ef2c01 --- /dev/null +++ b/src/main/java/org/traccar/protocol/WristbandProtocol.java @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class WristbandProtocol extends BaseProtocol { + + public WristbandProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 2, 3, 0)); + pipeline.addLast(new WristbandProtocolDecoder(WristbandProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/WristbandProtocolDecoder.java b/src/main/java/org/traccar/protocol/WristbandProtocolDecoder.java new file mode 100644 index 000000000..7f2b0af85 --- /dev/null +++ b/src/main/java/org/traccar/protocol/WristbandProtocolDecoder.java @@ -0,0 +1,207 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +public class WristbandProtocolDecoder extends BaseProtocolDecoder { + + public WristbandProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private void sendResponse( + Channel channel, String imei, String version, int type, String data) { + + if (channel != null) { + String sentence = String.format("YX%s|%s|0|{F%02d#%s}\r\n", imei, version, type, data); + ByteBuf response = Unpooled.buffer(); + if (type != 91) { + response.writeBytes(new byte[]{0x00, 0x01, 0x02}); + response.writeShort(sentence.length()); + } + response.writeCharSequence(sentence, StandardCharsets.US_ASCII); + if (type != 91) { + response.writeBytes(new byte[]{(byte) 0xFF, (byte) 0xFE, (byte) 0xFC}); + } + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression("..") // header + .number("(d+)|") // imei + .number("([vV]d+.d+)|") // version + .number("d+|") // model + .text("{") + .number("F(d+)") // function + .groupBegin() + .text("#") + .expression("(.*)") // data + .groupEnd("?") + .text("}") + .text("\r\n") + .compile(); + + private Position decodePosition(DeviceSession deviceSession, String sentence) throws ParseException { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + String[] values = sentence.split(","); + + position.setValid(true); + position.setLongitude(Double.parseDouble(values[0])); + position.setLatitude(Double.parseDouble(values[1])); + position.setTime(new SimpleDateFormat("yyyyMMddHHmm").parse(values[2])); + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[3]))); + + return position; + } + + private Position decodeStatus(DeviceSession deviceSession, String sentence) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(sentence.split(",")[0])); + + return position; + } + + private Position decodeNetwork(DeviceSession deviceSession, String sentence, boolean wifi) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + Network network = new Network(); + String[] fragments = sentence.split("\\|"); + + if (wifi) { + for (String item : fragments[0].split("_")) { + String[] values = item.split(","); + network.addWifiAccessPoint(WifiAccessPoint.from(values[0], Integer.parseInt(values[1]))); + } + } + + for (String item : fragments[wifi ? 1 : 0].split(":")) { + String[] values = item.split(","); + int lac = Integer.parseInt(values[0]); + int mnc = Integer.parseInt(values[1]); + int mcc = Integer.parseInt(values[2]); + int cid = Integer.parseInt(values[3]); + int rssi = Integer.parseInt(values[4]); + network.addCellTower(CellTower.from(mcc, mnc, lac, cid, rssi)); + } + + position.setNetwork(network); + + return position; + } + + private List<Position> decodeMessage( + Channel channel, SocketAddress remoteAddress, String sentence) throws ParseException { + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + String imei = parser.next(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession == null) { + return null; + } + + String version = parser.next(); + int type = parser.nextInt(); + + List<Position> positions = new LinkedList<>(); + String data = parser.next(); + + switch (type) { + case 90: + sendResponse(channel, imei, version, type, getServer(channel, ',')); + break; + case 91: + String time = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); + sendResponse(channel, imei, version, type, time + "|" + getServer(channel, ',')); + break; + case 1: + positions.add(decodeStatus(deviceSession, data)); + sendResponse(channel, imei, version, type, data.split(",")[1]); + break; + case 2: + for (String fragment : data.split("\\|")) { + positions.add(decodePosition(deviceSession, fragment)); + } + break; + case 3: + case 4: + positions.add(decodeNetwork(deviceSession, data, type == 3)); + break; + case 64: + sendResponse(channel, imei, version, type, data); + break; + default: + break; + } + + return positions.isEmpty() ? null : positions; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + buf.skipBytes(3); // header + buf.readUnsignedShort(); // length + + String sentence = buf.toString(buf.readerIndex(), buf.readableBytes() - 3, StandardCharsets.US_ASCII); + + buf.skipBytes(3); // footer + + return decodeMessage(channel, remoteAddress, sentence); + } + +} diff --git a/src/main/java/org/traccar/protocol/XexunFrameDecoder.java b/src/main/java/org/traccar/protocol/XexunFrameDecoder.java new file mode 100644 index 000000000..114e94061 --- /dev/null +++ b/src/main/java/org/traccar/protocol/XexunFrameDecoder.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseFrameDecoder; +import org.traccar.helper.BufferUtil; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; + +public class XexunFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + if (buf.readableBytes() < 80) { + return null; + } + + int beginIndex = BufferUtil.indexOf("GPRMC", buf); + if (beginIndex == -1) { + beginIndex = BufferUtil.indexOf("GNRMC", buf); + if (beginIndex == -1) { + return null; + } + } + + int identifierIndex = BufferUtil.indexOf("imei:", buf, beginIndex, buf.writerIndex()); + if (identifierIndex == -1) { + return null; + } + + int endIndex = buf.indexOf(identifierIndex, buf.writerIndex(), (byte) ','); + if (endIndex == -1) { + return null; + } + + buf.skipBytes(beginIndex - buf.readerIndex()); + + return buf.readRetainedSlice(endIndex - beginIndex + 1); + } + +} diff --git a/src/main/java/org/traccar/protocol/XexunProtocol.java b/src/main/java/org/traccar/protocol/XexunProtocol.java new file mode 100644 index 000000000..401844e7b --- /dev/null +++ b/src/main/java/org/traccar/protocol/XexunProtocol.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.Context; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; + +public class XexunProtocol extends BaseProtocol { + + public XexunProtocol() { + setSupportedDataCommands( + Command.TYPE_ENGINE_STOP, + Command.TYPE_ENGINE_RESUME); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + boolean full = Context.getConfig().getBoolean(getName() + ".extended"); + if (full) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); // tracker bug \n\r + } else { + pipeline.addLast(new XexunFrameDecoder()); + } + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new XexunProtocolEncoder(XexunProtocol.this)); + pipeline.addLast(new XexunProtocolDecoder(XexunProtocol.this, full)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/XexunProtocolDecoder.java b/src/main/java/org/traccar/protocol/XexunProtocolDecoder.java new file mode 100644 index 000000000..73d386477 --- /dev/null +++ b/src/main/java/org/traccar/protocol/XexunProtocolDecoder.java @@ -0,0 +1,153 @@ +/* + * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.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 XexunProtocolDecoder extends BaseProtocolDecoder { + + private final boolean full; + + public XexunProtocolDecoder(Protocol protocol, boolean full) { + super(protocol); + this.full = full; + } + + private static final Pattern PATTERN_BASIC = new PatternBuilder() + .expression("G[PN]RMC,") + .number("(?:(dd)(dd)(dd))?.?d*,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(d*?)(d?d.d+),([NS]),") // latitude + .number("(d*?)(d?d.d+),([EW])?,") // longitude + .number("(d+.?d*),") // speed + .number("(d+.?d*)?,") // course + .number("(?:(dd)(dd)(dd))?,") // date (ddmmyy) + .expression("[^*]*").text("*") + .number("xx") // checksum + .expression("\\r\\n").optional() + .expression(",([FL]),") // signal + .expression("([^,]*),").optional() // alarm + .any() + .number("imei:(d+),") // imei + .compile(); + + private static final Pattern PATTERN_FULL = new PatternBuilder() + .any() + .number("(d+),") // serial + .expression("([^,]+)?,") // phone number + .expression(PATTERN_BASIC.pattern()) + .number("(d+),") // satellites + .number("(-?d+.d+)?,") // altitude + .number("[FL]:(d+.d+)V") // power + .any() + .compile(); + + private String decodeStatus(Position position, String value) { + if (value != null) { + switch (value.toLowerCase()) { + case "acc on": + case "accstart": + position.set(Position.KEY_IGNITION, true); + break; + case "acc off": + case "accstop": + position.set(Position.KEY_IGNITION, false); + break; + case "help me!": + case "help me": + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + break; + case "low battery": + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + case "move!": + case "moved!": + position.set(Position.KEY_ALARM, Position.ALARM_MOVEMENT); + break; + default: + break; + } + } + return null; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Pattern pattern = PATTERN_BASIC; + if (full) { + pattern = PATTERN_FULL; + } + + Parser parser = new Parser(pattern, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + if (full) { + position.set("serial", parser.next()); + position.set("number", parser.next()); + } + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + position.setSpeed(convertSpeed(parser.nextDouble(0), "kn")); + + position.setCourse(parser.nextDouble(0)); + + dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + position.setTime(dateBuilder.getDate()); + + position.set("signal", parser.next()); + + decodeStatus(position, parser.next()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + if (full) { + position.set(Position.KEY_SATELLITES, parser.nextInt()); + + position.setAltitude(parser.nextDouble(0)); + + position.set(Position.KEY_POWER, parser.nextDouble(0)); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/XexunProtocolEncoder.java b/src/main/java/org/traccar/protocol/XexunProtocolEncoder.java new file mode 100644 index 000000000..4f2707c2a --- /dev/null +++ b/src/main/java/org/traccar/protocol/XexunProtocolEncoder.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class XexunProtocolEncoder extends StringProtocolEncoder { + + public XexunProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + initDevicePassword(command, "123456"); + + switch (command.getType()) { + case Command.TYPE_ENGINE_STOP: + return formatCommand(command, "powercar%s 11", Command.KEY_DEVICE_PASSWORD); + case Command.TYPE_ENGINE_RESUME: + return formatCommand(command, "powercar%s 00", Command.KEY_DEVICE_PASSWORD); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/XirgoProtocol.java b/src/main/java/org/traccar/protocol/XirgoProtocol.java new file mode 100644 index 000000000..1be5b6c4b --- /dev/null +++ b/src/main/java/org/traccar/protocol/XirgoProtocol.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.CharacterDelimiterFrameDecoder; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; + +public class XirgoProtocol extends BaseProtocol { + + public XirgoProtocol() { + setSupportedDataCommands( + Command.TYPE_OUTPUT_CONTROL); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, "##")); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new XirgoProtocolEncoder(XirgoProtocol.this)); + pipeline.addLast(new XirgoProtocolDecoder(XirgoProtocol.this)); + } + }); + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new XirgoProtocolEncoder(XirgoProtocol.this)); + pipeline.addLast(new XirgoProtocolDecoder(XirgoProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/XirgoProtocolDecoder.java b/src/main/java/org/traccar/protocol/XirgoProtocolDecoder.java new file mode 100644 index 000000000..4d0cc314b --- /dev/null +++ b/src/main/java/org/traccar/protocol/XirgoProtocolDecoder.java @@ -0,0 +1,371 @@ +/* + * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import io.netty.channel.socket.nio.NioDatagramChannel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class XirgoProtocolDecoder extends BaseProtocolDecoder { + + private Boolean newFormat; + private String form; + + public XirgoProtocolDecoder(Protocol protocol) { + super(protocol); + form = Context.getConfig().getString(getProtocolName() + ".form"); + } + + public void setForm(String form) { + this.form = form; + } + + private static final Pattern PATTERN_OLD = new PatternBuilder() + .text("$$") + .number("(d+),") // imei + .number("(d+),") // event + .number("(dddd)/(dd)/(dd),") // date (yyyy/mm/dd) + .number("(dd):(dd):(dd),") // time (hh:mm:ss) + .number("(-?d+.?d*),") // latitude + .number("(-?d+.?d*),") // longitude + .number("(-?d+.?d*),") // altitude + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .number("(d+),") // satellites + .number("(d+.?d*),") // hdop + .number("(d+.d+),") // battery + .number("(d+),") // gsm + .number("(d+.?d*),") // odometer + .number("(d+),") // gps + .any() + .compile(); + + private static final Pattern PATTERN_NEW = new PatternBuilder() + .text("$$") + .number("(d+),") // imei + .number("(d+),") // event + .number("(dddd)/(dd)/(dd),") // date (yyyy/mm/dd) + .number("(dd):(dd):(dd),") // time (hh:mm:ss) + .number("(-?d+.?d*),") // latitude + .number("(-?d+.?d*),") // longitude + .number("(-?d+.?d*),") // altitude + .number("(d+.?d*),") // speed + .number("d+.?d*,") // acceleration + .number("d+.?d*,") // deceleration + .number("d+,") + .number("(d+.?d*),") // course + .number("(d+),") // satellites + .number("(d+.?d*),") // hdop + .number("(d+.?d*),") // odometer + .number("(d+.?d*),") // fuel consumption + .number("(d+.d+),") // battery + .number("(d+),") // gsm + .number("(d+),") // gps + .groupBegin() + .number("d,") // reset mode + .expression("([01])") // input 1 + .expression("([01])") // input 1 + .expression("([01])") // input 1 + .expression("([01]),") // output 1 + .number("(d+.?d*),") // adc 1 + .number("(d+.?d*),") // fuel level + .number("d+,") // engine load + .number("(d+),") // engine hours + .number("(d+),") // oil pressure + .number("(d+),") // oil level + .number("(-?d+),") // oil temperature + .number("(d+),") // coolant pressure + .number("(d+),") // coolant level + .number("(-?d+)") // coolant temperature + .groupEnd("?") + .any() + .compile(); + + private void decodeEvent(Position position, int event) { + + position.set(Position.KEY_EVENT, event); + + switch (event) { + case 4001: + case 4003: + case 6011: + case 6013: + position.set(Position.KEY_IGNITION, true); + break; + case 4002: + case 4004: + case 6012: + case 6014: + position.set(Position.KEY_IGNITION, false); + break; + case 4005: + position.set(Position.KEY_CHARGE, false); + break; + case 6002: + position.set(Position.KEY_ALARM, Position.ALARM_OVERSPEED); + break; + case 6006: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 6007: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 6008: + position.set(Position.KEY_ALARM, Position.ALARM_LOW_POWER); + break; + case 6009: + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + break; + case 6010: + position.set(Position.KEY_ALARM, Position.ALARM_POWER_RESTORED); + break; + case 6016: + position.set(Position.KEY_ALARM, Position.ALARM_IDLE); + break; + case 6017: + position.set(Position.KEY_ALARM, Position.ALARM_TOW); + break; + case 6030: + case 6071: + position.set(Position.KEY_MOTION, true); + break; + case 6031: + position.set(Position.KEY_MOTION, false); + break; + case 6032: + position.set(Position.KEY_ALARM, Position.ALARM_PARKING); + break; + case 6090: + position.set(Position.KEY_ALARM, Position.ALARM_REMOVING); + break; + case 6091: + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + default: + break; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + if (channel instanceof NioDatagramChannel) { + Matcher matcher = Pattern.compile("\\$\\$\\d+,(\\d+),.*,(\\d+)##").matcher(sentence); + if (matcher.matches()) { + String response = "!UDP_ACK," + matcher.group(1) + "," + matcher.group(2); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + if (form != null) { + return decodeCustom(channel, remoteAddress, sentence); + } else { + return decodeFixed(channel, remoteAddress, sentence); + } + } + + + private Object decodeCustom( + Channel channel, SocketAddress remoteAddress, String sentence) { + + String[] keys = form.split(","); + String[] values = sentence.replace("$$", "").replace("##", "").split(","); + + if (values.length < keys.length) { + return null; + } + + Position position = new Position(getProtocolName()); + DateBuilder dateBuilder = new DateBuilder(); + + for (int i = 0; i < keys.length; i++) { + switch (keys[i]) { + case "UID": + case "IM": + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[i]); + if (deviceSession != null) { + position.setDeviceId(deviceSession.getDeviceId()); + } + break; + case "EV": + decodeEvent(position, Integer.parseInt(values[i])); + break; + case "D": + String[] date = values[i].split("/"); + dateBuilder.setMonth(Integer.parseInt(date[0])); + dateBuilder.setDay(Integer.parseInt(date[1])); + dateBuilder.setYear(Integer.parseInt(date[2])); + break; + case "T": + String[] time = values[i].split(":"); + dateBuilder.setHour(Integer.parseInt(time[0])); + dateBuilder.setMinute(Integer.parseInt(time[1])); + dateBuilder.setSecond(Integer.parseInt(time[2])); + break; + case "LT": + position.setLatitude(Double.parseDouble(values[i])); + break; + case "LN": + position.setLongitude(Double.parseDouble(values[i])); + break; + case "AL": + position.setAltitude(Integer.parseInt(values[i])); + break; + case "GSPT": + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[i]))); + break; + case "HD": + if (values[i].contains(".")) { + position.setCourse(Double.parseDouble(values[i])); + } else { + position.setCourse(Integer.parseInt(values[i]) * 0.1); + } + break; + case "SV": + position.set(Position.KEY_SATELLITES, Integer.parseInt(values[i])); + break; + case "BV": + position.set(Position.KEY_BATTERY, Double.parseDouble(values[i])); + break; + case "CQ": + position.set(Position.KEY_RSSI, Integer.parseInt(values[i])); + break; + case "MI": + position.set(Position.KEY_ODOMETER, Integer.parseInt(values[i])); + break; + case "GS": + position.setValid(Integer.parseInt(values[i]) == 3); + break; + case "SI": + position.set(Position.KEY_ICCID, values[i]); + break; + case "IG": + int ignition = Integer.parseInt(values[i]); + if (ignition > 0) { + position.set(Position.KEY_IGNITION, ignition == 1); + } + break; + case "OT": + position.set(Position.KEY_OUTPUT, Integer.parseInt(values[i])); + break; + default: + break; + } + } + + position.setTime(dateBuilder.getDate()); + + return position.getDeviceId() > 0 ? position : null; + } + + private Object decodeFixed( + Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser; + if (newFormat == null) { + parser = new Parser(PATTERN_NEW, sentence); + if (parser.matches()) { + newFormat = true; + } else { + parser = new Parser(PATTERN_OLD, sentence); + if (parser.matches()) { + newFormat = false; + } else { + return null; + } + } + } else { + if (newFormat) { + parser = new Parser(PATTERN_NEW, sentence); + } else { + parser = new Parser(PATTERN_OLD, sentence); + } + 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()); + + decodeEvent(position, parser.nextInt()); + + position.setTime(parser.nextDateTime()); + + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + position.setSpeed(UnitsConverter.knotsFromMph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_HDOP, parser.nextDouble()); + + if (newFormat) { + position.set(Position.KEY_ODOMETER, UnitsConverter.metersFromMiles(parser.nextDouble(0))); + position.set(Position.KEY_FUEL_CONSUMPTION, parser.next()); + } + + position.set(Position.KEY_BATTERY, parser.nextDouble(0)); + position.set(Position.KEY_RSSI, parser.nextDouble()); + + if (!newFormat) { + position.set(Position.KEY_ODOMETER, UnitsConverter.metersFromMiles(parser.nextDouble(0))); + } + + position.setValid(parser.nextInt(0) == 1); + + if (newFormat && parser.hasNext(13)) { + position.set(Position.PREFIX_IN + 1, parser.nextInt()); + position.set(Position.PREFIX_IN + 2, parser.nextInt()); + position.set(Position.PREFIX_IN + 3, parser.nextInt()); + position.set(Position.PREFIX_OUT + 1, parser.nextInt()); + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.KEY_FUEL_LEVEL, parser.nextDouble()); + position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(parser.nextInt())); + position.set("oilPressure", parser.nextInt()); + position.set("oilLevel", parser.nextInt()); + position.set("oilTemp", parser.nextInt()); + position.set("coolantPressure", parser.nextInt()); + position.set("coolantLevel", parser.nextInt()); + position.set("coolantTemp", parser.nextInt()); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/XirgoProtocolEncoder.java b/src/main/java/org/traccar/protocol/XirgoProtocolEncoder.java new file mode 100644 index 000000000..aa85e9e0e --- /dev/null +++ b/src/main/java/org/traccar/protocol/XirgoProtocolEncoder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.StringProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class XirgoProtocolEncoder extends StringProtocolEncoder { + + public XirgoProtocolEncoder(Protocol protocol) { + super(protocol); + } + + @Override + protected Object encodeCommand(Command command) { + + switch (command.getType()) { + case Command.TYPE_OUTPUT_CONTROL: + return String.format("+XT:7005,%d,1", command.getInteger(Command.KEY_DATA) + 1); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Xrb28Protocol.java b/src/main/java/org/traccar/protocol/Xrb28Protocol.java new file mode 100644 index 000000000..5d8af418b --- /dev/null +++ b/src/main/java/org/traccar/protocol/Xrb28Protocol.java @@ -0,0 +1,49 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.model.Command; + +import java.nio.charset.StandardCharsets; + +public class Xrb28Protocol extends BaseProtocol { + + public Xrb28Protocol() { + setSupportedDataCommands( + Command.TYPE_CUSTOM, + Command.TYPE_POSITION_SINGLE, + Command.TYPE_POSITION_PERIODIC, + Command.TYPE_ALARM_ARM, + Command.TYPE_ALARM_DISARM); + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder(StandardCharsets.ISO_8859_1)); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new Xrb28ProtocolEncoder(Xrb28Protocol.this)); + pipeline.addLast(new Xrb28ProtocolDecoder(Xrb28Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java new file mode 100644 index 000000000..69e5b7372 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Xrb28ProtocolDecoder.java @@ -0,0 +1,187 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Command; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class Xrb28ProtocolDecoder extends BaseProtocolDecoder { + + private String pendingCommand; + + public void setPendingCommand(String pendingCommand) { + this.pendingCommand = pendingCommand; + } + + public Xrb28ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("*") + .expression("....,") + .expression("..,") // vendor + .number("d{15},") // imei + .expression("..,") // type + .number("[01],") // reserved + .number("(dd)(dd)(dd).d+,") // time (hhmmss) + .expression("([AV]),") // validity + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(d{2,3})(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+),") // satellites + .number("(d+.d+),") // hdop + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(-?d+.?d*),") // altitude + .expression(".,") // height unit + .expression(".#") // mode + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, sentence.substring(9, 24)); + if (deviceSession == null) { + return null; + } + + String type = sentence.substring(25, 27); + if (channel != null) { + if (type.matches("L0|L1|W0|E1")) { + channel.write(new NetworkMessage( + "\u00ff\u00ff*SCOS" + sentence.substring(5, 27) + "#\n", + remoteAddress)); + } else if (type.equals("R0") && pendingCommand != null) { + String command = pendingCommand.equals(Command.TYPE_ALARM_ARM) ? "L1," : "L0,"; + channel.write(new NetworkMessage( + "\u00ff\u00ff*SCOS" + sentence.substring(5, 25) + command + sentence.substring(30) + "\n", + remoteAddress)); + pendingCommand = null; + } + } + + if (!type.startsWith("D")) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + String payload = sentence.substring(25, sentence.length() - 1); + + int index = 0; + String[] values = payload.substring(3).split(","); + + switch (type) { + case "Q0": + position.set(Position.KEY_BATTERY, Integer.parseInt(values[index++]) * 0.01); + position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(values[index++])); + position.set(Position.KEY_RSSI, Integer.parseInt(values[index++])); + break; + case "H0": + position.set(Position.KEY_BLOCKED, Integer.parseInt(values[index++]) > 0); + position.set(Position.KEY_BATTERY, Integer.parseInt(values[index++]) * 0.01); + position.set(Position.KEY_RSSI, Integer.parseInt(values[index++])); + position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(values[index++])); + break; + case "W0": + switch (Integer.parseInt(values[index++])) { + case 1: + position.set(Position.KEY_ALARM, Position.ALARM_MOVEMENT); + break; + case 2: + position.set(Position.KEY_ALARM, Position.ALARM_FALL_DOWN); + break; + case 3: + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + default: + break; + } + break; + case "E0": + position.set(Position.KEY_ALARM, Position.ALARM_FAULT); + position.set("error", Integer.parseInt(values[index++])); + break; + case "S1": + position.set(Position.KEY_EVENT, Integer.parseInt(values[index++])); + break; + case "R0": + case "L0": + case "L1": + case "S4": + case "S5": + case "S6": + case "S7": + case "V0": + case "G0": + case "K0": + case "I0": + case "M0": + position.set(Position.KEY_RESULT, payload); + break; + default: + break; + } + + return !position.getAttributes().isEmpty() ? position : null; + + } else { + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_HDOP, parser.nextDouble()); + + dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + position.setAltitude(parser.nextDouble()); + + return position; + + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Xrb28ProtocolEncoder.java b/src/main/java/org/traccar/protocol/Xrb28ProtocolEncoder.java new file mode 100644 index 000000000..3e69af329 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Xrb28ProtocolEncoder.java @@ -0,0 +1,57 @@ +/* + * Copyright 2018 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolEncoder; +import org.traccar.model.Command; +import org.traccar.Protocol; + +public class Xrb28ProtocolEncoder extends BaseProtocolEncoder { + + public Xrb28ProtocolEncoder(Protocol protocol) { + super(protocol); + } + + private String formatCommand(Command command, String content) { + return String.format("\u00ff\u00ff*SCOS,OM,%s,%s#\n", getUniqueId(command.getDeviceId()), content); + } + + @Override + protected Object encodeCommand(Channel channel, Command command) { + + switch (command.getType()) { + case Command.TYPE_CUSTOM: + return formatCommand(command, command.getString(Command.KEY_DATA)); + case Command.TYPE_POSITION_SINGLE: + return formatCommand(command, "D0"); + case Command.TYPE_POSITION_PERIODIC: + return formatCommand(command, "D1," + command.getInteger(Command.KEY_FREQUENCY)); + case Command.TYPE_ENGINE_STOP: + case Command.TYPE_ALARM_DISARM: + if (channel != null) { + Xrb28ProtocolDecoder decoder = channel.pipeline().get(Xrb28ProtocolDecoder.class); + if (decoder != null) { + decoder.setPendingCommand(command.getType()); + } + } + return formatCommand(command, "R0,0,20,1234," + System.currentTimeMillis() / 1000); + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/Xt013Protocol.java b/src/main/java/org/traccar/protocol/Xt013Protocol.java new file mode 100644 index 000000000..ebb3c123f --- /dev/null +++ b/src/main/java/org/traccar/protocol/Xt013Protocol.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; + +public class Xt013Protocol extends BaseProtocol { + + public Xt013Protocol() { + 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 Xt013ProtocolDecoder(Xt013Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Xt013ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xt013ProtocolDecoder.java new file mode 100644 index 000000000..f49fb9563 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Xt013ProtocolDecoder.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class Xt013ProtocolDecoder extends BaseProtocolDecoder { + + public Xt013ProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .number("HI,d+").optional() + .text("TK,") + .number("(d+),") // imei + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("([+-]d+.d+),") // latitude + .number("([+-]d+.d+),") // longitude + .number("(d+),") // speed + .number("(d+),") // course + .number("d+,") + .number("(d+),") // altitude + .expression("([FL]),") // gps fix + .number("d+,") + .number("(d+),") // gps level + .number("x+,") + .number("x+,") + .number("(d+),") // gsm level + .expression("[^,]*,") + .number("(d+.d+),") // battery + .number("(d),") // charging + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime()); + + position.setLatitude(parser.nextDouble(0)); + position.setLongitude(parser.nextDouble(0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + position.setValid(parser.next().equals("F")); + + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_RSSI, parser.nextDouble()); + position.set(Position.KEY_BATTERY, parser.nextDouble(0)); + position.set(Position.KEY_CHARGE, parser.next().equals("1")); + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/Xt2400Protocol.java b/src/main/java/org/traccar/protocol/Xt2400Protocol.java new file mode 100644 index 000000000..9427876c8 --- /dev/null +++ b/src/main/java/org/traccar/protocol/Xt2400Protocol.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class Xt2400Protocol extends BaseProtocol { + + public Xt2400Protocol() { + addServer(new TrackerServer(true, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new Xt2400ProtocolDecoder(Xt2400Protocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java new file mode 100644 index 000000000..c132f194b --- /dev/null +++ b/src/main/java/org/traccar/protocol/Xt2400ProtocolDecoder.java @@ -0,0 +1,208 @@ +/* + * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.DataConverter; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Xt2400ProtocolDecoder extends BaseProtocolDecoder { + + public Xt2400ProtocolDecoder(Protocol protocol) { + super(protocol); + + String config = Context.getConfig().getString(getProtocolName() + ".config"); + if (config != null) { + setConfig(config); + } + } + + private static final Map<Integer, Integer> TAG_LENGTH_MAP = new HashMap<>(); + + static { + int[] l1 = { + 0x01, 0x02, 0x04, 0x0b, 0x0c, 0x0d, 0x12, 0x13, + 0x16, 0x17, 0x1c, 0x1f, 0x23, 0x2c, 0x2d, 0x30, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x40, 0x41, + 0x53, 0x66, 0x69, 0x6a, 0x93, 0x94, 0x96 + }; + int[] l2 = { + 0x05, 0x09, 0x0a, 0x14, 0x15, 0x1d, 0x1e, 0x24, + 0x26, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x57, 0x58, 0x59, 0x5a, 0x6b, 0x6f, 0x7A, + 0x7B, 0x7C, 0x7d, 0x7E, 0x7F, 0x80, 0x81, 0x82, + 0x83, 0x84, 0x85, 0x86, 0xc8 + }; + int[] l4 = { + 0x03, 0x06, 0x07, 0x08, 0x0e, 0x0f, 0x10, 0x11, + 0x18, 0x19, 0x1a, 0x1b, 0x20, 0x21, 0x22, 0x2e, + 0x2f, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x54, 0x55, 0x56, 0x5b, 0x5c, 0x5d, + 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x68, 0x6e, 0x71, + 0x72, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d + }; + for (int i : l1) { + TAG_LENGTH_MAP.put(i, 1); + } + for (int i : l2) { + TAG_LENGTH_MAP.put(i, 2); + } + for (int i : l4) { + TAG_LENGTH_MAP.put(i, 4); + } + TAG_LENGTH_MAP.put(0x95, 24); + TAG_LENGTH_MAP.put(0xD0, 21); + } + + private static int getTagLength(int tag) { + Integer length = TAG_LENGTH_MAP.get(tag); + if (length == null) { + throw new IllegalArgumentException(String.format("Unknown tag: 0x%02X", tag)); + } + return length; + } + + private Map<Short, byte[]> formats = new HashMap<>(); + + public void setConfig(String configString) { + Pattern pattern = Pattern.compile(":wycfg pcr\\[\\d+] ([0-9a-fA-F]{2})[0-9a-fA-F]{2}([0-9a-fA-F]+)"); + Matcher matcher = pattern.matcher(configString); + while (matcher.find()) { + formats.put(Short.parseShort(matcher.group(1), 16), DataConverter.parseHex(matcher.group(2))); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + byte[] format = null; + if (formats.size() > 1) { + format = formats.get(buf.getUnsignedByte(buf.readerIndex())); + } else if (!formats.isEmpty()) { + format = formats.values().iterator().next(); + } + + if (format == null) { + return null; + } + + Position position = new Position(getProtocolName()); + + for (byte b : format) { + int tag = b & 0xFF; + switch (tag) { + case 0x03: + DeviceSession deviceSession = getDeviceSession( + channel, remoteAddress, String.valueOf(buf.readUnsignedInt())); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + break; + case 0x04: + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + break; + case 0x05: + position.set(Position.KEY_INDEX, buf.readUnsignedShort()); + break; + case 0x06: + position.setTime(new Date(buf.readUnsignedInt() * 1000)); + break; + case 0x07: + position.setLatitude(buf.readInt() * 0.000001); + break; + case 0x08: + position.setLongitude(buf.readInt() * 0.000001); + break; + case 0x09: + position.setAltitude(buf.readShort() * 0.1); + break; + case 0x0a: + position.setCourse(buf.readShort() * 0.1); + break; + case 0x0b: + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte())); + break; + case 0x10: + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedInt()); + break; + case 0x12: + position.set(Position.KEY_HDOP, buf.readUnsignedByte() * 0.1); + break; + case 0x13: + position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); + break; + case 0x14: + position.set(Position.KEY_RSSI, buf.readShort()); + break; + case 0x16: + position.set(Position.KEY_BATTERY, buf.readUnsignedByte() * 0.1); + break; + case 0x17: + position.set(Position.KEY_POWER, buf.readUnsignedByte() * 0.1); + break; + case 0x57: + position.set(Position.KEY_OBD_SPEED, UnitsConverter.knotsFromKph(buf.readUnsignedShort())); + break; + case 0x65: + position.set(Position.KEY_VIN, buf.readSlice(17).toString(StandardCharsets.US_ASCII)); + break; + case 0x6C: + buf.readUnsignedByte(); // mil + int ecuCount = buf.readUnsignedByte(); + for (int i = 0; i < ecuCount; i++) { + buf.readUnsignedByte(); // ecu id + buf.skipBytes(buf.readUnsignedByte() * 6); + } + break; + case 0x73: + position.set(Position.KEY_VERSION_FW, buf.readSlice(16).toString(StandardCharsets.US_ASCII).trim()); + break; + default: + buf.skipBytes(getTagLength(tag)); + break; + } + } + + if (position.getLatitude() != 0 && position.getLongitude() != 0) { + position.setValid(true); + } else { + getLastLocation(position, position.getDeviceTime()); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/protocol/YwtProtocol.java b/src/main/java/org/traccar/protocol/YwtProtocol.java new file mode 100644 index 000000000..c525b75cf --- /dev/null +++ b/src/main/java/org/traccar/protocol/YwtProtocol.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; + +public class YwtProtocol extends BaseProtocol { + + public YwtProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new StringEncoder()); + pipeline.addLast(new StringDecoder()); + pipeline.addLast(new YwtProtocolDecoder(YwtProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/YwtProtocolDecoder.java b/src/main/java/org/traccar/protocol/YwtProtocolDecoder.java new file mode 100644 index 000000000..bf5a23fa7 --- /dev/null +++ b/src/main/java/org/traccar/protocol/YwtProtocolDecoder.java @@ -0,0 +1,116 @@ +/* + * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class YwtProtocolDecoder extends BaseProtocolDecoder { + + public YwtProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression("%(..),") // type + .number("(d+):") // unit identifier + .number("d+,") // subtype + .number("(dd)(dd)(dd)") // date (yymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([EW])") + .number("(ddd.d{6}),") // longitude + .expression("([NS])") + .number("(dd.d{6}),") // latitude + .number("(d+)?,") // altitude + .number("(d+),") // speed + .number("(d+),") // course + .number("(d+),") // satellite + .expression("([^,]+),") // report identifier + .expression("([-0-9a-fA-F]+)") // status + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + // Synchronization + if (sentence.startsWith("%SN") && channel != null) { + int start = sentence.indexOf(':'); + int end = start; + for (int i = 0; i < 4; i++) { + end = sentence.indexOf(',', end + 1); + } + if (end == -1) { + end = sentence.length(); + } + + channel.writeAndFlush(new NetworkMessage("%AT+SN=" + sentence.substring(start, end), remoteAddress)); + return null; + } + + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + + String type = parser.next(); + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + position.setDeviceId(deviceSession.getDeviceId()); + + position.setTime(parser.nextDateTime()); + + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.HEM_DEG)); + position.setAltitude(parser.nextDouble(0)); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + + int satellites = parser.nextInt(); + position.setValid(satellites != 0); + position.set(Position.KEY_SATELLITES, satellites); + + String reportId = parser.next(); + + position.set(Position.KEY_STATUS, parser.next()); + + // Send response + if ((type.equals("KP") || type.equals("EP")) && channel != null) { + channel.writeAndFlush(new NetworkMessage("%AT+" + type + "=" + reportId + "\r\n", remoteAddress)); + } + + return position; + } + +} diff --git a/src/main/java/org/traccar/reports/Events.java b/src/main/java/org/traccar/reports/Events.java new file mode 100644 index 000000000..66d9e708d --- /dev/null +++ b/src/main/java/org/traccar/reports/Events.java @@ -0,0 +1,133 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; + +import org.apache.poi.ss.util.WorkbookUtil; +import org.traccar.Context; +import org.traccar.model.Device; +import org.traccar.model.Event; +import org.traccar.model.Geofence; +import org.traccar.model.Group; +import org.traccar.model.Maintenance; +import org.traccar.reports.model.DeviceReport; + +public final class Events { + + private Events() { + } + + public static Collection<Event> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Collection<String> types, Date from, Date to) throws SQLException { + ReportUtils.checkPeriodLimit(from, to); + ArrayList<Event> result = new ArrayList<>(); + for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { + Context.getPermissionsManager().checkDevice(userId, deviceId); + Collection<Event> events = Context.getDataManager().getEvents(deviceId, from, to); + boolean all = types.isEmpty() || types.contains(Event.ALL_EVENTS); + for (Event event : events) { + if (all || types.contains(event.getType())) { + long geofenceId = event.getGeofenceId(); + long maintenanceId = event.getMaintenanceId(); + if ((geofenceId == 0 || Context.getGeofenceManager().checkItemPermission(userId, geofenceId)) + && (maintenanceId == 0 + || Context.getMaintenancesManager().checkItemPermission(userId, maintenanceId))) { + result.add(event); + } + } + } + } + return result; + } + + public static void getExcel(OutputStream outputStream, + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Collection<String> types, Date from, Date to) throws SQLException, IOException { + ReportUtils.checkPeriodLimit(from, to); + ArrayList<DeviceReport> devicesEvents = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + HashMap<Long, String> geofenceNames = new HashMap<>(); + HashMap<Long, String> maintenanceNames = new HashMap<>(); + for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { + Context.getPermissionsManager().checkDevice(userId, deviceId); + Collection<Event> events = Context.getDataManager().getEvents(deviceId, from, to); + boolean all = types.isEmpty() || types.contains(Event.ALL_EVENTS); + for (Iterator<Event> iterator = events.iterator(); iterator.hasNext();) { + Event event = iterator.next(); + if (all || types.contains(event.getType())) { + long geofenceId = event.getGeofenceId(); + long maintenanceId = event.getMaintenanceId(); + if (geofenceId != 0) { + if (Context.getGeofenceManager().checkItemPermission(userId, geofenceId)) { + Geofence geofence = Context.getGeofenceManager().getById(geofenceId); + if (geofence != null) { + geofenceNames.put(geofenceId, geofence.getName()); + } + } else { + iterator.remove(); + } + } else if (maintenanceId != 0) { + if (Context.getMaintenancesManager().checkItemPermission(userId, maintenanceId)) { + Maintenance maintenance = Context.getMaintenancesManager().getById(maintenanceId); + if (maintenance != null) { + maintenanceNames.put(maintenanceId, maintenance.getName()); + } + } else { + iterator.remove(); + } + } + } else { + iterator.remove(); + } + } + DeviceReport deviceEvents = new DeviceReport(); + Device device = Context.getIdentityManager().getById(deviceId); + deviceEvents.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(deviceEvents.getDeviceName())); + if (device.getGroupId() != 0) { + Group group = Context.getGroupsManager().getById(device.getGroupId()); + if (group != null) { + deviceEvents.setGroupName(group.getName()); + } + } + deviceEvents.setObjects(events); + devicesEvents.add(deviceEvents); + } + String templatePath = Context.getConfig().getString("report.templatesPath", + "templates/export/"); + try (InputStream inputStream = new FileInputStream(templatePath + "/events.xlsx")) { + org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); + jxlsContext.putVar("devices", devicesEvents); + jxlsContext.putVar("sheetNames", sheetNames); + jxlsContext.putVar("geofenceNames", geofenceNames); + jxlsContext.putVar("maintenanceNames", maintenanceNames); + jxlsContext.putVar("from", from); + jxlsContext.putVar("to", to); + ReportUtils.processTemplateWithSheets(inputStream, outputStream, jxlsContext); + } + } +} diff --git a/src/main/java/org/traccar/reports/ReportUtils.java b/src/main/java/org/traccar/reports/ReportUtils.java new file mode 100644 index 000000000..3a631e0d9 --- /dev/null +++ b/src/main/java/org/traccar/reports/ReportUtils.java @@ -0,0 +1,378 @@ +/* + * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports; + +import org.apache.velocity.tools.generic.DateTool; +import org.apache.velocity.tools.generic.NumberTool; +import org.jxls.area.Area; +import org.jxls.builder.xls.XlsCommentAreaBuilder; +import org.jxls.common.CellRef; +import org.jxls.formula.StandardFormulaProcessor; +import org.jxls.transform.Transformer; +import org.jxls.transform.poi.PoiTransformer; +import org.jxls.util.TransformerFactory; +import org.traccar.Context; +import org.traccar.database.DeviceManager; +import org.traccar.database.IdentityManager; +import org.traccar.handler.events.MotionEventHandler; +import org.traccar.model.DeviceState; +import org.traccar.model.Driver; +import org.traccar.model.Event; +import org.traccar.model.Position; +import org.traccar.reports.model.BaseReport; +import org.traccar.reports.model.StopReport; +import org.traccar.reports.model.TripReport; +import org.traccar.reports.model.TripsConfig; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +public final class ReportUtils { + + private ReportUtils() { + } + + public static void checkPeriodLimit(Date from, Date to) { + long limit = Context.getConfig().getLong("report.periodLimit") * 1000; + if (limit > 0 && to.getTime() - from.getTime() > limit) { + throw new IllegalArgumentException("Time period exceeds the limit"); + } + } + + public static String getDistanceUnit(long userId) { + return (String) Context.getPermissionsManager().lookupAttribute(userId, "distanceUnit", "km"); + } + + public static String getSpeedUnit(long userId) { + return (String) Context.getPermissionsManager().lookupAttribute(userId, "speedUnit", "kn"); + } + + public static String getVolumeUnit(long userId) { + return (String) Context.getPermissionsManager().lookupAttribute(userId, "volumeUnit", "ltr"); + } + + public static TimeZone getTimezone(long userId) { + String timezone = (String) Context.getPermissionsManager().lookupAttribute(userId, "timezone", null); + return timezone != null ? TimeZone.getTimeZone(timezone) : TimeZone.getDefault(); + } + + public static Collection<Long> getDeviceList(Collection<Long> deviceIds, Collection<Long> groupIds) { + Collection<Long> result = new ArrayList<>(); + result.addAll(deviceIds); + for (long groupId : groupIds) { + result.addAll(Context.getPermissionsManager().getGroupDevices(groupId)); + } + return result; + } + + public static double calculateDistance(Position firstPosition, Position lastPosition) { + return calculateDistance(firstPosition, lastPosition, true); + } + + public static double calculateDistance(Position firstPosition, Position lastPosition, boolean useOdometer) { + double distance = 0.0; + double firstOdometer = firstPosition.getDouble(Position.KEY_ODOMETER); + double lastOdometer = lastPosition.getDouble(Position.KEY_ODOMETER); + + if (useOdometer && (firstOdometer != 0.0 || lastOdometer != 0.0)) { + distance = lastOdometer - firstOdometer; + } else if (firstPosition.getAttributes().containsKey(Position.KEY_TOTAL_DISTANCE) + && lastPosition.getAttributes().containsKey(Position.KEY_TOTAL_DISTANCE)) { + distance = lastPosition.getDouble(Position.KEY_TOTAL_DISTANCE) + - firstPosition.getDouble(Position.KEY_TOTAL_DISTANCE); + } + return distance; + } + + public static double calculateFuel(Position firstPosition, Position lastPosition) { + + if (firstPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null + && lastPosition.getAttributes().get(Position.KEY_FUEL_LEVEL) != null) { + + BigDecimal value = new BigDecimal(firstPosition.getDouble(Position.KEY_FUEL_LEVEL) + - lastPosition.getDouble(Position.KEY_FUEL_LEVEL)); + return value.setScale(1, RoundingMode.HALF_EVEN).doubleValue(); + } + return 0; + } + + public static String findDriver(Position firstPosition, Position lastPosition) { + if (firstPosition.getAttributes().containsKey(Position.KEY_DRIVER_UNIQUE_ID)) { + return firstPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); + } else if (lastPosition.getAttributes().containsKey(Position.KEY_DRIVER_UNIQUE_ID)) { + return lastPosition.getString(Position.KEY_DRIVER_UNIQUE_ID); + } + return null; + } + + public static String findDriverName(String driverUniqueId) { + if (driverUniqueId != null && Context.getDriversManager() != null) { + Driver driver = Context.getDriversManager().getDriverByUniqueId(driverUniqueId); + if (driver != null) { + return driver.getName(); + } + } + return null; + } + + public static org.jxls.common.Context initializeContext(long userId) { + org.jxls.common.Context jxlsContext = PoiTransformer.createInitialContext(); + jxlsContext.putVar("distanceUnit", getDistanceUnit(userId)); + jxlsContext.putVar("speedUnit", getSpeedUnit(userId)); + jxlsContext.putVar("volumeUnit", getVolumeUnit(userId)); + jxlsContext.putVar("webUrl", Context.getVelocityEngine().getProperty("web.url")); + jxlsContext.putVar("dateTool", new DateTool()); + jxlsContext.putVar("numberTool", new NumberTool()); + jxlsContext.putVar("timezone", getTimezone(userId)); + jxlsContext.putVar("locale", Locale.getDefault()); + jxlsContext.putVar("bracketsRegex", "[\\{\\}\"]"); + return jxlsContext; + } + + public static void processTemplateWithSheets( + InputStream templateStream, OutputStream targetStream, + org.jxls.common.Context jxlsContext) throws IOException { + + Transformer transformer = TransformerFactory.createTransformer(templateStream, targetStream); + List<Area> xlsAreas = new XlsCommentAreaBuilder(transformer).build(); + for (Area xlsArea : xlsAreas) { + xlsArea.applyAt(new CellRef(xlsArea.getStartCellRef().getCellName()), jxlsContext); + xlsArea.setFormulaProcessor(new StandardFormulaProcessor()); + xlsArea.processFormulas(); + } + transformer.deleteSheet(xlsAreas.get(0).getStartCellRef().getSheetName()); + transformer.write(); + } + + private static TripReport calculateTrip( + ArrayList<Position> positions, int startIndex, int endIndex, boolean ignoreOdometer) { + + Position startTrip = positions.get(startIndex); + Position endTrip = positions.get(endIndex); + + double speedMax = 0.0; + double speedSum = 0.0; + for (int i = startIndex; i <= endIndex; i++) { + double speed = positions.get(i).getSpeed(); + speedSum += speed; + if (speed > speedMax) { + speedMax = speed; + } + } + + TripReport trip = new TripReport(); + + long tripDuration = endTrip.getFixTime().getTime() - startTrip.getFixTime().getTime(); + long deviceId = startTrip.getDeviceId(); + trip.setDeviceId(deviceId); + trip.setDeviceName(Context.getIdentityManager().getById(deviceId).getName()); + + trip.setStartPositionId(startTrip.getId()); + trip.setStartLat(startTrip.getLatitude()); + trip.setStartLon(startTrip.getLongitude()); + trip.setStartTime(startTrip.getFixTime()); + String startAddress = startTrip.getAddress(); + if (startAddress == null && Context.getGeocoder() != null + && Context.getConfig().getBoolean("geocoder.onRequest")) { + startAddress = Context.getGeocoder().getAddress(startTrip.getLatitude(), startTrip.getLongitude(), null); + } + trip.setStartAddress(startAddress); + + trip.setEndPositionId(endTrip.getId()); + trip.setEndLat(endTrip.getLatitude()); + trip.setEndLon(endTrip.getLongitude()); + trip.setEndTime(endTrip.getFixTime()); + String endAddress = endTrip.getAddress(); + if (endAddress == null && Context.getGeocoder() != null + && Context.getConfig().getBoolean("geocoder.onRequest")) { + endAddress = Context.getGeocoder().getAddress(endTrip.getLatitude(), endTrip.getLongitude(), null); + } + trip.setEndAddress(endAddress); + + trip.setDistance(calculateDistance(startTrip, endTrip, !ignoreOdometer)); + trip.setDuration(tripDuration); + trip.setAverageSpeed(speedSum / (endIndex - startIndex)); + trip.setMaxSpeed(speedMax); + trip.setSpentFuel(calculateFuel(startTrip, endTrip)); + + trip.setDriverUniqueId(findDriver(startTrip, endTrip)); + trip.setDriverName(findDriverName(trip.getDriverUniqueId())); + + if (!ignoreOdometer + && startTrip.getDouble(Position.KEY_ODOMETER) != 0 + && endTrip.getDouble(Position.KEY_ODOMETER) != 0) { + trip.setStartOdometer(startTrip.getDouble(Position.KEY_ODOMETER)); + trip.setEndOdometer(endTrip.getDouble(Position.KEY_ODOMETER)); + } else { + trip.setStartOdometer(startTrip.getDouble(Position.KEY_TOTAL_DISTANCE)); + trip.setEndOdometer(endTrip.getDouble(Position.KEY_TOTAL_DISTANCE)); + } + + return trip; + } + + private static StopReport calculateStop( + ArrayList<Position> positions, int startIndex, int endIndex, boolean ignoreOdometer) { + + Position startStop = positions.get(startIndex); + Position endStop = positions.get(endIndex); + + StopReport stop = new StopReport(); + + long deviceId = startStop.getDeviceId(); + stop.setDeviceId(deviceId); + stop.setDeviceName(Context.getIdentityManager().getById(deviceId).getName()); + + stop.setPositionId(startStop.getId()); + stop.setLatitude(startStop.getLatitude()); + stop.setLongitude(startStop.getLongitude()); + stop.setStartTime(startStop.getFixTime()); + String address = startStop.getAddress(); + if (address == null && Context.getGeocoder() != null + && Context.getConfig().getBoolean("geocoder.onRequest")) { + address = Context.getGeocoder().getAddress(stop.getLatitude(), stop.getLongitude(), null); + } + stop.setAddress(address); + + stop.setEndTime(endStop.getFixTime()); + + long stopDuration = endStop.getFixTime().getTime() - startStop.getFixTime().getTime(); + stop.setDuration(stopDuration); + stop.setSpentFuel(calculateFuel(startStop, endStop)); + + long engineHours = 0; + if (startStop.getAttributes().containsKey(Position.KEY_HOURS) + && endStop.getAttributes().containsKey(Position.KEY_HOURS)) { + engineHours = endStop.getLong(Position.KEY_HOURS) - startStop.getLong(Position.KEY_HOURS); + } else if (Context.getConfig().getBoolean("processing.engineHours.enable")) { + // Temporary fallback for old data, to be removed in May 2019 + for (int i = startIndex + 1; i <= endIndex; i++) { + if (positions.get(i).getBoolean(Position.KEY_IGNITION) + && positions.get(i - 1).getBoolean(Position.KEY_IGNITION)) { + engineHours += positions.get(i).getFixTime().getTime() + - positions.get(i - 1).getFixTime().getTime(); + } + } + } + stop.setEngineHours(engineHours); + + if (!ignoreOdometer + && startStop.getDouble(Position.KEY_ODOMETER) != 0 + && endStop.getDouble(Position.KEY_ODOMETER) != 0) { + stop.setStartOdometer(startStop.getDouble(Position.KEY_ODOMETER)); + stop.setEndOdometer(endStop.getDouble(Position.KEY_ODOMETER)); + } else { + stop.setStartOdometer(startStop.getDouble(Position.KEY_TOTAL_DISTANCE)); + stop.setEndOdometer(endStop.getDouble(Position.KEY_TOTAL_DISTANCE)); + } + + return stop; + + } + + private static <T extends BaseReport> T calculateTripOrStop( + ArrayList<Position> positions, int startIndex, int endIndex, boolean ignoreOdometer, Class<T> reportClass) { + + if (reportClass.equals(TripReport.class)) { + return (T) calculateTrip(positions, startIndex, endIndex, ignoreOdometer); + } else { + return (T) calculateStop(positions, startIndex, endIndex, ignoreOdometer); + } + } + + private static boolean isMoving(ArrayList<Position> positions, int index, TripsConfig tripsConfig) { + if (tripsConfig.getMinimalNoDataDuration() > 0) { + boolean beforeGap = index < positions.size() - 1 + && positions.get(index + 1).getFixTime().getTime() - positions.get(index).getFixTime().getTime() + >= tripsConfig.getMinimalNoDataDuration(); + boolean afterGap = index > 0 + && positions.get(index).getFixTime().getTime() - positions.get(index - 1).getFixTime().getTime() + >= tripsConfig.getMinimalNoDataDuration(); + if (beforeGap || afterGap) { + return false; + } + } + if (positions.get(index).getAttributes().containsKey(Position.KEY_MOTION) + && positions.get(index).getAttributes().get(Position.KEY_MOTION) instanceof Boolean) { + return positions.get(index).getBoolean(Position.KEY_MOTION); + } else { + return positions.get(index).getSpeed() > tripsConfig.getSpeedThreshold(); + } + } + + public static <T extends BaseReport> Collection<T> detectTripsAndStops( + IdentityManager identityManager, DeviceManager deviceManager, + Collection<Position> positionCollection, + TripsConfig tripsConfig, boolean ignoreOdometer, Class<T> reportClass) { + + Collection<T> result = new ArrayList<>(); + + ArrayList<Position> positions = new ArrayList<>(positionCollection); + if (!positions.isEmpty()) { + boolean trips = reportClass.equals(TripReport.class); + MotionEventHandler motionHandler = new MotionEventHandler(identityManager, deviceManager, tripsConfig); + DeviceState deviceState = new DeviceState(); + deviceState.setMotionState(isMoving(positions, 0, tripsConfig)); + int startEventIndex = trips == deviceState.getMotionState() ? 0 : -1; + int startNoEventIndex = -1; + for (int i = 0; i < positions.size(); i++) { + Map<Event, Position> event = motionHandler.updateMotionState(deviceState, positions.get(i), + isMoving(positions, i, tripsConfig)); + if (startEventIndex == -1 + && (trips != deviceState.getMotionState() && deviceState.getMotionPosition() != null + || trips == deviceState.getMotionState() && event != null)) { + startEventIndex = i; + startNoEventIndex = -1; + } else if (trips != deviceState.getMotionState() && startEventIndex != -1 + && deviceState.getMotionPosition() == null && event == null) { + startEventIndex = -1; + } + if (startNoEventIndex == -1 + && (trips == deviceState.getMotionState() && deviceState.getMotionPosition() != null + || trips != deviceState.getMotionState() && event != null)) { + startNoEventIndex = i; + } else if (startNoEventIndex != -1 && deviceState.getMotionPosition() == null && event == null) { + startNoEventIndex = -1; + } + if (startEventIndex != -1 && startNoEventIndex != -1 && event != null + && trips != deviceState.getMotionState()) { + result.add(calculateTripOrStop(positions, startEventIndex, startNoEventIndex, + ignoreOdometer, reportClass)); + startEventIndex = -1; + } + } + if (startEventIndex != -1 && (startNoEventIndex != -1 || !trips)) { + result.add(calculateTripOrStop(positions, startEventIndex, + startNoEventIndex != -1 ? startNoEventIndex : positions.size() - 1, + ignoreOdometer, reportClass)); + } + } + + return result; + } + +} diff --git a/src/main/java/org/traccar/reports/Route.java b/src/main/java/org/traccar/reports/Route.java new file mode 100644 index 000000000..6adb00aae --- /dev/null +++ b/src/main/java/org/traccar/reports/Route.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import org.apache.poi.ss.util.WorkbookUtil; +import org.traccar.Context; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.model.Position; +import org.traccar.reports.model.DeviceReport; + +public final class Route { + + private Route() { + } + + public static Collection<Position> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws SQLException { + ReportUtils.checkPeriodLimit(from, to); + ArrayList<Position> result = new ArrayList<>(); + for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { + Context.getPermissionsManager().checkDevice(userId, deviceId); + result.addAll(Context.getDataManager().getPositions(deviceId, from, to)); + } + return result; + } + + public static void getExcel(OutputStream outputStream, + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws SQLException, IOException { + ReportUtils.checkPeriodLimit(from, to); + ArrayList<DeviceReport> devicesRoutes = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { + Context.getPermissionsManager().checkDevice(userId, deviceId); + Collection<Position> positions = Context.getDataManager() + .getPositions(deviceId, from, to); + DeviceReport deviceRoutes = new DeviceReport(); + Device device = Context.getIdentityManager().getById(deviceId); + deviceRoutes.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(deviceRoutes.getDeviceName())); + if (device.getGroupId() != 0) { + Group group = Context.getGroupsManager().getById(device.getGroupId()); + if (group != null) { + deviceRoutes.setGroupName(group.getName()); + } + } + deviceRoutes.setObjects(positions); + devicesRoutes.add(deviceRoutes); + } + String templatePath = Context.getConfig().getString("report.templatesPath", + "templates/export/"); + try (InputStream inputStream = new FileInputStream(templatePath + "/route.xlsx")) { + org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); + jxlsContext.putVar("devices", devicesRoutes); + jxlsContext.putVar("sheetNames", sheetNames); + jxlsContext.putVar("from", from); + jxlsContext.putVar("to", to); + ReportUtils.processTemplateWithSheets(inputStream, outputStream, jxlsContext); + } + } +} diff --git a/src/main/java/org/traccar/reports/Stops.java b/src/main/java/org/traccar/reports/Stops.java new file mode 100644 index 000000000..2036b0641 --- /dev/null +++ b/src/main/java/org/traccar/reports/Stops.java @@ -0,0 +1,102 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.traccar.reports; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import org.apache.poi.ss.util.WorkbookUtil; +import org.traccar.Context; +import org.traccar.Main; +import org.traccar.database.DeviceManager; +import org.traccar.database.IdentityManager; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.reports.model.DeviceReport; +import org.traccar.reports.model.StopReport; + +public final class Stops { + + private Stops() { + } + + private static Collection<StopReport> detectStops(long deviceId, Date from, Date to) throws SQLException { + boolean ignoreOdometer = Context.getDeviceManager() + .lookupAttributeBoolean(deviceId, "report.ignoreOdometer", false, false, true); + + IdentityManager identityManager = Main.getInjector().getInstance(IdentityManager.class); + DeviceManager deviceManager = Main.getInjector().getInstance(DeviceManager.class); + + return ReportUtils.detectTripsAndStops( + identityManager, deviceManager, Context.getDataManager().getPositions(deviceId, from, to), + Context.getTripsConfig(), ignoreOdometer, StopReport.class); + } + + public static Collection<StopReport> getObjects( + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws SQLException { + ReportUtils.checkPeriodLimit(from, to); + ArrayList<StopReport> result = new ArrayList<>(); + for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { + Context.getPermissionsManager().checkDevice(userId, deviceId); + result.addAll(detectStops(deviceId, from, to)); + } + return result; + } + + public static void getExcel( + OutputStream outputStream, long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws SQLException, IOException { + ReportUtils.checkPeriodLimit(from, to); + ArrayList<DeviceReport> devicesStops = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { + Context.getPermissionsManager().checkDevice(userId, deviceId); + Collection<StopReport> stops = detectStops(deviceId, from, to); + DeviceReport deviceStops = new DeviceReport(); + Device device = Context.getIdentityManager().getById(deviceId); + deviceStops.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(deviceStops.getDeviceName())); + if (device.getGroupId() != 0) { + Group group = Context.getGroupsManager().getById(device.getGroupId()); + if (group != null) { + deviceStops.setGroupName(group.getName()); + } + } + deviceStops.setObjects(stops); + devicesStops.add(deviceStops); + } + String templatePath = Context.getConfig().getString("report.templatesPath", + "templates/export/"); + try (InputStream inputStream = new FileInputStream(templatePath + "/stops.xlsx")) { + org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); + jxlsContext.putVar("devices", devicesStops); + jxlsContext.putVar("sheetNames", sheetNames); + jxlsContext.putVar("from", from); + jxlsContext.putVar("to", to); + ReportUtils.processTemplateWithSheets(inputStream, outputStream, jxlsContext); + } + } + +} diff --git a/src/main/java/org/traccar/reports/Summary.java b/src/main/java/org/traccar/reports/Summary.java new file mode 100644 index 000000000..6d179a873 --- /dev/null +++ b/src/main/java/org/traccar/reports/Summary.java @@ -0,0 +1,117 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import org.jxls.util.JxlsHelper; +import org.traccar.Context; +import org.traccar.model.Position; +import org.traccar.reports.model.SummaryReport; + +public final class Summary { + + private Summary() { + } + + private static SummaryReport calculateSummaryResult(long deviceId, Date from, Date to) throws SQLException { + SummaryReport result = new SummaryReport(); + result.setDeviceId(deviceId); + result.setDeviceName(Context.getIdentityManager().getById(deviceId).getName()); + Collection<Position> positions = Context.getDataManager().getPositions(deviceId, from, to); + if (positions != null && !positions.isEmpty()) { + Position firstPosition = null; + Position previousPosition = null; + double speedSum = 0; + boolean engineHoursEnabled = Context.getConfig().getBoolean("processing.engineHours.enable"); + for (Position position : positions) { + if (firstPosition == null) { + firstPosition = position; + } + if (engineHoursEnabled && previousPosition != null + && position.getBoolean(Position.KEY_IGNITION) + && previousPosition.getBoolean(Position.KEY_IGNITION)) { + // Temporary fallback for old data, to be removed in May 2019 + result.addEngineHours(position.getFixTime().getTime() + - previousPosition.getFixTime().getTime()); + } + previousPosition = position; + speedSum += position.getSpeed(); + result.setMaxSpeed(position.getSpeed()); + } + boolean ignoreOdometer = Context.getDeviceManager() + .lookupAttributeBoolean(deviceId, "report.ignoreOdometer", false, false, true); + result.setDistance(ReportUtils.calculateDistance(firstPosition, previousPosition, !ignoreOdometer)); + result.setAverageSpeed(speedSum / positions.size()); + result.setSpentFuel(ReportUtils.calculateFuel(firstPosition, previousPosition)); + + if (engineHoursEnabled + && firstPosition.getAttributes().containsKey(Position.KEY_HOURS) + && previousPosition.getAttributes().containsKey(Position.KEY_HOURS)) { + result.setEngineHours( + previousPosition.getLong(Position.KEY_HOURS) - firstPosition.getLong(Position.KEY_HOURS)); + } + + if (!ignoreOdometer + && firstPosition.getDouble(Position.KEY_ODOMETER) != 0 + && previousPosition.getDouble(Position.KEY_ODOMETER) != 0) { + result.setStartOdometer(firstPosition.getDouble(Position.KEY_ODOMETER)); + result.setEndOdometer(previousPosition.getDouble(Position.KEY_ODOMETER)); + } else { + result.setStartOdometer(firstPosition.getDouble(Position.KEY_TOTAL_DISTANCE)); + result.setEndOdometer(previousPosition.getDouble(Position.KEY_TOTAL_DISTANCE)); + } + + } + return result; + } + + public static Collection<SummaryReport> getObjects(long userId, Collection<Long> deviceIds, + Collection<Long> groupIds, Date from, Date to) throws SQLException { + ReportUtils.checkPeriodLimit(from, to); + ArrayList<SummaryReport> result = new ArrayList<>(); + for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { + Context.getPermissionsManager().checkDevice(userId, deviceId); + result.add(calculateSummaryResult(deviceId, from, to)); + } + return result; + } + + public static void getExcel(OutputStream outputStream, + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws SQLException, IOException { + ReportUtils.checkPeriodLimit(from, to); + Collection<SummaryReport> summaries = getObjects(userId, deviceIds, groupIds, from, to); + String templatePath = Context.getConfig().getString("report.templatesPath", + "templates/export/"); + try (InputStream inputStream = new FileInputStream(templatePath + "/summary.xlsx")) { + org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); + jxlsContext.putVar("summaries", summaries); + jxlsContext.putVar("from", from); + jxlsContext.putVar("to", to); + JxlsHelper.getInstance().setUseFastFormulaProcessor(false) + .processTemplate(inputStream, outputStream, jxlsContext); + } + } +} diff --git a/src/main/java/org/traccar/reports/Trips.java b/src/main/java/org/traccar/reports/Trips.java new file mode 100644 index 000000000..7c0cd6921 --- /dev/null +++ b/src/main/java/org/traccar/reports/Trips.java @@ -0,0 +1,100 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; + +import org.apache.poi.ss.util.WorkbookUtil; +import org.traccar.Context; +import org.traccar.Main; +import org.traccar.database.DeviceManager; +import org.traccar.database.IdentityManager; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.reports.model.DeviceReport; +import org.traccar.reports.model.TripReport; + +public final class Trips { + + private Trips() { + } + + private static Collection<TripReport> detectTrips(long deviceId, Date from, Date to) throws SQLException { + boolean ignoreOdometer = Context.getDeviceManager() + .lookupAttributeBoolean(deviceId, "report.ignoreOdometer", false, false, true); + + IdentityManager identityManager = Main.getInjector().getInstance(IdentityManager.class); + DeviceManager deviceManager = Main.getInjector().getInstance(DeviceManager.class); + + return ReportUtils.detectTripsAndStops( + identityManager, deviceManager, Context.getDataManager().getPositions(deviceId, from, to), + Context.getTripsConfig(), ignoreOdometer, TripReport.class); + } + + public static Collection<TripReport> getObjects(long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws SQLException { + ReportUtils.checkPeriodLimit(from, to); + ArrayList<TripReport> result = new ArrayList<>(); + for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { + Context.getPermissionsManager().checkDevice(userId, deviceId); + result.addAll(detectTrips(deviceId, from, to)); + } + return result; + } + + public static void getExcel(OutputStream outputStream, + long userId, Collection<Long> deviceIds, Collection<Long> groupIds, + Date from, Date to) throws SQLException, IOException { + ReportUtils.checkPeriodLimit(from, to); + ArrayList<DeviceReport> devicesTrips = new ArrayList<>(); + ArrayList<String> sheetNames = new ArrayList<>(); + for (long deviceId: ReportUtils.getDeviceList(deviceIds, groupIds)) { + Context.getPermissionsManager().checkDevice(userId, deviceId); + Collection<TripReport> trips = detectTrips(deviceId, from, to); + DeviceReport deviceTrips = new DeviceReport(); + Device device = Context.getIdentityManager().getById(deviceId); + deviceTrips.setDeviceName(device.getName()); + sheetNames.add(WorkbookUtil.createSafeSheetName(deviceTrips.getDeviceName())); + if (device.getGroupId() != 0) { + Group group = Context.getGroupsManager().getById(device.getGroupId()); + if (group != null) { + deviceTrips.setGroupName(group.getName()); + } + } + deviceTrips.setObjects(trips); + devicesTrips.add(deviceTrips); + } + String templatePath = Context.getConfig().getString("report.templatesPath", + "templates/export/"); + try (InputStream inputStream = new FileInputStream(templatePath + "/trips.xlsx")) { + org.jxls.common.Context jxlsContext = ReportUtils.initializeContext(userId); + jxlsContext.putVar("devices", devicesTrips); + jxlsContext.putVar("sheetNames", sheetNames); + jxlsContext.putVar("from", from); + jxlsContext.putVar("to", to); + ReportUtils.processTemplateWithSheets(inputStream, outputStream, jxlsContext); + } + } + +} diff --git a/src/main/java/org/traccar/reports/model/BaseReport.java b/src/main/java/org/traccar/reports/model/BaseReport.java new file mode 100644 index 000000000..9f2d1188c --- /dev/null +++ b/src/main/java/org/traccar/reports/model/BaseReport.java @@ -0,0 +1,106 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports.model; + +public class BaseReport { + + private long deviceId; + + public long getDeviceId() { + return deviceId; + } + + public void setDeviceId(long deviceId) { + this.deviceId = deviceId; + } + + private String deviceName; + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + private double distance; + + public double getDistance() { + return distance; + } + + public void setDistance(double distance) { + this.distance = distance; + } + + public void addDistance(double distance) { + this.distance += distance; + } + + private double averageSpeed; + + public double getAverageSpeed() { + return averageSpeed; + } + + public void setAverageSpeed(Double averageSpeed) { + this.averageSpeed = averageSpeed; + } + + private double maxSpeed; + + public double getMaxSpeed() { + return maxSpeed; + } + + public void setMaxSpeed(double maxSpeed) { + if (maxSpeed > this.maxSpeed) { + this.maxSpeed = maxSpeed; + } + } + + private double spentFuel; + + public double getSpentFuel() { + return spentFuel; + } + + public void setSpentFuel(double spentFuel) { + this.spentFuel = spentFuel; + } + + private double startOdometer; + + public double getStartOdometer() { + return startOdometer; + } + + public void setStartOdometer(double startOdometer) { + this.startOdometer = startOdometer; + } + private double endOdometer; + + public double getEndOdometer() { + return endOdometer; + } + + public void setEndOdometer(double endOdometer) { + this.endOdometer = endOdometer; + } + +} diff --git a/src/main/java/org/traccar/reports/model/DeviceReport.java b/src/main/java/org/traccar/reports/model/DeviceReport.java new file mode 100644 index 000000000..932753d15 --- /dev/null +++ b/src/main/java/org/traccar/reports/model/DeviceReport.java @@ -0,0 +1,55 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports.model; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class DeviceReport { + + private String deviceName; + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + private String groupName = ""; + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + private List<?> objects; + + public Collection<?> getObjects() { + return objects; + } + + public void setObjects(Collection<?> objects) { + this.objects = new ArrayList<>(objects); + } + +} diff --git a/src/main/java/org/traccar/reports/model/StopReport.java b/src/main/java/org/traccar/reports/model/StopReport.java new file mode 100644 index 000000000..245292b63 --- /dev/null +++ b/src/main/java/org/traccar/reports/model/StopReport.java @@ -0,0 +1,106 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports.model; + +import java.util.Date; + +public class StopReport extends BaseReport { + + private long positionId; + + public long getPositionId() { + return positionId; + } + + public void setPositionId(long positionId) { + this.positionId = positionId; + } + + private double latitude; + + public double getLatitude() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + private double longitude; + + public double getLongitude() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + private Date startTime; + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + private Date endTime; + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + + private String address; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + private long duration; + + public long getDuration() { + return duration; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + private long engineHours; // milliseconds + + public long getEngineHours() { + return engineHours; + } + + public void setEngineHours(long engineHours) { + this.engineHours = engineHours; + } + + public void addEngineHours(long engineHours) { + this.engineHours += engineHours; + } +} diff --git a/src/main/java/org/traccar/reports/model/SummaryReport.java b/src/main/java/org/traccar/reports/model/SummaryReport.java new file mode 100644 index 000000000..6f9e9459f --- /dev/null +++ b/src/main/java/org/traccar/reports/model/SummaryReport.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports.model; + +public class SummaryReport extends BaseReport { + + private long engineHours; // milliseconds + + public long getEngineHours() { + return engineHours; + } + + public void setEngineHours(long engineHours) { + this.engineHours = engineHours; + } + + public void addEngineHours(long engineHours) { + this.engineHours += engineHours; + } +} diff --git a/src/main/java/org/traccar/reports/model/TripReport.java b/src/main/java/org/traccar/reports/model/TripReport.java new file mode 100644 index 000000000..3140f3019 --- /dev/null +++ b/src/main/java/org/traccar/reports/model/TripReport.java @@ -0,0 +1,152 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports.model; + +import java.util.Date; + +public class TripReport extends BaseReport { + + private long startPositionId; + + public long getStartPositionId() { + return startPositionId; + } + + public void setStartPositionId(long startPositionId) { + this.startPositionId = startPositionId; + } + + private long endPositionId; + + public long getEndPositionId() { + return endPositionId; + } + + public void setEndPositionId(long endPositionId) { + this.endPositionId = endPositionId; + } + + private double startLat; + + public double getStartLat() { + return startLat; + } + + public void setStartLat(double startLat) { + this.startLat = startLat; + } + + private double startLon; + + public double getStartLon() { + return startLon; + } + + public void setStartLon(double startLon) { + this.startLon = startLon; + } + + private double endLat; + + public double getEndLat() { + return endLat; + } + + public void setEndLat(double endLat) { + this.endLat = endLat; + } + + private double endLon; + + public double getEndLon() { + return endLon; + } + + public void setEndLon(double endLon) { + this.endLon = endLon; + } + + private Date startTime; + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + private String startAddress; + + public String getStartAddress() { + return startAddress; + } + + public void setStartAddress(String address) { + this.startAddress = address; + } + + private Date endTime; + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + + private String endAddress; + + public String getEndAddress() { + return endAddress; + } + + public void setEndAddress(String address) { + this.endAddress = address; + } + + private long duration; + + public long getDuration() { + return duration; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + private String driverUniqueId; + + public String getDriverUniqueId() { + return driverUniqueId; + } + + public void setDriverUniqueId(String driverUniqueId) { + this.driverUniqueId = driverUniqueId; + } + + private String driverName; + + public String getDriverName() { + return driverName; + } + + public void setDriverName(String driverName) { + this.driverName = driverName; + } +} diff --git a/src/main/java/org/traccar/reports/model/TripsConfig.java b/src/main/java/org/traccar/reports/model/TripsConfig.java new file mode 100644 index 000000000..0f0c615d3 --- /dev/null +++ b/src/main/java/org/traccar/reports/model/TripsConfig.java @@ -0,0 +1,105 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.reports.model; + +public class TripsConfig { + + public TripsConfig() { + } + + public TripsConfig(double minimalTripDistance, long minimalTripDuration, long minimalParkingDuration, + long minimalNoDataDuration, boolean useIgnition, boolean processInvalidPositions, double speedThreshold) { + this.minimalTripDistance = minimalTripDistance; + this.minimalTripDuration = minimalTripDuration; + this.minimalParkingDuration = minimalParkingDuration; + this.minimalNoDataDuration = minimalNoDataDuration; + this.useIgnition = useIgnition; + this.processInvalidPositions = processInvalidPositions; + this.speedThreshold = speedThreshold; + } + + private double minimalTripDistance; + + public double getMinimalTripDistance() { + return minimalTripDistance; + } + + public void setMinimalTripDistance(double minimalTripDistance) { + this.minimalTripDistance = minimalTripDistance; + } + + private long minimalTripDuration; + + public long getMinimalTripDuration() { + return minimalTripDuration; + } + + public void setMinimalTripDuration(long minimalTripDuration) { + this.minimalTripDuration = minimalTripDuration; + } + + private long minimalParkingDuration; + + public long getMinimalParkingDuration() { + return minimalParkingDuration; + } + + public void setMinimalParkingDuration(long minimalParkingDuration) { + this.minimalParkingDuration = minimalParkingDuration; + } + + private long minimalNoDataDuration; + + public long getMinimalNoDataDuration() { + return minimalNoDataDuration; + } + + public void setMinimalNoDataDuration(long minimalNoDataDuration) { + this.minimalNoDataDuration = minimalNoDataDuration; + } + + private boolean useIgnition; + + public boolean getUseIgnition() { + return useIgnition; + } + + public void setUseIgnition(boolean useIgnition) { + this.useIgnition = useIgnition; + } + + private boolean processInvalidPositions; + + public boolean getProcessInvalidPositions() { + return processInvalidPositions; + } + + public void setProcessInvalidPositions(boolean processInvalidPositions) { + this.processInvalidPositions = processInvalidPositions; + } + + private double speedThreshold; + + public double getSpeedThreshold() { + return speedThreshold; + } + + public void setSpeedThreshold(double speedThreshold) { + this.speedThreshold = speedThreshold; + } + +} diff --git a/src/main/java/org/traccar/sms/HttpSmsClient.java b/src/main/java/org/traccar/sms/HttpSmsClient.java new file mode 100644 index 000000000..8e2b67bf7 --- /dev/null +++ b/src/main/java/org/traccar/sms/HttpSmsClient.java @@ -0,0 +1,110 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.sms; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.InvocationCallback; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.api.SecurityRequestFilter; +import org.traccar.helper.DataConverter; +import org.traccar.notification.MessageException; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +public class HttpSmsClient implements SmsManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(HttpSmsClient.class); + + private String url; + private String authorizationHeader; + private String authorization; + private String template; + private boolean encode; + private MediaType mediaType; + + public HttpSmsClient() { + url = Context.getConfig().getString("sms.http.url"); + authorizationHeader = Context.getConfig().getString("sms.http.authorizationHeader", + SecurityRequestFilter.AUTHORIZATION_HEADER); + authorization = Context.getConfig().getString("sms.http.authorization"); + if (authorization == null) { + String user = Context.getConfig().getString("sms.http.user"); + String password = Context.getConfig().getString("sms.http.password"); + authorization = "Basic " + + DataConverter.printBase64((user + ":" + password).getBytes(StandardCharsets.UTF_8)); + } + template = Context.getConfig().getString("sms.http.template").trim(); + if (template.charAt(0) == '{' || template.charAt(0) == '[') { + encode = false; + mediaType = MediaType.APPLICATION_JSON_TYPE; + } else { + encode = true; + mediaType = MediaType.APPLICATION_FORM_URLENCODED_TYPE; + } + } + + private String prepareValue(String value) throws UnsupportedEncodingException { + return encode ? URLEncoder.encode(value, StandardCharsets.UTF_8.name()) : value; + } + + private String preparePayload(String destAddress, String message) { + try { + return template + .replace("{phone}", prepareValue(destAddress)) + .replace("{message}", prepareValue(message.trim())); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + private Invocation.Builder getRequestBuilder() { + return Context.getClient().target(url).request() + .header(authorizationHeader, authorization); + } + + @Override + public void sendMessageSync(String destAddress, String message, boolean command) throws MessageException { + Response response = getRequestBuilder().post(Entity.entity(preparePayload(destAddress, message), mediaType)); + if (response.getStatus() / 100 != 2) { + throw new MessageException(response.readEntity(String.class)); + } + } + + @Override + public void sendMessageAsync(final String destAddress, final String message, final boolean command) { + getRequestBuilder().async().post( + Entity.entity(preparePayload(destAddress, message), mediaType), new InvocationCallback<String>() { + @Override + public void completed(String s) { + } + + @Override + public void failed(Throwable throwable) { + LOGGER.warn("SMS send failed", throwable); + } + }); + } + +} diff --git a/src/main/java/org/traccar/sms/SmsManager.java b/src/main/java/org/traccar/sms/SmsManager.java new file mode 100644 index 000000000..3b0cbda7f --- /dev/null +++ b/src/main/java/org/traccar/sms/SmsManager.java @@ -0,0 +1,29 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.sms; + +import org.traccar.notification.MessageException; + +public interface SmsManager { + + void sendMessageSync( + String destAddress, String message, boolean command) throws InterruptedException, MessageException; + + void sendMessageAsync( + String destAddress, String message, boolean command); + +} diff --git a/src/main/java/org/traccar/sms/smpp/ClientSmppSessionHandler.java b/src/main/java/org/traccar/sms/smpp/ClientSmppSessionHandler.java new file mode 100644 index 000000000..6b9de3107 --- /dev/null +++ b/src/main/java/org/traccar/sms/smpp/ClientSmppSessionHandler.java @@ -0,0 +1,82 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.sms.smpp; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.cloudhopper.commons.charset.CharsetUtil; +import com.cloudhopper.smpp.SmppConstants; +import com.cloudhopper.smpp.impl.DefaultSmppSessionHandler; +import com.cloudhopper.smpp.pdu.DeliverSm; +import com.cloudhopper.smpp.pdu.PduRequest; +import com.cloudhopper.smpp.pdu.PduResponse; +import com.cloudhopper.smpp.util.SmppUtil; + +public class ClientSmppSessionHandler extends DefaultSmppSessionHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(ClientSmppSessionHandler.class); + + private SmppClient smppClient; + + public ClientSmppSessionHandler(SmppClient smppClient) { + this.smppClient = smppClient; + } + + @Override + public void firePduRequestExpired(PduRequest pduRequest) { + LOGGER.warn("PDU request expired: " + pduRequest); + } + + @Override + public PduResponse firePduRequestReceived(PduRequest request) { + PduResponse response; + try { + if (request instanceof DeliverSm) { + String sourceAddress = ((DeliverSm) request).getSourceAddress().getAddress(); + String message = CharsetUtil.decode(((DeliverSm) request).getShortMessage(), + smppClient.mapDataCodingToCharset(((DeliverSm) request).getDataCoding())); + LOGGER.info("SMS Message Received: " + message.trim() + ", Source Address: " + sourceAddress); + + boolean isDeliveryReceipt; + if (smppClient.getDetectDlrByOpts()) { + isDeliveryReceipt = request.getOptionalParameters() != null; + } else { + isDeliveryReceipt = SmppUtil.isMessageTypeAnyDeliveryReceipt(((DeliverSm) request).getEsmClass()); + } + + if (!isDeliveryReceipt) { + TextMessageEventHandler.handleTextMessage(sourceAddress, message); + } + } + response = request.createResponse(); + } catch (Exception error) { + LOGGER.warn("SMS receiving error", error); + response = request.createResponse(); + response.setResultMessage(error.getMessage()); + response.setCommandStatus(SmppConstants.STATUS_UNKNOWNERR); + } + return response; + } + + @Override + public void fireChannelUnexpectedlyClosed() { + LOGGER.warn("SMPP session channel unexpectedly closed"); + smppClient.scheduleReconnect(); + } + +} diff --git a/src/main/java/org/traccar/sms/smpp/EnquireLinkTask.java b/src/main/java/org/traccar/sms/smpp/EnquireLinkTask.java new file mode 100644 index 000000000..7086709d7 --- /dev/null +++ b/src/main/java/org/traccar/sms/smpp/EnquireLinkTask.java @@ -0,0 +1,59 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.sms.smpp; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.cloudhopper.smpp.SmppSession; +import com.cloudhopper.smpp.pdu.EnquireLink; +import com.cloudhopper.smpp.type.RecoverablePduException; +import com.cloudhopper.smpp.type.SmppChannelException; +import com.cloudhopper.smpp.type.SmppTimeoutException; +import com.cloudhopper.smpp.type.UnrecoverablePduException; + +public class EnquireLinkTask implements Runnable { + + private static final Logger LOGGER = LoggerFactory.getLogger(EnquireLinkTask.class); + + private SmppClient smppClient; + private Integer enquireLinkTimeout; + + public EnquireLinkTask(SmppClient smppClient, Integer enquireLinkTimeout) { + this.smppClient = smppClient; + this.enquireLinkTimeout = enquireLinkTimeout; + } + + @Override + public void run() { + SmppSession smppSession = smppClient.getSession(); + if (smppSession != null && smppSession.isBound()) { + try { + smppSession.enquireLink(new EnquireLink(), enquireLinkTimeout); + } catch (SmppTimeoutException | SmppChannelException + | RecoverablePduException | UnrecoverablePduException error) { + LOGGER.warn("Enquire link failed, executing reconnect: ", error); + smppClient.scheduleReconnect(); + } catch (InterruptedException error) { + LOGGER.info("Enquire link interrupted, probably killed by reconnecting"); + } + } else { + LOGGER.warn("Enquire link running while session is not connected"); + } + } + +} diff --git a/src/main/java/org/traccar/sms/smpp/ReconnectionTask.java b/src/main/java/org/traccar/sms/smpp/ReconnectionTask.java new file mode 100644 index 000000000..c009de8e7 --- /dev/null +++ b/src/main/java/org/traccar/sms/smpp/ReconnectionTask.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.sms.smpp; + +public class ReconnectionTask implements Runnable { + + private final SmppClient smppClient; + + protected ReconnectionTask(SmppClient smppClient) { + this.smppClient = smppClient; + } + + @Override + public void run() { + smppClient.reconnect(); + } + +} diff --git a/src/main/java/org/traccar/sms/smpp/SmppClient.java b/src/main/java/org/traccar/sms/smpp/SmppClient.java new file mode 100644 index 000000000..874253d36 --- /dev/null +++ b/src/main/java/org/traccar/sms/smpp/SmppClient.java @@ -0,0 +1,272 @@ +/* + * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.sms.smpp; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.notification.MessageException; +import org.traccar.sms.SmsManager; + +import com.cloudhopper.commons.charset.CharsetUtil; +import com.cloudhopper.smpp.SmppBindType; +import com.cloudhopper.smpp.SmppConstants; +import com.cloudhopper.smpp.SmppSession; +import com.cloudhopper.smpp.SmppSessionConfiguration; +import com.cloudhopper.smpp.impl.DefaultSmppClient; +import com.cloudhopper.smpp.impl.DefaultSmppSessionHandler; +import com.cloudhopper.smpp.pdu.SubmitSm; +import com.cloudhopper.smpp.pdu.SubmitSmResp; +import com.cloudhopper.smpp.tlv.Tlv; +import com.cloudhopper.smpp.type.Address; +import com.cloudhopper.smpp.type.RecoverablePduException; +import com.cloudhopper.smpp.type.SmppChannelException; +import com.cloudhopper.smpp.type.SmppTimeoutException; +import com.cloudhopper.smpp.type.UnrecoverablePduException; + +public class SmppClient implements SmsManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(SmppClient.class); + + private SmppSessionConfiguration sessionConfig = new SmppSessionConfiguration(); + private SmppSession smppSession; + private DefaultSmppSessionHandler sessionHandler = new ClientSmppSessionHandler(this); + private ExecutorService executorService = Executors.newCachedThreadPool(); + private DefaultSmppClient clientBootstrap = new DefaultSmppClient(); + + private ScheduledExecutorService enquireLinkExecutor; + private ScheduledFuture<?> enquireLinkTask; + private Integer enquireLinkPeriod; + private Integer enquireLinkTimeout; + + private ScheduledExecutorService reconnectionExecutor; + private ScheduledFuture<?> reconnectionTask; + private Integer reconnectionDelay; + + private String sourceAddress; + private String commandSourceAddress; + private int submitTimeout; + private boolean requestDlr; + private boolean detectDlrByOpts; + private String notificationsCharsetName; + private byte notificationsDataCoding; + private String commandsCharsetName; + private byte commandsDataCoding; + + private byte sourceTon; + private byte sourceNpi; + private byte commandSourceTon; + private byte commandSourceNpi; + + private byte destTon; + private byte destNpi; + + public SmppClient() { + sessionConfig.setName("Traccar.smppSession"); + sessionConfig.setInterfaceVersion( + (byte) Context.getConfig().getInteger("sms.smpp.version", SmppConstants.VERSION_3_4)); + sessionConfig.setType(SmppBindType.TRANSCEIVER); + sessionConfig.setHost(Context.getConfig().getString("sms.smpp.host", "localhost")); + sessionConfig.setPort(Context.getConfig().getInteger("sms.smpp.port", 2775)); + sessionConfig.setSystemId(Context.getConfig().getString("sms.smpp.username", "user")); + sessionConfig.setSystemType(Context.getConfig().getString("sms.smpp.systemType", null)); + sessionConfig.setPassword(Context.getConfig().getString("sms.smpp.password", "password")); + sessionConfig.getLoggingOptions().setLogBytes(false); + sessionConfig.getLoggingOptions().setLogPdu(Context.getConfig().getBoolean("sms.smpp.logPdu")); + + sourceAddress = Context.getConfig().getString("sms.smpp.sourceAddress", ""); + commandSourceAddress = Context.getConfig().getString("sms.smpp.commandSourceAddress", sourceAddress); + submitTimeout = Context.getConfig().getInteger("sms.smpp.submitTimeout", 10000); + + requestDlr = Context.getConfig().getBoolean("sms.smpp.requestDlr"); + detectDlrByOpts = Context.getConfig().getBoolean("sms.smpp.detectDlrByOpts"); + + notificationsCharsetName = Context.getConfig().getString("sms.smpp.notificationsCharset", + CharsetUtil.NAME_UCS_2); + notificationsDataCoding = (byte) Context.getConfig().getInteger("sms.smpp.notificationsDataCoding", + SmppConstants.DATA_CODING_UCS2); + commandsCharsetName = Context.getConfig().getString("sms.smpp.commandsCharset", + CharsetUtil.NAME_GSM); + commandsDataCoding = (byte) Context.getConfig().getInteger("sms.smpp.commandsDataCoding", + SmppConstants.DATA_CODING_DEFAULT); + + + sourceTon = (byte) Context.getConfig().getInteger("sms.smpp.sourceTon", SmppConstants.TON_ALPHANUMERIC); + commandSourceTon = (byte) Context.getConfig().getInteger("sms.smpp.commandSourceTon", sourceTon); + sourceNpi = (byte) Context.getConfig().getInteger("sms.smpp.sourceNpi", SmppConstants.NPI_UNKNOWN); + commandSourceNpi = (byte) Context.getConfig().getInteger("sms.smpp.commandSourceNpi", sourceNpi); + + destTon = (byte) Context.getConfig().getInteger("sms.smpp.destTon", SmppConstants.TON_INTERNATIONAL); + destNpi = (byte) Context.getConfig().getInteger("sms.smpp.destNpi", SmppConstants.NPI_E164); + + enquireLinkPeriod = Context.getConfig().getInteger("sms.smpp.enquireLinkPeriod", 60000); + enquireLinkTimeout = Context.getConfig().getInteger("sms.smpp.enquireLinkTimeout", 10000); + enquireLinkExecutor = Executors.newScheduledThreadPool(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(runnable); + String name = sessionConfig.getName(); + thread.setName("EnquireLink-" + name); + return thread; + } + }); + + reconnectionDelay = Context.getConfig().getInteger("sms.smpp.reconnectionDelay", 10000); + reconnectionExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(runnable); + String name = sessionConfig.getName(); + thread.setName("Reconnection-" + name); + return thread; + } + }); + + scheduleReconnect(); + } + + public synchronized SmppSession getSession() { + return smppSession; + } + + public String mapDataCodingToCharset(byte dataCoding) { + switch (dataCoding) { + case SmppConstants.DATA_CODING_LATIN1: + return CharsetUtil.NAME_ISO_8859_1; + case SmppConstants.DATA_CODING_UCS2: + return CharsetUtil.NAME_UCS_2; + default: + return CharsetUtil.NAME_GSM; + } + } + + public boolean getDetectDlrByOpts() { + return detectDlrByOpts; + } + + protected synchronized void reconnect() { + try { + disconnect(); + smppSession = clientBootstrap.bind(sessionConfig, sessionHandler); + stopReconnectionkTask(); + runEnquireLinkTask(); + LOGGER.info("SMPP session connected"); + } catch (SmppTimeoutException | SmppChannelException + | UnrecoverablePduException | InterruptedException error) { + LOGGER.warn("Unable to connect to SMPP server: ", error); + } + } + + public void scheduleReconnect() { + if (reconnectionTask == null || reconnectionTask.isDone()) { + reconnectionTask = reconnectionExecutor.scheduleWithFixedDelay( + new ReconnectionTask(this), + reconnectionDelay, reconnectionDelay, TimeUnit.MILLISECONDS); + } + } + + private void stopReconnectionkTask() { + if (reconnectionTask != null) { + reconnectionTask.cancel(false); + } + } + + private void disconnect() { + stopEnquireLinkTask(); + destroySession(); + } + + private void runEnquireLinkTask() { + enquireLinkTask = enquireLinkExecutor.scheduleWithFixedDelay( + new EnquireLinkTask(this, enquireLinkTimeout), + enquireLinkPeriod, enquireLinkPeriod, TimeUnit.MILLISECONDS); + } + + private void stopEnquireLinkTask() { + if (enquireLinkTask != null) { + enquireLinkTask.cancel(true); + } + } + + private void destroySession() { + if (smppSession != null) { + LOGGER.info("Cleaning up SMPP session... "); + smppSession.destroy(); + smppSession = null; + } + } + + @Override + public synchronized void sendMessageSync(String destAddress, String message, boolean command) + throws MessageException, InterruptedException, IllegalStateException { + if (getSession() != null && getSession().isBound()) { + try { + SubmitSm submit = new SubmitSm(); + byte[] textBytes; + textBytes = CharsetUtil.encode(message, command ? commandsCharsetName : notificationsCharsetName); + submit.setDataCoding(command ? commandsDataCoding : notificationsDataCoding); + if (requestDlr) { + submit.setRegisteredDelivery(SmppConstants.REGISTERED_DELIVERY_SMSC_RECEIPT_REQUESTED); + } + + if (textBytes != null && textBytes.length > 255) { + submit.addOptionalParameter(new Tlv(SmppConstants.TAG_MESSAGE_PAYLOAD, textBytes, + "message_payload")); + } else { + submit.setShortMessage(textBytes); + } + + submit.setSourceAddress(command ? new Address(commandSourceTon, commandSourceNpi, commandSourceAddress) + : new Address(sourceTon, sourceNpi, sourceAddress)); + submit.setDestAddress(new Address(destTon, destNpi, destAddress)); + SubmitSmResp submitResponce = getSession().submit(submit, submitTimeout); + if (submitResponce.getCommandStatus() == SmppConstants.STATUS_OK) { + LOGGER.info("SMS submitted, message id: " + submitResponce.getMessageId()); + } else { + throw new IllegalStateException(submitResponce.getResultMessage()); + } + } catch (SmppChannelException | RecoverablePduException + | SmppTimeoutException | UnrecoverablePduException error) { + throw new MessageException(error); + } + } else { + throw new MessageException(new SmppChannelException("SMPP session is not connected")); + } + } + + @Override + public void sendMessageAsync(final String destAddress, final String message, final boolean command) { + executorService.execute(new Runnable() { + @Override + public void run() { + try { + sendMessageSync(destAddress, message, command); + } catch (MessageException | InterruptedException | IllegalStateException error) { + LOGGER.warn("SMS sending error", error); + } + } + }); + } + +} diff --git a/src/main/java/org/traccar/sms/smpp/TextMessageEventHandler.java b/src/main/java/org/traccar/sms/smpp/TextMessageEventHandler.java new file mode 100644 index 000000000..37c29972d --- /dev/null +++ b/src/main/java/org/traccar/sms/smpp/TextMessageEventHandler.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.sms.smpp; + +import org.traccar.Context; +import org.traccar.model.Device; +import org.traccar.model.Event; + +public final class TextMessageEventHandler { + + private TextMessageEventHandler() { + } + + public static void handleTextMessage(String phone, String message) { + Device device = Context.getDeviceManager().getDeviceByPhone(phone); + if (device != null && Context.getNotificationManager() != null) { + Event event = new Event(Event.TYPE_TEXT_MESSAGE, device.getId()); + event.set("message", message); + Context.getNotificationManager().updateEvent(event, null); + } + } + +} diff --git a/src/main/java/org/traccar/web/ConsoleServlet.java b/src/main/java/org/traccar/web/ConsoleServlet.java new file mode 100644 index 000000000..2b38a935a --- /dev/null +++ b/src/main/java/org/traccar/web/ConsoleServlet.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web; + +import org.h2.server.web.ConnectionInfo; +import org.h2.server.web.WebServlet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class ConsoleServlet extends WebServlet { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConsoleServlet.class); + + @Override + public void init() { + super.init(); + + try { + Field field = WebServlet.class.getDeclaredField("server"); + field.setAccessible(true); + org.h2.server.web.WebServer server = (org.h2.server.web.WebServer) field.get(this); + + ConnectionInfo connectionInfo = new ConnectionInfo("Traccar|" + + Context.getConfig().getString("database.driver") + "|" + + Context.getConfig().getString("database.url") + "|" + + Context.getConfig().getString("database.user")); + + Method method; + + method = org.h2.server.web.WebServer.class.getDeclaredMethod("updateSetting", ConnectionInfo.class); + method.setAccessible(true); + method.invoke(server, connectionInfo); + + method = org.h2.server.web.WebServer.class.getDeclaredMethod("setAllowOthers", boolean.class); + method.setAccessible(true); + method.invoke(server, true); + + } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + LOGGER.warn("Console reflection error", e); + } + } + +} diff --git a/src/main/java/org/traccar/web/CsvBuilder.java b/src/main/java/org/traccar/web/CsvBuilder.java new file mode 100644 index 000000000..3fe7e408f --- /dev/null +++ b/src/main/java/org/traccar/web/CsvBuilder.java @@ -0,0 +1,164 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web; + +import java.beans.Introspector; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.helper.DateUtil; + +public class CsvBuilder { + + private static final Logger LOGGER = LoggerFactory.getLogger(CsvBuilder.class); + + private static final String LINE_ENDING = "\r\n"; + private static final String SEPARATOR = ";"; + + private StringBuilder builder = new StringBuilder(); + + private void addLineEnding() { + builder.append(LINE_ENDING); + } + private void addSeparator() { + builder.append(SEPARATOR); + } + + private SortedSet<Method> getSortedMethods(Object object) { + Method[] methodArray = object.getClass().getMethods(); + SortedSet<Method> methods = new TreeSet<>(new Comparator<Method>() { + @Override + public int compare(Method m1, Method m2) { + if (m1.getName().equals("getAttributes") && !m1.getName().equals(m2.getName())) { + return 1; + } + if (m2.getName().equals("getAttributes") && !m1.getName().equals(m2.getName())) { + return -1; + } + return m1.getName().compareTo(m2.getName()); + } + }); + methods.addAll(Arrays.asList(methodArray)); + return methods; + } + + public void addLine(Object object) { + + SortedSet<Method> methods = getSortedMethods(object); + + for (Method method : methods) { + if (method.getName().startsWith("get") && method.getParameterTypes().length == 0) { + try { + if (method.getReturnType().equals(boolean.class)) { + builder.append(method.invoke(object)); + addSeparator(); + } else if (method.getReturnType().equals(int.class)) { + builder.append(method.invoke(object)); + addSeparator(); + } else if (method.getReturnType().equals(long.class)) { + builder.append(method.invoke(object)); + addSeparator(); + } else if (method.getReturnType().equals(double.class)) { + builder.append(method.invoke(object)); + addSeparator(); + } else if (method.getReturnType().equals(String.class)) { + builder.append((String) method.invoke(object)); + addSeparator(); + } else if (method.getReturnType().equals(Date.class)) { + Date value = (Date) method.invoke(object); + builder.append(DateUtil.formatDate(value)); + addSeparator(); + } else if (method.getReturnType().equals(Map.class)) { + Map value = (Map) method.invoke(object); + if (value != null) { + try { + String map = Context.getObjectMapper().writeValueAsString(value); + map = map.replaceAll("[\\{\\}\"]", ""); + map = map.replaceAll(",", " "); + builder.append(map); + addSeparator(); + } catch (JsonProcessingException e) { + LOGGER.warn("Map JSON formatting error", e); + } + } + } + } catch (IllegalAccessException | InvocationTargetException error) { + LOGGER.warn("Reflection invocation error", error); + } + } + } + addLineEnding(); + } + + public void addHeaderLine(Object object) { + + SortedSet<Method> methods = getSortedMethods(object); + + for (Method method : methods) { + if (method.getName().startsWith("get") && method.getParameterTypes().length == 0) { + String name = Introspector.decapitalize(method.getName().substring(3)); + if (!name.equals("class")) { + builder.append(name); + addSeparator(); + } + } + } + addLineEnding(); + } + + public void addArray(Collection<?> array) { + for (Object object : array) { + switch (object.getClass().getSimpleName().toLowerCase()) { + case "string": + builder.append(object.toString()); + addLineEnding(); + break; + case "long": + builder.append((long) object); + addLineEnding(); + break; + case "double": + builder.append((double) object); + addLineEnding(); + break; + case "boolean": + builder.append((boolean) object); + addLineEnding(); + break; + default: + addLine(object); + break; + } + } + } + + public String build() { + return builder.toString(); + } + +} diff --git a/src/main/java/org/traccar/web/GpxBuilder.java b/src/main/java/org/traccar/web/GpxBuilder.java new file mode 100644 index 000000000..638d100e5 --- /dev/null +++ b/src/main/java/org/traccar/web/GpxBuilder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Anton Tananaev (anton@traccar.org) + * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web; + +import java.util.Collection; + +import org.traccar.helper.DateUtil; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +public class GpxBuilder { + + private StringBuilder builder = new StringBuilder(); + private static final String HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>" + + "<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" creator=\"Traccar\" version=\"1.1\" " + + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + + "xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 " + + "http://www.topografix.com/GPX/1/1/gpx.xsd\"><trk>\n"; + private static final String NAME = "<name>%1$s</name><trkseg>%n"; + private static final String POINT = "<trkpt lat=\"%1$f\" lon=\"%2$f\">" + + "<time>%3$s</time>" + + "<geoidheight>%4$f</geoidheight>" + + "<course>%5$f</course>" + + "<speed>%6$f</speed>" + + "</trkpt>%n"; + private static final String FOOTER = "</trkseg></trk></gpx>"; + + public GpxBuilder() { + builder.append(HEADER); + builder.append("<trkseg>\n"); + } + + public GpxBuilder(String name) { + builder.append(HEADER); + builder.append(String.format(NAME, name)); + } + + public void addPosition(Position position) { + builder.append(String.format(POINT, position.getLatitude(), position.getLongitude(), + DateUtil.formatDate(position.getFixTime()), position.getAltitude(), + position.getCourse(), UnitsConverter.mpsFromKnots(position.getSpeed()))); + } + + public void addPositions(Collection<Position> positions) { + for (Position position : positions) { + addPosition(position); + } + } + + public String build() { + builder.append(FOOTER); + return builder.toString(); + } + +} diff --git a/src/main/java/org/traccar/web/WebServer.java b/src/main/java/org/traccar/web/WebServer.java new file mode 100644 index 000000000..70fef4ed3 --- /dev/null +++ b/src/main/java/org/traccar/web/WebServer.java @@ -0,0 +1,173 @@ +/* + * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web; + +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.proxy.AsyncProxyServlet; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.glassfish.jersey.jackson.JacksonFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.servlet.ServletContainer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.api.AsyncSocketServlet; +import org.traccar.api.CorsResponseFilter; +import org.traccar.api.MediaFilter; +import org.traccar.api.ObjectMapperProvider; +import org.traccar.api.ResourceErrorHandler; +import org.traccar.api.SecurityRequestFilter; +import org.traccar.api.resource.ServerResource; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.net.InetSocketAddress; +import java.util.EnumSet; + +public class WebServer { + + private static final Logger LOGGER = LoggerFactory.getLogger(WebServer.class); + + private Server server; + + private void initServer(Config config) { + + String address = config.getString("web.address"); + int port = config.getInteger("web.port", 8082); + if (address == null) { + server = new Server(port); + } else { + server = new Server(new InetSocketAddress(address, port)); + } + } + + public WebServer(Config config) { + + initServer(config); + + ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); + + int sessionTimeout = config.getInteger("web.sessionTimeout"); + if (sessionTimeout > 0) { + servletHandler.getSessionHandler().setMaxInactiveInterval(sessionTimeout); + } + + initApi(config, servletHandler); + + if (config.getBoolean("web.console")) { + servletHandler.addServlet(new ServletHolder(new ConsoleServlet()), "/console/*"); + } + + initWebApp(config, servletHandler); + + servletHandler.setErrorHandler(new ErrorHandler() { + @Override + protected void handleErrorPage( + HttpServletRequest request, Writer writer, int code, String message) throws IOException { + writer.write("<!DOCTYPE<html><head><title>Error</title></head><html><body>" + + code + " - " + HttpStatus.getMessage(code) + "</body></html>"); + } + }); + + HandlerList handlers = new HandlerList(); + initClientProxy(config, handlers); + handlers.addHandler(servletHandler); + server.setHandler(handlers); + } + + private void initClientProxy(Config config, HandlerList handlers) { + int port = config.getInteger("osmand.port"); + if (port != 0) { + ServletContextHandler servletHandler = new ServletContextHandler() { + @Override + public void doScope( + String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + if (target.equals("/") && request.getMethod().equals(HttpMethod.POST.asString())) { + super.doScope(target, baseRequest, request, response); + } + } + }; + ServletHolder servletHolder = new ServletHolder(new AsyncProxyServlet.Transparent()); + servletHolder.setInitParameter("proxyTo", "http://localhost:" + port); + servletHandler.addServlet(servletHolder, "/"); + handlers.addHandler(servletHandler); + } + } + + private void initWebApp(Config config, ServletContextHandler servletHandler) { + ServletHolder servletHolder = new ServletHolder(DefaultServlet.class); + servletHolder.setInitParameter("resourceBase", new File(config.getString("web.path")).getAbsolutePath()); + if (config.getBoolean("web.debug")) { + servletHandler.setWelcomeFiles(new String[] {"debug.html", "index.html"}); + } else { + String cache = config.getString("web.cacheControl"); + if (cache != null && !cache.isEmpty()) { + servletHolder.setInitParameter("cacheControl", cache); + } + servletHandler.setWelcomeFiles(new String[] {"release.html", "index.html"}); + } + servletHandler.addServlet(servletHolder, "/*"); + } + + private void initApi(Config config, ServletContextHandler servletHandler) { + servletHandler.addServlet(new ServletHolder(new AsyncSocketServlet()), "/api/socket"); + + if (config.hasKey("media.path")) { + ServletHolder servletHolder = new ServletHolder(DefaultServlet.class); + servletHolder.setInitParameter("resourceBase", new File(config.getString("media.path")).getAbsolutePath()); + servletHolder.setInitParameter("dirAllowed", config.getString("media.dirAllowed", "false")); + servletHolder.setInitParameter("pathInfoOnly", "true"); + servletHandler.addServlet(servletHolder, "/api/media/*"); + servletHandler.addFilter(MediaFilter.class, "/api/media/*", EnumSet.allOf(DispatcherType.class)); + } + + ResourceConfig resourceConfig = new ResourceConfig(); + resourceConfig.registerClasses(JacksonFeature.class, ObjectMapperProvider.class, ResourceErrorHandler.class); + resourceConfig.registerClasses(SecurityRequestFilter.class, CorsResponseFilter.class); + resourceConfig.packages(ServerResource.class.getPackage().getName()); + servletHandler.addServlet(new ServletHolder(new ServletContainer(resourceConfig)), "/api/*"); + } + + public void start() { + try { + server.start(); + } catch (Exception error) { + LOGGER.warn("Web server start failed", error); + } + } + + public void stop() { + try { + server.stop(); + } catch (Exception error) { + LOGGER.warn("Web server stop failed", error); + } + } + +} |