/* * 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 org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; import org.traccar.BaseProtocolDecoder; import org.traccar.ServerManager; import org.traccar.helper.Log; import org.traccar.model.ExtendedInfoFormatter; import org.traccar.model.Position; public class AplicomProtocolDecoder extends BaseProtocolDecoder { public AplicomProtocolDecoder(ServerManager serverManager) { super(serverManager); } 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("aplicom"); 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; } }