From c51119948fcd7b1f77f174310cc1b3cfaa031fd7 Mon Sep 17 00:00:00 2001 From: Edward Valley Date: Tue, 2 Jul 2019 23:46:51 -0400 Subject: Enhance Laipac protocol decoder --- .../traccar/protocol/LaipacProtocolDecoder.java | 240 +++++++++++++++++---- 1 file changed, 193 insertions(+), 47 deletions(-) (limited to 'src/main') diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java index 2f3cbb1b9..17298c6f3 100644 --- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java @@ -17,6 +17,7 @@ package org.traccar.protocol; import io.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; import org.traccar.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; @@ -25,41 +26,57 @@ import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; import org.traccar.model.CellTower; +import org.traccar.model.Device; +import org.traccar.model.Command; import org.traccar.model.Network; import org.traccar.model.Position; +import org.traccar.database.ConnectionManager; import java.net.SocketAddress; import java.util.regex.Pattern; +import java.util.Date; public class LaipacProtocolDecoder extends BaseProtocolDecoder { + private final String defaultDevicePassword; + public LaipacProtocolDecoder(Protocol protocol) { super(protocol); + defaultDevicePassword = Context.getConfig().getString( + getProtocolName() + ".defaultPassword", "00000000"); } - private static final Pattern PATTERN = new PatternBuilder() - .text("$AVRMC,") - .expression("([^,]+),") // identifier - .number("(dd)(dd)(dd),") // time (hhmmss) - .expression("([AVRPavrp]),") // validity - .number("(dd)(dd.d+),") // latitude - .expression("([NS]),") - .number("(ddd)(dd.d+),") // longitude - .number("([EW]),") - .number("(d+.d+),") // speed - .number("(d+.d+),") // course - .number("(dd)(dd)(dd),") // date (ddmmyy) - .expression("([abZXTSMHFE86430]),") // event code - .expression("([\\d.]+),") // battery voltage - .number("(d+),") // current mileage - .number("(d),") // gps status - .number("(d+),") // adc1 - .number("(d+)") // adc2 - .number(",(xxxx)") // lac - .number("(xxxx),") // cid - .number("(ddd)") // mcc - .number("(ddd)") // mnc - .optional(4) + private static final Pattern PATTERN_ECHK = new PatternBuilder() + .text("$ECHK") + .expression(",([^,]+)") // identifier + .number(",(d+)") // sequence number + .text("*") + .number("(xx)") // checksum + .compile(); + + private static final Pattern PATTERN_AVRMC = new PatternBuilder() + .text("$AVRMC") + .expression(",([^,]+)") // identifier + .number(",(dd)(dd)(dd)") // time (hhmmss) + .expression(",([AVRPavrp])") // validity + .number(",(dd)(dd.d+)") // latitude + .expression(",([NS])") // latitude hemisphere + .number(",(ddd)(dd.d+)") // longitude + .number(",([EW])") // longitude hemisphere + .number(",(d+.d+)") // speed + .number(",(d+.d+)") // course + .number(",(dd)(dd)(dd)") // date (ddmmyy) + .expression(",([0-9A-Za-z])") // event code + .expression(",([\\d.]+)") // battery voltage + .number(",(d+)") // current mileage + .number(",(d)") // gps status + .number(",(d+)") // adc1 + .number(",(d+)") // adc2 + .number(",(x{1}|x{8})") // lac+cid + .number(",(d{1}|d{6})") // mcc+mnc + .optional(2) + .expression(",([^*]*)") // anything remaining (be forward compatible) + .optional(1) .text("*") .number("(xx)") // checksum .compile(); @@ -68,6 +85,8 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder { switch (event) { case "Z": return Position.ALARM_LOW_BATTERY; + case "Y": + return Position.ALARM_TOW; case "X": return Position.ALARM_GEOFENCE_ENTER; case "T": @@ -81,6 +100,8 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder { return Position.ALARM_GEOFENCE_EXIT; case "6": return Position.ALARM_OVERSPEED; + case "5": + return Position.ALARM_POWER_CUT; case "3": return Position.ALARM_SOS; default: @@ -88,74 +109,157 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder { } } - @Override - protected Object decode( - Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + private String decodeEvent(String event, Position position) { - String sentence = (String) msg; + if (event.length() == 1) { + char inputStatus = event.charAt(0); + if (inputStatus >= 'A' && inputStatus <= 'D') { + int inputStatusInt = inputStatus - 'A'; + position.set(Position.PREFIX_IN + 1, inputStatusInt & 1); + position.set(Position.PREFIX_IN + 2, inputStatusInt & 2); + position.set(Position.KEY_IGNITION, ((inputStatusInt & 1) != 0) ? true : false); + return null; + } + } - if (sentence.startsWith("$ECHK") && channel != null) { - channel.writeAndFlush(new NetworkMessage(sentence + "\r\n", remoteAddress)); // heartbeat - return null; + return event; + } + + private String getDevicePassword(DeviceSession deviceSession) { + + String devicePassword = defaultDevicePassword; + + Device device = Context.getIdentityManager().getById(deviceSession.getDeviceId()); + if (device != null) { + String password = device.getString(Command.KEY_DEVICE_PASSWORD); + if (password != null) { + devicePassword = password; + } } - Parser parser = new Parser(PATTERN, sentence); - if (!parser.matches()) { - return null; + return devicePassword; + } + + private Object handleEchk( + String sentence, int checksum, Parser parser, Channel channel, SocketAddress remoteAddress) { + + if (channel != null) { + channel.writeAndFlush(new NetworkMessage(sentence + "\r\n", remoteAddress)); } - DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + DeviceSession deviceSession = + getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession != null) { + ConnectionManager cm = Context.getConnectionManager(); + if (cm != null) { + cm.updateDevice(deviceSession.getDeviceId(), + Device.STATUS_ONLINE, new Date()); + } + } + + return null; + } + + protected Object handleAvrmc( + String sentence, int checksum, Parser parser, Channel channel, SocketAddress remoteAddress) { + + DeviceSession deviceSession = + getDeviceSession(channel, remoteAddress, parser.next()); if (deviceSession == null) { return null; } Position position = new Position(getProtocolName()); + + // Device ID position.setDeviceId(deviceSession.getDeviceId()); + // Time DateBuilder dateBuilder = new DateBuilder() .setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); + // Status [ V: Invalid | A: Valid | R: Not realtime | P: Parked ] String status = parser.next(); String upperCaseStatus = status.toUpperCase(); - position.setValid(upperCaseStatus.equals("A") || upperCaseStatus.equals("R") || upperCaseStatus.equals("P")); + position.setValid(upperCaseStatus.equals("A")); position.set(Position.KEY_STATUS, status); + // Position position.setLatitude(parser.nextCoordinate()); position.setLongitude(parser.nextCoordinate()); + + // Speed position.setSpeed(parser.nextDouble(0)); + + // Course position.setCourse(parser.nextDouble(0)); + // Date dateBuilder.setDateReverse(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0)); position.setTime(dateBuilder.getDate()); + // Alarm / Event String event = parser.next(); position.set(Position.KEY_ALARM, decodeAlarm(event)); - position.set(Position.KEY_EVENT, event); + position.set(Position.KEY_EVENT, decodeEvent(event, position)); + + // Battery position.set(Position.KEY_BATTERY, Double.parseDouble(parser.next().replaceAll("\\.", "")) * 0.001); + + // Odometer position.set(Position.KEY_ODOMETER, parser.nextDouble()); + + // GPS status position.set(Position.KEY_GPS, parser.nextInt()); + + // ADC1 / ADC2 position.set(Position.PREFIX_ADC + 1, parser.nextDouble() * 0.001); position.set(Position.PREFIX_ADC + 2, parser.nextDouble() * 0.001); - Integer lac = parser.nextHexInt(); - Integer cid = parser.nextHexInt(); - Integer mcc = parser.nextInt(); - Integer mnc = parser.nextInt(); - if (lac != null && cid != null && mcc != null && mnc != null) { + // LAC, CID, MCC, MNC + Integer laccid = parser.nextHexInt(); + Integer mccmnc = parser.nextInt(); + if (laccid != null && laccid != 0 && mccmnc != null && mccmnc != 0) { + Integer lac = (laccid >> 16) & 0xFFFF; + Integer cid = laccid & 0xFFFF; + Integer mcc = mccmnc / 10000; + Integer mnc = mccmnc % 100; position.setNetwork(new Network(CellTower.from(mcc, mnc, lac, cid))); } - String checksum = parser.next(); + // Skip remaining parameters + String unused = parser.next(); + + // Checksum + String checksumStr = parser.next(); + if (checksum != Integer.parseInt(checksumStr, 16)) { + return null; + } if (channel != null) { + + if (Character.isLowerCase(status.charAt(0))) { + String ack = "$EAVACK," + event + "," + checksumStr; + ack += Checksum.nmea(ack) + "\r\n"; + channel.writeAndFlush(new NetworkMessage(ack, remoteAddress)); + } + + String response = ""; + String devicePassword = getDevicePassword(deviceSession); + if (event.equals("3")) { - channel.writeAndFlush(new NetworkMessage("$AVCFG,00000000,d*31\r\n", remoteAddress)); + response = "$AVCFG," + devicePassword + ",d"; + } else if (event.equals("S") || event.equals("T")) { + response = "$AVCFG," + devicePassword + ",t"; } else if (event.equals("X") || event.equals("4")) { - channel.writeAndFlush(new NetworkMessage("$AVCFG,00000000,x*2D\r\n", remoteAddress)); + response = "$AVCFG," + devicePassword + ",x"; + } else if (event.equals("Y")) { + response = "$AVCFG," + devicePassword + ",y"; } else if (event.equals("Z")) { - channel.writeAndFlush(new NetworkMessage("$AVCFG,00000000,z*2F\r\n", remoteAddress)); - } else if (Character.isLowerCase(status.charAt(0))) { - String response = "$EAVACK," + event + "," + checksum; + response = "$AVCFG," + devicePassword + ",z"; + } + + if (response.length() > 0) { response += Checksum.nmea(response) + "\r\n"; channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); } @@ -164,4 +268,46 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder { return position; } + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + // Validate sentence length + int slen = sentence.length(); + if (slen <= 5) { + return null; + } + + // Validate sentence format + if (sentence.charAt(0) != '$') { + return null; + } + if (sentence.charAt(slen - 3) != '*') { + return null; + } + + // Verify sentence checksum + int checksum = Integer.parseInt(sentence.substring(slen - 2), 16); + if (checksum != Checksum.xor(sentence.substring(1, slen - 3))) { + return null; + } + + // Handle ECHK sentences + Parser parser = new Parser(PATTERN_ECHK, sentence); + if (parser.matches()) { + return handleEchk(sentence, checksum, parser, channel, remoteAddress); + } + + // Handle AVRMC sentences + parser = new Parser(PATTERN_AVRMC, sentence); + if (parser.matches()) { + return handleAvrmc(sentence, checksum, parser, channel, remoteAddress); + } + + return null; + } + + } -- cgit v1.2.3