kopia lustrzana https://github.com/DL7AD/pecanpico9
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 managermaster
rodzic
1a925b5ecb
commit
c745ef50c9
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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'];
|
||||
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
|
@ -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;
|
||||
}*/
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -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()
|
||||
));
|
||||
?>
|
||||
|
|
@ -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);
|
||||
?>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
$db->close();
|
||||
?>
|
|
@ -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>
|
||||
|
|
@ -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";
|
||||
}
|
||||
?>
|
|
@ -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>
|
||||
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";
|
||||
?>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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";
|
||||
?>
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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: <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">
|
||||
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";
|
||||
?>
|
||||
|
219
decoder/image.py
219
decoder/image.py
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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.
|
@ -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]))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
BIN
decoder/ssdv
BIN
decoder/ssdv
Plik binarny nie jest wyświetlany.
|
@ -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]);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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[]);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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, ×pec);
|
||||
|
||||
|
@ -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, ×pec);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Ładowanie…
Reference in New Issue