/* * Copyright 2018 - 2020 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.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; 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.UnitsConverter; import org.traccar.model.Position; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.LinkedList; import java.util.List; public class EgtsProtocolDecoder extends BaseProtocolDecoder { public EgtsProtocolDecoder(Protocol protocol) { super(protocol); } private boolean useObjectIdAsDeviceId = true; public static final int PT_RESPONSE = 0; public static final int PT_APPDATA = 1; public static final int PT_SIGNED_APPDATA = 2; public static final int SERVICE_AUTH = 1; public static final int SERVICE_TELEDATA = 2; public static final int SERVICE_COMMANDS = 4; public static final int SERVICE_FIRMWARE = 9; public static final int SERVICE_ECALL = 10; public static final int MSG_RECORD_RESPONSE = 0; public static final int MSG_TERM_IDENTITY = 1; public static final int MSG_MODULE_DATA = 2; public static final int MSG_VEHICLE_DATA = 3; public static final int MSG_AUTH_PARAMS = 4; public static final int MSG_AUTH_INFO = 5; public static final int MSG_SERVICE_INFO = 6; public static final int MSG_RESULT_CODE = 7; public static final int MSG_POS_DATA = 16; public static final int MSG_EXT_POS_DATA = 17; public static final int MSG_AD_SENSORS_DATA = 18; public static final int MSG_COUNTERS_DATA = 19; public static final int MSG_STATE_DATA = 20; public static final int MSG_LOOPIN_DATA = 22; public static final int MSG_ABS_DIG_SENS_DATA = 23; public static final int MSG_ABS_AN_SENS_DATA = 24; public static final int MSG_ABS_CNTR_DATA = 25; public static final int MSG_ABS_LOOPIN_DATA = 26; public static final int MSG_LIQUID_LEVEL_SENSOR = 27; public static final int MSG_PASSENGERS_COUNTERS = 28; private int packetId; private void sendResponse( Channel channel, int packetType, int index, int serviceType, int type, ByteBuf content) { if (channel != null) { ByteBuf data = Unpooled.buffer(); data.writeByte(type); data.writeShortLE(content.readableBytes()); data.writeBytes(content); content.release(); ByteBuf record = Unpooled.buffer(); if (packetType == PT_RESPONSE) { record.writeShortLE(index); record.writeByte(0); // success } record.writeShortLE(data.readableBytes()); record.writeShortLE(0); record.writeByte(0); // flags (possibly 1 << 6) record.writeByte(serviceType); record.writeByte(serviceType); record.writeBytes(data); data.release(); int recordChecksum = Checksum.crc16(Checksum.CRC16_CCITT_FALSE, record.nioBuffer()); ByteBuf response = Unpooled.buffer(); response.writeByte(1); // protocol version response.writeByte(0); // security key id response.writeByte(0); // flags response.writeByte(5 + 2 + 2 + 2); // header length response.writeByte(0); // encoding response.writeShortLE(record.readableBytes()); response.writeShortLE(packetId++); response.writeByte(packetType); response.writeByte(Checksum.crc8(Checksum.CRC8_EGTS, response.nioBuffer())); response.writeBytes(record); record.release(); response.writeShortLE(recordChecksum); channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress())); } } @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; List<Position> positions = new LinkedList<>(); short headerLength = buf.getUnsignedByte(buf.readerIndex() + 3); int index = buf.getUnsignedShort(buf.readerIndex() + 5 + 2); short packetType = buf.getUnsignedByte(buf.readerIndex() + 5 + 2 + 2); buf.skipBytes(headerLength); if (packetType == PT_RESPONSE) { return null; } long objectId = 0L; while (buf.readableBytes() > 2) { int length = buf.readUnsignedShortLE(); int recordIndex = buf.readUnsignedShortLE(); int recordFlags = buf.readUnsignedByte(); if (BitUtil.check(recordFlags, 0)) { objectId = buf.readUnsignedIntLE(); } if (BitUtil.check(recordFlags, 1)) { buf.readUnsignedIntLE(); // event id } if (BitUtil.check(recordFlags, 2)) { buf.readUnsignedIntLE(); // time } int serviceType = buf.readUnsignedByte(); buf.readUnsignedByte(); // recipient service type int recordEnd = buf.readerIndex() + length; Position position = new Position(getProtocolName()); DeviceSession deviceSession = getDeviceSession(channel, remoteAddress); if (deviceSession != null) { position.setDeviceId(deviceSession.getDeviceId()); } ByteBuf response = Unpooled.buffer(); response.writeShortLE(recordIndex); response.writeByte(0); // success sendResponse(channel, PT_RESPONSE, index, serviceType, MSG_RECORD_RESPONSE, response); while (buf.readerIndex() < recordEnd) { int type = buf.readUnsignedByte(); int end = buf.readUnsignedShortLE() + buf.readerIndex(); if (type == MSG_TERM_IDENTITY) { useObjectIdAsDeviceId = false; buf.readUnsignedIntLE(); // object id int flags = buf.readUnsignedByte(); if (BitUtil.check(flags, 0)) { buf.readUnsignedShortLE(); // home dispatcher identifier } if (BitUtil.check(flags, 1)) { getDeviceSession( channel, remoteAddress, buf.readSlice(15).toString(StandardCharsets.US_ASCII).trim()); } if (BitUtil.check(flags, 2)) { getDeviceSession( channel, remoteAddress, buf.readSlice(16).toString(StandardCharsets.US_ASCII).trim()); } if (BitUtil.check(flags, 3)) { buf.skipBytes(3); // language identifier } if (BitUtil.check(flags, 5)) { buf.skipBytes(3); // network identifier } if (BitUtil.check(flags, 6)) { buf.readUnsignedShortLE(); // buffer size } if (BitUtil.check(flags, 7)) { getDeviceSession( channel, remoteAddress, buf.readSlice(15).toString(StandardCharsets.US_ASCII).trim()); } response = Unpooled.buffer(); response.writeByte(0); // success sendResponse(channel, PT_APPDATA, 0, serviceType, MSG_RESULT_CODE, response); } else if (type == MSG_POS_DATA) { position.setTime(new Date((buf.readUnsignedIntLE() + 1262304000) * 1000)); // since 2010-01-01 position.setLatitude(buf.readUnsignedIntLE() * 90.0 / 0xFFFFFFFFL); position.setLongitude(buf.readUnsignedIntLE() * 180.0 / 0xFFFFFFFFL); int flags = buf.readUnsignedByte(); position.setValid(BitUtil.check(flags, 0)); if (BitUtil.check(flags, 5)) { position.setLatitude(-position.getLatitude()); } if (BitUtil.check(flags, 6)) { position.setLongitude(-position.getLongitude()); } int speed = buf.readUnsignedShortLE(); position.setSpeed(UnitsConverter.knotsFromKph(BitUtil.to(speed, 14) * 0.1)); position.setCourse(buf.readUnsignedByte() + (BitUtil.check(speed, 15) ? 0x100 : 0)); position.set(Position.KEY_ODOMETER, buf.readUnsignedMediumLE() * 100); position.set(Position.KEY_INPUT, buf.readUnsignedByte()); position.set(Position.KEY_EVENT, buf.readUnsignedByte()); if (BitUtil.check(flags, 7)) { position.setAltitude(buf.readMediumLE()); } } else if (type == MSG_EXT_POS_DATA) { int flags = buf.readUnsignedByte(); if (BitUtil.check(flags, 0)) { position.set(Position.KEY_VDOP, buf.readUnsignedShortLE()); } if (BitUtil.check(flags, 1)) { position.set(Position.KEY_HDOP, buf.readUnsignedShortLE()); } if (BitUtil.check(flags, 2)) { position.set(Position.KEY_PDOP, buf.readUnsignedShortLE()); } if (BitUtil.check(flags, 3)) { position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); } } else if (type == MSG_AD_SENSORS_DATA) { int inputMask = buf.readUnsignedByte(); position.set(Position.KEY_OUTPUT, buf.readUnsignedByte()); int adcMask = buf.readUnsignedByte(); for (int i = 0; i < 8; i++) { if (BitUtil.check(inputMask, i)) { buf.readUnsignedByte(); // input } } for (int i = 0; i < 8; i++) { if (BitUtil.check(adcMask, i)) { position.set(Position.PREFIX_ADC + (i + 1), buf.readUnsignedMediumLE()); } } } else if (type == MSG_LIQUID_LEVEL_SENSOR) { int flags = buf.readUnsignedByte(); buf.readUnsignedShortLE(); // address if (BitUtil.check(flags, 3)) { position.set("liquidRaw", ByteBufUtil.hexDump(buf.readSlice(end - buf.readerIndex()))); } else { position.set("liquid", buf.readUnsignedIntLE()); } } buf.readerIndex(end); } if (serviceType == SERVICE_TELEDATA && position.getValid()) { if (useObjectIdAsDeviceId && objectId != 0L) { deviceSession = getDeviceSession(channel, remoteAddress, true, String.valueOf(objectId)); if (deviceSession != null) { position.setDeviceId(deviceSession.getDeviceId()); } } if (deviceSession != null) { positions.add(position); } } } return positions.isEmpty() ? null : positions; } }