934 lines
33 KiB
C++

#include <string>
#include <sys/mman.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "INIReader.h"
#include "arp_table.h"
#include "hostapd_ctrl.h"
#include "telem_dest.h"
#include "util.h"
#include "link_packet.h"
#include "mutex.h"
#include "net_wmm.h"
/*
* telem_ctrl
*
* Receive telemetry (mavlink) via UDP from Solo, and forward to:
* stm32 process (sends to controller's STM32)
* tlog process (creates tlog file on controller)
* any host detected on network that
* - is associated with AP (e.g. app, gcs)
* - appears in ARP table (e.g gcs running in VM)
*
* Intercept SYSTEM_TIME messages in downlink, and set system time from GPS the
* first time it is found valid.
*
* Receive telemetry in the upstream direction and forward to Solo.
*
* Log telemetry statistics periodically:
* downlink, total per (system id, component id)
* downlink, sequence errors per (system id, component id)
* uplink, total messages only
*
* This consumes about 2% of the CPU when forwarding 90 packets/second.
*/
/* process priority - this is used with SCHED_FIFO. A higher number is a
higher priority. When priorities are viewed on the target (in /proc, or
using lsproc.py), the number seen there will be (1 - number_here), so
e.g. if PRIORITY is 50, it will show up as priority -51. */
#define TC_MAIN_PRIORITY 58
/* instead of pulling in all the mavlink headers...*/
#define MAVLINK_MSG_ID_SYSTEM_TIME 2
/* controller sends mavlink to this port on all discovered telemetry destinations (gcs, app) */
static short telem_dest_port_hbo; /* read from config on startup */
static pthread_t tlm_ctx;
/* log stats this often */
static uint64_t log_interval_us = 10 * 1000000;
/* read from config; whether we set the system time when a GPS message
comes through */
static int use_gps_time = true;
/* becomes true when we get a good GPS time and set the system time */
static int got_gps_time = 0;
/* TOS used for telemetry packets from controller to solo */
static int uplink_tos = IP_TOS_BE;
/* TOS used for telemetry packets from controller to any GCS (including app) */
static int gcs_tos = IP_TOS_BE;
/* This is used to prevent swamping the log with error messages if
something unexpected happens.
Returns -1 if we cannot log an error now, or returns the number of
messages skipped due to rate limiting if we can, i.e. a return of
2 means log, and we have skipped 2 messages due to rate limiting. */
static int can_log_error(uint64_t now_us)
{
static uint64_t err_time_us = 0; /* last time we logged */
static uint64_t err_interval_us = 1000000; /* once per second max */
static unsigned skipped = 0;
unsigned ret_val;
if ((now_us - err_time_us) < err_interval_us) {
/* can't log */
skipped++;
return -1;
}
/* yes; say we can and set err_time_us assuming we do log something */
err_time_us = now_us;
ret_val = skipped;
skipped = 0;
return ret_val;
}
/* another process creates this file and writes Solo's IP to it when it connects */
static const char *solo_ip_file = "/var/run/solo.ip";
/* how often to check it when waiting for Solo */
static unsigned solo_ip_poll_us = 100000;
/* solo's address */
static struct sockaddr_in sa_solo;
/*
* wait_solo - wait for Solo to appear on network
*
* Wait for the solo ip file to appear, and when it does, set our global solo
* ip address from it. This is called once at startup and does not return
* until Solo is there.
*/
static void wait_solo(in_addr_t *solo_addr)
{
FILE *fp;
char buf[40];
char *status;
int err;
while (1) {
while ((fp = fopen(solo_ip_file, "r")) == NULL)
usleep(solo_ip_poll_us);
memset(buf, 0, sizeof(buf));
status = fgets(buf, sizeof(buf) - 1, fp);
err = errno; /* save in case of error */
fclose(fp);
if (status == buf) {
*solo_addr = inet_addr(buf);
if (*solo_addr != htonl(INADDR_NONE))
return;
syslog(LOG_ERR, "converting \"%s\" to ip address", buf);
} else {
syslog(LOG_ERR, "reading %s: %s", solo_ip_file, strerror(err));
}
/* loop around and try again, probably futile */
sleep(1);
}
} /* wait_solo */
/*
* telemetry destinations table
*/
/* how often to update the table */
static unsigned telem_dest_update_interval_s = 5;
/* instance of telemetry destinations table */
static telem_dest_table dest_table;
static pthread_mutex_t dest_table_mutex;
/* arp table, filled in by arp_table_get() each time we update the telemetry
destinations table */
#define ARP_ENTRIES_MAX 32
static arp_entry_t arp_table[ARP_ENTRIES_MAX];
/* table of everyone associated with the AP, filled in by each time we update
the telemetry destinations table */
#define HOSTAPD_ENTRIES_MAX 32
static hostapd_station_info_t hostapd_table[HOSTAPD_ENTRIES_MAX];
/*
* update_telem_dest - update the telemetry destinations table
*
* Add a destination when we have the ip (from arp).
* Remove a destination when the mac is not in hostapd's list and the ip is not
* in the arp table.
*
* In testing, it is now looking like IP addresses "never" disappear from the
* arp table; they are just marked with flags=0 when the entry times out (but
* the IP is still there). That means once an IP gets in the list of
* destinations, it will never be removed. For our purposes, that is okay. We
* could remove it when flags=0, but choose not to, with the fear that there
* may be some other condition that causes flags to be zero and we should not
* delete the destination.
*/
static int update_telem_dest(void *hostapd_handle)
{
int arp_entries;
int hostapd_entries;
int i;
/* get arp table */
memset(arp_table, 0, sizeof(arp_table));
arp_entries = ARP_ENTRIES_MAX;
if (arp_table_get(arp_table, &arp_entries) != 0) {
syslog(LOG_ERR, "ERROR reading arp table");
return -1;
}
/* get associated stations table */
memset(hostapd_table, 0, sizeof(hostapd_table));
hostapd_entries = HOSTAPD_ENTRIES_MAX;
if (hostapd_ctrl_get_stations(hostapd_handle, hostapd_table, &hostapd_entries) != 0) {
syslog(LOG_ERR, "ERROR getting station info");
return -1;
}
/* The telemetry thread reads information from dest_table. To update it,
we copy it to work_table, update work_table, then copy work_table back
to dest_table inside a critical section. The telemetry thread might
hold the lock for a while as it sends a packet to all the destinations,
but this update function should hold it only for the final memcpy. */
telem_dest_table work_table;
memcpy(&work_table, &dest_table, sizeof(work_table));
/* for each entry in the arp table, if it is not in work_dest table, add it */
arp_table_dump(LOG_DEBUG, arp_table, arp_entries);
for (i = 0; i < arp_entries; i++) {
int s = work_table.find_by_mac_ip(arp_table[i].mac, arp_table[i].ip);
/* is this arp entry one of our known telemetry destinations? */
if (s == -1) {
struct in_addr in;
in.s_addr = arp_table[i].ip;
syslog(LOG_DEBUG, "arp entry %d is not a known telem dest", i);
if (in.s_addr == sa_solo.sin_addr.s_addr) {
syslog(LOG_DEBUG, "skipping telem dest at %s (solo)", inet_ntoa(in));
} else if (arp_table[i].flags == 0) {
syslog(LOG_DEBUG, "skipping telem dest at %s (flags=0)", inet_ntoa(in));
} else {
syslog(LOG_INFO, "adding telem dest @ %s:%d", inet_ntoa(in), telem_dest_port_hbo);
work_table.add(arp_table[i].mac, arp_table[i].ip, telem_dest_port_hbo, gcs_tos);
}
} else {
syslog(LOG_DEBUG, "arp entry %d is telem dest %d", i, s);
}
}
/* for each telem dest, if its mac is not in hostapd's list and its ip is not in the arp table,
* remove it */
syslog(LOG_DEBUG, "%d telemetry destinations", work_table.num_dest);
for (i = 0; i < work_table.num_dest; i++) {
/* don't mess with destinations on localhost */
if (work_table.dest[i].sa.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) {
syslog(LOG_DEBUG, "work_table.dest[%d] (%s:%d) is local", i,
inet_ntoa(work_table.dest[i].sa.sin_addr),
ntohs(work_table.dest[i].sa.sin_port));
continue;
}
/* in hostapd's list? */
if (hostapd_ctrl_find_by_mac(hostapd_table, hostapd_entries, work_table.dest[i].mac) !=
-1) {
syslog(LOG_DEBUG, "work_table.dest[%d] (%s:%d) is associated", i,
inet_ntoa(work_table.dest[i].sa.sin_addr),
ntohs(work_table.dest[i].sa.sin_port));
continue;
}
/* in arp table? */
if (arp_table_find_by_ip(arp_table, arp_entries, work_table.dest[i].sa.sin_addr.s_addr) !=
-1) {
syslog(LOG_DEBUG, "work_table.dest[%d] (%s:%d) is in the arp table", i,
inet_ntoa(work_table.dest[i].sa.sin_addr),
ntohs(work_table.dest[i].sa.sin_port));
continue;
}
/* work_table.dest[i] is not associated with hostapd, and is not in arp table;
delete it */
syslog(LOG_DEBUG, "work_table.dest[%d] is neither associated nor in arp table", i);
syslog(LOG_INFO, "deleting telem dest at %s", inet_ntoa(work_table.dest[i].sa.sin_addr));
work_table.delete_by_index(i);
i--; /* the next entry is at [i] now */
}
mutex_lock(&dest_table_mutex, NULL);
memcpy(&dest_table, &work_table, sizeof(dest_table));
mutex_unlock(&dest_table_mutex);
dest_table.check();
return 0;
} /* update_telem_dest */
/*
* sources[] table - one entry per mavlink source, containing expected next
* sequence number and basic stats (packet count, error count).
*
* Normally we only have two or three sources (pixhawk, rssi process, maybe
* gimbal).
*
* It has been observed in the past that we can get total garbage from Pixhawk
* (don't know if that can still happen). If that happens, the sources table
* will fill up with junk (bogus sys_id, comp_id), then we will stop adding
* new sources (they just won't be checked). Then the next time stats are
* printed (e.g. every 10 seconds), we'll print meaningless stats about the
* garbage, clear the table, and start over. If the junk goes away, this should
* recover.
*/
#define SOURCE_MAX 32
typedef struct {
uint8_t sys_id;
uint8_t comp_id;
uint8_t seq;
unsigned pkt_cnt;
unsigned err_cnt;
} source_info_t;
static source_info_t sources[SOURCE_MAX];
static int num_sources = 0;
static void source_set(source_info_t *si, uint8_t sys_id, uint8_t comp_id, uint8_t seq)
{
si->sys_id = sys_id;
si->comp_id = comp_id;
si->seq = seq;
si->pkt_cnt = 1;
si->err_cnt = 0;
}
/*
* source_check - check sequence number of a message
*
* Also maintain basic statistics on messages from each source (packet count,
* error count).
*
* Return zero if sequence correct, nonzero if sequence error
*/
static int source_check(uint8_t sys_id, uint8_t comp_id, uint8_t seq)
{
int i;
uint16_t new_id = (uint16_t(sys_id) << 8) | comp_id;
/* find source */
for (i = 0; i < num_sources; i++) {
uint16_t old_id = (uint16_t(sources[i].sys_id) << 8) | sources[i].comp_id;
if (old_id == new_id) {
sources[i].pkt_cnt++;
sources[i].seq += 1; /* expected sequence */
if (seq == sources[i].seq)
return 0; /* correct */
/* sequence error */
sources[i].err_cnt++;
sources[i].seq = seq; /* set for next time */
return -1;
} else if (old_id > new_id)
break; /* insert new one at [i] */
}
/* didn't find src_id, comp_id */
/* i == num_source if we got to the end,
or is index where we want to insert */
if (num_sources < SOURCE_MAX) {
/* add/insert new source */
memmove(&sources[i + 1], &sources[i], (num_sources - i) * sizeof(source_info_t));
source_set(&sources[i], sys_id, comp_id, seq);
num_sources++;
}
return 0;
}
/*
* sources_clear - clear sources and stats
*
* This is called after each time stats are printed. There are two reasons:
* 1. So stats are deltas since the last printout, and
* 2. So if we get junk and fill up the sources[] table, it will be cleared
* the next time we print stats.
*/
static void sources_clear(void)
{
memset(sources, 0, sizeof(sources));
num_sources = 0;
}
/*
* create_and_bind - create a socket and bind it to a local UDP port
*
* Used to create the socket on the upstream side that receives from and sends
* to Solo, and the socket on the downstream side that receives from and sends
* to everything else (stm32, tlog, app, gcs).
*
* Returns fd on success, -1 on error.
*/
int create_and_bind(short port_hbo, int tos)
{
int fd;
struct sockaddr_in sa;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket");
return -1;
}
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = htonl(INADDR_ANY);
sa.sin_port = htons(port_hbo);
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
perror("bind");
close(fd);
return -1;
}
if (setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) != 0) {
perror("setsockopt");
close(fd);
return -1;
}
return fd;
} /* create_and_bind */
/*
* lock_mem - force allocation of stack and lock the pages
*/
static void lock_mem(uint32_t size)
{
char stack[size];
mlockall(MCL_CURRENT | MCL_FUTURE);
memset(stack, 0, sizeof(stack));
}
#define TELEM_PKT_MAX 512
/*
* tlm_main
*/
static void *tlm_main(void *)
{
int fd_solo;
fd_set fds;
int nfds = 0;
int res;
uint64_t now_us;
uint8_t pkt[TELEM_PKT_MAX];
LinkPacket packet;
uint32_t link_next_seq = 0;
struct sockaddr_in sa;
socklen_t sa_len;
int i;
uint64_t log_time_us;
uint32_t pkts_down_total = 0;
uint32_t pkts_up_total = 0;
uint32_t select_timeout = 0;
struct timeval timeout;
const uint64_t dropped_log_interval_us =
1000000; // (1 sec) minimum time between logging drop events
uint64_t dropped_log_us = 0; // last time we logged a drop event
unsigned dropped_total = 0; // total dropped since last time we logged
/* Solo socket. This is where downlink packets arrive. */
fd_solo = create_and_bind(telem_dest_port_hbo, uplink_tos);
if (fd_solo < 0) {
syslog(LOG_CRIT, "can't create solo socket and bind to port %d", telem_dest_port_hbo);
return NULL;
}
/* Clear mavlink sources and stats. */
sources_clear();
/* Memory lock size should be tuned, but the value passed here is
in addition to main's stack, and it is the only thing that uses
significant stack. */
lock_mem(32768);
now_us = clock_gettime_us(CLOCK_MONOTONIC);
log_time_us = now_us + log_interval_us;
/* Wait for a packet to arrive in either the upstream or downstream
direction, and forward it as needed. Also periodically check to see if
there are any new destinations on the network where the downlink should
go. Downlink goes to everyone on the network, port telem_dest_port_hbo. */
while (1) {
uint64_t t1_us;
uint64_t t2_us;
/* Wait for a packet, or time out if no packets arrive so we always
periodically log status and check for new destinations. Downlink
packets are on the order of 100/sec, so the timeout is such that
we don't expect timeouts unless solo stops sending packets. We
almost always get a packet with a 200 msec timeout, but not with
a 100 msec timeout. (Timeouts don't really matter though.) */
FD_ZERO(&fds);
nfds = 0;
FD_SET(fd_solo, &fds);
if (fd_solo >= nfds)
nfds = fd_solo + 1;
mutex_lock(&dest_table_mutex, NULL);
for (i = 0; i < dest_table.num_dest; i++) {
int fd = dest_table.dest[i].fd;
FD_SET(fd, &fds);
if (fd >= nfds)
nfds = fd + 1;
}
mutex_unlock(&dest_table_mutex);
timeout.tv_sec = 0;
timeout.tv_usec = 200000;
t1_us = clock_gettime_us(CLOCK_MONOTONIC);
/* The time from returning from select in loop N to getting to here in
loop N+1 should be very short. 100 msec should be far far longer
than we need; we are looking for unexpected blocks or stalls here. */
if ((t1_us - now_us) > 100000) {
syslog(LOG_ERR, "loop: took %u usec", (unsigned)(t1_us - now_us));
}
/* This select is intended to be the only place this thread blocks, so
is the only place where the telemetry destinations table should get
updated.
Don't count on that (blocking has showed up in unexpected places),
but do this select while not holding the telemetry dests table mutex
in case it is int fact true.
If a telemetry dest goes away, its fd is closed, which should have
no effect on the select for Linux, but is unportable behavior. */
res = select(nfds, &fds, NULL, NULL, &timeout);
now_us = clock_gettime_us(CLOCK_MONOTONIC);
/* select should time out in 200 msec at the most; warn if it blocks
longer than that */
if ((now_us - t1_us) > 250000)
syslog(LOG_ERR, "select: blocked %u usec", (unsigned)(now_us - t1_us));
if (res < 0) {
int skipped;
if ((skipped = can_log_error(now_us)) >= 0)
syslog(LOG_ERR, "[%u] select: %s", skipped, strerror(errno));
/* this sleep is to avoid soaking the CPU if select starts
returning immediately for some reason */
usleep(10000);
continue;
}
if (res == 0) {
/* timeout */
select_timeout++;
/* Could skip checking the two fds, but we need to continue on
below that, possibly checking for new telemetry destinations
or logging stats. */
}
/* Either have a packet ready in one of the sockets, or timed out.
Processing one packet per loop makes it so neither uplink nor
downlink can starve the other, e.g. if we get a huge burst of
downlink packets, uplink still gets to send one packet per downlink
packet if it wants to (vs no uplink at all until we work through
the downlink burst). */
/* check for downstream packets */
if (FD_ISSET(fd_solo, &fds)) {
/* packet from solo */
sa_len = sizeof(sa);
t1_us = clock_gettime_us(CLOCK_MONOTONIC);
res = recvfrom(fd_solo, &packet, sizeof(packet), 0, (struct sockaddr *)&sa, &sa_len);
t2_us = clock_gettime_us(CLOCK_MONOTONIC);
/* recvfrom should not block; warn if it does */
if ((t2_us - t1_us) > 10000)
syslog(LOG_ERR, "recvfrom(solo): blocked %u usec", (unsigned)(t2_us - t1_us));
packet.tc_recv_us = now_us; // timestamp when it was received in controller
// initialize sequence check if this is the first downstream packet
if (link_next_seq == 0)
link_next_seq = packet.seq;
// check sequence
// downlink packets dropped between this packet and the previous one (normally zero)
unsigned dropped = packet.seq - link_next_seq;
// total dropped since last time we logged a message
dropped_total += dropped;
// Should we log? Yes, if we have seen drops and have not logged too recently.
if ((dropped_total > 0) && ((now_us - dropped_log_us) >= dropped_log_interval_us)) {
syslog(LOG_ERR, "dropped %u downlink packets", dropped_total);
dropped_total = 0;
dropped_log_us = now_us;
}
link_next_seq = packet.seq + 1;
pkts_down_total++;
/* We get one mavlink packet per udp datagram. Sanity checks here
are: must be from solo's IP and have a valid mavlink header. */
if (sa.sin_addr.s_addr != sa_solo.sin_addr.s_addr) {
int skipped;
if ((skipped = can_log_error(now_us)) >= 0)
syslog(LOG_ERR, "[%u] received packet not from solo (0x%08x)", skipped,
sa.sin_addr.s_addr);
} else if (res < (LinkPacket::HDR_LEN + 8)) {
int skipped;
if ((skipped = can_log_error(now_us)) >= 0)
syslog(LOG_ERR, "[%u] received runt packet (%d bytes)", skipped, res);
} else if (packet.payload[0] != 254) {
int skipped;
if ((skipped = can_log_error(now_us)) >= 0)
syslog(LOG_ERR, "[%u] received bad magic (0x%02x)", skipped, packet.payload[0]);
}
/*
else if (packet.payload[1] != (res - LinkPacket::HDR_LEN - 8))
{
int skipped;
if ((skipped = can_log_error(now_us)) >= 0)
syslog(LOG_ERR, "[%u] inconsistent length (%u, %u)",
skipped, packet.payload[1], res);
}
*/
else {
/* packet is from solo and passes sanity checks */
/* Run through the entire datagram and check each sequence and sys/comp.
* Also check for a GPS time that we can set the clock to */
uint8_t *p_payload = packet.payload;
while (p_payload < ((uint8_t *)&packet + res)) {
uint8_t seq = p_payload[2];
uint8_t sys_id = p_payload[3];
uint8_t comp_id = p_payload[4];
source_check(sys_id, comp_id, seq);
if (!got_gps_time && p_payload[5] == MAVLINK_MSG_ID_SYSTEM_TIME) {
// XXX hefty magic here
// usec since unix epoch is bytes 6..13, little-endian
int b;
uint64_t time_us = 0;
for (b = 13; b >= 6; b--)
time_us = time_us * 256 + p_payload[b];
if (time_us != 0) {
char buf[32];
syslog(LOG_INFO, "gps time %s", clock_tostr_r(time_us, buf));
got_gps_time = 1;
if (use_gps_time) {
clock_settime_us(CLOCK_REALTIME, time_us);
syslog(LOG_INFO, "system time set from gps time");
}
}
}
p_payload += (8 + p_payload[1]); // Increase by the length of the payload+header
}
/* check source port */
if (sa_solo.sin_port == 0) {
sa_solo.sin_port = sa.sin_port;
syslog(LOG_INFO, "solo @ %s:%d", inet_ntoa(sa_solo.sin_addr),
ntohs(sa_solo.sin_port));
} else if (sa_solo.sin_port != sa.sin_port) {
sa_solo.sin_port = sa.sin_port;
syslog(LOG_INFO, "solo is now at %s:%d", inet_ntoa(sa_solo.sin_addr),
ntohs(sa_solo.sin_port));
}
/* send to all destinations */
t1_us = clock_gettime_us(CLOCK_MONOTONIC);
mutex_lock(&dest_table_mutex, NULL);
t2_us = clock_gettime_us(CLOCK_MONOTONIC);
/* mutex_lock should not take longer than the memcpy in
update_telem_dest; warn if it takes too long */
if ((t2_us - t1_us) > 10000)
syslog(LOG_ERR, "mutex_lock: blocked %u usec", (unsigned)(t2_us - t1_us));
packet.tc_send_us = clock_gettime_us(CLOCK_MONOTONIC);
for (i = 0; i < dest_table.num_dest; i++) {
// send the whole LinkPacket or just the mavlink payload
const void *buf = NULL;
ssize_t len = 0;
if (dest_table.dest[i].format == TF_MAVLINK) {
buf = (const void *)(packet.payload);
len = res - LinkPacket::HDR_LEN;
} else if (dest_table.dest[i].format == TF_LINK_PACKET) {
buf = (const void *)(&packet);
len = res;
}
/* sendto blocks if the network has gone away */
errno = 0;
ssize_t r = sendto(dest_table.dest[i].fd, buf, len, MSG_DONTWAIT,
(struct sockaddr *)&(dest_table.dest[i].sa),
sizeof(dest_table.dest[i].sa));
if (r != len) {
dest_table.dest[i].err_cnt++;
if ((now_us - dest_table.dest[i].err_us) >= 1000000) {
syslog(LOG_ERR, "[%u] sendto %s:%d returned %d, expected %d: %s",
dest_table.dest[i].err_cnt,
inet_ntoa(dest_table.dest[i].sa.sin_addr),
ntohs(dest_table.dest[i].sa.sin_port), r, len, strerror(errno));
dest_table.dest[i].err_cnt = 0;
dest_table.dest[i].err_us = now_us;
}
}
}
mutex_unlock(&dest_table_mutex);
}
}
/* check for upstream packets */
mutex_lock(&dest_table_mutex, NULL);
for (i = 0; i < dest_table.num_dest; i++) {
int fd = dest_table.dest[i].fd;
if (FD_ISSET(fd, &fds)) {
/* packet from gcs */
sa_len = sizeof(sa);
t1_us = clock_gettime_us(CLOCK_MONOTONIC);
res = recvfrom(fd, pkt, sizeof(pkt), 0, (struct sockaddr *)&sa, &sa_len);
t2_us = clock_gettime_us(CLOCK_MONOTONIC);
/* recvfrom should not block; warn if it does */
if ((t2_us - t1_us) > 10000)
syslog(LOG_ERR, "recvfrom(gcs): blocked %u usec", (unsigned)(t2_us - t1_us));
pkts_up_total++;
/* send to solo */
if (sa_solo.sin_addr.s_addr != 0 && sa_solo.sin_port != 0) {
/* sendto blocks if the network has gone away */
if (sendto(fd_solo, pkt, res, MSG_DONTWAIT, (struct sockaddr *)&sa_solo,
sizeof(sa_solo)) != res) {
int skipped;
if ((skipped = can_log_error(now_us)) >= 0)
syslog(LOG_ERR, "[%u] sendto %s:%d: %s", skipped,
inet_ntoa(sa_solo.sin_addr), ntohs(sa_solo.sin_port),
strerror(errno));
}
}
}
} // for (int i...)
mutex_unlock(&dest_table_mutex);
/* check for time to log */
if (now_us >= log_time_us) {
char msg[200]; /* usually use about 40 characters */
char *m = msg;
int n, r, s;
memset(msg, 0, sizeof(msg));
r = sizeof(msg) - 1; /* never overwrite that last '\0' */
/* In the following, snprintf will never write more than 'r'
* characters, so if we maintain that correctly and just let it
* go non-positive if we overflow, then this won't overrun. The
* return from snprint when it runs out of room is >= 'r', so 'r'
* goes zero or negative if we run out of room. */
n = snprintf(m, r, "dn:%d", pkts_down_total);
m += n;
r -= n;
for (s = 0; s < num_sources; s++) {
if (r > 0) {
n = snprintf(m, r, " (%d,%d)%d/%d", sources[s].sys_id, sources[s].comp_id,
sources[s].pkt_cnt, sources[s].err_cnt);
m += n;
r -= n;
}
}
if (r > 0) {
n = snprintf(m, r, " up:%d", pkts_up_total);
m += n;
r -= n;
}
#if 0
/* timeout count is only useful for tuning the timeout value */
if (r > 0)
{
n = snprintf(m, r, " to:%d", select_timeout);
m += n;
r -= n;
}
#endif
syslog(LOG_INFO, msg);
sources_clear();
pkts_down_total = 0;
pkts_up_total = 0;
select_timeout = 0;
log_time_us += log_interval_us;
}
} /* while (1) */
return NULL;
} /* tlm_main */
static int start_tlm_main(void)
{
int r;
if ((r = pthread_create(&tlm_ctx, NULL, tlm_main, NULL)) != 0) {
syslog(LOG_ERR, "start_tlm_main: pthread_create returned %d", r);
return 1;
}
if ((r = pthread_setname_np(tlm_ctx, "tlm_main")) != 0) {
syslog(LOG_ERR, "start_tlm_main: pthread_setname_np returned %d", r);
return 1;
}
struct sched_param sp;
memset(&sp, 0, sizeof(sp));
sp.sched_priority = TC_MAIN_PRIORITY;
if ((r = pthread_setschedparam(tlm_ctx, SCHED_FIFO, &sp)) != 0) {
syslog(LOG_ERR, "start_tlm_main: pthread_setschedparam returned %d", r);
return 1;
}
return 0;
} /* start_tlm_main */
int main(int argc, char *argv[])
{
//
// initialize logging
//
openlog("tlm", LOG_NDELAY, LOG_LOCAL1);
setlogmask(LOG_UPTO(LOG_INFO));
syslog(LOG_INFO, "telem_ctrl starting: built " __DATE__ " " __TIME__);
//
// read configuration
//
INIReader reader("/etc/sololink.conf");
if (reader.ParseError() < 0) {
syslog(LOG_CRIT, "can't parse /etc/sololink.conf");
exit(1);
}
std::string s1 = reader.Get("solo", "mavDestIp", "127.0.0.1");
const char *stm32_ip = s1.c_str();
short stm32_port_hbo = reader.GetInteger("solo", "mavDestPort", 5015);
syslog(LOG_INFO, "stm32 @ %s:%d", stm32_ip, stm32_port_hbo);
std::string s2 = reader.Get("solo", "tlogDestIp", "127.0.0.1");
const char *tlog_ip = s2.c_str();
short tlog_port_hbo = reader.GetInteger("solo", "tlogDestPort", 14583);
syslog(LOG_INFO, "tlog @ %s:%d", tlog_ip, tlog_port_hbo);
telem_dest_port_hbo = reader.GetInteger("solo", "telemDestPort", 14550);
syslog(LOG_INFO, "telem_dest_port = %d", telem_dest_port_hbo);
use_gps_time = reader.GetBoolean("solo", "useGpsTime", true) ? 1 : 0;
syslog(LOG_INFO, "use_gps_time = %d", use_gps_time);
uplink_tos = reader.GetInteger("solo", "telemUpTos", IP_TOS_DEFAULT);
syslog(LOG_INFO, "uplink_tos = 0x%02x", uplink_tos);
gcs_tos = reader.GetInteger("solo", "telemGcsTos", IP_TOS_DEFAULT);
syslog(LOG_INFO, "gcs_tos = 0x%02x", gcs_tos);
// Stations where downlink packets will be forwarded. Solo sends all
// mavlink to this process, and this process figures out who needs them
// (stm32, tlog process, app, gcs, etc.).
dest_table.init();
mutex_init(&dest_table_mutex);
// add internal destinations (stm32, tlog)
mutex_lock(&dest_table_mutex, NULL);
dest_table.add(NULL, inet_addr(stm32_ip), stm32_port_hbo, 0, TF_LINK_PACKET);
dest_table.add(NULL, inet_addr(tlog_ip), tlog_port_hbo);
mutex_unlock(&dest_table_mutex);
// Solo destination socket address - the address in Solo where uplink
// packets will be sent. Fill in the family now, fill in the ip address
// from the solo ip address file (below), then finally fill in the port
// when we get any downlink (since port number on solo is dynamic).
memset(&sa_solo, 0, sizeof(sa_solo));
sa_solo.sin_family = AF_INET;
// This does not return until Solo is there.
wait_solo(&sa_solo.sin_addr.s_addr);
//
// start telemetry thread
//
if (start_tlm_main() != 0) {
syslog(LOG_ERR, "telem_ctrl: error starting tlm_main");
exit(1);
}
//
// periodically scan for telemetry consumers
//
// hostapd connection. This is used to query hostapd for a list of all
// associated telemetry destinations. (Underneath, it is a unix-domain
// socket that talks to the hostapd process.)
void *hostapd_handle = hostapd_ctrl_new("wlan0-ap");
if (hostapd_handle == NULL) {
syslog(LOG_CRIT, "can't create connection to hostapd");
exit(1);
}
while (true) {
update_telem_dest(hostapd_handle);
sleep(telem_dest_update_interval_s);
} // while (true)
} // main