From 249279c56e780be8ba7a4eaff0a3d6082bdbeec4 Mon Sep 17 00:00:00 2001 From: Sam Hatfield Date: Sat, 1 Aug 2015 00:10:59 +0100 Subject: [PATCH] Added python parser for raw pico tracker data. I haven't rigorously tested it, but the fact that the data starts in Montpelier on the 25th, and it correctly decodes the APRS test longitude of '<*e7' to -72.75 degrees is promising. --- firmware/tools/raw_parser.py | 140 +++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 firmware/tools/raw_parser.py diff --git a/firmware/tools/raw_parser.py b/firmware/tools/raw_parser.py new file mode 100644 index 0000000..19aa014 --- /dev/null +++ b/firmware/tools/raw_parser.py @@ -0,0 +1,140 @@ +""" +This script parses the raw data from the pico tracker. +At the very bottom the data is printed. Feel free to change +the print statement to suit the data you're interested in. +The parsed data is a list of dicts, with keys 'time', 'coords', +'battery', 'solar', 'temperature' and 'satellites'. + +Data is expected in the form of a series of lines containing +'/A={6 digits} {6 digits}z.{18 chars}{11 chars}\n'. Anything +else will be ignored. +""" + +import re +import sys +from datetime import datetime +from math import log, exp + +""" +Decodes a 'base 91' encoded string +""" +def base91_decode(enc_str): + enc_ints = [ord(x) - 33 for x in enc_str] + powers = range(len(enc_ints))[::-1] + return sum(x * pow(91, i) for x, i in zip(enc_ints, powers)) + +""" +Takes a parsed telemetry line and returns a datetime +Default year and month are 2015 and July +""" +def extract_time(line): + # Capture a 6 digit string + p = re.compile(r'(\d{6})z\S{18}') + match = p.match(line) + + if match == None: + return None + else: + date_str = '201507' + match.group(1) + + # Return a datetime object + return datetime.strptime(date_str, '%Y%m%d%H%M') + +""" +Takes a parsed telemetry line and returns latitude, longitude and +altitude. It decodes from base 91 along the way +""" +def extract_lat_long_alt(line): + # Capture a 4 char encoded latitude + p = re.compile(r'\d{6}z(\S{4})(\S{4})(\S{2})\S{8}') + match = p.match(line) + + if match == None: + return None + else: + enc_lat, enc_long, enc_alt = match.groups() + + # Lat/long in fractional degrees, alt in metres + latitude = 90.0 - (base91_decode(enc_lat) / 380926.0) + longitude = -180.0 + (base91_decode(enc_long) / 190463.0) + altitude = exp(log(1.002) * base91_decode(enc_alt)) / 3.2808 + + return (latitude, longitude, altitude) + +""" +Takes a parsed telemetry line and returns readings on battery, solar +temperature and satellite count. It decodes from base91 along the +way +""" +def extract_telemetry(line): + # Capture an 8 char encoded telemetry segment + p = re.compile(r'\d{6}z\S{10}(\S{8})') + match = p.match(line) + + if match == None: + return None + else: + tel = match.group(1) + + # Split into 2 char chunks + parts = [tel[i:i+2] for i in range(0, 8, 2)] + batt_enc, sol_enc, temp_enc, sat_enc = tuple(parts) + + # Reverse Richard's conversions + battery = base91_decode(batt_enc) / 1000.0 + solar = base91_decode(sol_enc) / 1000.0 + temperature = (base91_decode(temp_enc) / 10) - 273.2 + satellite_count = base91_decode(sat_enc) + + return (battery, solar, temperature, satellite_count) + +""" +Exracts the 'raw data' segment from a line of data; this is the 25 +character section after /A={6 digits} +""" +def extract_raw_data(line): + # Capture the raw data segment + p = re.compile(r'/A=\d{6} (\S{25})\|\S{11}$') + match = p.search(line) + + if match == None: + return None + else: + return match.group(1) + +#------------------------------------------------------------------------------- + +if len(sys.argv) != 2: + print 'Usage: python raw_parser.py rawdata.txt' + sys.exit() + +file_name = sys.argv[1] + +with open(file_name, 'r') as data_file: + data = [] + + for line in data_file: + # Extract raw data string + raw = extract_raw_data(line) + + if raw == None: + continue + else: + tele = extract_telemetry(raw) + + data.append({ + 'time': extract_time(raw), + 'coords': extract_lat_long_alt(raw), + 'battery': tele[0], + 'solar': tele[1], + 'temperature': tele[2], + 'satellites': tele[3] + }) + + # Sort data lines by time + data = sorted(data, key=lambda x: x['time']) + + # Print data + for datum in data: + print "%s: %s, %s" % ((str(datum['time']),) + datum['coords'][:2]) +