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/DwayProtocolDecoder.java15
-rw-r--r--src/org/traccar/protocol/Gt06ProtocolDecoder.java79
-rw-r--r--src/org/traccar/protocol/MeiligaoProtocolDecoder.java153
-rw-r--r--src/org/traccar/protocol/Pt502FrameDecoder.java11
-rw-r--r--src/org/traccar/protocol/Pt502Protocol.java3
-rw-r--r--src/org/traccar/protocol/Pt502ProtocolDecoder.java4
-rw-r--r--src/org/traccar/protocol/Pt502ProtocolEncoder.java2
-rw-r--r--src/org/traccar/protocol/RecodaProtocol.java46
-rw-r--r--src/org/traccar/protocol/RecodaProtocolDecoder.java110
9 files changed, 332 insertions, 91 deletions
diff --git a/src/org/traccar/protocol/DwayProtocolDecoder.java b/src/org/traccar/protocol/DwayProtocolDecoder.java
index 993aa91b2..767b35c72 100644
--- a/src/org/traccar/protocol/DwayProtocolDecoder.java
+++ b/src/org/traccar/protocol/DwayProtocolDecoder.java
@@ -42,15 +42,16 @@ public class DwayProtocolDecoder extends BaseProtocolDecoder {
.number("(-?d+.d+),") // latitude
.number("(-?d+.d+),") // longitude
.number("(-?d+),") // altitude
- .number("(d+.d+),") // speed
+ .number(" ?(d+.d+),") // speed
.number("(d+),") // course
.number("([01]{4}),") // input
.number("([01]{4}),") // output
- .number("([01])([01])([01])([01]),") // flags
+ .number("([01]+),") // flags
.number("(d+),") // battery
.number("(d+),") // adc1
.number("(d+),") // adc2
.number("(d+)") // driver
+ .any()
.compile();
@Override
@@ -79,6 +80,7 @@ public class DwayProtocolDecoder extends BaseProtocolDecoder {
position.setProtocol(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
+ position.setValid(true);
position.setTime(parser.nextDateTime());
position.setLatitude(parser.nextDouble());
position.setLongitude(parser.nextDouble());
@@ -89,15 +91,6 @@ public class DwayProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_INPUT, parser.nextBinInt());
position.set(Position.KEY_OUTPUT, parser.nextBinInt());
- position.setValid(parser.next().equals("1"));
-
- position.set(Position.KEY_IGNITION, parser.next().equals("1"));
- position.set(Position.KEY_CHARGE, parser.next().equals("1"));
-
- if (parser.next().equals("1")) {
- position.set(Position.KEY_ALARM, Position.ALARM_SHOCK);
- }
-
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);
diff --git a/src/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/org/traccar/protocol/Gt06ProtocolDecoder.java
index fbd1adfc6..177c0b653 100644
--- a/src/org/traccar/protocol/Gt06ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Gt06ProtocolDecoder.java
@@ -21,6 +21,7 @@ import org.jboss.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
import org.traccar.Context;
import org.traccar.DeviceSession;
+import org.traccar.helper.BcdUtil;
import org.traccar.helper.BitUtil;
import org.traccar.helper.Checksum;
import org.traccar.helper.DateBuilder;
@@ -67,6 +68,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
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;
@@ -81,6 +83,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
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_COMMAND_0 = 0x80;
public static final int MSG_COMMAND_1 = 0x81;
public static final int MSG_COMMAND_2 = 0x82;
@@ -107,17 +110,21 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
|| type == MSG_GPS_LBS_STATUS_1 || type == MSG_GPS_LBS_STATUS_2 || type == MSG_GPS_LBS_STATUS_3;
}
- private void sendResponse(Channel channel, boolean extended, int type) {
+ private void sendResponse(Channel channel, boolean extended, int type, ChannelBuffer content) {
if (channel != null) {
ChannelBuffer response = ChannelBuffers.dynamicBuffer();
+ int length = 5 + (content != null ? content.readableBytes() : 0);
if (extended) {
response.writeShort(0x7979);
- response.writeShort(5);
+ response.writeShort(length);
} else {
response.writeShort(0x7878);
- response.writeByte(5);
+ response.writeByte(length);
}
response.writeByte(type);
+ if (content != null) {
+ response.writeBytes(content);
+ }
response.writeShort(++serverIndex);
response.writeShort(Checksum.crc16(Checksum.CRC16_X25,
response.toByteBuffer(2, response.writerIndex() - 2)));
@@ -127,21 +134,12 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
}
private void sendPhotoRequest(Channel channel, int pictureId) {
- if (channel != null) {
- ChannelBuffer photo = photos.get(pictureId);
- ChannelBuffer response = ChannelBuffers.dynamicBuffer();
- response.writeShort(0x7878); // header
- response.writeByte(15); // size
- response.writeByte(MSG_X1_PHOTO_DATA);
- response.writeInt(pictureId);
- response.writeInt(photo.writerIndex());
- response.writeShort(Math.min(photo.writableBytes(), 1024));
- response.writeShort(++serverIndex);
- response.writeShort(Checksum.crc16(Checksum.CRC16_X25,
- response.toByteBuffer(2, response.writerIndex() - 2)));
- response.writeByte('\r'); response.writeByte('\n'); // ending
- channel.write(response);
- }
+ ChannelBuffer photo = photos.get(pictureId);
+ ChannelBuffer content = ChannelBuffers.dynamicBuffer();
+ content.writeInt(pictureId);
+ content.writeInt(photo.writerIndex());
+ content.writeShort(Math.min(photo.writableBytes(), 1024));
+ sendResponse(channel, false, MSG_X1_PHOTO_DATA, content);
}
private boolean decodeGps(Position position, ChannelBuffer buf, boolean hasLength) {
@@ -347,7 +345,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
}
if (getDeviceSession(channel, remoteAddress, imei) != null) {
- sendResponse(channel, false, type);
+ sendResponse(channel, false, type, null);
}
} else if (type == MSG_X1_GPS) {
@@ -385,6 +383,43 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
photos.put(pictureId, photo);
sendPhotoRequest(channel, pictureId);
+ } else if (type == MSG_WIFI || type == MSG_WIFI_2) {
+
+ Position position = new Position();
+ position.setDeviceId(deviceSession.getDeviceId());
+ position.setProtocol(getProtocolName());
+
+ 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));
+ 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);
+
+ return position;
+
} else {
return decodeBasicOther(channel, buf, deviceSession, type, dataLength);
@@ -476,13 +511,13 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
buf.skipBytes(dataLength);
if (type != MSG_COMMAND_0 && type != MSG_COMMAND_1 && type != MSG_COMMAND_2) {
- sendResponse(channel, false, type);
+ sendResponse(channel, false, type, null);
}
return null;
}
- sendResponse(channel, false, type);
+ sendResponse(channel, false, type, null);
return position;
}
@@ -602,7 +637,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder {
buf.skipBytes(buf.readUnsignedByte()); // reserved extension
- sendResponse(channel, true, type);
+ sendResponse(channel, true, type, null);
return position;
diff --git a/src/org/traccar/protocol/MeiligaoProtocolDecoder.java b/src/org/traccar/protocol/MeiligaoProtocolDecoder.java
index e41a42843..b0793037f 100644
--- a/src/org/traccar/protocol/MeiligaoProtocolDecoder.java
+++ b/src/org/traccar/protocol/MeiligaoProtocolDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 - 2016 Anton Tananaev (anton@traccar.org)
+ * 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.
@@ -30,6 +30,8 @@ import org.traccar.model.Position;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
+import java.util.LinkedList;
+import java.util.List;
import java.util.regex.Pattern;
public class MeiligaoProtocolDecoder extends BaseProtocolDecoder {
@@ -55,11 +57,19 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder {
.number("|(xxxx)?") // state
.groupBegin()
.number("|(xxxx),(xxxx)") // adc
- .number("(?:,(xxxx),(xxxx),(xxxx),(xxxx),(xxxx),(xxxx))?")
+ .number(",(xxxx)").optional()
+ .number(",(xxxx)").optional()
+ .number(",(xxxx)").optional()
+ .number(",(xxxx)").optional()
+ .number(",(xxxx)").optional()
+ .number(",(xxxx)").optional()
.groupBegin()
- .number("|x{16}") // cell
- .number("|(xx)") // gsm
+ .number("|x{16,20}") // cell
+ .number("|(xx)") // rssi
.number("|(x{8})") // odometer
+ .groupBegin()
+ .number("|(xx)") // satellites
+ .groupEnd("?")
.or()
.number("|(x{9})") // odometer
.groupBegin()
@@ -118,6 +128,7 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder {
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;
@@ -242,25 +253,14 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder {
position.set(Position.KEY_STATUS, parser.next());
for (int i = 1; i <= 8; i++) {
- if (parser.hasNext()) {
- position.set(Position.PREFIX_ADC + i, parser.nextHexInt(0));
- }
- }
-
- if (parser.hasNext()) {
- position.set(Position.KEY_RSSI, parser.nextHexInt(0));
+ position.set(Position.PREFIX_ADC + i, parser.nextHexInt());
}
- if (parser.hasNext()) {
- position.set(Position.KEY_ODOMETER, parser.nextLong(16, 0));
- }
- if (parser.hasNext()) {
- position.set(Position.KEY_ODOMETER, parser.nextLong(16, 0));
- }
-
- if (parser.hasNext()) {
- position.set(Position.KEY_DRIVER_UNIQUE_ID, String.valueOf(parser.nextHexInt(0)));
- }
+ position.set(Position.KEY_RSSI, parser.nextHexInt());
+ position.set(Position.KEY_ODOMETER, parser.nextHexLong());
+ position.set(Position.KEY_SATELLITES, parser.nextHexInt());
+ position.set(Position.KEY_ODOMETER, parser.nextHexLong());
+ position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next());
return position;
}
@@ -328,6 +328,40 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder {
return position;
}
+ private List<Position> decodeRetransmission(ChannelBuffer 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.readBytes(endIndex - buf.readerIndex()).toString(StandardCharsets.US_ASCII);
+
+ Position position = new Position();
+ position.setProtocol(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 {
@@ -355,48 +389,57 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder {
}
}
- Position position = new Position();
- position.setProtocol(getProtocolName());
-
- 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);
- }
-
DeviceSession deviceSession = identify(id, channel, remoteAddress);
if (deviceSession == null) {
return null;
}
- position.setDeviceId(deviceSession.getDeviceId());
-
- 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);
+
+ if (command == MSG_RETRANSMISSION) {
+
+ return decodeRetransmission(buf, deviceSession);
+
+ } else {
+
+ Position position = new Position();
+ position.setProtocol(getProtocolName());
+
+ 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);
}
- }
- String sentence = buf.toString(buf.readerIndex(), buf.readableBytes() - 4, StandardCharsets.US_ASCII);
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ 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);
+ }
+ }
+ }
+
+ String sentence = buf.toString(buf.readerIndex(), buf.readableBytes() - 4, StandardCharsets.US_ASCII);
+
+ if (command == MSG_POSITION || command == MSG_POSITION_LOGGED || command == MSG_ALARM) {
+ return decodeRegular(position, sentence);
+ } else if (command == MSG_RFID) {
+ return decodeRfid(position, sentence);
+ } else if (command == MSG_OBD_RT) {
+ return decodeObd(position, sentence);
+ } else if (command == MSG_OBD_RTA) {
+ return decodeObdA(position, sentence);
+ }
- if (command == MSG_POSITION || command == MSG_POSITION_LOGGED || command == MSG_ALARM) {
- return decodeRegular(position, sentence);
- } else if (command == MSG_RFID) {
- return decodeRfid(position, sentence);
- } else if (command == MSG_OBD_RT) {
- return decodeObd(position, sentence);
- } else if (command == MSG_OBD_RTA) {
- return decodeObdA(position, sentence);
}
return null;
diff --git a/src/org/traccar/protocol/Pt502FrameDecoder.java b/src/org/traccar/protocol/Pt502FrameDecoder.java
index ce20dff1f..252c8dd02 100644
--- a/src/org/traccar/protocol/Pt502FrameDecoder.java
+++ b/src/org/traccar/protocol/Pt502FrameDecoder.java
@@ -37,9 +37,16 @@ public class Pt502FrameDecoder extends FrameDecoder {
}
int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '\r');
- if (index != -1 && index + 1 < buf.writerIndex()) {
+ if (index < 0) {
+ index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '\n');
+ }
+
+ if (index > 0) {
ChannelBuffer result = buf.readBytes(index - buf.readerIndex());
- buf.skipBytes(2);
+ while (buf.readable()
+ && (buf.getByte(buf.readerIndex()) == '\r' || buf.getByte(buf.readerIndex()) == '\n')) {
+ buf.skipBytes(1);
+ }
return result;
}
diff --git a/src/org/traccar/protocol/Pt502Protocol.java b/src/org/traccar/protocol/Pt502Protocol.java
index ad97a777e..0116422c2 100644
--- a/src/org/traccar/protocol/Pt502Protocol.java
+++ b/src/org/traccar/protocol/Pt502Protocol.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org)
+ * 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.
@@ -31,6 +31,7 @@ public class Pt502Protocol extends BaseProtocol {
public Pt502Protocol() {
super("pt502");
setSupportedDataCommands(
+ Command.TYPE_CUSTOM,
Command.TYPE_SET_TIMEZONE,
Command.TYPE_ALARM_SPEED,
Command.TYPE_OUTPUT_CONTROL,
diff --git a/src/org/traccar/protocol/Pt502ProtocolDecoder.java b/src/org/traccar/protocol/Pt502ProtocolDecoder.java
index fef5d9b39..1d976dcd5 100644
--- a/src/org/traccar/protocol/Pt502ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Pt502ProtocolDecoder.java
@@ -62,6 +62,10 @@ public class Pt502ProtocolDecoder extends BaseProtocolDecoder {
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":
diff --git a/src/org/traccar/protocol/Pt502ProtocolEncoder.java b/src/org/traccar/protocol/Pt502ProtocolEncoder.java
index 4a876f6da..bd56e306a 100644
--- a/src/org/traccar/protocol/Pt502ProtocolEncoder.java
+++ b/src/org/traccar/protocol/Pt502ProtocolEncoder.java
@@ -41,6 +41,8 @@ public class Pt502ProtocolEncoder extends StringProtocolEncoder implements Strin
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:
diff --git a/src/org/traccar/protocol/RecodaProtocol.java b/src/org/traccar/protocol/RecodaProtocol.java
new file mode 100644
index 000000000..daf167fd9
--- /dev/null
+++ b/src/org/traccar/protocol/RecodaProtocol.java
@@ -0,0 +1,46 @@
+/*
+ * 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.bootstrap.ServerBootstrap;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.handler.codec.frame.LengthFieldBasedFrameDecoder;
+import org.traccar.BaseProtocol;
+import org.traccar.TrackerServer;
+
+import java.nio.ByteOrder;
+import java.util.List;
+
+public class RecodaProtocol extends BaseProtocol {
+
+ public RecodaProtocol() {
+ super("recoda");
+ }
+
+ @Override
+ public void initTrackerServers(List<TrackerServer> serverList) {
+ TrackerServer server = new TrackerServer(new ServerBootstrap(), getName()) {
+ @Override
+ protected void addSpecificHandlers(ChannelPipeline pipeline) {
+ pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(1024, 4, 4, -8, 0));
+ pipeline.addLast("objectDecoder", new RecodaProtocolDecoder(RecodaProtocol.this));
+ }
+ };
+ server.setEndianness(ByteOrder.LITTLE_ENDIAN);
+ serverList.add(server);
+ }
+
+}
diff --git a/src/org/traccar/protocol/RecodaProtocolDecoder.java b/src/org/traccar/protocol/RecodaProtocolDecoder.java
new file mode 100644
index 000000000..8db582d35
--- /dev/null
+++ b/src/org/traccar/protocol/RecodaProtocolDecoder.java
@@ -0,0 +1,110 @@
+/*
+ * 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.traccar.BaseProtocolDecoder;
+import org.traccar.DeviceSession;
+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(RecodaProtocol 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 {
+
+ ChannelBuffer buf = (ChannelBuffer) msg;
+
+ int type = buf.readInt();
+ buf.readUnsignedInt(); // length
+
+ if (type != MSG_HEARTBEAT) {
+ buf.readUnsignedShort(); // version
+ buf.readUnsignedShort(); // index
+ }
+
+ if (type == MSG_SIGNAL_LINK_REGISTRATION) {
+
+ getDeviceSession(channel, remoteAddress, buf.readBytes(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();
+ position.setProtocol(getProtocolName());
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ position.setTime(new Date(buf.readLong()));
+
+ int flags = buf.readUnsignedByte();
+
+ if (BitUtil.check(flags, 0)) {
+
+ buf.readUnsignedShort(); // declination
+
+ position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedShort()));
+
+ position.setLongitude(buf.readUnsignedByte() + buf.readUnsignedByte() / 60.0);
+ position.setLatitude(buf.readUnsignedByte() + buf.readUnsignedByte() / 60.0);
+
+ position.setLongitude(position.getLongitude() + buf.readUnsignedInt() / 3600.0);
+ position.setLatitude(position.getLatitude() + buf.readUnsignedInt() / 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;
+ }
+
+}