pecanpico10/tracker/software/source/protocols/packet/aprs.c

1383 wiersze
57 KiB
C

/* trackuino copyright (C) 2010 EA5HAV Javi
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "aprs.h"
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "debug.h"
#include "base91.h"
#include "digipeater.h"
#include "dedupe.h"
#include "radio.h"
#include "flash.h"
#include "image.h"
#define METER_TO_FEET(m) (((m)*26876) / 8192)
typedef struct {
sysinterval_t time;
char call[AX25_MAX_ADDR_LEN];
} heard_t;
static uint16_t msg_id;
char alias_re[] = "WIDE[4-7]-[1-7]|CITYD";
char wide_re[] = "WIDE[1-7]-[1-7]";
enum preempt_e preempt = PREEMPT_OFF;
static heard_t heard_list[APRS_HEARD_LIST_SIZE];
static bool dedupe_initialized;
const conf_command_t command_list[] = {
{TYPE_INT, "pos_pri.active", sizeof(conf_sram.pos_pri.beacon.active), &conf_sram.pos_pri.beacon.active },
{TYPE_TIME, "pos_pri.init_delay", sizeof(conf_sram.pos_pri.beacon.init_delay), &conf_sram.pos_pri.beacon.init_delay },
{TYPE_INT, "pos_pri.sleep_conf.type", sizeof(conf_sram.pos_pri.beacon.sleep_conf.type), &conf_sram.pos_pri.beacon.sleep_conf.type },
{TYPE_INT, "pos_pri.sleep_conf.vbat_thres", sizeof(conf_sram.pos_pri.beacon.sleep_conf.vbat_thres), &conf_sram.pos_pri.beacon.sleep_conf.vbat_thres },
{TYPE_INT, "pos_pri.sleep_conf.vsol_thres", sizeof(conf_sram.pos_pri.beacon.sleep_conf.vsol_thres), &conf_sram.pos_pri.beacon.sleep_conf.vsol_thres },
{TYPE_TIME, "pos_pri.cycle", sizeof(conf_sram.pos_pri.beacon.cycle), &conf_sram.pos_pri.beacon.cycle },
{TYPE_INT, "pos_pri.pwr", sizeof(conf_sram.pos_pri.radio_conf.pwr), &conf_sram.pos_pri.radio_conf.pwr },
{TYPE_INT, "pos_pri.freq", sizeof(conf_sram.pos_pri.radio_conf.freq), &conf_sram.pos_pri.radio_conf.freq },
{TYPE_INT, "pos_pri.mod", sizeof(conf_sram.pos_pri.radio_conf.mod), &conf_sram.pos_pri.radio_conf.mod },
{TYPE_INT, "pos_pri.cca", sizeof(conf_sram.pos_pri.radio_conf.cca), &conf_sram.pos_pri.radio_conf.cca },
{TYPE_STR, "pos_pri.call", sizeof(conf_sram.pos_pri.call), &conf_sram.pos_pri.call },
{TYPE_STR, "pos_pri.path", sizeof(conf_sram.pos_pri.path), &conf_sram.pos_pri.path },
{TYPE_INT, "pos_pri.symbol", sizeof(conf_sram.pos_pri.symbol), &conf_sram.pos_pri.symbol },
{TYPE_INT, "pos_pri.aprs_msg", sizeof(conf_sram.pos_pri.aprs_msg), &conf_sram.pos_pri.aprs_msg },
{TYPE_INT, "pos_sec.active", sizeof(conf_sram.pos_sec.beacon.active), &conf_sram.pos_sec.beacon.active },
{TYPE_TIME, "pos_sec.init_delay", sizeof(conf_sram.pos_sec.beacon.init_delay), &conf_sram.pos_sec.beacon.init_delay },
{TYPE_INT, "pos_sec.sleep_conf.type", sizeof(conf_sram.pos_sec.beacon.sleep_conf.type), &conf_sram.pos_sec.beacon.sleep_conf.type },
{TYPE_INT, "pos_sec.sleep_conf.vbat_thres", sizeof(conf_sram.pos_sec.beacon.sleep_conf.vbat_thres), &conf_sram.pos_sec.beacon.sleep_conf.vbat_thres },
{TYPE_INT, "pos_sec.sleep_conf.vsol_thres", sizeof(conf_sram.pos_sec.beacon.sleep_conf.vsol_thres), &conf_sram.pos_sec.beacon.sleep_conf.vsol_thres },
{TYPE_TIME, "pos_sec.cycle", sizeof(conf_sram.pos_sec.beacon.cycle), &conf_sram.pos_sec.beacon.cycle },
{TYPE_INT, "pos_sec.pwr", sizeof(conf_sram.pos_sec.radio_conf.pwr), &conf_sram.pos_sec.radio_conf.pwr },
{TYPE_INT, "pos_sec.freq", sizeof(conf_sram.pos_sec.radio_conf.freq), &conf_sram.pos_sec.radio_conf.freq },
{TYPE_INT, "pos_sec.mod", sizeof(conf_sram.pos_sec.radio_conf.mod), &conf_sram.pos_sec.radio_conf.mod },
{TYPE_INT, "pos_sec.cca", sizeof(conf_sram.pos_sec.radio_conf.cca), &conf_sram.pos_sec.radio_conf.cca },
{TYPE_STR, "pos_sec.call", sizeof(conf_sram.pos_sec.call), &conf_sram.pos_sec.call },
{TYPE_STR, "pos_sec.path", sizeof(conf_sram.pos_sec.path), &conf_sram.pos_sec.path },
{TYPE_INT, "pos_sec.symbol", sizeof(conf_sram.pos_sec.symbol), &conf_sram.pos_sec.symbol },
{TYPE_INT, "pos_sec.aprs_msg", sizeof(conf_sram.pos_sec.aprs_msg), &conf_sram.pos_sec.aprs_msg },
{TYPE_INT, "img_pri.active", sizeof(conf_sram.img_pri.svc_conf.active), &conf_sram.img_pri.svc_conf.active },
{TYPE_TIME, "img_pri.init_delay", sizeof(conf_sram.img_pri.svc_conf.init_delay), &conf_sram.img_pri.svc_conf.init_delay },
{TYPE_TIME, "img_pri.send_spacing", sizeof(conf_sram.img_pri.svc_conf.send_spacing), &conf_sram.img_pri.svc_conf.send_spacing },
{TYPE_INT, "img_pri.sleep_conf.type", sizeof(conf_sram.img_pri.svc_conf.sleep_conf.type), &conf_sram.img_pri.svc_conf.sleep_conf.type },
{TYPE_INT, "img_pri.sleep_conf.vbat_thres", sizeof(conf_sram.img_pri.svc_conf.sleep_conf.vbat_thres), &conf_sram.img_pri.svc_conf.sleep_conf.vbat_thres },
{TYPE_INT, "img_pri.sleep_conf.vsol_thres", sizeof(conf_sram.img_pri.svc_conf.sleep_conf.vsol_thres), &conf_sram.img_pri.svc_conf.sleep_conf.vsol_thres },
{TYPE_TIME, "img_pri.cycle", sizeof(conf_sram.img_pri.svc_conf.cycle), &conf_sram.img_pri.svc_conf.cycle },
{TYPE_INT, "img_pri.pwr", sizeof(conf_sram.img_pri.radio_conf.pwr), &conf_sram.img_pri.radio_conf.pwr },
{TYPE_INT, "img_pri.freq", sizeof(conf_sram.img_pri.radio_conf.freq), &conf_sram.img_pri.radio_conf.freq },
{TYPE_INT, "img_pri.mod", sizeof(conf_sram.img_pri.radio_conf.mod), &conf_sram.img_pri.radio_conf.mod },
{TYPE_INT, "img_pri.cca", sizeof(conf_sram.img_pri.radio_conf.cca), &conf_sram.img_pri.radio_conf.cca },
{TYPE_INT, "img_pri.speed", sizeof(conf_sram.img_pri.radio_conf.speed), &conf_sram.img_pri.radio_conf.speed },
{TYPE_INT, "img_pri.redundantTx", sizeof(conf_sram.img_pri.redundantTx), &conf_sram.img_pri.redundantTx },
{TYPE_STR, "img_pri.call", sizeof(conf_sram.img_pri.call), &conf_sram.img_pri.call },
{TYPE_STR, "img_pri.path", sizeof(conf_sram.img_pri.path), &conf_sram.img_pri.path },
{TYPE_INT, "img_pri.res", sizeof(conf_sram.img_pri.res), &conf_sram.img_pri.res },
{TYPE_INT, "img_pri.quality", sizeof(conf_sram.img_pri.quality), &conf_sram.img_pri.quality },
{TYPE_INT, "img_pri.buf_size", sizeof(conf_sram.img_pri.buf_size), &conf_sram.img_pri.buf_size },
{TYPE_INT, "img_sec.active", sizeof(conf_sram.img_sec.svc_conf.active), &conf_sram.img_sec.svc_conf.active },
{TYPE_TIME, "img_sec.init_delay", sizeof(conf_sram.img_sec.svc_conf.init_delay), &conf_sram.img_sec.svc_conf.init_delay },
{TYPE_TIME, "img_sec.send_spacing", sizeof(conf_sram.img_sec.svc_conf.send_spacing), &conf_sram.img_sec.svc_conf.send_spacing },
{TYPE_INT, "img_sec.sleep_conf.type", sizeof(conf_sram.img_sec.svc_conf.sleep_conf.type), &conf_sram.img_sec.svc_conf.sleep_conf.type },
{TYPE_INT, "img_sec.sleep_conf.vbat_thres", sizeof(conf_sram.img_sec.svc_conf.sleep_conf.vbat_thres), &conf_sram.img_sec.svc_conf.sleep_conf.vbat_thres },
{TYPE_INT, "img_sec.sleep_conf.vsol_thres", sizeof(conf_sram.img_sec.svc_conf.sleep_conf.vsol_thres), &conf_sram.img_sec.svc_conf.sleep_conf.vsol_thres },
{TYPE_TIME, "img_sec.cycle", sizeof(conf_sram.img_sec.svc_conf.cycle), &conf_sram.img_sec.svc_conf.cycle },
{TYPE_INT, "img_sec.pwr", sizeof(conf_sram.img_sec.radio_conf.pwr), &conf_sram.img_sec.radio_conf.pwr },
{TYPE_INT, "img_sec.freq", sizeof(conf_sram.img_sec.radio_conf.freq), &conf_sram.img_sec.radio_conf.freq },
{TYPE_INT, "img_sec.mod", sizeof(conf_sram.img_sec.radio_conf.mod), &conf_sram.img_sec.radio_conf.mod },
{TYPE_INT, "img_sec.cca", sizeof(conf_sram.img_sec.radio_conf.cca), &conf_sram.img_sec.radio_conf.cca },
{TYPE_INT, "img_sec.speed", sizeof(conf_sram.img_sec.radio_conf.speed), &conf_sram.img_sec.radio_conf.speed },
{TYPE_INT, "img_sec.redundantTx", sizeof(conf_sram.img_sec.redundantTx), &conf_sram.img_sec.redundantTx },
{TYPE_STR, "img_sec.call", sizeof(conf_sram.img_sec.call), &conf_sram.img_sec.call },
{TYPE_STR, "img_sec.path", sizeof(conf_sram.img_sec.path), &conf_sram.img_sec.path },
{TYPE_INT, "img_sec.res", sizeof(conf_sram.img_sec.res), &conf_sram.img_sec.res },
{TYPE_INT, "img_sec.quality", sizeof(conf_sram.img_sec.quality), &conf_sram.img_sec.quality },
{TYPE_INT, "img_sec.buf_size", sizeof(conf_sram.img_sec.buf_size), &conf_sram.img_sec.buf_size },
{TYPE_INT, "log.active", sizeof(conf_sram.log.svc_conf.active), &conf_sram.log.svc_conf.active },
{TYPE_TIME, "log.init_delay", sizeof(conf_sram.log.svc_conf.init_delay), &conf_sram.log.svc_conf.init_delay },
{TYPE_TIME, "log.send_spacing", sizeof(conf_sram.log.svc_conf.send_spacing), &conf_sram.log.svc_conf.send_spacing },
{TYPE_INT, "log.sleep_conf.type", sizeof(conf_sram.log.svc_conf.sleep_conf.type), &conf_sram.log.svc_conf.sleep_conf.type },
{TYPE_INT, "log.sleep_conf.vbat_thres", sizeof(conf_sram.log.svc_conf.sleep_conf.vbat_thres), &conf_sram.log.svc_conf.sleep_conf.vbat_thres },
{TYPE_INT, "log.sleep_conf.vsol_thres", sizeof(conf_sram.log.svc_conf.sleep_conf.vsol_thres), &conf_sram.log.svc_conf.sleep_conf.vsol_thres },
{TYPE_TIME, "log.cycle", sizeof(conf_sram.log.svc_conf.cycle), &conf_sram.log.svc_conf.cycle },
{TYPE_INT, "log.pwr", sizeof(conf_sram.log.radio_conf.pwr), &conf_sram.log.radio_conf.pwr },
{TYPE_INT, "log.freq", sizeof(conf_sram.log.radio_conf.freq), &conf_sram.log.radio_conf.freq },
{TYPE_INT, "log.mod", sizeof(conf_sram.log.radio_conf.mod), &conf_sram.log.radio_conf.mod },
{TYPE_INT, "log.cca", sizeof(conf_sram.log.radio_conf.cca), &conf_sram.log.radio_conf.cca },
{TYPE_INT, "log.speed", sizeof(conf_sram.log.radio_conf.speed), &conf_sram.log.radio_conf.speed },
{TYPE_STR, "log.call", sizeof(conf_sram.log.call), &conf_sram.log.call },
{TYPE_STR, "log.path", sizeof(conf_sram.log.path), &conf_sram.log.path },
{TYPE_INT, "log.density", sizeof(conf_sram.log.density), &conf_sram.log.density },
{TYPE_INT, "aprs.rx.active", sizeof(conf_sram.aprs.rx.svc_conf.active), &conf_sram.aprs.rx.svc_conf.active },
{TYPE_TIME, "aprs.rx.init_delay", sizeof(conf_sram.aprs.rx.svc_conf.init_delay), &conf_sram.aprs.rx.svc_conf.init_delay },
{TYPE_INT, "aprs.rx.freq", sizeof(conf_sram.aprs.rx.radio_conf.freq), &conf_sram.aprs.rx.radio_conf.freq },
{TYPE_INT, "aprs.rx.mod", sizeof(conf_sram.aprs.rx.radio_conf.mod), &conf_sram.aprs.rx.radio_conf.mod },
{TYPE_INT, "aprs.rx.speed", sizeof(conf_sram.aprs.rx.radio_conf.speed), &conf_sram.aprs.rx.radio_conf.speed },
{TYPE_STR, "aprs.rx.call", sizeof(conf_sram.aprs.rx.call), &conf_sram.aprs.rx.call },
{TYPE_INT, "aprs.digi", sizeof(conf_sram.aprs.digi), &conf_sram.aprs.digi },
{TYPE_INT, "aprs.tx.freq", sizeof(conf_sram.aprs.tx.radio_conf.freq), &conf_sram.aprs.tx.radio_conf.freq },
{TYPE_INT, "aprs.tx.pwr", sizeof(conf_sram.aprs.tx.radio_conf.pwr), &conf_sram.aprs.tx.radio_conf.pwr },
{TYPE_INT, "aprs.tx.mod", sizeof(conf_sram.aprs.tx.radio_conf.mod), &conf_sram.aprs.tx.radio_conf.mod },
{TYPE_INT, "aprs.tx.cca", sizeof(conf_sram.aprs.tx.radio_conf.cca), &conf_sram.aprs.tx.radio_conf.cca },
{TYPE_STR, "aprs.tx.call", sizeof(conf_sram.aprs.tx.call), &conf_sram.aprs.tx.call },
{TYPE_STR, "aprs.tx.path", sizeof(conf_sram.aprs.tx.path), &conf_sram.aprs.tx.path },
{TYPE_INT, "aprs.tx.symbol", sizeof(conf_sram.aprs.tx.symbol), &conf_sram.aprs.tx.symbol },
{TYPE_INT, "aprs.beacon", sizeof(conf_sram.aprs.tx.beacon.active), &conf_sram.aprs.tx.beacon.active },
{TYPE_INT, "aprs.beacon.lat", sizeof(conf_sram.aprs.tx.beacon.lat), &conf_sram.aprs.tx.beacon.lat },
{TYPE_INT, "aprs.beacon.lon", sizeof(conf_sram.aprs.tx.beacon.lon), &conf_sram.aprs.tx.beacon.lon },
{TYPE_INT, "aprs.beacon.alt", sizeof(conf_sram.aprs.tx.beacon.alt), &conf_sram.aprs.tx.beacon.alt },
{TYPE_INT, "aprs.beacon.cycle", sizeof(conf_sram.aprs.tx.beacon.cycle), &conf_sram.aprs.tx.beacon.cycle },
{TYPE_INT, "keep_cam_switched_on", sizeof(conf_sram.keep_cam_switched_on), &conf_sram.keep_cam_switched_on },
{TYPE_INT, "gps_on_vbat", sizeof(conf_sram.gps_on_vbat), &conf_sram.gps_on_vbat },
{TYPE_INT, "gps_off_vbat", sizeof(conf_sram.gps_off_vbat), &conf_sram.gps_off_vbat },
{TYPE_INT, "gps_onper_vbat", sizeof(conf_sram.gps_onper_vbat), &conf_sram.gps_onper_vbat },
{TYPE_INT, "gps_pressure", sizeof(conf_sram.gps_pressure), &conf_sram.gps_pressure },
{TYPE_INT, "gps_low_alt", sizeof(conf_sram.gps_low_alt), &conf_sram.gps_low_alt },
{TYPE_INT, "gps_high_alt", sizeof(conf_sram.gps_high_alt), &conf_sram.gps_high_alt },
{TYPE_INT, "default.freq", sizeof(conf_sram.freq), &conf_sram.freq },
{TYPE_INT, "base.freq", sizeof(conf_sram.base.radio_conf.freq), &conf_sram.base.radio_conf.freq },
{TYPE_INT, "base.pwr", sizeof(conf_sram.base.radio_conf.pwr), &conf_sram.base.radio_conf.pwr },
{TYPE_INT, "base.mod", sizeof(conf_sram.base.radio_conf.mod), &conf_sram.base.radio_conf.mod },
{TYPE_INT, "base.cca", sizeof(conf_sram.base.radio_conf.cca), &conf_sram.base.radio_conf.cca },
{TYPE_STR, "base.call", sizeof(conf_sram.base.call), &conf_sram.base.call },
{TYPE_TIME, "tel_enc_cycle", sizeof(conf_sram.tel_enc_cycle), &conf_sram.tel_enc_cycle },
{TYPE_NULL}
};
/*
* Table of commands that can be embedded in a message.
*/
const APRSCommand aprs_commands[] = {
{"?aprsd", aprs_send_aprsd_message},
{"?aprsh", aprs_send_aprsh_message},
{"?aprsp", aprs_send_position_response},
{"?gpio", aprs_execute_gpio_command},
{"?reset", aprs_execute_system_reset},
{"?save", aprs_execute_config_save},
{"?img", aprs_execute_img_command},
{"?config", aprs_execute_config_command},
{NULL, NULL}
};
/**
* @brief parse arguments from a command string.
*
* @return pointer to next element in string.
* @retval NULL if end.
*/
static char *aprs_parse_arguments(char *str, char **saveptr) {
char *p;
if (str != NULL)
*saveptr = str;
p = *saveptr;
if (!p) {
return NULL;
}
/* Skipping white space.*/
p += strspn(p, " \t");
if (*p == '"') {
/* If an argument starts with a double quote then its delimiter is another
quote.*/
p++;
*saveptr = strpbrk(p, "\"");
}
else {
/* The delimiter is white space.*/
*saveptr = strpbrk(p, " \t");
}
/* Replacing the delimiter with a zero.*/
if (*saveptr != NULL) {
*(*saveptr)++ = '\0';
}
return *p != '\0' ? p : NULL;
}
/**
* @brief Execute a command in an APRS message.
* @notes Known commands are in APRS command table.
* @notes Commands themselves return only MSG_OK or MSG_ERROR.
*
* @return result of command.
* @retval MSG_OK if the command completed.
* @retval MSG_ERROR if there was an error in command execution.
* @retval MSG_TIMEOUT if the command was not found in known commands.
*/
static msg_t aprs_cmd_exec(const APRSCommand *acp,
char *name,
aprs_identity_t *id,
int argc,
char *argv[]) {
while (acp->ac_name != NULL) {
if (strcmp(acp->ac_name, name) == 0) {
return acp->ac_function(id, argc, argv);
}
acp++;
}
return MSG_TIMEOUT;
}
/**
*
*/
void aprs_get_identity(void) {
}
/**
*
*/
void aprs_debug_getPacket(packet_t pp, char* buf, uint32_t len)
{
// Decode packet
char rec[127];
unsigned char *pinfo;
ax25_format_addrs(pp, rec, sizeof(rec));
if(ax25_get_info(pp, &pinfo) == 0)
return;
// Print decoded packet
uint32_t out = chsnprintf(buf, len, "%s", rec);
for(uint32_t i = 0; pinfo[i]; i++) {
if(pinfo[i] < 32 || pinfo[i] > 126) {
out += chsnprintf(&buf[out], len - out, "<0x%02x>", pinfo[i]);
} else {
out += chsnprintf(&buf[out], len - out, "%c", pinfo[i]);
}
}
}
/**
* @brief Transmit APRS position packet.
*
* @param[in] callsign origination call sign
* @param[in] path path to use
* @param[in] symbol symbol for originator
* @param[in] dataPoint position data object
*
* @return encoded packet object pointer
* @retval NULL if encoding failed
*/
packet_t aprs_encode_stamped_position_and_telemetry(const char *callsign,
const char *path, aprs_sym_t symbol,
dataPoint_t *dataPoint) {
// Latitude
uint32_t y = 380926 * (90 - dataPoint->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 + dataPoint->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(dataPoint->gps_alt)) / logf(1.002f);
uint32_t a1 = a / 91;
uint32_t a1r = a % 91;
uint8_t gpsFix = dataPoint->gps_state == GPS_LOCKED1
|| dataPoint->gps_state == GPS_LOCKED2
|| dataPoint->gps_state == GPS_FIXED ? GSP_FIX_CURRENT : GSP_FIX_OLD;
uint8_t src = NMEA_SRC_GGA;
uint8_t origin = ORIGIN_PICO;
ptime_t time;
getTime(&time);
if(time.year == RTC_BASE_YEAR)
/* RTC is not set so use dataPoint (it may have a valid date). */
unixTimestamp2Date(&time, dataPoint->gps_time);
char xmit[256];
uint32_t len = chsnprintf(xmit, sizeof(xmit), "%s>%s,%s:@%02d%02d%02dz",
callsign,
APRS_DEVICE_CALLSIGN,
path,
time.day,
time.hour,
time.minute);
xmit[len+0] = (symbol >> 8) & 0xFF;
xmit[len+1] = y3+33;
xmit[len+2] = y2+33;
xmit[len+3] = y1+33;
xmit[len+4] = y1r+33;
xmit[len+5] = x3+33;
xmit[len+6] = x2+33;
xmit[len+7] = x1+33;
xmit[len+8] = x1r+33;
xmit[len+9] = symbol & 0xFF;
xmit[len+10] = a1+33;
xmit[len+11] = a1r+33;
xmit[len+12] = ((gpsFix << 5) | (src << 3) | origin) + 33;
// Comments
uint32_t len2 = base91_encode((uint8_t*)dataPoint,
(uint8_t*)&xmit[len+13],
sizeof(dataPoint_t));
xmit[len+len2+13] = '|';
/* APRS base91 encoded telemetry. */
// Sequence ID
uint32_t t = dataPoint->id & 0x1FFF;
xmit[len+len2+14] = t/91 + 33;
xmit[len+len2+15] = t%91 + 33;
// Telemetry analog parameters
for(uint8_t i=0; i<5; i++) {
switch(i) {
case 0: t = dataPoint->adc_vbat; break;
case 1: t = dataPoint->adc_vsol; break;
case 2: t = dataPoint->pac_pbat+4096; break;
case 3: t = dataPoint->sen_i1_temp/10 + 1000; break;
case 4: t = dataPoint->sen_i1_press/125 - 40; break;
}
xmit[len+len2+16+i*2] = t/91 + 33;
xmit[len+len2+16+i*2+1] = t%91 + 33;
}
// Telemetry digital parameter
xmit[len+len2+26] = dataPoint->gpio + 33;
/* Digital bits second byte - set zero. */
xmit[len+len2+27] = 33;
xmit[len+len2+28] = '|';
xmit[len+len2+29] = 0;
return ax25_from_text(xmit, true);
}
/**
* @brief Transmit APRS position and telemetry packet.
* @notes Base 91 telemetry encoding is used.
* @notes The comments are filled with:
* @notes - Battery voltage in mV
* @notes - Solar voltage in mW (if tracker is solar-enabled)
* @notes - Temperature in Celcius
* @notes - Air pressure in Pascal
* @notes - Number of satellites being used
* @notes - Number of cycles where GPS has been lost (if applicable in cycle)
* @notes - State of GPIO port(s)
*
* @param[in] callsign origination call sign
* @param[in] path path to use
* @param[in] symbol symbol for originator
* @param[in] dataPoint position data object
*
* @return encoded packet object pointer
* @retval NULL if encoding failed
*/
packet_t aprs_encode_position_and_telemetry(const char *callsign,
const char *path, aprs_sym_t symbol,
dataPoint_t *dataPoint,
bool extended) {
(void)extended;
// Latitude
uint32_t y = 380926 * (90 - dataPoint->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 + dataPoint->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(dataPoint->gps_alt)) / logf(1.002f);
uint32_t a1 = a / 91;
uint32_t a1r = a % 91;
/* ptime_t time;
unixTimestamp2Date(&time, dataPoint->gps_time);*/
char xmit[256];
uint32_t len = chsnprintf(xmit, sizeof(xmit), "%s>%s,%s:=",
callsign,
APRS_DEVICE_CALLSIGN,
path);
uint8_t gpsFix = dataPoint->gps_state == GPS_LOCKED1
|| dataPoint->gps_state == GPS_LOCKED2
|| dataPoint->gps_state == GPS_FIXED ? GSP_FIX_CURRENT : GSP_FIX_OLD;
uint8_t src = NMEA_SRC_GGA;
uint8_t origin = ORIGIN_PICO;
xmit[len+0] = (symbol >> 8) & 0xFF;
xmit[len+1] = y3+33;
xmit[len+2] = y2+33;
xmit[len+3] = y1+33;
xmit[len+4] = y1r+33;
xmit[len+5] = x3+33;
xmit[len+6] = x2+33;
xmit[len+7] = x1+33;
xmit[len+8] = x1r+33;
xmit[len+9] = symbol & 0xFF;
xmit[len+10] = a1+33;
xmit[len+11] = a1r+33;
xmit[len+12] = ((gpsFix << 5) | (src << 3) | origin) + 33;
// Comments
uint32_t len2 = base91_encode((uint8_t*)dataPoint,
(uint8_t*)&xmit[len+13],
sizeof(dataPoint_t));
xmit[len+len2+13] = '|';
/* APRS base91 encoded telemetry. */
// Sequence ID
uint32_t t = dataPoint->id & 0x1FFF;
xmit[len+len2+14] = t/91 + 33;
xmit[len+len2+15] = t%91 + 33;
// Telemetry analog parameters
for(uint8_t i=0; i<5; i++) {
switch(i) {
case 0: t = dataPoint->adc_vbat; break;
case 1: t = dataPoint->adc_vsol; break;
case 2: t = dataPoint->pac_pbat+4096; break;
case 3: t = dataPoint->sen_i1_temp/10 + 1000; break;
case 4: t = dataPoint->sen_i1_press/125 - 40; break;
}
xmit[len+len2+16+i*2] = t/91 + 33;
xmit[len+len2+16+i*2+1] = t%91 + 33;
}
// Telemetry digital parameter
xmit[len+len2+26] = dataPoint->gpio + 33;
/* Digital bits second byte - set zero. */
xmit[len+len2+27] = 33;
xmit[len+len2+28] = '|';
xmit[len+len2+29] = 0;
return ax25_from_text(xmit, true);
}
/*
*
*/
packet_t aprs_encode_data_packet(const char *callsign, const char *path,
char packetType, uint8_t *data)
{
char xmit[256];
chsnprintf(xmit, sizeof(xmit), "%s>%s,%s:{{%c%s", callsign,
APRS_DEVICE_CALLSIGN, path, packetType, data);
return ax25_from_text(xmit, true);
}
/**
* @brief Transmit message packet
*
* @param[in] originator call sign of originator of this message
* @param[in path path for message
* @param[in] recipient call sign of recipient
* @param[in text text of the message
* @param[in] ack true if message acknowledgment requested
*/
packet_t aprs_encode_message(const char *originator, const char *path,
const char *recipient, const char *text,
const bool ack) {
char xmit[256];
if((strlen(text) > AX25_MAX_APRS_MSG_LEN)
|| (strpbrk(text, "|~{") != NULL))
/* Invalid message. */
return NULL;
if(!ack)
chsnprintf(xmit, sizeof(xmit), "%s>%s,%s::%-9s:%s",
originator,
APRS_DEVICE_CALLSIGN,
path,
recipient,
text);
else
chsnprintf(xmit, sizeof(xmit), "%s>%s,%s::%-9s:%s{%d",
originator,
APRS_DEVICE_CALLSIGN,
path,
recipient,
text,
++msg_id);
return ax25_from_text(xmit, true);
}
/*
* @brief Compose an APRSD message
* @notes Used by position service.
*
* @param[in] originator originator of this message
* @param[in] path path to use
* @param[in] recipient identity that requested the message
*/
packet_t aprs_compose_aprsd_message(const char *originator,
const char *path,
const char *recipient) {
char buf[256] = "Directs=";
uint32_t out = strlen(buf);
uint32_t empty = out;
for(uint8_t i = 0; i < APRS_HEARD_LIST_SIZE; i++) {
if(heard_list[i].time
&& heard_list[i].time + TIME_S2I(600) >= chVTGetSystemTime()
&& heard_list[i].time <= chVTGetSystemTime())
out += chsnprintf(&buf[out], sizeof(buf)-out, "%s ",
heard_list[i].call);
}
if(out == empty) {
out += chsnprintf(&buf[out], sizeof(buf)-out, "[none]");
} else {
buf[out-1] = 0; // Remove last space
}
return aprs_encode_message(originator, path, recipient, buf, false);
}
/*
* @brief Encode and send an APRSD message
*
* @param[in] id aprs node identity
* @param[in] argc number of parameters
* @param[in] argv array of pointers to parameter strings
*
* @return result of command
* @retval MSG_OK if the command completed.
* @retval MSG_ERROR if there was an error.
*/
msg_t aprs_send_aprsd_message(aprs_identity_t *id,
int argc, char *argv[]) {
(void)argc;
(void)argv;
packet_t pp = aprs_compose_aprsd_message(id->call, id->path, id->src);
if(pp == NULL) {
TRACE_WARN("RX > No free packet objects or badly formed message");
return MSG_ERROR;
}
if(!transmitOnRadio(pp,
id->freq,
0,
0,
id->pwr,
id->mod,
id->cca)) {
TRACE_ERROR("RX > Transmit of APRSD failed");
return MSG_ERROR;
}
return MSG_OK;
}
/*
* @brief Encode and send an APRSH message
*
* @param[in] id aprs node identity
* @param[in] argc number of parameters
* @param[in] argv array of pointers to parameter strings
*
* @return result of command
* @retval MSG_OK if the command completed.
* @retval MSG_ERROR if there was an error.
*/
msg_t aprs_send_aprsh_message(aprs_identity_t *id,
int argc, char *argv[]) {
if(argc != 1)
return MSG_ERROR;
char buf[AX25_MAX_APRS_MSG_LEN + 1];
uint32_t out = 0;
strupr(argv[0]);
for(uint8_t i = 0; i < APRS_HEARD_LIST_SIZE; i++) {
if(heard_list[i].time && (strncmp(heard_list[i].call, argv[0],
strlen(argv[0])) == 0)) {
/* Convert time to human readable form. */
time_secs_t diff = chTimeI2S(chVTTimeElapsedSinceX(heard_list[i].time));
out = chsnprintf(buf, sizeof(buf),
"%s heard %02i:%02i ago",
heard_list[i].call, diff/60, diff % 60);
break;
}
if(out == 0) {
out = chsnprintf(buf, sizeof(buf),
"%s not heard", argv[0]);
}
}
packet_t pp = aprs_encode_message(id->call, id->path, id->src, buf, false);
if(pp == NULL) {
TRACE_WARN("RX > No free packet objects or badly formed message");
return MSG_ERROR;
}
if(!transmitOnRadio(pp,
id->freq,
0,
0,
id->pwr,
id->mod,
id->cca)) {
TRACE_ERROR("RX > Transmit of APRSH failed");
return MSG_ERROR;
}
return MSG_OK;
}
/*
* @brief Handle GPIO set/clear/query
*
* @param[in] id aprs node identity
* @param[in] argc number of parameters
* @param[in] argv array of pointers to parameter strings
*
* @return result of command
* @retval MSG_OK if the command completed.
* @retval MSG_ERROR if there was an error.
*/
msg_t aprs_execute_gpio_command(aprs_identity_t *id,
int argc, char *argv[]) {
if(argc != 1)
return MSG_ERROR;
/* char *tok = strtok(argv[0], ":");
if(tok == NULL)
return MSG_ERROR;*/
/* TODO: WIP to generalize by parsing out the port # and operation. */
if(!strcmp(argv[0], "io1:1")) {
TRACE_INFO("RX > Message: GPIO set IO1 HIGH");
pktSetGPIOlineMode(LINE_IO1, PAL_MODE_OUTPUT_PUSHPULL);
pktWriteGPIOline(LINE_IO1, PAL_HIGH);
return MSG_OK;
}
if(!strcmp(argv[0], "io1:0")) {
TRACE_INFO("RX > Message: GPIO set IO1 LOW");
pktSetGPIOlineMode(LINE_IO1, PAL_MODE_OUTPUT_PUSHPULL);
pktWriteGPIOline(LINE_IO1, PAL_LOW);
return MSG_OK;
}
if(!strcmp(argv[0], "io2:1")) {
TRACE_INFO("RX > Message: GPIO set IO2 HIGH");
pktSetGPIOlineMode(LINE_IO2, PAL_MODE_OUTPUT_PUSHPULL);
pktWriteGPIOline(LINE_IO2, PAL_HIGH);
return MSG_OK;
}
if(!strcmp(argv[0], "io2:0")) {
TRACE_INFO("RX > Message: GPIO set IO2 LOW");
pktSetGPIOlineMode(LINE_IO2, PAL_MODE_OUTPUT_PUSHPULL);
pktWriteGPIOline(LINE_IO2, PAL_LOW);
return MSG_OK;
}
if(!strcmp(argv[0], "io3:1")) {
TRACE_INFO("RX > Message: GPIO set IO3 HIGH");
pktSetGPIOlineMode(LINE_IO3, PAL_MODE_OUTPUT_PUSHPULL);
pktWriteGPIOline(LINE_IO3, PAL_HIGH);
return MSG_OK;
}
if(!strcmp(argv[0], "io3:0")) {
TRACE_INFO("RX > Message: GPIO set IO3 LOW");
pktSetGPIOlineMode(LINE_IO3, PAL_MODE_OUTPUT_PUSHPULL);
pktWriteGPIOline(LINE_IO3, PAL_LOW);
return MSG_OK;
}
if(!strcmp(argv[0], "io4:1")) {
TRACE_INFO("RX > Message: GPIO set IO4 HIGH");
pktSetGPIOlineMode(LINE_IO4, PAL_MODE_OUTPUT_PUSHPULL);
pktWriteGPIOline(LINE_IO4, PAL_HIGH);
return MSG_OK;
}
if(!strcmp(argv[0], "io4:0")) {
TRACE_INFO("RX > Message: GPIO set IO4 LOW");
pktSetGPIOlineMode(LINE_IO4, PAL_MODE_OUTPUT_PUSHPULL);
pktWriteGPIOline(LINE_IO4, PAL_LOW);
return MSG_OK;
}
/* TODO: Parse out IO number and reduce above and below ugly DRY code. */
packet_t pp;
do {
if(!strcmp(argv[0], "io1:?")) {
char buf[AX25_MAX_APRS_MSG_LEN + 1];
/* TODO: Need to read mode and if not output then report as "input" etc. */
chsnprintf(buf, sizeof(buf),
"IO1 is %s ",
(pktReadGPIOline(LINE_IO1) == PAL_HIGH) ? "HIGH" : "LOW");
TRACE_INFO("RX > Message: GPIO query IO1 is %s",
(pktReadGPIOline(LINE_IO1) == PAL_HIGH) ? "HIGH" : "LOW");
pp = aprs_encode_message(id->call, id->path, id->src, buf, false);
if(pp == NULL) {
TRACE_WARN("RX > No free packet objects or badly formed message");
return MSG_ERROR;
}
break;
}
if(!strcmp(argv[0], "io2:?")) {
char buf[AX25_MAX_APRS_MSG_LEN + 1];
/* TODO: Need to read mode and if not output then report as "input" etc. */
chsnprintf(buf, sizeof(buf),
"IO2 is %s ",
(pktReadGPIOline(LINE_IO2) == PAL_HIGH) ? "HIGH" : "LOW");
TRACE_INFO("RX > Message: GPIO query IO2 is %s",
(pktReadGPIOline(LINE_IO2) == PAL_HIGH) ? "HIGH" : "LOW");
pp = aprs_encode_message(id->call, id->path, id->src, buf, false);
if(pp == NULL) {
TRACE_WARN("RX > No free packet objects or badly formed message");
return MSG_ERROR;
}
break;
}
if(!strcmp(argv[0], "io3:?")) {
char buf[AX25_MAX_APRS_MSG_LEN + 1];
/* TODO: Need to read mode and if not output then report as "input" etc. */
chsnprintf(buf, sizeof(buf),
"IO3 is %s ",
(pktReadGPIOline(LINE_IO3) == PAL_HIGH) ? "HIGH" : "LOW");
TRACE_INFO("RX > Message: GPIO query IO3 is %s",
(pktReadGPIOline(LINE_IO3) == PAL_HIGH) ? "HIGH" : "LOW");
pp = aprs_encode_message(id->call, id->path, id->src, buf, false);
if(pp == NULL) {
TRACE_WARN("RX > No free packet objects or badly formed message");
return MSG_ERROR;
}
break;
}
if(!strcmp(argv[0], "io4:?")) {
char buf[AX25_MAX_APRS_MSG_LEN + 1];
/* TODO: Need to read mode and if not output then report as "input" etc. */
chsnprintf(buf, sizeof(buf),
"IO4 is %s ",
(pktReadGPIOline(LINE_IO4) == PAL_HIGH) ? "HIGH" : "LOW");
TRACE_INFO("RX > Message: GPIO query IO4 is %s",
(pktReadGPIOline(LINE_IO4) == PAL_HIGH) ? "HIGH" : "LOW");
pp = aprs_encode_message(id->call, id->path, id->src, buf, false);
if(pp == NULL) {
TRACE_WARN("RX > No free packet objects or badly formed message");
return MSG_ERROR;
}
break;
}
/* No known IO port found. */
return MSG_ERROR;
} while(true);
if(!transmitOnRadio(pp,
id->freq,
0,
0,
id->pwr,
id->mod,
id->cca)) {
TRACE_ERROR("RX > Transmit of GPIO status failed");
return MSG_ERROR;
}
return MSG_OK;
}
/**
* @brief Request for position beacon to be sent
*
* @param[in] id aprs node identity
* @param[in] argc number of parameters
* @param[in] argv array of pointers to parameter strings
*
* @return result of command
* @retval MSG_OK if the command completed.
* @retval MSG_ERROR if there was an error.
*/
msg_t aprs_send_position_response(aprs_identity_t *id,
int argc, char *argv[]) {
(void)argv;
if(argc != 0)
return MSG_ERROR;
/*
* TODO: This should just send a request to the collector service.
*/
// Encode and transmit telemetry config first
for(uint8_t type = 0; type < APRS_NUM_TELEM_GROUPS; type++) {
packet_t packet = aprs_encode_telemetry_configuration(
id->call,
id->path,
id->call,
type);
if(packet == NULL) {
TRACE_WARN("BCN > No free packet objects for"
" telemetry config transmission");
} else {
if(!transmitOnRadio(packet,
id->freq,
0,
0,
id->pwr,
id->mod,
id->cca)) {
TRACE_ERROR("BCN > Failed to transmit telemetry config");
}
}
chThdSleep(TIME_S2I(5));
}
TRACE_INFO("RX > Message: Position query");
dataPoint_t* dataPoint = getLastDataPoint();
packet_t pp = aprs_encode_stamped_position_and_telemetry(id->call,
id->path,
id->symbol,
dataPoint);
if(pp == NULL) {
TRACE_WARN("RX > No free packet objects or badly formed message");
return MSG_ERROR;
}
if(!transmitOnRadio(pp,
id->freq,
0,
0,
id->pwr,
id->mod,
id->cca)) {
TRACE_ERROR("RX > Transmit of APRSP failed");
return MSG_ERROR;
}
return MSG_OK;
}
/**
* @brief Request for system reset
*
* @param[in] id aprs node identity
* @param[in] argc number of parameters
* @param[in] argv array of pointers to parameter strings
*
* @return result of command
* @retval MSG_ERROR if there was an error.
*/
msg_t aprs_execute_system_reset(aprs_identity_t *id,
int argc, char *argv[]) {
(void)argv;
if(argc != 0)
return MSG_ERROR;
TRACE_INFO("RX > Message: System Reset");
char buf[16];
chsnprintf(buf, sizeof(buf), "ack%s", id->num);
packet_t pp = aprs_encode_message(id->call,
id->path,
id->src, buf, false);
if(pp == NULL) {
TRACE_WARN("RX > No free packet objects");
return MSG_ERROR;
}
transmitOnRadio(pp,
id->freq,
0,
0,
id->pwr,
id->mod,
id->cca);
chThdSleep(TIME_S2I(10));
NVIC_SystemReset();
/* We don't arrive here. */
return MSG_OK;
}
/*
* @brief Handle config command
*
* @param[in] id aprs node identity
* @param[in] argc number of parameters
* @param[in] argv array of pointers to parameter strings
*
* @return result of command
* @retval MSG_OK if the command completed.
* @retval MSG_ERROR if there was an error.
*/
msg_t aprs_execute_config_command(aprs_identity_t *id,
int argc, char *argv[]) {
(void)id;
if(argc != 2)
return MSG_ERROR;
for(uint8_t i=0; command_list[i].type != TYPE_NULL; i++) {
if(!strncmp(argv[1], command_list[i].name,
strlen(command_list[i].name))) {
/* Parameter being changed is in argv[0], new value is in argv[1]. */
TRACE_INFO("RX > Message: Configuration Command");
TRACE_INFO("RX > %s => %s", argv[1], argv[1]);
if(command_list[i].type == TYPE_INT
&& command_list[i].size == 1) {
*((uint8_t*)command_list[i].ptr) = atoi(argv[1]);
} else if(command_list[i].type == TYPE_INT
&& command_list[i].size == 2) {
*((uint16_t*)command_list[i].ptr) = atoi(argv[1]);
} else if(command_list[i].type == TYPE_INT
&& command_list[i].size == 4) {
*((uint32_t*)command_list[i].ptr) = atoi(argv[1]);
} else if(command_list[i].type == TYPE_TIME) {
*((sysinterval_t*)command_list[i].ptr) =
TIME_MS2I(atoi(argv[2]));
} else if(command_list[i].type == TYPE_STR) {
strncpy((char*)command_list[i].ptr, argv[1],
sizeof(command_list[i].size)-1);
}
return MSG_OK;
} /* Next parameter. */
} /* Parameter not found. */
return MSG_ERROR;
}
/**
* @brief Request configuration save to flash
*
* @param[in] id aprs node identity
* @param[in] argc number of parameters
* @param[in] argv array of pointers to parameter strings
*
* @return result of command
* @retval MSG_ERROR if there was an error.
*/
msg_t aprs_execute_config_save(aprs_identity_t *id,
int argc, char *argv[]) {
(void)id;
(void)argc;
(void)argv;
TRACE_INFO("RX > Message: Config Save");
conf_sram.magic = CONFIG_MAGIC_UPDATED;
flashSectorBegin(flashSectorAt(0x08060000));
flashErase(0x08060000, 0x20000);
flashWrite(0x08060000, (char*)&conf_sram, sizeof(conf_t));
flashSectorEnd(flashSectorAt(0x08060000));
return MSG_OK;
}
/*
* @brief Handle Image command
*
* @param[in] id aprs node identity
* @param[in] argc number of parameters
* @param[in] argv array of pointers to parameter strings
*
* @return result of command
* @retval MSG_OK if the command completed.
* @retval MSG_ERROR if there was an error.
*/
msg_t aprs_execute_img_command(aprs_identity_t *id,
int argc, char *argv[]) {
(void)id;
if(argc < 2)
return MSG_ERROR;
if(!strcmp(argv[0], "reject") && argc == 2) {
if(!strcmp(argv[1], "pri")) {
reject_pri = true;
TRACE_INFO("RX > Message: Image reject pri");
return MSG_OK;
}
if(!strcmp(argv[1], "sec")) {
reject_sec = true;
TRACE_INFO("RX > Message: Image reject sec");
return MSG_OK;
}
return MSG_ERROR;
}
if(!strcmp(argv[0], "repeat")) {
TRACE_INFO("RX > Message: Image packet repeat request");
/* Start at arg 2. */
int c = 2;
while(c <= argc) {
uint32_t req = strtol(argv[c++], NULL, 16);
for(uint8_t i = 0; i < 16; i++) {
/* Find an empty repeat slot. */
if(!packetRepeats[i].n_done) {
packetRepeats[i].image_id = (req >> 16) & 0xFF;
packetRepeats[i].packet_id = req & 0xFFFF;
packetRepeats[i].n_done = true;
TRACE_INFO("RX > ... Image %3d Packet %3d",
packetRepeats[i].image_id,
packetRepeats[i].packet_id);
break;
} /* Not an empty slot. */
} /* No more slots. */
} /* No more image IDs. */
return MSG_OK;
}
/* Unknown parameter. */
return MSG_ERROR;
}
/*
* @brief Decode APRS content and check for message
*
* @param[in] pp an APRS packet object
*
* @return result of command
* @retval true if not a message or not addressed to any node on this device.
* in that case the APRS content can be digipeated.
* @retval false if this was a message for a node on this device.
* in that case the APRS content should not be digipeated.
*/
static bool aprs_decode_message(packet_t pp) {
// Get Info field
char src[127];
unsigned char *pinfo;
if(ax25_get_info(pp, &pinfo) == 0)
return false;
ax25_format_addrs(pp, src, sizeof(src));
/* Decode destination call sign. */
char dest[AX25_MAX_ADDR_LEN];
uint8_t i = 0;
while(i < sizeof(dest) - 1) {
if(pinfo[i+1] == ':' || pinfo[i+1] == ' ') {
dest[i++] = 0;
break;
}
dest[i] = pinfo[i+1];
i++;
}
/* Convert destination call sign to upper case. */
strupr(dest);
/* Decode source call sign. */
for(uint32_t i = 0; i < sizeof(src); i++) {
if(src[i] == '>') {
src[i] = 0;
break;
}
}
/* Convert source call sign to upper case. */
strupr(src);
/*
* Setup default responding app identity.
* Default identity id set to tx.
*/
aprs_identity_t identity = {0};
strcpy(identity.src, src);
strcpy(identity.call, conf_sram.aprs.tx.call);
/* TODO: define a length for path. */
strcpy(identity.path, conf_sram.aprs.tx.path);
identity.symbol = conf_sram.aprs.tx.symbol;
identity.freq = conf_sram.aprs.tx.radio_conf.freq;
identity.pwr = conf_sram.aprs.tx.radio_conf.pwr;
identity.mod = conf_sram.aprs.tx.radio_conf.mod;
identity.cca = conf_sram.aprs.tx.radio_conf.cca;
/* Check which apps are enabled to accept APRS messages. */
bool pos_pri = (strcmp(conf_sram.pos_pri.call, dest) == 0)
&& (conf_sram.pos_pri.aprs_msg)
&& (conf_sram.pos_pri.beacon.active);
if(pos_pri) {
strcpy(identity.call, conf_sram.pos_pri.call);
strcpy(identity.path, conf_sram.pos_pri.path);
identity.symbol = conf_sram.pos_pri.symbol;
}
bool pos_sec = (strcmp(conf_sram.pos_sec.call, dest) == 0)
&& (conf_sram.pos_sec.aprs_msg)
&& (conf_sram.pos_sec.beacon.active);
if(pos_sec) {
strcpy(identity.call, conf_sram.pos_sec.call);
strcpy(identity.path, conf_sram.pos_sec.path);
identity.symbol = conf_sram.pos_sec.symbol;
}
bool aprs_rx = (strcmp(conf_sram.aprs.rx.call, dest) == 0)
&& (conf_sram.aprs.rx.svc_conf.active);
if(aprs_rx) {
strcpy(identity.call, conf_sram.aprs.rx.call);
identity.symbol = conf_sram.aprs.rx.symbol;
/* Other parameters come from tx identity. */
}
/* Even if the digi is not active respond on the digi (TX) call. */
bool aprs_tx = (strcmp(conf_sram.aprs.tx.call, dest) == 0)
&& (conf_sram.aprs.rx.svc_conf.active)
/*&& (conf_sram.aprs.digi.active)*/;
/* Default already set tx parameters. */
/* Check if this is message and address is one of the apps on this device. */
if(!((pinfo[10] == ':') && (pos_pri || pos_sec || aprs_rx || aprs_tx))) {
/*
* Not a command or not addressed to one of the active apps on this device.
* Flag that message should be digipeated.
*/
return true;
}
/* Proceed with command analysis. */
char msg_id_rx[8] = {0};
//memset(msg_id_rx, 0, sizeof(msg_id_rx));
// Cut off control chars
for(uint16_t i = 11; pinfo[i] != 0
&& i < (AX25_MAX_APRS_MSG_LEN + 11); i++) {
/* FIXME: Trim trailing spaces before {. */
if(pinfo[i] == '{') {
// Copy ACK ID
memcpy(msg_id_rx, &pinfo[i+1], sizeof(msg_id_rx)-1);
// Cut off non-printable chars
for(uint8_t j=0; j<sizeof(msg_id_rx); j++) {
if(msg_id_rx[j] < 32 || msg_id_rx[j] > 126) {
msg_id_rx[j] = 0;
break;
}
}
pinfo[i] = 0; // Mark end of message
}
if(pinfo[i] == '\r' || pinfo[i] == '\n') {
pinfo[i] = 0;
}
}
strcpy(identity.num, msg_id_rx);
/* Convert command string to lower case. */
char *astrng = strlwr((char*)&pinfo[11]);
// Trace
TRACE_INFO("RX > Received message from %s (ID=%s): %s",
src, msg_id_rx[0] == 0 ? "none" : msg_id_rx, astrng);
/* Filter out telemetry configuration and "Directs=" messages sent to ourselves. */
char const *cfgs[] = {"parm.", "unit.", "eqns.", "bits.", "directs="};
uint8_t x;
for(x = 0; x < (sizeof(cfgs) / sizeof((cfgs)[0])); x++) {
if(strncmp(astrng, cfgs[x], strlen(cfgs[x])) == 0)
return false;
}
/* Parse arguments. */
char *lp, *cmd, *tokp;
char *args[APRS_MAX_MSG_ARGUMENTS];
lp = aprs_parse_arguments(astrng, &tokp);
/* The command itself. */
cmd = lp;
int n = 0;
while ((lp = aprs_parse_arguments(NULL, &tokp)) != NULL) {
if (n >= APRS_MAX_MSG_ARGUMENTS) {
TRACE_INFO("RX > Too many APRS command arguments");
cmd = NULL;
break;
}
args[n++] = lp;
}
/* Parse and execute command. */
msg_t msg = aprs_cmd_exec(aprs_commands, cmd, &identity, n, args);
if(msg == MSG_TIMEOUT) {
TRACE_INFO("RX > No command found in message");
}
if(msg_id_rx[0]) {
/* Incoming message ID exists so an ACK or REJ has to be sent. */
char buf[16];
chsnprintf(buf, sizeof(buf), "%s%s",
(msg == MSG_OK || msg == MSG_TIMEOUT) ?
"ack" : "rej", msg_id_rx);
/*
* Use the receiving node identity as sender.
* Don't request acknowledgment.
*/
packet_t pp = aprs_encode_message(identity.call,
identity.path,
identity.src, buf, false);
if(pp == NULL) {
TRACE_WARN("RX > No free packet objects");
return false;
}
transmitOnRadio(pp,
identity.freq,
0,
0,
identity.pwr,
identity.mod,
identity.cca);
}
/* Flag that the APRS content should not be digipeated. */
return false;
}
/**
* Transmit failure will release the packet memory.
*/
static void aprs_digipeat(packet_t pp) {
if(!dedupe_initialized) {
dedupe_init(TIME_S2I(10));
dedupe_initialized = true;
}
if(!dedupe_check(pp, 0)) { // Last identical packet older than 10 seconds
packet_t result = digipeat_match(0, pp, conf_sram.aprs.rx.call,
conf_sram.aprs.tx.call, alias_re,
wide_re, 0, preempt, NULL);
if(result != NULL) { // Should be digipeated
dedupe_remember(result, 0);
/* If transmit fails the packet buffer is released. */
if(!transmitOnRadio(result,
conf_sram.aprs.tx.radio_conf.freq,
0,
0,
conf_sram.aprs.tx.radio_conf.pwr,
conf_sram.aprs.tx.radio_conf.mod,
conf_sram.aprs.tx.radio_conf.cca)) {
TRACE_INFO("RX > Failed to digipeat packet");
} /* TX failed. */
} /* Should be digipeated. */
} /* Duplicate check. */
}
/**
* Transmit APRS telemetry configuration
*/
packet_t aprs_encode_telemetry_configuration(const char *originator,
const char *path,
const char *destination,
uint8_t type) {
switch(type) {
case 0: return aprs_encode_message(originator, path, destination,
"PARM.Vbat,Vsol,Pbat,Temperature,Airpressure,"
"IO1,IO2,IO3,IO4", false);
case 1: return aprs_encode_message(originator, path, destination,
"UNIT.V,V,W,degC,Pa,1,1,1,1", false);
case 2: return aprs_encode_message(originator, path, destination,
"EQNS.0,0.001,0,0,0.001,0,0,0.001,"
"-4.096,0,0.1,-100,0,12.5,500", false);
case 3: return aprs_encode_message(originator, path, destination,
"BITS.1111,Pecan Pico", false);
default: return NULL;
}
}
/*
*
*/
void aprs_decode_packet(packet_t pp) {
// Get heard callsign
char call[AX25_MAX_ADDR_LEN];
int8_t v = -1;
do {
v++;
ax25_get_addr_with_ssid(pp, ax25_get_heard(pp)-v, call);
} while(((ax25_get_heard(pp) - v) >= AX25_SOURCE)
&& (!strncmp("WIDE", call, 4) || !strncmp("TRACE", call, 5)));
// Fill/Update direct list
sysinterval_t first_time = 0xFFFFFFFF; // Timestamp of oldest heard list entry
uint8_t first_id = 0; // ID of oldest heard list entry
for(uint8_t i=0; i <= APRS_HEARD_LIST_SIZE; i++) {
if(i < APRS_HEARD_LIST_SIZE) {
// Search for callsign in list
if(!strcmp(heard_list[i].call, call)) { // Callsign found in list
heard_list[i].time = chVTGetSystemTime(); // Update time the callsign was last heard
break;
}
// Find oldest entry
if(first_time > heard_list[i].time) {
first_time = heard_list[i].time;
first_id = i;
}
} else { // Callsign not in list
// Overwrite old entry/ use empty entry
memcpy(heard_list[first_id].call, call, sizeof(heard_list[first_id].call));
heard_list[first_id].time = chVTGetSystemTime();
}
}
// Decode message packets
bool digipeat = true;
unsigned char *pinfo;
if(ax25_get_info(pp, &pinfo) == 0)
return;
/*
* Check if the message is for us.
* Execute any command found in the message.
* If not then digipeat it.
*/
if(pinfo[0] == ':') {
digipeat = aprs_decode_message(pp); // ax25_get_dti(pp)
}
// Digipeat packet
if(conf_sram.aprs.digi && digipeat) {
aprs_digipeat(pp);
}
}