/*
* Copyright 2012 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.nio.charset.Charset;
import java.util.Calendar;
import java.util.TimeZone;
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.Position;
/**
* T55 tracker protocol decoder
*/
public class SkypatrolProtocolDecoder extends BaseProtocolDecoder {
/**
* Initialize
*/
public SkypatrolProtocolDecoder(ServerManager serverManager) {
super(serverManager);
}
private static boolean checkBit(long mask, int bit) {
long checkMask = 1 << bit;
return (mask & checkMask) == checkMask;
}
private static double convertCoordinate(long coordinate) {
int sign = 1;
if (coordinate > 0x7fffffffl) {
sign = -1;
coordinate = 0xffffffffl - coordinate;
}
double degrees = coordinate / 1000000;
degrees += (coordinate % 1000000) / 600000.0;
return sign * degrees;
}
/**
* Decode message
*/
@Override
protected Object decode(
ChannelHandlerContext ctx, Channel channel, Object msg)
throws Exception {
ChannelBuffer buf = (ChannelBuffer) msg;
// Read header
int apiNumber = buf.readUnsignedShort();
int commandType = buf.readUnsignedByte();
int messageType = buf.getUnsignedByte(buf.readerIndex()) >> 4;
boolean needAck = (buf.readUnsignedByte() & 0xf) == 1;
long mask = 0;
if (buf.readUnsignedByte() == 4) {
mask = buf.readUnsignedInt();
}
// Binary position report
if (apiNumber == 5 &&
commandType == 2 &&
messageType == 1 &&
checkBit(mask, 0)) {
// Create new position
Position position = new Position();
StringBuilder extendedInfo = new StringBuilder("skypatrol");
// Status code
if (checkBit(mask, 1)) {
extendedInfo.append("");
extendedInfo.append(buf.readUnsignedInt());
extendedInfo.append("");
}
// Device id
String id = null;
if (checkBit(mask, 23)) {
id = buf.toString(buf.readerIndex(), 8, Charset.defaultCharset()).trim();
buf.skipBytes(8);
} else if (checkBit(mask, 2)) {
id = buf.toString(buf.readerIndex(), 22, Charset.defaultCharset()).trim();
buf.skipBytes(22);
} else {
Log.warning("No device id field");
return null;
}
try {
position.setDeviceId(getDataManager().getDeviceByImei(id).getId());
} catch(Exception error) {
Log.warning("Unknown device - " + id);
return null;
}
// IO data
if (checkBit(mask, 3)) {
buf.readUnsignedShort();
}
// ADC 1
if (checkBit(mask, 4)) {
buf.readUnsignedShort();
}
// ADC 2
if (checkBit(mask, 5)) {
buf.readUnsignedShort();
}
// Function category
if (checkBit(mask, 7)) {
buf.readUnsignedByte();
}
Calendar time = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
time.clear();
// Date
if (checkBit(mask, 8)) {
time.set(Calendar.DAY_OF_MONTH, buf.readUnsignedByte());
time.set(Calendar.MONTH, buf.readUnsignedByte() - 1);
time.set(Calendar.YEAR, 2000 + buf.readUnsignedByte());
}
// GPS status
if (checkBit(mask, 9)) {
position.setValid(buf.readUnsignedByte() == 1);
}
// Latitude
if (checkBit(mask, 10)) {
position.setLatitude(convertCoordinate(buf.readUnsignedInt()));
}
// Longitude
if (checkBit(mask, 11)) {
position.setLongitude(convertCoordinate(buf.readUnsignedInt()));
}
// Speed
if (checkBit(mask, 12)) {
position.setSpeed(buf.readUnsignedShort() / 10.0);
}
// Course
if (checkBit(mask, 13)) {
position.setCourse(buf.readUnsignedShort() / 10.0);
}
// Time
if (checkBit(mask, 14)) {
time.set(Calendar.HOUR, buf.readUnsignedByte());
time.set(Calendar.MINUTE, buf.readUnsignedByte());
time.set(Calendar.SECOND, buf.readUnsignedByte());
}
position.setTime(time.getTime());
// Altitude
if (checkBit(mask, 15)) {
buf.skipBytes(3);
}
// Satellites
if (checkBit(mask, 16)) {
extendedInfo.append("");
extendedInfo.append(buf.readUnsignedByte());
extendedInfo.append("");
}
// Battery percentage
if (checkBit(mask, 17)) {
buf.readUnsignedShort();
}
// Trip milage
if (checkBit(mask, 20)) {
extendedInfo.append("");
extendedInfo.append(buf.readUnsignedInt());
extendedInfo.append("");
}
// Milage
if (checkBit(mask, 21)) {
extendedInfo.append("");
extendedInfo.append(buf.readUnsignedInt());
extendedInfo.append("");
}
// Time of message generation
if (checkBit(mask, 22)) {
buf.skipBytes(6);
}
// Battery level
if (checkBit(mask, 24)) {
position.setPower(buf.readUnsignedShort() / 1000.0);
}
// GPS overspeed
if (checkBit(mask, 25)) {
buf.skipBytes(18);
}
// Cell information
if (checkBit(mask, 26)) {
buf.skipBytes(54);
}
// Sequence number
if (checkBit(mask, 28)) {
position.setId((long) buf.readUnsignedShort());
}
// Extended info
position.setExtendedInfo(extendedInfo.toString());
return position;
}
return null;
}
}