aboutsummaryrefslogtreecommitdiff
path: root/src/org/traccar/protocol
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/traccar/protocol')
-rw-r--r--src/org/traccar/protocol/EelinkProtocolDecoder.java2
-rw-r--r--src/org/traccar/protocol/Jt600ProtocolDecoder.java8
-rw-r--r--src/org/traccar/protocol/Tk103FrameDecoder.java75
-rw-r--r--src/org/traccar/protocol/Tk103Protocol.java11
-rw-r--r--src/org/traccar/protocol/Tk103ProtocolDecoder.java173
-rw-r--r--src/org/traccar/protocol/Tk103ProtocolEncoder.java52
-rw-r--r--src/org/traccar/protocol/TmgProtocolDecoder.java15
7 files changed, 317 insertions, 19 deletions
diff --git a/src/org/traccar/protocol/EelinkProtocolDecoder.java b/src/org/traccar/protocol/EelinkProtocolDecoder.java
index 159a93d9b..98a9f7d6d 100644
--- a/src/org/traccar/protocol/EelinkProtocolDecoder.java
+++ b/src/org/traccar/protocol/EelinkProtocolDecoder.java
@@ -190,6 +190,8 @@ public class EelinkProtocolDecoder extends BaseProtocolDecoder {
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)) {
diff --git a/src/org/traccar/protocol/Jt600ProtocolDecoder.java b/src/org/traccar/protocol/Jt600ProtocolDecoder.java
index 58835c7d6..e935c1449 100644
--- a/src/org/traccar/protocol/Jt600ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Jt600ProtocolDecoder.java
@@ -63,8 +63,9 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder {
return null;
}
+ int protocolVersion = 0;
if (longFormat) {
- buf.readUnsignedByte(); // protocol
+ protocolVersion = buf.readUnsignedByte();
}
int version = BitUtil.from(buf.readUnsignedByte(), 4);
@@ -122,6 +123,11 @@ public class Jt600ProtocolDecoder extends BaseProtocolDecoder {
cellTower.setSignalStrength((int) buf.readUnsignedByte());
position.setNetwork(new Network(cellTower));
+ if (protocolVersion == 0x17) {
+ buf.readUnsignedByte(); // geofence id
+ buf.skipBytes(3); // reserved
+ }
+
} else if (version == 1) {
position.set(Position.KEY_SATELLITES, buf.readUnsignedByte());
diff --git a/src/org/traccar/protocol/Tk103FrameDecoder.java b/src/org/traccar/protocol/Tk103FrameDecoder.java
new file mode 100644
index 000000000..390e38906
--- /dev/null
+++ b/src/org/traccar/protocol/Tk103FrameDecoder.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017 Valerii Vyshniak (val@val.one)
+ * 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 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 Tk103FrameDecoder extends FrameDecoder {
+
+ @Override
+ protected Object decode(
+ ChannelHandlerContext ctx, Channel channel, ChannelBuffer 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.readBytes(frameEndIndex + 1 - frameStartIndex);
+ }
+
+}
diff --git a/src/org/traccar/protocol/Tk103Protocol.java b/src/org/traccar/protocol/Tk103Protocol.java
index 07a68e2d8..6ef9c0a56 100644
--- a/src/org/traccar/protocol/Tk103Protocol.java
+++ b/src/org/traccar/protocol/Tk103Protocol.java
@@ -22,7 +22,6 @@ import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;
import org.traccar.BaseProtocol;
-import org.traccar.CharacterDelimiterFrameDecoder;
import org.traccar.TrackerServer;
import org.traccar.model.Command;
@@ -33,6 +32,14 @@ public class Tk103Protocol extends BaseProtocol {
public Tk103Protocol() {
super("tk103");
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,
@@ -48,7 +55,7 @@ public class Tk103Protocol extends BaseProtocol {
serverList.add(new TrackerServer(new ServerBootstrap(), getName()) {
@Override
protected void addSpecificHandlers(ChannelPipeline pipeline) {
- pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, ')'));
+ pipeline.addLast("frameDecoder", new Tk103FrameDecoder());
pipeline.addLast("stringDecoder", new StringDecoder());
pipeline.addLast("stringEncoder", new StringEncoder());
pipeline.addLast("objectEncoder", new Tk103ProtocolEncoder());
diff --git a/src/org/traccar/protocol/Tk103ProtocolDecoder.java b/src/org/traccar/protocol/Tk103ProtocolDecoder.java
index be3def453..eda29e3f8 100644
--- a/src/org/traccar/protocol/Tk103ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Tk103ProtocolDecoder.java
@@ -26,6 +26,7 @@ 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;
@@ -40,6 +41,7 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
}
private static final Pattern PATTERN = new PatternBuilder()
+ .text("(").optional()
.number("(d+)(,)?") // device id
.expression("(.{4}),?") // command
.number("(d*)")
@@ -63,21 +65,28 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
.number("(?:L(x+))?") // odometer
.any()
.number("([+-]ddd.d)?") // temperature
+ .groupBegin()
+ .number("([+-]?d+.d{1,2}),") // altitude
+ .number("(d+)$") // number of visible satellites
+ .groupEnd("?")
.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 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
@@ -87,6 +96,31 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
.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:
@@ -112,30 +146,53 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
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:
@@ -143,6 +200,23 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
}
}
+ 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()) {
@@ -160,6 +234,11 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
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);
@@ -195,20 +274,84 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
return position;
}
- @Override
+
+ 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();
+ position.setProtocol(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();
+ position.setProtocol(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;
- int beginIndex = sentence.indexOf('(');
- if (beginIndex != -1) {
- sentence = sentence.substring(beginIndex + 1);
- }
-
if (channel != null) {
- String id = sentence.substring(0, 12);
- String type = sentence.substring(12, 16);
+ String id = sentence.substring(1, 13);
+ String type = sentence.substring(13, 17);
if (type.equals("BP00")) {
channel.write("(" + id + "AP01HSO)");
return null;
@@ -221,6 +364,10 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
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);
@@ -299,6 +446,14 @@ public class Tk103ProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.PREFIX_TEMP + 1, parser.nextDouble(0));
}
+ if (parser.hasNext()) {
+ position.setAltitude(parser.nextDouble(0));
+ }
+
+ if (parser.hasNext()) {
+ position.set(Position.KEY_SATELLITES_VISIBLE, parser.nextInt(0));
+ }
+
return position;
}
diff --git a/src/org/traccar/protocol/Tk103ProtocolEncoder.java b/src/org/traccar/protocol/Tk103ProtocolEncoder.java
index 9e49b6ff1..d7b17eeb7 100644
--- a/src/org/traccar/protocol/Tk103ProtocolEncoder.java
+++ b/src/org/traccar/protocol/Tk103ProtocolEncoder.java
@@ -16,33 +16,75 @@
*/
package org.traccar.protocol;
+import org.traccar.Context;
import org.traccar.StringProtocolEncoder;
import org.traccar.helper.Log;
import org.traccar.model.Command;
public class Tk103ProtocolEncoder extends StringProtocolEncoder {
+ private final boolean forceAlternative;
+
+ public Tk103ProtocolEncoder() {
+ this.forceAlternative = false;
+ }
+
+ public Tk103ProtocolEncoder(boolean forceAlternative) {
+ this.forceAlternative = forceAlternative;
+ }
+
@Override
protected Object encodeCommand(Command command) {
+ boolean alternative = forceAlternative || Context.getIdentityManager().lookupAttributeBoolean(
+ command.getDeviceId(), "tk103.alternative", false, true);
+
+ initDevicePassword(command, "123456");
+
switch (command.getType()) {
case Command.TYPE_GET_VERSION:
- return formatCommand(command, "({%s}AP07)", Command.KEY_UNIQUE_ID);
+ return alternative ? formatCommand(command, "[begin]sms2,*about*,[end]")
+ : formatCommand(command, "({%s}AP07)", Command.KEY_UNIQUE_ID);
case Command.TYPE_REBOOT_DEVICE:
- return formatCommand(command, "({%s}AT00)", Command.KEY_UNIQUE_ID);
+ return alternative ? formatCommand(command, "[begin]sms2,88888888,[end]")
+ : formatCommand(command, "({%s}AT00)", Command.KEY_UNIQUE_ID);
case Command.TYPE_SET_ODOMETER:
return formatCommand(command, "({%s}AX01)", Command.KEY_UNIQUE_ID);
case Command.TYPE_POSITION_SINGLE:
- return formatCommand(command, "({%s}AP00)", Command.KEY_UNIQUE_ID);
+ return alternative ? formatCommand(command, "[begin]sms2,*getposl*,[end]")
+ : formatCommand(command, "({%s}AP00)", Command.KEY_UNIQUE_ID);
case Command.TYPE_POSITION_PERIODIC:
- return formatCommand(command, "({%s}AR00%s0000)", Command.KEY_UNIQUE_ID,
+ return alternative ? formatCommand(command, "[begin]sms2,*routetrack*99*,[end]")
+ : formatCommand(command, "({%s}AR00%s0000)", Command.KEY_UNIQUE_ID,
String.format("%04X", command.getInteger(Command.KEY_FREQUENCY)));
case Command.TYPE_POSITION_STOP:
- return formatCommand(command, "({%s}AR0000000000)", Command.KEY_UNIQUE_ID);
+ return alternative ? formatCommand(command, "[begin]sms2,*routetrackoff*,[end]")
+ : formatCommand(command, "({%s}AR0000000000)", Command.KEY_UNIQUE_ID);
case Command.TYPE_ENGINE_STOP:
return formatCommand(command, "({%s}AV011)", Command.KEY_UNIQUE_ID);
case Command.TYPE_ENGINE_RESUME:
return formatCommand(command, "({%s}AV010)", Command.KEY_UNIQUE_ID);
+ case Command.TYPE_CUSTOM:
+ return formatCommand(command, "[begin]sms2,{%s},[end]", Command.KEY_DATA);
+ case Command.TYPE_GET_DEVICE_STATUS:
+ return formatCommand(command, "[begin]sms2,*status*,[end]");
+ case Command.TYPE_IDENTIFICATION:
+ return formatCommand(command, "[begin]sms2,999999,[end]");
+ case Command.TYPE_MODE_DEEP_SLEEP:
+ return formatCommand(command, command.getBoolean(Command.KEY_ENABLE)
+ ? "[begin]sms2,*sleep*2*,[end]" : "[begin]sms2,*sleepoff*,[end]");
+ case Command.TYPE_MODE_POWER_SAVING:
+ return formatCommand(command, command.getBoolean(Command.KEY_ENABLE)
+ ? "[begin]sms2,*sleepv*,[end]" : "[begin]sms2,*sleepoff*,[end]");
+ case Command.TYPE_ALARM_SOS:
+ return formatCommand(command, command.getBoolean(Command.KEY_ENABLE)
+ ? "[begin]sms2,*soson*,[end]" : "[begin]sms2,*sosoff*,[end]");
+ case Command.TYPE_SET_CONNECTION:
+ return formatCommand(command, "[begin]sms2,*setip*%s*{%s}*,[end]",
+ command.getString(Command.KEY_SERVER).replace(".", "*"), Command.KEY_PORT);
+ case Command.TYPE_SOS_NUMBER:
+ return formatCommand(command, "[begin]sms2,*master*{%s}*{%s}*,[end]",
+ Command.KEY_DEVICE_PASSWORD, Command.KEY_PHONE);
default:
Log.warning(new UnsupportedOperationException(command.getType()));
break;
diff --git a/src/org/traccar/protocol/TmgProtocolDecoder.java b/src/org/traccar/protocol/TmgProtocolDecoder.java
index 5e852aff2..4a3055932 100644
--- a/src/org/traccar/protocol/TmgProtocolDecoder.java
+++ b/src/org/traccar/protocol/TmgProtocolDecoder.java
@@ -72,9 +72,9 @@ public class TmgProtocolDecoder extends BaseProtocolDecoder {
.number("(d+),") // satellites
.number("[^,]*,") // battery level
.expression("([01]),") // ignition
- .expression("[LH]{4},") // input
+ .expression("([LH]{4}),") // input
.expression("[NT]{4},") // tamper status
- .expression("[LH]{2},") // output
+ .expression("([LH]{2}),") // output
.number("(d+.d+),") // adc1
.number("(d+.d+),") // adc1
.number("[^,]*,") // device id
@@ -171,6 +171,17 @@ public class TmgProtocolDecoder extends BaseProtocolDecoder {
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());