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
Develop
Sven Steudte 2017-10-07 06:55:11 +02:00
rodzic 8a4d8ff5ef
commit 1ad5b02eb7
23 zmienionych plików z 758 dodań i 416 usunięć

2
.gitignore vendored
Wyświetl plik

@ -2,3 +2,5 @@ build
.dep
*.pyc
__pycache__
*.sqlite
decoder/html/images/

Wyświetl plik

@ -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))

Wyświetl plik

@ -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)

Wyświetl plik

@ -0,0 +1,13 @@
<?php
require "database.class.php";
$data = array();
$db = new MyDB();
foreach($db->getCallsigns() as $callsign)
foreach($db->getRoute($callsign) as $point)
$data[] = $point;
header("Content-Type: application/json");
echo json_encode($data);
?>

Wyświetl plik

@ -0,0 +1,48 @@
<?php
class MyDB extends SQLite3 {
function __construct() {
$this->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;
}
}
?>

Wyświetl plik

@ -0,0 +1,111 @@
<?php
require "database.class.php";
$db = new MyDB();
?>
<!DOCTYPE html>
<html>
<head>
<style>
html {
font: 10pt Monospace;
}
#map {
position: absolute;
top: 30px;
left: 0;
bottom: 50px;
right: 0;
}
</style>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js" type="text/javascript"></script>
</head>
<body>
<div id="map"></div>
<script>
var map;
var items = [];
function drawItems() {
$.getJSON("data.php", function(path) {
// Remove old items
while(true) {
try {
items.pop().setMap(null);
} catch(err) {
break;
}
}
// Add new items on map
var last = null;
$.each(path, function(index, value) {
// Point
if(value.img) {
var marker = new google.maps.Marker({
position: new google.maps.LatLng(value.lat, value.lng),
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: value.img ? 4 : 1.5,
strokeColor: value.img ? '#000080' : value.org == 'log' ? '#FF0000' : '#008000'
},
map: map,
});
items.push(marker);
// Image Info Window
var infowindow = new google.maps.InfoWindow({
content: '<img src="' + value.img + '" />'
});
marker.addListener('mouseover', function() {
infowindow.open(map, marker);
});
marker.addListener('mouseout', function() {
infowindow.close();
});
}
// Line between points
if(last)
var line = new google.maps.Polyline({
path: [last,value],
geodesic: true,
strokeColor: last.org == 'log' || value.org == 'log' ? '#FF0000' : '#008000',
strokeOpacity: 0.4,
strokeWeight: 5,
map: map
});
items.push(line);
last = value;
});
});
}
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
zoom: 9,
center: new google.maps.LatLng(53.2,7.1),
gestureHandling: 'greedy'
});
drawItems();
window.setInterval(drawItems, 10000);
}
</script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCrrxJc6mu5DjFZVHiFqhFMO7JJg2g89Y8&callback=initMap"></script>
</body>
</html>

125
decoder/image.py 100644
Wyświetl plik

@ -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())

Wyświetl plik

@ -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]))

Wyświetl plik

@ -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]);
}

Wyświetl plik

@ -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,

Wyświetl plik

@ -2,10 +2,9 @@
#include "hal.h"
#include "config.h"
#include "padc.h"
#include "pac1720.h"
#include "debug.h"
#include "pi2c.h"
#include <stdlib.h>
#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)

Wyświetl plik

@ -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);

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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; i<size; i++)
ax25_send_byte(&packet, data[i]);
ax25_send_byte(packet, '|');
// Encode footer
ax25_send_footer(&packet);
scramble(&packet);
nrzi_encode(&packet);
return packet.size;
ax25_send_footer(packet);
}
/**
* Transmit custom data packet (the methods aprs_encode_data allow multiple APRS packets in a row without preable being sent)
*/
void aprs_encode_packet_init(ax25_t* packet, uint8_t* message, mod_t mod)
void aprs_encode_init(ax25_t* packet, uint8_t* buffer, uint16_t size, mod_t mod)
{
packet->data = 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);
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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;

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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<sizeof(pkt_base91); t++) pkt_base91[t] = 0; // Deleting buffer
TRACE_INFO("LOG > 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:

Wyświetl plik

@ -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++;

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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);

Wyświetl plik

@ -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