diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/org/traccar/BasePipelineFactory.java | 62 | ||||
-rw-r--r-- | src/org/traccar/MainEventHandler.java | 42 | ||||
-rw-r--r-- | src/org/traccar/protocol/CellocatorProtocolDecoder.java | 62 | ||||
-rw-r--r-- | src/org/traccar/protocol/EelinkProtocolDecoder.java | 25 | ||||
-rw-r--r-- | src/org/traccar/protocol/EelinkProtocolEncoder.java | 1 | ||||
-rw-r--r-- | src/org/traccar/protocol/Gps103ProtocolDecoder.java | 47 | ||||
-rw-r--r-- | src/org/traccar/protocol/Gt06ProtocolDecoder.java | 39 | ||||
-rw-r--r-- | src/org/traccar/protocol/H02ProtocolDecoder.java | 77 | ||||
-rw-r--r-- | src/org/traccar/protocol/ItsProtocolDecoder.java | 2 | ||||
-rw-r--r-- | src/org/traccar/protocol/NeosProtocol.java | 37 | ||||
-rw-r--r-- | src/org/traccar/protocol/NeosProtocolDecoder.java | 98 | ||||
-rw-r--r-- | src/org/traccar/protocol/SatsolProtocol.java | 37 | ||||
-rw-r--r-- | src/org/traccar/protocol/SatsolProtocolDecoder.java | 104 | ||||
-rw-r--r-- | src/org/traccar/protocol/TotemProtocolDecoder.java | 44 | ||||
-rw-r--r-- | src/org/traccar/protocol/UproProtocolDecoder.java | 12 | ||||
-rw-r--r-- | src/org/traccar/protocol/VtfmsProtocolDecoder.java | 25 |
16 files changed, 595 insertions, 119 deletions
diff --git a/src/org/traccar/BasePipelineFactory.java b/src/org/traccar/BasePipelineFactory.java index 401c42d8b..b45e3a280 100644 --- a/src/org/traccar/BasePipelineFactory.java +++ b/src/org/traccar/BasePipelineFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * 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. @@ -44,6 +44,7 @@ import org.traccar.processing.ComputedAttributesHandler; import org.traccar.processing.CopyAttributesHandler; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Map; public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { @@ -99,25 +100,29 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { private static class NetworkMessageHandler extends ChannelDuplexHandler { @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + 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 { + } 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) throws Exception { - 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); + 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(message.getMessage(), promise); + ctx.write(msg, promise); } } @@ -138,7 +143,17 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { } public void log(ChannelHandlerContext ctx, boolean downstream, Object o) { - NetworkMessage networkMessage = (NetworkMessage) 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(": "); @@ -149,15 +164,15 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { message.append(" < "); } - if (networkMessage.getRemoteAddress() != null) { - message.append(((InetSocketAddress) networkMessage.getRemoteAddress()).getHostString()); + if (remoteAddress instanceof InetSocketAddress) { + message.append(((InetSocketAddress) remoteAddress).getHostString()); } else { - message.append("null"); + message.append("unknown"); } message.append("]"); message.append(" HEX: "); - message.append(ByteBufUtil.hexDump((ByteBuf) networkMessage.getMessage())); + message.append(ByteBufUtil.hexDump(buf)); LOGGER.info(message.toString()); } @@ -267,18 +282,15 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { pipeline.addLast(new NetworkMessageHandler()); pipeline.addLast(new StandardLoggingHandler()); - addProtocolHandlers(new PipelineBuilder() { - @Override - public void addLast(ChannelHandler handler) { - if (!(handler instanceof BaseProtocolDecoder || handler instanceof BaseProtocolEncoder)) { - if (handler instanceof ChannelInboundHandler) { - handler = new WrapperInboundHandler((ChannelInboundHandler) handler); - } else { - handler = new WrapperOutboundHandler((ChannelOutboundHandler) handler); - } + 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); } + pipeline.addLast(handler); }); addHandlers( diff --git a/src/org/traccar/MainEventHandler.java b/src/org/traccar/MainEventHandler.java index d4e1ff860..c905508e0 100644 --- a/src/org/traccar/MainEventHandler.java +++ b/src/org/traccar/MainEventHandler.java @@ -35,18 +35,19 @@ import java.util.Set; public class MainEventHandler extends ChannelInboundHandlerAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(GeocoderHandler.class); - private static final String DEFAULT_LOGGER_EVENTS = "time,position,speed,course,accuracy,result"; + + private static final String DEFAULT_LOGGER_ATTRIBUTES = "time,position,speed,course,accuracy,result"; private final Set<String> connectionlessProtocols = new HashSet<>(); - private final Set<String> logEvents; + 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(","))); } - logEvents = new LinkedHashSet<>(Arrays.asList( - Context.getConfig().getString("logger.events", DEFAULT_LOGGER_EVENTS).split(","))); + logAttributes.addAll(Arrays.asList( + Context.getConfig().getString("logger.attributes", DEFAULT_LOGGER_ATTRIBUTES).split(","))); } @Override @@ -62,51 +63,50 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter { String uniqueId = Context.getIdentityManager().getById(position.getDeviceId()).getUniqueId(); - // Log position - StringBuilder s = new StringBuilder(); - s.append(formatChannel(ctx.channel())).append(" "); - s.append("id: ").append(uniqueId); - for (String event : logEvents) { - switch (event) { + StringBuilder builder = new StringBuilder(); + builder.append(formatChannel(ctx.channel())).append(" "); + builder.append("id: ").append(uniqueId); + for (String attribute : logAttributes) { + switch (attribute) { case "time": - s.append(", time: ").append(DateUtil.formatDate(position.getFixTime(), false)); + builder.append(", time: ").append(DateUtil.formatDate(position.getFixTime(), false)); break; case "position": - s.append(", lat: ").append(String.format("%.5f", position.getLatitude())); - s.append(", lon: ").append(String.format("%.5f", position.getLongitude())); + 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) { - s.append(", speed: ").append(String.format("%.1f", position.getSpeed())); + builder.append(", speed: ").append(String.format("%.1f", position.getSpeed())); } break; case "course": - s.append(", course: ").append(String.format("%.1f", position.getCourse())); + builder.append(", course: ").append(String.format("%.1f", position.getCourse())); break; case "accuracy": if (position.getAccuracy() > 0) { - s.append(", accuracy: ").append(String.format("%.1f", position.getAccuracy())); + builder.append(", accuracy: ").append(String.format("%.1f", position.getAccuracy())); } break; case "outdated": if (position.getOutdated()) { - s.append(", outdated"); + builder.append(", outdated"); } break; case "invalid": if (!position.getValid()) { - s.append(", invalid"); + builder.append(", invalid"); } break; default: - Object value = position.getAttributes().get(event); + Object value = position.getAttributes().get(attribute); if (value != null) { - s.append(", ").append(event).append(": ").append(value); + builder.append(", ").append(attribute).append(": ").append(value); } break; } } - LOGGER.info(s.toString()); + LOGGER.info(builder.toString()); Context.getStatisticsManager().registerMessageStored(position.getDeviceId()); } diff --git a/src/org/traccar/protocol/CellocatorProtocolDecoder.java b/src/org/traccar/protocol/CellocatorProtocolDecoder.java index fb805658a..453d8d7e7 100644 --- a/src/org/traccar/protocol/CellocatorProtocolDecoder.java +++ b/src/org/traccar/protocol/CellocatorProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * 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. @@ -88,6 +88,8 @@ public class CellocatorProtocolDecoder extends BaseProtocolDecoder { 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(); @@ -111,40 +113,60 @@ public class CellocatorProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_VERSION_HW, buf.readUnsignedByte()); position.set(Position.KEY_VERSION_FW, buf.readUnsignedByte()); - position.set("protocolVersion", buf.readUnsignedByte()); + buf.readUnsignedByte(); // protocol version - position.set(Position.KEY_STATUS, buf.getUnsignedByte(buf.readerIndex()) & 0x0f); + position.set(Position.KEY_STATUS, buf.readUnsignedByte() & 0x0f); - int operator = (buf.readUnsignedByte() & 0xf0) << 4; - operator += buf.readUnsignedByte(); + if (alternative) { + buf.readUnsignedByte(); // configuration flags + } else { + buf.readUnsignedByte(); // operator + } buf.readUnsignedByte(); // reason data position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); position.set("mode", buf.readUnsignedByte()); - position.set(Position.PREFIX_IO + 1, buf.readUnsignedIntLE()); - - operator <<= 8; - operator += buf.readUnsignedByte(); - position.set(Position.KEY_OPERATOR, operator); + 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.PREFIX_ADC + 1, buf.readUnsignedIntLE()); position.set(Position.KEY_ODOMETER, buf.readUnsignedMediumLE()); - buf.skipBytes(6); // multi-purpose data - position.set(Position.KEY_GPS, buf.readUnsignedShortLE()); - position.set("locationStatus", buf.readUnsignedByte()); - position.set("mode1", buf.readUnsignedByte()); - position.set("mode2", buf.readUnsignedByte()); + 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); - position.setLongitude(buf.readIntLE() / Math.PI * 180 / 100000000); - position.setLatitude(buf.readIntLE() / Math.PI * 180 / 100000000.0); + + 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); - position.setSpeed(UnitsConverter.knotsFromMps(buf.readIntLE() * 0.01)); - position.setCourse(buf.readUnsignedShortLE() / Math.PI * 180.0 / 1000.0); + + 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()) diff --git a/src/org/traccar/protocol/EelinkProtocolDecoder.java b/src/org/traccar/protocol/EelinkProtocolDecoder.java index bffefbddf..14bf44fbf 100644 --- a/src/org/traccar/protocol/EelinkProtocolDecoder.java +++ b/src/org/traccar/protocol/EelinkProtocolDecoder.java @@ -17,6 +17,7 @@ package org.traccar.protocol; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.socket.DatagramChannel; import org.traccar.BaseProtocolDecoder; @@ -62,13 +63,6 @@ public class EelinkProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_CAMERA_INFO = 0x1E; public static final int MSG_CAMERA_DATA = 0x1F; - private void sendResponse(Channel channel, SocketAddress remoteAddress, String uniqueId, int type, int index) { - if (channel != null) { - channel.writeAndFlush(new NetworkMessage(EelinkProtocolEncoder.encodeContent( - channel instanceof DatagramChannel, uniqueId, type, index, null), remoteAddress)); - } - } - private String decodeAlarm(Short value) { switch (value) { case 0x01: @@ -273,6 +267,10 @@ public class EelinkProtocolDecoder extends BaseProtocolDecoder { position.set("co2", buf.readUnsignedInt()); } + if (buf.readableBytes() >= 2) { + position.set(Position.PREFIX_TEMP + 2, buf.readUnsignedShort() / 16.0); + } + } return position; @@ -352,7 +350,18 @@ public class EelinkProtocolDecoder extends BaseProtocolDecoder { int index = buf.readUnsignedShort(); if (type != MSG_GPS && type != MSG_DATA) { - sendResponse(channel, remoteAddress, uniqueId, type, index); + 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) { diff --git a/src/org/traccar/protocol/EelinkProtocolEncoder.java b/src/org/traccar/protocol/EelinkProtocolEncoder.java index c005fbc67..8f33441fb 100644 --- a/src/org/traccar/protocol/EelinkProtocolEncoder.java +++ b/src/org/traccar/protocol/EelinkProtocolEncoder.java @@ -69,6 +69,7 @@ public class EelinkProtocolEncoder extends BaseProtocolEncoder { } result.writeBytes(buf); + buf.release(); return result; } diff --git a/src/org/traccar/protocol/Gps103ProtocolDecoder.java b/src/org/traccar/protocol/Gps103ProtocolDecoder.java index 1fbae15ce..27b94739b 100644 --- a/src/org/traccar/protocol/Gps103ProtocolDecoder.java +++ b/src/org/traccar/protocol/Gps103ProtocolDecoder.java @@ -15,11 +15,15 @@ */ 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; @@ -34,6 +38,9 @@ import java.util.regex.Pattern; public class Gps103ProtocolDecoder extends BaseProtocolDecoder { + private int photoPackets = 0; + private ByteBuf photo; + public Gps103ProtocolDecoder(Protocol protocol) { super(protocol); } @@ -190,6 +197,9 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder { 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")) { @@ -336,6 +346,39 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder { 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 { @@ -365,7 +408,9 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder { } } - if (sentence.contains("OBD")) { + 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); diff --git a/src/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/org/traccar/protocol/Gt06ProtocolDecoder.java index f9c65f08b..6388455b5 100644 --- a/src/org/traccar/protocol/Gt06ProtocolDecoder.java +++ b/src/org/traccar/protocol/Gt06ProtocolDecoder.java @@ -67,7 +67,6 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { 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_PHONE = 0x17; public static final int MSG_LBS_EXTEND = 0x18; public static final int MSG_LBS_STATUS = 0x19; public static final int MSG_GPS_PHONE = 0x1A; @@ -389,7 +388,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return position; } - private Object decodeBasic(Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws Exception { + private Object decodeBasic(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { int length = buf.readUnsignedByte(); int dataLength = length - 5; @@ -488,7 +487,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { } else if (type == MSG_WIFI || type == MSG_WIFI_2) { - return decodeWifi(buf, deviceSession); + return decodeWifi(channel, buf, deviceSession, type); } else if (type == MSG_INFO) { @@ -559,18 +558,19 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return null; } - private Object decodeWifi(ByteBuf buf, DeviceSession deviceSession) throws Exception { + 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(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)); + .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(); @@ -593,11 +593,22 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { 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) throws Exception { + DeviceSession deviceSession, int type, int dataLength) { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); @@ -701,7 +712,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return position; } - private Object decodeExtended(Channel channel, SocketAddress remoteAddress, ByteBuf buf) throws Exception { + private Object decodeExtended(Channel channel, SocketAddress remoteAddress, ByteBuf buf) { DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); if (deviceSession == null) { @@ -755,7 +766,9 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { position.set("iccid", ByteBufUtil.hexDump(buf.readSlice(8))); return position; } else if (subType == 0x0d) { - buf.skipBytes(6); + if (buf.getByte(buf.readerIndex()) != '!') { + buf.skipBytes(6); + } return decodeFuelData(position, buf.toString( buf.readerIndex(), buf.readableBytes() - 4 - 2, StandardCharsets.US_ASCII)); } diff --git a/src/org/traccar/protocol/H02ProtocolDecoder.java b/src/org/traccar/protocol/H02ProtocolDecoder.java index a5f87fe8f..c4443a00b 100644 --- a/src/org/traccar/protocol/H02ProtocolDecoder.java +++ b/src/org/traccar/protocol/H02ProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * 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. @@ -163,16 +163,12 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { .expression("..,") // manufacturer .number("(d+)?,") // imei .groupBegin() - .text("VP1,") - .or() - .groupBegin() .text("V4,") .expression("(.*),") // response .or() .expression("(V[^,]*),") .groupEnd() .number("(?:(dd)(dd)(dd))?,") // time (hhmmss) - .groupEnd() .groupBegin() .expression("([ABV])?,") // validity .or() @@ -268,6 +264,28 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { .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) { DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); @@ -478,6 +496,53 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { 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 { @@ -499,6 +564,8 @@ public class H02ProtocolDecoder extends BaseProtocolDecoder { 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); } diff --git a/src/org/traccar/protocol/ItsProtocolDecoder.java b/src/org/traccar/protocol/ItsProtocolDecoder.java index 000e7759f..62bf1f1e6 100644 --- a/src/org/traccar/protocol/ItsProtocolDecoder.java +++ b/src/org/traccar/protocol/ItsProtocolDecoder.java @@ -76,7 +76,7 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { String sentence = (String) msg; if (channel != null && sentence.startsWith("$,01,")) { - channel.writeAndFlush(new NetworkMessage("$,01,", remoteAddress)); + channel.writeAndFlush(new NetworkMessage("$,1,*", remoteAddress)); } Parser parser = new Parser(PATTERN, sentence); diff --git a/src/org/traccar/protocol/NeosProtocol.java b/src/org/traccar/protocol/NeosProtocol.java new file mode 100644 index 000000000..e545a9969 --- /dev/null +++ b/src/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/org/traccar/protocol/NeosProtocolDecoder.java b/src/org/traccar/protocol/NeosProtocolDecoder.java new file mode 100644 index 000000000..6b5596dba --- /dev/null +++ b/src/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/org/traccar/protocol/SatsolProtocol.java b/src/org/traccar/protocol/SatsolProtocol.java new file mode 100644 index 000000000..b69fdd1fe --- /dev/null +++ b/src/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/org/traccar/protocol/SatsolProtocolDecoder.java b/src/org/traccar/protocol/SatsolProtocolDecoder.java new file mode 100644 index 000000000..77d292d92 --- /dev/null +++ b/src/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())); + 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/org/traccar/protocol/TotemProtocolDecoder.java b/src/org/traccar/protocol/TotemProtocolDecoder.java index ed4c1409a..cd7f684b8 100644 --- a/src/org/traccar/protocol/TotemProtocolDecoder.java +++ b/src/org/traccar/protocol/TotemProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * 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. @@ -150,10 +150,14 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder { .number("(dddd)") // adc 4 .groupEnd("?") .number("(dddd)") // temperature 1 - .number("(dddd)") // temperature 2 + .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 @@ -337,29 +341,43 @@ public class TotemProtocolDecoder extends BaseProtocolDecoder { 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.setValid(BitUtil.check(status, 32 - 20)); position.setTime(parser.nextDateTime()); - position.set(Position.KEY_BATTERY, parser.nextDouble(0) * 0.1); - position.set(Position.KEY_POWER, parser.nextDouble(0)); + 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()); - position.set(Position.PREFIX_TEMP + 2, parser.next()); - CellTower cellTower = CellTower.fromLacCid(parser.nextHexInt(0), parser.nextHexInt(0)); - position.set(Position.KEY_SATELLITES, parser.nextInt(0)); - cellTower.setSignalStrength(parser.nextInt(0)); + 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(0)); - position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); - position.set(Position.KEY_HDOP, parser.nextDouble(0)); - position.set(Position.KEY_ODOMETER, parser.nextInt(0) * 1000); + 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()); diff --git a/src/org/traccar/protocol/UproProtocolDecoder.java b/src/org/traccar/protocol/UproProtocolDecoder.java index c5b72ea0a..dc7a9200d 100644 --- a/src/org/traccar/protocol/UproProtocolDecoder.java +++ b/src/org/traccar/protocol/UproProtocolDecoder.java @@ -164,11 +164,13 @@ public class UproProtocolDecoder extends BaseProtocolDecoder { position.set("statusExtended", data.toString(StandardCharsets.US_ASCII)); break; case 'P': - 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)))); + 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)); diff --git a/src/org/traccar/protocol/VtfmsProtocolDecoder.java b/src/org/traccar/protocol/VtfmsProtocolDecoder.java index c08261a59..17fac4311 100644 --- a/src/org/traccar/protocol/VtfmsProtocolDecoder.java +++ b/src/org/traccar/protocol/VtfmsProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2018 Anton Tananaev (anton@traccar.org) + * 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. @@ -60,7 +60,7 @@ public class VtfmsProtocolDecoder extends BaseProtocolDecoder { .number("(d+.d+),") // power voltage .number("[^,]*,") // reserved .number("(d+)?,") // fuel level - .number("(d+.d+),") // adc 1 + .number("(d+.d+)?,") // adc 1 .number("[^,]*,") // reserved .number("(d+.d+)?,") // adc 2 .expression("([01]),") // di 1 @@ -93,6 +93,11 @@ public class VtfmsProtocolDecoder extends BaseProtocolDecoder { } } + 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 { @@ -116,12 +121,18 @@ public class VtfmsProtocolDecoder extends BaseProtocolDecoder { position.setValid(parser.next().equals("A")); position.setTime(parser.nextDateTime(Parser.DateTimeFormat.HMS_DMY)); - position.setLatitude(parser.nextDouble(0)); - position.setLongitude(parser.nextDouble(0)); - if (parser.hasNext()) { - position.setCourse(parser.nextDouble(0)); + 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++) { @@ -132,7 +143,7 @@ public class VtfmsProtocolDecoder extends BaseProtocolDecoder { } } - position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(parser.nextInt())); position.set("idleHours", parser.nextInt()); |