diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/org/traccar/BaseProtocolDecoder.java | 9 | ||||
-rw-r--r-- | src/org/traccar/api/resource/ServerResource.java | 6 | ||||
-rw-r--r-- | src/org/traccar/protocol/AtrackFrameDecoder.java | 23 | ||||
-rw-r--r-- | src/org/traccar/protocol/AtrackProtocolDecoder.java | 127 | ||||
-rw-r--r-- | src/org/traccar/protocol/Gl200TextProtocolDecoder.java | 15 | ||||
-rw-r--r-- | src/org/traccar/protocol/MeiligaoProtocolDecoder.java | 10 | ||||
-rw-r--r-- | src/org/traccar/protocol/SanavProtocolDecoder.java | 2 | ||||
-rw-r--r-- | src/org/traccar/protocol/WristbandProtocol.java | 42 | ||||
-rw-r--r-- | src/org/traccar/protocol/WristbandProtocolDecoder.java | 146 |
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); + } + +} |