From 1ad5b02eb7add732f9e593e308bd8f1ec8ceb368 Mon Sep 17 00:00:00 2001 From: Sven Steudte Date: Sat, 7 Oct 2017 06:55:11 +0200 Subject: [PATCH] Modified Base91 characters Modified APRS format of Image and Log packets Implemented image Added memory address to log command (Serial USB console) Added failsafe to ADC measurements (if PAC1720 fails) Improved radio buffer management (less memory needed now) Changed tracking manager: Last sequence ID read from memory now (at startup) Implemented Webserver for displaying Position, Log and Images packets Implemented SSDV Decoding Server --- .gitignore | 2 + decoder/base91.py | 2 +- decoder/decoder.py | 322 ++++++++++-------------- decoder/html/data.php | 13 + decoder/html/database.class.php | 48 ++++ decoder/html/index.php | 111 ++++++++ decoder/image.py | 125 +++++++++ decoder/position.py | 83 ++++++ tracker/software/config.c | 31 +-- tracker/software/debug.c | 6 +- tracker/software/drivers/wrapper/padc.c | 36 ++- tracker/software/drivers/wrapper/padc.h | 1 - tracker/software/math/base91.c | 2 +- tracker/software/protocols/aprs/aprs.c | 267 ++++++++++---------- tracker/software/protocols/aprs/aprs.h | 13 +- tracker/software/radio.c | 2 +- tracker/software/threads/image.c | 45 ++-- tracker/software/threads/log.c | 41 +-- tracker/software/threads/position.c | 14 +- tracker/software/threads/threads.c | 2 +- tracker/software/threads/tracking.c | 3 +- tracker/software/threads/tracking.h | 4 +- tracker/software/types.h | 1 - 23 files changed, 758 insertions(+), 416 deletions(-) create mode 100644 decoder/html/data.php create mode 100644 decoder/html/database.class.php create mode 100644 decoder/html/index.php create mode 100644 decoder/image.py create mode 100644 decoder/position.py 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