// Copyright 2012 (C) Matthew Brejza
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
package ukhas;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import org.msgpack.MessagePack;
import org.msgpack.MessageTypeException;
import org.msgpack.type.Value;
import org.msgpack.unpacker.Unpacker;
import org.apache.commons.codec.binary.Base64;
public class Telemetry_string implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 0x5901fa8c0e38abb1L;
public String callsign = "";
public Date time = null;
public int packetID=0;
public Gps_coordinate coords;
private String raw_string = "";
public String[] user_fields;
public double frequency = 0;
public boolean checksum_valid;
public Map<String,String> habitat_metadata = null;
private double[] extraFields;
public String doc_time_created;
public String getSentence()
{
return raw_string + "\n";
}
public Telemetry_string(byte[] telem, TelemetryConfig tc) {
parse_telem(telem, System.currentTimeMillis() / 1000L, tc);
checksum_valid = true;
//get time created
Date time = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
String t = dateFormat.format(time);
t = t.substring(0, t.length()-2) + ":" + t.substring(t.length()-2, t.length());
doc_time_created = t;
}
public Telemetry_string(String telem, TelemetryConfig tc) {
parse_telem(telem, System.currentTimeMillis() / 1000L, tc);
checksum_valid = check_checksum(telem,0);
//get time created
Date time = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
String t = dateFormat.format(time);
t = t.substring(0, t.length()-2) + ":" + t.substring(t.length()-2, t.length());
doc_time_created = t;
}
public Telemetry_string(String telem, long timerx, TelemetryConfig tc) {
parse_telem(telem,timerx, tc);
checksum_valid = check_checksum(telem,0);
//get time created
Date time = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
String t = dateFormat.format(time);
t = t.substring(0, t.length()-2) + ":" + t.substring(t.length()-2, t.length());
doc_time_created = t;
}
public Telemetry_string(String telem, boolean _checksum_valid, TelemetryConfig tc) {
parse_telem(telem, System.currentTimeMillis() / 1000L, tc);
checksum_valid = _checksum_valid;
//get time created
Date time = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
String t = dateFormat.format(time);
t = t.substring(0, t.length()-2) + ":" + t.substring(t.length()-2, t.length());
doc_time_created = t;
}
private void parse_telem(byte[] str, long timerx, TelemetryConfig tc)
{
MessagePack msgpack = new MessagePack();
ByteArrayInputStream in = new ByteArrayInputStream(str);
Unpacker unpacker = msgpack.createUnpacker(in);
byte[] base64enc = Base64.encodeBase64(str);
Map<Integer, String> telemmap = new HashMap<Integer, String>();
try {
while(in.available()>0) //TODO: handle multiple messages
{
Value v = unpacker.readValue();
if (v.isMapValue())
{
//raw_string = v.toString();
boolean valid_lock = true;
for (Value s : v.asMapValue().keySet())
{
Value item = v.asMapValue().get(s);
if (s.isIntegerValue()){
switch (s.asIntegerValue().intValue())
{
case 0: //CALLSIGN
callsign = item.toString();
if (callsign.startsWith("\"")) //TODO: this is horrible
callsign = callsign.substring(1);
if (callsign.endsWith("\""))
callsign = callsign.substring(0, callsign.length() -1);
telemmap.put(new Integer(0), callsign);
//callsign = callsign + "_b";
//raw_string = raw_string + callsign + ",";
break;
case 2: //TIME
if (item.isIntegerValue())
{
//Date time_in = new Date(item.asIntegerValue().getInt()*1000);
//setTime(time_in,timerx);
//SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
int time_in = item.asIntegerValue().getInt();
int hours = (int)Math.floor(time_in/(60*60));
time_in = time_in - (hours*60*60);
int mins = (int)Math.floor(time_in/60);
int secs = time_in-mins*60;
String timestr = String.format("%02d:%02d:%02d",hours,mins,secs);
SimpleDateFormat ft = new SimpleDateFormat ("HH:mm:ss");
ft.setTimeZone(TimeZone.getTimeZone("UTC"));
Date time_;
try {
time_ = ft.parse(timestr);
setTime(time_,timerx);
} catch (ParseException e) {
System.out.println("Error parsing - " + e.toString());
}
//raw_string = raw_string + String.format("%02d:%02d:%02d",hours,mins,secs) + ",";
telemmap.put(new Integer(2), timestr);
}
break;
case 1: //PACKET COUNT
if (item.isIntegerValue()){
packetID = item.asIntegerValue().getInt();
//raw_string = raw_string + packetID + ",";
telemmap.put(new Integer(1), Integer.toString(packetID));
}
break;
case 3: //POSITION
if (item.isArrayValue())
{
String pos = "";
if (item.asArrayValue().size() >= 2)
{
if (item.asArrayValue().get(0).isIntegerValue()
&& item.asArrayValue().get(1).isIntegerValue())
{
coords = new Gps_coordinate(item.asArrayValue().get(0).asIntegerValue().intValue(),
item.asArrayValue().get(1).asIntegerValue().intValue());
pos = Integer.toString(item.asArrayValue().get(0).asIntegerValue().intValue()) + ","
+ Integer.toString(item.asArrayValue().get(1).asIntegerValue().intValue());
}
if (item.asArrayValue().size() >= 3){
if (item.asArrayValue().get(2).isIntegerValue())
{
coords.Set_altitude(item.asArrayValue().get(2).asIntegerValue().getInt());
pos = pos + "," + Integer.toString(item.asArrayValue().get(2).asIntegerValue().intValue());
}
}
//raw_string = raw_string + coords.latitude + "," + coords.longitude + "," + coords.altitude + ",";
}
telemmap.put(new Integer(3), pos);
}
break;
//case 4: //SATS
//
// break;
//case 5:
//
// break;
//case 6:
//
// break;
default:
if (item.isIntegerValue()){
//raw_string = raw_string + packetID + ",";
telemmap.put(new Integer(s.asIntegerValue().intValue()),
Integer.toString(item.asIntegerValue().getInt()));
}
else if (item.isArrayValue()){
String s_out = "";
Integer first = 1;
for (Value item2 : item.asArrayValue()){
if (item2.isIntegerValue()){
if (first>0)
s_out = s_out + item2.toString();
else
s_out = s_out + "," + item2.toString();
first = 0;
}
}
if (first == 0)
telemmap.put(new Integer(s.asIntegerValue().intValue()),s_out);
}
break;
}
}
}
if (coords != null)
coords.latlong_valid = valid_lock;
break;
}
}
//if (raw_string.length() > 0)
if(telemmap.size()>0)
{
for (int i = 0; i <128; i++){ //TODO: fix this
if (telemmap.containsKey(i)){
raw_string = raw_string + telemmap.get(i) + ',';
}
}
if (raw_string.length() > 0){
raw_string = raw_string + new String(base64enc) + "*";;
//raw_string = raw_string.substring(0,raw_string.length()-1) + "*";
int crc = calculate_checksum(raw_string,0);
raw_string = raw_string + String.format("%04x", crc);
}
}
//Map<Integer, String> dstMap = unpacker.read(mapTmpl);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (MessageTypeException e){
e.printStackTrace();
}
}
public static byte[][] gen_telem_mask(byte[] input)
{
//generates an expected bit pattern and mask based on callsign and markers
byte[][] output = new byte[2][input.length];
if (input.length < 3)
return null;
if ( (input[0] & 0xF0) == 0x80 &&
(input[1] & 0xFF) == 0x00 &&
(input[2] & 0xE0) == 0xA0 )
{
int calllen = input[2] & 0x1F;
output[0][0] = input[0];
output[0][1] = input[1];
output[0][2] = input[2];
output[1][0] = (byte) 0xFF;
output[1][1] = (byte) 0xFF;
output[1][2] = (byte) 0xFF;
if (input.length > 4+calllen){
for (int i = 0; i < calllen + 1; i++)
{
output[0][i+3] = input[3+i];
output[1][i+3] = (byte) 0xFF;
}
}
}
//i began to do something that would work for pretty much everything but settled on one that just does the callsign
/*
boolean incall = false;
int infield_len = 0;
int infield_count = 0;
boolean inmap = false;
boolean map_val = false;
for (int i = 0; i < input.length; i++)
{
if (infield_count == 0)
{
if ((input[i] & 0xF0)== 0x80) //map <16 elements
{
output[0][i] = input[i];
output[1][i] = (byte) 0xFF;
inmap = true;
}
else if ((input[i] & 0xFF)== 0xde && input.length > i+2) //map <65536 elements
{
output[0][i] = input[i];
output[1][i] = (byte) 0xFF;
output[0][i+1] = input[i];
output[1][i+1] = (byte) 0xFF;
output[0][i+2] = input[i];
output[1][i+2] = (byte) 0xFF;
inmap = true;
i += 2;
}
else if ()
}
}
*/
return output;
}
private void parse_telem(String str, long timerx, TelemetryConfig tc)
{
int start = str.lastIndexOf('$');
if (start < 0)
start = 0;
else
start++;
raw_string = str.substring(start, str.length()).trim();
String[] fields;
String[] cksplit = raw_string.split("\\*",0); //remove checksum
if (cksplit.length>0)
fields = cksplit[0].split("\\,",-1);
else
fields = raw_string.split("\\,",-1);
if (fields.length > 6)
{
user_fields = new String[fields.length-6];
System.arraycopy(fields, 6, user_fields, 0, fields.length-6);
}
int ci = 0;
int offset = 0;
if (fields.length > 1)
{
//see if counter exists
ci = fields[1].indexOf(':');
offset = 0;
if (ci > 0)
offset = -1;
}
if (fields.length >= 6+offset)
{
callsign = fields[0];
try
{
if (offset == 0)
packetID = Integer.parseInt(fields[1]);
//handle time
String format = "";
if (fields[2+offset].length() > 6)
format = "HH:mm:ss";
else
format = "HHmmss";
SimpleDateFormat ft = new SimpleDateFormat (format);
ft.setTimeZone(TimeZone.getTimeZone("UTC"));
Date time_in = ft.parse(fields[2+offset]);
setTime(time_in,timerx);
coords = new Gps_coordinate(fields[3+offset],fields[4+offset],fields[5+offset]);
}
catch (Exception e)
{
System.out.println("Error parsing - " + e.toString());
}
//now parse extra data
extraFields = new double[fields.length-1];
for (int j = 6+offset; j < fields.length; j++)
{
if (tc == null)
{
try
{
extraFields[j-1] = Double.parseDouble(fields[j]);
}
catch (Exception e)
{
}
}
else if (tc.getTotalFields() <= j-1)
{
try
{
extraFields[j-1] = Double.parseDouble(fields[j]);
}
catch (Exception e)
{
}
}
else
{
if (tc.getFieldDataType(j-1) == TelemetryConfig.DataType.FLOAT)
{
try
{
extraFields[j-1] = Double.parseDouble(fields[j]);
}
catch (Exception e)
{
}
}
else if (tc.getFieldDataType(j-1) == TelemetryConfig.DataType.INT)
{
try
{
extraFields[j-1] = (double)Integer.parseInt(fields[j]);
}
catch (Exception e)
{
}
}
}
}
}
}
public boolean getExtraFieldExists(int index)
{
if (extraFields == null)
return false;
if (!(index < extraFields.length))
return false;
return true;
}
public double getExtraFields(int index)
{
if (index < 0)
return 0;
if (extraFields == null)
return 0;
if (!(index < extraFields.length))
return 0;
return extraFields[index];
}
public String toSha256()
{
String str = "$$" + raw_string + "\n";
byte [] enc = Base64.encodeBase64(str.getBytes());
byte[] sha = null;
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
md.update(enc);
sha = md.digest();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return bytesToHexStr(sha);
}
//ref: http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
public static String bytesToHexStr(byte[] bytes) {
final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
char[] hexChars = new char[bytes.length * 2];
int v;
for ( int j = 0; j < bytes.length; j++ ) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public String raw_64_str()
{
String str = "$$" + raw_string + "\n";
byte [] enc = Base64.encodeBase64(str.getBytes());
String out = new String(enc);
return out;
}
public static int calculate_checksum(String in, int start)
{
int crc = 0xFFFF;
int i=0;
while (i < in.length() && in.charAt(i) != '*')
{
if (in.charAt(i) != '$')
{
int j;
crc = (crc ^ (in.charAt(i) << 8 ));
for (j=0; j< 8; j++)
{
if ((crc & 0x8000) != 0)
crc = ((crc << 1) ^ 0x1021);
else
crc = (crc << 1);
}
}
i++;
}
return crc & 0xFFFF;
}
public static boolean check_checksum(String in, int start)
{
int i;
int crc = calculate_checksum(in,start);
int ckloc = in.indexOf((int)'*',start);
if (ckloc < 0)
return false;
if (ckloc + 4 >= in.length())
return false;
//now extract checksum based on its known location and compare
String crcstr = in.substring(ckloc+1, ckloc+5);
crcstr = crcstr.toLowerCase();
for (i = 0; i < crcstr.length(); i++)
{
int c = (int)crcstr.charAt(i);
if (c < 48 || c > 102 || (c<97 && c >57))
return false;
}
int rccrc = Integer.parseInt(crcstr, 16);
if (rccrc == (crc & 0xFFFF))
return true;
else
return false;
}
private void setTime(Date time_in, long timerx)
{
try //this is all a bit horrible :(
{
//get time rx @ 12am
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Calendar cal2 = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal2.setTime(time_in);
cal.setTimeInMillis(timerx*1000);
cal.set(Calendar.HOUR_OF_DAY, cal2.get(Calendar.HOUR_OF_DAY));
cal.set(Calendar.MINUTE, cal2.get(Calendar.MINUTE));
cal.set(Calendar.SECOND, cal2.get(Calendar.SECOND));
//long best_guess = cal2.getTimeInMillis();
if (cal.getTimeInMillis() < timerx*1000 - 1*60*60*1000)
cal.roll(Calendar.DAY_OF_YEAR, 1);
if (cal.getTimeInMillis() < timerx*1000 + 12*60*60*1000)
;
else
cal.roll(Calendar.DAY_OF_YEAR, -1);
time = cal.getTime();
}
catch (Exception e)
{
System.out.println("Error parsing - " + e.toString());
}
}
public boolean isZeroGPS(){
if (coords == null)
return true;
return (coords.latitude == 0.0 || coords.longitude == 0.0);
}
}