package org.traccar.protocol;
import com.google.common.primitives.Doubles;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.BaseProtocolDecoder;
import org.traccar.Protocol;
import org.traccar.model.Position;
import org.traccar.session.ConnectionManager;
import org.traccar.session.DeviceSession;
import java.net.SocketAddress;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
public class G1rusProtocolDecoder extends BaseProtocolDecoder {
public G1rusProtocolDecoder(Protocol protocol) {
super(protocol);
}
private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class);
/* Constants */
private static final int G1RUS_HEAD_TAIL = 0xF8;
private static final int G1RUS_TYPE_HEARTBEAT = 0;
private static final int G1RUS_TYPE_BCD_MASK = 0b00111111;
private static final int G1RUS_TYPE_REGULAR = 1;
private static final int G1RUS_TYPE_SMS_FORWARD = 2;
private static final int G1RUS_TYPE_SERIAL_PASS_THROUGH = 3;
private static final int G1RUS_TYPE_MIXED = 4;
private static final int G1RUS_TYPE_EVENT_MASK = 0b01000000;
private static final int G1RUS_TYPE_NON_EVENT = 0;
private static final int G1RUS_TYPE_EVENT = 1;
private static final int G1RUS_TYPE_IMEI_MASK = 0b10000000;
private static final int G1RUS_TYPE_IMEI_LONG = 0;
private static final int G1RUS_TYPE_IMEI_SHORT = 1;
private static final int G1RUS_DATA_SYS_MASK = 0b00000001;
private static final int G1RUS_DATA_GPS_MASK = 0b00000010;
private static final int G1RUS_DATA_GSM_MASK = 0b00000100;
private static final int G1RUS_DATA_COT_MASK = 0b00001000;
private static final int G1RUS_DATA_ADC_MASK = 0b00010000;
private static final int G1RUS_DATA_DTT_MASK = 0b00100000;
/* Reserved */
private static final int G1RUS_DATA_ETD_MASK = 0b10000000;
private static final int G1RUS_GPS_SIGN_MASK = 0b00000001;
private static final int G1RUS_GPS_POS_MASK = 0b00000010;
private static final int G1RUS_GPS_SPD_MASK = 0b00000100;
private static final int G1RUS_GPS_AZTH_MASK = 0b00001000;
private static final int G1RUS_GPS_ALT_MASK = 0b00010000;
private static final int G1RUS_GPS_HDOP_MASK = 0b00100000;
private static final int G1RUS_GPS_VDOP_MASK = 0b01000000;
private static final int G1RUS_GPS_STAT_MASK = 0b10000000;
private void decodeSYSSub(ByteBuf buf) {
LOGGER.info("");
buf.skipBytes(1); /* Total length */
/* NOTE: assuming order:
* Device name -> Firmware version -> Hardware version.
* TODO: actually check it.
*/
/* Device name */
byte devNameLen = (byte) buf.readUnsignedByte();
byte[] devName = new byte[devNameLen & 0xF];
buf.readBytes(devName);
String devNameString = new String(devName);
LOGGER.info("Device name: " + devNameString);
/* Firmware version */
byte firmwareLen = (byte) buf.readUnsignedByte();
byte[] firmware = new byte[firmwareLen & 0xF];
buf.readBytes(firmware);
String firmwareString = new String(firmware);
LOGGER.info("Firmware version: " + firmwareString);
/* Hardware version */
byte hardwareLen = (byte) buf.readUnsignedByte();
byte[] hardware = new byte[hardwareLen & 0xF];
buf.readBytes(hardware);
String hardwareString = new String(hardware);
LOGGER.info("Hardware version: " + hardwareString);
LOGGER.info("");
}
private void decodeGPSSub(ByteBuf buf, Position position) {
LOGGER.info("");
buf.skipBytes(1); /* Total length */
short subMask = (short) buf.readUnsignedShort();
if ((subMask & G1RUS_GPS_SIGN_MASK) == G1RUS_GPS_SIGN_MASK) {
buf.skipBytes(1);
}
if ((subMask & G1RUS_GPS_POS_MASK) == G1RUS_GPS_POS_MASK) {
byte[] pos_buf = new byte[4];
buf.readBytes(pos_buf);
position.setLatitude((float) Ints.fromByteArray(pos_buf) / 1000000);
LOGGER.info("Latitude: " + position.getLatitude());
buf.readBytes(pos_buf);
position.setLongitude((float) Ints.fromByteArray(pos_buf) / 1000000);
LOGGER.info("Longitude: " + position.getLongitude());
}
if ((subMask & G1RUS_GPS_SPD_MASK) == G1RUS_GPS_SPD_MASK) {
position.setSpeed(buf.readUnsignedShort());
LOGGER.info("Speed: " + position.getSpeed());
}
if ((subMask & G1RUS_GPS_AZTH_MASK) == G1RUS_GPS_AZTH_MASK) {
position.setCourse(buf.readUnsignedShort());
LOGGER.info("Course: " + position.getCourse());
}
if ((subMask & G1RUS_GPS_ALT_MASK) == G1RUS_GPS_ALT_MASK) {
position.setAltitude(buf.readUnsignedShort());
LOGGER.info("Altitude: " + position.getAltitude());
}
if ((subMask & G1RUS_GPS_HDOP_MASK) == G1RUS_GPS_HDOP_MASK) {
buf.skipBytes(2);
}
if ((subMask & G1RUS_GPS_VDOP_MASK) == G1RUS_GPS_VDOP_MASK) {
buf.skipBytes(2);
}
LOGGER.info("");
}
private void decodeADCSub(ByteBuf buf, Position position) {
}
private Position decodeRegular(Channel channel, SocketAddress remoteAddress, ByteBuf buf, long imei, byte packetType) {
int timestamp_ = buf.readInt();
long timestamp = (946684800 + timestamp_) * 1000L; /* Convert received time to proper UNIX timestamp */
LOGGER.info("Date and time: " + new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date(timestamp)));
if ((packetType & G1RUS_TYPE_EVENT_MASK) != G1RUS_TYPE_NON_EVENT) {
buf.skipBytes(1); /* Event ID */
}
DeviceSession deviceSession = null;
Position position = null;
short dataUploadingMask = (short) buf.readUnsignedShort();
if ((dataUploadingMask & G1RUS_DATA_SYS_MASK) == G1RUS_DATA_SYS_MASK) {
decodeSYSSub(buf);
}
if ((dataUploadingMask & G1RUS_DATA_GPS_MASK) == G1RUS_DATA_GPS_MASK) {
deviceSession = getDeviceSession(channel, remoteAddress, String.valueOf(imei));
if (deviceSession == null) {
return null;
}
position = new Position(getProtocolName());
position.setDeviceId(deviceSession.getDeviceId());
position.setTime(new Date(timestamp));
decodeGPSSub(buf, position);
}
if ((dataUploadingMask & G1RUS_DATA_GSM_MASK) == G1RUS_DATA_GSM_MASK) {
buf.skipBytes(buf.readUnsignedByte());
}
if ((dataUploadingMask & G1RUS_DATA_COT_MASK) == G1RUS_DATA_COT_MASK) {
buf.skipBytes(buf.readUnsignedByte());
}
if ((dataUploadingMask & G1RUS_DATA_ADC_MASK) == G1RUS_DATA_ADC_MASK) {
/*if (deviceSession == null) {
return null;
}*/
buf.skipBytes(buf.readUnsignedByte());
// decodeADCSub(buf, position);
}
if ((dataUploadingMask & G1RUS_DATA_DTT_MASK) == G1RUS_DATA_DTT_MASK) {
buf.skipBytes(buf.readUnsignedByte());
}
if ((dataUploadingMask & G1RUS_DATA_ETD_MASK) == G1RUS_DATA_ETD_MASK) {
buf.skipBytes(buf.readUnsignedByte());
}
return position;
}
private Object decodeSMSForward(ByteBuf buf) {
return null;
}
private Object decodeSerialPassThrough(ByteBuf buf) {
return null;
}
private void printPacketType(byte packetType) {
LOGGER.info("Packet type: " + (packetType == G1RUS_TYPE_HEARTBEAT ? "HEARTBEAT" :
"[" + ((packetType & G1RUS_TYPE_IMEI_MASK) == G1RUS_TYPE_IMEI_LONG ? "IMEI_LONG" : "IMEI_SHORT") + "]" +
"[" + ((packetType & G1RUS_TYPE_EVENT_MASK) == G1RUS_TYPE_NON_EVENT ? "NON-EVENT" : "EVENT") + "]" +
"[" + ((packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_REGULAR ? "REGULAR" : (packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_SMS_FORWARD ? "SMS FORWARD" : (packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_SERIAL_PASS_THROUGH ? "PASS THROUGH" : (packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_MIXED ? "MIXED PACKED" : "RESERVED/INVALID") + "]"));
}
@Override
protected Object decode(Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
buf.skipBytes(1); /* Head */
LOGGER.info("Protocol version: " + buf.readUnsignedByte());
byte packetType = (byte) buf.readUnsignedByte();
printPacketType(packetType);
byte[] imei = new byte[8];
buf.readBytes(imei, 0, 7);
long imei_long = Longs.fromByteArray(imei);
LOGGER.info("IMEI: " + imei_long);
List positions = null;
if (packetType == G1RUS_TYPE_HEARTBEAT) {
return null;
} else if ((packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_REGULAR) {
positions = new LinkedList<>();
Position position = decodeRegular(channel, remoteAddress, buf, imei_long, packetType);
if (position != null) {
positions.add(position);
}
} else if ((packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_SMS_FORWARD) {
return decodeSMSForward(buf);
} else if ((packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_SERIAL_PASS_THROUGH) {
return decodeSerialPassThrough(buf);
} else if ((packetType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_MIXED) {
positions = new LinkedList<>();
while (buf.readableBytes() > 3) {
short subPacketLength = (short) buf.readUnsignedShort();
byte subPacketType = (byte) buf.readUnsignedByte();
printPacketType(subPacketType);
if ((subPacketType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_REGULAR) {
Position position = decodeRegular(channel, remoteAddress, buf, imei_long, packetType);
if (position != null) {
positions.add(position);
}
} else {
try {
buf.skipBytes(subPacketLength - 1);
} catch (Exception e) {
return positions;
}
}
/* else if ((subPacketType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_SMS_FORWARD) {
buf.skipBytes(subPacketLength - 1);
*//*decodeSMSForward(buf);*//*
} else if ((subPacketType & G1RUS_TYPE_BCD_MASK) == G1RUS_TYPE_SERIAL_PASS_THROUGH) {
buf.skipBytes(subPacketLength - 1);
*//*decodeSerialPassThrough(buf);*//*
}*/
}
} else {
LOGGER.error("Unknown packet type!");
}
buf.skipBytes(2); /* CRC */ /* TODO: actually check it */
// buf.skipBytes(1); /* Tail */
byte tail = (byte) buf.readUnsignedByte();
return positions;
}
}