aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/traccar/protocol/TotemProtocolDecoder.java')
-rw-r--r--src/main/java/org/traccar/protocol/TotemProtocolDecoder.java444
1 files changed, 444 insertions, 0 deletions
diff --git a/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java b/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java
new file mode 100644
index 000000000..cd7f684b8
--- /dev/null
+++ b/src/main/java/org/traccar/protocol/TotemProtocolDecoder.java
@@ -0,0 +1,444 @@
+/*
+ * 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.
+ * 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 io.netty.channel.Channel;
+import org.traccar.BaseProtocolDecoder;
+import org.traccar.DeviceSession;
+import org.traccar.NetworkMessage;
+import org.traccar.Protocol;
+import org.traccar.helper.BitUtil;
+import org.traccar.helper.Checksum;
+import org.traccar.helper.DateBuilder;
+import org.traccar.helper.Parser;
+import org.traccar.helper.PatternBuilder;
+import org.traccar.helper.UnitsConverter;
+import org.traccar.model.CellTower;
+import org.traccar.model.Network;
+import org.traccar.model.Position;
+
+import java.net.SocketAddress;
+import java.util.regex.Pattern;
+
+public class TotemProtocolDecoder extends BaseProtocolDecoder {
+
+ public TotemProtocolDecoder(Protocol protocol) {
+ super(protocol);
+ }
+
+ private static final Pattern PATTERN1 = new PatternBuilder()
+ .text("$$") // header
+ .number("xx") // length
+ .number("(d+)|") // imei
+ .expression("(..)") // alarm
+ .text("$GPRMC,")
+ .number("(dd)(dd)(dd).d+,") // time (hhmmss)
+ .expression("([AV]),") // validity
+ .number("(d+)(dd.d+),([NS]),") // latitude
+ .number("(d+)(dd.d+),([EW]),") // longitude
+ .number("(d+.?d*)?,") // speed
+ .number("(d+.?d*)?,") // course
+ .number("(dd)(dd)(dd)") // date (ddmmyy)
+ .expression("[^*]*").text("*")
+ .number("xx|") // checksum
+ .number("(d+.d+)|") // pdop
+ .number("(d+.d+)|") // hdop
+ .number("(d+.d+)|") // vdop
+ .number("(d+)|") // io status
+ .number("d+|") // battery time
+ .number("d") // charged
+ .number("(ddd)") // battery
+ .number("(dddd)|") // power
+ .number("(d+)|").optional() // adc
+ .number("x*(xxxx)") // lac
+ .number("(xxxx)|") // cid
+ .number("(d+)|") // temperature
+ .number("(d+.d+)|") // odometer
+ .number("d+|") // serial number
+ .any()
+ .number("xxxx") // checksum
+ .any()
+ .compile();
+
+ private static final Pattern PATTERN2 = new PatternBuilder()
+ .text("$$") // header
+ .number("xx") // length
+ .number("(d+)|") // imei
+ .expression("(..)") // alarm type
+ .number("(dd)(dd)(dd)") // date (ddmmyy)
+ .number("(dd)(dd)(dd)|") // time (hhmmss)
+ .expression("([AV])|") // validity
+ .number("(d+)(dd.d+)|") // latitude
+ .expression("([NS])|")
+ .number("(d+)(dd.d+)|") // longitude
+ .expression("([EW])|")
+ .number("(d+.d+)?|") // speed
+ .number("(d+)?|") // course
+ .number("(d+.d+)|") // hdop
+ .number("(d+)|") // io status
+ .number("d") // charged
+ .number("(dd)") // battery
+ .number("(dd)|") // external power
+ .number("(d+)|") // adc
+ .number("(xxxx)") // lac
+ .number("(xxxx)|") // cid
+ .number("(d+)|") // temperature
+ .number("(d+.d+)|") // odometer
+ .number("d+|") // serial number
+ .number("xxxx") // checksum
+ .any()
+ .compile();
+
+ private static final Pattern PATTERN3 = new PatternBuilder()
+ .text("$$") // header
+ .number("xx") // length
+ .number("(d+)|") // imei
+ .expression("(..)") // alarm type
+ .number("(dd)(dd)(dd)") // date (ddmmyy)
+ .number("(dd)(dd)(dd)") // time (hhmmss)
+ .number("(xxxx)") // io status
+ .expression("[01]") // charging
+ .number("(dd)") // battery
+ .number("(dd)") // external power
+ .number("(dddd)") // adc 1
+ .number("(dddd)") // adc 2
+ .number("(ddd)") // temperature 1
+ .number("(ddd)") // temperature 2
+ .number("(xxxx)") // lac
+ .number("(xxxx)") // cid
+ .expression("([AV])") // validity
+ .number("(dd)") // satellites
+ .number("(ddd)") // course
+ .number("(ddd)") // speed
+ .number("(dd.d)") // pdop
+ .number("(d{7})") // odometer
+ .number("(dd)(dd.dddd)([NS])") // latitude
+ .number("(ddd)(dd.dddd)([EW])") // longitude
+ .number("dddd") // serial number
+ .number("xxxx") // checksum
+ .any()
+ .compile();
+
+ private static final Pattern PATTERN4 = new PatternBuilder()
+ .text("$$") // header
+ .number("dddd") // length
+ .number("(xx)") // type
+ .number("(d+)|") // imei
+ .number("(x{8})") // status
+ .number("(dd)(dd)(dd)") // date (yymmdd)
+ .number("(dd)(dd)(dd)") // time (hhmmss)
+ .number("(dd)") // battery
+ .number("(dd)") // external power
+ .number("(dddd)") // adc 1
+ .groupBegin()
+ .groupBegin()
+ .number("(dddd)") // adc 2
+ .number("(dddd)") // adc 3
+ .number("(dddd)") // adc 4
+ .groupEnd("?")
+ .number("(dddd)") // temperature 1
+ .number("(dddd)?") // temperature 2
+ .groupEnd("?")
+ .number("(xxxx)") // lac
+ .number("(xxxx)") // cid
+ .groupBegin()
+ .number("(dd)") // mcc
+ .number("(ddd)") // mnc
+ .groupEnd("?")
+ .number("(dd)") // satellites
+ .number("(dd)") // gsm (rssi)
+ .number("(ddd)") // course
+ .number("(ddd)") // speed
+ .number("(dd.d)") // hdop
+ .number("(d{7})") // odometer
+ .number("(dd)(dd.dddd)([NS])") // latitude
+ .number("(ddd)(dd.dddd)([EW])") // longitude
+ .number("dddd") // serial number
+ .number("xx") // checksum
+ .any()
+ .compile();
+
+ private String decodeAlarm123(int value) {
+ switch (value) {
+ case 0x01:
+ return Position.ALARM_SOS;
+ case 0x10:
+ return Position.ALARM_LOW_BATTERY;
+ case 0x11:
+ return Position.ALARM_OVERSPEED;
+ case 0x30:
+ return Position.ALARM_PARKING;
+ case 0x42:
+ return Position.ALARM_GEOFENCE_EXIT;
+ case 0x43:
+ return Position.ALARM_GEOFENCE_ENTER;
+ default:
+ return null;
+ }
+ }
+
+ private String decodeAlarm4(int value) {
+ switch (value) {
+ case 0x01:
+ return Position.ALARM_SOS;
+ case 0x02:
+ return Position.ALARM_OVERSPEED;
+ case 0x04:
+ return Position.ALARM_GEOFENCE_EXIT;
+ case 0x05:
+ return Position.ALARM_GEOFENCE_ENTER;
+ case 0x40:
+ return Position.ALARM_SHOCK;
+ case 0x42:
+ return Position.ALARM_ACCELERATION;
+ case 0x43:
+ return Position.ALARM_BRAKING;
+ default:
+ return null;
+ }
+ }
+
+ private boolean decode12(Position position, Parser parser, Pattern pattern) {
+
+ if (parser.hasNext()) {
+ position.set(Position.KEY_ALARM, decodeAlarm123(Short.parseShort(parser.next(), 16)));
+ }
+ DateBuilder dateBuilder = new DateBuilder();
+ int year = 0, month = 0, day = 0;
+ if (pattern == PATTERN2) {
+ day = parser.nextInt(0);
+ month = parser.nextInt(0);
+ year = parser.nextInt(0);
+ }
+ dateBuilder.setTime(parser.nextInt(0), parser.nextInt(0), parser.nextInt(0));
+
+ position.setValid(parser.next().equals("A"));
+ position.setLatitude(parser.nextCoordinate());
+ position.setLongitude(parser.nextCoordinate());
+ position.setSpeed(parser.nextDouble(0));
+ position.setCourse(parser.nextDouble(0));
+
+ if (pattern == PATTERN1) {
+ day = parser.nextInt(0);
+ month = parser.nextInt(0);
+ year = parser.nextInt(0);
+ }
+ if (year == 0) {
+ return false; // ignore invalid data
+ }
+ dateBuilder.setDate(year, month, day);
+ position.setTime(dateBuilder.getDate());
+
+ if (pattern == PATTERN1) {
+ position.set(Position.KEY_PDOP, parser.nextDouble());
+ position.set(Position.KEY_HDOP, parser.nextDouble());
+ position.set(Position.KEY_VDOP, parser.nextDouble());
+ } else {
+ position.set(Position.KEY_HDOP, parser.nextDouble());
+ }
+
+ int io = parser.nextBinInt();
+ position.set(Position.KEY_STATUS, io);
+ if (pattern == PATTERN1) {
+ position.set(Position.KEY_ALARM, BitUtil.check(io, 0) ? Position.ALARM_SOS : null);
+ position.set(Position.PREFIX_IN + 3, BitUtil.check(io, 4));
+ position.set(Position.PREFIX_IN + 4, BitUtil.check(io, 5));
+ position.set(Position.PREFIX_IN + 1, BitUtil.check(io, 6));
+ position.set(Position.PREFIX_IN + 2, BitUtil.check(io, 7));
+ position.set(Position.PREFIX_OUT + 1, BitUtil.check(io, 8));
+ position.set(Position.PREFIX_OUT + 2, BitUtil.check(io, 9));
+ position.set(Position.KEY_BATTERY, parser.nextDouble(0) * 0.01);
+ } else {
+ position.set(Position.KEY_ANTENNA, BitUtil.check(io, 0));
+ position.set(Position.KEY_CHARGE, BitUtil.check(io, 1));
+ for (int i = 1; i <= 6; i++) {
+ position.set(Position.PREFIX_IN + i, BitUtil.check(io, 1 + i));
+ }
+ for (int i = 1; i <= 4; i++) {
+ position.set(Position.PREFIX_OUT + i, BitUtil.check(io, 7 + i));
+ }
+ position.set(Position.KEY_BATTERY, parser.nextDouble(0) * 0.1);
+ }
+
+ position.set(Position.KEY_POWER, parser.nextDouble(0));
+ position.set(Position.PREFIX_ADC + 1, parser.next());
+
+ int lac = parser.nextHexInt(0);
+ int cid = parser.nextHexInt(0);
+ if (lac != 0 && cid != 0) {
+ position.setNetwork(new Network(CellTower.fromLacCid(lac, cid)));
+ }
+
+ position.set(Position.PREFIX_TEMP + 1, parser.next());
+ position.set(Position.KEY_ODOMETER, parser.nextDouble(0) * 1000);
+
+ return true;
+ }
+
+ private boolean decode3(Position position, Parser parser) {
+
+ if (parser.hasNext()) {
+ position.set(Position.KEY_ALARM, decodeAlarm123(Short.parseShort(parser.next(), 16)));
+ }
+
+ position.setTime(parser.nextDateTime(Parser.DateTimeFormat.DMY_HMS));
+
+ position.set(Position.PREFIX_IO + 1, parser.next());
+ position.set(Position.KEY_BATTERY, parser.nextDouble(0) * 0.1);
+ position.set(Position.KEY_POWER, parser.nextDouble(0));
+ position.set(Position.PREFIX_ADC + 1, parser.next());
+ position.set(Position.PREFIX_ADC + 2, parser.next());
+ position.set(Position.PREFIX_TEMP + 1, parser.next());
+ position.set(Position.PREFIX_TEMP + 2, parser.next());
+
+ position.setNetwork(new Network(
+ CellTower.fromLacCid(parser.nextHexInt(0), parser.nextHexInt(0))));
+
+ position.setValid(parser.next().equals("A"));
+ position.set(Position.KEY_SATELLITES, parser.nextInt());
+ position.setCourse(parser.nextDouble(0));
+ position.setSpeed(parser.nextDouble(0));
+ position.set(Position.KEY_PDOP, parser.nextDouble());
+ position.set(Position.KEY_ODOMETER, parser.nextInt(0) * 1000);
+
+ position.setLatitude(parser.nextCoordinate());
+ position.setLongitude(parser.nextCoordinate());
+
+ return true;
+ }
+
+ private boolean decode4(Position position, Parser parser) {
+
+ long status = parser.nextHexLong();
+
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 1) ? Position.ALARM_SOS : null);
+ position.set(Position.KEY_IGNITION, BitUtil.check(status, 32 - 2));
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 3) ? Position.ALARM_OVERSPEED : null);
+ position.set(Position.KEY_CHARGE, BitUtil.check(status, 32 - 4));
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 5) ? Position.ALARM_GEOFENCE_EXIT : null);
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 6) ? Position.ALARM_GEOFENCE_ENTER : null);
+ position.set(Position.PREFIX_OUT + 1, BitUtil.check(status, 32 - 9));
+ position.set(Position.PREFIX_OUT + 2, BitUtil.check(status, 32 - 10));
+ position.set(Position.PREFIX_OUT + 3, BitUtil.check(status, 32 - 11));
+ position.set(Position.PREFIX_OUT + 4, BitUtil.check(status, 32 - 12));
+ position.set(Position.PREFIX_IN + 2, BitUtil.check(status, 32 - 13));
+ position.set(Position.PREFIX_IN + 3, BitUtil.check(status, 32 - 14));
+ position.set(Position.PREFIX_IN + 4, BitUtil.check(status, 32 - 15));
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 16) ? Position.ALARM_SHOCK : null);
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 18) ? Position.ALARM_LOW_BATTERY : null);
+ position.set(Position.KEY_ALARM, BitUtil.check(status, 32 - 22) ? Position.ALARM_JAMMING : null);
+
+
+ position.setTime(parser.nextDateTime());
+
+ position.set(Position.KEY_BATTERY, parser.nextDouble() * 0.1);
+ position.set(Position.KEY_POWER, parser.nextDouble());
+
+ position.set(Position.PREFIX_ADC + 1, parser.next());
+ position.set(Position.PREFIX_ADC + 2, parser.next());
+ position.set(Position.PREFIX_ADC + 3, parser.next());
+ position.set(Position.PREFIX_ADC + 4, parser.next());
+ position.set(Position.PREFIX_TEMP + 1, parser.next());
+
+ if (parser.hasNext()) {
+ position.set(Position.PREFIX_TEMP + 2, parser.next());
+ position.setValid(BitUtil.check(status, 32 - 20));
+ } else {
+ position.setValid(BitUtil.check(status, 32 - 18));
+ }
+
+ int lac = parser.nextHexInt();
+ int cid = parser.nextHexInt();
+ CellTower cellTower;
+ if (parser.hasNext(2)) {
+ int mnc = parser.nextInt();
+ int mcc = parser.nextInt();
+ cellTower = CellTower.from(mcc, mnc, lac, cid);
+ } else {
+ cellTower = CellTower.fromLacCid(lac, cid);
+ }
+ position.set(Position.KEY_SATELLITES, parser.nextInt());
+ cellTower.setSignalStrength(parser.nextInt());
+ position.setNetwork(new Network(cellTower));
+
+ position.setCourse(parser.nextDouble());
+ position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble()));
+ position.set(Position.KEY_HDOP, parser.nextDouble());
+ position.set(Position.KEY_ODOMETER, parser.nextInt() * 1000);
+
+ position.setLatitude(parser.nextCoordinate());
+ position.setLongitude(parser.nextCoordinate());
+
+ return true;
+ }
+
+ @Override
+ protected Object decode(
+ Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
+
+ String sentence = (String) msg;
+ Pattern pattern = PATTERN3;
+ if (sentence.charAt(2) == '0') {
+ pattern = PATTERN4;
+ } else if (sentence.contains("$GPRMC")) {
+ pattern = PATTERN1;
+ } else {
+ int index = sentence.indexOf('|');
+ if (index != -1 && sentence.indexOf('|', index + 1) != -1) {
+ pattern = PATTERN2;
+ }
+ }
+
+ Parser parser = new Parser(pattern, sentence);
+ if (!parser.matches()) {
+ return null;
+ }
+
+ Position position = new Position(getProtocolName());
+
+ if (pattern == PATTERN4) {
+ position.set(Position.KEY_ALARM, decodeAlarm4(parser.nextHexInt()));
+ }
+
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next());
+ if (deviceSession == null) {
+ return null;
+ }
+ position.setDeviceId(deviceSession.getDeviceId());
+
+ boolean result;
+ if (pattern == PATTERN1 || pattern == PATTERN2) {
+ result = decode12(position, parser, pattern);
+ } else if (pattern == PATTERN3) {
+ result = decode3(position, parser);
+ } else {
+ result = decode4(position, parser);
+ }
+
+ if (channel != null) {
+ if (pattern == PATTERN4) {
+ String response = "$$0014AA" + sentence.substring(sentence.length() - 6, sentence.length() - 2);
+ response += String.format("%02X", Checksum.xor(response)).toUpperCase();
+ channel.writeAndFlush(new NetworkMessage(response, remoteAddress));
+ } else {
+ channel.writeAndFlush(new NetworkMessage("ACK OK\r\n", remoteAddress));
+ }
+ }
+
+ return result ? position : null;
+ }
+
+}