From c51119948fcd7b1f77f174310cc1b3cfaa031fd7 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
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/java/org/traccar')

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


From efb64c30835eccf1e532ea7d7618d2d40ce7b761 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
Date: Tue, 2 Jul 2019 23:48:54 -0400
Subject: Implement basic Laipac protocol encoder

---
 .../java/org/traccar/protocol/LaipacProtocol.java  |  8 +++
 .../traccar/protocol/LaipacProtocolEncoder.java    | 71 ++++++++++++++++++++++
 2 files changed, 79 insertions(+)
 create mode 100644 src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java

(limited to 'src/main/java/org/traccar')

diff --git a/src/main/java/org/traccar/protocol/LaipacProtocol.java b/src/main/java/org/traccar/protocol/LaipacProtocol.java
index 923b08a16..601e652c4 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocol.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocol.java
@@ -18,6 +18,7 @@ package org.traccar.protocol;
 import io.netty.handler.codec.LineBasedFrameDecoder;
 import io.netty.handler.codec.string.StringDecoder;
 import io.netty.handler.codec.string.StringEncoder;
+import org.traccar.model.Command;
 import org.traccar.BaseProtocol;
 import org.traccar.PipelineBuilder;
 import org.traccar.TrackerServer;
@@ -25,12 +26,19 @@ import org.traccar.TrackerServer;
 public class LaipacProtocol extends BaseProtocol {
 
     public LaipacProtocol() {
+
+        setSupportedDataCommands(
+                Command.TYPE_CUSTOM,
+                Command.TYPE_POSITION_SINGLE,
+                Command.TYPE_REBOOT_DEVICE);
+
         addServer(new TrackerServer(false, getName()) {
             @Override
             protected void addProtocolHandlers(PipelineBuilder pipeline) {
                 pipeline.addLast(new LineBasedFrameDecoder(1024));
                 pipeline.addLast(new StringEncoder());
                 pipeline.addLast(new StringDecoder());
+                pipeline.addLast(new LaipacProtocolEncoder(LaipacProtocol.this));
                 pipeline.addLast(new LaipacProtocolDecoder(LaipacProtocol.this));
             }
         });
diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
new file mode 100644
index 000000000..090203806
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.protocol;
+
+import org.traccar.Context;
+import org.traccar.Protocol;
+import org.traccar.StringProtocolEncoder;
+import org.traccar.model.Command;
+import org.traccar.helper.Checksum;
+
+public class LaipacProtocolEncoder extends StringProtocolEncoder {
+
+    private final Protocol protocol;
+    private final String defaultDevicePassword;
+
+    public LaipacProtocolEncoder(Protocol protocol) {
+        this.protocol = protocol;
+        defaultDevicePassword = Context.getConfig().getString(
+            getProtocolName() + ".defaultPassword", "00000000");
+    }
+
+    protected String getProtocolName() {
+        return protocol.getName();
+    }
+
+    @Override
+    protected Object encodeCommand(Command command) {
+
+        initDevicePassword(command, defaultDevicePassword);
+
+        String cmd = "";
+
+        switch (command.getType()) {
+            case Command.TYPE_CUSTOM:
+                cmd = formatCommand(command, "${%s}",
+                    Command.KEY_DATA);
+                break;
+            case Command.TYPE_POSITION_SINGLE:
+                cmd = formatCommand(command, "$AVREQ,{%s},1",
+                    Command.KEY_DEVICE_PASSWORD);
+                break;
+            case Command.TYPE_REBOOT_DEVICE:
+                cmd = formatCommand(command, "$AVRESET,{%s},{%s}",
+                    Command.KEY_UNIQUE_ID, Command.KEY_DEVICE_PASSWORD);
+                break;
+            default:
+                break;
+        }
+
+        if (cmd.length() > 0) {
+            cmd += Checksum.nmea(cmd) + "\r\n";
+            return cmd;
+        }
+
+        return null;
+    }
+
+}
-- 
cgit v1.2.3


From 9cd863136dbcf9e8062f633869de212426bed960 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
Date: Wed, 3 Jul 2019 17:04:36 -0400
Subject: Changes after first review

---
 .../traccar/protocol/LaipacProtocolDecoder.java    | 164 +++++++++------------
 .../traccar/protocol/LaipacProtocolEncoder.java    |  20 ++-
 .../protocol/LaipacProtocolDecoderTest.java        |  12 +-
 3 files changed, 83 insertions(+), 113 deletions(-)

(limited to 'src/main/java/org/traccar')

diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
index 17298c6f3..544f1dc80 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
@@ -30,11 +30,9 @@ 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 {
 
@@ -72,9 +70,11 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
             .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)
+            .number(",(x{4}|x{1})")              // lac | lac+cid = 0
+            .number("(x{4})")                    // cid | nothing
+            .number(",(d{3}|d{1})")              // mcc | mcc+mnc = 0
+            .number("(d{3})")                    // mnc | nothing
+            .optional(4)
             .expression(",([^*]*)")              // anything remaining (be forward compatible)
             .optional(1)
             .text("*")
@@ -140,28 +140,64 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
         return devicePassword;
     }
 
+    private String getEventResponse(String event, String devicePassword) {
+
+        String responseCode = null;
+
+        switch (event) {
+            case "3":
+                responseCode = "d";
+                break;
+            case "S":
+            case "T":
+                responseCode = "t";
+                break;
+            case "X":
+            case "4":
+                responseCode = "x";
+                break;
+            case "Y":
+                responseCode = "y";
+                break;
+            case "Z":
+                responseCode = "z";
+                break;
+            default:
+                break;
+        }
+
+        if (responseCode != null) {
+            String response = "$AVCFG," + devicePassword + "," + responseCode;
+            response += Checksum.nmea(response) + "\r\n";
+            return response;
+        }
+
+        return null;
+    }
+
+    private String getStatusResponse(String status, String event, String checksum) {
+
+        if (Character.isLowerCase(status.charAt(0))) {
+            String response = "$EAVACK," + event + "," + checksum;
+            response += Checksum.nmea(response) + "\r\n";
+            return response;
+        }
+
+        return null;
+    }
+
     private Object handleEchk(
-            String sentence, int checksum, Parser parser, Channel channel, SocketAddress remoteAddress) {
+            String sentence, Parser parser, Channel channel, SocketAddress remoteAddress) {
 
         if (channel != null) {
             channel.writeAndFlush(new NetworkMessage(sentence + "\r\n", remoteAddress));
         }
 
-        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) {
+            String sentence, Parser parser, Channel channel, SocketAddress remoteAddress) {
 
         DeviceSession deviceSession =
             getDeviceSession(channel, remoteAddress, parser.next());
@@ -171,97 +207,55 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
         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"));
+        position.setValid(upperCaseStatus.equals("A") || upperCaseStatus.equals("R") || upperCaseStatus.equals("P"));
         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, 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);
 
-        // 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;
+        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) {
             position.setNetwork(new Network(CellTower.from(mcc, mnc, lac, cid)));
         }
 
-        // Skip remaining parameters
         String unused = parser.next();
 
-        // Checksum
-        String checksumStr = parser.next();
-        if (checksum != Integer.parseInt(checksumStr, 16)) {
-            return null;
-        }
+        String checksum = parser.next();
 
         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 statusResponse = getStatusResponse(status, event, checksum);
+            if (statusResponse != null) {
+                channel.writeAndFlush(new NetworkMessage(statusResponse, remoteAddress));
             }
 
-            String response = "";
             String devicePassword = getDevicePassword(deviceSession);
-
-            if (event.equals("3")) {
-                response = "$AVCFG," + devicePassword + ",d";
-            } else if (event.equals("S") || event.equals("T")) {
-                response = "$AVCFG," + devicePassword + ",t";
-            } else if (event.equals("X") || event.equals("4")) {
-                response = "$AVCFG," + devicePassword + ",x";
-            } else if (event.equals("Y")) {
-                response = "$AVCFG," + devicePassword + ",y";
-            } else if (event.equals("Z")) {
-                response = "$AVCFG," + devicePassword + ",z";
-            }
-
-            if (response.length() > 0) {
-                response += Checksum.nmea(response) + "\r\n";
-                channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
+            String eventResponse = getEventResponse(event, devicePassword);
+            if (eventResponse != null) {
+                channel.writeAndFlush(new NetworkMessage(eventResponse, remoteAddress));
             }
         }
 
@@ -274,36 +268,14 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
         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);
+            return handleEchk(sentence, parser, channel, remoteAddress);
         }
 
-        // Handle AVRMC sentences
         parser = new Parser(PATTERN_AVRMC, sentence);
         if (parser.matches()) {
-            return handleAvrmc(sentence, checksum,  parser, channel, remoteAddress);
+            return handleAvrmc(sentence, parser, channel, remoteAddress);
         }
 
         return null;
diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
index 090203806..fac82d2fd 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
+ * Copyright 2019 Anton Tananaev (anton@traccar.org)
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,7 +15,6 @@
  */
 package org.traccar.protocol;
 
-import org.traccar.Context;
 import org.traccar.Protocol;
 import org.traccar.StringProtocolEncoder;
 import org.traccar.model.Command;
@@ -28,8 +27,7 @@ public class LaipacProtocolEncoder extends StringProtocolEncoder {
 
     public LaipacProtocolEncoder(Protocol protocol) {
         this.protocol = protocol;
-        defaultDevicePassword = Context.getConfig().getString(
-            getProtocolName() + ".defaultPassword", "00000000");
+        defaultDevicePassword = "00000000";
     }
 
     protected String getProtocolName() {
@@ -41,28 +39,28 @@ public class LaipacProtocolEncoder extends StringProtocolEncoder {
 
         initDevicePassword(command, defaultDevicePassword);
 
-        String cmd = "";
+        String commandSentence = null;
 
         switch (command.getType()) {
             case Command.TYPE_CUSTOM:
-                cmd = formatCommand(command, "${%s}",
+                commandSentence = formatCommand(command, "${%s}",
                     Command.KEY_DATA);
                 break;
             case Command.TYPE_POSITION_SINGLE:
-                cmd = formatCommand(command, "$AVREQ,{%s},1",
+                commandSentence = formatCommand(command, "$AVREQ,{%s},1",
                     Command.KEY_DEVICE_PASSWORD);
                 break;
             case Command.TYPE_REBOOT_DEVICE:
-                cmd = formatCommand(command, "$AVRESET,{%s},{%s}",
+                commandSentence = formatCommand(command, "$AVRESET,{%s},{%s}",
                     Command.KEY_UNIQUE_ID, Command.KEY_DEVICE_PASSWORD);
                 break;
             default:
                 break;
         }
 
-        if (cmd.length() > 0) {
-            cmd += Checksum.nmea(cmd) + "\r\n";
-            return cmd;
+        if (commandSentence != null) {
+            commandSentence += Checksum.nmea(commandSentence) + "\r\n";
+            return commandSentence;
         }
 
         return null;
diff --git a/src/test/java/org/traccar/protocol/LaipacProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/LaipacProtocolDecoderTest.java
index 1426e5c35..0bbb58490 100644
--- a/src/test/java/org/traccar/protocol/LaipacProtocolDecoderTest.java
+++ b/src/test/java/org/traccar/protocol/LaipacProtocolDecoderTest.java
@@ -104,27 +104,27 @@ public class LaipacProtocolDecoderTest extends ProtocolTest {
         verifyPosition(decoder, text(
                 "$AVRMC,999999999999999,084514,r,5050.1314,N,00419.9719,E,0.68,306.39,120318,0,3882,84,1,0,0,3EE4A617,020610*4D"));
 
-        //Alarm button
+        // Alarm button
         verifyPosition(decoder, text(
                 "$AVRMC,358174067149865,142945,R,5050.1254,N,00420.0490,E,0.00,0.00,190318,3,3455,119,1,0,0,3EE4A617,020610*53"));
 
-        //G-Sensor
+        // G-Sensor
         verifyPosition(decoder, text(
                 "$AVRMC,358174067149865,143407,R,5050.1254,N,00420.0490,E,0.00,0.00,190318,8,3455,119,1,0,0,3EE4A617,020610*52"));
 
-        //Powered off
+        // Powered off
         verifyPosition(decoder, text(
                 "$AVRMC,358174067149865,143648,A,5050.1141,N,00420.0525,E,1.24,174.38,190318,H,3455,119,1,0,0,3EE4A617,020610*3E"));
 
-        //No network
+        // No network
         verifyPosition(decoder, text(
                 "$AVRMC,358174067149865,143747,R,5050.1124,N,00420.0542,E,1.34,161.96,190318,a,3416,119,1,0,0*7D"));
 
-        //Zero LAC, CID, MCC, MNC
+        // Zero LAC, CID, MCC, MNC
         verifyPosition(decoder, text(
                 "$AVRMC,358174067149865,143747,P,5050.1124,N,00420.0542,E,1.34,161.96,190318,A,3416,119,1,0,0,0,0*5F"));
 
-        //New unknown parameters
+        // New unknown parameters
         verifyPosition(decoder, text(
                 "$AVRMC,358174067149865,143747,P,5050.1124,N,00420.0542,E,1.34,161.96,190318,A,3416,119,1,0,0,0,0,0,0*5F"));
     }
-- 
cgit v1.2.3


From 0e4e396fee573aa8d00a0f970085266ffdb45e93 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
Date: Thu, 4 Jul 2019 12:22:13 -0400
Subject: Missed minor change

---
 src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

(limited to 'src/main/java/org/traccar')

diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
index 544f1dc80..b8a0b167c 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
@@ -40,8 +40,7 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
     public LaipacProtocolDecoder(Protocol protocol) {
         super(protocol);
-        defaultDevicePassword = Context.getConfig().getString(
-            getProtocolName() + ".defaultPassword", "00000000");
+        defaultDevicePassword = "00000000";
     }
 
     private static final Pattern PATTERN_ECHK = new PatternBuilder()
-- 
cgit v1.2.3


From 11568839d89fa5bf58da0a654248f9b7696ee406 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
Date: Fri, 5 Jul 2019 02:42:01 -0400
Subject: Changes after second review

---
 .../java/org/traccar/protocol/LaipacProtocol.java  |  4 +-
 .../traccar/protocol/LaipacProtocolDecoder.java    | 73 ++++++++++------------
 .../traccar/protocol/LaipacProtocolEncoder.java    | 14 +----
 3 files changed, 37 insertions(+), 54 deletions(-)

(limited to 'src/main/java/org/traccar')

diff --git a/src/main/java/org/traccar/protocol/LaipacProtocol.java b/src/main/java/org/traccar/protocol/LaipacProtocol.java
index 601e652c4..b325755c8 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocol.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocol.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -38,7 +38,7 @@ public class LaipacProtocol extends BaseProtocol {
                 pipeline.addLast(new LineBasedFrameDecoder(1024));
                 pipeline.addLast(new StringEncoder());
                 pipeline.addLast(new StringDecoder());
-                pipeline.addLast(new LaipacProtocolEncoder(LaipacProtocol.this));
+                pipeline.addLast(new LaipacProtocolEncoder());
                 pipeline.addLast(new LaipacProtocolDecoder(LaipacProtocol.this));
             }
         });
diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
index b8a0b167c..c028d78b6 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org)
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,13 +36,12 @@ import java.util.regex.Pattern;
 
 public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
-    private final String defaultDevicePassword;
-
     public LaipacProtocolDecoder(Protocol protocol) {
         super(protocol);
-        defaultDevicePassword = "00000000";
     }
 
+    public static final String DEFAULT_DEVICE_PASSWORD = "00000000";
+
     private static final Pattern PATTERN_ECHK = new PatternBuilder()
             .text("$ECHK")
             .expression(",([^,]+)")             // identifier
@@ -52,27 +51,27 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
             .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{4}|x{1})")              // lac | lac+cid = 0
-            .number("(x{4})")                    // cid | nothing
-            .number(",(d{3}|d{1})")              // mcc | mcc+mnc = 0
-            .number("(d{3})")                    // mnc | nothing
+            .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(",(xxxx|x)")                 // lac | lac+cid = 0
+            .number("(xxxx),")                   // cid | nothing
+            .number("(ddd|d)")                   // mcc | mcc+mnc = 0
+            .number("(ddd)")                     // mnc | nothing
             .optional(4)
             .expression(",([^*]*)")              // anything remaining (be forward compatible)
             .optional(1)
@@ -126,7 +125,7 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
     private String getDevicePassword(DeviceSession deviceSession) {
 
-        String devicePassword = defaultDevicePassword;
+        String devicePassword = DEFAULT_DEVICE_PASSWORD;
 
         Device device = Context.getIdentityManager().getById(deviceSession.getDeviceId());
         if (device != null) {
@@ -139,7 +138,8 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
         return devicePassword;
     }
 
-    private String getEventResponse(String event, String devicePassword) {
+    private void sendEventResponse(
+            String event, String devicePassword, Channel channel, SocketAddress remoteAddress) {
 
         String responseCode = null;
 
@@ -168,21 +168,20 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
         if (responseCode != null) {
             String response = "$AVCFG," + devicePassword + "," + responseCode;
             response += Checksum.nmea(response) + "\r\n";
-            return response;
+            channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
         }
 
-        return null;
     }
 
-    private String getStatusResponse(String status, String event, String checksum) {
+    private void sendAcknowledge(
+            String status, String event, String checksum, Channel channel, SocketAddress remoteAddress) {
 
         if (Character.isLowerCase(status.charAt(0))) {
             String response = "$EAVACK," + event + "," + checksum;
             response += Checksum.nmea(response) + "\r\n";
-            return response;
+            channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
         }
 
-        return null;
     }
 
     private Object handleEchk(
@@ -246,16 +245,10 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
         if (channel != null) {
 
-            String statusResponse = getStatusResponse(status, event, checksum);
-            if (statusResponse != null) {
-                channel.writeAndFlush(new NetworkMessage(statusResponse, remoteAddress));
-            }
+            sendAcknowledge(status, event, checksum, channel, remoteAddress);
 
             String devicePassword = getDevicePassword(deviceSession);
-            String eventResponse = getEventResponse(event, devicePassword);
-            if (eventResponse != null) {
-                channel.writeAndFlush(new NetworkMessage(eventResponse, remoteAddress));
-            }
+            sendEventResponse(event, devicePassword, channel, remoteAddress);
         }
 
         return position;
diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
index fac82d2fd..6f540fa3e 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
@@ -15,29 +15,19 @@
  */
 package org.traccar.protocol;
 
-import org.traccar.Protocol;
 import org.traccar.StringProtocolEncoder;
 import org.traccar.model.Command;
 import org.traccar.helper.Checksum;
 
 public class LaipacProtocolEncoder extends StringProtocolEncoder {
 
-    private final Protocol protocol;
-    private final String defaultDevicePassword;
-
-    public LaipacProtocolEncoder(Protocol protocol) {
-        this.protocol = protocol;
-        defaultDevicePassword = "00000000";
-    }
-
-    protected String getProtocolName() {
-        return protocol.getName();
+    public LaipacProtocolEncoder() {
     }
 
     @Override
     protected Object encodeCommand(Command command) {
 
-        initDevicePassword(command, defaultDevicePassword);
+        initDevicePassword(command, LaipacProtocolDecoder.DEFAULT_DEVICE_PASSWORD);
 
         String commandSentence = null;
 
-- 
cgit v1.2.3


From 2c604b9c9ee8f996902acfdd60626324a8414d34 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
Date: Wed, 10 Jul 2019 17:03:25 -0400
Subject: Changes after third review

---
 src/main/java/org/traccar/BaseProtocolEncoder.java | 13 +++-----
 .../java/org/traccar/database/DeviceManager.java   | 18 +++++++++-
 .../java/org/traccar/database/IdentityManager.java |  6 +++-
 .../traccar/protocol/LaipacProtocolDecoder.java    | 39 +++++++---------------
 .../traccar/protocol/LaipacProtocolEncoder.java    | 22 +++++-------
 src/test/java/org/traccar/TestIdentityManager.java |  8 +++++
 6 files changed, 54 insertions(+), 52 deletions(-)

(limited to 'src/main/java/org/traccar')

diff --git a/src/main/java/org/traccar/BaseProtocolEncoder.java b/src/main/java/org/traccar/BaseProtocolEncoder.java
index d7625e4b8..6d96280f7 100644
--- a/src/main/java/org/traccar/BaseProtocolEncoder.java
+++ b/src/main/java/org/traccar/BaseProtocolEncoder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import io.netty.channel.ChannelPromise;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.traccar.model.Command;
-import org.traccar.model.Device;
 
 public abstract class BaseProtocolEncoder extends ChannelOutboundHandlerAdapter {
 
@@ -34,13 +33,9 @@ public abstract class BaseProtocolEncoder extends ChannelOutboundHandlerAdapter
 
     protected void initDevicePassword(Command command, String defaultPassword) {
         if (!command.getAttributes().containsKey(Command.KEY_DEVICE_PASSWORD)) {
-            Device device = Context.getIdentityManager().getById(command.getDeviceId());
-            String password = device.getString(Command.KEY_DEVICE_PASSWORD);
-            if (password != null) {
-                command.set(Command.KEY_DEVICE_PASSWORD, password);
-            } else {
-                command.set(Command.KEY_DEVICE_PASSWORD, defaultPassword);
-            }
+            String password = Context.getIdentityManager()
+                .getDevicePassword(command.getDeviceId(), defaultPassword);
+            command.set(Command.KEY_DEVICE_PASSWORD, password);
         }
     }
 
diff --git a/src/main/java/org/traccar/database/DeviceManager.java b/src/main/java/org/traccar/database/DeviceManager.java
index de4607d1f..d1a205b01 100644
--- a/src/main/java/org/traccar/database/DeviceManager.java
+++ b/src/main/java/org/traccar/database/DeviceManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org)
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.traccar.config.Config;
 import org.traccar.Context;
+import org.traccar.model.Command;
 import org.traccar.model.Device;
 import org.traccar.model.DeviceState;
 import org.traccar.model.DeviceAccumulators;
@@ -114,6 +115,21 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity
         return devicesByUniqueId.get(uniqueId);
     }
 
+    public String getDevicePassword(long id) {
+
+        return getById(id).getString(Command.KEY_DEVICE_PASSWORD);
+    }
+
+    public String getDevicePassword(long id, String defaultPassword) {
+
+        String password = getDevicePassword(id);
+        if (password != null) {
+            return password;
+        }
+
+        return defaultPassword;
+    }
+
     public Device getDeviceByPhone(String phone) {
         return devicesByPhone.get(phone);
     }
diff --git a/src/main/java/org/traccar/database/IdentityManager.java b/src/main/java/org/traccar/database/IdentityManager.java
index 6228a0f75..331706669 100644
--- a/src/main/java/org/traccar/database/IdentityManager.java
+++ b/src/main/java/org/traccar/database/IdentityManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,6 +26,10 @@ public interface IdentityManager {
 
     Device getByUniqueId(String uniqueId) throws Exception;
 
+    String getDevicePassword(long id);
+
+    String getDevicePassword(long id, String defaultPassword);
+
     Position getLastPosition(long deviceId);
 
     boolean isLatestPosition(Position position);
diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
index c028d78b6..381f497e4 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
@@ -26,8 +26,6 @@ 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;
 
@@ -123,21 +121,6 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
         return event;
     }
 
-    private String getDevicePassword(DeviceSession deviceSession) {
-
-        String devicePassword = DEFAULT_DEVICE_PASSWORD;
-
-        Device device = Context.getIdentityManager().getById(deviceSession.getDeviceId());
-        if (device != null) {
-            String password = device.getString(Command.KEY_DEVICE_PASSWORD);
-            if (password != null) {
-                devicePassword = password;
-            }
-        }
-
-        return devicePassword;
-    }
-
     private void sendEventResponse(
             String event, String devicePassword, Channel channel, SocketAddress remoteAddress) {
 
@@ -247,7 +230,8 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
             sendAcknowledge(status, event, checksum, channel, remoteAddress);
 
-            String devicePassword = getDevicePassword(deviceSession);
+            String devicePassword = Context.getIdentityManager()
+                .getDevicePassword(deviceSession.getDeviceId(), DEFAULT_DEVICE_PASSWORD);
             sendEventResponse(event, devicePassword, channel, remoteAddress);
         }
 
@@ -260,18 +244,19 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
         String sentence = (String) msg;
 
-        Parser parser = new Parser(PATTERN_ECHK, sentence);
-        if (parser.matches()) {
-            return handleEchk(sentence, parser, channel, remoteAddress);
-        }
-
-        parser = new Parser(PATTERN_AVRMC, sentence);
-        if (parser.matches()) {
-            return handleAvrmc(sentence, parser, channel, remoteAddress);
+        if (sentence.startsWith("$ECHK")) {
+            Parser parser = new Parser(PATTERN_ECHK, sentence);
+            if (parser.matches()) {
+                return handleEchk(sentence, parser, channel, remoteAddress);
+            }
+        } else if (sentence.startsWith("$AVRMC")) {
+            Parser parser = new Parser(PATTERN_AVRMC, sentence);
+            if (parser.matches()) {
+                return handleAvrmc(sentence, parser, channel, remoteAddress);
+            }
         }
 
         return null;
     }
 
-
 }
diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
index 6f540fa3e..9b99486f7 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
@@ -21,7 +21,11 @@ import org.traccar.helper.Checksum;
 
 public class LaipacProtocolEncoder extends StringProtocolEncoder {
 
-    public LaipacProtocolEncoder() {
+    @Override
+    protected String formatCommand(Command command, String format, String... keys) {
+        String sentence = super.formatCommand(command, "$" + format, keys);
+        sentence += Checksum.nmea(sentence) + "\r\n";
+        return sentence;
     }
 
     @Override
@@ -29,30 +33,20 @@ public class LaipacProtocolEncoder extends StringProtocolEncoder {
 
         initDevicePassword(command, LaipacProtocolDecoder.DEFAULT_DEVICE_PASSWORD);
 
-        String commandSentence = null;
-
         switch (command.getType()) {
             case Command.TYPE_CUSTOM:
-                commandSentence = formatCommand(command, "${%s}",
+                return formatCommand(command, "{%s}",
                     Command.KEY_DATA);
-                break;
             case Command.TYPE_POSITION_SINGLE:
-                commandSentence = formatCommand(command, "$AVREQ,{%s},1",
+                return formatCommand(command, "AVREQ,{%s},1",
                     Command.KEY_DEVICE_PASSWORD);
-                break;
             case Command.TYPE_REBOOT_DEVICE:
-                commandSentence = formatCommand(command, "$AVRESET,{%s},{%s}",
+                return formatCommand(command, "AVRESET,{%s},{%s}",
                     Command.KEY_UNIQUE_ID, Command.KEY_DEVICE_PASSWORD);
-                break;
             default:
                 break;
         }
 
-        if (commandSentence != null) {
-            commandSentence += Checksum.nmea(commandSentence) + "\r\n";
-            return commandSentence;
-        }
-
         return null;
     }
 
diff --git a/src/test/java/org/traccar/TestIdentityManager.java b/src/test/java/org/traccar/TestIdentityManager.java
index 0f7405dbd..1f7867875 100644
--- a/src/test/java/org/traccar/TestIdentityManager.java
+++ b/src/test/java/org/traccar/TestIdentityManager.java
@@ -29,6 +29,14 @@ public final class TestIdentityManager implements IdentityManager {
         return createDevice();
     }
 
+    public String getDevicePassword(long id) {
+        return null;
+    }
+
+    public String getDevicePassword(long id, String defaultPassword) {
+        return defaultPassword;
+    }
+
     @Override
     public Position getLastPosition(long deviceId) {
         return null;
-- 
cgit v1.2.3


From 0064dec35205fba7b1c8e6c1c0553407688055a8 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
Date: Wed, 10 Jul 2019 18:11:13 -0400
Subject: Changes after fourth review

---
 .../java/org/traccar/database/DeviceManager.java   |  7 +----
 .../java/org/traccar/database/IdentityManager.java |  2 --
 .../traccar/protocol/LaipacProtocolDecoder.java    | 31 +++++++++++++---------
 .../traccar/protocol/LaipacProtocolEncoder.java    |  3 +--
 4 files changed, 20 insertions(+), 23 deletions(-)

(limited to 'src/main/java/org/traccar')

diff --git a/src/main/java/org/traccar/database/DeviceManager.java b/src/main/java/org/traccar/database/DeviceManager.java
index d1a205b01..231be7b07 100644
--- a/src/main/java/org/traccar/database/DeviceManager.java
+++ b/src/main/java/org/traccar/database/DeviceManager.java
@@ -115,14 +115,9 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity
         return devicesByUniqueId.get(uniqueId);
     }
 
-    public String getDevicePassword(long id) {
-
-        return getById(id).getString(Command.KEY_DEVICE_PASSWORD);
-    }
-
     public String getDevicePassword(long id, String defaultPassword) {
 
-        String password = getDevicePassword(id);
+        String password = getById(id).getString(Command.KEY_DEVICE_PASSWORD);
         if (password != null) {
             return password;
         }
diff --git a/src/main/java/org/traccar/database/IdentityManager.java b/src/main/java/org/traccar/database/IdentityManager.java
index 331706669..add3e5a79 100644
--- a/src/main/java/org/traccar/database/IdentityManager.java
+++ b/src/main/java/org/traccar/database/IdentityManager.java
@@ -26,8 +26,6 @@ public interface IdentityManager {
 
     Device getByUniqueId(String uniqueId) throws Exception;
 
-    String getDevicePassword(long id);
-
     String getDevicePassword(long id, String defaultPassword);
 
     Position getLastPosition(long deviceId);
diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
index 381f497e4..3eecf58a3 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
@@ -167,8 +167,13 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
     }
 
-    private Object handleEchk(
-            String sentence, Parser parser, Channel channel, SocketAddress remoteAddress) {
+    private Object decodeEchk(
+            String sentence, Channel channel, SocketAddress remoteAddress) {
+
+        Parser parser = new Parser(PATTERN_ECHK, sentence);
+        if (!parser.matches()) {
+            return null;
+        }
 
         if (channel != null) {
             channel.writeAndFlush(new NetworkMessage(sentence + "\r\n", remoteAddress));
@@ -177,8 +182,13 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
         return null;
     }
 
-    protected Object handleAvrmc(
-            String sentence, Parser parser, Channel channel, SocketAddress remoteAddress) {
+    protected Object decodeAvrmc(
+            String sentence, Channel channel, SocketAddress remoteAddress) {
+
+        Parser parser = new Parser(PATTERN_AVRMC, sentence);
+        if (!parser.matches()) {
+            return null;
+        }
 
         DeviceSession deviceSession =
             getDeviceSession(channel, remoteAddress, parser.next());
@@ -245,15 +255,10 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
         String sentence = (String) msg;
 
         if (sentence.startsWith("$ECHK")) {
-            Parser parser = new Parser(PATTERN_ECHK, sentence);
-            if (parser.matches()) {
-                return handleEchk(sentence, parser, channel, remoteAddress);
-            }
-        } else if (sentence.startsWith("$AVRMC")) {
-            Parser parser = new Parser(PATTERN_AVRMC, sentence);
-            if (parser.matches()) {
-                return handleAvrmc(sentence, parser, channel, remoteAddress);
-            }
+            return decodeEchk(sentence, channel, remoteAddress);
+        }
+        if (sentence.startsWith("$AVRMC")) {
+            return decodeAvrmc(sentence, channel, remoteAddress);
         }
 
         return null;
diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
index 9b99486f7..dec76b83c 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolEncoder.java
@@ -44,10 +44,9 @@ public class LaipacProtocolEncoder extends StringProtocolEncoder {
                 return formatCommand(command, "AVRESET,{%s},{%s}",
                     Command.KEY_UNIQUE_ID, Command.KEY_DEVICE_PASSWORD);
             default:
-                break;
+                return null;
         }
 
-        return null;
     }
 
 }
-- 
cgit v1.2.3


From 9990000d67907cb41bdc18639d7dbe081c363f09 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
Date: Wed, 10 Jul 2019 21:29:27 -0400
Subject: Fixed not receiving ignition off event

---
 src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

(limited to 'src/main/java/org/traccar')

diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
index 3eecf58a3..767d70a5c 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
@@ -107,13 +107,17 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
     private String decodeEvent(String event, Position position) {
 
+        position.set(Position.KEY_IGNITION, false);
+
         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);
+                if ((inputStatusInt & 1) != 0) {
+                    position.set(Position.KEY_IGNITION, true);
+                }
                 return null;
             }
         }
-- 
cgit v1.2.3


From 35b6dc85f8c0c23ede5ece005bdc893c4320b8f3 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
Date: Wed, 10 Jul 2019 22:00:50 -0400
Subject: Fixed odometer value read as meters instead of kilometers

---
 src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/main/java/org/traccar')

diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
index 767d70a5c..c3b12aa80 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
@@ -223,7 +223,7 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
         position.set(Position.KEY_ALARM, decodeAlarm(event));
         position.set(Position.KEY_EVENT, decodeEvent(event, position));
         position.set(Position.KEY_BATTERY, Double.parseDouble(parser.next().replaceAll("\\.", "")) * 0.001);
-        position.set(Position.KEY_ODOMETER, parser.nextDouble());
+        position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000);
         position.set(Position.KEY_GPS, parser.nextInt());
         position.set(Position.PREFIX_ADC + 1, parser.nextDouble() * 0.001);
         position.set(Position.PREFIX_ADC + 2, parser.nextDouble() * 0.001);
-- 
cgit v1.2.3


From c00703abad85ae27dbd26ffffd6908813bcb5e49 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
Date: Thu, 11 Jul 2019 02:39:05 -0400
Subject: Changes after fifth review

---
 src/main/java/org/traccar/database/DeviceManager.java | 1 +
 src/test/java/org/traccar/TestIdentityManager.java    | 5 +----
 2 files changed, 2 insertions(+), 4 deletions(-)

(limited to 'src/main/java/org/traccar')

diff --git a/src/main/java/org/traccar/database/DeviceManager.java b/src/main/java/org/traccar/database/DeviceManager.java
index 231be7b07..62e6de080 100644
--- a/src/main/java/org/traccar/database/DeviceManager.java
+++ b/src/main/java/org/traccar/database/DeviceManager.java
@@ -115,6 +115,7 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity
         return devicesByUniqueId.get(uniqueId);
     }
 
+    @Override
     public String getDevicePassword(long id, String defaultPassword) {
 
         String password = getById(id).getString(Command.KEY_DEVICE_PASSWORD);
diff --git a/src/test/java/org/traccar/TestIdentityManager.java b/src/test/java/org/traccar/TestIdentityManager.java
index 1f7867875..98297c53c 100644
--- a/src/test/java/org/traccar/TestIdentityManager.java
+++ b/src/test/java/org/traccar/TestIdentityManager.java
@@ -29,10 +29,7 @@ public final class TestIdentityManager implements IdentityManager {
         return createDevice();
     }
 
-    public String getDevicePassword(long id) {
-        return null;
-    }
-
+    @Override
     public String getDevicePassword(long id, String defaultPassword) {
         return defaultPassword;
     }
-- 
cgit v1.2.3


From 01cb6d0be635976128fade70a36a9726539bc401 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
Date: Thu, 11 Jul 2019 23:47:54 -0400
Subject: Changes after sixth review

---
 .../org/traccar/protocol/LaipacProtocolDecoder.java     | 17 ++---------------
 1 file changed, 2 insertions(+), 15 deletions(-)

(limited to 'src/main/java/org/traccar')

diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
index c3b12aa80..e03ff672c 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
@@ -40,15 +40,7 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
     public static final String DEFAULT_DEVICE_PASSWORD = "00000000";
 
-    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()
+    private static final Pattern PATTERN = new PatternBuilder()
             .text("$AVRMC,")
             .expression("([^,]+),")              // identifier
             .number("(dd)(dd)(dd),")             // time (hhmmss)
@@ -174,11 +166,6 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
     private Object decodeEchk(
             String sentence, Channel channel, SocketAddress remoteAddress) {
 
-        Parser parser = new Parser(PATTERN_ECHK, sentence);
-        if (!parser.matches()) {
-            return null;
-        }
-
         if (channel != null) {
             channel.writeAndFlush(new NetworkMessage(sentence + "\r\n", remoteAddress));
         }
@@ -189,7 +176,7 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
     protected Object decodeAvrmc(
             String sentence, Channel channel, SocketAddress remoteAddress) {
 
-        Parser parser = new Parser(PATTERN_AVRMC, sentence);
+        Parser parser = new Parser(PATTERN, sentence);
         if (!parser.matches()) {
             return null;
         }
-- 
cgit v1.2.3


From 4fd816d4fcd35b1ab59857c3662d592a23372d94 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
Date: Fri, 12 Jul 2019 08:53:31 -0400
Subject: Changes after seventh review

---
 .../org/traccar/protocol/LaipacProtocolDecoder.java     | 17 ++++-------------
 1 file changed, 4 insertions(+), 13 deletions(-)

(limited to 'src/main/java/org/traccar')

diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
index e03ff672c..7c73ee7be 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
@@ -163,16 +163,6 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
     }
 
-    private Object decodeEchk(
-            String sentence, Channel channel, SocketAddress remoteAddress) {
-
-        if (channel != null) {
-            channel.writeAndFlush(new NetworkMessage(sentence + "\r\n", remoteAddress));
-        }
-
-        return null;
-    }
-
     protected Object decodeAvrmc(
             String sentence, Channel channel, SocketAddress remoteAddress) {
 
@@ -246,9 +236,10 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
         String sentence = (String) msg;
 
         if (sentence.startsWith("$ECHK")) {
-            return decodeEchk(sentence, channel, remoteAddress);
-        }
-        if (sentence.startsWith("$AVRMC")) {
+            if (channel != null) {
+                channel.writeAndFlush(new NetworkMessage(sentence + "\r\n", remoteAddress));
+            }
+        } else if (sentence.startsWith("$AVRMC")) {
             return decodeAvrmc(sentence, channel, remoteAddress);
         }
 
-- 
cgit v1.2.3


From 1da58124acf0ce8c820b78519d17c2a408aced24 Mon Sep 17 00:00:00 2001
From: Edward Valley <ed.valley@yandex.com>
Date: Fri, 12 Jul 2019 13:26:48 -0400
Subject: Avoid directly handling ignition attribute

---
 src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java | 5 -----
 1 file changed, 5 deletions(-)

(limited to 'src/main/java/org/traccar')

diff --git a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
index 7c73ee7be..2302015f5 100644
--- a/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
+++ b/src/main/java/org/traccar/protocol/LaipacProtocolDecoder.java
@@ -99,17 +99,12 @@ public class LaipacProtocolDecoder extends BaseProtocolDecoder {
 
     private String decodeEvent(String event, Position position) {
 
-        position.set(Position.KEY_IGNITION, false);
-
         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);
-                if ((inputStatusInt & 1) != 0) {
-                    position.set(Position.KEY_IGNITION, true);
-                }
                 return null;
             }
         }
-- 
cgit v1.2.3