diff options
Diffstat (limited to 'src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java')
-rw-r--r-- | src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java | 1388 |
1 files changed, 815 insertions, 573 deletions
diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java index 517499f02..2e5ffa8d6 100644 --- a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2024 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. @@ -15,12 +15,15 @@ */ 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.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; import org.traccar.config.Keys; import org.traccar.helper.BitUtil; +import org.traccar.helper.DataConverter; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; import org.traccar.helper.UnitsConverter; @@ -28,15 +31,14 @@ import org.traccar.model.CellTower; import org.traccar.model.Network; import org.traccar.model.Position; import org.traccar.model.WifiAccessPoint; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; +import org.traccar.session.DeviceSession; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.TimeZone; @@ -47,8 +49,12 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { private boolean ignoreFixTime; + private final DateFormat dateFormat; + public Gl200TextProtocolDecoder(Protocol protocol) { super(protocol); + dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); } @Override @@ -56,21 +62,67 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { ignoreFixTime = getConfig().getBoolean(Keys.PROTOCOL_IGNORE_FIX_TIME.withPrefix(getProtocolName())); } - private static final Pattern PATTERN_ACK = new PatternBuilder() - .text("+ACK:GT") - .expression("...,") // type - .number("([0-9A-Z]{2}xxxx),") // protocol version - .number("(d{15}|x{14}),") // imei - .any().text(",") - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd),") // time (hhmmss) - .number("(xxxx)") // counter - .text("$").optional() - .compile(); + private String getDeviceModel(DeviceSession deviceSession, String value) { + String model = value.isEmpty() ? getDeviceModel(deviceSession) : value; + return model != null ? model.toUpperCase() : ""; + } + + private Position initPosition(Parser parser, Channel channel, SocketAddress remoteAddress) { + if (parser.matches()) { + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession != null) { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + return position; + } + } + return null; + } + + private void decodeDeviceTime(Position position, Parser parser) { + if (parser.hasNext(6)) { + if (ignoreFixTime) { + position.setTime(parser.nextDateTime()); + } else { + position.setDeviceTime(parser.nextDateTime()); + } + } + } + + private Long parseHours(String hoursString) { + if (hoursString != null && !hoursString.isEmpty()) { + String[] hours = hoursString.split(":"); + return (Integer.parseInt(hours[0]) * 3600L + + (hours.length > 1 ? Integer.parseInt(hours[1]) * 60L : 0) + + (hours.length > 2 ? Integer.parseInt(hours[2]) : 0)) * 1000; + } + return null; + } + + private Position decodeAck(Channel channel, SocketAddress remoteAddress, String[] values) throws ParseException { + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[2]); + if (deviceSession == null) { + return null; + } + if (values[0].equals("+ACK:GTHBD")) { + if (channel != null) { + channel.writeAndFlush(new NetworkMessage( + "+SACK:GTHBD," + values[1] + "," + values[values.length - 1] + "$", remoteAddress)); + } + } else { + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + getLastLocation(position, dateFormat.parse(values[values.length - 2])); + position.setValid(false); + position.set(Position.KEY_RESULT, values[0]); + return position; + } + return null; + } private static final Pattern PATTERN_INF = new PatternBuilder() .text("+").expression("(?:RESP|BUFF):GTINF,") - .number("[0-9A-Z]{2}xxxx,") // protocol version + .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("(?:[0-9A-Z]{17},)?") // vin .expression("(?:[^,]+)?,") // device name @@ -109,374 +161,6 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .text("$").optional() .compile(); - private static final Pattern PATTERN_VER = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF):GTVER,") - .number("[0-9A-Z]{2}xxxx,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .expression("([^,]*),") // device type - .number("(xxxx),") // firmware version - .number("(xxxx),") // hardware version - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd),") // time (hhmmss) - .number("(xxxx)") // counter - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_LOCATION = new PatternBuilder() - .number("(d{1,2}.?d?)?,") // hdop - .number("(d{1,3}.d)?,") // speed - .number("(d{1,3}.?d?)?,") // course - .number("(-?d{1,5}.d)?,") // altitude - .number("(-?d{1,3}.d{6})?,") // longitude - .number("(-?d{1,2}.d{6})?,") // latitude - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(d+)?,") // mcc - .number("(d+)?,") // mnc - .groupBegin() - .number("(d+),") // lac - .number("(d+),") // cid - .or() - .number("(x+)?,") // lac - .number("(x+)?,") // cid - .groupEnd() - .number("(?:d+|(d+.d))?,") // rssi / odometer - .compile(); - - private static final Pattern PATTERN_OBD = new PatternBuilder() - .text("+RESP:GTOBD,") - .number("[0-9A-Z]{2}xxxx,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("(?:[0-9A-Z]{17})?,") // vin - .expression("[^,]{0,20},") // device name - .expression("[01],") // report type - .number("x{1,8},") // report mask - .expression("(?:[0-9A-Z]{17})?,") // vin - .number("[01],") // obd connect - .number("(?:d{1,5})?,") // obd voltage - .number("(?:x{8})?,") // support pids - .number("(d{1,5})?,") // engine rpm - .number("(d{1,3})?,") // speed - .number("(-?d{1,3})?,") // coolant temp - .number("(d+.?d*|Inf|NaN)?,") // fuel consumption - .number("(d{1,5})?,") // dtcs cleared distance - .number("(?:d{1,5})?,") - .expression("([01])?,") // obd connect - .number("(d{1,3})?,") // number of dtcs - .number("(x*),") // dtcs - .number("(d{1,3})?,") // throttle - .number("(?:d{1,3})?,") // engine load - .number("(d{1,3})?,") // fuel level - .expression("(?:[0-9A],)?") // obd protocol - .number("(d+),") // odometer - .expression(PATTERN_LOCATION.pattern()) - .number("(d{1,7}.d)?,") // odometer - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_FRI = new PatternBuilder() - .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}),").optional() // report type - .number("d{1,2},").optional() // count - .number("d*,").optional() // reserved - .number("(d+),").optional() // battery - .expression("((?:") - .expression(PATTERN_LOCATION.pattern()) - .expression(")+)") - .groupBegin() - .number("d{1,2},,") - .number("(d{1,3}),") // battery - .number("[01],") // mode - .number("(?:[01])?,") // motion - .number("(?:-?d{1,2}.d)?,") // temperature - .or() - .number("(d{1,7}.d)?,") // odometer - .number("(d{5}:dd:dd)?,") // hour meter - .number("(x+)?,") // adc 1 - .number("(x+)?,") // adc 2 - .number("(d{1,3})?,") // battery - .number("(?:(xx)(xx)(xx))?,") // device status - .number("(d+)?,") // rpm - .number("(?:d+.?d*|Inf|NaN)?,") // fuel consumption - .number("(d+)?,") // fuel level - .or() - .number("(-?d),") // rssi - .number("(d{1,3}),") // battery - .or() - .number("(d{1,7}.d)?,").optional() // odometer - .number("(d{1,3})?,") // battery - .groupEnd() - .any() - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_ERI = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF):GTERI,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("(x{8}),") // mask - .number("(d+)?,") // power - .number("d{1,2},") // report type - .number("d{1,2},") // count - .expression("((?:") - .expression(PATTERN_LOCATION.pattern()) - .expression(")+)") - .groupBegin() - .number("(d{1,7}.d)?,") // odometer - .number("(d{5}:dd:dd)?,") // hour meter - .number("(x+)?,") // adc 1 - .number("(x+)?,").optional() // adc 2 - .groupBegin() - .number("(x+)?,") // adc 3 - .number("(xx),") // inputs - .number("(xx),") // outputs - .or() - .number("(d{1,3})?,") // battery - .number("(?:(xx)(xx)(xx))?,") // device status - .groupEnd() - .expression("(.*)") // additional data - .or() - .number("d*,,") - .number("(d+),") // battery - .any() - .groupEnd() - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_IGN = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF):GTIG[NF],") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("d+,") // ignition off duration - .expression(PATTERN_LOCATION.pattern()) - .number("(d{5}:dd:dd)?,") // hour meter - .number("(d{1,7}.d)?,") // odometer - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_LSW = new PatternBuilder() - .text("+RESP:").expression("GT[LT]SW,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("[01],") // type - .number("([01]),") // state - .expression(PATTERN_LOCATION.pattern()) - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_IDA = new PatternBuilder() - .text("+RESP:GTIDA,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,,") // device name - .number("([^,]+),") // rfid - .expression("[01],") // report type - .number("1,") // count - .expression(PATTERN_LOCATION.pattern()) - .number("(d+.d),") // odometer - .text(",,,,") - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_WIF = new PatternBuilder() - .text("+RESP:GTWIF,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("(d+),") // count - .number("((?:x{12},-?d+,,,,)+),,,,") // wifi - .number("(d{1,3}),") // battery - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_GSM = new PatternBuilder() - .text("+RESP:GTGSM,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("(?:STR|CTN|NMR|RTL),") // fix type - .expression("(.*)") // cells - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_PNA = new PatternBuilder() - .text("+RESP:GT").expression("P[NF]A,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_DAR = new PatternBuilder() - .text("+RESP:GTDAR,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("(d),") // warning type - .number("(d{1,2}),,,") // fatigue degree - .expression(PATTERN_LOCATION.pattern()) - .any() - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF):GT...,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .number("(d{15}|x{14}),") // imei - .expression("[^,]*,") // device name - .number("d*,") - .number("(x{1,2}),") // report type - .number("d{1,2},") // count - .number("d*,").optional() // reserved - .expression(PATTERN_LOCATION.pattern()) - .groupBegin() - .number("(?:(d{1,7}.d)|0)?,").optional() // odometer - .number("(d{1,3})?,") // battery - .or() - .number("(d{1,7}.d)?,") // odometer - .groupEnd() - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)") // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private static final Pattern PATTERN_BASIC = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF)").text(":") - .expression("GT...,") - .number("(?:[0-9A-Z]{2}xxxx)?,").optional() // protocol version - .number("(d{15}|x{14}),") // imei - .any() - .text(",") - .number("(d{1,2})?,") // hdop - .number("(d{1,3}.d)?,") // speed - .number("(d{1,3})?,") // course - .number("(-?d{1,5}.d)?,") // altitude - .number("(-?d{1,3}.d{6})?,") // longitude - .number("(-?d{1,2}.d{6})?,") // latitude - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(d+),") // mcc - .number("(d+),") // mnc - .number("(x+),") // lac - .number("(x+),").optional(4) // cell - .any() - .number("(dddd)(dd)(dd)") // date (yyyymmdd) - .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) - .text(",") - .number("(xxxx)") // count number - .text("$").optional() - .compile(); - - private Object decodeAck(Channel channel, SocketAddress remoteAddress, String sentence, String type) { - Parser parser = new Parser(PATTERN_ACK, sentence); - if (parser.matches()) { - String protocolVersion = parser.next(); - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); - if (deviceSession == null) { - return null; - } - if (type.equals("HBD")) { - if (channel != null) { - parser.skip(6); - channel.writeAndFlush(new NetworkMessage( - "+SACK:GTHBD," + protocolVersion + "," + parser.next() + "$", remoteAddress)); - } - } else { - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); - getLastLocation(position, parser.nextDateTime()); - position.setValid(false); - position.set(Position.KEY_RESULT, "Command " + type + " accepted"); - return position; - } - } - return null; - } - - private Position initPosition(Parser parser, Channel channel, SocketAddress remoteAddress) { - if (parser.matches()) { - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); - if (deviceSession != null) { - Position position = new Position(getProtocolName()); - position.setDeviceId(deviceSession.getDeviceId()); - return position; - } - } - return null; - } - - private void decodeDeviceTime(Position position, Parser parser) { - if (parser.hasNext(6)) { - if (ignoreFixTime) { - position.setTime(parser.nextDateTime()); - } else { - position.setDeviceTime(parser.nextDateTime()); - } - } - } - - private Long parseHours(String hoursString) { - if (hoursString != null) { - String[] hours = hoursString.split(":"); - return (long) (Integer.parseInt(hours[0]) * 3600 - + (hours.length > 1 ? Integer.parseInt(hours[1]) * 60 : 0) - + (hours.length > 2 ? Integer.parseInt(hours[2]) : 0)) * 1000; - } - return null; - } - private Object decodeInf(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_INF, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -537,6 +221,20 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_VER = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GTVER,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .expression("([^,]*),") // device type + .number("(xxxx),") // firmware version + .number("(xxxx),") // hardware version + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(xxxx)") // counter + .text("$").optional() + .compile(); + private Object decodeVer(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_VER, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -554,9 +252,35 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { } private void skipLocation(Parser parser) { - parser.skip(19); + parser.skip(20); } + private static final Pattern PATTERN_LOCATION = new PatternBuilder() + .number("(d{1,2}.?d?)?,") // hdop + .number("(d{1,3}.d)?,") // speed + .number("(d{1,3}.?d?)?,") // course + .number("(-?d{1,5}.d)?,") // altitude + .number("(-?d{1,3}.d{6})?,") // longitude + .number("(-?d{1,2}.d{6})?,") // latitude + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .groupBegin() + .number(",d+") // wifi count + .number("((?:,x{12},-d+,,,)+)") // wifi + .groupEnd("?") + .text(",") + .number("(d+)?,") // mcc + .number("(d+)?,") // mnc + .groupBegin() + .number("(d+),") // lac + .number("(d+),") // cid + .or() + .number("(x+)?,") // lac + .number("(x+)?,") // cid + .groupEnd() + .number("(?:d+|(d+.d))?,") // rssi / odometer + .compile(); + private void decodeLocation(Position position, Parser parser) { Double hdop = parser.nextDouble(); position.setValid(hdop == null || hdop > 0); @@ -575,22 +299,125 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { getLastLocation(position, null); } + Network network = new Network(); + + if (parser.hasNext()) { + String[] values = parser.next().split(","); + for (int i = 0; i < values.length; i += 5) { + String mac = values[i + 1].replaceAll("(..)", "$1:"); + network.addWifiAccessPoint(WifiAccessPoint.from( + mac.substring(0, mac.length() - 1), Integer.parseInt(values[i + 2]))); + } + } + if (parser.hasNext(6)) { int mcc = parser.nextInt(); int mnc = parser.nextInt(); if (parser.hasNext(2)) { - position.setNetwork(new Network(CellTower.from(mcc, mnc, parser.nextInt(), parser.nextInt()))); + network.addCellTower(CellTower.from(mcc, mnc, parser.nextInt(), parser.nextInt())); } if (parser.hasNext(2)) { - position.setNetwork(new Network(CellTower.from(mcc, mnc, parser.nextHexInt(), parser.nextHexInt()))); + network.addCellTower(CellTower.from(mcc, mnc, parser.nextHexInt(), parser.nextHexInt())); } } + if (network.getWifiAccessPoints() != null || network.getCellTowers() != null) { + position.setNetwork(network); + } + if (parser.hasNext()) { position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); } } + private int decodeLocation(Position position, String model, String[] values, int index) throws ParseException { + double hdop = values[index++].isEmpty() ? 0 : Double.parseDouble(values[index - 1]); + position.set(Position.KEY_HDOP, hdop); + + position.setSpeed(UnitsConverter.knotsFromKph( + values[index++].isEmpty() ? 0 : Double.parseDouble(values[index - 1]))); + position.setCourse(values[index++].isEmpty() ? 0 : Integer.parseInt(values[index - 1])); + position.setAltitude(values[index++].isEmpty() ? 0 : Double.parseDouble(values[index - 1])); + + if (!values[index].isEmpty()) { + position.setValid(true); + position.setLongitude(values[index++].isEmpty() ? 0 : Double.parseDouble(values[index - 1])); + position.setLatitude(values[index++].isEmpty() ? 0 : Double.parseDouble(values[index - 1])); + position.setTime(dateFormat.parse(values[index++])); + } else { + index += 3; + getLastLocation(position, null); + } + + Network network = new Network(); + + if (!values[index].isEmpty()) { + network.addCellTower(CellTower.from( + Integer.parseInt(values[index++]), + Integer.parseInt(values[index++]), + Integer.parseInt(values[index++], 16), + Long.parseLong(values[index++], 16))); + } else { + index += 4; + } + + if (network.getWifiAccessPoints() != null || network.getCellTowers() != null) { + position.setNetwork(network); + } + + if (model.startsWith("GL5")) { + index += 1; // csq rssi + index += 1; // csq ber + } + + if (!values[index++].isEmpty()) { + int appendMask = Integer.parseInt(values[index - 1]); + if (BitUtil.check(appendMask, 0)) { + position.set(Position.KEY_SATELLITES, Integer.parseInt(values[index++])); + } + if (BitUtil.check(appendMask, 1)) { + index += 1; // trigger type + } + } + + return index; + } + + private static final Pattern PATTERN_OBD = new PatternBuilder() + .text("+RESP:GTOBD,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:[0-9A-Z]{17})?,") // vin + .expression("[^,]{0,20},") // device name + .expression("[01],") // report type + .number("x{1,8},") // report mask + .expression("(?:[0-9A-Z]{17})?,") // vin + .number("[01],") // obd connect + .number("(?:d{1,5})?,") // obd voltage + .number("(?:x{8})?,") // support pids + .number("(d{1,5})?,") // engine rpm + .number("(d{1,3})?,") // speed + .number("(-?d{1,3})?,") // coolant temp + .number("(d+.?d*|Inf|NaN)?,") // fuel consumption + .number("(d{1,5})?,") // dtcs cleared distance + .number("(?:d{1,5})?,") + .expression("([01])?,") // obd connect + .number("(d{1,3})?,") // number of dtcs + .number("(x*),") // dtcs + .number("(d{1,3})?,") // throttle + .number("(?:d{1,3})?,") // engine load + .number("(d{1,3})?,") // fuel level + .expression("(?:[0-9A],)?") // obd protocol + .number("(d+),") // odometer + .expression(PATTERN_LOCATION.pattern()) + .number("(d{1,7}.d)?,") // odometer + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeObd(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_OBD, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -625,104 +452,140 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } - private Object decodeCan(Channel channel, SocketAddress remoteAddress, String sentence) throws ParseException { - Position position = new Position(getProtocolName()); - + private Object decodeCan(Channel channel, SocketAddress remoteAddress, String[] v) throws ParseException { int index = 0; - String[] values = sentence.split(","); - index += 1; // header index += 1; // protocol version + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, v[index++]); + if (deviceSession == null) { + return null; + } - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, values[index++]); + Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - index += 1; // device name + String model = getDeviceModel(deviceSession, v[index++]); index += 1; // report type - index += 1; // canbus state - long reportMask = Long.parseLong(values[index++], 16); + index += 1; // can bus state + long reportMask = Long.parseLong(v[index++], 16); long reportMaskExt = 0; if (BitUtil.check(reportMask, 0)) { - position.set(Position.KEY_VIN, values[index++]); + position.set(Position.KEY_VIN, v[index++]); } - if (BitUtil.check(reportMask, 1)) { - position.set(Position.KEY_IGNITION, Integer.parseInt(values[index++]) > 0); + if (BitUtil.check(reportMask, 1) && !v[index++].isEmpty()) { + position.set(Position.KEY_IGNITION, Integer.parseInt(v[index - 1]) > 0); } - if (BitUtil.check(reportMask, 2)) { - position.set(Position.KEY_OBD_ODOMETER, values[index++]); + if (BitUtil.check(reportMask, 2) && !v[index++].isEmpty()) { + position.set(Position.KEY_OBD_ODOMETER, Integer.parseInt(v[index - 1].substring(1))); } - if (BitUtil.check(reportMask, 3) && !values[index++].isEmpty()) { - position.set(Position.KEY_FUEL_USED, Double.parseDouble(values[index - 1])); + if (BitUtil.check(reportMask, 3) && !v[index++].isEmpty()) { + position.set(Position.KEY_FUEL_USED, Double.parseDouble(v[index - 1])); } - if (BitUtil.check(reportMask, 5) && !values[index++].isEmpty()) { - position.set(Position.KEY_RPM, Integer.parseInt(values[index - 1])); + if (BitUtil.check(reportMask, 5) && !v[index++].isEmpty()) { + position.set(Position.KEY_RPM, Integer.parseInt(v[index - 1])); } - if (BitUtil.check(reportMask, 4) && !values[index++].isEmpty()) { - position.set(Position.KEY_OBD_SPEED, UnitsConverter.knotsFromKph(Integer.parseInt(values[index - 1]))); + if (BitUtil.check(reportMask, 4) && !v[index++].isEmpty()) { + position.set(Position.KEY_OBD_SPEED, Integer.parseInt(v[index - 1])); } - if (BitUtil.check(reportMask, 6) && !values[index++].isEmpty()) { - position.set(Position.KEY_COOLANT_TEMP, Integer.parseInt(values[index - 1])); + if (BitUtil.check(reportMask, 6) && !v[index++].isEmpty()) { + position.set(Position.KEY_COOLANT_TEMP, Integer.parseInt(v[index - 1])); } - if (BitUtil.check(reportMask, 7) && !values[index++].isEmpty()) { - position.set(Position.KEY_FUEL_CONSUMPTION, Double.parseDouble(values[index - 1].substring(1))); + if (BitUtil.check(reportMask, 7) && !v[index++].isEmpty()) { + String value = v[index - 1]; + if (value.startsWith("L/H")) { + position.set(Position.KEY_FUEL_CONSUMPTION, Double.parseDouble(value.substring(3))); + } } - if (BitUtil.check(reportMask, 8) && !values[index++].isEmpty()) { - position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(values[index - 1].substring(1))); + if (BitUtil.check(reportMask, 8) && !v[index++].isEmpty()) { + position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(v[index - 1].substring(1))); } - if (BitUtil.check(reportMask, 9) && !values[index++].isEmpty()) { - position.set("range", Long.parseLong(values[index - 1]) * 100); + if (BitUtil.check(reportMask, 9) && !v[index++].isEmpty()) { + position.set("range", Long.parseLong(v[index - 1]) * 100); } - if (BitUtil.check(reportMask, 10) && !values[index++].isEmpty()) { - position.set(Position.KEY_THROTTLE, Integer.parseInt(values[index - 1])); + if (BitUtil.check(reportMask, 10) && !v[index++].isEmpty()) { + position.set(Position.KEY_THROTTLE, Integer.parseInt(v[index - 1])); } - if (BitUtil.check(reportMask, 11) && !values[index++].isEmpty()) { - position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(Double.parseDouble(values[index - 1]))); + if (BitUtil.check(reportMask, 11) && !v[index++].isEmpty()) { + position.set(Position.KEY_HOURS, UnitsConverter.msFromHours(Double.parseDouble(v[index - 1]))); } - if (BitUtil.check(reportMask, 12)) { - position.set("drivingHours", Double.parseDouble(values[index++])); + if (BitUtil.check(reportMask, 12) && !v[index++].isEmpty()) { + position.set(Position.KEY_DRIVING_TIME, Double.parseDouble(v[index - 1])); } - if (BitUtil.check(reportMask, 13)) { - position.set("idleHours", Double.parseDouble(values[index++])); + if (BitUtil.check(reportMask, 13) && !v[index++].isEmpty()) { + position.set("idleHours", Double.parseDouble(v[index - 1])); } - if (BitUtil.check(reportMask, 14) && !values[index++].isEmpty()) { - position.set("idleFuelConsumption", Double.parseDouble(values[index - 1])); + if (BitUtil.check(reportMask, 14) && !v[index++].isEmpty()) { + position.set("idleFuelConsumption", Double.parseDouble(v[index - 1])); } - if (BitUtil.check(reportMask, 15) && !values[index++].isEmpty()) { - position.set(Position.KEY_AXLE_WEIGHT, Integer.parseInt(values[index - 1])); + if (BitUtil.check(reportMask, 15) && !v[index++].isEmpty()) { + position.set(Position.KEY_AXLE_WEIGHT, Integer.parseInt(v[index - 1])); } - if (BitUtil.check(reportMask, 16) && !values[index++].isEmpty()) { - position.set("tachographInfo", Integer.parseInt(values[index - 1], 16)); + if (BitUtil.check(reportMask, 16) && !v[index++].isEmpty()) { + position.set("tachographInfo", Integer.parseInt(v[index - 1], 16)); } - if (BitUtil.check(reportMask, 17) && !values[index++].isEmpty()) { - position.set("indicators", Integer.parseInt(values[index - 1], 16)); + if (BitUtil.check(reportMask, 17) && !v[index++].isEmpty()) { + position.set("indicators", Integer.parseInt(v[index - 1], 16)); } - if (BitUtil.check(reportMask, 18) && !values[index++].isEmpty()) { - position.set("lights", Integer.parseInt(values[index - 1], 16)); + if (BitUtil.check(reportMask, 18) && !v[index++].isEmpty()) { + position.set("lights", Integer.parseInt(v[index - 1], 16)); } - if (BitUtil.check(reportMask, 19) && !values[index++].isEmpty()) { - position.set("doors", Integer.parseInt(values[index - 1], 16)); + if (BitUtil.check(reportMask, 19) && !v[index++].isEmpty()) { + position.set("doors", Integer.parseInt(v[index - 1], 16)); } - if (BitUtil.check(reportMask, 20) && !values[index++].isEmpty()) { - position.set("vehicleOverspeed", Double.parseDouble(values[index - 1])); + if (BitUtil.check(reportMask, 20) && !v[index++].isEmpty()) { + position.set("vehicleOverspeed", Double.parseDouble(v[index - 1])); } - if (BitUtil.check(reportMask, 21) && !values[index++].isEmpty()) { - position.set("engineOverspeed", Double.parseDouble(values[index - 1])); + if (BitUtil.check(reportMask, 21) && !v[index++].isEmpty()) { + position.set("engineOverspeed", Double.parseDouble(v[index - 1])); } - if (BitUtil.check(reportMask, 29)) { - reportMaskExt = Long.parseLong(values[index++], 16); + if ("GV350M".equals(model)) { + if (BitUtil.check(reportMask, 22)) { + index += 1; // impulse distance + } + if (BitUtil.check(reportMask, 23)) { + index += 1; // gross vehicle weight + } + if (BitUtil.check(reportMask, 24)) { + index += 1; // catalyst liquid level + } + } else if ("GV355CEU".equals(model)) { + if (BitUtil.check(reportMask, 22)) { + index += 1; // impulse distance + } + if (BitUtil.check(reportMask, 23)) { + index += 1; // engine cold starts + } + if (BitUtil.check(reportMask, 24)) { + index += 1; // engine all starts + } + if (BitUtil.check(reportMask, 25)) { + index += 1; // engine starts by ignition + } + if (BitUtil.check(reportMask, 26)) { + index += 1; // total engine cold running time + } + if (BitUtil.check(reportMask, 27)) { + index += 1; // handbrake applies during ride + } + if (BitUtil.check(reportMask, 28)) { + index += 1; // electric report mask + } + } + if (BitUtil.check(reportMask, 29) && !v[index++].isEmpty()) { + reportMaskExt = Long.parseLong(v[index - 1], 16); } - if (BitUtil.check(reportMaskExt, 0) && !values[index++].isEmpty()) { - position.set("adBlueLevel", Integer.parseInt(values[index - 1])); + if (BitUtil.check(reportMaskExt, 0) && !v[index++].isEmpty()) { + position.set("adBlueLevel", Integer.parseInt(v[index - 1])); } - if (BitUtil.check(reportMaskExt, 1) && !values[index++].isEmpty()) { - position.set("axleWeight1", Integer.parseInt(values[index - 1])); + if (BitUtil.check(reportMaskExt, 1) && !v[index++].isEmpty()) { + position.set("axleWeight1", Integer.parseInt(v[index - 1])); } - if (BitUtil.check(reportMaskExt, 2) && !values[index++].isEmpty()) { - position.set("axleWeight3", Integer.parseInt(values[index - 1])); + if (BitUtil.check(reportMaskExt, 2) && !v[index++].isEmpty()) { + position.set("axleWeight3", Integer.parseInt(v[index - 1])); } - if (BitUtil.check(reportMaskExt, 3) && !values[index++].isEmpty()) { - position.set("axleWeight4", Integer.parseInt(values[index - 1])); + if (BitUtil.check(reportMaskExt, 3) && !v[index++].isEmpty()) { + position.set("axleWeight4", Integer.parseInt(v[index - 1])); } if (BitUtil.check(reportMaskExt, 4)) { index += 1; // tachograph overspeed @@ -733,8 +596,8 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { if (BitUtil.check(reportMaskExt, 6)) { index += 1; // tachograph direction } - if (BitUtil.check(reportMaskExt, 7) && !values[index++].isEmpty()) { - position.set(Position.PREFIX_ADC + 1, Integer.parseInt(values[index - 1]) * 0.001); + if (BitUtil.check(reportMaskExt, 7) && !v[index++].isEmpty()) { + position.set(Position.PREFIX_ADC + 1, Integer.parseInt(v[index - 1]) * 0.001); } if (BitUtil.check(reportMaskExt, 8)) { index += 1; // pedal breaking factor @@ -757,20 +620,20 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { if (BitUtil.check(reportMaskExt, 14)) { index += 1; // total brake application } - if (BitUtil.check(reportMaskExt, 15) && !values[index++].isEmpty()) { - position.set("driver1Card", values[index - 1]); + if (BitUtil.check(reportMaskExt, 15) && !v[index++].isEmpty()) { + position.set("driver1Card", v[index - 1]); } - if (BitUtil.check(reportMaskExt, 16) && !values[index++].isEmpty()) { - position.set("driver2Card", values[index - 1]); + if (BitUtil.check(reportMaskExt, 16) && !v[index++].isEmpty()) { + position.set("driver2Card", v[index - 1]); } - if (BitUtil.check(reportMaskExt, 17) && !values[index++].isEmpty()) { - position.set("driver1Name", values[index - 1]); + if (BitUtil.check(reportMaskExt, 17) && !v[index++].isEmpty()) { + position.set("driver1Name", v[index - 1]); } - if (BitUtil.check(reportMaskExt, 18) && !values[index++].isEmpty()) { - position.set("driver2Name", values[index - 1]); + if (BitUtil.check(reportMaskExt, 18) && !v[index++].isEmpty()) { + position.set("driver2Name", v[index - 1]); } - if (BitUtil.check(reportMaskExt, 19) && !values[index++].isEmpty()) { - position.set("registration", values[index - 1]); + if (BitUtil.check(reportMaskExt, 19) && !v[index++].isEmpty()) { + position.set("registration", v[index - 1]); } if (BitUtil.check(reportMaskExt, 20)) { index += 1; // expansion information @@ -788,18 +651,18 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - if (BitUtil.check(reportMask, 30)) { - while (values[index].isEmpty()) { + if (!"GV355CEU".equals(model) && BitUtil.check(reportMask, 30)) { + while (v[index].isEmpty()) { index += 1; } - position.setValid(Integer.parseInt(values[index++]) > 0); - if (!values[index].isEmpty()) { - position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(values[index++]))); - position.setCourse(Integer.parseInt(values[index++])); - position.setAltitude(Double.parseDouble(values[index++])); - position.setLongitude(Double.parseDouble(values[index++])); - position.setLatitude(Double.parseDouble(values[index++])); - position.setTime(dateFormat.parse(values[index++])); + position.setValid(Integer.parseInt(v[index++]) > 0); + if (!v[index].isEmpty()) { + position.setSpeed(UnitsConverter.knotsFromKph(Double.parseDouble(v[index++]))); + position.setCourse(Integer.parseInt(v[index++])); + position.setAltitude(Double.parseDouble(v[index++])); + position.setLongitude(Double.parseDouble(v[index++])); + position.setLatitude(Double.parseDouble(v[index++])); + position.setTime(dateFormat.parse(v[index++])); } else { index += 6; // no location getLastLocation(position, null); @@ -813,34 +676,79 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { index += 1; // reserved } + index = v.length - 2; if (ignoreFixTime) { - position.setTime(dateFormat.parse(values[index])); + position.setTime(dateFormat.parse(v[index])); } else { - position.setDeviceTime(dateFormat.parse(values[index])); + position.setDeviceTime(dateFormat.parse(v[index])); } return position; } - private void decodeStatus(Position position, Parser parser) { - if (parser.hasNext(3)) { - int ignition = parser.nextHexInt(); - if (BitUtil.check(ignition, 4)) { - position.set(Position.KEY_IGNITION, false); - } else if (BitUtil.check(ignition, 5)) { - position.set(Position.KEY_IGNITION, true); - } - int input = parser.nextHexInt(); - int output = parser.nextHexInt(); - position.set(Position.KEY_INPUT, input); - position.set(Position.PREFIX_IN + 1, BitUtil.check(input, 1)); - position.set(Position.PREFIX_IN + 2, BitUtil.check(input, 2)); - position.set(Position.KEY_OUTPUT, output); - position.set(Position.PREFIX_OUT + 1, BitUtil.check(output, 0)); - position.set(Position.PREFIX_OUT + 2, BitUtil.check(output, 1)); - } + private void decodeStatus(Position position, long value) { + long ignition = BitUtil.between(value, 2 * 8, 3 * 8); + if (BitUtil.check(ignition, 4)) { + position.set(Position.KEY_IGNITION, false); + } else if (BitUtil.check(ignition, 5)) { + position.set(Position.KEY_IGNITION, true); + } + long input = BitUtil.between(value, 8, 2 * 8); + long output = BitUtil.to(value, 8); + position.set(Position.KEY_INPUT, input); + position.set(Position.PREFIX_IN + 1, BitUtil.check(input, 1)); + position.set(Position.PREFIX_IN + 2, BitUtil.check(input, 2)); + position.set(Position.KEY_OUTPUT, output); + position.set(Position.PREFIX_OUT + 1, BitUtil.check(output, 0)); + position.set(Position.PREFIX_OUT + 2, BitUtil.check(output, 1)); } + private static final Pattern PATTERN_FRI = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GT...,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:([0-9A-Z]{17}),)?") // vin + .expression("[^,]*,") // device name + .number("(d+)?,") // power + .number("(d{1,2}),").optional() // report type + .number("d{1,2},").optional() // count + .number("d*,").optional() // reserved + .number("(d+),").optional() // battery + .expression("((?:") + .expression(PATTERN_LOCATION.pattern()) + .expression(")+)") + .groupBegin() + .number("d{1,2},") + .number("(d{1,5})?,") // battery + .number("(d{1,3}),") // battery level + .number("[01],") // mode + .number("(?:[01])?,") // motion + .number("(-?d{1,2}.d)?,") // temperature + .or() + .number("(d{1,7}.d)?,") // odometer + .number("(d{5}:dd:dd)?,") // hour meter + .number("(x+)?,") // adc 1 + .number("(x+)?,") // adc 2 + .number("(d{1,3})?,") // battery + .number("(x{6})?,") // device status + .number("(d+)?,") // rpm + .number("(?:d+.?d*|Inf|NaN)?,") // fuel consumption + .number("(d+)?,") // fuel level + .or() + .number("(-?d),") // rssi + .number("(d{1,3}),") // battery + .or() + .number("(d{1,7}.d)?,").optional() // odometer + .number("(d{1,3})?,") // battery + .groupEnd() + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeFri(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_FRI, sentence); if (!parser.matches()) { @@ -883,8 +791,10 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { } if (parser.hasNext()) { - position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + position.set(Position.KEY_BATTERY, parser.nextInt() * 0.001); } + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, parser.nextDouble()); if (parser.hasNext()) { position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); @@ -894,7 +804,9 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { position.set(Position.PREFIX_ADC + 2, parser.next()); position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); - decodeStatus(position, parser); + if (parser.hasNext()) { + decodeStatus(position, parser.nextHexLong()); + } position.set(Position.KEY_RPM, parser.nextInt()); position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); @@ -921,109 +833,112 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return positions; } - private Object decodeEri(Channel channel, SocketAddress remoteAddress, String sentence) { - Parser parser = new Parser(PATTERN_ERI, sentence); - if (!parser.matches()) { - return null; - } - - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + private Object decodeEri(Channel channel, SocketAddress remoteAddress, String[] v) throws ParseException { + int index = 0; + index += 1; // header + index += 1; // protocol version + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, v[index++]); if (deviceSession == null) { return null; } - long mask = parser.nextHexLong(); + String model = getDeviceModel(deviceSession, v[index++]); + long mask = Long.parseLong(v[index++], 16); + Double power = v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]) * 0.001; + index += 1; // report type + int count = Integer.parseInt(v[index++]); LinkedList<Position> positions = new LinkedList<>(); - - Integer power = parser.nextInt(); - - Parser itemParser = new Parser(PATTERN_LOCATION, parser.next()); - while (itemParser.find()) { + for (int i = 0; i < count; i++) { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - - decodeLocation(position, itemParser); - + index = decodeLocation(position, model, v, index); positions.add(position); } Position position = positions.getLast(); + position.set(Position.KEY_POWER, power); - skipLocation(parser); - - if (power != null) { - position.set(Position.KEY_POWER, power * 0.001); + if (!model.startsWith("GL5")) { + position.set(Position.KEY_ODOMETER, v[index++].isEmpty() ? null : Double.parseDouble(v[index - 1]) * 1000); + position.set(Position.KEY_HOURS, parseHours(v[index++])); + position.set(Position.PREFIX_ADC + 1, v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]) * 0.001); + } + if (model.startsWith("GV") && !model.startsWith("GV6")) { + position.set(Position.PREFIX_ADC + 2, v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]) * 0.001); } - if (parser.hasNext(12)) { - - position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); - position.set(Position.KEY_HOURS, parseHours(parser.next())); - position.set(Position.PREFIX_ADC + 1, parser.next()); - position.set(Position.PREFIX_ADC + 2, parser.next()); - position.set(Position.PREFIX_ADC + 3, parser.next()); - if (parser.hasNext(2)) { - position.set(Position.KEY_INPUT, parser.nextHexInt()); - position.set(Position.KEY_OUTPUT, parser.nextHexInt()); - } - if (parser.hasNext(4)) { - position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); - decodeStatus(position, parser); + position.set(Position.KEY_BATTERY_LEVEL, v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1])); + if (model.startsWith("GL5")) { + index += 1; // mode selection + position.set(Position.KEY_MOTION, v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]) > 0); + } else { + if (!v[index++].isEmpty()) { + decodeStatus(position, Long.parseLong(v[index - 1])); } + index += 1; // reserved / uart device type + } - int index = 0; - String[] data = parser.next().split(","); - - index += 1; // device type - - if (BitUtil.check(mask, 0)) { - index += 1; // digital fuel sensor data - } + if (BitUtil.check(mask, 0)) { + position.set(Position.KEY_FUEL_LEVEL, Integer.parseInt(v[index++], 16)); + } - if (BitUtil.check(mask, 1)) { - int deviceCount = Integer.parseInt(data[index++]); - for (int i = 1; i <= deviceCount; i++) { - index += 1; // id - index += 1; // type - if (!data[index++].isEmpty()) { - position.set(Position.PREFIX_TEMP + i, (short) Integer.parseInt(data[index - 1], 16) * 0.0625); - } + if (BitUtil.check(mask, 1)) { + int deviceCount = Integer.parseInt(v[index++]); + for (int i = 1; i <= deviceCount; i++) { + index += 1; // id + index += 1; // type + if (!v[index++].isEmpty()) { + position.set(Position.PREFIX_TEMP + i, (short) Integer.parseInt(v[index - 1], 16) * 0.0625); } } + } - if (BitUtil.check(mask, 2)) { - index += 1; // can data - } + if (BitUtil.check(mask, 2)) { + index += 1; // can data + } - if (BitUtil.check(mask, 3) || BitUtil.check(mask, 4)) { - int deviceCount = Integer.parseInt(data[index++]); - for (int i = 1; i <= deviceCount; i++) { - index += 1; // type - if (BitUtil.check(mask, 3)) { - position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(data[index++])); - } - if (BitUtil.check(mask, 4)) { - index += 1; // volume - } + if (BitUtil.check(mask, 3) || BitUtil.check(mask, 4)) { + int deviceCount = Integer.parseInt(v[index++]); + for (int i = 1; i <= deviceCount; i++) { + index += 1; // type + if (BitUtil.check(mask, 3)) { + position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(v[index++])); + } + if (BitUtil.check(mask, 4)) { + index += 1; // volume } } - } - if (parser.hasNext()) { - position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); - } - - decodeDeviceTime(position, parser); + Date time = dateFormat.parse(v[v.length - 2]); if (ignoreFixTime) { + position.setTime(time); positions.clear(); positions.add(position); + } else { + position.setDeviceTime(time); } return positions; } + private static final Pattern PATTERN_IGN = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GTIG[NF],") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("d+,") // ignition off duration + .expression(PATTERN_LOCATION.pattern()) + .number("(d{5}:dd:dd)?,") // hour meter + .number("(d{1,7}.d)?,") // odometer + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeIgn(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_IGN, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1042,6 +957,21 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_LSW = new PatternBuilder() + .text("+RESP:").expression("GT[LT]SW,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("[01],") // type + .number("([01]),") // state + .expression(PATTERN_LOCATION.pattern()) + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeLsw(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_LSW, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1058,6 +988,24 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_IDA = new PatternBuilder() + .text("+RESP:GTIDA,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,,") // device name + .number("([^,]+),") // rfid + .expression("[01],") // report type + .number("1,") // count + .expression(PATTERN_LOCATION.pattern()) + .number("(d+.d),") // odometer + .text(",,,,") + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeIda(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_IDA, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1076,6 +1024,21 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_WIF = new PatternBuilder() + .text("+RESP:GTWIF,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("(d+),") // count + .number("((?:x{12},-?d+,,,,)+),,,,") // wifi + .number("(d{1,3}),") // battery + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeWif(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_WIF, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1102,6 +1065,19 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_GSM = new PatternBuilder() + .text("+RESP:GTGSM,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:STR|CTN|NMR|RTL),") // fix type + .expression("(.*)") // cells + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeGsm(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_GSM, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1128,6 +1104,18 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_PNA = new PatternBuilder() + .text("+RESP:GT").expression("P[NF]A,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodePna(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_PNA, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1142,6 +1130,22 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_DAR = new PatternBuilder() + .text("+RESP:GTDAR,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("(d),") // warning type + .number("(d{1,2}),,,") // fatigue degree + .expression(PATTERN_LOCATION.pattern()) + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeDar(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_DAR, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1165,6 +1169,204 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_DTT = new PatternBuilder() + .text("+RESP:GTDTT,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,,,") // device name + .number("d,") // data type + .number("d+,") // data length + .number("(x+),") // data + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private Object decodeDtt(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_DTT, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + getLastLocation(position, null); + + String data = Unpooled.wrappedBuffer(DataConverter.parseHex(parser.next())) + .toString(StandardCharsets.US_ASCII); + if (data.contains("COMB")) { + position.set(Position.KEY_FUEL_LEVEL, Double.parseDouble(data.split(",")[2])); + } else { + position.set(Position.KEY_RESULT, data); + } + + decodeDeviceTime(position, parser); + + return position; + } + + private static final Pattern PATTERN_BAA = new PatternBuilder() + .text("+RESP:GTBAA,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("x+,") // index + .number("d,") // accessory type + .number("d,") // accessory model + .number("x+,") // alarm type + .number("(x{4}),") // append mask + .expression("((?:[^,]+,){0,6})") // accessory optionals + .expression(PATTERN_LOCATION.pattern()) + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private Object decodeBaa(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_BAA, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + int mask = parser.nextHexInt(); + String[] values = parser.next().split(","); + int index = 0; + if (BitUtil.check(mask, 0)) { + position.set("accessoryName", values[index++]); + } + if (BitUtil.check(mask, 1)) { + position.set("accessoryMac", values[index++]); + } + if (BitUtil.check(mask, 2)) { + position.set("accessoryStatus", Integer.parseInt(values[index++])); + } + if (BitUtil.check(mask, 3)) { + position.set("accessoryVoltage", Integer.parseInt(values[index++]) * 0.001); + } + if (BitUtil.check(mask, 4)) { + position.set("accessoryTemp", Integer.parseInt(values[index++])); + } + if (BitUtil.check(mask, 5)) { + position.set("accessoryHumidity", Integer.parseInt(values[index])); + } + + decodeLocation(position, parser); + + decodeDeviceTime(position, parser); + + return position; + } + + private static final Pattern PATTERN_BID = new PatternBuilder() + .text("+RESP:GTBID,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("d,") // count + .number("d,") // accessory model + .number("(x{4}),") // append mask + .expression("((?:[^,]+,){0,2})") // accessory optionals + .expression(PATTERN_LOCATION.pattern()) + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private Object decodeBid(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_BID, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + int mask = parser.nextHexInt(); + String[] values = parser.next().split(","); + int index = 0; + if (BitUtil.check(mask, 1)) { + position.set("accessoryMac", values[index++]); + } + if (BitUtil.check(mask, 3)) { + position.set("accessoryVoltage", Integer.parseInt(values[index]) * 0.001); + } + + decodeLocation(position, parser); + + decodeDeviceTime(position, parser); + + return position; + } + + private static final Pattern PATTERN_LSA = new PatternBuilder() + .text("+RESP:GTLSA,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("d,") // event state 1 + .number("d,") // event state 2 + .number("d+,") // number + .expression(PATTERN_LOCATION.pattern()) + .number("d+,") // bit error rate + .number("(d),") // light level + .number("(d+),") // battery level + .number("[01],") // mode selection + .number("[01]?,") // movement status + .number("(-?d+.d)?,") // temperature + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private Object decodeLsa(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_LSA, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + decodeLocation(position, parser); + + position.set("lightLevel", parser.nextInt()); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, parser.nextDouble()); + + decodeDeviceTime(position, parser); + + return position; + } + + private static final Pattern PATTERN = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GT...,") + .expression("(?:.{6}|.{10})?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("d*,") + .number("(x{1,2}),") // report type + .number("d{1,2},") // count + .number("d*,").optional() // reserved + .expression(PATTERN_LOCATION.pattern()) + .groupBegin() + .number("(?:(d{1,7}.d)|0)?,").optional() // odometer + .number("(d{1,3})?,") // battery + .or() + .number("(d{1,7}.d)?,") // odometer + .groupEnd() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)") // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeOther(Channel channel, SocketAddress remoteAddress, String sentence, String type) { Parser parser = new Parser(PATTERN, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1215,6 +1417,38 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { return position; } + private static final Pattern PATTERN_BASIC = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF)").text(":") + .expression("GT...,") + .expression("[^,]+,").optional() // protocol version + .number("(d{15}|x{14}),") // imei + .any() + .text(",") + .number("(d{1,2}),") // hdop + .groupBegin() + .number("(d{1,3}.d),") // speed + .number("(d{1,3}),") // course + .number("(-?d{1,5}.d),") // altitude + .number("(-?d{1,3}.d{6}),") // longitude + .number("(-?d{1,2}.d{6}),") // latitude + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)") // time (hhmmss) + .text(",") + .or() + .text(",,,,,,") + .groupEnd() + .number("(d+),") // mcc + .number("(d+),") // mnc + .number("(x+),") // lac + .number("(x+),").optional(4) // cell + .any() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd)").optional(2) // time (hhmmss) + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + private Object decodeBasic(Channel channel, SocketAddress remoteAddress, String sentence, String type) { Parser parser = new Parser(PATTERN_BASIC, sentence); Position position = initPosition(parser, channel, remoteAddress); @@ -1299,17 +1533,19 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { - String sentence = ((ByteBuf) msg).toString(StandardCharsets.US_ASCII); + String sentence = ((ByteBuf) msg).toString(StandardCharsets.US_ASCII).replaceAll("\\$$", ""); int typeIndex = sentence.indexOf(":GT"); if (typeIndex < 0) { return null; } + String[] values = sentence.split(","); + Object result; String type = sentence.substring(typeIndex + 3, typeIndex + 6); if (sentence.startsWith("+ACK")) { - result = decodeAck(channel, remoteAddress, sentence, type); + result = decodeAck(channel, remoteAddress, values); } else { switch (type) { case "INF": @@ -1319,7 +1555,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { result = decodeObd(channel, remoteAddress, sentence); break; case "CAN": - result = decodeCan(channel, remoteAddress, sentence); + result = decodeCan(channel, remoteAddress, values); break; case "CTN": case "FRI": @@ -1330,7 +1566,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { result = decodeFri(channel, remoteAddress, sentence); break; case "ERI": - result = decodeEri(channel, remoteAddress, sentence); + result = decodeEri(channel, remoteAddress, values); break; case "IGN": case "IGF": @@ -1359,6 +1595,18 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { case "DAR": result = decodeDar(channel, remoteAddress, sentence); break; + case "DTT": + result = decodeDtt(channel, remoteAddress, sentence); + break; + case "BAA": + result = decodeBaa(channel, remoteAddress, sentence); + break; + case "BID": + result = decodeBid(channel, remoteAddress, sentence); + break; + case "LSA": + result = decodeLsa(channel, remoteAddress, sentence); + break; default: result = decodeOther(channel, remoteAddress, sentence, type); break; @@ -1380,13 +1628,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { } if (channel != null && getConfig().getBoolean(Keys.PROTOCOL_ACK.withPrefix(getProtocolName()))) { - String checksum; - if (sentence.endsWith("$")) { - checksum = sentence.substring(sentence.length() - 1 - 4, sentence.length() - 1); - } else { - checksum = sentence.substring(sentence.length() - 4); - } - channel.writeAndFlush(new NetworkMessage("+SACK:" + checksum + "$", remoteAddress)); + channel.writeAndFlush(new NetworkMessage("+SACK:" + values[values.length - 1] + "$", remoteAddress)); } return result; |