From 8bb68423f9a1e3c30f900e89f2016bfcc26a324b Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 15 Jul 2017 13:29:32 +1200 Subject: Rename GL200 text protocol decoder --- .../traccar/protocol/Gl200TextProtocolDecoder.java | 880 +++++++++++++++++++++ 1 file changed, 880 insertions(+) create mode 100644 src/org/traccar/protocol/Gl200TextProtocolDecoder.java (limited to 'src/org/traccar/protocol/Gl200TextProtocolDecoder.java') diff --git a/src/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/org/traccar/protocol/Gl200TextProtocolDecoder.java new file mode 100644 index 000000000..57fecc3fe --- /dev/null +++ b/src/org/traccar/protocol/Gl200TextProtocolDecoder.java @@ -0,0 +1,880 @@ +/* + * Copyright 2012 - 2017 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 org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; +import org.traccar.DeviceSession; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.CellTower; +import org.traccar.model.Network; +import org.traccar.model.Position; +import org.traccar.model.WifiAccessPoint; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { + + private boolean ignoreFixTime; + + public Gl200TextProtocolDecoder(Gl200Protocol protocol) { + super(protocol); + + ignoreFixTime = Context.getConfig().getBoolean(getProtocolName() + ".ignoreFixTime"); + } + + 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 static final Pattern PATTERN_INF = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GTINF,") + .number("[0-9A-Z]{2}xxxx,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:[0-9A-Z]{17},)?") // vin + .expression("(?:[^,]+)?,") // device name + .number("(xx),") // state + .expression("(?:[0-9F]{20})?,") // iccid + .number("d{1,2},") + .number("d{1,2},") + .expression("[01],") // external power + .number("([d.]+)?,") // odometer or external power + .number("d*,") // backup battery or lightness + .number("(d+.d+),") // battery + .expression("([01]),") // charging + .number("(?:d),") // led + .number("(?:d)?,") // gps on need + .number("(?:d)?,") // gps antenna type + .number("(?:d),").optional() // gps antenna state + .number("d{14},") // last fix time + .groupBegin() + .number("(d+),") // battery percentage + .expression("[01]?,") // flash type + .number("(-?[d.]+)?,,,") // temperature + .or() + .expression("(?:[01])?,").optional() // pin15 mode + .number("(d+)?,") // adc1 + .number("(d+)?,").optional() // adc2 + .number("(xx)?,") // digital input + .number("(xx)?,") // digital output + .number("[-+]dddd,") // timezone + .expression("[01],") // daylight saving + .groupEnd() + .number("(dddd)(dd)(dd)") // date (yyyymmdd) + .number("(dd)(dd)(dd),") // time (hhmmss) + .number("(xxxx)") // counter + .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})?,") // 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 + .groupBegin() + .number("(d+),") // lac + .number("(d+),") // cid + .or() + .number("(x+)?,") // lac + .number("(x+)?,") // cid + .groupEnd() + .number("(?:d+|(d+.d))?,") // 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):GTFRI,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("(?:([0-9A-Z]{17}),)?") // vin + .expression("[^,]*,") // device name + .number("(d+)?,") // power + .number("d{1,2},") // report type + .number("d{1,2},") // count + .expression("((?:") + .expression(PATTERN_LOCATION.pattern()) + .expression(")+)") + .groupBegin() + .number("(d{1,7}.d)?,").optional() // odometer + .number("(d{1,3})?,") // battery + .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 + .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_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(")+)") + .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 + .expression("(.*)") // additional data + .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_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 = 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("(d{1,2}),") // report type + .number("d{1,2},") // count + .expression(PATTERN_LOCATION.pattern()) + .groupBegin() + .number("(d{1,7}.d)?,").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() + .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.write("+SACK:GTHBD," + protocolVersion + "," + parser.next() + "$", remoteAddress); + } + } else { + Position position = new Position(); + position.setProtocol(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(); + position.setProtocol(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 Object decodeInf(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_INF, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + position.set(Position.KEY_STATUS, parser.next()); + + parser.next(); // odometer or external power + + position.set(Position.KEY_BATTERY, parser.nextDouble(0)); + position.set(Position.KEY_CHARGE, parser.nextInt(0) == 1); + + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + + position.set(Position.PREFIX_TEMP + 1, parser.next()); + + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.PREFIX_ADC + 2, parser.next()); + + position.set(Position.KEY_INPUT, parser.next()); + position.set(Position.KEY_OUTPUT, parser.next()); + + getLastLocation(position, parser.nextDateTime()); + + position.set(Position.KEY_INDEX, parser.nextHexInt(0)); + + return position; + } + + private Object decodeVer(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_VER, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + position.set("deviceType", parser.next()); + position.set(Position.KEY_VERSION_FW, parser.nextHexInt(0)); + position.set(Position.KEY_VERSION_HW, parser.nextHexInt(0)); + + getLastLocation(position, parser.nextDateTime()); + + return position; + } + + private void decodeLocation(Position position, Parser parser) { + int hdop = parser.nextInt(0); + position.setValid(hdop > 0); + position.set(Position.KEY_HDOP, hdop); + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + if (parser.hasNext(8)) { + position.setValid(true); + position.setLongitude(parser.nextDouble(0)); + position.setLatitude(parser.nextDouble(0)); + position.setTime(parser.nextDateTime()); + } else { + getLastLocation(position, null); + } + + if (parser.hasNext(6)) { + int mcc = parser.nextInt(0); + int mnc = parser.nextInt(0); + if (parser.hasNext(2)) { + position.setNetwork(new Network(CellTower.from(mcc, mnc, parser.nextInt(0), parser.nextInt(0)))); + } + if (parser.hasNext(2)) { + position.setNetwork(new Network(CellTower.from(mcc, mnc, parser.nextHexInt(0), parser.nextHexInt(0)))); + } + } + + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + } + + private Object decodeObd(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_OBD, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + position.set(Position.KEY_RPM, parser.nextInt()); + position.set(Position.KEY_OBD_SPEED, parser.nextInt()); + position.set(Position.PREFIX_TEMP + 1, parser.nextInt()); + position.set(Position.KEY_FUEL_CONSUMPTION, parser.next()); + position.set("dtcsClearedDistance", parser.nextInt()); + position.set("odbConnect", parser.nextInt(0) == 1); + position.set("dtcsNumber", parser.nextInt()); + position.set("dtcsCodes", parser.next()); + position.set(Position.KEY_THROTTLE, parser.nextInt()); + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); + position.set(Position.KEY_OBD_ODOMETER, parser.nextInt(0) * 1000); + + decodeLocation(position, parser); + + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + + decodeDeviceTime(position, parser); + + return position; + } + + private void decodeStatus(Position position, Parser parser) { + if (parser.hasNext(3)) { + int ignition = parser.nextHexInt(0); + if (BitUtil.check(ignition, 4)) { + position.set(Position.KEY_IGNITION, false); + } else if (BitUtil.check(ignition, 5)) { + position.set(Position.KEY_IGNITION, true); + } + position.set(Position.KEY_INPUT, parser.nextHexInt(0)); + position.set(Position.KEY_OUTPUT, parser.nextHexInt(0)); + } + } + + private Object decodeFri(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_FRI, sentence); + if (!parser.matches()) { + return null; + } + + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + + LinkedList positions = new LinkedList<>(); + + String vin = parser.next(); + int power = parser.nextInt(0); + + Parser itemParser = new Parser(PATTERN_LOCATION, parser.next()); + while (itemParser.find()) { + Position position = new Position(); + position.setProtocol(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + position.set(Position.KEY_VIN, vin); + + decodeLocation(position, itemParser); + + positions.add(position); + } + + Position position = positions.getLast(); + + decodeLocation(position, parser); + + // power value only on some devices + if (power > 10) { + position.set(Position.KEY_POWER, power); + } + + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + position.set(Position.KEY_HOURS, parser.next()); + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.PREFIX_ADC + 2, parser.next()); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + + decodeStatus(position, parser); + + position.set(Position.KEY_RPM, parser.nextInt()); + position.set(Position.KEY_FUEL_LEVEL, parser.nextInt()); + + decodeDeviceTime(position, parser); + + 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()); + if (deviceSession == null) { + return null; + } + + LinkedList positions = new LinkedList<>(); + + int power = parser.nextInt(0); + + Parser itemParser = new Parser(PATTERN_LOCATION, parser.next()); + while (itemParser.find()) { + Position position = new Position(); + position.setProtocol(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + decodeLocation(position, itemParser); + + positions.add(position); + } + + Position position = positions.getLast(); + + decodeLocation(position, parser); + + position.set(Position.KEY_POWER, power); + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + position.set(Position.KEY_HOURS, parser.next()); + position.set(Position.PREFIX_ADC + 1, parser.next()); + position.set(Position.PREFIX_ADC + 2, parser.next()); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + + decodeStatus(position, parser); + + int index = 0; + String[] data = parser.next().split(","); + if (data.length > 1) { + int deviceType = Integer.parseInt(data[index++]); + if (deviceType == 2) { + int deviceCount = Integer.parseInt(data[index++]); + for (int i = 1; i <= deviceCount; i++) { + index++; // id + index++; // type + position.set(Position.PREFIX_TEMP + i, Short.parseShort(data[index++], 16) * 0.0625); + } + } + } + + decodeDeviceTime(position, parser); + + return positions; + } + + private Object decodeIgn(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_IGN, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + decodeLocation(position, parser); + + position.set(Position.KEY_HOURS, parser.next()); + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + + decodeDeviceTime(position, parser); + + return position; + } + + private Object decodeIda(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_IDA, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + position.set(Position.KEY_DRIVER_UNIQUE_ID, parser.next()); + + decodeLocation(position, parser); + + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + + decodeDeviceTime(position, parser); + + return position; + } + + private Object decodeWif(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_WIF, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + getLastLocation(position, null); + + Network network = new Network(); + + parser.nextInt(0); // count + Matcher matcher = Pattern.compile("([0-9a-fA-F]{12}),(-?\\d+),,,,").matcher(parser.next()); + while (matcher.find()) { + String mac = matcher.group(1).replaceAll("(..)", "$1:"); + network.addWifiAccessPoint(WifiAccessPoint.from( + mac.substring(0, mac.length() - 1), Integer.parseInt(matcher.group(2)))); + } + + position.setNetwork(network); + + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt(0)); + + return position; + } + + private Object decodeGsm(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_GSM, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + getLastLocation(position, null); + + Network network = new Network(); + + String[] data = parser.next().split(","); + for (int i = 0; i < 6; i++) { + if (!data[i * 6].isEmpty()) { + network.addCellTower(CellTower.from( + Integer.parseInt(data[i * 6]), Integer.parseInt(data[i * 6 + 1]), + Integer.parseInt(data[i * 6 + 2], 16), Integer.parseInt(data[i * 6 + 3], 16), + Integer.parseInt(data[i * 6 + 4]))); + } + } + + position.setNetwork(network); + + return position; + } + + private Object decodeOther(Channel channel, SocketAddress remoteAddress, String sentence, String type) { + Parser parser = new Parser(PATTERN, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + int reportType = parser.nextInt(0); + if (type.equals("NMR")) { + position.set(Position.KEY_MOTION, reportType == 1); + } else if (type.equals("SOS")) { + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + } + + decodeLocation(position, parser); + + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt(0)); + + position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000); + + decodeDeviceTime(position, parser); + + if (Context.getConfig().getBoolean(getProtocolName() + ".ack") && channel != null) { + channel.write("+SACK:" + parser.next() + "$", remoteAddress); + } + + return position; + } + + private Object decodeBasic(Channel channel, SocketAddress remoteAddress, String sentence, String type) { + Parser parser = new Parser(PATTERN_BASIC, sentence); + Position position = initPosition(parser, channel, remoteAddress); + if (position == null) { + return null; + } + + int hdop = parser.nextInt(0); + position.setValid(hdop > 0); + position.set(Position.KEY_HDOP, hdop); + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble(0))); + position.setCourse(parser.nextDouble(0)); + position.setAltitude(parser.nextDouble(0)); + + if (parser.hasNext(2)) { + position.setLongitude(parser.nextDouble(0)); + position.setLatitude(parser.nextDouble(0)); + } else { + getLastLocation(position, null); + } + + if (parser.hasNext(6)) { + position.setTime(parser.nextDateTime()); + } + + if (parser.hasNext(4)) { + position.setNetwork(new Network(CellTower.from( + parser.nextInt(0), parser.nextInt(0), parser.nextHexInt(0), parser.nextHexInt(0)))); + } + + decodeDeviceTime(position, parser); + + switch (type) { + case "PNA": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_ON); + break; + case "PFA": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_OFF); + break; + case "EPN": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_RESTORED); + break; + case "EPF": + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + break; + case "BPL": + position.set(Position.KEY_ALARM, Position.ALARM_LOW_BATTERY); + break; + case "STT": + position.set(Position.KEY_ALARM, Position.ALARM_MOVEMENT); + break; + case "SWG": + position.set(Position.KEY_ALARM, Position.ALARM_GEOFENCE); + break; + case "TMP": + case "TEM": + position.set(Position.KEY_ALARM, Position.ALARM_TEMPERATURE); + break; + case "JDR": + case "JDS": + position.set(Position.KEY_ALARM, Position.ALARM_JAMMING); + break; + default: + break; + } + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = ((ChannelBuffer) msg).toString(StandardCharsets.US_ASCII); + + int typeIndex = sentence.indexOf(":GT"); + if (typeIndex < 0) { + return null; + } + + Object result; + String type = sentence.substring(typeIndex + 3, typeIndex + 6); + if (sentence.startsWith("+ACK")) { + result = decodeAck(channel, remoteAddress, sentence, type); + } else { + switch (type) { + case "INF": + result = decodeInf(channel, remoteAddress, sentence); + break; + case "OBD": + result = decodeObd(channel, remoteAddress, sentence); + break; + case "FRI": + result = decodeFri(channel, remoteAddress, sentence); + break; + case "ERI": + result = decodeEri(channel, remoteAddress, sentence); + break; + case "IGN": + case "IGF": + result = decodeIgn(channel, remoteAddress, sentence); + break; + case "IDA": + result = decodeIda(channel, remoteAddress, sentence); + break; + case "WIF": + result = decodeWif(channel, remoteAddress, sentence); + break; + case "GSM": + result = decodeGsm(channel, remoteAddress, sentence); + break; + case "VER": + result = decodeVer(channel, remoteAddress, sentence); + break; + default: + result = decodeOther(channel, remoteAddress, sentence, type); + break; + } + + if (result == null) { + result = decodeBasic(channel, remoteAddress, sentence, type); + } + + if (result != null) { + if (result instanceof Position) { + ((Position) result).set(Position.KEY_TYPE, type); + } else { + for (Position p : (List) result) { + p.set(Position.KEY_TYPE, type); + } + } + } + } + + return result; + } + +} -- cgit v1.2.3