/* * meshcore_info.c * * One-shot: open a tty, interrogate the attached MeshCore Companion Radio for * everything it will tell us -- model/firmware/version, radio parameters, * device time, every configured channel (name + PSK), and runtime stats -- then * print it and exit. Read-only; sends only query commands. * * build: via CMakeLists.txt at the repo root, or: * cc -std=c99 -Wall -Wextra -I../../src \ * meshcore_info.c ../../src/meshcore_companion.c -o meshcore_info * * run: ./meshcore_info /dev/ttyACM0 * * SPDX-License-Identifier: MIT * Author: Scott Penrose / Digital Dimensions. */ #define _DEFAULT_SOURCE /* cfmakeraw, B115200, clock_gettime on glibc/c99 */ #include #include #include #include #include #include #include #include #include #include "meshcore_companion.h" #define MAX_CHANNELS 64 /* ------------------------------------------------------------- collected state */ typedef struct { int have_device, have_self, have_time; mc_device_info_t dev; mc_self_info_t self; uint32_t epoch; int nchan; /* channels we queried */ int chan_got[MAX_CHANNELS]; mc_channel_info_t chan[MAX_CHANNELS]; int have_core, have_radio, have_packets; mc_stats_t core, radio, packets; } info_t; /* ------------------------------------------------------------------ tty + I/O */ static int tty_open(const char *path) { int fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd < 0) { fprintf(stderr, "open %s: %s\n", path, strerror(errno)); return -1; } struct termios tio; if (tcgetattr(fd, &tio) != 0) { fprintf(stderr, "tcgetattr: %s\n", strerror(errno)); close(fd); return -1; } cfmakeraw(&tio); cfsetispeed(&tio, B115200); cfsetospeed(&tio, B115200); tio.c_cflag |= (CLOCAL | CREAD); tio.c_cc[VMIN] = 0; tio.c_cc[VTIME] = 0; if (tcsetattr(fd, TCSANOW, &tio) != 0) { fprintf(stderr, "tcsetattr: %s\n", strerror(errno)); close(fd); return -1; } tcflush(fd, TCIOFLUSH); return fd; } static void send_payload(int fd, const uint8_t *payload, size_t len) { uint8_t frame[MC_RX_BUFSZ]; size_t flen = mc_frame_encode(payload, len, frame, sizeof frame); size_t off = 0; while (off < flen) { ssize_t w = write(fd, frame + off, flen - off); if (w < 0) { if (errno == EAGAIN || errno == EINTR) continue; break; } off += (size_t)w; } } static long now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L; } /* ------------------------------------------------------------ event collection */ static void handle(info_t *c, const mc_event_t *ev) { switch (ev->code) { case MC_RESP_DEVICE_INFO: c->dev = ev->u.device_info; c->have_device = 1; break; case MC_RESP_SELF_INFO: c->self = ev->u.self_info; c->have_self = 1; break; case MC_RESP_CURR_TIME: c->epoch = ev->u.curr_time; c->have_time = 1; break; case MC_RESP_CHANNEL_INFO: { uint8_t i = ev->u.channel_info.channel_idx; if (i < MAX_CHANNELS) { c->chan[i] = ev->u.channel_info; c->chan_got[i] = 1; } break; } case MC_RESP_STATS: if (ev->u.stats.subtype == MC_STATS_CORE) { c->core = ev->u.stats; c->have_core = 1; } else if (ev->u.stats.subtype == MC_STATS_RADIO) { c->radio = ev->u.stats; c->have_radio = 1; } else if (ev->u.stats.subtype == MC_STATS_PACKETS) { c->packets = ev->u.stats; c->have_packets = 1; } break; default: break; } } /* Pump the tty until `done(c)` is satisfied or `cap_ms` elapses. */ static void pump(int fd, mc_rx_t *rx, info_t *c, int (*done)(const info_t *), long cap_ms) { long deadline = now_ms() + cap_ms; for (;;) { if (done && done(c)) return; long rem = deadline - now_ms(); if (rem <= 0) return; fd_set rf; FD_ZERO(&rf); FD_SET(fd, &rf); struct timeval tv = { rem / 1000, (rem % 1000) * 1000 }; int r = select(fd + 1, &rf, NULL, NULL, &tv); if (r < 0) { if (errno == EINTR) continue; return; } if (r == 0) return; /* timed out */ uint8_t in[256]; ssize_t got = read(fd, in, sizeof in); if (got <= 0) { if (got < 0 && (errno == EAGAIN || errno == EINTR)) continue; return; } mc_rx_feed(rx, in, (size_t)got); uint8_t pl[MC_MAX_PAYLOAD]; size_t pn; while (mc_rx_poll(rx, pl, sizeof pl, &pn)) { mc_event_t ev; if (mc_parse(pl, pn, &ev)) handle(c, &ev); } } } static int identity_ready(const info_t *c) { return c->have_device && c->have_self && c->have_time; } static int details_ready(const info_t *c) { for (int i = 0; i < c->nchan; i++) if (!c->chan_got[i]) return 0; return c->have_core && c->have_radio && c->have_packets; } /* --------------------------------------------------------------------- output */ static void print_report(const info_t *c) { if (!c->have_device && !c->have_self) { printf("No response from the radio. Is it the serial companion firmware,\n" "bound to this port at 115200 8N1?\n"); return; } if (c->have_device) { const mc_device_info_t *d = &c->dev; printf("Device\n"); printf(" model : %s\n", d->model); printf(" firmware ver : %d\n", d->fw_ver); if (d->ver[0]) printf(" version : %s\n", d->ver); if (d->build_date[0]) printf(" build date : %s\n", d->build_date); printf(" max channels : %u\n", d->max_channels); printf(" max contacts : %u\n", d->max_contacts); printf(" BLE pin : %u\n", d->ble_pin); if (d->have_repeat) printf(" repeater mode : %s\n", d->repeat ? "yes" : "no"); if (d->have_path_hash) printf(" path hash mode: %u\n", d->path_hash_mode); } if (c->have_self) { const mc_self_info_t *s = &c->self; printf("\nRadio / identity\n"); printf(" name : %s\n", s->name); printf(" public key : "); for (int i = 0; i < 8; i++) printf("%02x", s->public_key[i]); printf("...\n"); printf(" frequency : %.3f MHz\n", s->radio_freq / 1000.0); printf(" bandwidth : %.1f kHz\n", s->radio_bw / 1000.0); printf(" spreading : SF%u\n", s->radio_sf); printf(" coding rate : 4/%u\n", s->radio_cr); printf(" tx power : %u dBm (max %u)\n", s->tx_power, s->max_tx_power); if (s->adv_lat || s->adv_lon) printf(" advert loc : %.6f, %.6f\n", s->adv_lat / 1e6, s->adv_lon / 1e6); printf(" telemetry mode: base=%u loc=%u env=%u\n", s->tm_base, s->tm_loc, s->tm_env); printf(" multi acks : %u\n", s->multi_acks); } if (c->have_time) { time_t t = (time_t)c->epoch; struct tm tmv; char buf[32] = ""; if (gmtime_r(&t, &tmv)) strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S UTC", &tmv); printf("\nClock\n device time : %u (%s)\n", c->epoch, buf); } if (c->nchan > 0) { printf("\nChannels (%d)\n", c->nchan); for (int i = 0; i < c->nchan; i++) { if (!c->chan_got[i]) { printf(" [%2d] (no response)\n", i); continue; } const mc_channel_info_t *ch = &c->chan[i]; int configured = ch->name[0] != 0; if (configured) { // can print unconfigured printf(" [%2d] %-16s psk=", i, configured ? ch->name : "(unconfigured)"); if (ch->have_secret) { for (int k = 0; k < MC_SECRET_LEN; k++) printf("%02x", ch->secret[k]); } else printf("(none)"); printf("\n"); } } } if (c->have_core || c->have_radio || c->have_packets) { printf("\nStats\n"); if (c->have_core) printf(" battery : %u mV uptime %u s errors %u queue %u\n", c->core.u.core.battery_mv, c->core.u.core.uptime_secs, c->core.u.core.errors, c->core.u.core.queue_len); if (c->have_radio) printf(" radio : noise %d last RSSI %d last SNR %.1f dB air tx %u s rx %u s\n", c->radio.u.radio.noise_floor, c->radio.u.radio.last_rssi, (double)MC_SNR_DB(c->radio.u.radio.last_snr_q4), c->radio.u.radio.tx_air_secs, c->radio.u.radio.rx_air_secs); if (c->have_packets) { const mc_stats_t *p = &c->packets; printf(" packets : recv %u sent %u flood tx/rx %u/%u direct tx/rx %u/%u", p->u.packets.recv, p->u.packets.sent, p->u.packets.flood_tx, p->u.packets.flood_rx, p->u.packets.direct_tx, p->u.packets.direct_rx); if (p->has_recv_errors) printf(" recv errors %u", p->u.packets.recv_errors); printf("\n"); } } } /* ----------------------------------------------------------------------- main */ int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s e.g. /dev/ttyACM0, /dev/ttyUSB0\n", argv[0]); return 2; } int fd = tty_open(argv[1]); if (fd < 0) return 1; info_t c; memset(&c, 0, sizeof c); mc_rx_t rx; mc_rx_init(&rx); uint8_t cmd[MC_MAX_PAYLOAD]; size_t n; /* Identity: AppStart -> SelfInfo, DeviceQuery -> DeviceInfo, plus time. */ n = mc_cmd_app_start(cmd, sizeof cmd, "meshcore_info"); if (n) send_payload(fd, cmd, n); n = mc_cmd_device_query(cmd, sizeof cmd, 1); if (n) send_payload(fd, cmd, n); n = mc_cmd_get_device_time(cmd, sizeof cmd); if (n) send_payload(fd, cmd, n); pump(fd, &rx, &c, identity_ready, 2000); /* Now that we know the channel count, enumerate channels + ask for stats. */ if (c.have_device) { c.nchan = c.dev.max_channels; if (c.nchan > MAX_CHANNELS) c.nchan = MAX_CHANNELS; for (int i = 0; i < c.nchan; i++) { n = mc_cmd_get_channel(cmd, sizeof cmd, (uint8_t)i); if (n) send_payload(fd, cmd, n); } } n = mc_cmd_get_stats(cmd, sizeof cmd, MC_STATS_CORE); if (n) send_payload(fd, cmd, n); n = mc_cmd_get_stats(cmd, sizeof cmd, MC_STATS_RADIO); if (n) send_payload(fd, cmd, n); n = mc_cmd_get_stats(cmd, sizeof cmd, MC_STATS_PACKETS); if (n) send_payload(fd, cmd, n); pump(fd, &rx, &c, details_ready, 3000); print_report(&c); close(fd); return c.have_device || c.have_self ? 0 : 1; }