/*
* Copyright 2013 Anton Tananaev (anton.tananaev@gmail.com)
*
* 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 java.util.Date;
import java.util.Properties;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.traccar.BaseProtocolDecoder;
import org.traccar.database.DataManager;
import org.traccar.helper.Log;
import org.traccar.model.ExtendedInfoFormatter;
import org.traccar.model.Position;
public class AplicomProtocolDecoder extends BaseProtocolDecoder {
public AplicomProtocolDecoder(DataManager dataManager, String protocol, Properties properties) {
super(dataManager, protocol, properties);
}
private static final long IMEI_BASE_TC65_V20 = 0x1437207000000L;
private static final long IMEI_BASE_TC65_V28 = 358244010000000L;
private static final long IMEI_BASE_TC65I_V11 = 0x14143B4000000L;
private static long imeiFromUnitId(long unitId) {
if(unitId == 0) {
return 0L;
} else if(unitId < 0x1000000) {
// Assume TC65i.
long imei = IMEI_BASE_TC65I_V11 + unitId;
if(validateImei(imei)) {
return imei;
}
// No? Maybe it's TC65 v2.8?
imei = IMEI_BASE_TC65_V28 + ((unitId + 0xA8180) & 0xFFFFFF);
if(validateImei(imei)) {
return imei;
}
// Still no match? How about TC65 v2.0?
imei = IMEI_BASE_TC65_V20 + unitId;
if(validateImei(imei)) {
return imei;
}
} else {
// Unit ID is full IMEI, just check it.
if(validateImei(unitId)) {
return unitId;
}
}
return unitId;
}
private static boolean validateImei(long imei2) {
int checksum = 0;
long remain = imei2;
// Iterate through all meaningful digits.
for (int i = 0; remain != 0; i++) {
// Extract the rightmost digit, that is, compute
// imei modulo 10 (or remainder of imei / 10).
int digit = (int) (remain % 10);
// For each even-positioned digit, calculate the value
// to be added to sum: Double the digit and then sum up
// the digits of the result.
// Example: 7 -> 2*7 = 14 -> 1 + 4 = 5
if (0 != (i % 2)) {
digit = digit * 2;
if (digit >= 10) {
digit = digit - 9;
}
}
checksum = checksum + digit;
// Remove the rightmost digit as it's already processed.
remain = remain / 10;
}
// The IMEI is valid if the calculated checksum is dividable by 10.
return 0 == (checksum % 10);
}
private static final int DEFAULT_SELECTOR = 0x0002FC;
@Override
protected Object decode(
ChannelHandlerContext ctx, Channel channel, Object msg)
throws Exception {
ChannelBuffer buf = (ChannelBuffer) msg;
buf.readUnsignedByte(); // marker
int version = buf.readUnsignedByte();
if ((version & 0x80) != 0) {
buf.skipBytes(4); // unit id high
}
String imei = String.valueOf(imeiFromUnitId(buf.readUnsignedMedium()));
buf.readUnsignedShort(); // length
// Selector
int selector = DEFAULT_SELECTOR; // default selector
if ((version & 0x40) != 0) {
selector = buf.readUnsignedMedium();
}
// Create new position
Position position = new Position();
ExtendedInfoFormatter extendedInfo = new ExtendedInfoFormatter(getProtocol());
try {
position.setDeviceId(getDataManager().getDeviceByImei(imei).getId());
} catch(Exception error) {
Log.warning("Unknown device - " + imei);
return null;
}
// Event
extendedInfo.set("event", buf.readUnsignedByte());
buf.readUnsignedByte();
// Validity
if ((selector & 0x0008) != 0) {
position.setValid((buf.readUnsignedByte() & 0x40) != 0);
} else {
return null; // no location data
}
// Time
if ((selector & 0x0004) != 0) {
buf.skipBytes(4); // snapshot time
}
// Location
if ((selector & 0x0008) != 0) {
position.setTime(new Date(buf.readUnsignedInt() * 1000));
position.setLatitude(buf.readInt() / 1000000.0);
position.setLongitude(buf.readInt() / 1000000.0);
extendedInfo.set("satellites", buf.readUnsignedByte());
}
// Speed and heading
if ((selector & 0x0010) != 0) {
position.setSpeed(buf.readUnsignedByte() * 0.539957);
buf.readUnsignedByte(); // maximum speed
position.setCourse(buf.readUnsignedByte() * 2.0);
} else {
position.setSpeed(0.0);
position.setCourse(0.0);
}
// Input
if ((selector & 0x0040) != 0) {
extendedInfo.set("input", buf.readUnsignedByte());
}
// ADC
if ((selector & 0x0020) != 0) {
extendedInfo.set("adc1", buf.readUnsignedShort());
extendedInfo.set("adc2", buf.readUnsignedShort());
extendedInfo.set("adc3", buf.readUnsignedShort());
extendedInfo.set("adc4", buf.readUnsignedShort());
}
// Power
if ((selector & 0x8000) != 0) {
extendedInfo.set("power", buf.readUnsignedShort() / 1000.0);
extendedInfo.set("battery", buf.readUnsignedShort());
}
// Pulse rate 1
if ((selector & 0x10000) != 0) {
buf.readUnsignedShort();
buf.readUnsignedInt();
}
// Pulse rate 2
if ((selector & 0x20000) != 0) {
buf.readUnsignedShort();
buf.readUnsignedInt();
}
// Trip 1
if ((selector & 0x0080) != 0) {
extendedInfo.set("trip1", buf.readUnsignedInt());
}
// Trip 2
if ((selector & 0x0100) != 0) {
extendedInfo.set("trip2", buf.readUnsignedInt());
}
// Output
if ((selector & 0x0040) != 0) {
extendedInfo.set("output", buf.readUnsignedByte());
}
// Button
if ((selector & 0x0200) != 0) {
buf.skipBytes(6);
}
// Keypad
if ((selector & 0x0400) != 0) {
buf.readUnsignedByte();
}
// Altitude
if ((selector & 0x0800) != 0) {
position.setAltitude((double) buf.readShort());
} else {
position.setAltitude(0.0);
}
position.setExtendedInfo(extendedInfo.toString());
return position;
}
}