Files
2026-06-08 13:37:27 +10:00

128 lines
4.4 KiB
C

/*
* meshcore_contacts.c
*
* One-shot: open a tty, ask the companion radio for its contact list, print
* each contact (name, key prefix, type, path, location, last-seen), then exit.
* Demonstrates the Phase 2 contact API: GET_CONTACTS -> CONTACTS_START, a stream
* of CONTACT records, then END_OF_CONTACTS.
*
* build: via CMakeLists.txt at the repo root, or:
* cc -std=c99 -Wall -Wextra -I../../src \
* meshcore_contacts.c ../../src/meshcore_companion.c -o meshcore_contacts
*
* run: ./meshcore_contacts /dev/ttyACM0
*
* SPDX-License-Identifier: MIT
* Author: Scott Penrose / Digital Dimensions.
*/
#define _DEFAULT_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include "meshcore_companion.h"
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) { 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) { 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), 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;
}
static int g_expected = -1; /* CONTACTS_START count, or -1 if not seen */
static int g_got = 0;
static int g_done = 0;
static void print_contact(const mc_contact_t *c) {
printf(" %-20s ", c->adv_name[0] ? c->adv_name : "(unnamed)");
for (int i = 0; i < 6; i++) printf("%02x", c->public_key[i]);
printf(" type=%u", c->type);
if (c->out_path_len == 0xFF) printf(" path=direct");
else printf(" path=%u hop(s)", c->out_path_len);
if (c->adv_lat || c->adv_lon)
printf(" loc=%.5f,%.5f", c->adv_lat / 1e6, c->adv_lon / 1e6);
printf("\n");
}
static void handle(const mc_event_t *ev) {
switch (ev->code) {
case MC_RESP_CONTACTS_START:
g_expected = (int)ev->u.contacts_count;
printf("Contacts (%d)\n", g_expected);
break;
case MC_RESP_CONTACT:
g_got++;
print_contact(&ev->u.contact);
break;
case MC_RESP_END_OF_CONTACTS:
g_done = 1;
break;
default: break;
}
}
int main(int argc, char **argv) {
if (argc < 2) { fprintf(stderr, "usage: %s <tty>\n", argv[0]); return 2; }
int fd = tty_open(argv[1]);
if (fd < 0) return 1;
mc_rx_t rx; mc_rx_init(&rx);
uint8_t cmd[MC_MAX_PAYLOAD]; size_t n;
n = mc_cmd_app_start(cmd, sizeof cmd, "meshcore_contacts"); 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_contacts(cmd, sizeof cmd, 0); if (n) send_payload(fd, cmd, n);
long deadline = now_ms() + 4000;
while (!g_done && now_ms() < deadline) {
fd_set rf; FD_ZERO(&rf); FD_SET(fd, &rf);
long rem = deadline - now_ms();
struct timeval tv = { rem / 1000, (rem % 1000) * 1000 };
int r = select(fd + 1, &rf, NULL, NULL, &tv);
if (r < 0) { if (errno == EINTR) continue; break; }
if (r == 0) break;
uint8_t in[256];
ssize_t got = read(fd, in, sizeof in);
if (got <= 0) { if (got < 0 && (errno == EAGAIN || errno == EINTR)) continue; break; }
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(&ev);
}
}
if (g_expected < 0) printf("No contact list received (is the radio connected?).\n");
else if (!g_done) printf("(timed out after %d of %d contacts)\n", g_got, g_expected);
close(fd);
return g_expected >= 0 ? 0 : 1;
}