/*
* Copyright 2013 - 2014 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.Calendar;
import java.util.Properties;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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 GlobalSatProtocolDecoder extends BaseProtocolDecoder {
// Default values
private String format0 = "TSPRXAB27GHKLMnaicz*U!";
private String format1 = "SARY*U!";
public GlobalSatProtocolDecoder(DataManager dataManager, String protocol, Properties properties) {
super(dataManager, protocol, properties);
if (properties != null) {
if (properties.containsKey(protocol + ".format0")) {
format0 = properties.getProperty(protocol + ".format0");
}
if (properties.containsKey(protocol + ".format1")) {
format1 = properties.getProperty(protocol + ".format1");
}
}
}
private Position decodeOriginal(Channel channel, String sentence) {
// Send acknowledgement
if (channel != null) {
channel.write("ACK\r");
}
// Message type
String format;
if (sentence.startsWith("GSr")) {
format = format0;
} else if (sentence.startsWith("GSh")) {
format = format1;
} else {
return null;
}
// Check that message contains required parameters
if (!format.contains("B") || !format.contains("S") ||
!(format.contains("1") || format.contains("2") || format.contains("3")) ||
!(format.contains("6") || format.contains("7") || format.contains("8"))) {
return null;
}
// Tokenise
if (format.contains("*")) {
format = format.substring(0, format.indexOf('*'));
sentence = sentence.substring(0, sentence.indexOf('*'));
}
String[] values = sentence.split(",");
// Parse data
Position position = new Position();
ExtendedInfoFormatter extendedInfo = new ExtendedInfoFormatter(getProtocol());
for (int formatIndex = 0, valueIndex = 1; formatIndex < format.length() && valueIndex < values.length; formatIndex++) {
String value = values[valueIndex];
switch(format.charAt(formatIndex)) {
case 'S':
try {
position.setDeviceId(getDataManager().getDeviceByImei(value).getId());
} catch(Exception error) {
Log.warning("Unknown device - " + value);
return null;
}
break;
case 'A':
if (value.isEmpty()) {
position.setValid(false);
} else {
position.setValid(Integer.valueOf(value) != 1);
}
break;
case 'B':
Calendar time = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
time.clear();
time.set(Calendar.DAY_OF_MONTH, Integer.valueOf(value.substring(0, 2)));
time.set(Calendar.MONTH, Integer.valueOf(value.substring(2, 4)) - 1);
time.set(Calendar.YEAR, 2000 + Integer.valueOf(value.substring(4)));
value = values[++valueIndex];
time.set(Calendar.HOUR_OF_DAY, Integer.valueOf(value.substring(0, 2)));
time.set(Calendar.MINUTE, Integer.valueOf(value.substring(2, 4)));
time.set(Calendar.SECOND, Integer.valueOf(value.substring(4)));
position.setTime(time.getTime());
break;
case 'C':
valueIndex += 1;
break;
case '1':
double longitude = Double.valueOf(value.substring(1));
if (value.charAt(0) == 'W') longitude = -longitude;
position.setLongitude(longitude);
break;
case '2':
longitude = Double.valueOf(value.substring(4)) / 60;
longitude += Integer.valueOf(value.substring(1, 4));
if (value.charAt(0) == 'W') longitude = -longitude;
position.setLongitude(longitude);
break;
case '3':
position.setLongitude(Double.valueOf(value) * 0.000001);
break;
case '6':
double latitude = Double.valueOf(value.substring(1));
if (value.charAt(0) == 'S') latitude = -latitude;
position.setLatitude(latitude);
break;
case '7':
latitude = Double.valueOf(value.substring(3)) / 60;
latitude += Integer.valueOf(value.substring(1, 3));
if (value.charAt(0) == 'S') latitude = -latitude;
position.setLatitude(latitude);
break;
case '8':
position.setLatitude(Double.valueOf(value) * 0.000001);
break;
case 'G':
position.setAltitude(Double.valueOf(value));
break;
case 'H':
position.setSpeed(Double.valueOf(value));
break;
case 'I':
position.setSpeed(Double.valueOf(value) * 0.539957);
break;
case 'J':
position.setSpeed(Double.valueOf(value) * 0.868976);
break;
case 'K':
position.setCourse(Double.valueOf(value));
break;
case 'N':
extendedInfo.set("battery", Double.valueOf(value));
break;
default:
// Unsupported
break;
}
valueIndex += 1;
}
position.setExtendedInfo(extendedInfo.toString());
return position;
}
private static final Pattern pattern = Pattern.compile(
"\\$" +
"(\\d+)," + // IMEI
"\\d+," + // mode
"(\\d)," + // Fix
"(\\d{2})(\\d{2})(\\d{2})," + // Date (DDMMYY)
"(\\d{2})(\\d{2})(\\d{2})," + // Time (HHMMSS)
"([EW])" +
"(\\d{3})(\\d{2}\\.\\d+)," + // Longitude (DDDMM.MMMM)
"([NS])" +
"(\\d{2})(\\d{2}\\.\\d+)," + // Latitude (DDMM.MMMM)
"(\\d+\\.?\\d*)," + // Altitude
"(\\d+\\.?\\d*)," + // Speed
"(\\d+\\.?\\d*)," + // Course
"(\\d+)," + // Satellites
"(\\d+\\.?\\d*)"); // HDOP
private Position decodeAlternative(Channel channel, String sentence) {
// Parse message
Matcher parser = pattern.matcher(sentence);
if (!parser.matches()) {
return null;
}
// Create new position
Position position = new Position();
ExtendedInfoFormatter extendedInfo = new ExtendedInfoFormatter(getProtocol());
Integer index = 1;
// Identification
String imei = parser.group(index++);
try {
position.setDeviceId(getDataManager().getDeviceByImei(imei).getId());
} catch(Exception error) {
Log.warning("Unknown device - " + imei);
return null;
}
// Validity
position.setValid(parser.group(index++).compareTo("1") != 0);
// Time
Calendar time = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
time.clear();
time.set(Calendar.DAY_OF_MONTH, Integer.valueOf(parser.group(index++)));
time.set(Calendar.MONTH, Integer.valueOf(parser.group(index++)) - 1);
time.set(Calendar.YEAR, 2000 + Integer.valueOf(parser.group(index++)));
time.set(Calendar.HOUR_OF_DAY, Integer.valueOf(parser.group(index++)));
time.set(Calendar.MINUTE, Integer.valueOf(parser.group(index++)));
time.set(Calendar.SECOND, Integer.valueOf(parser.group(index++)));
position.setTime(time.getTime());
// Longitude
String hemisphere = parser.group(index++);
Double longitude = Double.valueOf(parser.group(index++));
longitude += Double.valueOf(parser.group(index++)) / 60;
if (hemisphere.compareTo("W") == 0) longitude = -longitude;
position.setLongitude(longitude);
// Latitude
hemisphere = parser.group(index++);
Double latitude = Double.valueOf(parser.group(index++));
latitude += Double.valueOf(parser.group(index++)) / 60;
if (hemisphere.compareTo("S") == 0) latitude = -latitude;
position.setLatitude(latitude);
// Altitude
position.setAltitude(Double.valueOf(parser.group(index++)));
// Speed
position.setSpeed(Double.valueOf(parser.group(index++)));
// Course
position.setCourse(Double.valueOf(parser.group(index++)));
// Satellites
extendedInfo.set("satellites", Integer.valueOf(parser.group(index++)));
// HDOP
extendedInfo.set("hdop", parser.group(index++));
position.setExtendedInfo(extendedInfo.toString());
return position;
}
@Override
protected Object decode(
ChannelHandlerContext ctx, Channel channel, Object msg)
throws Exception {
String sentence = (String) msg;
if (sentence.startsWith("GS")) {
return decodeOriginal(channel, sentence);
} else if (sentence.startsWith("$")) {
return decodeAlternative(channel, sentence);
}
return null;
}
}