package com.aerodynelabs.habtk.connectors.parsers; import java.util.Calendar; import java.util.Scanner; import java.util.TimeZone; public class APRSPacket { String packet = ""; String fromCall = ""; String toCall = ""; String payload = ""; String comment = ""; char symbolTable; char symbolSymbol; long timestamp; double latitude; double longitude; double altitude; boolean validPos = false; public static final int SSID_DOT = 0; public static final int SSID_AMBULENCE = 1; public static final int SSID_BUS = 2; public static final int SSID_FIRETRUCK = 3; public static final int SSID_BIKE = 4; public static final int SSID_YACHT = 5; public static final int SSID_HELO = 6; public static final int SSID_AIRCRAFT = 7; public static final int SSID_SHIP = 8; public static final int SSID_CAR = 9; public static final int SSID_MOTORCYCLE = 10; public static final int SSID_BALLOON = 11; public static final int SSID_JEEP = 12; public static final int SSID_RV = 13; public static final int SSID_TRUCK = 14; public static final int SSID_VAN = 15; public APRSPacket(String from, String to, String payload) { packet = from + ">" + to + ":" + payload; } public APRSPacket(String pkt) { int s = 0; // Get source for(int i = s; i < pkt.length(); i++) { char c = pkt.charAt(i); if(c != '>') { fromCall += c; } else { s = i + 1; break; } } // Get destination for(int i = s; i < pkt.length(); i++) { char c = pkt.charAt(i); if(c != ':') { toCall += c; } else { s = i + 1; break; } } // Get payload payload = pkt.substring(s); s = 1; // Parse payload Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); switch(payload.charAt(0)) { case ':': // Message // XXX message break; case '>': // Status // XXX status break; case 'T': // Telemetry data // XXX telemetry break; case 0x1c: // Current mic-e case '`': // Current mic-e case '\'': // Old mic-e or new TM-D700 case 0x1d: // Old mic-e try { // Parse destination field // Decode latitude int dLat[] = new int[6]; for(int i = 0; i < 6; i++) { dLat[i] = decodeLat(toCall.charAt(i)); if(dLat[i] < 0) return; } latitude = dLat[0] * 10 + dLat[1]; latitude += ((dLat[2] * 10.0) + dLat[3] + (dLat[4] / 10.0) + (dLat[5] / 100.0)) / 60.0; int NS = decodeHemisphere(toCall.charAt(3)); if(NS == 0) return; if(NS < 0) latitude = -latitude; int lonOffset = decodeHemisphere(toCall.charAt(4)); int WE = -decodeHemisphere(toCall.charAt(5)); if(lonOffset == 0 || WE == 0) return; if(lonOffset < 0) { lonOffset = 0; } else if(lonOffset > 0) { lonOffset = 100; } // Parse information field longitude = decodeLonDeg(payload.charAt(1), lonOffset); if(longitude == -1) return; int min = decodeLonMin(payload.charAt(2)); if(min == -1) return; int hun = decodeLonHun(payload.charAt(3)); if(hun == -1) return; longitude += (min + (hun / 100.0)) / 60.0; if(WE < 0) longitude = -longitude; // XXX decode mic-e spd/cse // XXX decode mic-e telemetry symbolSymbol = payload.charAt(7); symbolTable = payload.charAt(8); comment = payload.substring(9); char typeCode = comment.charAt(0); if(typeCode == ' ' || typeCode == '>' || typeCode == ']' || typeCode == '\'' || typeCode == '`') comment = comment.substring(1); // Decode altitude if possible if(comment.length() >= 4) { if(comment.charAt(3) == '}') { int a = comment.charAt(0) - 33; int b = comment.charAt(1) - 33; int c = comment.charAt(2) - 33; altitude = (a*91*91 + b*91 + c) - 10000; } else { Scanner scan = new Scanner(comment); String tAlt = scan.findInLine("/A=[0-9]{6}"); scan.close(); if(tAlt != null) altitude = Integer.parseInt(tAlt.substring(3)) * 0.3048; } } timestamp = cal.getTimeInMillis(); validPos = true; } catch(Exception e) { validPos = false; } break; case '/': // Position, w/ timestamp, w/o messaging case '@': // Position, w/ timestamp, w/ messaging String time = ""; int stop = s; for(int i = s; i < stop + 8; i++) { s++; time += payload.charAt(i); if(!Character.isDigit(payload.charAt(i))) break; } char timeType = time.charAt(time.length() - 1); switch(timeType) { case '/': // DDHHmm local cal.setTimeZone(TimeZone.getDefault()); // resume parsing time as below case 'z': // DDHHmm zulu cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(time.substring(0, 2))); cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(time.substring(2, 4))); cal.set(Calendar.MINUTE, Integer.parseInt(time.substring(4, 6))); // cal.set(Calendar.SECOND, 0); // cal.set(Calendar.MILLISECOND, 0); break; case 'h': // HHmmss zulu cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(time.substring(0, 2))); cal.set(Calendar.MINUTE, Integer.parseInt(time.substring(2, 4))); cal.set(Calendar.SECOND, Integer.parseInt(time.substring(4, 6))); // cal.set(Calendar.MILLISECOND, 0); break; default: // MMDDHHmm zulu cal.set(Calendar.MONTH, Integer.parseInt(time.substring(0, 2))); cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(time.substring(2, 4))); cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(time.substring(4, 6))); cal.set(Calendar.MINUTE, Integer.parseInt(time.substring(6))); // cal.set(Calendar.SECOND, 0); // cal.set(Calendar.MILLISECOND, 0); break; } // Resume parsing position below case '!': // Position, w/o timestamp, w/o messaging case '=': // Position, w/o timestamp, w/ messaging // Timestamp timestamp = cal.getTimeInMillis(); Scanner scanner = new Scanner(payload.substring(s)); // Read position info // Test for compressed/uncompressed format if(Character.isDigit(payload.charAt(s))) { // Uncompressed position format try { String rLat = scanner.findInLine("[0-9.]+[NS]"); symbolTable = scanner.findInLine(".").charAt(0); String rLon = scanner.findInLine("[0-9.]+[EW]"); symbolSymbol = scanner.findInLine(".").charAt(0); // Parse lat latitude = Integer.parseInt(rLat.substring(0, 2)); latitude += Double.parseDouble(rLat.substring(2, rLat.length() - 2)) / 60.0; if(rLat.charAt(rLat.length() - 1) == 'S') latitude = -latitude; // Parse lon longitude = Integer.parseInt(rLon.substring(0, 3)); longitude += Double.parseDouble(rLon.substring(3, rLon.length() - 2)) / 60.0; if(rLon.charAt(rLon.length() - 1) == 'W') longitude = -longitude; // Save remaining payload to reinit scanners // Remaining fields can occur anywhere comment = scanner.nextLine(); // Parse altitude if possible scanner = new Scanner(comment); String rAlt = scanner.findInLine("/A=[0-9]{6}"); if(rAlt != null) { altitude = Integer.parseInt(rAlt.substring(3)) * 0.3048; } validPos = true; } catch(Exception e) { validPos = false; } } else { // Compressed position format try { //String position = payload.substring(1, 14); String position = payload.substring(s); // Split fields symbolTable = position.charAt(0); symbolSymbol = position.charAt(9); String cLat = position.substring(1, 5); String cLon = position.substring(5, 9); String cExt = position.substring(10, 12); char cType = position.charAt(12); // Uncompress lat/lon latitude = 90.0 - (( (cLat.charAt(0) - 33) * 753571 + (cLat.charAt(1) - 33) * 8281 + (cLat.charAt(2) - 33) * 91 + (cLat.charAt(3) - 33) ) / 380926.0); longitude = -180.0 + (( (cLon.charAt(0) - 33) * 753571 + (cLon.charAt(1) - 33) * 8281 + (cLon.charAt(2) - 33) * 91 + (cLon.charAt(3) - 33) ) / 190463.0); if(!cExt.equals(" ")) { if(((byte)cType & 0x18) == 0x10) { int e = (cExt.charAt(0) - 33) * 91 + (cExt.charAt(1) - 33); altitude = (int)(Math.pow(1.002, e) + 0.5); } else { scanner = new Scanner(pkt); String rAlt = scanner.findInLine("/A=[0-9]{6}"); if(rAlt != null) { altitude = Integer.parseInt(rAlt.substring(3)) * 0.3048; } } } comment = payload.substring(14); validPos = true; } catch(Exception e) { validPos = false; } } // Parse extensions // TODO parse aprs extensions (APRS Spec p.27) break; } } private int decodeLonHun(char c) { int h = c - 28; if(h >= 0 && h < 100) return h; return -1; } private int decodeLonMin(char c) { int m = c - 28; if(m >= 60) m = m - 60; if(m >= 0 && m < 60) return m; return -1; } private int decodeLonDeg(char c, int offset) { int d = c - 28; d += offset; if(d >= 180 && d <= 189) d = d - 80; if(d >= 190 && d <= 199) d = d - 190; if(d >= 0 && d < 180) return d; return -1; } private int decodeHemisphere(char c) { switch(c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'L': return -1; case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': return 1; } return 0; } private int decodeLat(char c) { switch(c) { case '0': case 'A': case 'P': return 0; case '1': case 'B': case 'Q': return 1; case '2': case 'C': case 'R': return 2; case '3': case 'D': case 'S': return 3; case '4': case 'E': case 'T': return 4; case '5': case 'F': case 'U': return 5; case '6': case 'G': case 'V': return 6; case '7': case 'H': case 'W': return 7; case '8': case 'I': case 'X': return 8; case '9': case 'J': case 'Y': return 9; } return -1; } public String getComment() { return comment; } public String getPayload() { return payload; } public boolean isPosition() { return validPos; } public String getFrom() { return fromCall; } public String getTo() { return toCall; } public long getTimestamp() { return timestamp; } public double getLatitude() { return latitude; } public double getLongitude() { return longitude; } public double getAltitude() { return altitude; } public String getPacket() { return packet; } public String toString() { String out = "Source: " + fromCall + "\n" + "Dest: " + toCall + "\n" + "Payload: " + payload; return out; } }