aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/org/traccar/BaseProtocolDecoder.java9
-rw-r--r--src/org/traccar/api/resource/ServerResource.java6
-rw-r--r--src/org/traccar/protocol/AtrackFrameDecoder.java23
-rw-r--r--src/org/traccar/protocol/AtrackProtocolDecoder.java127
-rw-r--r--src/org/traccar/protocol/Gl200TextProtocolDecoder.java15
-rw-r--r--src/org/traccar/protocol/MeiligaoProtocolDecoder.java10
-rw-r--r--src/org/traccar/protocol/SanavProtocolDecoder.java2
-rw-r--r--src/org/traccar/protocol/WristbandProtocol.java42
-rw-r--r--src/org/traccar/protocol/WristbandProtocolDecoder.java146
9 files changed, 350 insertions, 30 deletions
diff --git a/src/org/traccar/BaseProtocolDecoder.java b/src/org/traccar/BaseProtocolDecoder.java
index d7ccb6460..d9ab60e3a 100644
--- a/src/org/traccar/BaseProtocolDecoder.java
+++ b/src/org/traccar/BaseProtocolDecoder.java
@@ -67,6 +67,15 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder {
return protocol.getName();
}
+ public String getServer(Channel channel) {
+ String server = Context.getConfig().getString(getProtocolName() + ".server");
+ if (server == null && channel != null) {
+ InetSocketAddress address = (InetSocketAddress) channel.localAddress();
+ server = address.getAddress().getHostAddress() + ":" + address.getPort();
+ }
+ return server;
+ }
+
protected double convertSpeed(double value, String defaultUnits) {
switch (Context.getConfig().getString(getProtocolName() + ".speed", defaultUnits)) {
case "kmh":
diff --git a/src/org/traccar/api/resource/ServerResource.java b/src/org/traccar/api/resource/ServerResource.java
index 61d3221f0..e7cad2a0c 100644
--- a/src/org/traccar/api/resource/ServerResource.java
+++ b/src/org/traccar/api/resource/ServerResource.java
@@ -53,7 +53,11 @@ public class ServerResource extends BaseResource {
@Path("geocode")
@GET
public String geocode(@QueryParam("latitude") double latitude, @QueryParam("longitude") double longitude) {
- return Context.getGeocoder().getAddress(latitude, longitude, null);
+ if (Context.getGeocoder() != null) {
+ return Context.getGeocoder().getAddress(latitude, longitude, null);
+ } else {
+ throw new RuntimeException("Reverse geocoding is not enabled");
+ }
}
}
diff --git a/src/org/traccar/protocol/AtrackFrameDecoder.java b/src/org/traccar/protocol/AtrackFrameDecoder.java
index 3d33d9862..f071e2d97 100644
--- a/src/org/traccar/protocol/AtrackFrameDecoder.java
+++ b/src/org/traccar/protocol/AtrackFrameDecoder.java
@@ -21,6 +21,8 @@ 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;
@@ -48,9 +50,24 @@ public class AtrackFrameDecoder extends BaseFrameDecoder {
} else {
- int endIndex = BufferUtil.indexOf("\r\n", buf);
- if (endIndex > 0) {
- return buf.readRetainedSlice(endIndex - buf.readerIndex() + 2);
+ 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);
+ }
}
}
diff --git a/src/org/traccar/protocol/AtrackProtocolDecoder.java b/src/org/traccar/protocol/AtrackProtocolDecoder.java
index 4a2d4b848..49f44a4f0 100644
--- a/src/org/traccar/protocol/AtrackProtocolDecoder.java
+++ b/src/org/traccar/protocol/AtrackProtocolDecoder.java
@@ -104,20 +104,81 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder {
}
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.PREFIX_TEMP + 1, 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;
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) {
@@ -208,6 +269,30 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder {
case "MA":
readString(buf); // mac address
break;
+ case "PD":
+ buf.readUnsignedByte(); // pending code status
+ break;
+ case "CD":
+ readString(buf); // sim cid
+ 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;
}
@@ -215,7 +300,7 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder {
if (cellTower.getMobileCountryCode() != null
&& cellTower.getMobileNetworkCode() != null
- && cellTower.getCellId() != null
+ && cellTower.getCellId() != null && cellTower.getCellId() != 0
&& cellTower.getLocationAreaCode() != null) {
position.setNetwork(new Network(cellTower));
} else if (cellTower.getSignalStrength() != null) {
@@ -282,11 +367,6 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder {
}
private static final Pattern PATTERN = new PatternBuilder()
- .text("@P,")
- .number("x+,") // checksum
- .number("d+,") // length
- .number("d+,") // index
- .number("(d+),") // imei
.number("(d+),") // date and time
.number("d+,") // rtc date and time
.number("d+,") // device date and time
@@ -308,18 +388,41 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder {
.optional(2)
.compile();
- private Position decodeText(Channel channel, SocketAddress remoteAddress, String sentence) {
+ private List<Position> decodeText(Channel channel, SocketAddress remoteAddress, String sentence) {
- Parser parser = new Parser(PATTERN, sentence);
- if (!parser.matches()) {
- return null;
+ int startIndex = 0;
+ for (int i = 0; i < 4; i++) {
+ startIndex = sentence.indexOf(',', startIndex + 1);
}
+ int endIndex = sentence.indexOf(',', startIndex + 1);
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ 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());
@@ -442,7 +545,7 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder {
if (custom) {
String form = this.form;
if (form == null) {
- form = readString(buf).substring("%CI".length());
+ form = readString(buf).trim().substring("%CI".length());
}
readBinaryCustomData(position, buf, form);
}
diff --git a/src/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/org/traccar/protocol/Gl200TextProtocolDecoder.java
index 675a7c1fe..164f20135 100644
--- a/src/org/traccar/protocol/Gl200TextProtocolDecoder.java
+++ b/src/org/traccar/protocol/Gl200TextProtocolDecoder.java
@@ -175,14 +175,16 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
.compile();
private static final Pattern PATTERN_FRI = new PatternBuilder()
- .text("+").expression("(?:RESP|BUFF):GTFRI,")
+ .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},") // report type
- .number("d{1,2},") // count
+ .number("d{1,2},").optional() // report type
+ .number("d{1,2},").optional() // count
+ .number(",").optional() // reserved
+ .number("(d+),").optional() // battery
.expression("((?:")
.expression(PATTERN_LOCATION.pattern())
.expression(")+)")
@@ -200,6 +202,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
.number("(?:d+.?d*|Inf|NaN)?,") // fuel consumption
.number("(d+)?,") // fuel level
.groupEnd()
+ .any()
.number("(dddd)(dd)(dd)") // date (yyyymmdd)
.number("(dd)(dd)(dd)").optional(2) // time (hhmmss)
.text(",")
@@ -711,6 +714,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
String vin = parser.next();
Integer power = parser.nextInt();
+ Integer battery = parser.nextInt();
Parser itemParser = new Parser(PATTERN_LOCATION, parser.next());
while (itemParser.find()) {
@@ -731,6 +735,9 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
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);
@@ -1094,6 +1101,8 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder {
result = decodeCan(channel, remoteAddress, sentence);
break;
case "FRI":
+ case "GEO":
+ case "STR":
result = decodeFri(channel, remoteAddress, sentence);
break;
case "ERI":
diff --git a/src/org/traccar/protocol/MeiligaoProtocolDecoder.java b/src/org/traccar/protocol/MeiligaoProtocolDecoder.java
index 30905076e..9fbd053eb 100644
--- a/src/org/traccar/protocol/MeiligaoProtocolDecoder.java
+++ b/src/org/traccar/protocol/MeiligaoProtocolDecoder.java
@@ -29,7 +29,6 @@ import org.traccar.helper.Parser;
import org.traccar.helper.PatternBuilder;
import org.traccar.model.Position;
-import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
@@ -206,15 +205,6 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder {
}
}
- private String getServer(Channel channel) {
- String server = Context.getConfig().getString(getProtocolName() + ".server");
- if (server == null && channel != null) {
- InetSocketAddress address = (InetSocketAddress) channel.localAddress();
- server = address.getAddress().getHostAddress() + ":" + address.getPort();
- }
- return server;
- }
-
private String decodeAlarm(short value) {
switch (value) {
case 0x01:
diff --git a/src/org/traccar/protocol/SanavProtocolDecoder.java b/src/org/traccar/protocol/SanavProtocolDecoder.java
index 688157171..763b51d04 100644
--- a/src/org/traccar/protocol/SanavProtocolDecoder.java
+++ b/src/org/traccar/protocol/SanavProtocolDecoder.java
@@ -87,7 +87,7 @@ public class SanavProtocolDecoder extends BaseProtocolDecoder {
position.setTime(dateBuilder.getDate());
if (parser.hasNext()) {
- int io = parser.nextInt();
+ int io = parser.nextHexInt();
for (int i = 0; i < 5; i++) {
position.set(Position.PREFIX_IN + (i + 1), BitUtil.check(io, i));
}
diff --git a/src/org/traccar/protocol/WristbandProtocol.java b/src/org/traccar/protocol/WristbandProtocol.java
new file mode 100644
index 000000000..02db38f4f
--- /dev/null
+++ b/src/org/traccar/protocol/WristbandProtocol.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.handler.codec.LengthFieldBasedFrameDecoder;
+import org.traccar.BaseProtocol;
+import org.traccar.PipelineBuilder;
+import org.traccar.TrackerServer;
+
+import java.util.List;
+
+public class WristbandProtocol extends BaseProtocol {
+
+ public WristbandProtocol() {
+ super("wristband");
+ }
+
+ @Override
+ public void initTrackerServers(List<TrackerServer> serverList) {
+ serverList.add(new TrackerServer(false, getName()) {
+ @Override
+ protected void addProtocolHandlers(PipelineBuilder pipeline) {
+ pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(1024, 3, 2, 3, 0));
+ pipeline.addLast("objectDecoder", new WristbandProtocolDecoder(WristbandProtocol.this));
+ }
+ });
+ }
+
+}
diff --git a/src/org/traccar/protocol/WristbandProtocolDecoder.java b/src/org/traccar/protocol/WristbandProtocolDecoder.java
new file mode 100644
index 000000000..f62836765
--- /dev/null
+++ b/src/org/traccar/protocol/WristbandProtocolDecoder.java
@@ -0,0 +1,146 @@
+/*
+ * 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.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.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(WristbandProtocol 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%d#%s}\r\n", imei, version, type, data);
+ ByteBuf response = Unpooled.buffer();
+ response.writeBytes(new byte[]{0x00, 0x01, 0x02});
+ response.writeShort(sentence.length());
+ response.writeCharSequence(sentence, StandardCharsets.US_ASCII);
+ 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("(vd+.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("yyyyMMddHHmmss").parse(values[2]));
+ position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[3])));
+
+ 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<>();
+
+ 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:
+ sendResponse(channel, imei, version, type, "0");
+ break;
+ case 2:
+ for (String fragment : parser.next().split("\\|")) {
+ positions.add(decodePosition(deviceSession, fragment));
+ }
+ 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);
+ }
+
+}