aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/traccar/protocol
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/traccar/protocol')
-rw-r--r--src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java240
1 files changed, 193 insertions, 47 deletions
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;
+ }
+
+
}