diff options
26 files changed, 549 insertions, 57 deletions
diff --git a/setup/default.xml b/setup/default.xml index ee66774ec..f0aecddb4 100644 --- a/setup/default.xml +++ b/setup/default.xml @@ -258,5 +258,6 @@ <entry key='its.port'>5179</entry> <entry key='xrb28.port'>5180</entry> <entry key='c2stek.port'>5181</entry> + <entry key='nyitech.port'>5182</entry> </properties> diff --git a/src/org/traccar/BaseProtocolDecoder.java b/src/org/traccar/BaseProtocolDecoder.java index 7d840960b..cbef4568d 100644 --- a/src/org/traccar/BaseProtocolDecoder.java +++ b/src/org/traccar/BaseProtocolDecoder.java @@ -55,13 +55,13 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { return protocol != null ? protocol.getName() : PROTOCOL_UNKNOWN; } - public String getServer(Channel channel) { + public String getServer(Channel channel, char delimiter) { String server = config.getString(getProtocolName() + ".server"); if (server == null && channel != null) { InetSocketAddress address = (InetSocketAddress) channel.localAddress(); server = address.getAddress().getHostAddress() + ":" + address.getPort(); } - return server; + return server != null ? server.replace(':', delimiter) : null; } protected double convertSpeed(double value, String defaultUnits) { diff --git a/src/org/traccar/MainEventHandler.java b/src/org/traccar/MainEventHandler.java index 3032646c1..d4e1ff860 100644 --- a/src/org/traccar/MainEventHandler.java +++ b/src/org/traccar/MainEventHandler.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. @@ -29,23 +29,28 @@ import org.traccar.model.Position; import java.sql.SQLException; import java.util.Arrays; import java.util.HashSet; +import java.util.LinkedHashSet; 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 final Set<String> connectionlessProtocols = new HashSet<>(); + private final Set<String> logEvents; 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(","))); } @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + public void channelRead(ChannelHandlerContext ctx, Object msg) { if (msg instanceof Position) { Position position = (Position) msg; @@ -61,19 +66,45 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter { StringBuilder s = new StringBuilder(); s.append(formatChannel(ctx.channel())).append(" "); s.append("id: ").append(uniqueId); - s.append(", time: ").append(DateUtil.formatDate(position.getFixTime(), false)); - s.append(", lat: ").append(String.format("%.5f", position.getLatitude())); - s.append(", lon: ").append(String.format("%.5f", position.getLongitude())); - if (position.getSpeed() > 0) { - s.append(", speed: ").append(String.format("%.1f", position.getSpeed())); - } - s.append(", course: ").append(String.format("%.1f", position.getCourse())); - if (position.getAccuracy() > 0) { - s.append(", accuracy: ").append(String.format("%.1f", position.getAccuracy())); - } - Object cmdResult = position.getAttributes().get(Position.KEY_RESULT); - if (cmdResult != null) { - s.append(", result: ").append(cmdResult); + for (String event : logEvents) { + switch (event) { + case "time": + s.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())); + break; + case "speed": + if (position.getSpeed() > 0) { + s.append(", speed: ").append(String.format("%.1f", position.getSpeed())); + } + break; + case "course": + s.append(", course: ").append(String.format("%.1f", position.getCourse())); + break; + case "accuracy": + if (position.getAccuracy() > 0) { + s.append(", accuracy: ").append(String.format("%.1f", position.getAccuracy())); + } + break; + case "outdated": + if (position.getOutdated()) { + s.append(", outdated"); + } + break; + case "invalid": + if (!position.getValid()) { + s.append(", invalid"); + } + break; + default: + Object value = position.getAttributes().get(event); + if (value != null) { + s.append(", ").append(event).append(": ").append(value); + } + break; + } } LOGGER.info(s.toString()); @@ -104,13 +135,16 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter { } @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + while (cause.getCause() != null && cause.getCause() != cause) { + cause = cause.getCause(); + } LOGGER.warn(formatChannel(ctx.channel()) + " error", cause); closeChannel(ctx.channel()); } @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof IdleStateEvent) { LOGGER.info(formatChannel(ctx.channel()) + " timed out"); closeChannel(ctx.channel()); diff --git a/src/org/traccar/ServerManager.java b/src/org/traccar/ServerManager.java index cdc679e8a..6a3273402 100644 --- a/src/org/traccar/ServerManager.java +++ b/src/org/traccar/ServerManager.java @@ -88,7 +88,7 @@ public class ServerManager { try { server.start(); } catch (BindException e) { - LOGGER.warn("One of the protocols is disabled due to port conflict"); + LOGGER.warn("Port {} is disabled due to conflict", server.getPort()); } } } diff --git a/src/org/traccar/geocoder/GoogleGeocoder.java b/src/org/traccar/geocoder/GoogleGeocoder.java index af9b58a90..9494cab45 100644 --- a/src/org/traccar/geocoder/GoogleGeocoder.java +++ b/src/org/traccar/geocoder/GoogleGeocoder.java @@ -90,4 +90,9 @@ public class GoogleGeocoder extends JsonGeocoder { return null; } + @Override + protected String parseError(JsonObject json) { + return json.getString("error_message"); + } + } diff --git a/src/org/traccar/geocoder/JsonGeocoder.java b/src/org/traccar/geocoder/JsonGeocoder.java index 80123e01e..ed59a1d8d 100644 --- a/src/org/traccar/geocoder/JsonGeocoder.java +++ b/src/org/traccar/geocoder/JsonGeocoder.java @@ -64,10 +64,11 @@ public abstract class JsonGeocoder implements Geocoder { } return formattedAddress; } else { + String msg = "Empty address. Error: " + parseError(json); if (callback != null) { - callback.onFailure(new GeocoderException("Empty address")); + callback.onFailure(new GeocoderException(msg)); } else { - LOGGER.warn("Empty address"); + LOGGER.warn(msg); } } return null; @@ -113,4 +114,8 @@ public abstract class JsonGeocoder implements Geocoder { public abstract Address parseAddress(JsonObject json); + protected String parseError(JsonObject json) { + return null; + } + } diff --git a/src/org/traccar/protocol/AtrackProtocolDecoder.java b/src/org/traccar/protocol/AtrackProtocolDecoder.java index 1963763ed..71bb6791c 100644 --- a/src/org/traccar/protocol/AtrackProtocolDecoder.java +++ b/src/org/traccar/protocol/AtrackProtocolDecoder.java @@ -381,7 +381,7 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { .number("(d+),") // speed .number("(d+),") // outputs .number("(d+),") // adc - .number("[^,]*,") // driver + .number("([^,]+)?,") // driver .number("(d+),") // temp1 .number("(d+),") // temp2 .expression("[^,]*,") // text message @@ -455,6 +455,11 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_OUTPUT, parser.nextInt()); position.set(Position.PREFIX_ADC + 1, parser.nextInt()); + + if (parser.hasNext()) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + } + position.set(Position.PREFIX_TEMP + 1, parser.nextInt()); position.set(Position.PREFIX_TEMP + 2, parser.nextInt()); diff --git a/src/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/org/traccar/protocol/Gl200TextProtocolDecoder.java index 97cc8f987..31ff4a670 100644 --- a/src/org/traccar/protocol/Gl200TextProtocolDecoder.java +++ b/src/org/traccar/protocol/Gl200TextProtocolDecoder.java @@ -1079,9 +1079,11 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return null; } - int hdop = parser.nextInt(); - position.setValid(hdop > 0); - position.set(Position.KEY_HDOP, hdop); + if (parser.hasNext()) { + int hdop = parser.nextInt(); + position.setValid(hdop > 0); + position.set(Position.KEY_HDOP, hdop); + } position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); position.setCourse(parser.nextDouble(0)); diff --git a/src/org/traccar/protocol/GoSafeProtocolDecoder.java b/src/org/traccar/protocol/GoSafeProtocolDecoder.java index 06bd0873d..95ef18f20 100644 --- a/src/org/traccar/protocol/GoSafeProtocolDecoder.java +++ b/src/org/traccar/protocol/GoSafeProtocolDecoder.java @@ -112,7 +112,7 @@ public class GoSafeProtocolDecoder extends BaseProtocolDecoder { break; case "COT": if (index < values.length) { - position.set(Position.KEY_ODOMETER, Integer.parseInt(values[index++])); + position.set(Position.KEY_ODOMETER, Long.parseLong(values[index++])); } if (index < values.length) { String[] hours = values[index].split("-"); diff --git a/src/org/traccar/protocol/ItsProtocolDecoder.java b/src/org/traccar/protocol/ItsProtocolDecoder.java index d3cdb14c3..000e7759f 100644 --- a/src/org/traccar/protocol/ItsProtocolDecoder.java +++ b/src/org/traccar/protocol/ItsProtocolDecoder.java @@ -18,6 +18,7 @@ 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; @@ -72,7 +73,13 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { - Parser parser = new Parser(PATTERN, (String) msg); + String sentence = (String) msg; + + if (channel != null && sentence.startsWith("$,01,")) { + channel.writeAndFlush(new NetworkMessage("$,01,", remoteAddress)); + } + + Parser parser = new Parser(PATTERN, sentence); if (!parser.matches()) { return null; } diff --git a/src/org/traccar/protocol/L100FrameDecoder.java b/src/org/traccar/protocol/L100FrameDecoder.java index b7caa14f4..9e08120dd 100644 --- a/src/org/traccar/protocol/L100FrameDecoder.java +++ b/src/org/traccar/protocol/L100FrameDecoder.java @@ -20,6 +20,8 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import org.traccar.BaseFrameDecoder; +import java.nio.charset.StandardCharsets; + public class L100FrameDecoder extends BaseFrameDecoder { @Override @@ -30,6 +32,15 @@ public class L100FrameDecoder extends BaseFrameDecoder { return null; } + if (buf.getCharSequence(buf.readerIndex(), 4, StandardCharsets.US_ASCII).equals("ATL,")) { + return decodeNew(buf); + } else { + return decodeOld(buf); + } + } + + private Object decodeOld(ByteBuf buf) { + int header = buf.getByte(buf.readerIndex()); boolean obd = header == 'L' || header == 'H'; @@ -38,9 +49,9 @@ public class L100FrameDecoder extends BaseFrameDecoder { index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '*'); } else { index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x02); - if (index == -1) { + if (index < 0) { index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0x04); - if (index == -1) { + if (index < 0) { return null; } } @@ -60,4 +71,20 @@ public class L100FrameDecoder extends BaseFrameDecoder { return null; } + private Object decodeNew(ByteBuf buf) { + + int index = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '@'); + if (index < 0) { + return null; + } + + if (buf.writerIndex() >= index + 1) { + ByteBuf frame = buf.readRetainedSlice(index - buf.readerIndex()); + buf.skipBytes(1); // delimiter + return frame; + } + + return null; + } + } diff --git a/src/org/traccar/protocol/L100ProtocolDecoder.java b/src/org/traccar/protocol/L100ProtocolDecoder.java index 07c5622cb..4d6b8f34b 100644 --- a/src/org/traccar/protocol/L100ProtocolDecoder.java +++ b/src/org/traccar/protocol/L100ProtocolDecoder.java @@ -40,6 +40,7 @@ public class L100ProtocolDecoder extends BaseProtocolDecoder { private static final Pattern PATTERN = new PatternBuilder() .text("ATL") + .expression(",[^,]+,").optional() .number("(d{15}),") // imei .text("$GPRMC,") .number("(dd)(dd)(dd)") // time (hhmmss.sss) @@ -109,6 +110,27 @@ public class L100ProtocolDecoder extends BaseProtocolDecoder { .expression("(.+)") // data .compile(); + private static final Pattern PATTERN_NEW = new PatternBuilder() + .groupBegin() + .text("ATL,") + .expression("[LH],") // archive + .number("(d{15}),") // imei + .groupEnd("?") + .expression("([NPT]),") // alarm + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd),") // time (hhmmss) + .expression("([AV]),") // validity + .number("(d+.d+),([NS]),") // latitude + .number("(d+.d+),([EW]),") // longitude + .number("(d+.?d*),") // speed + .expression("(?:GPS|GSM|INV),") + .number("(d+),") // battery + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(d+),") // lac + .number("(d+)") // cid + .compile(); + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -121,6 +143,8 @@ public class L100ProtocolDecoder extends BaseProtocolDecoder { } else { return decodeObdLocation(channel, remoteAddress, sentence); } + } else if (!sentence.contains("$GPRMC")) { + return decodeNew(channel, remoteAddress, sentence); } else { return decodeNormal(channel, remoteAddress, sentence); } @@ -264,4 +288,50 @@ public class L100ProtocolDecoder extends BaseProtocolDecoder { return position; } + private Object decodeNew(Channel channel, SocketAddress remoteAddress, String sentence) { + + Parser parser = new Parser(PATTERN_NEW, sentence); + if (!parser.matches()) { + return null; + } + + String imei = parser.next(); + DeviceSession deviceSession; + if (imei != null) { + deviceSession = getDeviceSession(channel, remoteAddress, imei); + } else { + deviceSession = getDeviceSession(channel, remoteAddress); + } + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + switch (parser.next()) { + case "P": + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + break; + case "T": + position.set(Position.KEY_ALARM, Position.ALARM_TAMPERING); + break; + default: + break; + } + + position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS)); + position.setValid(parser.next().equals("A")); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_HEM)); + position.setSpeed(parser.nextDouble()); + + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001); + + position.setNetwork(new Network(CellTower.from( + parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextHexInt()))); + + return position; + } + } diff --git a/src/org/traccar/protocol/MeiligaoProtocolDecoder.java b/src/org/traccar/protocol/MeiligaoProtocolDecoder.java index 06ecba357..cbfc3660a 100644 --- a/src/org/traccar/protocol/MeiligaoProtocolDecoder.java +++ b/src/org/traccar/protocol/MeiligaoProtocolDecoder.java @@ -402,7 +402,7 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder { sendResponse(channel, remoteAddress, id, MSG_HEARTBEAT, response); return null; } else if (command == MSG_SERVER) { - ByteBuf response = Unpooled.copiedBuffer(getServer(channel), StandardCharsets.US_ASCII); + ByteBuf response = Unpooled.copiedBuffer(getServer(channel, ':'), StandardCharsets.US_ASCII); sendResponse(channel, remoteAddress, id, MSG_SERVER, response); return null; } else if (command == MSG_UPLOAD_PHOTO) { diff --git a/src/org/traccar/protocol/NyitechProtocol.java b/src/org/traccar/protocol/NyitechProtocol.java new file mode 100644 index 000000000..58974be5c --- /dev/null +++ b/src/org/traccar/protocol/NyitechProtocol.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 NyitechProtocol extends BaseProtocol { + + public NyitechProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 2, 2, -4, 0, true)); + pipeline.addLast(new NyitechProtocolDecoder(NyitechProtocol.this)); + } + }); + } + +} diff --git a/src/org/traccar/protocol/NyitechProtocolDecoder.java b/src/org/traccar/protocol/NyitechProtocolDecoder.java new file mode 100644 index 000000000..e145205f7 --- /dev/null +++ b/src/org/traccar/protocol/NyitechProtocolDecoder.java @@ -0,0 +1,123 @@ +/* + * 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.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; + +public class NyitechProtocolDecoder extends BaseProtocolDecoder { + + public NyitechProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final short MSG_LOGIN = 0x1001; + public static final short MSG_COMPREHENSIVE_LIVE = 0x2001; + public static final short MSG_COMPREHENSIVE_HISTORY = 0x2002; + public static final short MSG_ALARM = 0x2003; + public static final short MSG_FIXED = 0x2004; + + private void decodeLocation(Position position, ByteBuf buf) { + + DateBuilder dateBuilder = new DateBuilder() + .setDateReverse(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()) + .setTime(buf.readUnsignedByte(), buf.readUnsignedByte(), buf.readUnsignedByte()); + position.setTime(dateBuilder.getDate()); + + int flags = buf.readUnsignedByte(); + position.setValid(BitUtil.to(flags, 2) > 0); + + double lat = buf.readUnsignedIntLE() / 3600000.0; + double lon = buf.readUnsignedIntLE() / 3600000.0; + + position.setLatitude(BitUtil.check(flags, 2) ? lat : -lat); + position.setLongitude(BitUtil.check(flags, 3) ? lon : -lon); + + position.setSpeed(UnitsConverter.knotsFromCps(buf.readUnsignedShortLE())); + position.setCourse(buf.readUnsignedShortLE() * 0.1); + position.setAltitude(buf.readShortLE() * 0.1); + } + + private String decodeAlarm(int type) { + switch (type) { + case 0x09: + return Position.ALARM_ACCELERATION; + case 0x0a: + return Position.ALARM_BRAKING; + case 0x0b: + return Position.ALARM_CORNERING; + case 0x0e: + return Position.ALARM_SOS; + default: + return null; + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.skipBytes(2); // header + buf.readUnsignedShortLE(); // length + + String id = buf.readCharSequence(12, StandardCharsets.US_ASCII).toString(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, id); + if (deviceSession == null) { + return null; + } + + int type = buf.readUnsignedShortLE(); + + if (type != MSG_LOGIN && type != MSG_COMPREHENSIVE_LIVE + && type != MSG_COMPREHENSIVE_HISTORY && type != MSG_ALARM && type != MSG_FIXED) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + if (type == MSG_COMPREHENSIVE_LIVE || type == MSG_COMPREHENSIVE_HISTORY) { + buf.skipBytes(6); // time + buf.skipBytes(3); // data + } else if (type == MSG_ALARM) { + buf.readUnsignedShortLE(); // random number + buf.readUnsignedByte(); // tag + position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + buf.readUnsignedShortLE(); // threshold + buf.readUnsignedShortLE(); // value + buf.skipBytes(6); // time + } else if (type == MSG_FIXED) { + buf.skipBytes(6); // time + } + + decodeLocation(position, buf); + + return position; + } + +} diff --git a/src/org/traccar/protocol/SpotProtocolDecoder.java b/src/org/traccar/protocol/SpotProtocolDecoder.java index 78b2b0487..da36c2048 100644 --- a/src/org/traccar/protocol/SpotProtocolDecoder.java +++ b/src/org/traccar/protocol/SpotProtocolDecoder.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. @@ -49,7 +49,14 @@ public class SpotProtocolDecoder extends BaseHttpProtocolDecoder { public SpotProtocolDecoder(Protocol protocol) { super(protocol); try { - documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + builderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + builderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); + builderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + builderFactory.setXIncludeAware(false); + builderFactory.setExpandEntityReferences(false); + documentBuilder = builderFactory.newDocumentBuilder(); xPath = XPathFactory.newInstance().newXPath(); messageExpression = xPath.compile("//messageList/message"); } catch (ParserConfigurationException | XPathExpressionException e) { diff --git a/src/org/traccar/protocol/WristbandProtocolDecoder.java b/src/org/traccar/protocol/WristbandProtocolDecoder.java index 4e044c915..7f2b0af85 100644 --- a/src/org/traccar/protocol/WristbandProtocolDecoder.java +++ b/src/org/traccar/protocol/WristbandProtocolDecoder.java @@ -25,7 +25,10 @@ import org.traccar.Protocol; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; import org.traccar.helper.UnitsConverter; +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.nio.charset.StandardCharsets; @@ -46,12 +49,16 @@ public class WristbandProtocolDecoder extends BaseProtocolDecoder { 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); + String sentence = String.format("YX%s|%s|0|{F%02d#%s}\r\n", imei, version, type, data); ByteBuf response = Unpooled.buffer(); - response.writeBytes(new byte[]{0x00, 0x01, 0x02}); - response.writeShort(sentence.length()); + if (type != 91) { + 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}); + if (type != 91) { + response.writeBytes(new byte[]{(byte) 0xFF, (byte) 0xFE, (byte) 0xFC}); + } channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); } } @@ -59,7 +66,7 @@ public class WristbandProtocolDecoder extends BaseProtocolDecoder { private static final Pattern PATTERN = new PatternBuilder() .expression("..") // header .number("(d+)|") // imei - .number("(vd+.d+)|") // version + .number("([vV]d+.d+)|") // version .number("d+|") // model .text("{") .number("F(d+)") // function @@ -81,12 +88,56 @@ public class WristbandProtocolDecoder extends BaseProtocolDecoder { position.setValid(true); position.setLongitude(Double.parseDouble(values[0])); position.setLatitude(Double.parseDouble(values[1])); - position.setTime(new SimpleDateFormat("yyyyMMddHHmmss").parse(values[2])); + position.setTime(new SimpleDateFormat("yyyyMMddHHmm").parse(values[2])); position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[3]))); return position; } + private Position decodeStatus(DeviceSession deviceSession, String sentence) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(sentence.split(",")[0])); + + return position; + } + + private Position decodeNetwork(DeviceSession deviceSession, String sentence, boolean wifi) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + Network network = new Network(); + String[] fragments = sentence.split("\\|"); + + if (wifi) { + for (String item : fragments[0].split("_")) { + String[] values = item.split(","); + network.addWifiAccessPoint(WifiAccessPoint.from(values[0], Integer.parseInt(values[1]))); + } + } + + for (String item : fragments[wifi ? 1 : 0].split(":")) { + String[] values = item.split(","); + int lac = Integer.parseInt(values[0]); + int mnc = Integer.parseInt(values[1]); + int mcc = Integer.parseInt(values[2]); + int cid = Integer.parseInt(values[3]); + int rssi = Integer.parseInt(values[4]); + network.addCellTower(CellTower.from(mcc, mnc, lac, cid, rssi)); + } + + position.setNetwork(network); + + return position; + } + private List<Position> decodeMessage( Channel channel, SocketAddress remoteAddress, String sentence) throws ParseException { @@ -105,23 +156,32 @@ public class WristbandProtocolDecoder extends BaseProtocolDecoder { int type = parser.nextInt(); List<Position> positions = new LinkedList<>(); + String data = parser.next(); switch (type) { case 90: - sendResponse(channel, imei, version, type, getServer(channel)); + 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)); + sendResponse(channel, imei, version, type, time + "|" + getServer(channel, ',')); break; case 1: - sendResponse(channel, imei, version, type, "0"); + positions.add(decodeStatus(deviceSession, data)); + sendResponse(channel, imei, version, type, data.split(",")[1]); break; case 2: - for (String fragment : parser.next().split("\\|")) { + for (String fragment : data.split("\\|")) { positions.add(decodePosition(deviceSession, fragment)); } break; + case 3: + case 4: + positions.add(decodeNetwork(deviceSession, data, type == 3)); + break; + case 64: + sendResponse(channel, imei, version, type, data); + break; default: break; } diff --git a/src/org/traccar/sms/HttpSmsClient.java b/src/org/traccar/sms/HttpSmsClient.java index 994e2db4e..8e2b67bf7 100644 --- a/src/org/traccar/sms/HttpSmsClient.java +++ b/src/org/traccar/sms/HttpSmsClient.java @@ -38,6 +38,7 @@ public class HttpSmsClient implements SmsManager { private static final Logger LOGGER = LoggerFactory.getLogger(HttpSmsClient.class); private String url; + private String authorizationHeader; private String authorization; private String template; private boolean encode; @@ -45,6 +46,8 @@ public class HttpSmsClient implements SmsManager { public HttpSmsClient() { url = Context.getConfig().getString("sms.http.url"); + authorizationHeader = Context.getConfig().getString("sms.http.authorizationHeader", + SecurityRequestFilter.AUTHORIZATION_HEADER); authorization = Context.getConfig().getString("sms.http.authorization"); if (authorization == null) { String user = Context.getConfig().getString("sms.http.user"); @@ -70,7 +73,7 @@ public class HttpSmsClient implements SmsManager { try { return template .replace("{phone}", prepareValue(destAddress)) - .replace("{message}", prepareValue(message)); + .replace("{message}", prepareValue(message.trim())); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } @@ -78,7 +81,7 @@ public class HttpSmsClient implements SmsManager { private Invocation.Builder getRequestBuilder() { return Context.getClient().target(url).request() - .header(SecurityRequestFilter.AUTHORIZATION_HEADER, authorization); + .header(authorizationHeader, authorization); } @Override diff --git a/swagger.json b/swagger.json index 54ff10d1b..040e9d40e 100644 --- a/swagger.json +++ b/swagger.json @@ -1494,7 +1494,10 @@ "network": { "type": "string" }, - "attributes": {} + "attributes": { + "type": "object", + "additionalProperties": true + } } }, "User": { @@ -1561,7 +1564,10 @@ "token": { "type": "string" }, - "attributes": {} + "attributes": { + "type": "object", + "additionalProperties": true + } } }, "Server": { @@ -1614,7 +1620,10 @@ "coordinateFormat": { "type": "string" }, - "attributes": {} + "attributes": { + "type": "object", + "additionalProperties": true + } } }, "Command": { @@ -1631,7 +1640,10 @@ "type": { "type": "string" }, - "attributes": {} + "attributes": { + "type": "object", + "additionalProperties": true + } } }, "Device": { @@ -1680,7 +1692,10 @@ "type": "integer" } }, - "attributes": {} + "attributes": { + "type": "object", + "additionalProperties": true + } } }, "Group": { @@ -1694,7 +1709,10 @@ "groupId": { "type": "integer" }, - "attributes": {} + "attributes": { + "type": "object", + "additionalProperties": true + } } }, "Permission": { @@ -1758,7 +1776,10 @@ "calendarId": { "type": "integer" }, - "attributes": {} + "attributes": { + "type": "object", + "additionalProperties": true + } } }, "Notification": { @@ -1784,7 +1805,10 @@ "calendarId": { "type": "integer" }, - "attributes": {} + "attributes": { + "type": "object", + "additionalProperties": true + } } }, "NotificationType": { @@ -1819,7 +1843,10 @@ "maintenanceId": { "type": "integer" }, - "attributes": {} + "attributes": { + "type": "object", + "additionalProperties": true + } } }, "ReportSummary": { @@ -2003,7 +2030,10 @@ "type": "string", "description": "base64 encoded in iCalendar format" }, - "atributes": {} + "attributes": { + "type": "object", + "additionalProperties": true + } } }, "Attribute": { @@ -2037,7 +2067,10 @@ "uniqueId": { "type": "string" }, - "atributes": {} + "attributes": { + "type": "object", + "additionalProperties": true + } } }, "Maintenance": { @@ -2057,7 +2090,10 @@ "period": { "type": "number" }, - "atributes": {} + "attributes": { + "type": "object", + "additionalProperties": true + } } } }, diff --git a/test/org/traccar/protocol/AtrackProtocolDecoderTest.java b/test/org/traccar/protocol/AtrackProtocolDecoderTest.java index 82b4c92af..3a9382086 100644 --- a/test/org/traccar/protocol/AtrackProtocolDecoderTest.java +++ b/test/org/traccar/protocol/AtrackProtocolDecoderTest.java @@ -35,6 +35,9 @@ public class AtrackProtocolDecoderTest extends ProtocolTest { decoder.setCustom(true); verifyPositions(decoder, buffer( + "@P,9493,402,143,356961075931165,1546830150,1546830151,1546830151,-88429209,44271154,54,10,0,10,1,0,0,0,1858AE010000,2000,2000,\u001A,%CI%FL%ML%VN%PD%FC%EL%ET%AT%MF%MV%BV%DT%GN%GV%ME%RL%RP%SA%SM%TR%IA%MP,0,0,2T1KR32E28C706185,0,1,0,7,251,89,118,41,0,00A5001A040800A5001A040B00A5001A040C00A5001A040900A4001C040D00A50019040900A60019040900A4001B040B00A5001A040900A7001A040E\u001A,008CFE7C03C4\u001A,356961075931165,0,0,12,0,18,5,0\n")); + + verifyPositions(decoder, buffer( "@P,FD34,720,12256,357520076794151,1535445349,1535445354,1535500603,106784149,-6283086,105,2,138,0,3,0,0,0,,2000,2000,,%CI%TR%MV%BV%AT%SA%ET%GQ%GS%PC%RP%OD%AV1%XS%VS,0,0,0,0,0,0,0,0,1011677,0,138,0,0,0\r\n", "1535445376,1535445374,1535500603,106783763,-6282981,105,101,138,6,2,0,0,0,,2000,2000,,%CI%TR%MV%BV%AT%SA%ET%GQ%GS%PC%RP%OD%AV1%XS%VS,0,141,41,60,12,0,0,7,1011677,0,138,0,0,0\r\n", "1535445380,1535445378,1535500603,106783763,-6282981,105,103,138,6,2,0,0,0,,2000,2000,,%CI%TR%MV%BV%AT%SA%ET%GQ%GS%PC%RP%OD%AV1%XS%VS,0,135,41,61,12,0,0,9,1011677,0,138,0,0,0\r\n", diff --git a/test/org/traccar/protocol/Gl200TextProtocolDecoderTest.java b/test/org/traccar/protocol/Gl200TextProtocolDecoderTest.java index d68479097..ae4201f94 100644 --- a/test/org/traccar/protocol/Gl200TextProtocolDecoderTest.java +++ b/test/org/traccar/protocol/Gl200TextProtocolDecoderTest.java @@ -10,6 +10,9 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest { Gl200TextProtocolDecoder decoder = new Gl200TextProtocolDecoder(null); + verifyAttributes(decoder, buffer( + "+BUFF:GTSTC,410301,864802030022424,,,0,,,,,,,0228,0002,4EE8,1BFF489,00,20181207134332,EC90$")); + verifyPositions(decoder, buffer( "+RESP:GTFRI,1A0900,860599000306845,G3-313,0,0,4,1,2.1,0,426.7,8.611466,47.681639,20181214134603,0228,0001,077F,4812,25.2,1,5.7,34,437.3,8.611600,47.681846,20181214134619,0228,0001,077F,4812,25.2,1,4.4,62,438.2,8.611893,47.681983,20181214134633,0228,0001,077F,4812,25.2,1,4.8,78,436.6,8.612236,47.682040,20181214134648,0228,0001,077F,4812,25.2,83,20181214134702,0654$")); diff --git a/test/org/traccar/protocol/GoSafeProtocolDecoderTest.java b/test/org/traccar/protocol/GoSafeProtocolDecoderTest.java index 9cd8e4800..70c86bb23 100644 --- a/test/org/traccar/protocol/GoSafeProtocolDecoderTest.java +++ b/test/org/traccar/protocol/GoSafeProtocolDecoderTest.java @@ -11,6 +11,9 @@ public class GoSafeProtocolDecoderTest extends ProtocolTest { GoSafeProtocolDecoder decoder = new GoSafeProtocolDecoder(null); verifyPositions(decoder, text( + "*GS06,860078024226974,101437211218,,SYS:G3SC;V3.36;V1.1.8,GPS:A;7;N3.052302;E101.787216;16;137;48;1.58,COT:4261733103,ADC:22.86;0.58;0.01,DTT:4004;E1;0;0;0;3$101439211218,,SYS:G3SC;V3.36;V1.1.8,GPS:A;8;N3.052265;E101.787200;12;152;46;1.31,COT:4261733103,ADC:22.98;0.58;0.01,DTT:4004;E1;0;0;0;3$101441211218,,SYS:G3SC;V3.36;V1.1.8,GPS:A;8;N3.052247;E101.787232;8;131;46;1.34,COT:4261733103,ADC:23.13;0.58;0.01,DTT:4004;E1;0;0;0;3$101510211218,,SYS:G3SC;V3.36;V1.1.8,GPS:A;8;N3.052150;E101.787152;0;131;40;0.97,COT:4261733160,ADC:22.88;0.58;0.01,DTT:4000;E1;0;0;0;1$101540211218,,SYS:G3SC;V3.36;V1.1.8,GPS:A;7;N3.052150;E101.787152;0;131;40;0.97,COT:4261733160,ADC:22.91;0.58;0.00,DTT:4000;E1;0;0;0;1#")); + + verifyPositions(decoder, text( "*GS06,359568052570135,090349191018,,SYS:G3C;V1.40;V1.0.4,GPS:A;10;S26.112722;E28.078766;0;23;1581;0.80,COT:,ADC:10.86;3.79,DTT:4000;E7;0;0;0;1$090419191018,,SYS:G3C;V1.40;V1.0.4,GPS:A;10;S26.112722;E28.078766;0;23;1581;0.80,COT:,ADC:10.85;3.79,DTT:4000;E7;0;0;0;1#")); verifyPositions(decoder, text( diff --git a/test/org/traccar/protocol/L100FrameDecoderTest.java b/test/org/traccar/protocol/L100FrameDecoderTest.java index a94f2eb0d..5ffa3d8d1 100644 --- a/test/org/traccar/protocol/L100FrameDecoderTest.java +++ b/test/org/traccar/protocol/L100FrameDecoderTest.java @@ -11,6 +11,10 @@ public class L100FrameDecoderTest extends ProtocolTest { L100FrameDecoder decoder = new L100FrameDecoder(); verifyFrame( + binary("41544c2c4c2c3836383334353033383137313936332c4e2c3230313231382c3039333031362c412c3032352e3036373134342c4e2c3035352e3134343833332c452c3030302e302c4750532c333933392c3432342c30332c30303430352c303038383334"), + decoder.decode(null, null, binary("41544c2c4c2c3836383334353033383137313936332c4e2c3230313231382c3039333031362c412c3032352e3036373134342c4e2c3035352e3134343833332c452c3030302e302c4750532c333933392c3432342c30332c30303430352c30303838333440"))); + + verifyFrame( binary("4c2c41544c2c3836363739353033303437373935322c30312c303033352c"), decoder.decode(null, null, binary("4c2c41544c2c3836363739353033303437373935322c30312c303033352c2a28"))); diff --git a/test/org/traccar/protocol/L100ProtocolDecoderTest.java b/test/org/traccar/protocol/L100ProtocolDecoderTest.java index e86ea0e81..04f586f7a 100644 --- a/test/org/traccar/protocol/L100ProtocolDecoderTest.java +++ b/test/org/traccar/protocol/L100ProtocolDecoderTest.java @@ -10,6 +10,15 @@ public class L100ProtocolDecoderTest extends ProtocolTest { L100ProtocolDecoder decoder = new L100ProtocolDecoder(null); + verifyPosition(decoder, text( + "ATL,NP,868004029750174,$GPRMC,062943,A,2533.6719,N,09154.3203,E,0,179,311218,,,*39,#01111011000000,0,0,0,934.82,27.13,4.0,25,405,755,15af,974b,0,0,0,ATL")); + + verifyPosition(decoder, text( + "ATL,L,868345038171963,N,201218,093016,A,025.067144,N,055.144833,E,000.0,GPS,3939,424,03,00405,008834")); + + verifyPosition(decoder, text( + "N,111116,090031,A,028.123456,N,077.123456,E,000.0,GPS,4180,404,11,00159,064753")); + verifyAttributes(decoder, text( "L,ATLOBD,866795030475584,03,7429,143344,130918,CAN,0101:00076100,0103:0200,0104:3C,0105:84,010A:XX,010B:19,010C:0F98,010D:22,010E:68,010F:5A,0110:XXXX,0111:28,011C:20,011F:XXXX,0121:0000,0122:XXXX,012F:XX,0162:XX,0132:XXXX,0133:61,0143:00A8,0145:0F,0146:XX,0147:30,0148:XX,0149:31,014A:18,014B:XX,014C:92,0151:XX,0131:00BB,0144:8000,015E:XXXX,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0902:XXXXXXXXXXXXXXX")); diff --git a/test/org/traccar/protocol/NyitechProtocolDecoderTest.java b/test/org/traccar/protocol/NyitechProtocolDecoderTest.java new file mode 100644 index 000000000..4cafd7612 --- /dev/null +++ b/test/org/traccar/protocol/NyitechProtocolDecoderTest.java @@ -0,0 +1,30 @@ +package org.traccar.protocol; + +import org.junit.Test; +import org.traccar.ProtocolTest; + +public class NyitechProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + NyitechProtocolDecoder decoder = new NyitechProtocolDecoder(null); + + verifyPosition(decoder, binary( + "4040690030313436383230303238373201201c0c12031a308080801c0c12031a3007d67e7e08aceb841002000000ae08000000000000000000000000001e002900f0ffdd002700f2ffe0002700f2ffe1002400f0ffdf002400f3ffe3008a00ffff01010000a9c70d0a")); + + verifyPosition(decoder, binary( + "4040390030313436383230303238373203200100010c000000001c0c1203192a1b0c12171d3104fed87d089288801000000000000011ec0d0a")); + + verifyPosition(decoder, binary( + "4040480030313436383230303238373201101c0c12031a2907fa7e7e08b8eb841002000000bc080101040904040300010100000a818283848586878862611c0c12031a293f9c0d0a")); + + verifyPosition(decoder, binary( + "40404b003247512d313630313030313901101e0b100604190c02c83707f887ac0f000000002d130101030304020000010100000d426162636465666768696a6ba51e0b1006041965c30d0a")); + + verifyPosition(decoder, binary( + "4040490030313436383230303238373202201c0c120319348080001b0c12171d3104fed87d0892888010000000000000000000000000000000000000008b00ffff010100008a480d0a")); + + } + +} diff --git a/test/org/traccar/protocol/WristbandProtocolDecoderTest.java b/test/org/traccar/protocol/WristbandProtocolDecoderTest.java index cedc06f79..5635ce3d4 100644 --- a/test/org/traccar/protocol/WristbandProtocolDecoderTest.java +++ b/test/org/traccar/protocol/WristbandProtocolDecoderTest.java @@ -10,9 +10,27 @@ public class WristbandProtocolDecoderTest extends ProtocolTest { WristbandProtocolDecoder decoder = new WristbandProtocolDecoder(null); + verifyNotNull(decoder, binary( + "000102004459583836383730343034343735303035357c56312e307c317c7b4630342331382c30372c332c3539303139322c33303a31382c30372c332c3539303139322c33307d0d0afffefc")); + + verifyNotNull(decoder, binary( + "000102009c59583836383730343034343735303035357c56312e307c317c7b4630332330383a30353a38313a64383a31383a38372c2d37365f30303a37663a32383a63373a62613a63312c2d37375f39633a33643a63663a65643a62643a36622c2d36335f64383a65623a39373a65653a37373a32342c2d38327c31382c30372c332c3539303735342c33303a31382c30372c332c3539303735342c33307d0d0afffefc")); + + verifyNull(decoder, binary( + "000102002259583836383730343034343735303035357c56312e307c307c7b46363423317d0d0afffefc")); + + verifyPositions(decoder, binary( + "00010200bc59583836383730343034343735303035357c56312e307c317c7b4630322337372e3437373831372c2d33382e3839363239322c3230313831323239313235352c302e35387c37372e3437373739362c2d33382e3839363234352c3230313831323239313235352c302e30307c37372e3437373738392c2d33382e3839363233322c3230313831323239313235352c302e30307c37372e3437373737362c2d33382e3839363232322c3230313831323239313235352c302e30307d0d0afffefc")); + + verifyNull(decoder, binary( + "000102004759583836383730343034343735303035357c56312e307c317c7b463931233331305f30307c30307c30307c30307c57414e444149323031382f31322f31342031353a35367d0d0afffefc")); + verifyNull(decoder, binary( "000102004159583336373535313631303030303934347c56312e307c317c7b4639312330317c30307c30307c33475f7065745f323031382f30352f31362031313a30307d0d0afffefc")); + verifyPositions(decoder, false, binary( + "000102003559583836383730343034343735303035357c56312e307c317c7b4630312339342c312c3130302c302c33313030302c3930307d0d0afffefc")); + } } |