Updated APRS protocols and much more

- Removed secondary decoder
- Updated image encoding at tracker firmware
- Updated image encoding at decoder software
- Implemented more sophisticated website
- Added error transmission
- Adjusted MCU speed to the highest rate
- Implemented configuration that allows GPS to be switched on permanently at a certain battery voltage
- Decreased humidity sensor accuray
- Imcreased voltage measurement accuracy
- Added temperature measurements of STM32 and Si4464 to TrackPoint struct
- Added light intensity measurements of OV5640 to TrackPoint struct
- Cleaned up (removed unused things)
- Implemented more accurate measurements for PAC1720
- Implemented failure detection for PAC1720 and ublox chips
- Fixed bug in Si4464 driver which used power when switched off
- Added more data readouts for GPS receiver (pDOP, gpsFixOK)
- Changed way of determination if GPS fix is good
- Implemented temperature measurement of STM32
- Added error detection of I2C bus
- Removed Milliseconds from ptime_t
- Fixed bug in timestamp conversion
- Removed RBAT measurement implementation (it was very inaccurate)
- Avoid radio being switched off when next packet is waiting for being transmitted
- Tidied up tracking manager
master
Sven Steudte 2018-01-09 06:01:19 +01:00
rodzic 1a925b5ecb
commit c745ef50c9
61 zmienionych plików z 2148 dodań i 1239 usunięć

Wyświetl plik

@ -1,6 +1,6 @@
#!/usr/bin/python3
import serial,re
import serial,re,io
import base91
import sys
import argparse
@ -12,11 +12,10 @@ import position
# Parse arguments from terminal
parser = argparse.ArgumentParser(description='APRS/SSDV decoder')
parser.add_argument('-c', '--call', help='Callsign of the station', default='N0CALL')
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('-c', '--call', help='Callsign of the station', default='DL7AD')
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')
parser.add_argument('-v', '--verbose', help='Activates more debug messages', action="store_true")
args = parser.parse_args()
# Open SQLite database
@ -25,36 +24,58 @@ sqlite.cursor().execute("""
CREATE TABLE IF NOT EXISTS position
(
call TEXT,
time INTEGER,
rxtime INTEGER,
org TEXT,
lat FLOAT,
lon FLOAT,
alt INTEGER,
isnew INTEGER,
comment TEXT,
sequ INTEGER,
tel1 INTEGER,
tel2 INTEGER,
tel3 INTEGER,
tel4 INTEGER,
tel5 INTEGER,
PRIMARY KEY (call, time)
reset INTEGER,
id INTEGER,
time INTEGER,
adc_vsol INTEGER,
adc_vbat INTEGER,
pac_vsol INTEGER,
pac_vbat INTEGER,
pac_pbat INTEGER,
pac_psol INTEGER,
light_intensity INTEGER,
gps_time INTEGER,
gps_lock INTEGER,
gps_sats INTEGER,
gps_ttff INTEGER,
gps_pdop INTEGER,
gps_alt INTEGER,
gps_lat INTEGER,
gps_lon INTEGER,
sen_i1_press INTEGER,
sen_e1_press INTEGER,
sen_e2_press INTEGER,
sen_i1_temp INTEGER,
sen_e1_temp INTEGER,
sen_e2_temp INTEGER,
sen_i1_hum INTEGER,
sen_e1_hum INTEGER,
sen_e2_hum INTEGER,
stm32_temp INTEGER,
si4464_temp INTEGER,
sys_time INTEGER,
sys_error INTEGER
)
""")
sqlite.cursor().execute("""
CREATE TABLE IF NOT EXISTS image
(
id INTEGER,
call TEXT,
time INTEGER,
rxtime INTEGER,
imageID INTEGER,
packetID INTEGER,
lat FLOAT,
lon FLOAT,
alt INTEGER,
data1 TEXT,
data2 TEXT,
crc TEXT,
PRIMARY KEY (call, time, imageID, packetID)
data TEXT,
PRIMARY KEY (call,id,packetID)
)
""")
@ -66,36 +87,35 @@ def received_data(data):
# 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|J|L)(.*)", data)
all = re.search("(.*)\>APECAN(.*?):", data)
pos = re.search("(.*)\>APECAN(.*?):\!(.{13})(.*?)\|(.*)\|", data)
dat = re.search("(.*)\>APECAN(.*?):\{\{(I|L)(.*)", data)
if all:
if pos or dat:
# Debug
print('='*100)
print(data)
print('-'*100)
if args.verbose:
print('='*100)
print(data.strip())
print('-'*100)
call = all.group(1).split(' ')[-1]
rxer = all.group(2).split(',')[-1]
if not len(rxer): rxer = args.call
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)
posi = pos.group(3)
comm = pos.group(4)
tel = pos.group(5)
position.insert_position(sqlite, call, posi, comm, tel)
elif dat: # Data packet
typ = dat.group(5)
data_b91 = dat.group(6)
data = base91.decode(data_b91) # Decode Base91
typ = dat.group(3)
data = base91.decode(dat.group(4)) # Decode Base91
if typ is 'I' or typ is 'J': # Image packet
image.insert_image(sqlite, rxer, call, tim, posi, data, typ, args.server, args.grouping)
if typ is 'I': # Image packet
image.insert_image(sqlite, rxer, call, data)
elif typ is 'L': # Log packet
position.insert_log(sqlite, call, data)
@ -111,12 +131,12 @@ if args.device == 'I': # Source APRS-IS
print('exit...')
sys.exit(1)
wdg = time.time() + 1 # Connection watchdog
wdg = time.time() + 10 # Connection watchdog
buf = ''
while True:
# Read data
try:
buf += tn.read_eager().decode('ascii')
buf += tn.read_eager().decode('charmap')
except EOFError: # Server has connection closed
wdg = 0 # Tell watchdog to restart connection
except UnicodeDecodeError:
@ -146,7 +166,7 @@ if args.device == 'I': # Source APRS-IS
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
wdg = time.time() + 10
except Exception as e:
print('Could not connect to APRS-IS: %s' % str(e))
print('Try again...')
@ -156,8 +176,7 @@ if args.device == 'I': # Source APRS-IS
elif args.device is '-': # Source stdin
while True:
data = sys.stdin.readline()
received_data(data)
received_data(sys.stdin.readline())
else: # Source Serial connection

Wyświetl plik

@ -0,0 +1,51 @@
<?php
require_once "Tracker.class.php";
class Database extends SQLite3 {
private static $instance = null;
function __construct() {
$this->open("/src/pecanpico9/decoder/decoder.sqlite");
if($this->lastErrorCode())
echo $this->lastErrorMsg();
}
static function getInstance() {
if(self::$instance == null)
self::$instance = new Database();
return self::$instance;
}
function close() {
parent::close();
}
function getTracker() {
$tracker = array();
$query = $this->query("
SELECT call,MAX(rxtime)
FROM (
SELECT call,rxtime FROM position
UNION ALL
SELECT call,rxtime FROM image
)
GROUP BY call
ORDER BY rxtime DESC
");
while($row = $query->fetchArray(SQLITE3_ASSOC))
$tracker[] = new Tracker($row['call']);
return $tracker;
}
}
?>

Wyświetl plik

@ -0,0 +1,19 @@
<?php
class Image {
function __construct($sqlResult) {
$this->id = $sqlResult['id'];
$this->call = $sqlResult['call'];
$this->time_first = $sqlResult['time_first'];
$this->time_last = $sqlResult['time_last'];
$this->imageID = $sqlResult['imageID'];
$this->packetID = $sqlResult['packetID'];
$this->count = $sqlResult['count'];
}
}
?>

Wyświetl plik

@ -0,0 +1,168 @@
<?php
class Telemetry {
/*const TYPE_INT = 0; // Integer
const TYPE_STR = 1; // String
const TYPE_HTML = 2; // HTML
const LOC_INT = 0; // Internal
const LOC_EXT = 1; // External
const LOC_INT1 = 0; // Internal 1
const LOC_EXT1 = 1; // External 1
const LOC_EXT2 = 2; // External 2
const COL_GREEN = "#008000";
const COL_ORANGE = "#CC6600";
const COL_RED = "#FF0000";*/
function __construct($sqlResult) {
$this->reset = $sqlResult['reset'];
$this->id = $sqlResult['id'];
$this->rxtime = $sqlResult['rxtime'];
$this->call = $sqlResult['call'];
$this->adc_vsol = $sqlResult['adc_vsol'];
$this->adc_vbat = $sqlResult['adc_vbat'];
$this->pac_vsol = $sqlResult['pac_vsol'];
$this->pac_vbat = $sqlResult['pac_vbat'];
$this->pac_pbat = $sqlResult['pac_pbat'];
$this->pac_psol = $sqlResult['pac_psol'];
$this->gps_time = $sqlResult['gps_time'];
$this->gps_lock = $sqlResult['gps_lock'];
$this->gps_sats = $sqlResult['gps_sats'];
$this->gps_ttff = $sqlResult['gps_ttff'];
$this->gps_pdop = $sqlResult['gps_pdop'];
$this->gps_alt = $sqlResult['gps_alt'];
$this->gps_lat = $sqlResult['gps_lat'];
$this->gps_lon = $sqlResult['gps_lon'];
$this->sen_i1_press = $sqlResult['sen_i1_press'];
$this->sen_e1_press = $sqlResult['sen_e1_press'];
$this->sen_e2_press = $sqlResult['sen_e2_press'];
$this->sen_i1_temp = $sqlResult['sen_i1_temp'];
$this->sen_e1_temp = $sqlResult['sen_e1_temp'];
$this->sen_e2_temp = $sqlResult['sen_e2_temp'];
$this->sen_i1_hum = $sqlResult['sen_i1_hum'];
$this->sen_e1_hum = $sqlResult['sen_e1_hum'];
$this->sen_e2_hum = $sqlResult['sen_e2_hum'];
$this->stm32_temp = $sqlResult['stm32_temp'];
$this->si4464_temp = $sqlResult['si4464_temp'];
$this->light_intensity = $sqlResult['light_intensity'];
$this->sys_time = $sqlResult['sys_time'];
$this->sys_error = $sqlResult['sys_error'];
$this->err_i2c1 = ($this->sys_error >> 0) & 0x1;
$this->err_i2c2 = ($this->sys_error >> 1) & 0x1;
$this->err_eva7m = ($this->sys_error >> 2) & 0x1;
$this->err_pac1720 = ($this->sys_error >> 3) & 0x3;
$this->err_ov5640 = ($this->sys_error >> 5) & 0x3;
$this->err_bme280_i1 = ($this->sys_error >> 8) & 0x1;
$this->err_bme280_e1 = ($this->sys_error >> 9) & 0x1;
$this->err_bme280_e2 = ($this->sys_error >> 10) & 0x1;
}
/*function getOV9655Error($type) {
$error = ($this->sys_error >> 4) & 0x3;
if($type == self::TYPE_INT)
return $error;
switch($error) {
case 0: return $this->colorize($type, self::COL_GREEN, "OK");
case 1: return $this->colorize($type, self::COL_RED, "I2C Error - Camera not found");
case 2: return $this->colorize($type, self::COL_RED, "DMA abort - last buffer segment");
case 3: return $this->colorize($type, self::COL_RED, "DMA FIFO error");
case 4: return $this->colorize($type, self::COL_RED, "DMA stream transfer error");
case 5: return $this->colorize($type, self::COL_RED, "DMA direct mode error");
}
}
function getGPSStatus($type) {
if($type == self::TYPE_INT)
return $this->gps_lock;
switch($this->gps_lock) {
case 0: return $this->colorize($type, self::COL_GREEN, "GPS locked");
case 1: return $this->colorize($type, self::COL_GREEN, "GPS locked - kept switched on");
case 2: return $this->colorize($type, self::COL_RED, "GPS loss");
case 3: return $this->colorize($type, self::COL_ORANGE, "Low Batt before switched on");
case 4: return $this->colorize($type, self::COL_ORANGE, "Low Batt while switched on");
case 5: return $this->colorize($type, self::COL_GREEN, "Data from memory");
case 6: return $this->colorize($type, self::COL_RED, "GPS communication error");
}
}
function getEVA7MError($type) {
$error = ($this->sys_error >> 2) & 0x1;
if($type == self::TYPE_INT)
return $error;
switch($error) {
case 0: return $this->colorize($type, self::COL_GREEN, "OK");
case 1: return $this->colorize($type, self::COL_RED, "Fail");
}
}
function getI2cError($loc, $type) {
$error = $loc == self::LOC_INT ? $this->sys_error & 0x1 : ($this->sys_error >> 1) & 0x1;
if($type == self::TYPE_INT)
return $error;
switch($error) {
case 0: return $this->colorize($type, self::COL_GREEN, "OK");
case 1: return $this->colorize($type, self::COL_RED, "Fail");
}
}
function getPAC1720Error($type) {
$error = ($this->sys_error >> 3) & 0x1;
if($type == self::TYPE_INT)
return $error;
switch($error) {
case 0: return $this->colorize($type, self::COL_GREEN, "OK");
case 1: return $this->colorize($type, self::COL_RED, "Fail");
}
}
function getBME280Error($type, $loc) {
switch($loc) {
case self::LOC_INT1: $error = ($this->sys_error >> 7) & 0x1; break;
case self::LOC_EXT1: $error = ($this->sys_error >> 8) & 0x1; break;
case self::LOC_EXT2: $error = ($this->sys_error >> 9) & 0x1; break;
}
if($type == self::TYPE_INT)
return $error;
switch($error) {
case 0: return $this->colorize($type, self::COL_GREEN, "OK");
case 1: return $this->colorize($type, self::COL_RED, "Fail");
}
}
private function colorize($type, $color, $str) {
if($type == self::TYPE_HTML)
return "<font color=\"$color\">$str</font>";
elseif($type == self::TYPE_STR)
return $str;
}*/
}
?>

Wyświetl plik

@ -0,0 +1,133 @@
<?php
require_once "Database.class.php";
require_once "Telemetry.class.php";
require_once "Image.class.php";
class Tracker {
private $call;
function __construct($call) {
$this->call = $call;
}
function getLastActivity() {
$act = array();
$stmt = Database::getInstance()->prepare("
SELECT * FROM (
SELECT CAST(STRFTIME('%s', 'now') as DECIMAL) - rxtime as lasttime,'pos' as type FROM position WHERE call = :call
UNION ALL
SELECT CAST(STRFTIME('%s', 'now') as DECIMAL) - rxtime as lasttime,'img' as type FROM image WHERE call = :call
)
GROUP BY type
ORDER BY lasttime DESC
");
$stmt->bindValue(':call', $this->call, SQLITE3_TEXT);
$query = $stmt->execute();
while($row = $query->fetchArray(SQLITE3_ASSOC))
$act[$row['type']] = $row['lasttime'];
return $act;
}
function getPictures($from, $to=NULL) {
if(is_null($to))
$to = time() + 1;
if($from > $to)
return array(); // Error $from is larger than $to
if($from - $to > 64281600)
$from = $from + 64281600; // Max. 744 days (2 non leap years + 14 weeks)
$stmt = Database::getInstance()->prepare("
SELECT t.id,call,MIN(rxtime) as time_first,MAX(rxtime) as time_last,
COUNT(*) as count,imageID,MAX(packetID) as packetID
FROM (
SELECT id
FROM image
WHERE :from <= rxtime
AND rxtime <= :to
AND call = :call
GROUP BY id
ORDER BY id ASC
) as s
JOIN image t ON t.id = s.id
GROUP BY t.id
");
$stmt->bindValue(':call', $this->call, SQLITE3_TEXT);
$stmt->bindValue(':from', $from, SQLITE3_INTEGER);
$stmt->bindValue(':to', $to, SQLITE3_INTEGER);
$query = $stmt->execute();
$pics = array();
while($row = $query->fetchArray(SQLITE3_ASSOC))
$pics[] = new Image($row);
return $pics;
}
function getLastTelemetry() {
$stmt = Database::getInstance()->prepare("SELECT * FROM position WHERE call = :call ORDER BY rxtime DESC LIMIT 1");
$stmt->bindValue(':call', $this->call, SQLITE3_TEXT);
$query = $stmt->execute();
return new Telemetry($query->fetchArray(SQLITE3_ASSOC));
}
function getTelemetry($from, $to=NULL) {
if(is_null($to))
$to = time() + 1;
if($from > $to)
return array(); // Error $from is larger than $to
if($from - $to > 64281600)
$from = $from + 64281600; // Max. 744 days (2 non leap years + 14 weeks)
$stmt = Database::getInstance()->prepare("SELECT * FROM position WHERE :from <= rxtime AND rxtime <= :to AND call = :call ORDER BY rxtime ASC");
$stmt->bindValue(':call', $this->call, SQLITE3_TEXT);
$stmt->bindValue(':from', $from, SQLITE3_INTEGER);
$stmt->bindValue(':to', $to, SQLITE3_INTEGER);
$query = $stmt->execute();
$datasets = array();
while($row = $query->fetchArray(SQLITE3_ASSOC)) {
$datasets[] = new Telemetry($row);
}
return $datasets;
}
function getPacketCount() {
$stmt = Database::getInstance()->prepare("SELECT *
FROM (
SELECT COUNT(*) as cnt86400,'pos' as type FROM position WHERE call = :call AND rxtime+86400 > CAST(STRFTIME('%s', 'now') as DECIMAL)
UNION ALL
SELECT COUNT(*) as cnt86400,'img' as type FROM image WHERE call = :call AND rxtime+86400 > CAST(STRFTIME('%s', 'now') as DECIMAL)
) AS a
JOIN (
SELECT COUNT(*) as cnt3600,'pos' as type FROM position WHERE call = :call AND rxtime+3600 > CAST(STRFTIME('%s', 'now') as DECIMAL)
UNION ALL
SELECT COUNT(*) as cnt3600,'img' as type FROM image WHERE call = :call AND rxtime+3600 > CAST(STRFTIME('%s', 'now') as DECIMAL)
) AS b
JOIN (
SELECT COUNT(*) as cnt300,'pos' as type FROM position WHERE call = :call AND rxtime+300 > CAST(STRFTIME('%s', 'now') as DECIMAL)
UNION ALL
SELECT COUNT(*) as cnt300,'img' as type FROM image WHERE call = :call AND rxtime+300 > CAST(STRFTIME('%s', 'now') as DECIMAL)
) AS c
WHERE a.type = b.type
AND a.type = c.type
");
$stmt->bindValue(':call', $this->call, SQLITE3_TEXT);
$query = $stmt->execute();
$ret = array();
while($row = $query->fetchArray(SQLITE3_ASSOC))
$ret[$row['type']] = $row;
return $ret;
}
function getCall() {
return $this->call;
}
}
?>

Wyświetl plik

@ -0,0 +1,16 @@
<?php
error_reporting(E_ALL ^ E_NOTICE);
require_once "../Tracker.class.php";
header("Content-Type: application/json");
$tracker = new Tracker($_GET['call']);
echo json_encode(array(
"telemetry" => $tracker->getTelemetry($_GET['from']),
"images" => $tracker->getPictures($_GET['from']),
"lastActivity" => $tracker->getLastActivity(),
"packetCount" => $tracker->getPacketCount(),
"time" => time()
));
?>

Wyświetl plik

@ -1,13 +0,0 @@
<?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

@ -1,49 +0,0 @@
<?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
AND position.isnew = 1
-- AND position.time + 86400*14 > CAST(strftime('%s', 'now') as DECIMAL)
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,3 @@
<?php
$db->close();
?>

Wyświetl plik

@ -0,0 +1,23 @@
<?php
error_reporting(E_ALL ^ E_NOTICE);
require_once "Database.class.php";
require_once "helper.inc.php";
?>
<!DOCTYPE html>
<html>
<head>
<title>Balloon Health</title>
<?php
$db = Database::getInstance();
if(array_key_exists('call', $_GET)) {
$tracker = new Tracker($_GET['call']);
$tel = $tracker->getLastTelemetry();
}
$range = isset($_GET['range']) && is_numeric($_GET['range']) ? $_GET['range'] : 86400;
?>
<link href="style.css" rel="stylesheet" type="text/css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

Wyświetl plik

@ -0,0 +1,11 @@
<?php
function time_format($time) {
if(!$time)
return "never";
if($time < 3600)
return floor($time/60) . "m" . ($time%60) . "s ago";
else
return floor($time/3600) . "h" . floor(($time/60)%60) . "m ago";
}
?>

Wyświetl plik

@ -0,0 +1,124 @@
<?php
include "header.inc.php";
?>
<script type="text/javascript">
lastrxtime = <?=time()-$range?>;
call = '<?=$_GET['call']?>';
var last = null;
function number_format(number, decimals, decPoint, thousandsSep) { // eslint-disable-line camelcase
// discuss at: http://locutus.io/php/number_format/
// original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
// improved by: Kevin van Zonneveld (http://kvz.io)
// improved by: davook
// improved by: Brett Zamir (http://brett-zamir.me)
// improved by: Brett Zamir (http://brett-zamir.me)
// improved by: Theriault (https://github.com/Theriault)
// improved by: Kevin van Zonneveld (http://kvz.io)
// bugfixed by: Michael White (http://getsprink.com)
// bugfixed by: Benjamin Lupton
// bugfixed by: Allan Jensen (http://www.winternet.no)
// bugfixed by: Howard Yeend
// bugfixed by: Diogo Resende
// bugfixed by: Rival
// bugfixed by: Brett Zamir (http://brett-zamir.me)
// revised by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
// revised by: Luke Smith (http://lucassmith.name)
// input by: Kheang Hok Chin (http://www.distantia.ca/)
// input by: Jay Klehr
// input by: Amir Habibi (http://www.residence-mixte.com/)
// input by: Amirouche
number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number;
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
var sep = (typeof thousandsSep === 'undefined') ? ',' : thousandsSep;
var dec = (typeof decPoint === 'undefined') ? '.' : decPoint;
var s = '';
var toFixedFix = function (n, prec) {
var k = Math.pow(10, prec);
return '' + (Math.round(n * k) / k).toFixed(prec);
}
// @todo: for IE parseFloat(0.55).toFixed(0) = 0;
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if(s[0].length > 3) {
s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
}
if((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
}
function time_format(time) {
if(time == undefined)
return "never";
if(time < 3600)
return Math.floor(time/60) + "m" + (time%60) + "s ago";
else
return Math.floor(time/3600) + "h" + Math.floor((time/60)%60) + "m ago";
}
function loadRecentData() {
$.getJSON("ajax/telemetry.php?call=" + call + "&from=" + lastrxtime, function(json) {
images = json['images'];
if(images.length) {
lastrxtime = images[images.length-1].time_last+1;
$.each(images, function(key, data) {
// Remove old div
$("#img_" + data['id']).remove();
// Process images
$('#images').prepend("<div class=\"pic\" id=\"img_" + data['id'] + "\">"
+ "<img src=\"images/" + data['call'].replace('-','') + "-" + data['id'] + ".jpg?packetID=" + data['packetID'] + "\"><br>"
+ "Last packet " + time_format(json['time']-data['time_last']) + ", " + number_format(data['count']) + " packets, "
+ number_format(data['packetID']-data['count']+1) + " lost" + "<br>ImageID " + number_format(data['imageID']) + ", ServerID "
+ number_format(data['id']) + "</div>");
});
}
});
}
function loadImages() {
loadRecentData();
setInterval(loadRecentData, 1000);
}
</script>
</head>
<body onload="loadImages()">
<?php
include "sidebar.inc.php";
?>
<table>
<tr>
<td>
&nbsp;Range:
<a href="?call=<?=$_GET['call']?>&range=3600">1h</a>
<a href="?call=<?=$_GET['call']?>&range=10800">3h</a>
<a href="?call=<?=$_GET['call']?>&range=21600">6h</a>
<a href="?call=<?=$_GET['call']?>&range=43200">12h</a>
<a href="?call=<?=$_GET['call']?>&range=86400">24h</a>
<a href="?call=<?=$_GET['call']?>&range=172800">2d</a>
<a href="?call=<?=$_GET['call']?>&range=259200">3d</a>
<a href="?call=<?=$_GET['call']?>&range=432000">5d</a>
<a href="?call=<?=$_GET['call']?>&range=604800">7d</a>
<a href="?call=<?=$_GET['call']?>&range=1209600">14d</a>
<a href="?call=<?=$_GET['call']?>&range=1814400">21d</a>
<a href="?call=<?=$_GET['call']?>&range=2592000">30d</a>
</td>
</tr>
</table>
<div style="width:1330px;float:left;" id="images"></div>
</body>
</html>
<?php
include "footer.inc.php";
?>

Wyświetl plik

@ -0,0 +1,103 @@
<?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 + '" width="512" height="384" />'
});
marker.addListener('mouseover', function() {
infowindow.open(map, marker);
});
marker.addListener('mouseout', function() {
infowindow.close();
});
}
// Line between points
if(last) {
if(last.lat != last.lng || value.lat != value.lng) {
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
});
console.log(last,value);
}
items.push(line);
}
last = value;
});
});
}
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
zoom: 12,
center: new google.maps.LatLng(52.45,13.5),
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>

Wyświetl plik

@ -1,103 +1,13 @@
<?php
require "database.class.php";
$db = new MyDB();
include "header.inc.php";
?>
<!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 + '" width="512" height="384" />'
});
marker.addListener('mouseover', function() {
infowindow.open(map, marker);
});
marker.addListener('mouseout', function() {
infowindow.close();
});
}
// Line between points
if(last) {
if(last.lat != last.lng || value.lat != value.lng) {
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
});
console.log(last,value);
}
items.push(line);
}
last = value;
});
});
}
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
zoom: 12,
center: new google.maps.LatLng(52.45,13.5),
gestureHandling: 'greedy'
});
drawItems();
window.setInterval(drawItems, 10000);
}
</script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCrrxJc6mu5DjFZVHiFqhFMO7JJg2g89Y8&callback=initMap"></script>
<?php
include "sidebar.inc.php";
?>
</body>
</html>
<?php
include "footer.inc.php";
?>

Wyświetl plik

@ -0,0 +1,23 @@
<div class="inner" style="width:270px;height:1535px;float:left;">
<?php
$lasthour = false;
$today = false;
$yesterday = false;
$older = false;
$trackers = $db->getTracker();
foreach($trackers as $tr) {
$cnt = $tr->getPacketCount();
echo "<div class=\"call\">
<b><a href=\"telemetry.php?call=" . $tr->getCall() . "\">" . $tr->getCall() . "</a> ...
<a href=\"map.php?call=" . $tr->getCall() . "\">Map</a>
<a href=\"images.php?call=" . $tr->getCall() . "\">Images</a></b><br>
Last Activity: " . time_format(max($tr->getLastActivity())) . "<br>
Packets: " . number_format($cnt['img']['cnt300'] + $cnt['pos']['cnt300']) . " (5m), " . number_format($cnt['img']['cnt3600'] + $cnt['pos']['cnt3600']) . " (1h)
</div>";
}
?>
</div>

Wyświetl plik

@ -0,0 +1,42 @@
html, body {
font: 10pt Monospace;
margin: 0;
margin: 0;
height: 100%;
}
.inner {
margin: 3px;
padding: 3px;
border: solid 1px #777777;
}
.call {
background-color: #DDDDDD;
padding: 5px;
margin: 4px 0;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
img {
margin: 3px;
}
.pic {
display: inline-block;
float: left;
border: solid 1px #777777;
margin: 3px 3px;
padding: 4px;
font: 9pt Monospace;
}
.pic img {
margin: 0;
}
td,th {
vertical-align: top;
text-align: left;
padding: 1px 0;
margin: 0;
}

Wyświetl plik

@ -0,0 +1,591 @@
<?php
include "header.inc.php";
?>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
lastrxtime = <?=time()-$range?>;
call = '<?=$_GET['call']?>';
var last = null;
var lastChartUpdate = 0;
const COL_GREEN = "#008000";
const COL_ORANGE = "#CC6600";
const COL_RED = "#FF0000";
function number_format(number, decimals, decPoint, thousandsSep) { // eslint-disable-line camelcase
// discuss at: http://locutus.io/php/number_format/
// original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
// improved by: Kevin van Zonneveld (http://kvz.io)
// improved by: davook
// improved by: Brett Zamir (http://brett-zamir.me)
// improved by: Brett Zamir (http://brett-zamir.me)
// improved by: Theriault (https://github.com/Theriault)
// improved by: Kevin van Zonneveld (http://kvz.io)
// bugfixed by: Michael White (http://getsprink.com)
// bugfixed by: Benjamin Lupton
// bugfixed by: Allan Jensen (http://www.winternet.no)
// bugfixed by: Howard Yeend
// bugfixed by: Diogo Resende
// bugfixed by: Rival
// bugfixed by: Brett Zamir (http://brett-zamir.me)
// revised by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
// revised by: Luke Smith (http://lucassmith.name)
// input by: Kheang Hok Chin (http://www.distantia.ca/)
// input by: Jay Klehr
// input by: Amir Habibi (http://www.residence-mixte.com/)
// input by: Amirouche
number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number;
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
var sep = (typeof thousandsSep === 'undefined') ? ',' : thousandsSep;
var dec = (typeof decPoint === 'undefined') ? '.' : decPoint;
var s = '';
var toFixedFix = function (n, prec) {
var k = Math.pow(10, prec);
return '' + (Math.round(n * k) / k).toFixed(prec);
}
// @todo: for IE parseFloat(0.55).toFixed(0) = 0;
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if(s[0].length > 3) {
s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
}
if((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
}
function colorize(color, str) {
return "<font color=\"" + color + "\">" + str + "</font>";
}
function time_format(time) {
if(time == undefined)
return "never";
if(time < 3600)
return Math.floor(time/60) + "m" + (time%60) + "s ago";
else
return Math.floor(time/3600) + "h" + Math.floor((time/60)%60) + "m ago";
}
function loadRecentData() {
$.getJSON("ajax/telemetry.php?call=" + call + "&from=" + lastrxtime, function(json) {
tel = json['telemetry'];
if(tel.length) {
lastrxtime = tel[tel.length-1].rxtime+1;
$.each(tel[tel.length-1], function(key, d) {
switch(key) {
case 'sen_i1_temp':
case 'sen_e1_temp':
case 'sen_e2_temp':
case 'stm32_temp':
case 'si4464_temp':
$('#' + key).text(number_format(d/100, 2));
break;
case 'gps_pdop':
$('#' + key).text(number_format(d/20, 2));
break;
case 'gps_lat':
s = d < 0 ? "S" : "N";
s += d < 100000000 ? "0" : "";
s += number_format(d/10000000, 5);
$('#' + key).text(s);
break;
case 'gps_lon':
s = d < 0 ? "W" : "E";
s += d < 100000000 ? "0" : "";
s += d < 1000000000 ? "0" : "";
s += number_format(d/10000000, 5);
$('#' + key).text(s);
break;
case 'gps_lock':
switch(d) {
case 0: $('#' + key).html(colorize(COL_GREEN, "GPS locked")); break;
case 1: $('#' + key).html(colorize(COL_GREEN, "GPS locked - kept switched on")); break;
case 2: $('#' + key).html(colorize(COL_RED, "GPS loss")); break;
case 3: $('#' + key).html(colorize(COL_ORANGE, "Low Batt before switched on")); break;
case 4: $('#' + key).html(colorize(COL_ORANGE, "Low Batt while switched on")); break;
case 5: $('#' + key).html(colorize(COL_GREEN, "Data from memory")); break;
case 6: $('#' + key).html(colorize(COL_RED, "GPS communication error")); break;
}
break;
case 'err_ov5640':
switch(d) {
case 0: $('#' + key).html(colorize(COL_GREEN, "OK")); break;
case 1: $('#' + key).html(colorize(COL_RED, "I2C Error - Camera not found")); break;
case 2: $('#' + key).html(colorize(COL_RED, "DMA abort - last buffer segment")); break;
case 3: $('#' + key).html(colorize(COL_RED, "DMA FIFO error")); break;
case 4: $('#' + key).html(colorize(COL_RED, "DMA stream transfer error")); break;
case 5: $('#' + key).html(colorize(COL_RED, "DMA direct mode error")); break;
}
break;
case 'err_pac1720':
switch(d) {
case 0: $('#' + key).html(colorize(COL_GREEN, "OK")); break;
case 1:
case 3: $('#' + key).html(colorize(COL_RED, "Fail")); break;
case 2: $('#' + key).html(colorize(COL_RED, "Unreliable values")); break;
}
break;
case 'err_i2c1':
case 'err_i2c2':
case 'err_eva7m':
case 'err_bme280_i1':
case 'err_bme280_e1':
case 'err_bme280_e2':
switch(d) {
case 0: $('#' + key).html(colorize(COL_GREEN, "OK")); break;
case 1: $('#' + key).html(colorize(COL_RED, "Fail")); break;
}
break;
case 'rxtime':
case 'gps_time':
var a = new Date(d * 1000);
var year = a.getFullYear();
var month = a.getMonth() < 9 ? "0" + (a.getMonth()+1) : (a.getMonth()+1);
var date = a.getDate() < 10 ? "0" + a.getDate() : a.getDate();
var hour = a.getHours() < 10 ? "0" + a.getHours() : a.getHours();
var min = a.getMinutes() < 10 ? "0" + a.getMinutes() : a.getMinutes();
var sec = a.getSeconds() < 10 ? "0" + a.getSeconds() : a.getSeconds();
$('#' + key).text(year + '-' + month + '-' + date + ' ' + hour + ':' + min + ':' + sec);
break;
case 'pac_pbat':
case 'pac_psol':
$('#' + key).text(number_format(d/10, 1));
break;
default:
if(Number.isInteger(d)) {
$('#' + key).text(number_format(d));
} else {
$('#' + key).text(d);
}
}
});
}
$('#pos_cnt300').text(json['packetCount']['pos']['cnt300']);
$('#pos_cnt3600').text(json['packetCount']['pos']['cnt3600']);
$('#pos_cnt86400').text(json['packetCount']['pos']['cnt86400']);
$('#img_cnt300').text(json['packetCount']['img']['cnt300']);
$('#img_cnt3600').text(json['packetCount']['img']['cnt3600']);
$('#img_cnt86400').text(json['packetCount']['img']['cnt86400']);
$('#act_pos').text(time_format(json['lastActivity']['pos']));
$('#act_img').text(time_format(json['lastActivity']['img']));
// TODO: Remove old entries
// Update charts if there is new data or at a timeout of 300 seconds
if(tel.length > 0 || json['time']-lastChartUpdate > 300) {
var time = json['time'];
xAxis.minValue = new Date((time-<?=$range?>)*1000),
xAxis.maxValue = new Date(time*1000),
xAxis.ticks = [];
interval = <?=$range?> / 48;
for(i=0; i<=48; i++)
xAxis.ticks.push(new Date(((Math.floor(time/interval)*interval)-i*interval)*1000))
$.each(tel, function(key, data) {
if(last != null && data['rxtime'] - last > 300) {
dataBattery.addRow([null, null, null, null]);
dataSolar.addRow([null, null, null, null]);
dataTemp.addRow([null,null,null,null,null,null]);
dataGPS.addRow([null,null,null,null]);
dataLight.addRow([null,null]);
}
var time = new Date(data['rxtime']*1000);
dataBattery.addRow([time, data['adc_vbat'], data['pac_vbat'], data['pac_pbat']/10]);
dataSolar.addRow([time, data['adc_vsol'], data['pac_vsol'], data['pac_psol']/10]);
dataTemp.addRow([time, data['sen_i1_temp']/100, data['sen_e1_temp']/100, data['sen_e2_temp']/100, data['stm32_temp']/100, data['si4464_temp']/100]);
dataGPS.addRow([time, data['gps_sats'], data['gps_ttff'], data['gps_pdop']/20]);
dataLight.addRow([time, data['light_intensity']]);
last = data['rxtime'];
});
batteryChart.draw(dataBattery, voltageOptions);
solarChart.draw(dataSolar, voltageOptions);
tempChart.draw(dataTemp, tempOptions);
gpsChart.draw(dataGPS, gpsOptions);
lightChart.draw(dataLight, lightOptions);
lastChartUpdate = json['time'];
}
});
}
// Common
var xAxis = {
format:<?=($range >= 86400 ? "'d. H:m'" : "'H:m'")?>,
slantedTextAngle: 90
}
var area = {left:70, top:20, right:70, bottom:70};
var scroll = {axis: 'horizontal', keepInBounds: true, maxZoomIn: 20.0};
// Chart 1/2
var batteryChart;
var solarChart;
var dataBattery;
var dataSolar;
var voltageOptions = {
explorer: scroll,
width: 1285,
height: 300,
series: {
0: {targetAxisIndex: 0},
1: {targetAxisIndex: 0},
2: {targetAxisIndex: 1}
},
vAxes: {
0: {title: 'Voltage (mV)'},
1: {title: 'Power (mW)'},
},
legend: {
position: 'top'
},
hAxis: xAxis,
chartArea: area
};
// Chart 3
var tempChart;
var dataTemp;
var tempOptions = {
explorer: scroll,
width: 1285,
height: 300,
series: {
0: {targetAxisIndex: 0},
1: {targetAxisIndex: 0},
2: {targetAxisIndex: 0},
3: {targetAxisIndex: 0},
4: {targetAxisIndex: 0}
},
vAxis: {
title: 'Temp (°C)',
},
legend: {
position: 'top'
},
hAxis: xAxis,
chartArea: area
};
// Chart 4
var gpsChart;
var dataGPS;
var gpsOptions = {
explorer: scroll,
width: 1285,
height: 300,
series: {
0: {targetAxisIndex: 0},
1: {targetAxisIndex: 1},
2: {targetAxisIndex: 0}
},
vAxes: {
0: {title: 'Sats / pDOP'},
1: {title: 'TTFF'},
},
legend: {
position: 'top'
},
hAxis: xAxis,
chartArea: area
};
// Chart 5
var lightChart;
var dataLight;
var lightOptions = {
explorer: scroll,
width: 1285,
height: 300,
series: {
0: {targetAxisIndex: 0}
},
legend: {
position: 'top'
},
hAxis: xAxis,
chartArea: area
};
google.charts.load('current', {'packages':['line', 'corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
// Chart 1
dataBattery = new google.visualization.DataTable();
dataBattery.addColumn('date', 'Time');
dataBattery.addColumn('number', "VBAT_STM");
dataBattery.addColumn('number', "VBAT_PAC");
dataBattery.addColumn('number', "PBAT_PAC");
batteryChart = new google.visualization.LineChart(document.getElementById('batteryDiv'));
// Chart 2
dataSolar = new google.visualization.DataTable();
dataSolar.addColumn('date', 'Time');
dataSolar.addColumn('number', "VSOL_STM");
dataSolar.addColumn('number', "VSOL_PAC");
dataSolar.addColumn('number', "PSOL_PAC");
solarChart = new google.visualization.LineChart(document.getElementById('solarDiv'));
// Chart 3
dataTemp = new google.visualization.DataTable();
dataTemp.addColumn('date', 'Time');
dataTemp.addColumn('number', "TEMP_BME_I1");
dataTemp.addColumn('number', "TEMP_BME_E1");
dataTemp.addColumn('number', "TEMP_BME_E2");
dataTemp.addColumn('number', "TEMP_STM32");
dataTemp.addColumn('number', "TEMP_Si4464");
tempChart = new google.visualization.LineChart(document.getElementById('tempDiv'));
// Chart 4
dataGPS = new google.visualization.DataTable();
dataGPS.addColumn('date', 'Time');
dataGPS.addColumn('number', "Sats");
dataGPS.addColumn('number', "TTFF");
dataGPS.addColumn('number', "pDOP");
gpsChart = new google.visualization.LineChart(document.getElementById('gpsDiv'));
// Chart 5
dataLight = new google.visualization.DataTable();
dataLight.addColumn('date', 'Time');
dataLight.addColumn('number', "LIGHT");
lightChart = new google.visualization.LineChart(document.getElementById('lightDiv'));
loadRecentData();
setInterval(loadRecentData, 1000);
}
</script>
</head>
<body>
<?php
include "sidebar.inc.php";
?>
<table style="float:left;margin-top:-3px;">
<tr>
<td>
<div class="inner" style="width:420px;height:260px;">
<table>
<tr>
<th width="70">Call:</th>
<td colspan="5"><span id="call"></span></td>
</tr>
<tr>
<th width="70">Serial:</th>
<td colspan="5">Reset <span id="reset"></span>, ID <span id="id"></span></td>
</tr>
<tr height="5"></tr>
<tr>
<th>Time:</th>
<td colspan="5">
RX: &nbsp;<span id="rxtime"></span><br>
SYS: <span id="sys_time"></span>s<br>
</td>
</tr>
<tr height="5"></tr>
<tr>
<th>GPS:</th>
<td colspan="5">
<b><span id="gps_lock"></span></b><br>
<span id="gps_sats"></span> Sats, TTFF <span id="gps_ttff"></span>s, pDOP <span id="gps_pdop"></span><br>
Time: <span id="gps_time"></span><br>
<span id="gps_lat"></span>° <span id="gps_lon"></span>°
</td>
</tr>
<tr height="5"></tr>
<tr>
<th>Packets:</th>
<td></td>
<td>5m</td>
<td>1h</td>
<td>24h</td>
<td>Last</td>
</tr>
<?php
$cnt = $tracker->getPacketCount();
$act = $tracker->getLastActivity();
?>
<tr>
<td></td>
<td width="40">POS:</td>
<td width="50"><span id="pos_cnt300"></span></td>
<td width="70"><span id="pos_cnt3600"></span></td>
<td width="70"><span id="pos_cnt86400"></span></td>
<td><span id="act_pos"></span></td>
</tr>
<tr>
<td></td>
<td>IMG:</td>
<td><span id="img_cnt300"></span></td>
<td><span id="img_cnt3600"></span></td>
<td><span id="img_cnt86400"></span></td>
<td><span id="act_img"></span></td>
</tr>
<tr>
<td></td>
<td>LOG:</td>
<td>?</td>
<td>?</td>
<td>?</td>
<td>?m?s ago</td>
</tr>
</table>
</div>
</td>
<td>
<div class="inner" style="width:420px;height:260px;">
<table>
<tr>
<th width="70">Battery:</th>
<td></td>
<td><span id="adc_vbat"></span>mV<sub>STM</sub>, <span id="pac_vbat"></span>mV<sub>PAC</sub>, <span id="pac_pbat"></span>mW<sub>PAC</sub></td>
</tr>
<tr>
<th>Solar:</th>
<td></td>
<td><span id="adc_vsol"></span>mV<sub>STM</sub>, <span id="pac_vsol"></span>mV<sub>PAC</sub>, <span id="pac_psol"></span>mW<sub>PAC</sub></td>
</tr>
<tr height="5"></tr>
<tr>
<th>Sensors:</th>
<td width="75">BME280<sub>I1</sub>:</td>
<td><span id="sen_i1_press"></span>Pa, <span id="sen_i1_temp"></span>°C, <span id="sen_i1_hum"></span>%</td>
</tr>
<tr>
<td></td>
<td>BME280<sub>E1</sub>:</td>
<td><span id="sen_e1_press"></span>Pa, <span id="sen_e1_temp"></span>°C, <span id="sen_e1_hum"></span>%</td>
</tr>
<tr>
<td></td>
<td>BME280<sub>E2</sub>:</td>
<td><span id="sen_e2_press"></span>Pa, <span id="sen_e2_temp"></span>°C, <span id="sen_e2_hum"></span>%</td>
</tr>
<tr>
<th>Temp.:</th>
<td></td>
<td><span id="stm32_temp"></span>°C<sub>STM</sub>, <span id="si4464_temp"></span>°C<sub>Si4464</sub></td>
</tr>
<tr>
<th>Light</th>
<td></td>
<td><span id="light_intensity"></span><sub>OV5640</sub></td>
</tr>
</table>
</div>
</td>
<td>
<div class="inner" style="width:420px;height:260px;">
<table>
<tr>
<td width="50%">
<table>
<tr>
<th width="75">I2C<sub>INT</sub></th>
<td><b><span id="err_i2c1"></span></b></td>
</tr>
<tr>
<th>I2C<sub>EXT</sub></th>
<td><b><span id="err_i2c2"></span></b></td>
</tr>
<tr height="5"></tr>
<tr>
<th>EVA7M:</th>
<td width="75"><b><span id="err_eva7m"></span></b></td>
</tr>
<tr>
<th>PAC1720:</th>
<td width="230"><b><span id="err_pac1720"></span></b></td>
</tr>
<tr>
<th>OV5640:</th>
<td><b><span id="err_ov5640"></span></b></td>
</tr>
</table>
</td>
<td width="50%">
<table>
<tr>
<th width="75">BME280<sub>I1</sub>:</th>
<td><b><span id="err_bme280_i1"></span></b></td>
</tr>
<tr>
<th>BME280<sub>E1</sub>:</th>
<td><b><span id="err_bme280_e1"></span></b></td>
</tr>
<tr>
<th>BME280<sub>E2</sub>:</th>
<td><b><span id="err_bme280_e2"></span></b></td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</td>
</tr>
<tr>
<td colspan="3">
&nbsp;Range:
<a href="?call=<?=$_GET['call']?>&range=3600">1h</a>
<a href="?call=<?=$_GET['call']?>&range=10800">3h</a>
<a href="?call=<?=$_GET['call']?>&range=21600">6h</a>
<a href="?call=<?=$_GET['call']?>&range=43200">12h</a>
<a href="?call=<?=$_GET['call']?>&range=86400">24h</a>
<a href="?call=<?=$_GET['call']?>&range=172800">2d</a>
<a href="?call=<?=$_GET['call']?>&range=259200">3d</a>
<a href="?call=<?=$_GET['call']?>&range=432000">5d</a>
<a href="?call=<?=$_GET['call']?>&range=604800">7d</a>
<a href="?call=<?=$_GET['call']?>&range=1209600">14d</a>
<a href="?call=<?=$_GET['call']?>&range=1814400">21d</a>
<a href="?call=<?=$_GET['call']?>&range=2592000">30d</a>
</td>
</tr>
<tr>
<td colspan="3" height="315"><div id="batteryDiv" class="inner"></div></td>
</tr>
<tr>
<td colspan="3" height="315"><div id="solarDiv" class="inner"></div></td>
</tr>
<tr>
<td colspan="3" height="315"><div id="tempDiv" class="inner"></div></td>
</tr>
<tr>
<td colspan="3" height="315"><div id="gpsDiv" class="inner"></div></td>
</tr>
<tr>
<td colspan="3" height="315"><div id="lightDiv" class="inner"></div></td>
</tr>
</table>
</body>
</html>
<?php
include "footer.inc.php";
?>

Wyświetl plik

@ -1,11 +1,12 @@
import binascii
import urllib.request
import urllib.error
import datetime
from datetime import datetime
from position import decode_position
from subprocess import *
jsons = []
import time
import threading
from shutil import copyfile
def decode_callsign(code):
callsign = ''
@ -31,153 +32,99 @@ def encode_callsign(callsign):
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, typ, server, grouping):
global jsons
imageProcessor = None
imageData = {}
lock = threading.RLock()
if not (typ is 'I' and len(data) == 125) and not (typ is 'J' and len(data) == 124):
def imgproc():
global imageData
while True:
with lock:
for _id in imageData:
(call, data) = imageData[_id]
filename = 'html/images/%s-%d.jpg' % (call.replace('-',''), _id)
f = open(filename, 'wb')
process = Popen(['./ssdv', '-d'], stdin=PIPE, stdout=f, stderr=PIPE)
process.stdin.write(data)
dummy,err = process.communicate()
f.close()
filename2 = 'html/images/%s.jpg' % (call.replace('-',''))
copyfile(filename, filename2)
imageData = {} # Clear data
time.sleep(1)
w = time.time()
def insert_image(sqlite, receiver, call, data):
global imageProcessor,imageData,w
if len(data) != 174:
return # APRS message has invalid type or length (or both)
cur = sqlite.cursor()
# Decode various meta data
timd,x,y,z,teld,new = decode_position(tim, posi, None)
imageID = data[0]
packetID = (data[1] << 8) | data[2]
data = binascii.hexlify(data[3:]).decode("ascii")
# 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 = ('68%08x%02x%04x' % (encode_callsign(bcall), imageID, packetID)) + data
data += "%08x" % (binascii.crc32(binascii.unhexlify(data)) & 0xffffffff)
timd = int(datetime.now().timestamp())
# Find image ID (or generate new one)
_id = None
cur.execute("SELECT id,packetID FROM image WHERE call = ? AND imageID = ? AND rxtime+15*60 >= ? ORDER BY rxtime DESC LIMIT 1", (call, imageID, timd))
fetch = cur.fetchall()
if len(fetch):
_id = fetch[0][0]
lastPacketId = fetch[0][1]
if _id is None or lastPacketId > packetID:
# Generate ID
cur.execute("SELECT id+1 FROM image ORDER BY id DESC LIMIT 1")
fetch = cur.fetchall()
if len(fetch):
_id = fetch[0][0]
else: # No entries in the database
_id = 0
# Debug
print('Received %s-Packet Image %d Packet %d' % (typ, imageID, packetID))
print('Received image packet %d Packet %d ServerID %d' % (imageID, packetID, _id))
# Insert
sqlite.cursor().execute("""
INSERT OR IGNORE INTO image (call,time,imageID,packetID,lat,lon,alt)
VALUES (?,?,?,?,?,?,?)""",
(call, int(timd.timestamp()), imageID, packetID, y, x, int(z))
)
sqlite.commit()
if typ is 'I':
# 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 = ('67%08x%02x%04x' % (encode_callsign(bcall), imageID, packetID)) + data
sqlite.cursor().execute("""
UPDATE image
SET data1 = ?
WHERE call = ?
AND time = ?
AND imageID = ?
AND packetID = ?""",
(data, call, int(timd.timestamp()), imageID, packetID)
)
elif typ is 'J':
sqlite.cursor().execute("""
UPDATE image
SET data2 = ?
WHERE call = ?
AND time = ?
AND imageID = ?
AND packetID = ?""",
(data, call, int(timd.timestamp()), imageID, packetID)
)
sqlite.commit()
# Get both data entries
cur = sqlite.cursor()
# Insert into database
cur.execute("""
SELECT data1,data2
FROM image
WHERE call = ?
AND time = ?
AND imageID = ?
AND packetID = ?""",
(call, int(timd.timestamp()), imageID, packetID)
INSERT OR IGNORE INTO image (call,rxtime,imageID,packetID,data,id)
VALUES (?,?,?,?,?,?)""",
(call, timd, imageID, packetID, data, _id)
)
row = cur.fetchall()[0]
if row[0] != None and row[1] != None: # Both entries have been received
data = ''.join(row)
# Calculate CRC for SSDV server
crc = binascii.crc32(binascii.unhexlify(data)) & 0xffffffff
# Update CRC in DB
sqlite.cursor().execute("""
UPDATE image
SET crc = ?
WHERE call = ?
AND time = ?
AND imageID = ?
AND packetID = ?""",
("%08x" % crc, call, int(timd.timestamp()), imageID, packetID)
)
if w+0.5 < time.time():
sqlite.commit()
w = time.time()
# SSDV decode
cur = sqlite.cursor()
cur.execute("SELECT GROUP_CONCAT('55' || data1 || data2 || crc, '') 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()
with lock:
cur.execute("SELECT GROUP_CONCAT('55' || data || '"+(144*'0')+"', '') FROM image WHERE id = ? GROUP BY id ORDER BY packetID", (_id,))
data = cur.fetchall()[0][0]
imageData[_id] = (call, binascii.unhexlify(data))
# Create message for SSDV server (and save to array)
ssdv = '55' + data + ('%08x' % crc)
jsons.append("""{
\"type\": \"packet\",
\"packet\": \"""" + ssdv + """\",
\"encoding\": \"hex\",
\"received\": \"""" + datetime.datetime.now().isoformat('T')[:19] + """Z\",
\"receiver\": \"""" + receiver + """\"
}""")
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:
err = True
while err:
print('Send to SSDV data server')
try:
result = urllib.request.urlopen(req, "".join(json.split(' ')).encode("ascii")) # Send packets to server
print('Response from SSDV-Server: OK')
err = False
except urllib.error.URLError as error:
if not hasattr(error, 'code'): # (Bug in urllib) most likely network not available
print('Error: Could not connect to SSDV-Server')
elif error.code == 400:
print('Response from SSDV-Server: %s' % error.read().decode('ascii').replace('\n',''))
err = 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())
if imageProcessor is None:
imageProcessor = threading.Thread(target=imgproc)
imageProcessor.start()

Wyświetl plik

@ -1,93 +0,0 @@
# Base91 encode/decode for Python 2 and Python 3
#
# Copyright (c) 2012 Adrien Beraud
# Copyright (c) 2015 Guillaume Jacquenot
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Adrien Beraud, Wisdom Vibes Pte. Ltd., nor the names
# of its contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import struct
base91_alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'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))
def decode(encoded_str):
''' Decode Base91 string to a bytearray '''
v = -1
b = 0
n = 0
out = bytearray()
for strletter in encoded_str:
if not strletter in decode_table:
continue
c = decode_table[strletter]
if(v < 0):
v = c
else:
v += c*91
b |= v << n
n += 13 if (v & 8191)>88 else 14
while True:
out += struct.pack('B', b&255)
b >>= 8
n -= 8
if not n>7:
break
v = -1
if v+1:
out += struct.pack('B', (b | v << n) & 255 )
return out
def encode(bindata):
''' Encode a bytearray to a Base91 string '''
b = 0
n = 0
out = ''
for count in range(len(bindata)):
byte = bindata[count:count+1]
b |= struct.unpack('B', byte)[0] << n
n += 8
if n>13:
v = b & 8191
if v > 88:
b >>= 13
n -= 13
else:
v = b & 16383
b >>= 14
n -= 14
out += base91_alphabet[v % 91] + base91_alphabet[v // 91]
if n:
out += base91_alphabet[b % 91]
if n>7 or b>90:
out += base91_alphabet[b // 91]
return out

Wyświetl plik

@ -1,178 +0,0 @@
#!/usr/bin/python
import serial,os,re,datetime
from subprocess import call
import base91
import binascii
import urllib2
import io
import sys
import argparse
import pygame, random
from pygame.locals import *
import pygame.time
from subprocess import *
from cStringIO import StringIO
send_to_server = False
SCREENX = 1024
SCREENY = 768
pygame.font.init()
myfont = pygame.font.SysFont('Comic Sans MS', 20)
textsurface = myfont.render('Callsign: DL7AD2 Image ID: 07 Resolution: 640x480', False, (0, 255, 255))
pygame.init()
screen = pygame.display.set_mode((SCREENX, SCREENY))
background = pygame.Surface(screen.get_rect().size)
displaygroup = pygame.sprite.RenderUpdates()
updategroup = pygame.sprite.Group()
clock = pygame.time.Clock()
pygame.display.set_caption('PecanRXGui v.1.0.0 (Q)uit (s)end image')
# Parse arguments from terminal
parser = argparse.ArgumentParser(description='APRS/SSDV decoder')
parser.add_argument('-c', '--call', help='Callsign of the station')
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='Server URL', default='https://ssdv.habhub.org/api/v0/packets')
args = parser.parse_args()
if args.device == 'I': # Connect to APRS-IS
aprsis = aprs.TCP('DL4MDW', aprs_filter='t/u u/APECAN')
aprsis.start()
elif args.device is not '-': # Use serial connection (probably TNC)
try:
serr = serial.Serial(
port=args.device,
baudrate=args.baudrate,
)
except:
sys.stderr.write('Error: Could not open serial port\n')
sys.exit(1)
ser = io.TextIOWrapper(io.BufferedRWPair(serr, serr, 1), newline = '\r', line_buffering = True) # Define Newline as \r
# Open logging file
if args.log is not None:
try:
logfile = open(args.log, 'a')
except:
sys.stderr.write('Error: Could not open logging file\n')
sys.exit(1)
jsons = []
current_filename = 'data.csv'
buf = ''
jpg = ''
imgbuf = ''
def received_data(data):
global jsons, old_filename, current_filename, send_to_server, buf, imgbuf
if str(type(data)) == "<class 'aprs.classes.Frame'>": # APRS-IS
call = str(data.source)
aprs = data.text[3:]
receiver = 'APRS-IS/' + str(data.path.pop())
else: # serial or stdin
# Parse line and detect data
m = re.search("(.*)\>APECAN(.*):\{\{I(.*)", data)
if m:
try:
call = m.group(1)
aprs = m.group(3)
receiver = 'bla'
except:
return # message format incorrect (probably no APRS message or line cut off too short)
else:
m = re.search("\[(.*)\]\[(.*)\] DATA \> (.*)", data)
try:
aprs = m.group(3)
except:
return
if args.log is not None:
logfile.write(data) # Log data to file
data = base91.decode(aprs) # Decode Base91
# 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')
if len(data) != 219:
return # APRS message sampled too short
if (data[7] + data[6] * 256) == 0:
buf = ''
buf += binascii.unhexlify(ssdv)
command = ['./ssdv', '-d']
process = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE)
process.stdin.write(buf)
jpg,dummy = process.communicate()
imgbuf = StringIO(jpg)
if send_to_server is False:
return
else:
if len(jsons) >= args.grouping:
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:
try:
result = urllib2.urlopen(req, "".join(json.split(' '))) # Send packets to server
print 'Send to SSDV data server: OK'
error = False
except urllib2.URLError, error:
print 'Send to SSDV data server: failed (connection error :( trying 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
aprsis.receive(callback=received_data) # Register APRS callback
else:
# read data from console ?
while 1:
for event in pygame.event.get():
if event.type == QUIT:
exit(0)
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
exit(0)
elif event.key == pygame.K_s:
send_to_server^=True
data = sys.stdin.readline() if args.device is '-' else ser.readline() # Read a line
received_data(data)
displaygroup.clear(screen, background)
updategroup.update()
filename = str("currximg.jpg")
try:
img=pygame.image.load(imgbuf)
textsurface = myfont.render("Call: %s send: %d" % (args.call, send_to_server), False, (0, 255, 255))
screen.blit(img,(0,0))
screen.blit(textsurface,(0,0))
pygame.display.flip()
pygame.display.update(displaygroup.draw(screen))
except Exception as e:
print str(e)
textsurface = myfont.render('Error %s' % (e), False, (255, 100, 100))
screen.blit(textsurface,(0,0))
pygame.display.flip()
pygame.display.update(displaygroup.draw(screen))
clock.tick(500)

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -1,6 +1,7 @@
from datetime import datetime,timedelta,timezone
import sqlite3
import base91
import struct
def insert_log(sqlite, call, data):
@ -25,7 +26,7 @@ def insert_log(sqlite, call, data):
sqlite.commit()
def decode_position(tim, posi, tel):
def decode_position(posi, tel):
# Decode latitude
y3 = ord(posi[1]) - 33
y2 = ord(posi[2]) - 33
@ -48,12 +49,8 @@ def decode_position(tim, posi, tel):
ze = z0 + z1*91
z = pow(1.002, ze) / 3.281
# Decode time
# Time
timd = datetime.now(timezone.utc)
timd = timd.replace(hour = int(tim[0:2]), minute = int(tim[2:4]), second = int(tim[4:6]), microsecond=0)
now = datetime.now(timezone.utc)
if now < timd: # Packet was sampled yesterday
timd -= timedelta(1)
# Decode GPS Fix Type
isnew = ((ord(posi[12])-33) >> 5) & 0x1
@ -68,15 +65,23 @@ def decode_position(tim, posi, tel):
return timd,x,y,z,teld,isnew
def insert_position(sqlite, call, tim, posi, comm, tel): #sqlite, call, data
# Decode
timd,x,y,z,teld,isnew = decode_position(tim, posi, tel)
def insert_position(sqlite, call, posi, comm, tel): #sqlite, call, data
# Decode position
timd,x,y,z,teld,isnew = decode_position(posi, tel)
# Decode comment
data = base91.decode(comm)
(adc_vsol,adc_vbat,pac_vsol,pac_vbat,pac_pbat,pac_psol,light_intensity,
gps_lock,gps_sats,gps_ttff,gps_pdop,gps_alt,gps_lat,
gps_lon,sen_i1_press,sen_e1_press,sen_e2_press,sen_i1_temp,sen_e1_temp,
sen_e2_temp,sen_i1_hum,sen_e1_hum,sen_e2_hum,dummy2,stm32_temp,
si4464_temp,reset,_id,gps_time,sys_time,sys_error) = struct.unpack('HHHHhhHBBBBHiiIIIhhhBBBBhhHIIII', data[:72])
# Insert
sqlite.cursor().execute("""
INSERT OR REPLACE INTO position (call,time,org,lat,lon,alt,isnew,comment,sequ,tel1,tel2,tel3,tel4,tel5)
VALUES (?,?,'pos',?,?,?,?,?,?,?,?,?,?,?)""",
(call, int(timd.timestamp()), y, x, int(z), isnew, comm, teld[0], teld[1], teld[2], teld[3], teld[4], teld[5])
sqlite.cursor().execute(
"""INSERT INTO position (call,rxtime,org,adc_vsol,adc_vbat,pac_vsol,pac_vbat,pac_pbat,pac_psol,light_intensity,gps_lock,gps_sats,gps_ttff,gps_pdop,gps_alt,gps_lat,gps_lon,sen_i1_press,sen_e1_press,sen_e2_press,sen_i1_temp,sen_e1_temp,sen_e2_temp,sen_i1_hum,sen_e1_hum,sen_e2_hum,sys_error,stm32_temp,si4464_temp,reset,id,sys_time,gps_time)
VALUES (?,?,'pos',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(call,int(timd.timestamp()), adc_vsol,adc_vbat,pac_vsol,pac_vbat,pac_pbat,pac_psol,light_intensity,gps_lock,gps_sats,gps_ttff,gps_pdop,gps_alt,gps_lat,gps_lon,sen_i1_press,sen_e1_press,sen_e2_press,sen_i1_temp,sen_e1_temp,sen_e2_temp,sen_i1_hum,sen_e1_hum,sen_e2_hum,sys_error,stm32_temp,si4464_temp,reset,_id,sys_time,gps_time)
)
sqlite.commit()
@ -85,3 +90,24 @@ def insert_position(sqlite, call, tim, posi, comm, tel): #sqlite, call, data
print("Decoded position from %s time %s => lat=%f lon=%f alt=%d new=%d comment=%s, sequ=%d tel=[%d,%d,%d,%d,%d]"
% (call, tim_stringified, y, x, int(z), isnew, comm, teld[0], teld[1], teld[2], teld[3], teld[4], teld[5]))

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -362,13 +362,14 @@
module_conf_t config[7];
uint8_t ssdv_buffer[128*1024] __attribute__((aligned(32))); // Image buffer
uint8_t ssdv_buffer[256*1024] __attribute__((aligned(32))); // Image buffer
systime_t track_cycle_time = S2ST(30); // Tracking cycle (all peripheral data [airpressure, GPS, temperature, ...] is collected each 60 seconds
systime_t log_cycle_time = S2ST(30); // Log cycle time in seconds (600 seconds)
systime_t track_cycle_time = S2ST(60); // Tracking cycle (all peripheral data [airpressure, GPS, temperature, ...] is collected each 60 seconds
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
uint16_t gps_onper_vbat = 3000; // Battery voltage threshold at which GPS is kept switched on all time. This value must be larger
// than gps_on_vbat and gps_off_vbat otherwise this value has no effect. Value 0 disables this feature
void start_user_modules(void)
{
@ -439,7 +440,6 @@ void start_user_modules(void)
config[3].trigger.type = TRIG_CONTINUOUSLY; // Transmit continuously
chsnprintf(config[3].aprs_conf.callsign, 16, "DL7AD"); // APRS Callsign
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)
config[3].ssdv_conf.ram_buffer = ssdv_buffer; // Camera buffer
config[3].ssdv_conf.ram_size = sizeof(ssdv_buffer); // Buffer size
@ -457,7 +457,6 @@ void start_user_modules(void)
config[4].trigger.type = TRIG_CONTINUOUSLY; // Transmit continuously
chsnprintf(config[4].aprs_conf.callsign, 16, "DL7AD"); // APRS Callsign
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)
config[4].ssdv_conf.ram_buffer = ssdv_buffer; // Camera buffer
config[4].ssdv_conf.ram_size = sizeof(ssdv_buffer); // Buffer size
@ -496,7 +495,6 @@ void start_user_modules(void)
config[6].trigger.timeout = 60; // Timeout 180 sec
chsnprintf(config[6].aprs_conf.callsign, 16, "DL7AD"); // APRS Callsign
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]);

Wyświetl plik

@ -14,7 +14,7 @@
* save energy using 1.8V. This option is activated automatically if ACTIVATE_USB is set
* true. */
#define ACTIVATE_USB TRUE /* If set to true, the USB interface will be switched on. The tracker is also switched to
#define ACTIVATE_USB FALSE /* If set to true, the USB interface will be switched on. The tracker is also switched to
* 3V, because USB would not work at 1.8V. Note that the transmission power is increased
* too when operating at 3V. This option will also run the STM32 at 48MHz (AHB) permanently
* because USB needs that speed, otherwise it is running at 6MHz which saves a lot of power. */
@ -32,6 +32,7 @@ extern systime_t log_cycle_time;
extern bool keep_cam_switched_on;
extern uint16_t gps_on_vbat;
extern uint16_t gps_off_vbat;
extern uint16_t gps_onper_vbat;
#endif

Wyświetl plik

@ -89,19 +89,19 @@ void readLog(BaseSequentialStream *chp, int argc, char *argv[])
(void)argc;
(void)argv;
chprintf(chp, "addr,id,date,time,lat,lon,alt,sats,ttff,vbat,vsol,vsub,pbat,rbat,press,temp,hum,idimg\r\n");
chprintf(chp, "addr,id,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,
"%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,
"%08x,%d,%d,%d.%05d,%d.%05d,%d,%d,%d,%d.%03d,%d.%03d,%d,%d.%01d,%2d.%02d,%2d.%01d\r\n",
tp, tp->id, tp->gps_time,
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,
tp->air_press/10, tp->air_press%10, tp->air_temp/100, tp->air_temp%100, tp->air_hum/10, tp->air_hum%10, tp->id_image
tp->adc_vbat/1000, (tp->adc_vbat%1000), tp->adc_vsol/1000, (tp->adc_vsol%1000), tp->pac_pbat,
tp->sen_i1_press/10, tp->sen_i1_press%10, tp->sen_i1_temp/100, tp->sen_i1_temp%100, tp->sen_i1_hum/10, tp->sen_i1_hum%10
);
}
}

Wyświetl plik

@ -98,12 +98,6 @@ extern bool debug_on_usb;
chMtxUnlock(&trace_mtx); \
}
#define PRINT_TIME(thd) { \
ptime_t dbgtime; \
getTime(&dbgtime); \
TRACE_INFO("%-4s > Current time: %02d-%02d-%02d %02d:%02d:%02d:%03d", thd, dbgtime.year, dbgtime.month, dbgtime.day, dbgtime.hour, dbgtime.minute, dbgtime.second, dbgtime.millisecond); \
}
void debugOnUSB(BaseSequentialStream *chp, int argc, char *argv[]);
void printConfig(BaseSequentialStream *chp, int argc, char *argv[]);
void printPicture(BaseSequentialStream *chp, int argc, char *argv[]);

Wyświetl plik

@ -139,7 +139,7 @@ uint32_t BME280_getPressure(bme280_t *handle, uint16_t means) {
* Reads the relative humidity
* @return rel. humidity in % * 10
*/
uint16_t BME280_getHumidity(bme280_t *handle) {
uint8_t BME280_getHumidity(bme280_t *handle) {
int32_t adc_H;
uint16_t tmp;
I2C_read16(handle->address, BME280_REGISTER_HUMIDDATA, &tmp);
@ -160,7 +160,7 @@ uint16_t BME280_getHumidity(bme280_t *handle) {
v_x1_u32r = (v_x1_u32r < 0) ? 0 : v_x1_u32r;
v_x1_u32r = (v_x1_u32r > 419430400) ? 419430400 : v_x1_u32r;
float h = (v_x1_u32r>>12);
return h / 102;
return h / 1020;
}
/**

Wyświetl plik

@ -90,7 +90,7 @@ bool BME280_isAvailable(uint8_t address);
void BME280_Init(bme280_t *handle, uint8_t address);
int16_t BME280_getTemperature(bme280_t *handle);
uint32_t BME280_getPressure(bme280_t *handle, uint16_t means);
uint16_t BME280_getHumidity(bme280_t *handle);
uint8_t BME280_getHumidity(bme280_t *handle);
int32_t BME280_getAltitude(uint32_t seaLevel, uint32_t atmospheric);
#endif

Wyświetl plik

@ -12,6 +12,9 @@
#include "padc.h"
#include <string.h>
static uint32_t lightIntensity;
static uint8_t error;
struct regval_list {
uint16_t reg;
uint8_t val;
@ -962,6 +965,8 @@ CH_IRQ_HANDLER(Vector5C) {
bool OV5640_Capture(uint8_t* buffer, uint32_t size)
{
OV5640_setLightIntensity();
/*
* Note:
* If there are no Chibios devices enabled that use DMA then...
@ -1073,21 +1078,31 @@ bool OV5640_Capture(uint8_t* buffer, uint32_t size)
// Capture done, unlock I2C
I2C_Unlock();
if(dma_error) {
if(dma_flags & STM32_DMA_ISR_HTIF)
if(dma_error)
{
if(dma_flags & STM32_DMA_ISR_HTIF) {
TRACE_ERROR("CAM > DMA abort - last buffer segment");
if(dma_flags & STM32_DMA_ISR_FEIF)
error = 0x2;
}
if(dma_flags & STM32_DMA_ISR_FEIF) {
TRACE_ERROR("CAM > DMA FIFO error");
if(dma_flags & STM32_DMA_ISR_TEIF)
error = 0x3;
}
if(dma_flags & STM32_DMA_ISR_TEIF) {
TRACE_ERROR("CAM > DMA stream transfer error");
if(dma_flags & STM32_DMA_ISR_DMEIF)
error = 0x4;
}
if(dma_flags & STM32_DMA_ISR_DMEIF) {
TRACE_ERROR("CAM > DMA direct mode error");
error = 0x5;
}
TRACE_ERROR("CAM > Error capturing image");
return false;
}
TRACE_INFO("CAM > Capture success");
error = 0x0;
return true;
}
@ -1226,6 +1241,8 @@ void OV5640_init(void)
chThdSleepMilliseconds(100);
OV5640_setLightIntensity();
// Send settings to OV5640
TRACE_INFO("CAM > Transmit config to camera");
OV5640_TransmitConfig();
@ -1281,6 +1298,7 @@ bool OV5640_isAvailable(void)
if(I2C_read8_16bitreg(OV5640_I2C_ADR, 0x300A, &val) && I2C_read8_16bitreg(OV5640_I2C_ADR, 0x300B, &val2)) {
ret = val == 0x56 && val2 == 0x40;
} else {
error = 0x1;
ret = false;
}
@ -1294,12 +1312,23 @@ bool OV5640_isAvailable(void)
return ret;
}
uint32_t OV5640_getLightIntensity(void)
void OV5640_setLightIntensity(void)
{
uint8_t val1,val2,val3;
I2C_read8_16bitreg(OV5640_I2C_ADR, 0x3C1B, &val1);
I2C_read8_16bitreg(OV5640_I2C_ADR, 0x3C1C, &val2);
I2C_read8_16bitreg(OV5640_I2C_ADR, 0x3C1D, &val3);
return (val1 << 16) | (val2 << 8) | val3;
lightIntensity = (val1 << 16) | (val2 << 8) | val3;
}
uint32_t OV5640_getLastLightIntensity(void)
{
uint32_t ret = lightIntensity;
return ret;
}
uint8_t OV5640_hasError(void)
{
return error;
}

Wyświetl plik

@ -21,7 +21,9 @@ void OV5640_SetResolution(resolution_t res);
void OV5640_init(void);
void OV5640_deinit(void);
bool OV5640_isAvailable(void);
uint32_t OV5640_getLightIntensity(void);
void OV5640_setLightIntensity(void);
uint32_t OV5640_getLastLightIntensity(void);
uint8_t OV5640_hasError(void);
#endif

Wyświetl plik

@ -12,72 +12,77 @@
* FSC = FSR / R_sense
*/
#define FSR 80 /* Full-scale-rage voltage in mV */
#define FSR 10 /* Full-scale-rage voltage in mV */
#define DENO 2047 /* Denominator see Tab. 4-5 in PAC1720 datasheet */
#define FSV (40 - 40 / (DENO))
#define FSC ((FSR) / (PAC1720_RSENSE))
static int32_t pac1720_pbat;
static int32_t pac1720_pbat_counter;
static int32_t pac1720_rbat;
static int32_t pac1720_rbat_counter;
static int32_t pac1720_psol;
static int32_t pac1720_vbat;
static int32_t pac1720_vsol;
static int32_t pac1720_counter;
int16_t pac1720_getPbat(void) {
static uint8_t error;
static mutex_t mtx;
bool mtx_init = false;
static void pac1720_lock(void) {
// Initialize mutex
if(!mtx_init)
chMtxObjectInit(&mtx);
mtx_init = true;
chMtxLock(&mtx);
}
static void pac1720_unlock(void)
{
chMtxUnlock(&mtx);
}
int16_t pac1720_get_pbat(void) {
int32_t fsp = FSV * FSC;
int16_t val;
uint8_t sign;
if(I2C_read16(PAC1720_ADDRESS, PAC1720_CH2_PWR_RAT_HIGH, (uint16_t*)&val)) {
I2C_read8(PAC1720_ADDRESS, PAC1720_CH2_VSENSE_HIGH, &sign);
return (sign >> 7 ? -1 : 1) * (val * fsp / 65535);
return (sign >> 7 ? -1 : 1) * 10 * (val * fsp / 65535);
} else {
error |= 0x1;
return 0; // PAC1720 not available (maybe Vcc too low)
}
}
int16_t pac1720_getAvgPbat(void) {
// Return current value if time interval too short
if(!pac1720_pbat_counter)
return pac1720_getPbat();
int16_t pac1720_get_psol(void) {
return 0;
}
// Calculate average power
int16_t ret = pac1720_pbat / pac1720_pbat_counter;
uint16_t pac1720_get_vbat(void) {
uint16_t val;
if(!I2C_read16(PAC1720_ADDRESS, PAC1720_CH2_VSOURCE_HIGH, &val)) {
error |= 0x1;
return 0; // PAC1720 not available (maybe Vcc too low)
}
// Reset current measurement
pac1720_pbat = 0;
pac1720_pbat_counter = 0;
uint16_t ret = (val >> 5) * 20000 / 0x400;
if(ret < 1500)
error |= 0x2; // The chip is unreliable
return ret;
}
uint16_t pac1720_getVbat(void) {
uint16_t pac1720_get_vsol(void) {
uint16_t val;
if(!I2C_read16(PAC1720_ADDRESS, PAC1720_CH2_VSOURCE_HIGH, &val))
if(!I2C_read16(PAC1720_ADDRESS, PAC1720_CH1_VSOURCE_HIGH, &val)) {
error |= 0x1;
return 0; // PAC1720 not available (maybe Vcc too low)
}
return (val >> 5) * 20000 / 0x400;
}
int16_t pac1720_getAvgRbat(void) {
// Return current value if time interval too short
if(!pac1720_rbat_counter)
return 0;
// Calculate average power
int16_t ret = pac1720_rbat / pac1720_rbat_counter;
// Reset current measurement
pac1720_rbat = 0;
pac1720_rbat_counter = 0;
return ret;
}
uint16_t pac1720_getVsol(void) {
uint16_t val;
if(!I2C_read16(PAC1720_ADDRESS, PAC1720_CH1_VSOURCE_HIGH, &val))
return 0; // PAC1720 not available (maybe Vcc too low)
return (val >> 5) * 20000 / 0x400;
}
@ -85,53 +90,74 @@ uint16_t pac1720_getVsol(void) {
bool pac1720_isAvailable(void)
{
uint8_t val;
if(I2C_read8(PAC1720_ADDRESS, PAC1720_PRODUCT_ID, &val))
if(I2C_read8(PAC1720_ADDRESS, PAC1720_PRODUCT_ID, &val)) {
error |= val != 0x57;
return val == 0x57;
else
} else {
error |= 0x1;
return false; // PAC1720 not available (maybe Vcc too low)
}
}
static void sendConfig(void)
{
/* Write for both channels
* Current sensor sampling time 80ms (Denominator 2047)
* Current sensing average enabled 0x3
* Current sensing range +-10mV (FSR)
*/
I2C_write8(PAC1720_ADDRESS, PAC1720_CH1_VSENSE_SAMP_CONFIG, 0x5C);
I2C_write8(PAC1720_ADDRESS, PAC1720_CH2_VSENSE_SAMP_CONFIG, 0x5C);
I2C_write8(PAC1720_ADDRESS, PAC1720_V_SOURCE_SAMP_CONFIG, 0xFF);
}
void pac1720_get_avg(uint16_t* vbat, uint16_t* vsol, int16_t* pbat, int16_t* psol) {
// Return current value if time interval too short
if(!pac1720_counter) {
*vbat = pac1720_get_vbat();
*vsol = pac1720_get_vsol();
*pbat = pac1720_get_pbat();
*psol = pac1720_get_psol();
return;
}
pac1720_lock();
// Calculate average power
*vbat = pac1720_vbat / pac1720_counter;
*vsol = pac1720_vsol / pac1720_counter;
*pbat = pac1720_pbat / pac1720_counter;
*psol = pac1720_psol / pac1720_counter;
// Reset current measurement
pac1720_vbat = 0;
pac1720_vsol = 0;
pac1720_pbat = 0;
pac1720_psol = 0;
pac1720_counter = 0;
pac1720_unlock();
}
THD_FUNCTION(pac1720_thd, arg)
{
(void)arg;
uint32_t counter = 0;
int32_t u1 = 999999;
int32_t p1 = 999999;
int32_t u2 = -999999;
int32_t p2 = -999999;
while(true)
{
// Sample data
int32_t v = pac1720_getVbat();
int32_t p = pac1720_getPbat();
// Send config
sendConfig();
// Measure battery power
pac1720_pbat += p;
pac1720_pbat_counter++;
pac1720_lock();
pac1720_vbat += pac1720_get_vbat();
pac1720_vsol += pac1720_get_vsol();
pac1720_pbat += pac1720_get_pbat();
pac1720_psol += pac1720_get_psol();
pac1720_counter++;
pac1720_unlock();
// Measure battery impedance
if(p < p1) {
u1 = v;
p1 = p;
}
if(p > p2) {
u2 = v;
p2 = p;
}
if(++counter%10 == 0 && abs(p1-p2) > 100 && abs(u1-u2) > 0 && p1*u2 != p2*u1)
{
int32_t rbat = abs((u1*u2*(u1-u2)) / (p1*u2 - p2*u1));
pac1720_rbat += rbat;
pac1720_rbat_counter++;
u1 = 999999;
p1 = 999999;
u2 = -999999;
p2 = -999999;
}
chThdSleepMilliseconds(50);
chThdSleepMilliseconds(200);
}
}
@ -143,16 +169,18 @@ void pac1720_init(void)
palSetLineMode(LINE_SOL_SHORT_EN, PAL_MODE_OUTPUT_PUSHPULL);
palSetLine(LINE_SOL_SHORT_EN);
/* Write for both channels
* Current sensor sampling time 80ms (Denominator 2047)
* Current sensing average enabled 0x3
* Current sensing range +-80mV (FSR)
*/
I2C_write8(PAC1720_ADDRESS, PAC1720_CH1_VSENSE_SAMP_CONFIG, 0x5F);
I2C_write8(PAC1720_ADDRESS, PAC1720_CH2_VSENSE_SAMP_CONFIG, 0x5F);
I2C_write8(PAC1720_ADDRESS, PAC1720_V_SOURCE_SAMP_CONFIG, 0xFF);
// Send config
sendConfig();
TRACE_INFO("PAC > Init PAC1720 continuous measurement");
chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(512), "PAC1720", LOWPRIO, pac1720_thd, NULL);
chThdSleepMilliseconds(10);
}
uint8_t pac1720_hasError(void)
{
uint8_t ret = error;
error = 0x0;
return ret;
}

Wyświetl plik

@ -49,13 +49,14 @@
#define PAC1720_REVISION 0xFF
void pac1720_init(void);
int16_t pac1720_getIsol(void);
int16_t pac1720_getPbat(void);
int16_t pac1720_getAvgPbat(void);
uint16_t pac1720_getVbat(void);
int16_t pac1720_getAvgRbat(void);
uint16_t pac1720_getVsol(void);
int16_t pac1720_get_pbat(void);
int16_t pac1720_get_psol(void);
uint16_t pac1720_get_vbat(void);
uint16_t pac1720_get_vsol(void);
bool pac1720_isAvailable(void);
void pac1720_get_avg(uint16_t* vbat, uint16_t* vsol, int16_t* pbat, int16_t* psol);
void pac1720_init(void);
uint8_t pac1720_hasError(void);
#endif

Wyświetl plik

@ -18,8 +18,9 @@ static const SPIConfig ls_spicfg = {
};
#define getSPIDriver() &ls_spicfg
uint32_t outdiv;
bool initialized = false;
static uint32_t outdiv;
static bool initialized = false;
static int16_t lastTemp;
/**
* Initializes Si4464 transceiver chip. Adjustes the frequency which is shifted by variable
@ -37,7 +38,7 @@ void Si4464_Init(void) {
palSetLine(LINE_RADIO_CS);
// Reset radio
Si4464_shutdown();
palSetLine(LINE_RADIO_SDN);
palSetLine(LINE_OSC_EN); // Activate Oscillator
chThdSleepMilliseconds(10);
@ -104,7 +105,8 @@ void Si4464_Init(void) {
Si4464_write(use_lsb_first, 5);
// Temperature readout
TRACE_INFO("SI > Transmitter temperature %d degC", Si4464_getTemperature());
lastTemp = Si4464_getTemperature();
TRACE_INFO("SI > Transmitter temperature %d degC", lastTemp/100);
initialized = true;
}
@ -293,10 +295,14 @@ void stopTx(void) {
}
void Si4464_shutdown(void) {
palSetLine(LINE_RADIO_SDN); // Power down chip
palSetLineMode(LINE_SPI_SCK, PAL_MODE_INPUT_PULLDOWN); // SCK
palSetLineMode(LINE_SPI_MISO, PAL_MODE_INPUT_PULLDOWN); // MISO
palSetLineMode(LINE_SPI_MOSI, PAL_MODE_INPUT_PULLDOWN); // MOSI
palSetLineMode(LINE_RADIO_CS, PAL_MODE_INPUT_PULLDOWN); // RADIO CS
palSetLineMode(LINE_RADIO_SDN, PAL_MODE_INPUT_PULLDOWN); // RADIO SDN
palSetLineMode(LINE_OSC_EN, PAL_MODE_OUTPUT_PUSHPULL); // Oscillator
palSetLine(LINE_IO_LED1); // Set indication LED
palClearLine(LINE_OSC_EN); // Shutdown oscillator
RADIO_WRITE_GPIO(false); // Set GPIO1 low
initialized = false;
}
@ -351,11 +357,14 @@ uint8_t Si4464_getState(void) {
return rxData[2];
}
int8_t Si4464_getTemperature(void) {
int16_t Si4464_getTemperature(void) {
uint8_t txData[2] = {0x14, 0x10};
uint8_t rxData[8];
Si4464_read(txData, 2, rxData, 8);
uint16_t adc = rxData[7] | ((rxData[6] & 0x7) << 8);
return (899*adc)/4096 - 293;
return (89900*adc)/4096 - 29300;
}
int16_t Si4464_getLastTemperature(void) {
return lastTemp;
}

Wyświetl plik

@ -39,7 +39,8 @@ bool radioTune(uint32_t frequency, uint16_t shift, int8_t level, uint16_t size);
void Si4464_writeFIFO(uint8_t *msg, uint8_t size);
uint8_t Si4464_freeFIFO(void);
uint8_t Si4464_getState(void);
int8_t Si4464_getTemperature(void);
int16_t Si4464_getTemperature(void);
int16_t Si4464_getLastTemperature(void);
#endif

Wyświetl plik

@ -186,35 +186,48 @@ uint16_t gps_receive_payload(uint8_t class_id, uint8_t msg_id, unsigned char *pa
*
*/
bool gps_get_fix(gpsFix_t *fix) {
static uint8_t response[92];
static uint8_t navpvt[128];
static uint8_t navstatus[32];
// Transmit request
uint8_t pvt[] = {0xB5, 0x62, 0x01, 0x07, 0x00, 0x00, 0x08, 0x19};
gps_transmit_string(pvt, sizeof(pvt));
uint8_t navpvt_req[] = {0xB5, 0x62, 0x01, 0x07, 0x00, 0x00, 0x08, 0x19};
gps_transmit_string(navpvt_req, sizeof(navpvt_req));
if(!gps_receive_payload(0x01, 0x07, response, 5000)) { // Receive request
TRACE_ERROR("GPS > PVT Polling FAILED");
if(!gps_receive_payload(0x01, 0x07, navpvt, 3000)) { // Receive request
TRACE_ERROR("GPS > NAV-PVT Polling FAILED");
return false;
}
fix->num_svs = response[23];
fix->type = response[20];
uint8_t navstatus_req[] = {0xB5, 0x62, 0x01, 0x03, 0x00, 0x00, 0x04, 0x0D};
gps_transmit_string(navstatus_req, sizeof(navstatus_req));
fix->time.year = response[4] + (response[5] << 8);
fix->time.month = response[6];
fix->time.day = response[7];
fix->time.hour = response[8];
fix->time.minute = response[9];
fix->time.second = response[10];
if(!gps_receive_payload(0x01, 0x03, navstatus, 3000)) { // Receive request
TRACE_ERROR("GPS > NAV-STATUS Polling FAILED");
return false;
}
// Extract data from message
fix->fixOK = navstatus[5] & 0x1;
fix->pdop = navpvt[76] + (navpvt[77] << 8);
fix->num_svs = navpvt[23];
fix->type = navpvt[20];
fix->time.year = navpvt[4] + (navpvt[5] << 8);
fix->time.month = navpvt[6];
fix->time.day = navpvt[7];
fix->time.hour = navpvt[8];
fix->time.minute = navpvt[9];
fix->time.second = navpvt[10];
fix->lat = (int32_t) (
(uint32_t)(response[28]) + ((uint32_t)(response[29]) << 8) + ((uint32_t)(response[30]) << 16) + ((uint32_t)(response[31]) << 24)
(uint32_t)(navpvt[28]) + ((uint32_t)(navpvt[29]) << 8) + ((uint32_t)(navpvt[30]) << 16) + ((uint32_t)(navpvt[31]) << 24)
);
fix->lon = (int32_t) (
(uint32_t)(response[24]) + ((uint32_t)(response[25]) << 8) + ((uint32_t)(response[26]) << 16) + ((uint32_t)(response[27]) << 24)
(uint32_t)(navpvt[24]) + ((uint32_t)(navpvt[25]) << 8) + ((uint32_t)(navpvt[26]) << 16) + ((uint32_t)(navpvt[27]) << 24)
);
int32_t alt_tmp = (((int32_t)
((uint32_t)(response[36]) + ((uint32_t)(response[37]) << 8) + ((uint32_t)(response[38]) << 16) + ((uint32_t)(response[39]) << 24))
((uint32_t)(navpvt[36]) + ((uint32_t)(navpvt[37]) << 8) + ((uint32_t)(navpvt[38]) << 16) + ((uint32_t)(navpvt[39]) << 24))
) / 1000);
if (alt_tmp <= 0) {
fix->alt = 1;
@ -224,10 +237,10 @@ bool gps_get_fix(gpsFix_t *fix) {
fix->alt = (uint16_t)alt_tmp;
}
TRACE_INFO("GPS > PVT Polling OK time=%04d-%02d-%02d %02d:%02d:%02d lat=%d.%05d lon=%d.%05d alt=%dm sats=%d",
TRACE_INFO("GPS > Polling OK time=%04d-%02d-%02d %02d:%02d:%02d lat=%d.%05d lon=%d.%05d alt=%dm sats=%d fixOK=%d pDOP=%d.%02d",
fix->time.year, fix->time.month, fix->time.day, fix->time.hour, fix->time.minute, fix->time.day,
fix->lat/10000000, (fix->lat > 0 ? 1:-1)*(fix->lat/100)%100000, fix->lon/10000000, (fix->lon > 0 ? 1:-1)*(fix->lon/100)%100000,
fix->alt, fix->num_svs
fix->alt, fix->num_svs, fix->fixOK, fix->pdop/100, fix->pdop%100
);
return true;
@ -385,7 +398,7 @@ bool GPS_Init(void) {
return false;
}
cntr = 5;
cntr = 3;
while((status = gps_set_airborne_model()) == false && cntr--);
if(status) {
TRACE_INFO("GPS > ... Set airborne model OK");

Wyświetl plik

@ -15,7 +15,7 @@
//#define UBLOX_USE_UART
#define UBLOX_USE_I2C
#define isGPSLocked(pos) ((pos)->type == 3 && (pos)->num_svs >= 5)
#define isGPSLocked(pos) ((pos)->type == 3 && (pos)->num_svs >= 4 && (pos)->fixOK == true)
typedef struct {
ptime_t time; // Time
@ -24,6 +24,8 @@ typedef struct {
int32_t lat; // latitude in deg * 10^7, range -90 .. +90 * 10^7
int32_t lon; // longitude in deg * 10^7, range -180 .. +180 * 10^7
int32_t alt; // altitude in m, range 0m, up to ~40000m, clamped
bool fixOK; // Flag that is set to true, when DOP is with the limits
uint16_t pdop; // Position DOP
} gpsFix_t;
uint8_t gps_set_gps_only(void);

Wyświetl plik

@ -68,44 +68,28 @@ void doConversion(void)
deinitADC();
}
static uint16_t getBatteryVoltageMV_STM32(void)
uint16_t stm32_get_vbat(void)
{
doConversion();
return samples[2] * vcc_ref * DIVIDER_VBAT / 4096;
}
static uint16_t getSolarVoltageMV_STM32(void)
uint16_t stm32_get_vsol(void)
{
doConversion();
return samples[0] * vcc_ref * DIVIDER_VSOL / 4096;
}
uint16_t getBatteryVoltageMV(void)
{
uint16_t vbat = getBatteryVoltageMV_STM32(); // Get value from STM32
uint16_t vbat_pac = pac1720_getVbat(); // Get value from PAC1720
return abs(vbat-vbat_pac) < 200 ? vbat_pac : vbat;
}
uint16_t getSolarVoltageMV(void)
{
uint16_t vsol = getSolarVoltageMV_STM32(); // Get value from STM32
uint16_t vsol_pac = pac1720_getVsol(); // Get value from PAC1720
return abs(vsol-vsol_pac) < 200 ? vsol_pac : vsol;
}
uint16_t getUSBVoltageMV(void)
uint16_t stm32_get_vusb(void)
{
doConversion();
return samples[1] * vcc_ref * DIVIDER_VUSB / 4096;
}
uint16_t getSTM32Temperature(void)
uint16_t stm32_get_temp(void)
{
doConversion();
return samples[3];
return (((int32_t)samples[3]*40 * vcc_ref / 4096)-30400) + 2500 + 850/*Calibration*/;
}
void boost_voltage(bool boost)

Wyświetl plik

@ -8,10 +8,10 @@
void initADC(void);
void deinitADC(void);
uint16_t getBatteryVoltageMV(void);
uint16_t getSolarVoltageMV(void);
uint16_t getUSBVoltageMV(void);
uint16_t getSTM32Temperature(void);
uint16_t stm32_get_vbat(void);
uint16_t stm32_get_vsol(void);
uint16_t stm32_get_vusb(void);
uint16_t stm32_get_temp(void);
void boost_voltage(bool boost);
#endif

Wyświetl plik

@ -8,6 +8,8 @@
#define I2C_DRIVER (&I2CD1)
static uint8_t error;
const I2CConfig _i2cfg = {
OPMODE_I2C,
200000,
@ -23,8 +25,12 @@ static bool I2C_transmit(uint8_t addr, uint8_t *txbuf, uint32_t txbytes, uint8_t
if(i2c_status == MSG_TIMEOUT) { // Restart I2C at timeout
TRACE_ERROR("I2C > TIMEOUT (ADDR 0x%02x)", addr);
error = 0x1;
} else if(i2c_status == MSG_RESET) {
TRACE_ERROR("I2C > RESET (ADDR 0x%02x)", addr);
error = 0x0;
} else {
error = 0x0;
}
return i2c_status == MSG_OK;
@ -97,3 +103,8 @@ bool I2C_write8_16bitreg(uint8_t address, uint16_t reg, uint8_t value) // 16bit
return I2C_transmit(address, txbuf, 3, NULL, 0, MS2ST(100));
}
uint8_t I2C_hasError(void)
{
return error;
}

Wyświetl plik

@ -26,5 +26,7 @@ bool I2C_read16_LE(uint8_t address, uint8_t reg, uint16_t *val);
void I2C_Lock(void);
void I2C_Unlock(void);
uint8_t I2C_hasError(void);
#endif

Wyświetl plik

@ -10,28 +10,27 @@ const uint16_t leapYear[] = {0,31,60,91,121,152,182,213,244,274,305,335,366};
* @param time Date to be converted
* @return UNIX timestamp in milliseconds
*/
uint64_t date2UnixTimestamp(ptime_t time) {
uint64_t timeC = 0;
timeC = time.second;
timeC += time.minute * 60;
timeC += time.hour * 3600;
timeC += (time.day-1) * 86400;
uint32_t date2UnixTimestamp(ptime_t *date) {
uint32_t timeC = 0;
timeC = date->second;
timeC += date->minute * 60;
timeC += date->hour * 3600;
timeC += (date->day-1) * 86400;
if(time.year % 4 == 0) { // is leapyear?
timeC += leapYear[time.month-1] * 86400;
if(date->year % 4 == 0) { // is leapyear?
timeC += leapYear[date->month-1] * 86400;
} else {
timeC += nonLeapYear[time.month-1] * 86400;
timeC += nonLeapYear[date->month-1] * 86400;
}
uint16_t i;
for(i=1970; i<time.year; i++) {
for(uint16_t i=1970; i<date->year; i++) {
if(i % 4 == 0) { // is leapyear?
timeC += 31622400;
} else {
timeC += 31536000;
}
}
return timeC * 1000 + time.millisecond;
return timeC;
}
/**
@ -40,32 +39,26 @@ uint64_t date2UnixTimestamp(ptime_t time) {
* @param UNIX timestamp in milliseconds
* @return Date in Julian calendar format
*/
ptime_t unixTimestamp2Date(uint64_t time) {
ptime_t date;
uint64_t dateRaw = time / 1000;
date.year = 1970;
void unixTimestamp2Date(ptime_t *date, uint32_t time) {
date->year = 1970;
while(true)
{
uint32_t secondsInThisYear = date.year % 4 ? 31536000 : 31622400;
if(dateRaw >= secondsInThisYear) {
dateRaw -= secondsInThisYear;
date.year++;
uint32_t secondsInThisYear = date->year % 4 ? 31536000 : 31622400;
if(time >= secondsInThisYear) {
time -= secondsInThisYear;
date->year++;
} else {
break;
}
}
for(date.month=1; (date.year%4 ? nonLeapYear[date.month] : (uint32_t)(leapYear[date.month])*86400)<=dateRaw; date.month++);
dateRaw -= (date.year%4 ? nonLeapYear[date.month-1] : leapYear[date.month-1])*86400;
for(date->month=1; (date->year%4 ? nonLeapYear[date->month]*86400 : leapYear[date->month]*86400)<=time; date->month++);
time -= (date->year%4 ? nonLeapYear[date->month-1] : leapYear[date->month-1])*86400;
date.day = (dateRaw / 86400) + 1;
date.hour = (dateRaw % 86400) / 3600;
date.minute = (dateRaw % 3600) / 60;
date.second = dateRaw % 60;
date.millisecond = time % 1000;
return date;
date->day = (time / 86400) + 1;
date->hour = (time % 86400) / 3600;
date->minute = (time % 3600) / 60;
date->second = time % 60;
}
/**
@ -73,6 +66,7 @@ ptime_t unixTimestamp2Date(uint64_t time) {
* @return Date in Julian calendar format
*/
void getTime(ptime_t *date) {
TRACE_INFO("GPS > Get time from RTC");
RTCDateTime timespec;
rtcGetTime(&RTCD1, &timespec);
@ -82,22 +76,20 @@ void getTime(ptime_t *date) {
date->hour = timespec.millisecond / 3600000;
date->minute = (timespec.millisecond%3600000) / 60000;
date->second = (timespec.millisecond / 1000) % 60;
date->millisecond = timespec.millisecond % 1000;
}
/**
* Sets the STM32 internal time (RTC)
* @param date Date in Julian calendar format
*/
void setTime(ptime_t date) {
RTCDateTime timespec;
timespec.year = date.year - 2000;
timespec.month = date.month;
timespec.day = date.day;
timespec.millisecond = date.hour * 3600000 + date.minute * 60000 + date.second * 1000 + date.millisecond;
void setTime(ptime_t *date) {
TRACE_INFO("GPS > Calibrate RTC");
PRINT_TIME("RTC");
RTCDateTime timespec;
timespec.year = date->year - 2000;
timespec.month = date->month;
timespec.day = date->day;
timespec.millisecond = date->hour * 3600000 + date->minute * 60000 + date->second * 1000;
rtcSetTime(&RTCD1, &timespec);
}

Wyświetl plik

@ -10,13 +10,12 @@ typedef struct {
uint8_t hour;
uint8_t minute;
uint8_t second;
uint16_t millisecond;
} ptime_t;
uint64_t date2UnixTimestamp(ptime_t time);
ptime_t unixTimestamp2Date(uint64_t time);
uint32_t date2UnixTimestamp(ptime_t *date);
void unixTimestamp2Date(ptime_t *date, uint32_t time);
void getTime(ptime_t *date);
void setTime(ptime_t date);
void setTime(ptime_t *date);
#endif

Wyświetl plik

@ -48,15 +48,15 @@
#define STM32_PLLSRC STM32_PLLSRC_HSI
#define STM32_PLLM_VALUE 16
#define STM32_PLLN_VALUE 192
#define STM32_PLLP_VALUE 4
#define STM32_PLLP_VALUE 2
#define STM32_PLLQ_VALUE 4
#if ACTIVATE_USB /* Activate 48MHz when USB is activated, otherwise 6MHz */
#define STM32_HPRE STM32_HPRE_DIV1
#else
#define STM32_HPRE STM32_HPRE_DIV16
#define STM32_HPRE STM32_HPRE_DIV1
#endif
#define STM32_PPRE1 STM32_PPRE1_DIV1
#define STM32_PPRE2 STM32_PPRE2_DIV1
#define STM32_PPRE1 STM32_PPRE1_DIV2
#define STM32_PPRE2 STM32_PPRE2_DIV2
#define STM32_RTCSEL STM32_RTCSEL_LSI
#define STM32_RTCPRE_VALUE 8
#define STM32_MCO1SEL STM32_MCO1SEL_PLL

Wyświetl plik

@ -21,10 +21,10 @@
#include <math.h>
#include <string.h>
#include "debug.h"
#include "base91.h"
#define METER_TO_FEET(m) (((m)*26876) / 8192)
static uint16_t loss_of_gps_counter;
static uint16_t msg_id;
/**
@ -39,16 +39,11 @@ static uint16_t msg_id;
*/
void aprs_encode_position(ax25_t* packet, const aprs_conf_t *config, trackPoint_t *trackPoint)
{
char temp[22];
ptime_t date = trackPoint->time;
char temp[128];
// 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_byte(packet, '!');
// Latitude
uint32_t y = 380926 * (90 - trackPoint->gps_lat/10000000.0);
@ -73,7 +68,7 @@ void aprs_encode_position(ax25_t* packet, const aprs_conf_t *config, trackPoint_
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 gpsFix = trackPoint->gps_lock == GPS_LOCKED1 || trackPoint->gps_lock == GPS_LOCKED2 ? GSP_FIX_CURRENT : GSP_FIX_OLD;
uint8_t src = NMEA_SRC_GGA;
uint8_t origin = ORIGIN_PICO;
@ -95,50 +90,9 @@ void aprs_encode_position(ax25_t* packet, const aprs_conf_t *config, trackPoint_
ax25_send_string(packet, temp);
// Comments
ax25_send_string(packet, "SATS ");
chsnprintf(temp, sizeof(temp), "%d", trackPoint->gps_sats);
base91_encode((uint8_t*)trackPoint, (uint8_t*)temp, sizeof(trackPoint_t));
ax25_send_string(packet, temp);
switch(trackPoint->gps_lock) {
case GPS_LOCKED: // GPS is locked
// TTFF (Time to first fix)
ax25_send_string(packet, " TTFF ");
chsnprintf(temp, sizeof(temp), "%d", trackPoint->gps_ttff);
ax25_send_string(packet, temp);
ax25_send_string(packet, "sec");
loss_of_gps_counter = 0;
break;
case GPS_LOSS: // No GPS lock
ax25_send_string(packet, " GPS LOSS ");
chsnprintf(temp, sizeof(temp), "%d", ++loss_of_gps_counter);
ax25_send_string(packet, temp);
break;
case GPS_LOWBATT1: // GPS switched off prematurely
ax25_send_string(packet, " GPS LOWBATT1 ");
chsnprintf(temp, sizeof(temp), "%d", ++loss_of_gps_counter);
ax25_send_string(packet, temp);
break;
case GPS_LOWBATT2: // GPS switched off prematurely
ax25_send_string(packet, " GPS LOWBATT2 ");
chsnprintf(temp, sizeof(temp), "%d", ++loss_of_gps_counter);
ax25_send_string(packet, temp);
break;
case GPS_ERROR: // GPS error
ax25_send_string(packet, " GPS ERROR ");
chsnprintf(temp, sizeof(temp), "%d", ++loss_of_gps_counter);
ax25_send_string(packet, temp);
break;
case GPS_LOG: // GPS position from log (because the tracker has been just switched on)
ax25_send_string(packet, " GPS FROM LOG");
loss_of_gps_counter = 0;
break;
}
ax25_send_byte(packet, '|');
// Sequence ID
@ -151,15 +105,14 @@ void aprs_encode_position(ax25_t* packet, const aprs_conf_t *config, trackPoint_
// Telemetry parameter
for(uint8_t i=0; i<5; i++) {
switch(config->tel[i]) {
case TEL_SATS: t = trackPoint->gps_sats; break;
case TEL_TTFF: t = trackPoint->gps_ttff; break;
case TEL_VBAT: t = trackPoint->adc_vbat; break;
case TEL_VSOL: t = trackPoint->adc_vsol; break;
case TEL_PBAT: t = trackPoint->adc_pbat+4096; break;
case TEL_RBAT: t = trackPoint->adc_rbat; break;
case TEL_HUM: t = trackPoint->air_hum; break;
case TEL_PRESS: t = trackPoint->air_press/125 - 40; break;
case TEL_TEMP: t = trackPoint->air_temp/10 + 1000; break;
case TEL_SATS: t = trackPoint->gps_sats; break;
case TEL_TTFF: t = trackPoint->gps_ttff; break;
case TEL_VBAT: t = trackPoint->adc_vbat; break;
case TEL_VSOL: t = trackPoint->adc_vsol; break;
case TEL_PBAT: t = trackPoint->pac_pbat+4096; break;
case TEL_HUM: t = trackPoint->sen_i1_hum; break;
case TEL_PRESS: t = trackPoint->sen_i1_press/125 - 40; break;
case TEL_TEMP: t = trackPoint->sen_i1_temp/10 + 1000; break;
}
temp[0] = t/91 + 33;
@ -185,62 +138,11 @@ void aprs_encode_init(ax25_t* packet, uint8_t* buffer, uint16_t size, mod_t mod)
// Encode APRS header
ax25_init(packet);
}
void aprs_encode_data_packet(ax25_t* packet, char packetType, const aprs_conf_t *config, uint8_t *data, size_t size, trackPoint_t *trackPoint)
void aprs_encode_data_packet(ax25_t* packet, char packetType, const aprs_conf_t *config, uint8_t *data, size_t size)
{
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_string(packet, "{{");
ax25_send_byte(packet, packetType);
// Encode message
@ -318,7 +220,6 @@ void aprs_encode_telemetry_configuration(ax25_t* packet, const aprs_conf_t *conf
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;
@ -351,10 +252,6 @@ void aprs_encode_telemetry_configuration(ax25_t* packet, const aprs_conf_t *conf
ax25_send_string(packet, "W");
break;
case TEL_RBAT:
ax25_send_string(packet, "Ohm");
break;
case TEL_HUM:
ax25_send_string(packet, "%");
break;
@ -386,7 +283,6 @@ void aprs_encode_telemetry_configuration(ax25_t* packet, const aprs_conf_t *conf
case TEL_VBAT:
case TEL_VSOL:
case TEL_RBAT:
ax25_send_string(packet, "0,.001,0");
break;

Wyświetl plik

@ -54,7 +54,7 @@ void aprs_encode_telemetry_configuration(ax25_t* packet, const aprs_conf_t *conf
void aprs_encode_message(ax25_t* packet, const aprs_conf_t *config, const char *receiver, const char *text);
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);
void aprs_encode_data_packet(ax25_t* packet, char packetType, const aprs_conf_t *config, uint8_t *data, size_t size);
uint32_t aprs_encode_finalize(ax25_t* packet);
#endif

Wyświetl plik

@ -133,7 +133,7 @@ void ax25_send_header(ax25_t *packet, const char *callsign, uint8_t ssid, const
}
for(i=0; i<preamble; i++)
{
ax25_send_sync(packet);
ax25_send_flag(packet);
}
// Send flag

Wyświetl plik

@ -632,6 +632,11 @@ static void ssdv_set_packet_conf(ssdv_t *s)
s->pkt_size_payload = SSDV_PKT_SIZE - SSDV_PKT_SIZE_HEADER - SSDV_PKT_SIZE_CRC;
s->pkt_size_crcdata = SSDV_PKT_SIZE_HEADER + s->pkt_size_payload - 1;
break;
case SSDV_TYPE_PADDING:
s->pkt_size_payload = SSDV_PKT_SIZE - SSDV_PKT_SIZE_HEADER - SSDV_PKT_SIZE_CRC - SSDV_PKT_SIZE_PADDING;
s->pkt_size_crcdata = SSDV_PKT_SIZE_HEADER + s->pkt_size_payload - 1;
break;
}
}
@ -1411,6 +1416,25 @@ char ssdv_dec_is_packet(uint8_t *packet, int *errors)
type = SSDV_TYPE_NORMAL;
}
}
else if(pkt[1] == 0x66 + SSDV_TYPE_PADDING)
{
/* Test for a valid NOFEC packet */
pkt_size_payload = SSDV_PKT_SIZE - SSDV_PKT_SIZE_HEADER - SSDV_PKT_SIZE_CRC - SSDV_PKT_SIZE_PADDING;
pkt_size_crcdata = SSDV_PKT_SIZE_HEADER + pkt_size_payload - 1;
/* No FEC scan */
if(errors) *errors = 0;
/* Test the checksum */
x = crc32(&pkt[1], pkt_size_crcdata);
i = 1 + pkt_size_crcdata;
if(x == (pkt[i + 3] | (pkt[i + 2] << 8) | (pkt[i + 1] << 16) | (pkt[i] << 24)))
{
/* Valid, set the type and continue */
type = SSDV_TYPE_PADDING;
}
}
if(type == SSDV_TYPE_INVALID)
{

Wyświetl plik

@ -36,6 +36,7 @@ extern "C" {
#define SSDV_PKT_SIZE_HEADER (0x0F)
#define SSDV_PKT_SIZE_CRC (0x04)
#define SSDV_PKT_SIZE_RSCODES (0x20)
#define SSDV_PKT_SIZE_PADDING (0x48)
#define TBL_LEN (546) /* Maximum size of the DQT and DHT tables */
#define HBUFF_LEN (16) /* Extra space for reading marker data */
@ -45,6 +46,7 @@ extern "C" {
#define SSDV_TYPE_INVALID (0xFF)
#define SSDV_TYPE_NORMAL (0x00)
#define SSDV_TYPE_NOFEC (0x01)
#define SSDV_TYPE_PADDING (0x02)
typedef struct
{

Wyświetl plik

@ -31,6 +31,7 @@ static uint32_t txj; // Bytecounter
// Radio related
static mutex_t radio_mtx; // Radio mutex
static bool nextTransmissionWaiting; // Flag that informs the feeder thread to keep the radio switched on
bool radio_mtx_init = false;
static mod_t active_mod = MOD_NOT_SET;
static radioMSG_t radio_msg;
@ -265,7 +266,7 @@ THD_FUNCTION(si_fifo_feeder_thd, arg)
}
Si4464_writeFIFO(&radio_msg.buffer[c], more); // Write into FIFO
c += more;
chThdSleepMilliseconds(15); // That value is ok up to 38k4
chThdSleepMilliseconds(15); // That value is ok up to 96k
}
// Shutdown radio (and wait for Si4464 to finish transmission)
@ -322,11 +323,17 @@ void shutdownRadio(void)
{
// Wait for PH to finish transmission
while(Si4464_getState() == SI4464_STATE_TX)
chThdSleepMilliseconds(10);
chThdSleepMilliseconds(1);
TRACE_INFO("RAD > Shutdown radio");
Si4464_shutdown();
active_mod = MOD_NOT_SET;
if(!nextTransmissionWaiting) { // No thread is waiting for radio, so shutdown radio
TRACE_INFO("RAD > Transmission finished");
TRACE_INFO("RAD > Shutdown radio");
Si4464_shutdown();
active_mod = MOD_NOT_SET;
} else {
TRACE_INFO("RAD > Transmission finished");
TRACE_INFO("RAD > Keep radio switched on");
}
}
/**
@ -477,6 +484,23 @@ void lockRadio(void)
chMtxObjectInit(&radio_mtx);
radio_mtx_init = true;
chMtxLock(&radio_mtx);
nextTransmissionWaiting = true;
// Wait for old feeder thread to terminate
if(feeder_thd != NULL) // No waiting on first use
chThdWait(feeder_thd);
}
/* This method is only called by image.c. It's not different to lockRadio() with
* the exception that the radio it shutdown after transmission (if there is any) */
void lockRadioByCamera(void)
{
// Initialize mutex
if(!radio_mtx_init)
chMtxObjectInit(&radio_mtx);
radio_mtx_init = true;
chMtxLock(&radio_mtx);
// Wait for old feeder thread to terminate
@ -486,6 +510,7 @@ void lockRadio(void)
void unlockRadio(void)
{
nextTransmissionWaiting = false;
chMtxUnlock(&radio_mtx);
}

Wyświetl plik

@ -23,6 +23,7 @@ bool transmitOnRadio(radioMSG_t *msg, bool shutdown);
void shutdownRadio(void);
uint32_t getFrequency(freq_conf_t *config);
void lockRadio(void);
void lockRadioByCamera(void);
void unlockRadio(void);
THD_FUNCTION(moduleRADIO, arg);

Wyświetl plik

@ -15,16 +15,16 @@ bool p_sleep(const sleep_conf_t *config)
switch(config->type)
{
case SLEEP_WHEN_VBAT_BELOW_THRES:
return getBatteryVoltageMV() < config->vbat_thres;
return stm32_get_vbat() < config->vbat_thres;
case SLEEP_WHEN_RBAT_BELOW_THRES:
return getLastTrackPoint()->adc_rbat < config->rbat_thres; // FIXME
case SLEEP_WHEN_VSOL_BELOW_THRES:
return stm32_get_vsol() < config->vsol_thres;
case SLEEP_WHEN_VBAT_ABOVE_THRES:
return getBatteryVoltageMV() > config->vbat_thres;
return stm32_get_vbat() > config->vbat_thres;
case SLEEP_WHEN_RBAT_ABOVE_THRES:
return getLastTrackPoint()->adc_rbat > config->rbat_thres; // FIXME
case SLEEP_WHEN_VSOL_ABOVE_THRES:
return stm32_get_vsol() > config->vsol_thres;
case SLEEP_WHEN_DISCHARGING:
case SLEEP_WHEN_CHARGING:

Wyświetl plik

@ -301,12 +301,11 @@ static void flush_ssdv_buffer(prot_t protocol, ax25_t *ax25_handle, radioMSG_t *
}
}
void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf, uint8_t image_id, trackPoint_t* captureLocation, bool redudantTx)
void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf, uint8_t image_id, bool redudantTx)
{
ssdv_t ssdv;
uint8_t pkt[SSDV_PKT_SIZE];
uint8_t pkt_base91i[160];
uint8_t pkt_base91j[160];
uint8_t pkt_base91[256];
const uint8_t *b;
uint32_t bi = 0;
uint8_t c = SSDV_OK;
@ -314,7 +313,7 @@ void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf,
// Init SSDV (FEC at 2FSK, non FEC at APRS)
bi = 0;
ssdv_enc_init(&ssdv, conf->protocol == PROT_SSDV_2FSK ? SSDV_TYPE_NORMAL : SSDV_TYPE_NOFEC, conf->ssdv_conf.callsign, image_id, conf->ssdv_conf.quality);
ssdv_enc_init(&ssdv, conf->protocol == PROT_SSDV_2FSK ? SSDV_TYPE_NORMAL : SSDV_TYPE_PADDING, conf->ssdv_conf.callsign, image_id, conf->ssdv_conf.quality);
ssdv_enc_set_buffer(&ssdv, pkt);
// Init transmission packet
@ -371,27 +370,11 @@ void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf,
TRACE_INFO("IMG > Encode APRS/SSDV packet");
// Sync byte, CRC and FEC of SSDV not transmitted (because its not neccessary inside an APRS packet)
base91_encode(&pkt[6 ], pkt_base91i, 109+16);
pkt[112+16] = pkt[6];
pkt[113+16] = pkt[7];
pkt[114+16] = pkt[8];
base91_encode(&pkt[112+16], pkt_base91j, 108+16);
base91_encode(&pkt[6], pkt_base91, 174);
aprs_encode_data_packet(&ax25_handle, 'I', &conf->aprs_conf, pkt_base91i, strlen((char*)pkt_base91i), captureLocation);
aprs_encode_data_packet(&ax25_handle, 'J', &conf->aprs_conf, pkt_base91j, strlen((char*)pkt_base91j), captureLocation);
aprs_encode_data_packet(&ax25_handle, 'I', &conf->aprs_conf, pkt_base91, strlen((char*)pkt_base91));
if(redudantTx)
{
if(conf->protocol == PROT_APRS_AFSK) // AFSK can handle max. 2 packets in the buffer
{
// Transmit packets
flush_ssdv_buffer(conf->protocol, &ax25_handle, &msg);
// Initialize new packet buffer
aprs_encode_init(&ax25_handle, buffer, sizeof(buffer), msg.mod);
}
aprs_encode_data_packet(&ax25_handle, 'I', &conf->aprs_conf, pkt_base91i, strlen((char*)pkt_base91i), captureLocation);
aprs_encode_data_packet(&ax25_handle, 'J', &conf->aprs_conf, pkt_base91j, strlen((char*)pkt_base91j), captureLocation);
}
aprs_encode_data_packet(&ax25_handle, 'I', &conf->aprs_conf, pkt_base91, strlen((char*)pkt_base91));
// Transmit if buffer is almost full or if single packet transmission is activated (packet_spacing != 0)
// or if AFSK is selected (because the encoding takes a lot of buffer)
@ -402,9 +385,6 @@ void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf,
// Initialize new packet buffer
aprs_encode_init(&ax25_handle, buffer, sizeof(buffer), msg.mod);
if(!conf->packet_spacing)
chThdSleepMilliseconds(8000); // Leave a little break because it will overflow some devices
}
break;
@ -432,6 +412,8 @@ void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf,
TRACE_ERROR("IMG > Unsupported protocol selected for module IMAGE");
}
chThdSleepMilliseconds(100); // Leave other threads some time
// Packet spacing (delay)
if(conf->packet_spacing)
chThdSleepMilliseconds(conf->packet_spacing);
@ -445,6 +427,7 @@ void encode_ssdv(const uint8_t *image, uint32_t image_len, module_conf_t* conf,
*/
static bool analyze_image(uint8_t *image, uint32_t image_len)
{
return true;
#if !OV5640_USE_DMA_DBM
if(image_len >= 65535)
{
@ -461,7 +444,7 @@ static bool analyze_image(uint8_t *image, uint32_t image_len)
uint16_t i = 0;
uint8_t c = SSDV_OK;
ssdv_enc_init(&ssdv, SSDV_TYPE_NORMAL, "", 0, 7);
ssdv_enc_init(&ssdv, SSDV_TYPE_NOFEC, "", 0, 7);
ssdv_enc_set_buffer(&ssdv, pkt);
while(true) // FIXME: I get caught in these loops occasionally and never return
@ -515,7 +498,7 @@ bool takePicture(ssdv_conf_t *conf, bool enableJpegValidation)
camera_found = true;
// Lock Radio (The radio uses the same DMA for SPI as the camera)
lockRadio(); // Lock radio
lockRadioByCamera(); // Lock radio
uint8_t cntr = 5;
bool jpegValid;
@ -582,17 +565,13 @@ 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, &captureLocation, conf->ssdv_conf.redundantTx);
encode_ssdv(conf->ssdv_conf.ram_buffer, conf->ssdv_conf.size_sampled, conf, gimage_id-1, 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, &captureLocation, conf->ssdv_conf.redundantTx);
encode_ssdv(noCameraFound, sizeof(noCameraFound), conf, gimage_id-1, conf->ssdv_conf.redundantTx);
}
}
@ -609,7 +588,7 @@ void start_image_thread(module_conf_t *conf)
TRACE_ERROR("IMG > Could not startup thread (not enough memory available)");
} else {
register_thread_at_wdg(conf);
conf->wdg_timeout = chVTGetSystemTimeX() + S2ST(1);
conf->wdg_timeout = chVTGetSystemTimeX() + S2ST(1) + MS2ST(conf->init_delay);
}
}

Wyświetl plik

@ -159,7 +159,7 @@ THD_FUNCTION(logThread, arg)
trackPoint_t log;
getNextLogTrackPoint(&log);
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);
TRACE_INFO("id=%d time=%d lat=%d lon=%d alt=%d", log.id, log.gps_time, 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;
@ -167,9 +167,8 @@ THD_FUNCTION(logThread, arg)
lon <<= 16;
lon /= 3600000000;
uint32_t time = date2UnixTimestamp(log.time) / 1000;
pkt[i*5+0] = time & 0xFFFF;
pkt[i*5+1] = time >> 16;
pkt[i*5+0] = log.gps_time & 0xFFFF;
pkt[i*5+1] = log.gps_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)
@ -196,7 +195,7 @@ THD_FUNCTION(logThread, arg)
// 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
aprs_encode_data_packet(&ax25_handle, 'L', &conf->aprs_conf, pkt_base91, strlen((char*)pkt_base91)); // Encode packet
msg.bin_len = aprs_encode_finalize(&ax25_handle);
// Transmit packet

Wyświetl plik

@ -70,12 +70,15 @@ void positionToMaidenhead(char m[], double lat, double lon)
* Replaces placeholders with variables
*/
void replace_placeholders(char* fskmsg, uint16_t size, trackPoint_t *tp) {
ptime_t time;
unixTimestamp2Date(&time, tp->gps_time);
char buf[16];
chsnprintf(buf, sizeof(buf), "%d", tp->id);
str_replace(fskmsg, size, "<ID>", buf);
chsnprintf(buf, sizeof(buf), "%04d-%02d-%02d", tp->time.year, tp->time.month, tp->time.day);
chsnprintf(buf, sizeof(buf), "%04d-%02d-%02d", time.year, time.month, time.day);
str_replace(fskmsg, size, "<DATE>", buf);
chsnprintf(buf, sizeof(buf), "%02d:%02d:%02d", tp->time.hour, tp->time.minute, tp->time.second);
chsnprintf(buf, sizeof(buf), "%02d:%02d:%02d", time.hour, time.minute, time.second);
str_replace(fskmsg, size, "<TIME>", buf);
chsnprintf(buf, sizeof(buf), "%d.%05d", tp->gps_lat/10000000, ((tp->gps_lat > 0 ? 1:-1)*tp->gps_lat%10000000)/100);
str_replace(fskmsg, size, "<LAT>", buf);
@ -91,15 +94,13 @@ void replace_placeholders(char* fskmsg, uint16_t size, trackPoint_t *tp) {
str_replace(fskmsg, size, "<VBAT>", buf);
chsnprintf(buf, sizeof(buf), "%d.%02d", tp->adc_vsol/1000, (tp->adc_vsol%1000)/10);
str_replace(fskmsg, size, "<VSOL>", buf);
chsnprintf(buf, sizeof(buf), "%d.%03d", tp->adc_pbat/1000, (tp->adc_pbat >= 0 ? 1 : -1) * (tp->adc_pbat%1000));
chsnprintf(buf, sizeof(buf), "%d.%03d", tp->pac_pbat/1000, (tp->pac_pbat >= 0 ? 1 : -1) * (tp->pac_pbat%1000));
str_replace(fskmsg, size, "<PBAT>", buf);
chsnprintf(buf, sizeof(buf), "%d.%03d", tp->adc_rbat/1000, (tp->adc_rbat >= 0 ? 1 : -1) * (tp->adc_rbat%1000));
str_replace(fskmsg, size, "<RBAT>", buf);
chsnprintf(buf, sizeof(buf), "%d", tp->air_press/10);
chsnprintf(buf, sizeof(buf), "%d", tp->sen_i1_press/10);
str_replace(fskmsg, size, "<PRESS>", buf);
chsnprintf(buf, sizeof(buf), "%d.%d", tp->air_temp/100, (tp->air_temp%100)/10);
chsnprintf(buf, sizeof(buf), "%d.%d", tp->sen_i1_temp/100, (tp->sen_i1_temp%100)/10);
str_replace(fskmsg, size, "<TEMP>", buf);
chsnprintf(buf, sizeof(buf), "%d", tp->air_hum/10);
chsnprintf(buf, sizeof(buf), "%d", tp->sen_i1_hum/10);
str_replace(fskmsg, size, "<HUM>", buf);
positionToMaidenhead(buf, tp->gps_lat/10000000.0, tp->gps_lon/10000000.0);
str_replace(fskmsg, size, "<LOC>", buf);

Wyświetl plik

@ -13,6 +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
chThdSleepMilliseconds(300); // Wait for tracking manager to initialize
}

Wyświetl plik

@ -8,14 +8,14 @@
#include "bme280.h"
#include "padc.h"
#include "pac1720.h"
#include "ov5640.h"
#include "radio.h"
#include "flash.h"
#include "watchdog.h"
#include "image.h"
#include "pi2c.h"
static trackPoint_t trackPoints[2];
static trackPoint_t* lastTrackPoint;
static systime_t nextLogEntryTimer;
static module_conf_t trac_conf = {.name = "TRAC"}; // Fake config needed for watchdog tracking
static bool threadStarted = false;
static bool tracking_useGPS = false;
@ -127,243 +127,256 @@ void waitForNewTrackPoint(void)
chThdSleepMilliseconds(1000);
}
static void aquirePosition(trackPoint_t* tp, trackPoint_t* ltp, systime_t timeout)
{
systime_t start = chVTGetSystemTimeX();
gpsFix_t gpsFix;
memset(&gpsFix, 0, sizeof(gpsFix_t));
// Switch on GPS if enough power is available and GPS is needed by any position thread
uint16_t batt = stm32_get_vbat();
if(!tracking_useGPS) { // No position thread running
tp->gps_lock = GPS_OFF;
} else if(batt < gps_on_vbat) {
tp->gps_lock = GPS_LOWBATT1;
} else {
// Switch on GPS
bool status = GPS_Init();
if(status) {
// Search for lock as long enough power is available
do {
batt = stm32_get_vbat();
gps_get_fix(&gpsFix);
} while(!isGPSLocked(&gpsFix) && batt >= gps_off_vbat && chVTGetSystemTimeX() <= start + timeout); // Do as long no GPS lock and within timeout, timeout=cycle-1sec (-3sec in order to keep synchronization)
if(batt < gps_off_vbat) { // GPS was switched on but prematurely switched off because the battery is low on power, switch off GPS
GPS_Deinit();
TRACE_WARN("TRAC > GPS sampling finished GPS LOW BATT");
tp->gps_lock = GPS_LOWBATT2;
} else if(!isGPSLocked(&gpsFix)) { // GPS was switched on but it failed to get a lock, keep GPS switched on
TRACE_WARN("TRAC > GPS sampling finished GPS LOSS");
tp->gps_lock = GPS_LOSS;
} else { // GPS locked successfully, switch off GPS (unless cycle is less than 60 seconds)
// Switch off GPS (if cycle time is more than 60 seconds)
if(track_cycle_time < S2ST(60)) {
TRACE_INFO("TRAC > Keep GPS switched on because cycle < 60sec");
tp->gps_lock = GPS_LOCKED2;
} else if(gps_onper_vbat != 0 && batt >= gps_onper_vbat) {
TRACE_INFO("TRAC > Keep GPS switched on because VBAT >= %dmV", gps_onper_vbat);
tp->gps_lock = GPS_LOCKED2;
} else {
TRACE_INFO("TRAC > Switch off GPS");
GPS_Deinit();
tp->gps_lock = GPS_LOCKED1;
}
// Debug
TRACE_INFO("TRAC > GPS sampling finished GPS LOCK");
// Calibrate RTC
setTime(&gpsFix.time);
// Take time from GPS
tp->gps_time = date2UnixTimestamp(&gpsFix.time);
// Set new GPS fix
tp->gps_lat = gpsFix.lat;
tp->gps_lon = gpsFix.lon;
tp->gps_alt = gpsFix.alt;
tp->gps_sats = gpsFix.num_svs;
tp->gps_pdop = (gpsFix.pdop+3)/5;
}
} else { // GPS communication error
GPS_Deinit();
tp->gps_lock = GPS_ERROR;
}
}
tp->gps_ttff = ST2S(chVTGetSystemTimeX() - start); // Time to first fix
if(tp->gps_lock != GPS_LOCKED1 && tp->gps_lock != GPS_LOCKED2) { // We have no valid GPS fix
// Take time from internal RTC
ptime_t time;
getTime(&time);
tp->gps_time = date2UnixTimestamp(&time);
// Take GPS fix from old lock
tp->gps_lat = ltp->gps_lat;
tp->gps_lon = ltp->gps_lon;
tp->gps_alt = ltp->gps_alt;
}
}
static void measureVoltage(trackPoint_t* tp)
{
tp->adc_vbat = stm32_get_vbat();
tp->adc_vsol = stm32_get_vsol();
pac1720_get_avg(&tp->pac_vbat, &tp->pac_vsol, &tp->pac_pbat, &tp->pac_psol);
}
static bool bme280_error;
static void getSensors(trackPoint_t* tp)
{
// Measure BME280
bme280_t handle;
if(BME280_isAvailable(BME280_ADDRESS_INT)) {
BME280_Init(&handle, BME280_ADDRESS_INT);
tp->sen_i1_press = BME280_getPressure(&handle, 256);
tp->sen_i1_hum = BME280_getHumidity(&handle);
tp->sen_i1_temp = BME280_getTemperature(&handle);
bme280_error = false;
} else { // No internal BME280 found
TRACE_ERROR("TRAC > Internal BME280 not available");
tp->sen_i1_press = 0;
tp->sen_i1_hum = 0;
tp->sen_i1_temp = 0;
bme280_error = true;
}
// Measure various temperature sensors
tp->stm32_temp = stm32_get_temp();
tp->si4464_temp = Si4464_getLastTemperature();
// Measure light intensity from OV5640
tp->light_intensity = OV5640_getLastLightIntensity() & 0xFFFF;
}
static void setSystemStatus(trackPoint_t* tp) {
// Set system errors
tp->sys_error = 0;
tp->sys_error |= (I2C_hasError() & 0x1) << 0;
tp->sys_error |= (tp->gps_lock == GPS_ERROR) << 2;
tp->sys_error |= (pac1720_hasError() & 0x3) << 3;
tp->sys_error |= (OV5640_hasError() & 0x7) << 5;
tp->sys_error |= (bme280_error & 0x1) << 8;
tp->sys_error |= 0x1 << 9; // No external BME280 available (PP9a doesnt have external sensors, but PP10a has)
tp->sys_error |= 0x1 << 10; // No external BME280 available (PP9a doesnt have external sensors, but PP10a has)
// Set system time
tp->sys_time = ST2S(chVTGetSystemTimeX());
}
/**
* Tracking Module (Thread)
*/
THD_FUNCTION(trackingThread, arg) {
(void)arg;
uint32_t id = 1;
uint32_t id = 0;
lastTrackPoint = &trackPoints[0];
// Fill initial values by PAC1720 and BME280 and RTC
// Time
ptime_t rtc;
getTime(&rtc);
lastTrackPoint->time.year = rtc.year;
lastTrackPoint->time.month = rtc.month;
lastTrackPoint->time.day = rtc.day;
lastTrackPoint->time.hour = rtc.hour;
lastTrackPoint->time.minute = rtc.minute;
lastTrackPoint->time.second = rtc.second;
// Read time from RTC
ptime_t time;
getTime(&time);
lastTrackPoint->gps_time = date2UnixTimestamp(&time);
// Get last tracking point from memory
TRACE_INFO("TRAC > Read last track point from flash memory");
trackPoint_t* lastLogPoint = getLastLog();
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;
} else {
TRACE_INFO("TRAC > No track point found in flash memory");
}
trackPoints[0].reset = lastLogPoint->reset+1;
trackPoints[1].reset = lastLogPoint->reset+1;
lastTrackPoint->gps_lat = lastLogPoint->gps_lat;
lastTrackPoint->gps_lon = lastLogPoint->gps_lon;
lastTrackPoint->gps_alt = lastLogPoint->gps_alt;
lastTrackPoint->gps_sats = lastLogPoint->gps_sats;
lastTrackPoint->gps_ttff = lastLogPoint->gps_ttff;
lastTrackPoint->gps_lock = GPS_LOG; // Tell other threads that it has been taken from log
lastTrackPoint->gps_sats = 0;
lastTrackPoint->gps_ttff = 0;
// Debug last stored GPS position
if(lastLogPoint != NULL) {
TRACE_INFO(
"TRAC > Last GPS position (from memory)\r\n"
"TRAC > Last track point (from memory)\r\n"
"%s Reset %d ID %d\r\n"
"%s Latitude: %d.%07ddeg\r\n"
"%s Longitude: %d.%07ddeg\r\n"
"%s Altitude: %d Meter",
TRACE_TAB, lastLogPoint->reset, lastLogPoint->id,
TRACE_TAB, lastTrackPoint->gps_lat/10000000, (lastTrackPoint->gps_lat > 0 ? 1:-1)*lastTrackPoint->gps_lat%10000000,
TRACE_TAB, lastTrackPoint->gps_lon/10000000, (lastTrackPoint->gps_lon > 0 ? 1:-1)*lastTrackPoint->gps_lon%10000000,
TRACE_TAB, lastTrackPoint->gps_alt
);
} else {
TRACE_INFO("TRAC > No GPS position in memory");
TRACE_INFO("TRAC > No track point found in flash memory");
}
// Voltage/Current
lastTrackPoint->adc_vsol = getSolarVoltageMV();
lastTrackPoint->adc_vbat = getBatteryVoltageMV();
lastTrackPoint->adc_vusb = getUSBVoltageMV();
lastTrackPoint->adc_pbat = pac1720_getPbat();
lastTrackPoint->gps_lock = GPS_LOG; // Mark trackPoint as LOG packet
bme280_t bme280;
// Initialize Si4464 to get Temperature readout
Si4464_Init();
Si4464_shutdown();
// Atmosphere condition
if(BME280_isAvailable(BME280_ADDRESS_INT)) {
BME280_Init(&bme280, BME280_ADDRESS_INT);
lastTrackPoint->air_press = BME280_getPressure(&bme280, 256);
lastTrackPoint->air_hum = BME280_getHumidity(&bme280);
lastTrackPoint->air_temp = BME280_getTemperature(&bme280);
} else { // No internal BME280 found
TRACE_ERROR("TRAC > No BME280 found");
lastTrackPoint->air_press = 0;
lastTrackPoint->air_hum = 0;
lastTrackPoint->air_temp = 0;
}
// Measure telemetry
measureVoltage(lastTrackPoint);
getSensors(lastTrackPoint);
setSystemStatus(lastTrackPoint);
/*
* Get last image ID. This is important because Habhub does mix up different
* images with the same it. So it is good to use a new image ID when the
* tracker has been reset.
*/
if(lastLogPoint != NULL)
gimage_id = lastLogPoint->id_image+1;
// Write Trackpoint to Flash memory
writeLogTrackPoint(lastTrackPoint);
systime_t time = chVTGetSystemTimeX();
// Wait for position threads to start
chThdSleepMilliseconds(100);
systime_t cycle_time = chVTGetSystemTimeX();
while(true)
{
TRACE_INFO("TRAC > Do module TRACKING MANAGER cycle");
trac_conf.wdg_timeout = chVTGetSystemTimeX() + S2ST(600); // TODO: Implement more sophisticated method
trackPoint_t* tp = &trackPoints[id % (sizeof(trackPoints) / sizeof(trackPoint_t))]; // Current track point
trackPoint_t* ltp = &trackPoints[(id-1) % (sizeof(trackPoints) / sizeof(trackPoint_t))]; // Last track point
trackPoint_t* tp = &trackPoints[(id+1) % 2]; // Current track point (the one which is processed now)
trackPoint_t* ltp = &trackPoints[ id % 2]; // Last track point
// Search for GPS satellites
gpsFix_t gpsFix = {{0,0,0,0,0,0,0},0,0,0,0,0};
// Get GPS position
aquirePosition(tp, ltp, track_cycle_time - S2ST(3));
// Switch on GPS is enough power is available and GPS is needed by any position thread
uint16_t batt = getBatteryVoltageMV();
if(!tracking_useGPS) { // No position thread running
tp->gps_lock = GPS_OFF;
} else if(batt < gps_on_vbat) {
tp->gps_lock = GPS_LOWBATT1;
} else {
tp->id = ++id; // Serial ID
// Switch on GPS
bool status = GPS_Init();
if(status) {
// Search for lock as long enough power is available
do {
batt = getBatteryVoltageMV();
gps_get_fix(&gpsFix);
} while(!isGPSLocked(&gpsFix) && batt >= gps_off_vbat && chVTGetSystemTimeX() <= time + track_cycle_time - S2ST(3)); // Do as long no GPS lock and within timeout, timeout=cycle-1sec (-3sec in order to keep synchronization)
if(batt < gps_off_vbat) { // GPS was switched on but prematurely switched off because the battery is low on power, switch off GPS
GPS_Deinit();
TRACE_WARN("TRAC > GPS sampling finished GPS LOW BATT");
tp->gps_lock = GPS_LOWBATT2;
} else if(!isGPSLocked(&gpsFix)) { // GPS was switched on but it failed to get a lock, keep GPS switched on
TRACE_WARN("TRAC > GPS sampling finished GPS LOSS");
tp->gps_lock = GPS_LOSS;
} else { // GPS locked successfully, switch off GPS (unless cycle is less than 60 seconds)
// Switch off GPS (if cycle time is more than 60 seconds)
if(track_cycle_time >= S2ST(60)) {
TRACE_INFO("TRAC > Switch off GPS");
GPS_Deinit();
} else {
TRACE_INFO("TRAC > Keep GPS switched of because cycle < 60sec");
}
// Debug
TRACE_INFO("TRAC > GPS sampling finished GPS LOCK");
// Calibrate RTC
setTime(gpsFix.time);
// Take time from GPS
tp->time.year = gpsFix.time.year;
tp->time.month = gpsFix.time.month;
tp->time.day = gpsFix.time.day;
tp->time.hour = gpsFix.time.hour;
tp->time.minute = gpsFix.time.minute;
tp->time.second = gpsFix.time.second;
// Set new GPS fix
tp->gps_lat = gpsFix.lat;
tp->gps_lon = gpsFix.lon;
tp->gps_alt = gpsFix.alt;
tp->gps_lock = GPS_LOCKED;
tp->gps_sats = gpsFix.num_svs;
}
} else { // GPS communication error
GPS_Deinit();
tp->gps_lock = GPS_ERROR;
}
}
if(tp->gps_lock != GPS_LOCKED) { // We have no valid GPS fix
// Take time from internal RTC
getTime(&rtc);
tp->time.year = rtc.year;
tp->time.month = rtc.month;
tp->time.day = rtc.day;
tp->time.hour = rtc.hour;
tp->time.minute = rtc.minute;
tp->time.second = rtc.second;
// Take GPS fix from old lock
tp->gps_lat = ltp->gps_lat;
tp->gps_lon = ltp->gps_lon;
tp->gps_alt = ltp->gps_alt;
}
tp->id = id; // Serial ID
tp->gps_ttff = ST2S(chVTGetSystemTimeX() - time); // Time to first fix
// Power management
tp->adc_vsol = getSolarVoltageMV();
tp->adc_vbat = getBatteryVoltageMV();
tp->adc_vusb = getUSBVoltageMV();
tp->adc_pbat = pac1720_getAvgPbat();
tp->adc_rbat = pac1720_getAvgRbat();
bme280_t bme280;
// Atmosphere condition
if(BME280_isAvailable(BME280_ADDRESS_INT)) {
BME280_Init(&bme280, BME280_ADDRESS_INT);
tp->air_press = BME280_getPressure(&bme280, 256);
tp->air_hum = BME280_getHumidity(&bme280);
tp->air_temp = BME280_getTemperature(&bme280);
} else { // No internal BME280 found
TRACE_ERROR("TRAC > Internal BME280 not available");
tp->air_press = 0;
tp->air_hum = 0;
tp->air_temp = 0;
}
// Set last time ID
tp->id_image = gimage_id;
// Measure telemetry
measureVoltage(tp);
getSensors(tp);
setSystemStatus(tp);
// Trace data
unixTimestamp2Date(&time, tp->gps_time);
TRACE_INFO( "TRAC > New tracking point available (ID=%d)\r\n"
"%s Time %04d-%02d-%02d %02d:%02d:%02d\r\n"
"%s Pos %d.%05d %d.%05d Alt %dm\r\n"
"%s Sats %d TTFF %dsec\r\n"
"%s ADC Vbat=%d.%03dV Vsol=%d.%03dV VUSB=%d.%03dV Pbat=%dmW Rbat=%dmOhm\r\n"
"%s AIR p=%6d.%01dPa T=%2d.%02ddegC phi=%2d.%01d%% ImageID=%d",
"%s Sats %d TTFF %dsec\r\n"
"%s ADC Vbat=%d.%03dV Vsol=%d.%03dV Pbat=%dmW\r\n"
"%s AIR p=%6d.%01dPa T=%2d.%02ddegC phi=%2d.%01d%%",
tp->id,
TRACE_TAB, tp->time.year, tp->time.month, tp->time.day, tp->time.hour, tp->time.minute, tp->time.day,
TRACE_TAB, time.year, time.month, time.day, time.hour, time.minute, time.day,
TRACE_TAB, 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,
TRACE_TAB, tp->gps_sats, tp->gps_ttff,
TRACE_TAB, 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,
TRACE_TAB, tp->air_press/10, tp->air_press%10, tp->air_temp/100, tp->air_temp%100, tp->air_hum/10, tp->air_hum%10, tp->id_image
TRACE_TAB, tp->adc_vbat/1000, (tp->adc_vbat%1000), tp->adc_vsol/1000, (tp->adc_vsol%1000), tp->pac_pbat,
TRACE_TAB, tp->sen_i1_press/10, tp->sen_i1_press%10, tp->sen_i1_temp/100, tp->sen_i1_temp%100, tp->sen_i1_hum/10, tp->sen_i1_hum%10
);
// Append logging (timeout)
if(nextLogEntryTimer <= chVTGetSystemTimeX())
{
writeLogTrackPoint(tp);
nextLogEntryTimer += log_cycle_time;
}
// Write Trackpoint to Flash memory
writeLogTrackPoint(tp);
// Switch last recent track point
// Switch last track point
lastTrackPoint = tp;
id++;
time = chThdSleepUntilWindowed(time, time + track_cycle_time); // Wait until time + cycletime
// Wait until cycle
cycle_time = chThdSleepUntilWindowed(cycle_time, cycle_time + track_cycle_time);
}
}
@ -377,7 +390,7 @@ void init_tracking_manager(bool useGPS)
threadStarted = true;
TRACE_INFO("TRAC > Startup tracking thread");
thread_t *th = chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(2*1024), "TRA", NORMALPRIO, trackingThread, NULL);
thread_t *th = chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(2*1024), "TRA", NORMALPRIO+1, trackingThread, NULL);
if(!th) {
// Print startup error, do not start watchdog for this thread
TRACE_ERROR("TRAC > Could not startup thread (not enough memory available)");

Wyświetl plik

@ -6,7 +6,8 @@
#include "ptime.h"
typedef enum {
GPS_LOCKED, // The GPS is locked and could aquire a fix
GPS_LOCKED1, // The GPS is locked, the GPS has been switched off
GPS_LOCKED2, // The GPS is locked, the GPS has been kept switched on
GPS_LOSS, // The GPS was switched on all time but it couln't aquire a fix
GPS_LOWBATT1, // The GPS wasnt switched on because the battery has not enough energy
GPS_LOWBATT2, // The GPS was switched on but has been switched off prematurely while the battery has not enough energy (or is too cold)
@ -16,30 +17,57 @@ typedef enum {
} gpsLock_t;
typedef struct {
uint32_t id; // Serial ID
ptime_t time; // GPS time
// GPS
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
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
// Voltage and current measurement
uint16_t adc_vsol; // Current solar voltage in mV
uint16_t adc_vbat; // Current battery voltage in mV
uint16_t adc_vusb; // Current USB voltage in mV
int16_t adc_pbat; // Average battery current (since last track point)
int16_t adc_rbat; // Battery impedance
uint16_t pac_vsol;
uint16_t pac_vbat;
int16_t pac_pbat;
int16_t pac_psol;
uint16_t light_intensity;
// GPS
gpsLock_t gps_lock; // 0: locked, 1: GPS loss, 2: low power (switched off), 3: taken from log, 4: GPS switch off permanently
uint8_t gps_sats; // Satellites used for solution
uint8_t gps_ttff; // Time to first fix in seconds
uint8_t gps_pdop; // Position DOP in 0.05 per unit (unitless)
uint16_t gps_alt; // Altitude in meter
int32_t gps_lat; // Latitude in 10^(-7)° per unit
int32_t gps_lon; // Longitude in 10^(-7)° per unit
// BME280 (on board)
uint32_t air_press; // Airpressure in Pa*10 (in 0.1Pa)
uint16_t air_hum; // Rel. humidity in %*10 (in 0.1%)
int16_t air_temp; // Temperature in degC*100 (in 0.01°C)
uint32_t sen_i1_press; // Airpressure in Pa*10 (in 0.1Pa)
uint32_t sen_e1_press; // Airpressure in Pa*10 (in 0.1Pa)
uint32_t sen_e2_press; // Airpressure in Pa*10 (in 0.1Pa)
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)
int16_t sen_i1_temp; // Temperature in 0.01°C per unit
int16_t sen_e1_temp; // Temperature in 0.01°C per unit
int16_t sen_e2_temp; // Temperature in 0.01°C per unit
uint8_t sen_i1_hum; // Rel. humidity in %
uint8_t sen_e1_hum; // Rel. humidity in %
uint8_t sen_e2_hum; // Rel. humidity in %
uint8_t dummy2;
int16_t stm32_temp;
int16_t si4464_temp;
uint16_t reset;
uint32_t id; // Serial ID
uint32_t gps_time; // GPS time
uint32_t sys_time; // System time (in seconds)
uint32_t sys_error; // System error flags
// Bit 0: I2C_I EVA7M
// Bit 1: I2C_I PAC1720
// Bit 2: I2C_I OV5640
// Bit 3: I2C_I BME280_I1
// Bit 4: I2C_E BME280_E1
// Bit 5: I2C_E BME280_E2
// Bit 6: UART EVA7M
// Bit 7: <reserved>
} trackPoint_t;
void waitForNewTrackPoint(void);

Wyświetl plik

@ -28,7 +28,7 @@ THD_FUNCTION(wdgThread, arg) {
uint8_t counter = 0;
while(true)
{
chThdSleepMilliseconds(1000);
chThdSleepMilliseconds(500);
bool healthy = true;
for(uint8_t i=0; i<threads_cnt; i++) {

Wyświetl plik

@ -32,7 +32,6 @@ typedef enum {
TEL_VBAT,
TEL_VSOL,
TEL_PBAT,
TEL_RBAT,
TEL_PRESS,
TEL_TEMP,
TEL_HUM
@ -53,9 +52,9 @@ typedef struct {
typedef enum {
SLEEP_DISABLED,
SLEEP_WHEN_VBAT_BELOW_THRES,
SLEEP_WHEN_RBAT_BELOW_THRES,
SLEEP_WHEN_VSOL_BELOW_THRES,
SLEEP_WHEN_VBAT_ABOVE_THRES,
SLEEP_WHEN_RBAT_ABOVE_THRES,
SLEEP_WHEN_VSOL_ABOVE_THRES,
SLEEP_WHEN_DISCHARGING,
SLEEP_WHEN_CHARGING
} sleep_type_t;
@ -63,7 +62,7 @@ typedef enum {
typedef struct {
sleep_type_t type;
uint16_t vbat_thres;
uint16_t rbat_thres;
uint16_t vsol_thres;
} sleep_conf_t;
typedef struct {