713 lines
20 KiB
C++

#include <fcntl.h>
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "mavlink.h"
#include "util.h"
#if (defined __MACH__ && defined __APPLE__)
#define B460800 460800
#define B921600 921600
#endif
// mavpkt
//
// Receive byte stream and output complete mavlink packets
//
// Input can be any file descriptor (i.e. stdin, file, serial port).
// Output is normally a datagram-ish file descriptor.
static void usage(void)
{
fprintf(stderr, "mavpkt\n");
fprintf(stderr, " input defaults to stdin\n");
fprintf(stderr, " -i tlog input is tlog format (default no)\n");
fprintf(stderr, " -i udp://0.0.0.0:7007 input from the specified UDP port\n");
fprintf(stderr, " output defaults to stdout\n");
fprintf(stderr, " -o tlog output is tlog format (default no)\n");
fprintf(stderr, " -o udp://127.0.0.1:6006 output to the specified UDP port\n");
fprintf(stderr, " -r realtime (requires tlog input)\n");
fprintf(stderr, " -s print stats to stderr at end\n");
fprintf(stderr, " -v print input packets to stderr\n");
exit(1);
}
// 'tlog' format means each mavlink packet is preceeded by the time since the
// epoch in microseconds as a 64-bit big-endian unsigned.
// 'realtime' means packets will be emitted in approximately real time,
// according to the timestamps in the input (which must be tlog). Allows
// "playing back" a tlog.
// -i udp://0.0.0.0:7007 means mavpkt will read packets from this UDP port.
// This example means the read socket will be bound to INADDR_ANY, port 7007,
// so an external program should send packets to that port on this machine.
// -o udp://127.0.0.1:6006 means mavpkt will send to this UDP port. This
// example means an ouput socket will be created and bound to a system-
// supplied port, and will send packets to localhost, port 6006.
// -i serial:///dev/ttymxc1?baud=115200,flow=1 reads from the given serial
// port with a specified baud rate. flow=1 enables hardware flow control.
// Examples:
//
// Read raw packets from a port and write a tlog:
// mavpkt -i udp://0.0.0.0:9000 -o tlog > packets.tlog
//
// Read raw packets from a port and write a raw data file:
// mavpkt -i udp://0.0.0.0:9000 > packets.bin
//
// Play a tlog to UDP port 8800 in real time:
// mavpkt -i tlog -o udp://127.0.0.1:8800 -r < packets.tlog
//
// Show stats for a tlog file:
// mavpkt -i tlog -s < packets.tlog > /dev/null
// MAVLINK_MESSAGE_INFO has 256 elements; sizeof(mavlink_info)=528384
static mavlink_message_info_t mavlink_info[] = MAVLINK_MESSAGE_INFO;
// MAVLINK_MESSAGE_CRCS is an array of 256 bytes
static uint8_t mavlink_crc[] = MAVLINK_MESSAGE_CRCS;
// A mavlink packet is limited to 6+255+2 = 263 bytes
// 6 byte header, 255 max bytes in payload, 2 byte crc
#ifndef MAVLINK_STX
#define MAVLINK_STX 254
#endif
// input stream bytes to read at once
#define READ_MAX 1024
static uint8_t data_in[READ_MAX];
// unpack_uint64 - unpack uint64_t from byte stream
static uint64_t unpack_uint64(const uint8_t* p)
{
uint64_t d = 0;
for (int i = 0; i < 8; i++)
d = (d << 8) | *p++;
return d;
}
// pack_uint64 - pack uint64_t into byte stream
static void pack_uint64(uint64_t d, uint8_t* p)
{
for (int i = 56; i >= 0; i -= 8)
*p++ = d >> i;
}
// calc_crc - calculate the CRC of a mavlink message
//
// CRC is seeded, then starts with the byte after the magic, continues through
// the payload up to the CRC in the message, then add the message-id-specific
// byte.
//
// Returns the 16-bit CRC.
static uint16_t calc_crc(const uint8_t* p)
{
uint8_t payload_len = p[1];
uint8_t msg_id = p[5];
unsigned crc_len = 5 + payload_len;
uint16_t crc = crc_calculate(p + 1, crc_len);
crc_accumulate(mavlink_crc[msg_id], &crc);
return crc;
} // calc_crc
// get_crc - get (extract) the CRC of a mavlink message
//
// Returns the 16-bit CRC.
static uint16_t get_crc(const uint8_t* p)
{
uint8_t payload_len = p[1];
uint16_t crc = p[6 + payload_len + 1]; // msb
crc <<= 8;
crc |= p[6 + payload_len]; // lsb
return crc;
} // get_crc
// check_crc - check the crc of a mavlink message
//
// Returns true if CRC correct, else false.
static bool check_crc(const uint8_t* p)
{
return calc_crc(p) == get_crc(p);
}
// When accumulating statistics, we keep track of messages received from each
// (system, comeonent). Rather than allowing for all combinations (65536 of
// them) we just keep track of the first SOURCES_MAX we see. If there are more
// than that, the stats will be wrong. Dynamic allocation might be better, but
// really if there are more than a handful, the input data is probably junk.
#define SOURCES_MAX 10
static struct
{
unsigned total;
unsigned crc_err;
unsigned bytes_discarded;
uint64_t first_us;
uint64_t last_us;
unsigned num_sources;
struct
{
unsigned total;
unsigned crc_err;
} by_id[256];
struct
{
uint8_t in_use;
uint8_t sys;
uint8_t comp;
unsigned total;
unsigned crc_err;
struct
{
unsigned total;
unsigned crc_err;
} by_id[256];
} by_src[SOURCES_MAX];
} pkt_stats;
static void pkt_stats_init(void)
{
memset(&pkt_stats, 0, sizeof(pkt_stats));
}
// pkt_stats_add - add packet to statistics
static void pkt_stats_add(uint8_t* p)
{
// 0..7 timestamp
// 8 magic
// 9 length
// 10 sequence
uint64_t pkt_us = unpack_uint64(p);
uint8_t sys = p[11];
uint8_t comp = p[12];
uint8_t id = p[13];
// 1 if crc bad, 0 if crc good
unsigned crc_err = check_crc(p + 8) ? 0 : 1;
pkt_stats.total++;
pkt_stats.crc_err += crc_err;
pkt_stats.by_id[id].total++;
pkt_stats.by_id[id].crc_err += crc_err;
if (pkt_stats.first_us == 0)
pkt_stats.first_us = pkt_us;
pkt_stats.last_us = pkt_us;
for (int s = 0; s < SOURCES_MAX; s++)
{
if (!pkt_stats.by_src[s].in_use)
{
pkt_stats.by_src[s].in_use = true;
pkt_stats.by_src[s].sys = sys;
pkt_stats.by_src[s].comp = comp;
pkt_stats.num_sources++;
}
if (pkt_stats.by_src[s].sys == sys &&
pkt_stats.by_src[s].comp == comp)
{
pkt_stats.by_src[s].total++;
pkt_stats.by_src[s].crc_err += crc_err;
pkt_stats.by_src[s].by_id[id].total++;
pkt_stats.by_src[s].by_id[id].crc_err += crc_err;
return;
}
}
pkt_stats.num_sources++;
} // pkt_stats_add
// pkt_stats_print - print accumulated statistics to stderr
static void pkt_stats_print(void)
{
fprintf(stderr, "\n");
fprintf(stderr, "packets: %u\n", pkt_stats.total);
fprintf(stderr, "crc errors: %u\n", pkt_stats.crc_err);
fprintf(stderr, "bytes discarded: %u\n", pkt_stats.bytes_discarded);
char ts_buf[32];
fprintf(stderr, "first packet: %s\n", clock_tostr_r(pkt_stats.first_us, ts_buf));
fprintf(stderr, "last packet: %s\n", clock_tostr_r(pkt_stats.last_us, ts_buf));
fprintf(stderr, "sources: %u\n", pkt_stats.num_sources);
uint64_t span_us = pkt_stats.last_us - pkt_stats.first_us;
uint64_t us_per_s = 1000000;
uint64_t us_per_m = 60 * us_per_s;
uint64_t us_per_h = 60 * us_per_m;
unsigned span_h = span_us / us_per_h;
span_us -= (span_h * us_per_h);
unsigned span_m = span_us / us_per_m;
span_us -= (span_m * us_per_m);
unsigned span_s = span_us / us_per_s;
span_us -= (span_s * us_per_s);
unsigned span_ms = span_us / 1000;
fprintf(stderr, "time span: %02u:%02u:%02u.%03u\n", span_h, span_m, span_s, span_ms);
fprintf(stderr, "\n");
// table heading, e.g.
// " id 0,0 1,1 10,0 1,154 total"
fprintf(stderr, " id");
for (int s = 0; s < SOURCES_MAX && pkt_stats.by_src[s].in_use; s++)
fprintf(stderr, " %5u,%-5u", pkt_stats.by_src[s].sys, pkt_stats.by_src[s].comp);
fprintf(stderr, " total\n");
// print row for each id we got any packets for
for (int id = 0; id < 256; id++)
{
if (pkt_stats.by_id[id].total == 0)
continue;
// table row, e.g.
// " 0 -/- 22/0 -/- -/- 22/0 HEARTBEAT"
fprintf(stderr, "%3d", id);
for (int s = 0; s < SOURCES_MAX && pkt_stats.by_src[s].in_use; s++)
{
if (pkt_stats.by_src[s].by_id[id].total > 0)
fprintf(stderr, " %5u/%-5u", pkt_stats.by_src[s].by_id[id].total,
pkt_stats.by_src[s].by_id[id].crc_err);
else
fprintf(stderr, " %5s/%-5s", "-", "-");
}
fprintf(stderr, " %5d/%-5d", pkt_stats.by_id[id].total, pkt_stats.by_id[id].crc_err);
fprintf(stderr, " %-s\n", mavlink_info[id].name);
}
} // pkt_stats_print
// udp_uri - parse uri for udp address and create socket for it
//
// is_dest determines whether the parsed address is where we send packets
// (is_dest true), or it is our local address, which we will bind to and read
// packets from (is_dest false).
//
// is_dest true: create a socket and connect it to the parsed address
// is_dest false: create a socket and bind it to the parsed address
//
// Note that for UDP sockets, 'connect' to an address saves the address as the
// destination to use in future send() calls.
//
// Returns: fd of socket on success, or -1 on error
static int udp_uri(const char* uri, bool is_dest)
{
if (strncmp(uri, "udp:", 4) != 0)
return -1;
unsigned int ip1, ip2, ip3, ip4, port;
if (sscanf(uri, "udp://%u.%u.%u.%u:%u", &ip1, &ip2, &ip3, &ip4, &port) != 5 ||
ip1 > 255 || ip2 > 255 || ip3 > 255 || ip4 > 255 || port > 65535)
{
fprintf(stderr, "error parsing \"%s\"\n", uri);
return -1;
}
uint32_t ip = (ip1 << 24) | (ip2 << 16) | (ip3 << 8) | ip4;
struct sockaddr_in sa_uri;
memset(&sa_uri, 0, sizeof(sa_uri));
sa_uri.sin_family = AF_INET;
sa_uri.sin_addr.s_addr = htonl(ip);
sa_uri.sin_port = htons(port);
struct sockaddr_in sa_any;
memset(&sa_any, 0, sizeof(sa_any));
sa_any.sin_family = AF_INET;
sa_any.sin_addr.s_addr = htonl(INADDR_ANY);
sa_any.sin_port = htons(0);
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
{
perror("socket");
return -1;
}
// is_dest = false means the uri is the address of this socket
// is_dest = true means the uri is where we send packets
if (is_dest)
{
if (bind(fd, (struct sockaddr*)&sa_any, sizeof(sa_any)) != 0)
{
perror("bind");
close(fd);
return -1;
}
// connect sets the default destination address
if (connect(fd, (struct sockaddr*)&sa_uri, sizeof(sa_uri)) != 0)
{
perror("connect");
close(fd);
return -1;
}
}
else
{
// just bind to specified uri
if (bind(fd, (struct sockaddr*)&sa_uri, sizeof(sa_uri)) != 0)
{
perror("bind");
close(fd);
return -1;
}
}
return fd;
} // udp_uri
// serial_uri - parse uri for serial port and create fd for it
//
// Returns: fd of port on success, or -1 on error
static int serial_uri(const char* uri)
{
if (strncmp(uri, "serial:", 7) != 0)
return -1;
char dev_opts[80];
if (sscanf(uri, "serial://%s", dev_opts) != 1)
{
fprintf(stderr, "error parsing \"%s\"\n", uri);
return -1;
}
// /dev/ttyS0?baud=115200,flow=1
char *str = dev_opts;
char *dev_name = strsep(&str, "?");
unsigned int baud = 0;
unsigned int flow = 0; // 0=none, 1=hw (2=sw TBD)
while (str != NULL && *str != '\0')
{
char *opt = strsep(&str, ",");
if (sscanf(opt, "baud=%u", &baud) == 1)
continue;
else if (sscanf(opt, "flow=%u", &flow) == 1)
continue;
else
{
fprintf(stderr, "error parsing \"%s\"\n", uri);
return -1;
}
}
int fd = open(dev_name, O_RDWR | O_NOCTTY);
if (fd < 0)
{
perror("open");
return -1;
}
struct termios options;
memset(&options, 0, sizeof(options));
tcgetattr(fd, &options);
switch (baud)
{
case 57600: cfsetspeed(&options, B57600); break;
case 115200: cfsetspeed(&options, B115200); break;
case 230400: cfsetspeed(&options, B230400); break;
case 460800: cfsetspeed(&options, B460800); break;
case 921600: cfsetspeed(&options, B921600); break;
default:
fprintf(stderr, "unsupported baudrate %u", baud);
return -1;
}
options.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ISTRIP | IXON);
options.c_oflag &= ~(OCRNL | ONLCR | ONLRET | ONOCR | OFILL | OPOST);
options.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
options.c_cflag &= ~(CSIZE | PARENB);
options.c_cflag |= CS8;
options.c_cc[VMIN] = 20;
options.c_cc[VTIME] = 0;
if (flow == 1)
{
options.c_cflag |= CRTSCTS;
}
else
{
options.c_cflag &= ~CRTSCTS;
options.c_cflag |= CLOCAL;
}
tcflush(fd, TCIFLUSH);
tcsetattr(fd, TCSANOW, &options);
return fd;
} // serial_uri
// print_pkt - print packet to stderr
static void print_pkt(const uint8_t* p, bool tlog)
{
uint64_t ts = unpack_uint64(p);
char ts_buf[32];
fprintf(stderr, "%s ", clock_tostr_r(ts, ts_buf));
p += 8;
unsigned num_bytes = 6 + p[1] + 2;
for (unsigned i = 0; i < (num_bytes - 1); i++)
fprintf(stderr, "%02x ", *p++);
fprintf(stderr, "%02x\n", *p);
} // print_pkt
// emit_packet - emit packet to file descriptor
//
// Packet can be emitted raw (just a write) or it can be emitted to a tlog
// (preceeded by timestamp).
//
// pkt supplied always has 8 bytes at the start for the timestamp. If not
// tlog, they are simply skipped.
static int emit_packet(int fd, const uint8_t* pkt, bool tlog, bool verbose)
{
if (verbose)
print_pkt(pkt, tlog);
int bytes;
if (tlog)
bytes = 8 + 6 + pkt[9] + 2;
else
{
bytes = 6 + pkt[9] + 2;
pkt += 8;
}
if (write(fd, pkt, bytes) != bytes)
return -1;
return bytes;
} // emit_packet
int main(int argc, char* argv[])
{
int fd;
int fd_in = STDIN_FILENO;
int fd_out = STDOUT_FILENO;
bool tlog_in = false;
bool tlog_out = false;
unsigned data_in_bytes;
bool verbose = false;
bool realtime = false;
bool stats = false;
uint64_t rt_first_us = 0; // real time first packet emitted
int opt;
//fprintf(stderr, "sizeof(mavlink_info) = %ld\n", sizeof(mavlink_info));
//fprintf(stderr, "nelems(mavlink_info) = %ld\n", sizeof(mavlink_info)/sizeof(mavlink_info[0]));
//
// Parse arguments
//
while ((opt = getopt(argc, argv, "hi:o:rsv")) != -1)
{
switch (opt)
{
case 'h':
usage();
break;
case 'i':
if (strcmp(optarg, "tlog") == 0)
tlog_in = true;
else if ((fd = udp_uri(optarg, false)) != -1)
fd_in = fd;
else if ((fd = serial_uri(optarg)) != -1)
fd_in = fd;
else
usage();
break;
case 'o':
if (strcmp(optarg, "tlog") == 0)
tlog_out = true;
else if ((fd = udp_uri(optarg, true)) != -1)
fd_out = fd;
else
usage();
break;
case 'r':
realtime = true;
break;
case 's':
stats = true;
break;
case 'v':
verbose = true;
break;
default:
usage();
}
}
//
// Initialize data structures
//
pkt_stats_init();
memset(data_in, 0, sizeof(data_in));
data_in_bytes = 0;
// If the input data does not have a timestamp, leave space at the start
// to insert a timestamp in the output packet
if (!tlog_in)
data_in_bytes = 8;
//
// Process input bytes, writing output packets
//
while (1)
{
unsigned read_max = READ_MAX - data_in_bytes;
int num_read = read(fd_in, data_in + data_in_bytes, read_max);
if (num_read == 0)
{
// eof
break;
// XXX Needs fixing for serial input
}
if (num_read < 0)
{
// error
perror("read");
break;
}
data_in_bytes += num_read;
// Outer loop is in case we have more than one packet
bool emitted;
do
{
emitted = false;
// Throw away data from the start until there's magic in the right
// place. This loop should almost never have to execute.
while (data_in_bytes > 8 && data_in[8] != MAVLINK_STX)
{
memmove(data_in, data_in + 1, --data_in_bytes);
pkt_stats.bytes_discarded++;
}
// emit packet if we have one
if (data_in_bytes > 9 && data_in_bytes >= (unsigned)(8 + 6 + data_in[9] + 2))
{
uint64_t now_us = clock_gettime_us(CLOCK_REALTIME);
uint64_t ts_pkt_us; // timestamp of this packet
if (tlog_in)
{
// get timestamp from packet
ts_pkt_us = unpack_uint64(data_in);
}
else
{
// use timestamp from clock; save it in packet
ts_pkt_us = now_us;
pack_uint64(ts_pkt_us, data_in);
}
pkt_stats_add(data_in);
// if simulating realtime, delay until it is time for this packet to go
if (tlog_in && realtime)
{
if (rt_first_us == 0)
{
// real time first packet emitted
rt_first_us = now_us;
}
else
{
// delay until time for this packet to go
int64_t delay_us = (ts_pkt_us - pkt_stats.first_us) - (now_us - rt_first_us);
if (delay_us > 0)
usleep(delay_us);
}
}
emit_packet(fd_out, data_in, tlog_out, verbose);
// If the input is not tlog, we still leave space at front for
// timestamp, making it so we can stuff in the timestamp and
// write the output without needing a separate output buffer.
if (tlog_in)
{
// consume timestamp; copy data to very start of buffer
unsigned consume_bytes = 8 + 6 + data_in[9] + 2;
data_in_bytes -= consume_bytes;
memmove(data_in, data_in + consume_bytes, data_in_bytes);
}
else
{
// don't consume timestamp; copy data to right after timestamp
unsigned consume_bytes = 6 + data_in[9] + 2;
data_in_bytes -= consume_bytes;
memmove(data_in + 8, data_in + 8 + consume_bytes, data_in_bytes);
}
// This flag makes it so we'll check the input buffer again,
// in case we got more than one packet in the last read
// (which is common).
emitted = true;
}
}
while (emitted);
} // while (1)
//
// Print statistics if requested
//
if (stats)
pkt_stats_print();
exit(0);
} // main