diff --git a/.gitignore b/.gitignore index bdb12e5..7e76400 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ build .dep *.pyc __pycache__ +*.sqlite +decoder/html/images/ diff --git a/decoder/base91.py b/decoder/base91.py index 4cc6ab5..cee43a0 100644 --- a/decoder/base91.py +++ b/decoder/base91.py @@ -35,7 +35,7 @@ base91_alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', ' 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$', '%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=', - '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"'] + '>', '?', '@', '[', ']', '^', '_', '`', '{', '-', '}', '~', '"'] decode_table = dict((v,k) for k,v in enumerate(base91_alphabet)) diff --git a/decoder/decoder.py b/decoder/decoder.py index 82b3434..f58cc42 100755 --- a/decoder/decoder.py +++ b/decoder/decoder.py @@ -1,40 +1,159 @@ -#!/usr/bin/python +#!/usr/bin/python3 -import serial,os,re,datetime -from subprocess import call +import serial,re import base91 -import binascii -import urllib2 -import io import sys import argparse -import getpass import telnetlib import time +import sqlite3 +import image +import position # Parse arguments from terminal parser = argparse.ArgumentParser(description='APRS/SSDV decoder') parser.add_argument('-c', '--call', help='Callsign of the station', required=True) -parser.add_argument('-l', '--log', help='Name of the logfile') parser.add_argument('-n', '--grouping', help='Amount packets that will be sent to the SSDV server in one request', default=1, type=int) parser.add_argument('-d', '--device', help='Serial device (\'-\' for stdin)', default='-') parser.add_argument('-b', '--baudrate', help='Baudrate for serial device', default=9600, type=int) parser.add_argument('-s', '--server', help='SSDV server URL', default='https://ssdv.habhub.org/api/v0/packets') args = parser.parse_args() -if args.device == 'I': # Connect to APRS-IS +# Open SQLite database +sqlite = sqlite3.connect("decoder.sqlite") +sqlite.cursor().execute(""" + CREATE TABLE IF NOT EXISTS position + ( + call TEXT, + time INTEGER, + org TEXT, + lat FLOAT, + lon FLOAT, + alt INTEGER, + comment TEXT, + sequ INTEGER, + tel1 INTEGER, + tel2 INTEGER, + tel3 INTEGER, + tel4 INTEGER, + tel5 INTEGER, + PRIMARY KEY (call, time) + ) +""") +sqlite.cursor().execute(""" + CREATE TABLE IF NOT EXISTS image + ( + call TEXT, + time INTEGER, + imageID INTEGER, + packetID INTEGER, + lat FLOAT, + lon FLOAT, + alt INTEGER, + data TEXT, + PRIMARY KEY (call, time, imageID, packetID) + ) +""") + + +""" Packet handler for received APRS packets""" +def received_data(data): + # Debug + print('====================================================================================================') + print(data) + print('----------------------------------------------------------------------------------------------------') + + # Parse line and detect data + # Position (.*)\>APECAN(.*?):\/([0-9]{6}h)(.{13})(.*?)\|(.*)\| + # Image (.*)\>APECAN(.*?):\/([0-9]{6}h)(.{13})I(.*) + # Log (.*)\>APECAN(.*?):\/([0-9]{6}h)(.{13})L(.*) + + all = re.search("(.*)\>APECAN(.*?):\/([0-9]{6}h)(.{13})", data) + pos = re.search("(.*)\>APECAN(.*?):\/([0-9]{6}h)(.{13})(.*?)\|(.*)\|", data) + dat = re.search("(.*)\>APECAN(.*?):\/([0-9]{6}h)(.{13})(I|L)(.*)", data) + + if all: + call = all.group(1) + rxer = all.group(2).split(',')[-1] + tim = all.group(3) + posi = all.group(4) + + if pos: # Position packet (with comment and telementry) + + comm = pos.group(5) + tel = pos.group(6) + position.insert_position(sqlite, call, tim, posi, comm, tel) + + elif dat: # Data packet + + typ = dat.group(5) + data_b91 = dat.group(6) + data = base91.decode(data_b91) # Decode Base91 + + if typ is 'I': # Image packet + image.insert_image(sqlite, rxer, call, tim, posi, data, args.server, args.grouping) + elif typ is 'L': # Log packet + position.insert_log(sqlite, call, data) + +if args.device == 'I': # Source APRS-IS print('Connect to APRS-IS') try: tn = telnetlib.Telnet("rotate.aprs2.net", 14580, 3) - tn.write("user %s filter u/APECAN\n" % args.call) + tn.write(("user %s filter u/APECAN\n" % args.call).encode('ascii')) print('Connected') except Exception as e: print('Could not connect to APRS-IS: %s' % str(e)) print('exit...') sys.exit(1) -elif args.device is not '-': # Use serial connection (probably TNC) + wdg = time.time() + 1 # Connection watchdog + buf = '' + while True: + # Read data + try: + buf += tn.read_eager().decode('ascii') + except EOFError: # Server has connection closed + wdg = 0 # Tell watchdog to restart connection + + # Line handler + if '\n' in buf: + pbuf = buf.split('\n') + for i in range(len(pbuf)-1): + # Separate lines handled here + + # Watchdog reload + if '# aprsc' in pbuf[i]: + print('Ping from APRS-IS') + wdg = time.time() + 30 + continue + + # Data handling + received_data(pbuf[i]) + + buf = pbuf[-1] + + # Watchdog reconnection + if wdg < time.time(): + print('APRS-IS connection lost... reconnect') + try: + tn = telnetlib.Telnet("rotate.aprs2.net", 14580, 3) + tn.write(("user %s filter u/APECAN\n" % args.call).encode('ascii')) + print('Connected') + wdg = time.time() + 1 + except Exception as e: + print('Could not connect to APRS-IS: %s' % str(e)) + print('Try again...') + + time.sleep(0.1) + +elif args.device is '-': # Source stdin + + while True: + data = sys.stdin.readline() + received_data(data) + +else: # Source Serial connection try: serr = serial.Serial( @@ -45,182 +164,13 @@ elif args.device is not '-': # Use serial connection (probably TNC) sys.stderr.write('Error: Could not open serial port\n') sys.exit(1) - -# Open logging file -if args.log is not None: - try: - f = open(args.log, 'a') - except: - sys.stderr.write('Error: Could not open logging file\n') - sys.exit(1) - -jsons = [] - -def decode_callsign(code): - callsign = '' - - while code > 0: - s = code % 40 - if s == 0: callsign += '-' - elif s < 11: callsign += chr(47 + s) - elif s < 14: callsign += '-' - else: callsign += chr(51 + s) - code /= 40 - - return callsign - -def encode_callsign(callsign): - x = 0 - for i in range(len(callsign)-1,-1,-1): - x *= 40 - c = ord(callsign[i]) - if c >= 65 and c <= 90: x += c - 51 - elif c >= 97 and c <= 122: x += c - 83 - elif c >= 48 and c <= 57: x += c - 47 - - return x - - - -def received_data(data): - global jsons - # Parse line and detect data - m = re.search("(.*)\>APECAN(.*):\!(.*)", data) - try: - call = m.group(1).split(' ')[-1] # transmitter callsign - data91 = m.group(3) # base91 encoded SSDV data (without SYNC, PacketType, Callsign, CRC, FEC) - if len(m.group(2)) > 0: - receiver = m.group(2).split(',')[-1] - else: - receiver = args.call - except: - return # message format incorrect (probably no APRS message or line cut off too short) - - if args.log is not None: - f.write(data) # Log data to file - - data = base91.decode(data91) # Decode Base91 - - if len(data) != 214: - return # APRS message sampled too short - - # Encode callsign (ensure callsign has no more than 6 chars) - bcall = call.split('-') # Split callsign and SSID - if len(bcall) == 1: # No SSID available, so take the callsign - bcall = bcall[0][0:6] - elif(len(bcall[0]) < 5): # Callsign has 4 chars, so take it with the SSID - bcall = bcall[0] + bcall[1][0:2] - elif(len(bcall[0]) < 6): # Callsign has 5 chars, so take it with the last digit of the SSID - bcall = bcall[0] + bcall[1][-1] - else: - bcall = bcall[0][0:6] # Callsign has 6 chars, so take the call without SSID - - data = binascii.unhexlify('66%08x' % encode_callsign(bcall)) + data - - # Calculate CRC for SSDV server - crc = binascii.crc32(data) & 0xffffffff - - # Create message for SSDV server (and save to array) - ssdv = '55' + binascii.hexlify(data) + ('%08x' % crc) + (64*'0') - jsons.append("""{ - \"type\": \"packet\", - \"packet\": \"""" + ssdv + """\", - \"encoding\": \"hex\", - \"received\": \"""" + datetime.datetime.now().isoformat('T')[:19] + """Z\", - \"receiver\": \"""" + receiver + """\" - }""") - - print datetime.datetime.now().isoformat('T') + ' Received packet call %s image %d packet %d' % (call, data[5], 0x100*data[6]+data[7]) - - if len(jsons) >= args.grouping: # Enough packets collected, send them all to the server - - req = urllib2.Request(args.server) - req.add_header('Content-Type', 'application/json') - - json = "{\"type\":\"packets\",\"packets\":[" + ",".join(jsons) + "]}" # Group all SSDV packets into a big JSON - jsons = [] - - try: - error = True - while error: - print('Send to SSDV data server') - try: - result = urllib2.urlopen(req, "".join(json.split(' '))) # Send packets to server - print('Response from Server: OK') - error = False - except urllib2.URLError, error: - if error.code == 400: - print('Response from Server: %s', error.read()) - error = False - else: - print 'SSDV-Server connection error... try again' - - except urllib2.HTTPError, error: # The server did not like our packets :( - print 'Send to SSDV data server: failed (the server did not like our packets :( )' - print error.read() - -if args.device == 'I': # APRS-IS - - wdg = time.time() # Watchdog - buf = '' while True: - buf += tn.read_eager() - if '\n' in buf: - pbuf = buf.split('\n') - for i in range(len(pbuf)-1): - # Separate lines handled here - - # Watchdog reload - if '# aprsc' in pbuf[i]: - print('Ping from APRS-IS') - wdg = time.time() - continue - - # Data handling - print(pbuf[i]) - received_data(pbuf[i]) - - buf = pbuf[-1] - - # Watchdog reconnection - if wdg + 30 < time.time(): - print('APRS-IS connection lost... reconnect') - try: - tn = telnetlib.Telnet("rotate.aprs2.net", 14580, 3) - tn.write("user %s filter u/APECAN\n" % args.call) - print('Connected') - wdg = time.time() - except Exception as e: - print('Could not connect to APRS-IS: %s' % str(e)) - print('Try again...') - - time.sleep(0.1) - -else: # stdin or serial - - while True: - # Read data - if args.device is '-': - data = sys.stdin.readline() - else: - data = '' - while True: - b = serr.read(1) - if b == '\r' or b == '\n': - break - data += b + data = '' + while True: + b = serr.read(1) + if b == '\r' or b == '\n': + break + data += b received_data(data) - - - - - - - - - - - - diff --git a/decoder/html/data.php b/decoder/html/data.php new file mode 100644 index 0000000..1e830fa --- /dev/null +++ b/decoder/html/data.php @@ -0,0 +1,13 @@ +getCallsigns() as $callsign) + foreach($db->getRoute($callsign) as $point) + $data[] = $point; + +header("Content-Type: application/json"); +echo json_encode($data); +?> + diff --git a/decoder/html/database.class.php b/decoder/html/database.class.php new file mode 100644 index 0000000..4994ec9 --- /dev/null +++ b/decoder/html/database.class.php @@ -0,0 +1,48 @@ +open("/src/pecanpico9/decoder/decoder.sqlite"); + + if($this->lastErrorCode()) + echo $this->lastErrorMsg(); + } + function getCallsigns() { + $calls = array(); + + $query = $this->query("SELECT call FROM position GROUP BY call"); + while($row = $query->fetchArray(SQLITE3_ASSOC)) + $calls[] = $row['call']; + + $query = $this->query("SELECT call FROM image GROUP BY call"); + while($row = $query->fetchArray(SQLITE3_ASSOC)) + if(!in_array($row['call'], $calls)) + $calls[] = $row['call']; + + return $calls; + } + function getRoute($callsign) { + $route = array(); + + $stmt = $this->prepare(" + SELECT position.time,ROUND(position.lat,5) as lat,ROUND(position.lon,5) as lng,position.alt,org, + 'images/' || REPLACE(image.call,'-','') || '-' || image.time || '-' || image.imageID || '.jpg' AS img + FROM position + LEFT JOIN image + ON position.time = image.time + WHERE position.call = :call + AND position.lat != 0 + AND position.lon != 0 + GROUP BY position.call,position.time + ORDER BY position.time ASC + "); + $stmt->bindValue(':call', $callsign, SQLITE3_TEXT); + $query = $stmt->execute(); + + while($row = $query->fetchArray(SQLITE3_ASSOC)) + $route[] = $row; + + return $route; + } +} +?> + diff --git a/decoder/html/index.php b/decoder/html/index.php new file mode 100644 index 0000000..59718e4 --- /dev/null +++ b/decoder/html/index.php @@ -0,0 +1,111 @@ + + + + + + + + +
+ + + + + + + + + + + + + + + + diff --git a/decoder/image.py b/decoder/image.py new file mode 100644 index 0000000..17f2d9f --- /dev/null +++ b/decoder/image.py @@ -0,0 +1,125 @@ +import binascii +import urllib.request +import urllib.error +import datetime +from position import decode_position +from subprocess import * + +jsons = [] + +def decode_callsign(code): + callsign = '' + + while code > 0: + s = code % 40 + if s == 0: callsign += '-' + elif s < 11: callsign += chr(47 + s) + elif s < 14: callsign += '-' + else: callsign += chr(51 + s) + code /= 40 + + return callsign + +def encode_callsign(callsign): + x = 0 + for i in range(len(callsign)-1,-1,-1): + x *= 40 + c = ord(callsign[i]) + if c >= 65 and c <= 90: x += c - 51 + elif c >= 97 and c <= 122: x += c - 83 + elif c >= 48 and c <= 57: x += c - 47 + + return x + +""" +Decodes an APRS/SSDV image packet (APRS header must be removed already) +sqlite - Database handle +reciever - The call of the receiving station +call - The call of the transmitter +data - Binary data +""" +def insert_image(sqlite, receiver, call, tim, posi, data, server, grouping): + global jsons + + if len(data) != 214: + return # APRS message sampled too short + + # Encode callsign (ensure callsign has no more than 6 chars) + bcall = call.split('-') # Split callsign and SSID + if len(bcall) == 1: # No SSID available, so take the callsign + bcall = bcall[0][0:6] + elif(len(bcall[0]) < 5): # Callsign has 4 chars, so take it with the SSID + bcall = bcall[0] + bcall[1][0:2] + elif(len(bcall[0]) < 6): # Callsign has 5 chars, so take it with the last digit of the SSID + bcall = bcall[0] + bcall[1][-1] + else: + bcall = bcall[0][0:6] # Callsign has 6 chars, so take the call without SSID + + data = binascii.unhexlify('66%08x' % encode_callsign(bcall)) + data + + # Calculate CRC for SSDV server + crc = binascii.crc32(data) & 0xffffffff + + # Create message for SSDV server (and save to array) + ssdv = '55' + binascii.hexlify(data).decode("ascii") + ('%08x' % crc) + (64*'0') + jsons.append("""{ + \"type\": \"packet\", + \"packet\": \"""" + ssdv + """\", + \"encoding\": \"hex\", + \"received\": \"""" + datetime.datetime.now().isoformat('T')[:19] + """Z\", + \"receiver\": \"""" + receiver + """\" + }""") + + # Decode various meta data + timd,x,y,z,dummy = decode_position(tim, posi, None) + imageID = data[5] + packetID = (data[6] << 8) | data[7] + + # Debug + print('Received packet from %s image %d packet %d' % (call, imageID, packetID)) + + # Insert + sqlite.cursor().execute(""" + INSERT OR REPLACE INTO image (call,time,imageID,packetID,lat,lon,alt,data) + VALUES (?,?,?,?,?,?,?,?)""", + (call, int(timd.timestamp()), imageID, packetID, y, x, int(z), ssdv) + ) + sqlite.commit() + + # SSDV decode + cur = sqlite.cursor() + cur.execute("SELECT GROUP_CONCAT(data,'') FROM image WHERE call = ? AND imageID = ? AND time = ? GROUP BY imageID ORDER BY packetID", (call, imageID, int(timd.timestamp()))) + name = 'html/images/%s-%d-%d.jpg' % (call.replace('-',''), int(timd.timestamp()), imageID) + f = open(name, 'wb') + process = Popen(['./ssdv', '-d'], stdin=PIPE, stdout=f, stderr=PIPE) + process.stdin.write(binascii.unhexlify(cur.fetchall()[0][0])) + dummy,err = process.communicate() + f.close() + + if len(jsons) >= grouping: # Enough packets collected, send them all to the server + + req = urllib.request.Request(server) + req.add_header('Content-Type', 'application/json') + + json = "{\"type\":\"packets\",\"packets\":[" + ",".join(jsons) + "]}" # Group all SSDV packets into a big JSON + jsons = [] + + try: + error = True + while error: + print('Send to SSDV data server') + try: + result = urllib.request.urlopen(req, "".join(json.split(' ')).encode("ascii")) # Send packets to server + print('Response from Server: OK') + error = False + except urllib.error.URLError as error: + if error.code == 400: + print('Response from Server: %s', error.read()) + error = False + else: + print('SSDV-Server connection error... try again') + + except urllib.error.HTTPError as error: # The server did not like our packets :( + print('Send to SSDV data server: failed (the server did not like our packets :( )') + print(error.read()) + diff --git a/decoder/position.py b/decoder/position.py new file mode 100644 index 0000000..808d0b9 --- /dev/null +++ b/decoder/position.py @@ -0,0 +1,83 @@ +from datetime import datetime,timedelta,timezone +import sqlite3 +import base91 + +def insert_log(sqlite, call, data): + + if len(data)/10*10 != len(data): # Is not multiple of 8 bytes + return # APRS message sampled too short + + for i in range(int(len(data)/10)): + tim = (data[i*10+3] << 24) | (data[i*10+2] << 16) | (data[i*10+1] << 8) | data[i*10+0] + lat = (data[i*10+5] << 8) | data[i*10+4] + lon = (data[i*10+7] << 8) | data[i*10+6] + alt = (data[i*10+9] << 8) | data[i*10+8] + + lat = float(lat * 180) / 65536 - 90 + lon = float(lon * 360) / 65536 - 180 + + tim_stringified = datetime.utcfromtimestamp(tim).strftime("%Y-%m-%d %H:%M:%S") + try: + sqlite.cursor().execute("INSERT OR FAIL INTO position (call,time,org,lat,lon,alt) VALUES (?,?,'log',?,?,?)", (call, tim, lat, lon, alt)) + print("Decoded log from %s time %s => lat=%06.3f lon=%07.3f alt=%05d" % (call, tim_stringified, lat, lon, alt)) + except sqlite3.IntegrityError: + print("Decoded log from %s time %s => lat=%06.3f lon=%07.3f alt=%05d already in db" % (call, tim_stringified, lat, lon, alt)) + + sqlite.commit() + +def decode_position(tim, posi, tel): + # Decode latitude + y3 = ord(posi[1]) - 33 + y2 = ord(posi[2]) - 33 + y1 = ord(posi[3]) - 33 + y0 = ord(posi[4]) - 33 + ye = y0 + y1*91 + y2*8281 + y3*753571 + y = -ye / 380926.0 + 90 + + # Decode longitude + x3 = ord(posi[5]) - 33 + x2 = ord(posi[6]) - 33 + x1 = ord(posi[7]) - 33 + x0 = ord(posi[8]) - 33 + xe = x0 + x1*91 + x2*8281 + x3*753571 + x = xe / 190463.0 - 180 + + # Decode altitude + z1 = ord(posi[10]) - 33 + z0 = ord(posi[11]) - 33 + ze = z0 + z1*91 + z = pow(1.002, ze) / 3.281 + + # Decode time + timd = datetime.now() + timd = timd.replace(hour = int(tim[0:2]), minute = int(tim[2:4]), second = int(tim[4:6]), microsecond=0) + if datetime.utcnow() < timd: # Packet was sampled yesterday + timd -= timedelta(1) + + # Decode telemetry + teld = [0]*6 + if tel != None: + for i in range(6): + t1 = ord(tel[i*2+0]) - 33 + t0 = ord(tel[i*2+1]) - 33 + teld.append(t0 + t1*91) + + return timd,x,y,z,teld + +def insert_position(sqlite, call, tim, posi, comm, tel): #sqlite, call, data + # Decode + timd,x,y,z,teld = decode_position(tim, posi, tel) + + # Insert + sqlite.cursor().execute(""" + INSERT OR REPLACE INTO position (call,time,org,lat,lon,alt,comment,sequ,tel1,tel2,tel3,tel4,tel5) + VALUES (?,?,'pos',?,?,?,?,?,?,?,?,?,?)""", + (call, int(timd.timestamp()), y, x, int(z), comm, teld[0], teld[1], teld[2], teld[3], teld[4], teld[5]) + ) + sqlite.commit() + + # Debug + tim_stringified = timd.strftime("%Y-%m-%d %H:%M:%S") + print("Decoded position from %s time %s => lat=%f lon=%f alt=%d comment=%s, sequ=%d tel=[%d,%d,%d,%d,%d]" + % (call, tim_stringified, y, x, z, comm, teld[0], teld[1], teld[2], teld[3], teld[4], teld[5])) + diff --git a/tracker/software/config.c b/tracker/software/config.c index e93e907..e804269 100644 --- a/tracker/software/config.c +++ b/tracker/software/config.c @@ -366,7 +366,7 @@ uint8_t ssdv_buffer[128*1024] __attribute__((aligned(32))); // Image buffer systime_t track_cycle_time = S2ST(60); // Tracking cycle (all peripheral data [airpressure, GPS, temperature, ...] is collected each 60 seconds systime_t log_cycle_time = S2ST(60); // Log cycle time in seconds (600 seconds) -bool keep_cam_switched_on = true; // Keep camera switched on and initialized, this makes image capturing faster but takes a lot of power over long time +bool keep_cam_switched_on = false; // Keep camera switched on and initialized, this makes image capturing faster but takes a lot of power over long time uint16_t gps_on_vbat = 3000; // Battery voltage threshold at which GPS is switched on uint16_t gps_off_vbat = 2500; // Battery voltage threshold at which GPS is switched off @@ -385,19 +385,19 @@ void start_user_modules(void) config[0].frequency.hz = 144800000; // Default frequency 144.800 MHz config[0].trigger.type = TRIG_NEW_POINT; // Transmit when tracking manager samples new tracking point chsnprintf(config[0].aprs_conf.callsign, 16, "DL7AD"); // APRS Callsign - config[0].aprs_conf.ssid = 12; // APRS SSID + config[0].aprs_conf.ssid = 13; // APRS SSID config[0].aprs_conf.symbol = SYM_BALLOON; // APRS Symbol chsnprintf(config[0].aprs_conf.path, 16, "WIDE1-1"); // APRS Path config[0].aprs_conf.preamble = 300; // APRS Preamble (300ms) config[0].aprs_conf.tel[0] = TEL_VBAT; // APRS Telemetry parameter 1: Battery voltage - config[0].aprs_conf.tel[1] = TEL_PBAT; // APRS Telemetry parameter 2: Battery charge/discharge power - config[0].aprs_conf.tel[2] = TEL_RBAT; // APRS Telemetry parameter 3: Battery impedance + config[0].aprs_conf.tel[1] = TEL_VSOL; // APRS Telemetry parameter 2: Solar voltage + config[0].aprs_conf.tel[2] = TEL_PBAT; // APRS Telemetry parameter 3: Battery charge/discharge power config[0].aprs_conf.tel[3] = TEL_TEMP; // APRS Telemetry parameter 4: Temperature config[0].aprs_conf.tel[4] = TEL_PRESS; // APRS Telemetry parameter 5: Airpressuse config[0].aprs_conf.tel_enc = TRUE; // Transmit Telemetry encoding information activated config[0].aprs_conf.tel_enc_cycle = 3600; // Transmit Telemetry encoding information every 3600sec chsnprintf(config[0].aprs_conf.tel_comment, 30, "http://ssdv.habhub.org/DL7AD");// Telemetry comment - //start_position_thread(&config[0]); + start_position_thread(&config[0]); // Module POSITION, UKHAS 2m 2FSK config[1].power = 127; // Transmission Power @@ -438,15 +438,15 @@ void start_user_modules(void) config[3].packet_spacing = 20000; // Packet spacing in ms config[3].trigger.type = TRIG_CONTINUOUSLY; // Transmit continuously chsnprintf(config[3].aprs_conf.callsign, 16, "DL7AD"); // APRS Callsign - config[3].aprs_conf.ssid = 12; // APRS SSID + config[3].aprs_conf.ssid = 14; // APRS SSID + config[3].aprs_conf.symbol = SYM_BALLOON; // APRS Symbol config[3].aprs_conf.preamble = 300; // APRS Preamble (300ms) - chsnprintf(config[3].ssdv_conf.callsign, 7, "DL7AD1"); // SSDV Callsign config[3].ssdv_conf.ram_buffer = ssdv_buffer; // Camera buffer config[3].ssdv_conf.ram_size = sizeof(ssdv_buffer); // Buffer size config[3].ssdv_conf.res = RES_QVGA; // Resolution QVGA config[3].ssdv_conf.redundantTx = true; // Redundant transmission (transmit packets twice) config[3].ssdv_conf.quality = 4; // Image quality - //start_image_thread(&config[3]); + start_image_thread(&config[3]); // Module IMAGE, APRS 2m 2GFSK config[4].power = 127; // Transmission Power @@ -456,9 +456,9 @@ void start_user_modules(void) config[4].frequency.hz = 144860000; // Transmission frequency 144.860 MHz config[4].trigger.type = TRIG_CONTINUOUSLY; // Transmit continuously chsnprintf(config[4].aprs_conf.callsign, 16, "DL7AD"); // APRS Callsign - config[4].aprs_conf.ssid = 12; // APRS SSID + config[4].aprs_conf.ssid = 13; // APRS SSID + config[4].aprs_conf.symbol = SYM_BALLOON; // APRS Symbol config[4].aprs_conf.preamble = 100; // APRS Preamble (100ms) - chsnprintf(config[4].ssdv_conf.callsign, 7, "DL7AD2"); // SSDV Callsign config[4].ssdv_conf.ram_buffer = ssdv_buffer; // Camera buffer config[4].ssdv_conf.ram_size = sizeof(ssdv_buffer); // Buffer size config[4].ssdv_conf.res = RES_VGA; // Resolution VGA @@ -476,7 +476,7 @@ void start_user_modules(void) config[5].fsk_conf.predelay = 1000; // Preamble (1000ms) config[5].fsk_conf.baud = 600; // Baudrate (600baud) config[5].fsk_conf.shift = 1000; // Frequency shift (1000Hz) - chsnprintf(config[5].ssdv_conf.callsign, 7, "DL7AD3"); // SSDV Callsign + chsnprintf(config[5].ssdv_conf.callsign, 7, "DL7AD"); // SSDV Callsign config[5].ssdv_conf.ram_buffer = ssdv_buffer; // Camera buffer config[5].ssdv_conf.ram_size = sizeof(ssdv_buffer); // Buffer size config[5].ssdv_conf.res = RES_QVGA; // Resolution QVGA @@ -491,14 +491,15 @@ void start_user_modules(void) config[6].protocol = PROT_APRS_AFSK; // Protocol APRS (AFSK) config[6].frequency.type = FREQ_APRS_REGION; // Dynamic frequency allocation config[6].frequency.hz = 144800000; // Default frequency 144.800 MHz - config[6].init_delay = 60000; // Module startup delay (60 seconds) + config[6].init_delay = 5000; // Module startup delay (5 seconds) config[6].trigger.type = TRIG_TIMEOUT; // Periodic cycling (every 180 seconds) - config[6].trigger.timeout = 180; // Timeout 180 sec + config[6].trigger.timeout = 60; // Timeout 180 sec chsnprintf(config[6].aprs_conf.callsign, 16, "DL7AD"); // APRS Callsign - config[6].aprs_conf.ssid = 12; // APRS SSID + config[6].aprs_conf.ssid = 13; // APRS SSID + config[6].aprs_conf.symbol = SYM_BALLOON; // APRS Symbol chsnprintf(config[6].aprs_conf.path, 16, "WIDE1-1"); // APRS Path config[6].aprs_conf.preamble = 300; // APRS Preamble (300ms) - //start_logging_thread(&config[6]); + start_logging_thread(&config[6]); } diff --git a/tracker/software/debug.c b/tracker/software/debug.c index 01415bd..bb6d1a1 100644 --- a/tracker/software/debug.c +++ b/tracker/software/debug.c @@ -89,15 +89,15 @@ void readLog(BaseSequentialStream *chp, int argc, char *argv[]) (void)argc; (void)argv; - chprintf(chp, "id,date,time,lat,lon,alt,sats,ttff,vbat,vsol,vsub,pbat,rbat,press,temp,hum,idimg\r\n"); + chprintf(chp, "addr,id,date,time,lat,lon,alt,sats,ttff,vbat,vsol,vsub,pbat,rbat,press,temp,hum,idimg\r\n"); trackPoint_t *tp; for(uint16_t i=0; (tp = getLogBuffer(i)) != NULL; i++) if(tp->id != 0xFFFFFFFF) { chprintf( chp, - "%d,%04d-%02d-%02d,%02d:%02d:%02d,%d.%05d,%d.%05d,%d,%d,%d,%d.%03d,%d.%03d,%d.%03d,%d,%d,%d.%01d,%2d.%02d,%2d.%01d,%d\r\n", - tp->id,tp->time.year, tp->time.month, tp->time.day, tp->time.hour, tp->time.minute, tp->time.day, + "%08x,%d,%04d-%02d-%02d,%02d:%02d:%02d,%d.%05d,%d.%05d,%d,%d,%d,%d.%03d,%d.%03d,%d.%03d,%d,%d,%d.%01d,%2d.%02d,%2d.%01d,%d\r\n", + tp, tp->id,tp->time.year, tp->time.month, tp->time.day, tp->time.hour, tp->time.minute, tp->time.day, tp->gps_lat/10000000, (tp->gps_lat > 0 ? 1:-1)*(tp->gps_lat/100)%100000, tp->gps_lon/10000000, (tp->gps_lon > 0 ? 1:-1)*(tp->gps_lon/100)%100000, tp->gps_alt, tp->gps_sats, tp->gps_ttff, tp->adc_vbat/1000, (tp->adc_vbat%1000), tp->adc_vsol/1000, (tp->adc_vsol%1000), tp->adc_vusb/1000, (tp->adc_vusb%1000), tp->adc_pbat, tp->adc_rbat, diff --git a/tracker/software/drivers/wrapper/padc.c b/tracker/software/drivers/wrapper/padc.c index 81f0ce8..5eae0bb 100644 --- a/tracker/software/drivers/wrapper/padc.c +++ b/tracker/software/drivers/wrapper/padc.c @@ -2,10 +2,9 @@ #include "hal.h" #include "config.h" -#include "padc.h" #include "pac1720.h" -#include "debug.h" #include "pi2c.h" +#include #define ADC_NUM_CHANNELS 4 /* Amount of channels (solar, battery, temperature) */ #define VCC_REF_LOW 1850 /* mV */ @@ -69,39 +68,32 @@ void doConversion(void) deinitADC(); } -uint16_t getBatteryVoltageMV_STM32(void) +static uint16_t getBatteryVoltageMV_STM32(void) { doConversion(); return samples[2] * vcc_ref * DIVIDER_VBAT / 4096; } +static uint16_t getSolarVoltageMV_STM32(void) +{ + doConversion(); + return samples[0] * vcc_ref * DIVIDER_VSOL / 4096; +} + uint16_t getBatteryVoltageMV(void) { - uint16_t vbat = getBatteryVoltageMV_STM32(); + uint16_t vbat = getBatteryVoltageMV_STM32(); // Get value from STM32 + uint16_t vbat_pac = pac1720_getVbat(); // Get value from PAC1720 - // Get voltage from PAC1720 (PAC1720 returns false redings below 2.35V) - if(vbat >= 2500) - { - uint16_t vbat_pac = pac1720_getVbat(); // Get value from PAC1720 - if(vbat_pac) // Apply it if valid - vbat = vbat_pac; - } - - return vbat; + return abs(vbat-vbat_pac) < 200 ? vbat_pac : vbat; } uint16_t getSolarVoltageMV(void) { - uint16_t vbat = getBatteryVoltageMV_STM32(); + uint16_t vsol = getSolarVoltageMV_STM32(); // Get value from STM32 + uint16_t vsol_pac = pac1720_getVsol(); // Get value from PAC1720 - // Get voltage from PAC1720 (PAC1720 returns false redings below 2.35V) - if(vbat >= 2500) - { - uint16_t vsol_pac = pac1720_getVsol(); // Get value from PAC1720 - if(vsol_pac) - return vsol_pac; - } - return samples[0] * vcc_ref * DIVIDER_VSOL / 4096; + return abs(vsol-vsol_pac) < 200 ? vsol_pac : vsol; } uint16_t getUSBVoltageMV(void) diff --git a/tracker/software/drivers/wrapper/padc.h b/tracker/software/drivers/wrapper/padc.h index e957d54..448e68b 100644 --- a/tracker/software/drivers/wrapper/padc.h +++ b/tracker/software/drivers/wrapper/padc.h @@ -8,7 +8,6 @@ void initADC(void); void deinitADC(void); -uint16_t getBatteryVoltageMV_STM32(void); uint16_t getBatteryVoltageMV(void); uint16_t getSolarVoltageMV(void); uint16_t getUSBVoltageMV(void); diff --git a/tracker/software/math/base91.c b/tracker/software/math/base91.c index 5818ef0..a0a570f 100644 --- a/tracker/software/math/base91.c +++ b/tracker/software/math/base91.c @@ -51,7 +51,7 @@ const unsigned char b91_table[91] = { 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '#', '$', '%', '&', '(', ')', '*', '+', ',', '.', '/', ':', ';', '<', '=', - '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~', '"' + '>', '?', '@', '[', ']', '^', '_', '`', '{', '-', '}', '~', '"' }; typedef struct { diff --git a/tracker/software/protocols/aprs/aprs.c b/tracker/software/protocols/aprs/aprs.c index 4dd789e..b06c227 100644 --- a/tracker/software/protocols/aprs/aprs.c +++ b/tracker/software/protocols/aprs/aprs.c @@ -37,22 +37,18 @@ static uint16_t msg_id; * - Number of satellites being used * - Number of cycles where GPS has been lost (if applicable in cycle) */ -uint32_t aprs_encode_position(uint8_t* message, mod_t mod, const aprs_conf_t *config, trackPoint_t *trackPoint) +void aprs_encode_position(ax25_t* packet, const aprs_conf_t *config, trackPoint_t *trackPoint) { char temp[22]; ptime_t date = trackPoint->time; - ax25_t packet; - packet.data = message; - packet.max_size = 512; // TODO: replace 512 with real size - packet.mod = mod; - ax25_init(&packet); - ax25_send_header(&packet, config->callsign, config->ssid, config->path, config->preamble); - ax25_send_byte(&packet, '/'); // Report w/ timestamp, no APRS messaging. $ = NMEA raw data + // Encode header + ax25_send_header(packet, config->callsign, config->ssid, config->path, packet->size > 0 ? 0 : config->preamble); + ax25_send_byte(packet, '/'); // 170915 = 17h:09m:15s zulu (not allowed in Status Reports) chsnprintf(temp, sizeof(temp), "%02d%02d%02dh", date.hour, date.minute, date.second); - ax25_send_string(&packet, temp); + ax25_send_string(packet, temp); // Latitude uint32_t y = 380926 * (90 - trackPoint->gps_lat/10000000.0); @@ -77,7 +73,7 @@ uint32_t aprs_encode_position(uint8_t* message, mod_t mod, const aprs_conf_t *co uint32_t a1 = a / 91; uint32_t a1r = a % 91; - uint8_t gpsFix = trackPoint->gps_lock ? GSP_FIX_OLD : GSP_FIX_CURRENT; + uint8_t gpsFix = trackPoint->gps_lock == GPS_LOCKED ? GSP_FIX_CURRENT : GSP_FIX_OLD; uint8_t src = NMEA_SRC_GGA; uint8_t origin = ORIGIN_PICO; @@ -96,52 +92,51 @@ uint32_t aprs_encode_position(uint8_t* message, mod_t mod, const aprs_conf_t *co temp[12] = ((gpsFix << 5) | (src << 3) | origin) + 33; temp[13] = 0; - ax25_send_string(&packet, temp); + ax25_send_string(packet, temp); // Comments - ax25_send_string(&packet, "SATS "); + ax25_send_string(packet, "SATS "); chsnprintf(temp, sizeof(temp), "%d", trackPoint->gps_sats); - ax25_send_string(&packet, temp); + ax25_send_string(packet, temp); if(trackPoint->gps_lock == GPS_LOCKED) // GPS is locked { // TTFF (Time to first fix) - ax25_send_string(&packet, " TTFF "); + ax25_send_string(packet, " TTFF "); chsnprintf(temp, sizeof(temp), "%d", trackPoint->gps_ttff); - ax25_send_string(&packet, temp); - ax25_send_string(&packet, "sec"); + ax25_send_string(packet, temp); + ax25_send_string(packet, "sec"); } if(trackPoint->gps_lock == GPS_LOSS) { // No GPS lock - ax25_send_string(&packet, " GPS LOSS "); + ax25_send_string(packet, " GPS LOSS "); chsnprintf(temp, sizeof(temp), "%d", ++loss_of_gps_counter); - ax25_send_string(&packet, temp); + ax25_send_string(packet, temp); } else if(trackPoint->gps_lock == GPS_LOWBATT) { // GPS switched off prematurely - ax25_send_string(&packet, " GPS LOWBATT "); + ax25_send_string(packet, " GPS LOWBATT "); chsnprintf(temp, sizeof(temp), "%d", ++loss_of_gps_counter); - ax25_send_string(&packet, temp); + ax25_send_string(packet, temp); } else if(trackPoint->gps_lock == GPS_LOG) { // GPS position from log (because the tracker has been just switched on) - ax25_send_string(&packet, " GPS FROM LOG"); + ax25_send_string(packet, " GPS FROM LOG"); loss_of_gps_counter = 0; } else { loss_of_gps_counter = 0; } - temp[2] = 0; - - ax25_send_byte(&packet, '|'); + ax25_send_byte(packet, '|'); // Sequence ID uint32_t t = trackPoint->id & 0x1FFF; temp[0] = t/91 + 33; temp[1] = t%91 + 33; - ax25_send_string(&packet, temp); + temp[2] = 0; + ax25_send_string(packet, temp); // Telemetry parameter for(uint8_t i=0; i<5; i++) { @@ -159,62 +154,83 @@ uint32_t aprs_encode_position(uint8_t* message, mod_t mod, const aprs_conf_t *co temp[0] = t/91 + 33; temp[1] = t%91 + 33; - ax25_send_string(&packet, temp); + ax25_send_string(packet, temp); } - ax25_send_byte(&packet, '|'); - - ax25_send_footer(&packet); - scramble(&packet); - nrzi_encode(&packet); - - return packet.size; -} - -/** - * Transmit custom experimental packet - */ -uint32_t aprs_encode_experimental(char packetType, uint8_t* message, mod_t mod, const aprs_conf_t *config, uint8_t *data, size_t size) -{ - ax25_t packet; - packet.data = message; - packet.max_size = 512; // TODO: replace 512 with real size - packet.mod = mod; - - // Encode APRS header - ax25_init(&packet); - ax25_send_header(&packet, config->callsign, config->ssid, config->path, config->preamble); - ax25_send_string(&packet, "{{"); - ax25_send_byte(&packet, packetType); - - // Encode message - for(uint16_t i=0; idata = message; - packet->max_size = 8192; // TODO: replace 8192 with real size + packet->data = buffer; + packet->max_size = size; packet->mod = mod; // Encode APRS header ax25_init(packet); } -uint32_t aprs_encode_packet_encodePacket(ax25_t* packet, char packetType, const aprs_conf_t *config, uint8_t *data, size_t size) +void aprs_encode_data_packet(ax25_t* packet, char packetType, const aprs_conf_t *config, uint8_t *data, size_t size, trackPoint_t *trackPoint) { + char temp[13]; + ptime_t date = trackPoint->time; + // Encode header ax25_send_header(packet, config->callsign, config->ssid, config->path, packet->size > 0 ? 0 : config->preamble); + ax25_send_byte(packet, '/'); + + // 170915 = 17h:09m:15s zulu (not allowed in Status Reports) + chsnprintf(temp, sizeof(temp), "%02d%02d%02dh", date.hour, date.minute, date.second); + ax25_send_string(packet, temp); + + // Latitude + uint32_t y = 380926 * (90 - trackPoint->gps_lat/10000000.0); + uint32_t y3 = y / 753571; + uint32_t y3r = y % 753571; + uint32_t y2 = y3r / 8281; + uint32_t y2r = y3r % 8281; + uint32_t y1 = y2r / 91; + uint32_t y1r = y2r % 91; + + // Longitude + uint32_t x = 190463 * (180 + trackPoint->gps_lon/10000000.0); + uint32_t x3 = x / 753571; + uint32_t x3r = x % 753571; + uint32_t x2 = x3r / 8281; + uint32_t x2r = x3r % 8281; + uint32_t x1 = x2r / 91; + uint32_t x1r = x2r % 91; + + // Altitude + uint32_t a = logf(METER_TO_FEET(trackPoint->gps_alt)) / logf(1.002f); + uint32_t a1 = a / 91; + uint32_t a1r = a % 91; + + uint8_t gpsFix = trackPoint->gps_lock == GPS_LOCKED ? GSP_FIX_CURRENT : GSP_FIX_OLD; + uint8_t src = NMEA_SRC_GGA; + uint8_t origin = ORIGIN_PICO; + + temp[0] = (config->symbol >> 8) & 0xFF; + temp[1] = y3+33; + temp[2] = y2+33; + temp[3] = y1+33; + temp[4] = y1r+33; + temp[5] = x3+33; + temp[6] = x2+33; + temp[7] = x1+33; + temp[8] = x1r+33; + temp[9] = config->symbol & 0xFF; + temp[10] = a1+33; + temp[11] = a1r+33; + temp[12] = ((gpsFix << 5) | (src << 3) | origin) + 33; + temp[13] = 0; + + ax25_send_string(packet, temp); ax25_send_byte(packet, packetType); // Encode message @@ -223,10 +239,8 @@ uint32_t aprs_encode_packet_encodePacket(ax25_t* packet, char packetType, const // Encode footer ax25_send_footer(packet); - - return packet->size; } -uint32_t aprs_encode_packet_finalize(ax25_t* packet) +uint32_t aprs_encode_finalize(ax25_t* packet) { scramble(packet); nrzi_encode(packet); @@ -236,91 +250,78 @@ uint32_t aprs_encode_packet_finalize(ax25_t* packet) /** * Transmit message packet */ -uint32_t aprs_encode_message(uint8_t* message, mod_t mod, const aprs_conf_t *config, const char *receiver, const char *text) +void aprs_encode_message(ax25_t* packet, const aprs_conf_t *config, const char *receiver, const char *text) { - ax25_t packet; - packet.data = message; - packet.max_size = 512; // TODO: replace 512 with real size - packet.mod = mod; - - // Encode APRS header char temp[10]; - ax25_init(&packet); - ax25_send_header(&packet, config->callsign, config->ssid, config->path, config->preamble); - ax25_send_byte(&packet, ':'); + + // Encode header + ax25_send_header(packet, config->callsign, config->ssid, config->path, packet->size > 0 ? 0 : config->preamble); + ax25_send_byte(packet, ':'); chsnprintf(temp, sizeof(temp), "%-9s", receiver); - ax25_send_string(&packet, temp); + ax25_send_string(packet, temp); - ax25_send_byte(&packet, ':'); - ax25_send_string(&packet, text); - ax25_send_byte(&packet, '{'); + ax25_send_byte(packet, ':'); + ax25_send_string(packet, text); + ax25_send_byte(packet, '{'); chsnprintf(temp, sizeof(temp), "%d", ++msg_id); - ax25_send_string(&packet, temp); + ax25_send_string(packet, temp); // Encode footer - ax25_send_footer(&packet); - scramble(&packet); - nrzi_encode(&packet); - - return packet.size; + ax25_send_footer(packet); } /** * Transmit APRS telemetry configuration */ -uint32_t aprs_encode_telemetry_configuration(uint8_t* message, mod_t mod, const aprs_conf_t *config, const telemetry_conf_t type) +void aprs_encode_telemetry_configuration(ax25_t* packet, const aprs_conf_t *config, const telemetry_conf_t type) { char temp[4]; - ax25_t packet; - packet.data = message; - packet.max_size = 512; // TODO: replace 512 with real size - packet.mod = mod; - ax25_init(&packet); - ax25_send_header(&packet, config->callsign, config->ssid, config->path, config->preamble); // Header - ax25_send_byte(&packet, ':'); // Message flag + // Encode header + ax25_send_header(packet, config->callsign, config->ssid, config->path, packet->size > 0 ? 0 : config->preamble); + ax25_send_byte(packet, ':'); // Message flag // Callsign - ax25_send_string(&packet, config->callsign); - ax25_send_byte(&packet, '-'); + ax25_send_string(packet, config->callsign); + ax25_send_byte(packet, '-'); chsnprintf(temp, sizeof(temp), "%d", config->ssid); - ax25_send_string(&packet, temp); + ax25_send_string(packet, temp); // Padding uint8_t length = strlen(config->callsign) + (config->ssid/10); for(uint8_t i=length; i<7; i++) - ax25_send_string(&packet, " "); + ax25_send_string(packet, " "); - ax25_send_string(&packet, ":"); // Message separator + ax25_send_string(packet, ":"); // Message separator switch(type) { case CONF_PARM: // Telemetry parameter names - ax25_send_string(&packet, "PARM."); + ax25_send_string(packet, "PARM."); for(uint8_t i=0; i<5; i++) { switch(config->tel[i]) { - case TEL_SATS: ax25_send_string(&packet, "Sats"); break; - case TEL_TTFF: ax25_send_string(&packet, "TTFF"); break; - case TEL_VBAT: ax25_send_string(&packet, "Vbat"); break; - case TEL_VSOL: ax25_send_string(&packet, "Vsol"); break; - case TEL_PBAT: ax25_send_string(&packet, "Pbat"); break; - case TEL_RBAT: ax25_send_string(&packet, "Rbat"); break; - case TEL_HUM: ax25_send_string(&packet, "Humidity"); break; - case TEL_PRESS: ax25_send_string(&packet, "Airpressure"); break; - case TEL_TEMP: ax25_send_string(&packet, "Temperature"); break; + case TEL_SATS: ax25_send_string(packet, "Sats"); break; + case TEL_TTFF: ax25_send_string(packet, "TTFF"); break; + case TEL_VBAT: ax25_send_string(packet, "Vbat"); break; + case TEL_VSOL: ax25_send_string(packet, "Vsol"); break; + case TEL_PBAT: ax25_send_string(packet, "Pbat"); break; + case TEL_RBAT: ax25_send_string(packet, "Rbat"); break; + case TEL_HUM: ax25_send_string(packet, "Humidity"); break; + case TEL_PRESS: ax25_send_string(packet, "Airpressure"); break; + case TEL_TEMP: ax25_send_string(packet, "Temperature"); break; } if(i < 4) - ax25_send_string(&packet, ","); + ax25_send_string(packet, ","); } break; case CONF_UNIT: // Telemetry units - ax25_send_string(&packet, "UNIT."); + ax25_send_string(packet, "UNIT."); for(uint8_t i=0; i<5; i++) { switch(config->tel[i]) { @@ -328,89 +329,87 @@ uint32_t aprs_encode_telemetry_configuration(uint8_t* message, mod_t mod, const break; // No unit case TEL_TTFF: - ax25_send_string(&packet, "sec"); + ax25_send_string(packet, "sec"); break; case TEL_VBAT: case TEL_VSOL: - ax25_send_string(&packet, "V"); + ax25_send_string(packet, "V"); break; case TEL_PBAT: - ax25_send_string(&packet, "W"); + ax25_send_string(packet, "W"); break; case TEL_RBAT: - ax25_send_string(&packet, "Ohm"); + ax25_send_string(packet, "Ohm"); break; case TEL_HUM: - ax25_send_string(&packet, "%"); + ax25_send_string(packet, "%"); break; case TEL_PRESS: - ax25_send_string(&packet, "Pa"); + ax25_send_string(packet, "Pa"); break; case TEL_TEMP: - ax25_send_string(&packet, "degC"); + ax25_send_string(packet, "degC"); break; } if(i < 4) - ax25_send_string(&packet, ","); + ax25_send_string(packet, ","); } break; case CONF_EQNS: // Telemetry conversion parameters - ax25_send_string(&packet, "EQNS."); + ax25_send_string(packet, "EQNS."); for(uint8_t i=0; i<5; i++) { switch(config->tel[i]) { case TEL_SATS: case TEL_TTFF: - ax25_send_string(&packet, "0,1,0"); + ax25_send_string(packet, "0,1,0"); break; case TEL_VBAT: case TEL_VSOL: case TEL_RBAT: - ax25_send_string(&packet, "0,.001,0"); + ax25_send_string(packet, "0,.001,0"); break; case TEL_PBAT: - ax25_send_string(&packet, "0,.001,-4.096"); + ax25_send_string(packet, "0,.001,-4.096"); break; case TEL_HUM: - ax25_send_string(&packet, "0,.1,0"); + ax25_send_string(packet, "0,.1,0"); break; case TEL_PRESS: - ax25_send_string(&packet, "0,12.5,500"); + ax25_send_string(packet, "0,12.5,500"); break; case TEL_TEMP: - ax25_send_string(&packet, "0,.1,-100"); + ax25_send_string(packet, "0,.1,-100"); break; } if(i < 4) - ax25_send_string(&packet, ","); + ax25_send_string(packet, ","); } break; case CONF_BITS: - ax25_send_string(&packet, "BITS.11111111,"); - ax25_send_string(&packet, config->tel_comment); + ax25_send_string(packet, "BITS.11111111,"); + ax25_send_string(packet, config->tel_comment); break; } - ax25_send_footer(&packet); // Footer - scramble(&packet); - nrzi_encode(&packet); - - return packet.size; + + // Encode footer + ax25_send_footer(packet); } diff --git a/tracker/software/protocols/aprs/aprs.h b/tracker/software/protocols/aprs/aprs.h index 09404b6..ad94e94 100644 --- a/tracker/software/protocols/aprs/aprs.h +++ b/tracker/software/protocols/aprs/aprs.h @@ -49,14 +49,13 @@ #define SYM_CAR 0x2F3E #define SYM_SHIP 0x2F73 -uint32_t aprs_encode_position(uint8_t* message, mod_t mod, const aprs_conf_t *config, trackPoint_t *trackPoint); -uint32_t aprs_encode_telemetry_configuration(uint8_t* message, mod_t mod, const aprs_conf_t *config, const telemetry_conf_t type); -uint32_t aprs_encode_message(uint8_t* message, mod_t mod, const aprs_conf_t *config, const char *receiver, const char *text); -uint32_t aprs_encode_experimental(char packetType, uint8_t* message, mod_t mod, const aprs_conf_t *config, uint8_t *data, size_t size); +void aprs_encode_position(ax25_t* packet, const aprs_conf_t *config, trackPoint_t *trackPoint); +void aprs_encode_telemetry_configuration(ax25_t* packet, const aprs_conf_t *config, const telemetry_conf_t type); +void aprs_encode_message(ax25_t* packet, const aprs_conf_t *config, const char *receiver, const char *text); -void aprs_encode_packet_init(ax25_t* packet, uint8_t* message, mod_t mod); -uint32_t aprs_encode_packet_encodePacket(ax25_t* packet, char packetType, const aprs_conf_t *config, uint8_t *data, size_t size); -uint32_t aprs_encode_packet_finalize(ax25_t* packet); +void aprs_encode_init(ax25_t* packet, uint8_t* buffer, uint16_t size, mod_t mod); +void aprs_encode_data_packet(ax25_t* packet, char packetType, const aprs_conf_t *config, uint8_t *data, size_t size, trackPoint_t *trackPoint); +uint32_t aprs_encode_finalize(ax25_t* packet); #endif diff --git a/tracker/software/radio.c b/tracker/software/radio.c index 09ca294..fb09c07 100644 --- a/tracker/software/radio.c +++ b/tracker/software/radio.c @@ -351,7 +351,7 @@ bool transmitOnRadio(radioMSG_t *msg, bool shutdown) // Copy data memcpy(&radio_msg, msg, sizeof(radioMSG_t)); - memcpy(&radio_buffer, msg->buffer, msg->buffer_len); + memcpy(&radio_buffer, msg->buffer, sizeof(radio_buffer)); radio_msg.buffer = radio_buffer; radio_freq = freq; diff --git a/tracker/software/threads/image.c b/tracker/software/threads/image.c index c5374fb..687f55b 100644 --- a/tracker/software/threads/image.c +++ b/tracker/software/threads/image.c @@ -279,7 +279,7 @@ uint8_t gimage_id; // Global image ID (for all image threads) mutex_t camera_mtx; bool camera_mtx_init = false; -void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf, uint8_t image_id, bool redudantTx) +void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf, uint8_t image_id, trackPoint_t* captureLocation, bool redudantTx) { ssdv_t ssdv; uint8_t pkt[SSDV_PKT_SIZE]; @@ -298,7 +298,6 @@ void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf, radioMSG_t msg; uint8_t buffer[conf->packet_spacing ? 8192 : 512]; msg.buffer = buffer; - msg.buffer_len = sizeof(buffer); msg.bin_len = 0; msg.freq = &conf->frequency; msg.power = conf->power; @@ -309,7 +308,7 @@ void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf, msg.mod = conf->protocol == PROT_APRS_AFSK ? MOD_AFSK : MOD_2GFSK; msg.afsk_conf = &(conf->afsk_conf); msg.gfsk_conf = &(conf->gfsk_conf); - aprs_encode_packet_init(&ax25_handle, msg.buffer, msg.mod); + aprs_encode_init(&ax25_handle, buffer, sizeof(buffer), msg.mod); } while(true) @@ -325,12 +324,11 @@ void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf, if(r <= 0) { TRACE_ERROR("SSDV > Premature end of file"); - if(conf->protocol == PROT_APRS_2GFSK || conf->protocol == PROT_APRS_AFSK) msg.bin_len = aprs_encode_packet_finalize(&ax25_handle); - if(msg.bin_len > 0) transmitOnRadio(&msg, false); // Empty buffer + if(conf->protocol == PROT_APRS_2GFSK || conf->protocol == PROT_APRS_AFSK) msg.bin_len = aprs_encode_finalize(&ax25_handle); + if(ax25_handle.size > 0) transmitOnRadio(&msg, false); // Empty buffer if(conf->protocol == PROT_APRS_2GFSK || conf->protocol == PROT_APRS_AFSK) { - aprs_encode_packet_init(&ax25_handle, msg.buffer, msg.mod); - msg.bin_len = 0; + aprs_encode_init(&ax25_handle, buffer, sizeof(buffer), msg.mod); } break; } @@ -340,22 +338,20 @@ void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf, if(c == SSDV_EOI) { TRACE_INFO("SSDV > ssdv_enc_get_packet said EOI"); - if(conf->protocol == PROT_APRS_2GFSK || conf->protocol == PROT_APRS_AFSK) msg.bin_len = aprs_encode_packet_finalize(&ax25_handle); - if(msg.bin_len > 0) transmitOnRadio(&msg, false); // Empty buffer + if(conf->protocol == PROT_APRS_2GFSK || conf->protocol == PROT_APRS_AFSK) msg.bin_len = aprs_encode_finalize(&ax25_handle); + if(ax25_handle.size > 0) transmitOnRadio(&msg, false); // Empty buffer if(conf->protocol == PROT_APRS_2GFSK || conf->protocol == PROT_APRS_AFSK) { - aprs_encode_packet_init(&ax25_handle, msg.buffer, msg.mod); - msg.bin_len = 0; + aprs_encode_init(&ax25_handle, buffer, sizeof(buffer), msg.mod); } break; } else if(c != SSDV_OK) { TRACE_ERROR("SSDV > ssdv_enc_get_packet failed: %i", c); - if(conf->protocol == PROT_APRS_2GFSK || conf->protocol == PROT_APRS_AFSK) msg.bin_len = aprs_encode_packet_finalize(&ax25_handle); - if(msg.bin_len > 0) transmitOnRadio(&msg, false); // Empty buffer + if(conf->protocol == PROT_APRS_2GFSK || conf->protocol == PROT_APRS_AFSK) msg.bin_len = aprs_encode_finalize(&ax25_handle); + if(ax25_handle.size > 0) transmitOnRadio(&msg, false); // Empty buffer if(conf->protocol == PROT_APRS_2GFSK || conf->protocol == PROT_APRS_AFSK) { - aprs_encode_packet_init(&ax25_handle, msg.buffer, msg.mod); - msg.bin_len = 0; + aprs_encode_init(&ax25_handle, buffer, sizeof(buffer), msg.mod); } return; } @@ -367,20 +363,19 @@ void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf, TRACE_INFO("IMG > Encode APRS/SSDV packet"); base91_encode(&pkt[6], pkt_base91, sizeof(pkt)-42); // Sync byte, CRC and FEC of SSDV not transmitted (because its not neccessary inside an APRS packet) - msg.bin_len = aprs_encode_packet_encodePacket(&ax25_handle, '!', &conf->aprs_conf, pkt_base91, strlen((char*)pkt_base91)); + aprs_encode_data_packet(&ax25_handle, 'I', &conf->aprs_conf, pkt_base91, strlen((char*)pkt_base91), captureLocation); if(redudantTx) - msg.bin_len = aprs_encode_packet_encodePacket(&ax25_handle, '!', &conf->aprs_conf, pkt_base91, strlen((char*)pkt_base91)); + aprs_encode_data_packet(&ax25_handle, 'I', &conf->aprs_conf, pkt_base91, strlen((char*)pkt_base91), captureLocation); // Transmit - if(msg.bin_len >= 58000 || conf->packet_spacing) // Transmit if buffer is almost full or if single packet transmission is activated (packet_spacing != 0) + if(ax25_handle.size >= 58000 || conf->packet_spacing) // Transmit if buffer is almost full or if single packet transmission is activated (packet_spacing != 0) { // Transmit packets - msg.bin_len = aprs_encode_packet_finalize(&ax25_handle); + msg.bin_len = aprs_encode_finalize(&ax25_handle); transmitOnRadio(&msg, false); // Initialize new packet buffer - aprs_encode_packet_init(&ax25_handle, msg.buffer, msg.mod); - msg.bin_len = 0; + aprs_encode_init(&ax25_handle, buffer, sizeof(buffer), msg.mod); } break; @@ -487,13 +482,17 @@ THD_FUNCTION(imgThread, arg) { bool camera_found = takePicture(&conf->ssdv_conf, true); gimage_id++; // Increase SSDV image counter + // Get capture location + trackPoint_t captureLocation; + memcpy(&captureLocation, getLastTrackPoint(), sizeof(trackPoint_t)); + // Radio transmission if(camera_found) { TRACE_INFO("IMG > Encode/Transmit SSDV ID=%d", gimage_id-1); - encode_ssdv(conf->ssdv_conf.ram_buffer, conf->ssdv_conf.size_sampled, conf, gimage_id-1, conf->ssdv_conf.redundantTx); + encode_ssdv(conf->ssdv_conf.ram_buffer, conf->ssdv_conf.size_sampled, conf, gimage_id-1, &captureLocation, conf->ssdv_conf.redundantTx); } else { // No camera found TRACE_INFO("IMG > Encode/Transmit SSDV (no cam found) ID=%d", gimage_id-1); - encode_ssdv(noCameraFound, sizeof(noCameraFound), conf, gimage_id-1, conf->ssdv_conf.redundantTx); + encode_ssdv(noCameraFound, sizeof(noCameraFound), conf, gimage_id-1, &captureLocation, conf->ssdv_conf.redundantTx); } } diff --git a/tracker/software/threads/log.c b/tracker/software/threads/log.c index 4b12765..3a48f14 100644 --- a/tracker/software/threads/log.c +++ b/tracker/software/threads/log.c @@ -145,8 +145,8 @@ THD_FUNCTION(logThread, arg) { // Get log from memory - uint16_t pkt[64]; // 16 PositionPoints each 10 bytes - uint8_t pkt_base91[BASE91LEN(128)]; + uint16_t pkt[80]; // 16 PositionPoints each 10 bytes + uint8_t pkt_base91[BASE91LEN(160)]; for(uint16_t t=0; t Encode 16 log points") @@ -156,21 +156,26 @@ THD_FUNCTION(logThread, arg) trackPoint_t log; getNextLogTrackPoint(&log); - TRACE_INFO("date=%02d.%02d. time=%02d:%02d lat=%d lon=%d alt=%d", log.time.day, log.time.month, log.time.hour, log.time.minute, log.gps_lat, log.gps_lon, log.gps_alt); + TRACE_INFO("id=%d date=%04d-%02d-%02d time=%02d:%02d:%02d lat=%d lon=%d alt=%d", log.id, log.time.year, log.time.month, log.time.day, log.time.hour, log.time.minute,log.time.second, log.gps_lat, log.gps_lon, log.gps_alt); + int64_t lat = (int64_t)log.gps_lat + (int64_t)900000000 + 13733; + lat <<= 16; + lat /= 1800000000; + int64_t lon = (int64_t)log.gps_lon + (int64_t)1800000000 + 27466; + lon <<= 16; + lon /= 3600000000; - pkt[i*4+0] = (log.time.minute/10) + (6*log.time.hour) + (144*(log.time.day-1)) + (4464*(log.time.month-1)); // Time/Date of year (1 = 5min, all monthes have 31days, day and month starts at 0) - pkt[i*4+1] = (((uint64_t)((uint64_t)log.gps_lat + 900000000)) * 65535) / 1800000000; // Latitude (get full 16bit resolution over 180°) - pkt[i*4+2] = (((uint64_t)((uint64_t)log.gps_lon + 1800000000)) * 65535) / 3600000000; // Longitude (get full 16bit resolution over 360°) - pkt[i*4+3] = log.gps_alt; // Altitude in meters (cut off first two MSB bytes) - - TRACE_INFO("%04x %04x %04x %04x", pkt[i*4+0], pkt[i*4+1], pkt[i*4+2], pkt[i*4+3]); + uint32_t time = date2UnixTimestamp(log.time) / 1000; + pkt[i*5+0] = time & 0xFFFF; + pkt[i*5+1] = time >> 16; + pkt[i*5+2] = lat; // Latitude (get full 16bit resolution over 180°) + pkt[i*5+3] = lon; // Longitude (get full 16bit resolution over 360°) + pkt[i*5+4] = log.gps_alt; // Altitude in meters (cut off first two MSB bytes) } // Encode radio message radioMSG_t msg; - uint8_t buffer[256]; + uint8_t buffer[512]; msg.buffer = buffer; - msg.buffer_len = sizeof(buffer); msg.freq = &conf->frequency; msg.power = conf->power; @@ -181,10 +186,18 @@ THD_FUNCTION(logThread, arg) msg.afsk_conf = &(conf->afsk_conf); msg.gfsk_conf = &(conf->gfsk_conf); - base91_encode((uint8_t*)pkt, pkt_base91, sizeof(pkt)); // Encode base 91 - msg.bin_len = aprs_encode_experimental('L', msg.buffer, msg.mod, &conf->aprs_conf, pkt_base91, strlen((char*)pkt_base91)); // Encode APRS + ax25_t ax25_handle; - transmitOnRadio(&msg, true); // Transmit packet + // Encode Base91 + base91_encode((uint8_t*)pkt, pkt_base91, sizeof(pkt)); + + // Encode and transmit log packet + aprs_encode_init(&ax25_handle, buffer, sizeof(buffer), msg.mod); + aprs_encode_data_packet(&ax25_handle, 'L', &conf->aprs_conf, pkt_base91, strlen((char*)pkt_base91), getLastTrackPoint()); // Encode packet + msg.bin_len = aprs_encode_finalize(&ax25_handle); + + // Transmit packet + transmitOnRadio(&msg, true); break; default: diff --git a/tracker/software/threads/position.c b/tracker/software/threads/position.c index 40a6fa3..6b24888 100644 --- a/tracker/software/threads/position.c +++ b/tracker/software/threads/position.c @@ -130,7 +130,6 @@ THD_FUNCTION(posThread, arg) { radioMSG_t msg; uint8_t buffer[256]; msg.buffer = buffer; - msg.buffer_len = sizeof(buffer); msg.freq = &conf->frequency; msg.power = conf->power; @@ -143,7 +142,12 @@ THD_FUNCTION(posThread, arg) { msg.gfsk_conf = &(conf->gfsk_conf); msg.afsk_conf = &(conf->afsk_conf); - msg.bin_len = aprs_encode_position(msg.buffer, msg.mod, &(conf->aprs_conf), trackPoint); // Encode packet + ax25_t ax25_handle; + + // Encode and transmit position packet + aprs_encode_init(&ax25_handle, buffer, sizeof(buffer), msg.mod); + aprs_encode_position(&ax25_handle, &(conf->aprs_conf), trackPoint); // Encode packet + msg.bin_len = aprs_encode_finalize(&ax25_handle); transmitOnRadio(&msg, true); // Telemetry encoding parameter transmission @@ -162,7 +166,11 @@ THD_FUNCTION(posThread, arg) { chThdSleepMilliseconds(5000); // Take a litte break between the package transmissions const telemetry_conf_t tel_conf[] = {CONF_PARM, CONF_UNIT, CONF_EQNS, CONF_BITS}; - msg.bin_len = aprs_encode_telemetry_configuration(msg.buffer, msg.mod, &(conf->aprs_conf), tel_conf[current_conf_count]); // Encode packet + + // Encode and transmit telemetry config packet + aprs_encode_init(&ax25_handle, buffer, sizeof(buffer), msg.mod); + aprs_encode_telemetry_configuration(&ax25_handle, &conf->aprs_conf, tel_conf[current_conf_count]); + msg.bin_len = aprs_encode_finalize(&ax25_handle); transmitOnRadio(&msg, true); current_conf_count++; diff --git a/tracker/software/threads/threads.c b/tracker/software/threads/threads.c index eea9e11..3077367 100644 --- a/tracker/software/threads/threads.c +++ b/tracker/software/threads/threads.c @@ -1,7 +1,6 @@ #include "ch.h" #include "hal.h" -#include "threads.h" #include "tracking.h" #include "watchdog.h" #include "pi2c.h" @@ -14,5 +13,6 @@ void start_essential_threads(void) { pi2cInit(); // Initialize I2C pac1720_init(); // Initialize current measurement init_tracking_manager(false); // Initialize tracking manager (without GPS, GPS is initialized if needed by position thread) + chThdSleepMilliseconds(50); // Wait for tracking manager to initialize } diff --git a/tracker/software/threads/tracking.c b/tracker/software/threads/tracking.c index b76ae3f..2a844e3 100644 --- a/tracker/software/threads/tracking.c +++ b/tracker/software/threads/tracking.c @@ -154,6 +154,7 @@ THD_FUNCTION(trackingThread, arg) { if(lastLogPoint != NULL) { // If there has been stored a trackpoint, then get the last know GPS fix TRACE_INFO("TRAC > Found track point in flash memory ID=%d", lastLogPoint->id); + id = lastLogPoint->id+1; lastTrackPoint->gps_lat = lastLogPoint->gps_lat; lastTrackPoint->gps_lon = lastLogPoint->gps_lon; lastTrackPoint->gps_alt = lastLogPoint->gps_alt; @@ -295,7 +296,7 @@ THD_FUNCTION(trackingThread, arg) { // Mark GPS loss (or low batt, GPS switch off) if(tracking_useGPS) - tp->gps_lock = batt < gps_off_vbat ? GPS_LOWBATT : GPS_LOSS; + tp->gps_lock = batt < gps_off_vbat || batt < gps_on_vbat ? GPS_LOWBATT : GPS_LOSS; else tp->gps_lock = GPS_OFF; tp->gps_sats = 0; diff --git a/tracker/software/threads/tracking.h b/tracker/software/threads/tracking.h index ae2203a..0331d7a 100644 --- a/tracker/software/threads/tracking.h +++ b/tracker/software/threads/tracking.h @@ -21,7 +21,7 @@ typedef struct { gpsLock_t gps_lock; // 0: locked, 1: GPS loss, 2: low power (switched off), 3: taken from log, 4: GPS switch off permanently int32_t gps_lat; // Latitude in °*10^7 int32_t gps_lon; // Longitude in °*10^7 - int32_t gps_alt; // Altitude in meter + uint16_t gps_alt; // Altitude in meter uint8_t gps_sats; // Satellites used for solution uint8_t gps_ttff; // Time to first fix in seconds @@ -37,7 +37,7 @@ typedef struct { uint16_t air_hum; // Rel. humidity in %*10 (in 0.1%) int16_t air_temp; // Temperature in degC*100 (in 0.01°C) - int8_t id_image; // Last image ID (this is important because it will set the image counter at reset so the last image wont get overwritten with the same image ID) + uint8_t id_image; // Last image ID (this is important because it will set the image counter at reset so the last image wont get overwritten with the same image ID) } trackPoint_t; void waitForNewTrackPoint(void); diff --git a/tracker/software/types.h b/tracker/software/types.h index a75103b..0b2eba8 100644 --- a/tracker/software/types.h +++ b/tracker/software/types.h @@ -98,7 +98,6 @@ typedef struct { typedef struct { // Radio message type uint8_t* buffer; // Message (data) - uint16_t buffer_len; // Buffer size (in bytes) uint32_t bin_len; // Binary length (it bits) int8_t power; // Power in dBm mod_t mod; // Modulation