summaryrefslogtreecommitdiff
path: root/stacks/max25-bcpr/src/bcpr_config.c
diff options
context:
space:
mode:
authorinfo@mode42.com <info@mode42.com>2026-08-07 18:25:13 +0000
committerinfo@mode42.com <info@mode42.com>2026-08-07 18:25:13 +0000
commit04d965d67a7264a1c7c211494aebda1953df7603 (patch)
tree0ebd700a6e219f84a26a656f4bee8778bc75da7b /stacks/max25-bcpr/src/bcpr_config.c
Initial push
Diffstat (limited to 'stacks/max25-bcpr/src/bcpr_config.c')
-rw-r--r--stacks/max25-bcpr/src/bcpr_config.c397
1 files changed, 397 insertions, 0 deletions
diff --git a/stacks/max25-bcpr/src/bcpr_config.c b/stacks/max25-bcpr/src/bcpr_config.c
new file mode 100644
index 0000000..3707891
--- /dev/null
+++ b/stacks/max25-bcpr/src/bcpr_config.c
@@ -0,0 +1,397 @@
+/*
+ * Simple INI loader for max25-bcpr (max 2 devices: [bc0] / [bc1]).
+ */
+#include "bcpr/bcpr_config.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void bcpr_config_defaults(bcpr_config_t *cfg)
+{
+ int i;
+ memset(cfg, 0, sizeof(*cfg));
+ snprintf(cfg->state_dir, sizeof(cfg->state_dir), "%s", "/tmp/max25-bcpr");
+ cfg->dry_run = 0;
+ cfg->n_dev = 0;
+ /* FlexNet SER12.doc: 14.5 s keyed / 500 ms unkey. */
+ cfg->ptt_wd = 1;
+ cfg->ptt_wd_key_ms = 14500;
+ cfg->ptt_wd_pause_ms = 500;
+ cfg->txd_bias = BCPR_TXD_PULSE;
+ cfg->tot_enabled = 1;
+ cfg->tot_max_key_ms = 25000;
+ cfg->tot_min_gap_ms = 1500;
+ cfg->tot_max_consecutive = 3;
+ cfg->tot_max_bursts = 8;
+ for (i = 0; i < BCPR_MAX_DEVICES; i++) {
+ bcpr_dev_config_t *d = &cfg->dev[i];
+ d->enabled = 0;
+ d->baud = 1200;
+ snprintf(d->mode, sizeof(d->mode), "%s", "ser12*");
+ d->tx_delay = 35;
+ d->tx_tail = 2;
+ d->slottime = 10;
+ d->ppersist = 40;
+ d->fulldup = 0;
+ d->ptt_wd = 1;
+ d->ptt_wd_key_ms = 14500;
+ d->ptt_wd_pause_ms = 500;
+ d->txd_bias = BCPR_TXD_PULSE;
+ d->tot_enabled = 1;
+ d->tot_max_key_ms = 25000;
+ d->tot_min_gap_ms = 1500;
+ d->tot_max_consecutive = 3;
+ d->tot_max_bursts = 8;
+ }
+}
+
+static void trim(char *s)
+{
+ char *e;
+ while (*s && isspace((unsigned char)*s)) {
+ memmove(s, s + 1, strlen(s));
+ }
+ e = s + strlen(s);
+ while (e > s && isspace((unsigned char)e[-1])) {
+ *--e = '\0';
+ }
+}
+
+static int parse_u(const char *v, unsigned *out)
+{
+ char *end = NULL;
+ unsigned long x = strtoul(v, &end, 0);
+ if (!v[0] || (end && *end)) {
+ return -1;
+ }
+ *out = (unsigned)x;
+ return 0;
+}
+
+static int parse_i(const char *v, int *out)
+{
+ char *end = NULL;
+ long x = strtol(v, &end, 0);
+ if (!v[0] || (end && *end)) {
+ return -1;
+ }
+ *out = (int)x;
+ return 0;
+}
+
+static int truthy(const char *v)
+{
+ return (strcasecmp(v, "yes") == 0 || strcasecmp(v, "true") == 0 ||
+ strcasecmp(v, "on") == 0 || strcmp(v, "1") == 0);
+}
+
+static int parse_txd_bias(const char *v, int *out)
+{
+ if (strcasecmp(v, "pulse") == 0 || strcasecmp(v, "sailer") == 0) {
+ *out = BCPR_TXD_PULSE;
+ return 0;
+ }
+ if (strcasecmp(v, "steady") == 0 || strcasecmp(v, "tfpcx") == 0 ||
+ strcasecmp(v, "break") == 0) {
+ *out = BCPR_TXD_STEADY;
+ return 0;
+ }
+ return -1;
+}
+
+static int apply_tot(int *enabled, int *max_key_ms, int *min_gap_ms,
+ int *max_consecutive, int *max_bursts,
+ const char *key, const char *val)
+{
+ if (strcmp(key, "tot") == 0 || strcmp(key, "tot_enabled") == 0) {
+ *enabled = truthy(val) ? 1 : 0;
+ return 0;
+ }
+ if (strcmp(key, "tot_max_key_sec") == 0 || strcmp(key, "tot_max_key_ms") == 0) {
+ if (strcmp(key, "tot_max_key_sec") == 0) {
+ int sec;
+ if (parse_i(val, &sec) != 0 || sec < 1) {
+ return -1;
+ }
+ *max_key_ms = sec * 1000;
+ return 0;
+ }
+ return parse_i(val, max_key_ms);
+ }
+ if (strcmp(key, "tot_min_gap_sec") == 0 || strcmp(key, "tot_min_gap_ms") == 0) {
+ if (strcmp(key, "tot_min_gap_sec") == 0) {
+ int sec;
+ if (parse_i(val, &sec) != 0 || sec < 0) {
+ return -1;
+ }
+ *min_gap_ms = sec * 1000;
+ return 0;
+ }
+ return parse_i(val, min_gap_ms);
+ }
+ if (strcmp(key, "tot_max_consecutive") == 0) {
+ return parse_i(val, max_consecutive);
+ }
+ if (strcmp(key, "tot_max_bursts") == 0) {
+ return parse_i(val, max_bursts);
+ }
+ return 1;
+}
+
+static int apply_wd_txd(int *ptt_wd, int *key_ms, int *pause_ms, int *txd_bias,
+ const char *key, const char *val)
+{
+ if (strcmp(key, "ptt_wd") == 0 || strcmp(key, "ptt_watchdog") == 0) {
+ *ptt_wd = truthy(val) ? 1 : 0;
+ return 0;
+ }
+ if (strcmp(key, "ptt_wd_key_ms") == 0) {
+ return parse_i(val, key_ms);
+ }
+ if (strcmp(key, "ptt_wd_pause_ms") == 0) {
+ return parse_i(val, pause_ms);
+ }
+ if (strcmp(key, "txd_bias") == 0) {
+ return parse_txd_bias(val, txd_bias);
+ }
+ return 1; /* not handled */
+}
+
+static void normalize_legacy_path(char *buf, size_t len)
+{
+ static const struct {
+ const char *from;
+ const char *to;
+ } map[] = {
+ {"/tmp/bcpr/", "/tmp/max25-bcpr/"},
+ {"/tmp/bcpr", "/tmp/max25-bcpr"},
+ {"/var/run/bcpr/", "/tmp/max25-bcpr/"},
+ {"/var/run/bcpr", "/tmp/max25-bcpr"},
+ };
+ size_t i;
+ size_t flen;
+ if (!buf || !len || !buf[0]) {
+ return;
+ }
+ for (i = 0; i < sizeof(map) / sizeof(map[0]); i++) {
+ flen = strlen(map[i].from);
+ if (strncmp(buf, map[i].from, flen) != 0) {
+ continue;
+ }
+ if (buf[flen] != '\0' && buf[flen] != '/') {
+ continue;
+ }
+ {
+ char tmp[256];
+ snprintf(tmp, sizeof(tmp), "%s%s", map[i].to, buf + flen);
+ snprintf(buf, len, "%s", tmp);
+ }
+ return;
+ }
+}
+
+static int apply_kv(bcpr_config_t *cfg, int section, const char *key,
+ const char *val)
+{
+ /* section: -1 = [max25-bcpr]/[bcpr] global, 0 = bc0, 1 = bc1 */
+ if (section < 0) {
+ int rc;
+ if (strcmp(key, "dry_run") == 0) {
+ cfg->dry_run = truthy(val);
+ return 0;
+ }
+ if (strcmp(key, "state_dir") == 0) {
+ snprintf(cfg->state_dir, sizeof(cfg->state_dir), "%s", val);
+ return 0;
+ }
+ if (strcmp(key, "user") == 0) {
+ snprintf(cfg->run_user, sizeof(cfg->run_user), "%s", val);
+ return 0;
+ }
+ if (strcmp(key, "group") == 0) {
+ snprintf(cfg->run_group, sizeof(cfg->run_group), "%s", val);
+ return 0;
+ }
+ rc = apply_wd_txd(&cfg->ptt_wd, &cfg->ptt_wd_key_ms,
+ &cfg->ptt_wd_pause_ms, &cfg->txd_bias, key, val);
+ if (rc <= 0) {
+ /* Propagate global WD/TXD defaults onto both devices. */
+ int i;
+ for (i = 0; i < BCPR_MAX_DEVICES; i++) {
+ cfg->dev[i].ptt_wd = cfg->ptt_wd;
+ cfg->dev[i].ptt_wd_key_ms = cfg->ptt_wd_key_ms;
+ cfg->dev[i].ptt_wd_pause_ms = cfg->ptt_wd_pause_ms;
+ cfg->dev[i].txd_bias = cfg->txd_bias;
+ }
+ return rc;
+ }
+ rc = apply_tot(&cfg->tot_enabled, &cfg->tot_max_key_ms, &cfg->tot_min_gap_ms,
+ &cfg->tot_max_consecutive, &cfg->tot_max_bursts, key, val);
+ if (rc <= 0) {
+ int i;
+ for (i = 0; i < BCPR_MAX_DEVICES; i++) {
+ cfg->dev[i].tot_enabled = cfg->tot_enabled;
+ cfg->dev[i].tot_max_key_ms = cfg->tot_max_key_ms;
+ cfg->dev[i].tot_min_gap_ms = cfg->tot_min_gap_ms;
+ cfg->dev[i].tot_max_consecutive = cfg->tot_max_consecutive;
+ cfg->dev[i].tot_max_bursts = cfg->tot_max_bursts;
+ }
+ return rc;
+ }
+ return 0;
+ }
+ if (section >= BCPR_MAX_DEVICES) {
+ return -1;
+ }
+ {
+ bcpr_dev_config_t *d = &cfg->dev[section];
+ int rc;
+ if (strcmp(key, "enabled") == 0) {
+ d->enabled = truthy(val);
+ } else if (strcmp(key, "serial") == 0 || strcmp(key, "tty") == 0) {
+ snprintf(d->serial, sizeof(d->serial), "%s", val);
+ d->enabled = 1;
+ } else if (strcmp(key, "iobase") == 0) {
+ return parse_u(val, &d->iobase);
+ } else if (strcmp(key, "irq") == 0) {
+ return parse_u(val, &d->irq);
+ } else if (strcmp(key, "baud") == 0) {
+ return parse_u(val, &d->baud);
+ } else if (strcmp(key, "mode") == 0) {
+ snprintf(d->mode, sizeof(d->mode), "%s", val);
+ } else if (strcmp(key, "kiss_link") == 0) {
+ snprintf(d->kiss_link, sizeof(d->kiss_link), "%s", val);
+ } else if (strcmp(key, "tx_delay") == 0 || strcmp(key, "txdelay") == 0) {
+ return parse_i(val, &d->tx_delay);
+ } else if (strcmp(key, "tx_tail") == 0 || strcmp(key, "txtail") == 0) {
+ return parse_i(val, &d->tx_tail);
+ } else if (strcmp(key, "slottime") == 0) {
+ return parse_i(val, &d->slottime);
+ } else if (strcmp(key, "ppersist") == 0) {
+ return parse_i(val, &d->ppersist);
+ } else if (strcmp(key, "fulldup") == 0) {
+ d->fulldup = truthy(val);
+ } else {
+ rc = apply_wd_txd(&d->ptt_wd, &d->ptt_wd_key_ms, &d->ptt_wd_pause_ms,
+ &d->txd_bias, key, val);
+ if (rc < 0) {
+ return -1;
+ }
+ if (rc == 0) {
+ return 0;
+ }
+ rc = apply_tot(&d->tot_enabled, &d->tot_max_key_ms, &d->tot_min_gap_ms,
+ &d->tot_max_consecutive, &d->tot_max_bursts, key, val);
+ if (rc < 0) {
+ return -1;
+ }
+ if (rc == 0) {
+ return 0;
+ }
+ }
+ }
+ return 0;
+}
+
+int bcpr_config_load(bcpr_config_t *cfg, const char *path)
+{
+ FILE *f;
+ char line[512];
+ int section = -1;
+ int i;
+
+ if (!cfg || !path) {
+ return -1;
+ }
+ bcpr_config_defaults(cfg);
+ f = fopen(path, "r");
+ if (!f) {
+ return -1;
+ }
+ while (fgets(line, sizeof(line), f)) {
+ char *eq;
+ char *p = line;
+ trim(p);
+ if (p[0] == '\0' || p[0] == '#' || p[0] == ';') {
+ continue;
+ }
+ if (p[0] == '[') {
+ char *end = strchr(p, ']');
+ if (!end) {
+ continue;
+ }
+ *end = '\0';
+ p++;
+ if (strcasecmp(p, "max25-bcpr") == 0 || strcasecmp(p, "bcpr") == 0 ||
+ strcasecmp(p, "global") == 0) {
+ section = -1;
+ } else if (strcasecmp(p, "bc0") == 0 ||
+ strcasecmp(p, "device.bc0") == 0) {
+ section = 0;
+ } else if (strcasecmp(p, "bc1") == 0 ||
+ strcasecmp(p, "device.bc1") == 0) {
+ section = 1;
+ } else {
+ section = -2; /* ignore unknown */
+ }
+ continue;
+ }
+ if (section == -2) {
+ continue;
+ }
+ eq = strchr(p, '=');
+ if (!eq) {
+ continue;
+ }
+ *eq = '\0';
+ trim(p);
+ trim(eq + 1);
+ (void)apply_kv(cfg, section, p, eq + 1);
+ }
+ fclose(f);
+
+ normalize_legacy_path(cfg->state_dir, sizeof(cfg->state_dir));
+
+ cfg->n_dev = 0;
+ for (i = 0; i < BCPR_MAX_DEVICES; i++) {
+ if (cfg->dev[i].enabled && cfg->dev[i].serial[0]) {
+ if (!cfg->dev[i].kiss_link[0]) {
+ snprintf(cfg->dev[i].kiss_link, sizeof(cfg->dev[i].kiss_link),
+ "%s/kiss-bc%d", cfg->state_dir, i);
+ }
+ normalize_legacy_path(cfg->dev[i].kiss_link,
+ sizeof(cfg->dev[i].kiss_link));
+ /* Clamp WD timings. */
+ if (cfg->dev[i].ptt_wd_key_ms < 1000) {
+ cfg->dev[i].ptt_wd_key_ms = 1000;
+ }
+ if (cfg->dev[i].ptt_wd_pause_ms < 50) {
+ cfg->dev[i].ptt_wd_pause_ms = 50;
+ }
+ if (cfg->dev[i].txd_bias != BCPR_TXD_STEADY) {
+ cfg->dev[i].txd_bias = BCPR_TXD_PULSE;
+ }
+ if (cfg->dev[i].tot_max_key_ms < 1000) {
+ cfg->dev[i].tot_max_key_ms = 1000;
+ }
+ if (cfg->dev[i].tot_min_gap_ms < 0) {
+ cfg->dev[i].tot_min_gap_ms = 0;
+ }
+ if (cfg->dev[i].tot_max_consecutive < 1) {
+ cfg->dev[i].tot_max_consecutive = 1;
+ }
+ if (cfg->dev[i].tot_max_bursts < 1) {
+ cfg->dev[i].tot_max_bursts = 1;
+ }
+ cfg->n_dev++;
+ } else {
+ cfg->dev[i].enabled = 0;
+ }
+ }
+ if (cfg->n_dev > BCPR_MAX_DEVICES) {
+ return -1;
+ }
+ return 0;
+}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com