diff options
Diffstat (limited to 'src/main/java')
25 files changed, 648 insertions, 148 deletions
diff --git a/src/main/java/org/traccar/BasePipelineFactory.java b/src/main/java/org/traccar/BasePipelineFactory.java index b3d37f689..a92dba7fa 100644 --- a/src/main/java/org/traccar/BasePipelineFactory.java +++ b/src/main/java/org/traccar/BasePipelineFactory.java @@ -26,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.config.Keys; import org.traccar.handler.DefaultDataHandler; +import org.traccar.handler.TimeHandler; import org.traccar.handler.events.AlertEventHandler; import org.traccar.handler.events.CommandResultEventHandler; import org.traccar.handler.events.DriverEventHandler; @@ -56,11 +57,13 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { private static final Logger LOGGER = LoggerFactory.getLogger(BasePipelineFactory.class); private final TrackerServer server; + private final String protocol; private boolean eventsEnabled; private int timeout; public BasePipelineFactory(TrackerServer server, String protocol) { this.server = server; + this.protocol = protocol; eventsEnabled = Context.getConfig().getBoolean(Keys.EVENT_ENABLE); timeout = Context.getConfig().getInteger(Keys.PROTOCOL_TIMEOUT.withPrefix(protocol)); if (timeout == 0) { @@ -103,7 +106,7 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { } pipeline.addLast(new OpenChannelHandler(server)); pipeline.addLast(new NetworkMessageHandler()); - pipeline.addLast(new StandardLoggingHandler()); + pipeline.addLast(new StandardLoggingHandler(protocol)); addProtocolHandlers(handler -> { if (!(handler instanceof BaseProtocolDecoder || handler instanceof BaseProtocolEncoder)) { @@ -118,6 +121,7 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { addHandlers( pipeline, + TimeHandler.class, GeolocationHandler.class, HemisphereHandler.class, DistanceHandler.class, diff --git a/src/main/java/org/traccar/MainEventHandler.java b/src/main/java/org/traccar/MainEventHandler.java index a8b53ff60..2309b1e70 100644 --- a/src/main/java/org/traccar/MainEventHandler.java +++ b/src/main/java/org/traccar/MainEventHandler.java @@ -45,10 +45,10 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter { public MainEventHandler() { String connectionlessProtocolList = Context.getConfig().getString("status.ignoreOffline"); if (connectionlessProtocolList != null) { - connectionlessProtocols.addAll(Arrays.asList(connectionlessProtocolList.split(","))); + connectionlessProtocols.addAll(Arrays.asList(connectionlessProtocolList.split("[, ]"))); } logAttributes.addAll(Arrays.asList( - Context.getConfig().getString("logger.attributes", DEFAULT_LOGGER_ATTRIBUTES).split(","))); + Context.getConfig().getString("logger.attributes", DEFAULT_LOGGER_ATTRIBUTES).split("[, ]"))); } @Override diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java index 6fe8bad1c..3acd19b6a 100644 --- a/src/main/java/org/traccar/MainModule.java +++ b/src/main/java/org/traccar/MainModule.java @@ -59,6 +59,7 @@ import org.traccar.handler.GeolocationHandler; import org.traccar.handler.HemisphereHandler; import org.traccar.handler.MotionHandler; import org.traccar.handler.RemoteAddressHandler; +import org.traccar.handler.TimeHandler; import org.traccar.handler.events.AlertEventHandler; import org.traccar.handler.events.CommandResultEventHandler; import org.traccar.handler.events.DriverEventHandler; @@ -300,6 +301,15 @@ public class MainModule extends AbstractModule { @Singleton @Provides + public static TimeHandler provideTimeHandler(Config config) { + if (config.hasKey(Keys.TIME_OVERRIDE)) { + return new TimeHandler(config); + } + return null; + } + + @Singleton + @Provides public static DefaultDataHandler provideDefaultDataHandler(@Nullable DataManager dataManager) { if (dataManager != null) { return new DefaultDataHandler(dataManager); diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 48cf3e558..a000329e2 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -184,6 +184,19 @@ public final class Keys { "filter.skipAttributes.enable", Boolean.class); /** + * Override device time. Possible values are 'deviceTime' and 'serverTime' + */ + public static final ConfigKey TIME_OVERRIDE = new ConfigKey( + "time.override", String.class); + + /** + * List of protocols for overriding time. If not specified override is applied globally. List consist of protocol + * names that can be separated by comma or single space character. + */ + public static final ConfigKey TIME_PROTOCOLS = new ConfigKey( + "time.protocols", String.class); + + /** * Replaces coordinates with last known if change is less than a 'coordinates.error' meters. Helps to avoid * coordinates jumps during parking period. */ diff --git a/src/main/java/org/traccar/handler/StandardLoggingHandler.java b/src/main/java/org/traccar/handler/StandardLoggingHandler.java index 88010458f..13c5f8281 100644 --- a/src/main/java/org/traccar/handler/StandardLoggingHandler.java +++ b/src/main/java/org/traccar/handler/StandardLoggingHandler.java @@ -31,6 +31,12 @@ public class StandardLoggingHandler extends ChannelDuplexHandler { private static final Logger LOGGER = LoggerFactory.getLogger(StandardLoggingHandler.class); + private final String protocol; + + public StandardLoggingHandler(String protocol) { + this.protocol = protocol; + } + @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { log(ctx, false, msg); @@ -58,7 +64,7 @@ public class StandardLoggingHandler extends ChannelDuplexHandler { StringBuilder message = new StringBuilder(); message.append("[").append(ctx.channel().id().asShortText()).append(": "); - message.append(((InetSocketAddress) ctx.channel().localAddress()).getPort()); + message.append(protocol); if (downstream) { message.append(" > "); } else { diff --git a/src/main/java/org/traccar/handler/TimeHandler.java b/src/main/java/org/traccar/handler/TimeHandler.java new file mode 100644 index 000000000..d8039518c --- /dev/null +++ b/src/main/java/org/traccar/handler/TimeHandler.java @@ -0,0 +1,63 @@ +/* + * 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.handler; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.model.Position; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +@ChannelHandler.Sharable +public class TimeHandler extends ChannelInboundHandlerAdapter { + + private final boolean useServerTime; + private final Set<String> protocols; + + public TimeHandler(Config config) { + useServerTime = config.getString(Keys.TIME_OVERRIDE).equalsIgnoreCase("serverTime"); + String protocolList = Context.getConfig().getString(Keys.TIME_PROTOCOLS); + if (protocolList != null) { + protocols = new HashSet<>(Arrays.asList(protocolList.split("[, ]"))); + } else { + protocols = null; + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + + if (msg instanceof Position && (protocols == null + || protocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName()))) { + + Position position = (Position) msg; + if (useServerTime) { + position.setDeviceTime(position.getServerTime()); + } + position.setFixTime(position.getDeviceTime()); + + } + ctx.fireChannelRead(msg); + } + +} diff --git a/src/main/java/org/traccar/model/Position.java b/src/main/java/org/traccar/model/Position.java index 4b327cbd2..6032dc588 100644 --- a/src/main/java/org/traccar/model/Position.java +++ b/src/main/java/org/traccar/model/Position.java @@ -157,7 +157,7 @@ public class Position extends Message { this.protocol = protocol; } - private Date serverTime; + private Date serverTime = new Date(); public Date getServerTime() { return serverTime; diff --git a/src/main/java/org/traccar/protocol/BoxProtocolDecoder.java b/src/main/java/org/traccar/protocol/BoxProtocolDecoder.java index 3635c29e5..853fa8f81 100644 --- a/src/main/java/org/traccar/protocol/BoxProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/BoxProtocolDecoder.java @@ -47,7 +47,10 @@ public class BoxProtocolDecoder extends BaseProtocolDecoder { .number("(d+.?d*),") // distance .number("(d+),") // event .number("(d+)") // status - .any() + .groupBegin() + .text(";") + .expression("(.+)") + .groupEnd("?") .compile(); @Override @@ -99,6 +102,14 @@ public class BoxProtocolDecoder extends BaseProtocolDecoder { position.setValid(!BitUtil.check(status, 2)); position.set(Position.KEY_STATUS, status); + if (parser.hasNext()) { + String[] data = parser.next().split(";"); + for (String item : data) { + int valueIndex = item.indexOf(','); + position.set(item.substring(0, valueIndex).toLowerCase(), item.substring(valueIndex + 1)); + } + } + return position; } diff --git a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java index beaa34125..35696ee12 100644 --- a/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/FifotrackProtocolDecoder.java @@ -97,9 +97,9 @@ public class FifotrackProtocolDecoder extends BaseProtocolDecoder { private void requestPhoto(Channel channel, SocketAddress socketAddress, String imei, String file) { if (channel != null) { - String content = "D06," + file + "," + photo.writerIndex() + "," + Math.min(1024, photo.writableBytes()); - int length = 1 + imei.length() + 1 + content.length() + 5; - String response = String.format("@@%02d,%s,%s*", length, imei, content); + String content = "1,D06," + file + "," + photo.writerIndex() + "," + Math.min(1024, photo.writableBytes()); + int length = 1 + imei.length() + 1 + content.length(); + String response = String.format("##%02d,%s,%s*", length, imei, content); response += Checksum.sum(response) + "\r\n"; channel.writeAndFlush(new NetworkMessage(response, socketAddress)); } diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java index aeb57a116..46ef4fff8 100644 --- a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java @@ -648,16 +648,16 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_AXLE_WEIGHT, Integer.parseInt(values[index - 1])); } if (BitUtil.check(reportMask, 16) && !values[index++].isEmpty()) { - position.set("tachographInfo", Integer.parseInt(values[index - 1])); + position.set("tachographInfo", Integer.parseInt(values[index - 1], 16)); } if (BitUtil.check(reportMask, 17) && !values[index++].isEmpty()) { - position.set("indicators", Integer.parseInt(values[index - 1])); + position.set("indicators", Integer.parseInt(values[index - 1], 16)); } if (BitUtil.check(reportMask, 18) && !values[index++].isEmpty()) { - position.set("lights", Integer.parseInt(values[index - 1])); + position.set("lights", Integer.parseInt(values[index - 1], 16)); } if (BitUtil.check(reportMask, 19) && !values[index++].isEmpty()) { - position.set("doors", Integer.parseInt(values[index - 1])); + position.set("doors", Integer.parseInt(values[index - 1], 16)); } if (BitUtil.check(reportMask, 20) && !values[index++].isEmpty()) { position.set("vehicleOverspeed", Double.parseDouble(values[index - 1])); diff --git a/src/main/java/org/traccar/protocol/ItsFrameDecoder.java b/src/main/java/org/traccar/protocol/ItsFrameDecoder.java new file mode 100644 index 000000000..cebfdca5f --- /dev/null +++ b/src/main/java/org/traccar/protocol/ItsFrameDecoder.java @@ -0,0 +1,67 @@ +/* + * 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 io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; +import org.traccar.helper.BufferUtil; + +public class ItsFrameDecoder extends BaseFrameDecoder { + + private static final int MINIMUM_LENGTH = 20; + + private ByteBuf readFrame(ByteBuf buf, int delimiterIndex, int skip) { + int headerIndex = buf.indexOf(buf.readerIndex() + 1, buf.writerIndex(), (byte) '$'); + if (headerIndex > 0 && headerIndex < delimiterIndex) { + return buf.readRetainedSlice(headerIndex - buf.readerIndex()); + } else { + ByteBuf frame = buf.readRetainedSlice(delimiterIndex - buf.readerIndex()); + buf.skipBytes(skip); + return frame; + } + } + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + while (buf.isReadable() && buf.getByte(buf.readerIndex()) != '$') { + buf.skipBytes(1); + } + + int delimiterIndex = BufferUtil.indexOf("\r\n", buf); + if (delimiterIndex > MINIMUM_LENGTH) { + return readFrame(buf, delimiterIndex, 2); + } else { + delimiterIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) '*'); + if (delimiterIndex > MINIMUM_LENGTH) { + if (buf.writerIndex() > delimiterIndex + 1 && buf.getByte(delimiterIndex + 1) == '*') { + delimiterIndex += 1; + } + if (buf.getByte(delimiterIndex - 2) == ',') { + return readFrame(buf, delimiterIndex - 1, 2); // skip binary checksum + } else { + return readFrame(buf, delimiterIndex, 1); + } + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/ItsProtocol.java b/src/main/java/org/traccar/protocol/ItsProtocol.java index f53600dc9..e074f9277 100644 --- a/src/main/java/org/traccar/protocol/ItsProtocol.java +++ b/src/main/java/org/traccar/protocol/ItsProtocol.java @@ -18,7 +18,6 @@ package org.traccar.protocol; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import org.traccar.BaseProtocol; -import org.traccar.CharacterDelimiterFrameDecoder; import org.traccar.PipelineBuilder; import org.traccar.TrackerServer; @@ -28,7 +27,7 @@ public class ItsProtocol extends BaseProtocol { addServer(new TrackerServer(false, getName()) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline) { - pipeline.addLast(new CharacterDelimiterFrameDecoder(1024, '*')); + pipeline.addLast(new ItsFrameDecoder()); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new ItsProtocolDecoder(ItsProtocol.this)); diff --git a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java index 482f34e65..313575f27 100644 --- a/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/ItsProtocolDecoder.java @@ -23,6 +23,8 @@ 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 java.net.SocketAddress; @@ -41,11 +43,11 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { .groupBegin() .expression("[^,]+,") // vendor .expression("[^,]+,") // firmware version - .expression("[^,]+,") // type - .number("d+,") - .expression("[LH],") // history + .expression("(..),") // status + .number("(d+),") // event + .expression("([LH]),") // history .or() - .expression("[^,]+,") // type + .expression("([^,]+),") // type .groupEnd() .number("(d{15}),") // imei .groupBegin() @@ -72,12 +74,17 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { .number("([01]),") // charging .number("(d+.?d*),") // power .number("(d+.?d*),") // battery - .number("[01],") // emergency + .number("([01]),") // emergency .expression("[CO]?,") // tamper - .number("(?:x+,){5}") // main cell - .number("(?:-?x+,){12}") // other cells + .number("((?:x+,){5}") // main cell + .number("(?:-?x+,){12})") // other cells .number("([01]{4}),") // inputs .number("([01]{2}),") // outputs + .groupBegin() + .number("d+,") // index + .number("(d+.d+),") // adc1 + .number("(d+.d+),") // adc2 + .groupEnd("?") .groupEnd("?") .or() .number("(-?d+.d+),") // altitude @@ -101,6 +108,12 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { return Position.ALARM_CORNERING; case "OS": return Position.ALARM_OVERSPEED; + case "TA": + return Position.ALARM_TAMPERING; + case "BD": + return Position.ALARM_POWER_CUT; + case "BR": + return Position.ALARM_POWER_RESTORED; default: return null; } @@ -121,6 +134,11 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { return null; } + String status = parser.next(); + Integer event = parser.nextInt(); + boolean history = "H".equals(parser.next()); + String type = parser.next(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); if (deviceSession == null) { return null; @@ -129,8 +147,28 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); + if (type != null && type.equals("EMR")) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + + if (event != null) { + position.set(Position.KEY_EVENT, event); + } + if (history) { + position.set(Position.KEY_ARCHIVE, true); + } + if (parser.hasNext()) { - position.set(Position.KEY_ALARM, decodeAlarm(parser.next())); + status = parser.next(); + } + if (status != null) { + if (status.equals("IN")) { + position.set(Position.KEY_IGNITION, true); + } else if (status.equals("IF")) { + position.set(Position.KEY_IGNITION, false); + } else { + position.set(Position.KEY_ALARM, decodeAlarm(status)); + } } if (parser.hasNext()) { @@ -149,17 +187,40 @@ public class ItsProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_SATELLITES, parser.nextInt()); } - if (parser.hasNext(7)) { + if (parser.hasNext(8)) { position.setAltitude(parser.nextDouble()); position.set(Position.KEY_IGNITION, parser.nextInt() > 0); position.set(Position.KEY_CHARGE, parser.nextInt() > 0); position.set(Position.KEY_POWER, parser.nextDouble()); position.set(Position.KEY_BATTERY, parser.nextDouble()); + + position.set("emergency", parser.nextInt() > 0); + + String[] cells = parser.next().split(","); + int mcc = Integer.parseInt(cells[1]); + int mnc = Integer.parseInt(cells[2]); + int lac = Integer.parseInt(cells[3], 16); + int cid = Integer.parseInt(cells[4], 16); + Network network = new Network(CellTower.from(mcc, mnc, lac, cid, Integer.parseInt(cells[0]))); + for (int i = 0; i < 4; i++) { + lac = Integer.parseInt(cells[5 + 3 * i + 1], 16); + cid = Integer.parseInt(cells[5 + 3 * i + 2], 16); + if (lac > 0 && cid > 0) { + network.addCellTower(CellTower.from(mcc, mnc, lac, cid)); + } + } + position.setNetwork(network); + position.set(Position.KEY_INPUT, parser.nextBinInt()); position.set(Position.KEY_OUTPUT, parser.nextBinInt()); } if (parser.hasNext(2)) { + position.set(Position.PREFIX_ADC + 1, parser.nextDouble()); + position.set(Position.PREFIX_ADC + 2, parser.nextDouble()); + } + + if (parser.hasNext(2)) { position.setAltitude(parser.nextDouble()); position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); } diff --git a/src/main/java/org/traccar/protocol/NyitechProtocolDecoder.java b/src/main/java/org/traccar/protocol/NyitechProtocolDecoder.java index e145205f7..62b41a2ea 100644 --- a/src/main/java/org/traccar/protocol/NyitechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/NyitechProtocolDecoder.java @@ -22,6 +22,7 @@ import org.traccar.DeviceSession; import org.traccar.Protocol; import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; +import org.traccar.helper.ObdDecoder; import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; @@ -102,20 +103,66 @@ public class NyitechProtocolDecoder extends BaseProtocolDecoder { position.setDeviceId(deviceSession.getDeviceId()); if (type == MSG_COMPREHENSIVE_LIVE || type == MSG_COMPREHENSIVE_HISTORY) { + buf.skipBytes(6); // time - buf.skipBytes(3); // data + boolean includeLocation = buf.readUnsignedByte() > 0; + boolean includeObd = buf.readUnsignedByte() > 0; + buf.readUnsignedByte(); // include sensor + + if (includeLocation) { + decodeLocation(position, buf); + } else { + getLastLocation(position, null); + } + + if (includeObd) { + int count = buf.readUnsignedByte(); + for (int i = 0; i < count; i++) { + int pid = buf.readUnsignedShortLE(); + int length = buf.readUnsignedByte(); + switch (length) { + case 1: + position.add(ObdDecoder.decodeData(pid, buf.readByte(), true)); + break; + case 2: + position.add(ObdDecoder.decodeData(pid, buf.readShortLE(), true)); + break; + case 4: + position.add(ObdDecoder.decodeData(pid, buf.readIntLE(), true)); + break; + default: + buf.skipBytes(length); + break; + } + } + } + + position.set(Position.KEY_FUEL_USED, buf.readUnsignedInt() * 0.01); + position.set(Position.KEY_ODOMETER_TRIP, buf.readUnsignedInt()); + + } 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 + + decodeLocation(position, buf); + } else if (type == MSG_FIXED) { + buf.skipBytes(6); // time - } - decodeLocation(position, buf); + decodeLocation(position, buf); + + } else { + + decodeLocation(position, buf); + + } return position; } diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocol.java b/src/main/java/org/traccar/protocol/RuptelaProtocol.java index 1ac62570a..a574293cd 100644 --- a/src/main/java/org/traccar/protocol/RuptelaProtocol.java +++ b/src/main/java/org/traccar/protocol/RuptelaProtocol.java @@ -26,6 +26,7 @@ public class RuptelaProtocol extends BaseProtocol { public RuptelaProtocol() { setSupportedDataCommands( Command.TYPE_CUSTOM, + Command.TYPE_REQUEST_PHOTO, Command.TYPE_CONFIGURATION, Command.TYPE_GET_VERSION, Command.TYPE_FIRMWARE_UPDATE, diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java index a9ab5f5fe..2d476427d 100644 --- a/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/RuptelaProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 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. @@ -48,6 +48,7 @@ public class RuptelaProtocolDecoder extends BaseProtocolDecoder { public static final int MSG_SMS_VIA_GPRS = 8; public static final int MSG_DTCS = 9; public static final int MSG_SET_IO = 17; + public static final int MSG_FILES = 37; public static final int MSG_EXTENDED_RECORDS = 68; private Position decodeCommandResponse(DeviceSession deviceSession, int type, ByteBuf buf) { diff --git a/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java b/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java index 4242584c9..96d0da5a7 100644 --- a/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/RuptelaProtocolEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 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. @@ -46,6 +46,12 @@ public class RuptelaProtocolEncoder extends BaseProtocolEncoder { case Command.TYPE_CUSTOM: content.writeBytes(command.getString(Command.KEY_DATA).getBytes(StandardCharsets.US_ASCII)); return encodeContent(RuptelaProtocolDecoder.MSG_SMS_VIA_GPRS, content); + case Command.TYPE_REQUEST_PHOTO: + content.writeByte(1); // sub-command + content.writeByte(0); // source + content.writeInt(0); // start timestamp + content.writeInt(Integer.MAX_VALUE); // end timestamp + return encodeContent(RuptelaProtocolDecoder.MSG_FILES, content); case Command.TYPE_CONFIGURATION: content.writeBytes((command.getString(Command.KEY_DATA) + "\r\n").getBytes(StandardCharsets.US_ASCII)); return encodeContent(RuptelaProtocolDecoder.MSG_DEVICE_CONFIGURATION, content); diff --git a/src/main/java/org/traccar/protocol/SanulProtocol.java b/src/main/java/org/traccar/protocol/SanulProtocol.java new file mode 100644 index 000000000..3104e9366 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SanulProtocol.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 SanulProtocol extends BaseProtocol { + + public SanulProtocol() { + addServer(new TrackerServer(false, getName()) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline) { + pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, 1024, 3, 2, 0, 0, true)); + pipeline.addLast(new SanulProtocolDecoder(SanulProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SanulProtocolDecoder.java b/src/main/java/org/traccar/protocol/SanulProtocolDecoder.java new file mode 100644 index 000000000..036d1ee51 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SanulProtocolDecoder.java @@ -0,0 +1,97 @@ +/* + * 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.buffer.Unpooled; +import io.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; + +public class SanulProtocolDecoder extends BaseProtocolDecoder { + + public static final int MSG_LOGIN = 0; + public static final int MSG_LOCATION = 1; + public static final int MSG_RESPONSE = 5; + + public SanulProtocolDecoder(Protocol protocol) { + super(protocol); + } + + private void sendResponse(Channel channel, int type) { + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(0xaa); // header + response.writeShortLE(0x85da); // reserved + response.writeShortLE(15); // length + response.writeByte(1); // edition + response.writeShortLE(MSG_RESPONSE); + response.writeShortLE(type); + response.writeIntLE(0); // command id + response.writeByte(0); // status + response.writeByte(0); // result length + response.writeIntLE(0x20000); // result data ? + channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); + } + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + buf.readUnsignedByte(); // header + buf.readUnsignedShortLE(); // reserved + buf.readUnsignedShortLE(); // length + buf.readUnsignedByte(); // edition + + int type = buf.readUnsignedShortLE(); + + buf.readUnsignedIntLE(); // command id + + sendResponse(channel, type); + + if (type == MSG_LOGIN) { + + getDeviceSession(channel, remoteAddress, buf.toString(StandardCharsets.US_ASCII).trim()); + + } else if (type == MSG_LOCATION) { + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession == null) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + return position; + + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java index a80e84728..bd485ca70 100644 --- a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java @@ -254,7 +254,8 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { String type = values[index++].substring(5); - if (!type.equals("STT") && !type.equals("EMG") && !type.equals("EVT") && !type.equals("ALT")) { + if (!type.equals("STT") && !type.equals("EMG") && !type.equals("EVT") + && !type.equals("ALT") && !type.equals("UEX")) { return null; } @@ -322,6 +323,17 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { case "ALT": position.set(Position.KEY_ALARM, decodeAlert(Integer.parseInt(values[index++]))); break; + case "UEX": + int remaining = Integer.parseInt(values[index++]); + while (remaining > 0) { + String value = values[index++]; + String[] pair = value.split("="); + if (pair.length >= 2) { + position.set(pair[0].toLowerCase(), pair[1].trim()); + } + remaining -= value.length() + 1; + } + break; default: break; } diff --git a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java index fc7b88b08..96fb7f96a 100644 --- a/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/T800xProtocolDecoder.java @@ -32,6 +32,7 @@ import org.traccar.model.Network; import org.traccar.model.Position; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; public class T800xProtocolDecoder extends BaseProtocolDecoder { @@ -101,7 +102,6 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedShort(); // length int index = buf.readUnsignedShort(); ByteBuf imei = buf.readSlice(8); - int alarm = 0; DeviceSession deviceSession = getDeviceSession( channel, remoteAddress, ByteBufUtil.hexDump(imei).substring(1)); @@ -111,96 +111,121 @@ public class T800xProtocolDecoder extends BaseProtocolDecoder { if (type == MSG_GPS || type == MSG_ALARM) { + return decodePosition(channel, deviceSession, buf, header, type, index, imei); + + } else if (type == MSG_COMMAND) { + Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - position.set(Position.KEY_INDEX, index); + getLastLocation(position, null); + + buf.readUnsignedByte(); // protocol number - buf.readUnsignedShort(); // acc on interval - buf.readUnsignedShort(); // acc off interval - buf.readUnsignedByte(); // angle compensation - buf.readUnsignedShort(); // distance compensation + position.set(Position.KEY_RESULT, buf.toString(StandardCharsets.UTF_16LE)); - position.set(Position.KEY_RSSI, BitUtil.to(buf.readUnsignedShort(), 7)); + sendResponse(channel, header, type, index, imei, 0); - int status = buf.readUnsignedByte(); - position.set(Position.KEY_SATELLITES, BitUtil.to(status, 5)); + return position; - buf.readUnsignedByte(); // gsensor manager status - buf.readUnsignedByte(); // other flags - buf.readUnsignedByte(); // heartbeat - buf.readUnsignedByte(); // relay status - buf.readUnsignedShort(); // drag alarm setting + } - int io = buf.readUnsignedShort(); - position.set(Position.KEY_IGNITION, BitUtil.check(io, 14)); - position.set("ac", BitUtil.check(io, 13)); + sendResponse(channel, header, type, index, imei, 0); - position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort()); - position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShort()); + return null; + } - alarm = buf.readUnsignedByte(); - position.set(Position.KEY_ALARM, decodeAlarm(alarm)); + private Position decodePosition( + Channel channel, DeviceSession deviceSession, + ByteBuf buf, short header, int type, int index, ByteBuf imei) { - buf.readUnsignedByte(); // reserved + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); - position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + position.set(Position.KEY_INDEX, index); - int battery = BcdUtil.readInteger(buf, 2); - if (battery == 0) { - battery = 100; - } - position.set(Position.KEY_BATTERY, battery); - - 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)); - - if (BitUtil.check(status, 6)) { - - position.setValid(!BitUtil.check(status, 7)); - position.setTime(dateBuilder.getDate()); - position.setAltitude(buf.readFloatLE()); - position.setLongitude(buf.readFloatLE()); - position.setLatitude(buf.readFloatLE()); - position.setSpeed(UnitsConverter.knotsFromKph(BcdUtil.readInteger(buf, 4) * 0.1)); - position.setCourse(buf.readUnsignedShort()); - - } else { - - getLastLocation(position, dateBuilder.getDate()); - - int mcc = buf.readUnsignedShortLE(); - int mnc = buf.readUnsignedShortLE(); - - if (mcc != 0xffff && mnc != 0xffff) { - Network network = new Network(); - for (int i = 0; i < 3; i++) { - network.addCellTower(CellTower.from( - mcc, mnc, buf.readUnsignedShortLE(), buf.readUnsignedShortLE())); - } - position.setNetwork(network); - } + buf.readUnsignedShort(); // acc on interval + buf.readUnsignedShort(); // acc off interval + buf.readUnsignedByte(); // angle compensation + buf.readUnsignedShort(); // distance compensation - } + position.set(Position.KEY_RSSI, BitUtil.to(buf.readUnsignedShort(), 7)); - if (buf.readableBytes() >= 2) { - position.set(Position.KEY_POWER, BcdUtil.readInteger(buf, 4) * 0.01); - } + int status = buf.readUnsignedByte(); + position.set(Position.KEY_SATELLITES, BitUtil.to(status, 5)); - sendResponse(channel, header, type, index, imei, alarm); + buf.readUnsignedByte(); // gsensor manager status + buf.readUnsignedByte(); // other flags + buf.readUnsignedByte(); // heartbeat + buf.readUnsignedByte(); // relay status + buf.readUnsignedShort(); // drag alarm setting - return position; + int io = buf.readUnsignedShort(); + position.set(Position.KEY_IGNITION, BitUtil.check(io, 14)); + position.set("ac", BitUtil.check(io, 13)); + for (int i = 0; i <= 2; i++) { + position.set(Position.PREFIX_OUT + (i + 1), BitUtil.check(io, 7 + i)); + } + + position.set(Position.PREFIX_ADC + 1, buf.readUnsignedShort()); + position.set(Position.PREFIX_ADC + 2, buf.readUnsignedShort()); + + int alarm = buf.readUnsignedByte(); + position.set(Position.KEY_ALARM, decodeAlarm(alarm)); + + buf.readUnsignedByte(); // reserved + + position.set(Position.KEY_ODOMETER, buf.readUnsignedInt()); + + int battery = BcdUtil.readInteger(buf, 2); + if (battery == 0) { + battery = 100; + } + position.set(Position.KEY_BATTERY, battery); + + 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)); + + if (BitUtil.check(status, 6)) { + + position.setValid(!BitUtil.check(status, 7)); + position.setTime(dateBuilder.getDate()); + position.setAltitude(buf.readFloatLE()); + position.setLongitude(buf.readFloatLE()); + position.setLatitude(buf.readFloatLE()); + position.setSpeed(UnitsConverter.knotsFromKph(BcdUtil.readInteger(buf, 4) * 0.1)); + position.setCourse(buf.readUnsignedShort()); + + } else { + + getLastLocation(position, dateBuilder.getDate()); + + int mcc = buf.readUnsignedShortLE(); + int mnc = buf.readUnsignedShortLE(); + + if (mcc != 0xffff && mnc != 0xffff) { + Network network = new Network(); + for (int i = 0; i < 3; i++) { + network.addCellTower(CellTower.from( + mcc, mnc, buf.readUnsignedShortLE(), buf.readUnsignedShortLE())); + } + position.setNetwork(network); + } } + if (buf.readableBytes() >= 2) { + position.set(Position.KEY_POWER, BcdUtil.readInteger(buf, 4) * 0.01); + } + sendResponse(channel, header, type, index, imei, alarm); - return null; + return position; } } diff --git a/src/main/java/org/traccar/protocol/T800xProtocolEncoder.java b/src/main/java/org/traccar/protocol/T800xProtocolEncoder.java index 1d0f3dabe..34f30b147 100644 --- a/src/main/java/org/traccar/protocol/T800xProtocolEncoder.java +++ b/src/main/java/org/traccar/protocol/T800xProtocolEncoder.java @@ -33,8 +33,8 @@ public class T800xProtocolEncoder extends BaseProtocolEncoder { ByteBuf buf = Unpooled.buffer(); - buf.writeByte('#'); - buf.writeByte('#'); + buf.writeByte('%'); + buf.writeByte('%'); buf.writeByte(T800xProtocolDecoder.MSG_COMMAND); buf.writeShort(7 + 8 + 1 + content.length()); buf.writeShort(1); // serial number diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java index 974d2c106..485df833a 100644 --- a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java @@ -145,8 +145,22 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { } else { position.set(Position.KEY_TYPE, type); - position.set(Position.KEY_RESULT, buf.readSlice(buf.readInt()).toString(StandardCharsets.US_ASCII)); + int length = buf.readInt(); + boolean readable = true; + for (int i = 0; i < length; i++) { + byte b = buf.getByte(buf.readerIndex() + i); + if (b < 32 && b != '\r' && b != '\n') { + readable = false; + break; + } + } + + if (readable) { + position.set(Position.KEY_RESULT, buf.readSlice(length).toString(StandardCharsets.US_ASCII)); + } else { + position.set(Position.KEY_RESULT, ByteBufUtil.hexDump(buf.readSlice(length))); + } } } diff --git a/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java b/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java index de7073b67..42ff3177e 100644 --- a/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/WialonProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 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. @@ -59,9 +59,10 @@ public class WialonProtocolDecoder extends BaseProtocolDecoder { .groupEnd("?") .compile(); - private void sendResponse(Channel channel, SocketAddress remoteAddress, String prefix, Integer number) { + private void sendResponse(Channel channel, SocketAddress remoteAddress, String type, Integer number) { if (channel != null) { - StringBuilder response = new StringBuilder(prefix); + StringBuilder response = new StringBuilder("#A"); + response.append(type.substring(1)); if (number != null) { response.append(number); } @@ -134,60 +135,69 @@ public class WialonProtocolDecoder extends BaseProtocolDecoder { Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { String sentence = (String) msg; + String type = sentence.substring(0, sentence.indexOf('#', 1) + 1); - if (sentence.startsWith("#L#")) { + switch (type) { - String[] values = sentence.substring(3).split(";"); + case "#L#": + String[] values = sentence.substring(3).split(";"); - String imei = values[0].indexOf('.') >= 0 ? values[1] : values[0]; - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); - if (deviceSession != null) { - sendResponse(channel, remoteAddress, "#AL#", 1); - } - - } else if (sentence.startsWith("#P#")) { - - sendResponse(channel, remoteAddress, "#AP#", null); // heartbeat + String imei = values[0].indexOf('.') >= 0 ? values[1] : values[0]; + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, imei); + if (deviceSession != null) { + sendResponse(channel, remoteAddress, type, 1); + } + break; - } else if (sentence.startsWith("#SD#") || sentence.startsWith("#D#")) { + case "#P#": + sendResponse(channel, remoteAddress, type, null); // heartbeat + break; - Position position = decodePosition( - channel, remoteAddress, sentence.substring(sentence.indexOf('#', 1) + 1)); + case "#D#": + case "#SD#": + Position position = decodePosition( + channel, remoteAddress, sentence.substring(sentence.indexOf('#', 1) + 1)); - if (position != null) { - sendResponse(channel, remoteAddress, "#AD#", 1); - return position; - } + if (position != null) { + sendResponse(channel, remoteAddress, type, 1); + return position; + } + break; - } else if (sentence.startsWith("#B#")) { + case "#B#": + String[] messages = sentence.substring(sentence.indexOf('#', 1) + 1).split("\\|"); + List<Position> positions = new LinkedList<>(); - String[] messages = sentence.substring(sentence.indexOf('#', 1) + 1).split("\\|"); - List<Position> positions = new LinkedList<>(); + for (String message : messages) { + position = decodePosition(channel, remoteAddress, message); + if (position != null) { + position.set(Position.KEY_ARCHIVE, true); + positions.add(position); + } + } - for (String message : messages) { - Position position = decodePosition(channel, remoteAddress, message); - if (position != null) { - position.set(Position.KEY_ARCHIVE, true); - positions.add(position); + sendResponse(channel, remoteAddress, type, messages.length); + if (!positions.isEmpty()) { + return positions; } - } + break; + + case "#M#": + deviceSession = getDeviceSession(channel, remoteAddress); + if (deviceSession != null) { + position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, new Date()); + position.setValid(false); + position.set(Position.KEY_RESULT, sentence.substring(sentence.indexOf('#', 1) + 1)); + sendResponse(channel, remoteAddress, type, 1); + return position; + } + break; - sendResponse(channel, remoteAddress, "#AB#", messages.length); - if (!positions.isEmpty()) { - return positions; - } + default: + break; - } else if (sentence.startsWith("#M#")) { - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); - if (deviceSession != null) { - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); - getLastLocation(position, new Date()); - position.setValid(false); - position.set(Position.KEY_RESULT, sentence.substring(sentence.indexOf('#', 1) + 1)); - sendResponse(channel, remoteAddress, "#AM#", 1); - return position; - } } return null; diff --git a/src/main/java/org/traccar/protocol/XirgoProtocolDecoder.java b/src/main/java/org/traccar/protocol/XirgoProtocolDecoder.java index 6d215e672..08809307f 100644 --- a/src/main/java/org/traccar/protocol/XirgoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/XirgoProtocolDecoder.java @@ -16,9 +16,11 @@ package org.traccar.protocol; import io.netty.channel.Channel; +import io.netty.channel.socket.nio.NioDatagramChannel; import org.traccar.BaseProtocolDecoder; import org.traccar.Context; import org.traccar.DeviceSession; +import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; @@ -27,6 +29,7 @@ import org.traccar.helper.UnitsConverter; import org.traccar.model.Position; import java.net.SocketAddress; +import java.util.regex.Matcher; import java.util.regex.Pattern; public class XirgoProtocolDecoder extends BaseProtocolDecoder { @@ -174,6 +177,15 @@ public class XirgoProtocolDecoder extends BaseProtocolDecoder { Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { String sentence = (String) msg; + + if (channel instanceof NioDatagramChannel) { + Matcher matcher = Pattern.compile("\\$\\$\\d+,(\\d+),.*,(\\d+)##").matcher(sentence); + if (matcher.matches()) { + String response = "!UDP_ACK," + matcher.group(1) + "," + matcher.group(2); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + if (form != null) { return decodeCustom(channel, remoteAddress, sentence); } else { @@ -188,10 +200,14 @@ public class XirgoProtocolDecoder extends BaseProtocolDecoder { String[] keys = form.split(","); String[] values = sentence.replace("$$", "").replace("##", "").split(","); + if (values.length < keys.length) { + return null; + } + Position position = new Position(getProtocolName()); DateBuilder dateBuilder = new DateBuilder(); - for (int i = 0; i < Math.min(keys.length, values.length); i++) { + for (int i = 0; i < keys.length; i++) { switch (keys[i]) { case "UID": case "IM": |