kopia lustrzana https://github.com/DL7AD/pecanpico9
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 ServerDevelop
rodzic
8a4d8ff5ef
commit
1ad5b02eb7
|
@ -2,3 +2,5 @@ build
|
|||
.dep
|
||||
*.pyc
|
||||
__pycache__
|
||||
*.sqlite
|
||||
decoder/html/images/
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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,164 +164,7 @@ 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)
|
||||
|
@ -212,15 +174,3 @@ else: # stdin or serial
|
|||
|
||||
received_data(data)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
?>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
|
@ -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>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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())
|
||||
|
|
@ -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]))
|
||||
|
|
@ -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]);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
||||
// Get voltage from PAC1720 (PAC1720 returns false redings below 2.35V)
|
||||
if(vbat >= 2500)
|
||||
{
|
||||
uint16_t vbat = getBatteryVoltageMV_STM32(); // Get value from STM32
|
||||
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();
|
||||
|
||||
// Get voltage from PAC1720 (PAC1720 returns false redings below 2.35V)
|
||||
if(vbat >= 2500)
|
||||
{
|
||||
uint16_t vsol = getSolarVoltageMV_STM32(); // Get value from STM32
|
||||
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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue