summaryrefslogtreecommitdiff
path: root/stacks/max25-bcpr/tools/max25-bcpr-ctl
diff options
context:
space:
mode:
Diffstat (limited to 'stacks/max25-bcpr/tools/max25-bcpr-ctl')
-rwxr-xr-xstacks/max25-bcpr/tools/max25-bcpr-ctl379
1 files changed, 379 insertions, 0 deletions
diff --git a/stacks/max25-bcpr/tools/max25-bcpr-ctl b/stacks/max25-bcpr/tools/max25-bcpr-ctl
new file mode 100755
index 0000000..39510a7
--- /dev/null
+++ b/stacks/max25-bcpr/tools/max25-bcpr-ctl
@@ -0,0 +1,379 @@
+#!/usr/bin/env bash
+# max25-bcpr-ctl — BayCom/based PC-COM helper (preflight / start / stop).
+# Lite policy: plain English operator messages (new Linux user level).
+set -euo pipefail
+
+INI="${MAX25_BCPR_INI:-${BCPR_INI:-/etc/max25/max25-bcpr.ini}}"
+BCPRD="${MAX25_BCPRD:-${BCPRD:-max25-bcprd}}"
+BCPRD_INIT="${MAX25_BCPRD_INIT:-max25-bcprd-init}"
+PIDFILE="${MAX25_BCPR_PIDFILE:-/tmp/max25-bcpr/max25-bcprd.pid}"
+STATE_DIR="/tmp/max25-bcpr"
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+SMOKE_SH="$SCRIPT_DIR/max25-bcpr-rxtx-smoke.sh"
+
+msg() { echo "max25-bcpr-ctl: $*"; }
+ok() { msg "OK — $*"; }
+warn(){ msg "WARN — $*"; }
+err() { msg "ERROR — $*" >&2; }
+
+usage() {
+ cat <<USAGE
+max25-bcpr-ctl — BayCom/based PC-COM modem helper
+
+Usage: max25-bcpr-ctl [-c ini] {preflight|start|status|stop|smoke|version}
+
+ preflight check config and serial port before start
+ start start the PC-COM background service
+ status show whether the service is running
+ stop stop the service
+ smoke offline/optional live test (see --help on smoke script)
+
+Config file default: /etc/max25/max25-bcpr.ini
+Create one with: max25-lite-setup (BayCom/based path)
+
+RX before TX — confirm receive before live transmit.
+USAGE
+}
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -c) INI="$2"; shift 2 ;;
+ -h|--help) usage; exit 0 ;;
+ *) break ;;
+ esac
+done
+CMD="${1:-}"
+if [[ $# -gt 0 ]]; then
+ shift
+fi
+
+resolve_max25_bcprd_init() {
+ if [[ -n "${BCPRD_INIT:-}" && "$BCPRD_INIT" != "max25-bcprd-init" && -x "$BCPRD_INIT" ]]; then
+ echo "$BCPRD_INIT"; return
+ fi
+ if command -v "$BCPRD_INIT" >/dev/null 2>&1; then
+ command -v "$BCPRD_INIT"; return
+ fi
+ local root cand
+ root="$(cd "$SCRIPT_DIR/../../.." && pwd)"
+ for cand in \
+ ${MAX25_BCPR_BUILD_DIR:-${BCPR_BUILD_DIR:-}}/bin/max25-bcprd-init \
+ "$root/build-max25-bcpr/bin/max25-bcprd-init" \
+ "$root/build-max25-bcpr-${USER:-user}/bin/max25-bcprd-init" \
+ "/tmp/max25-build-max25-bcpr-${USER:-user}/bin/max25-bcprd-init" \
+ "$root/build/bin/max25-bcprd-init" \
+ /usr/local/bin/max25-bcprd-init /usr/bin/max25-bcprd-init; do
+ [[ -n "$cand" && -x "$cand" ]] && { echo "$cand"; return; }
+ done
+ return 1
+}
+
+resolve_max25_bcprd() {
+ if [[ -n "${BCPRD:-}" && "$BCPRD" != "max25-bcprd" && -x "$BCPRD" ]]; then
+ echo "$BCPRD"; return
+ fi
+ if command -v "$BCPRD" >/dev/null 2>&1; then
+ command -v "$BCPRD"; return
+ fi
+ local root cand
+ root="$(cd "$SCRIPT_DIR/../../.." && pwd)"
+ for cand in \
+ ${MAX25_BCPR_BUILD_DIR:-${BCPR_BUILD_DIR:-}}/bin/max25-bcprd \
+ "$root/build-max25-bcpr/bin/max25-bcprd" \
+ "$root/build-max25-bcpr-${USER:-user}/bin/max25-bcprd" \
+ "/tmp/max25-build-max25-bcpr-${USER:-user}/bin/max25-bcprd" \
+ "$root/build/bin/max25-bcprd" \
+ /usr/local/bin/max25-bcprd /usr/bin/max25-bcprd; do
+ [[ -n "$cand" && -x "$cand" ]] && { echo "$cand"; return; }
+ done
+ return 1
+}
+
+ini_get() {
+ local section="$1" key="$2"
+ awk -F= -v s="[$section]" -v k="$key" '
+ $0 ~ /^\[/ { cur=$0; gsub(/[[:space:]]/,"",cur) }
+ cur==s && $1 ~ "^[[:space:]]*"k"[[:space:]]*$" {
+ v=$2; gsub(/^[[:space:]]+|[[:space:]]+$/,"",v); print v; exit
+ }' "$INI" 2>/dev/null || true
+}
+
+is_truthy() {
+ case "${1,,}" in yes|true|on|1) return 0 ;; *) return 1 ;; esac
+}
+
+find_setserial() {
+ local p
+ for p in /usr/bin/setserial /bin/setserial /sbin/setserial /usr/sbin/setserial; do
+ [[ -x "$p" ]] && { echo "$p"; return; }
+ done
+ command -v setserial 2>/dev/null || true
+}
+
+setserial_port_irq() {
+ local bin="$1" dev="$2" out port irq
+ out="$("$bin" -g "$dev" 2>/dev/null)" || return 1
+ port="$(printf '%s\n' "$out" | sed -n 's/.*Port:[[:space:]]*\(0x[0-9a-fA-F]\+\).*/\1/p' | head -1)"
+ irq="$(printf '%s\n' "$out" | sed -n 's/.*IRQ:[[:space:]]*\([0-9]\+\).*/\1/p' | head -1)"
+ printf '%s %s\n' "${port:-}" "${irq:-}"
+}
+
+norm_hex() {
+ local v="${1,,}"
+ if [[ "$v" =~ ^0x ]]; then
+ printf '0x%x' "$((v))"
+ else
+ printf '%s' "$v"
+ fi
+}
+
+is_freebsd() {
+ [[ "$(uname -s)" == "FreeBSD" ]]
+}
+
+port_busy() {
+ local serial="$1"
+ local out
+ if command -v lsof >/dev/null 2>&1; then
+ out="$(lsof "$serial" 2>/dev/null || true)"
+ [[ -n "$out" ]] && return 0
+ return 1
+ fi
+ if command -v fuser >/dev/null 2>&1; then
+ out="$(fuser "$serial" 2>/dev/null || true)"
+ [[ -n "$out" ]]
+ return $?
+ fi
+ return 1
+}
+
+baycom_modules_loaded() {
+ local m
+ if ! command -v lsmod >/dev/null 2>&1; then
+ return 1
+ fi
+ for m in baycom_ser_fdx baycom_ser_hdx baycom_par baycom_epp; do
+ if lsmod 2>/dev/null | awk '{print $1}' | grep -qx "$m"; then
+ echo "$m"
+ return 0
+ fi
+ done
+ return 1
+}
+
+cmd_preflight() {
+ local fail=0 dry serial irq iobase name sysirq got ss ss_port ss_irq pair
+ local -a seen_irq=() seen_serial=()
+ if [[ ! -f "$INI" ]]; then
+ err "config not found ($INI) — run max25-lite-setup or copy stacks/max25-bcpr/share/max25-bcpr.ini.example"
+ return 1
+ fi
+ dry="$(ini_get max25-bcpr dry_run)"; [[ -z "$dry" ]] && dry="$(ini_get bcpr dry_run)"
+ sd="$(ini_get max25-bcpr state_dir)"; [[ -z "$sd" ]] && sd="$(ini_get max25-bcpr state_dir)"
+ [[ -n "$sd" ]] && STATE_DIR="$sd"
+ msg "checking BayCom/based PC-COM setup (config: $INI)"
+
+ if mod="$(baycom_modules_loaded)"; then
+ err "Linux BayCom kernel driver is loaded ($mod) — unload it first: sudo modprobe -r $mod"
+ fail=1
+ else
+ ok "no conflicting Linux BayCom kernel driver"
+ fi
+
+ ss="$(find_setserial)"
+ if [[ -z "$ss" ]]; then
+ warn "setserial not installed — limited port checks (optional: sudo apt install setserial)"
+ else
+ ok "setserial found"
+ fi
+
+ for sec in bc0 bc1; do
+ serial="$(ini_get "$sec" serial)"
+ [[ -z "$serial" ]] && continue
+ enabled="$(ini_get "$sec" enabled)"
+ if [[ -n "$enabled" ]] && ! is_truthy "$enabled"; then
+ msg "section [$sec] disabled — skipped"
+ continue
+ fi
+ irq="$(ini_get "$sec" irq)"
+ iobase="$(ini_get "$sec" iobase)"
+ msg "port [$sec]: serial=$serial irq=${irq:-?} iobase=${iobase:-?}"
+
+ if is_truthy "$dry"; then
+ msg "dry-run mode — skipping live port checks"
+ continue
+ fi
+
+ if [[ ! -e "$serial" ]]; then
+ if is_freebsd && [[ -n "$iobase" && "$iobase" != "0" ]]; then
+ warn "$serial not present — using direct I/O address $iobase (FreeBSD)"
+ else
+ err "serial port missing ($serial) — check cable, enable UART in BIOS, or fix max25-bcpr.ini"
+ fail=1; continue
+ fi
+ else
+ ok "serial port exists ($serial)"
+ fi
+
+ if [[ -e "$serial" ]]; then
+ if port_busy "$serial"; then
+ err "serial port busy ($serial) — close minicom, other TNC tools, or second max25d"
+ fail=1
+ else
+ ok "serial port free ($serial)"
+ fi
+ elif is_freebsd && [[ -n "$iobase" && "$iobase" != "0" ]]; then
+ ok "no tty node — direct I/O path"
+ fi
+
+ if [[ -z "$irq" || "$irq" == "0" ]]; then
+ err "IRQ missing in [$sec] — set irq= in max25-bcpr.ini (often 4 for COM1 / ttyS0)"
+ fail=1
+ fi
+
+ name="${serial##*/}"
+ sysirq="/sys/class/tty/${name}/irq"
+ if [[ -r "$sysirq" ]]; then
+ got="$(tr -d '[:space:]' <"$sysirq")"
+ if [[ -n "$irq" && "$irq" != "0" && "$got" != "$irq" ]]; then
+ err "IRQ mismatch — config says $irq but system says $got — fix max25-bcpr.ini"
+ fail=1
+ elif [[ -n "$irq" && "$irq" != "0" ]]; then
+ ok "IRQ $irq matches system"
+ fi
+ else
+ warn "cannot read IRQ from $sysirq — check config by hand"
+ fi
+
+ if [[ -n "$ss" && -n "$iobase" ]]; then
+ pair="$(setserial_port_irq "$ss" "$serial" || true)"
+ ss_port="$(printf '%s' "$pair" | awk '{print $1}')"
+ ss_irq="$(printf '%s' "$pair" | awk '{print $2}')"
+ if [[ -n "$ss_port" ]]; then
+ if [[ "$(norm_hex "$iobase")" != "$(norm_hex "$ss_port")" ]]; then
+ err "I/O address mismatch — config $iobase vs setserial $ss_port"
+ fail=1
+ else
+ ok "I/O address matches setserial"
+ fi
+ else
+ warn "setserial did not report port address for $serial"
+ fi
+ if [[ -n "$ss_irq" && -n "$irq" && "$irq" != "0" && "$ss_irq" != "$irq" ]]; then
+ err "IRQ mismatch — config $irq vs setserial $ss_irq"
+ fail=1
+ elif [[ -n "$ss_irq" && -n "$irq" && "$irq" != "0" ]]; then
+ ok "IRQ matches setserial"
+ fi
+ elif [[ -n "$iobase" && -z "$ss" ]]; then
+ warn "cannot verify I/O address $iobase without setserial"
+ fi
+
+ for s in "${seen_serial[@]:-}"; do
+ if [[ -n "$s" && "$s" == "$serial" ]]; then
+ err "duplicate serial port $serial in config"
+ fail=1
+ fi
+ done
+ for i in "${seen_irq[@]:-}"; do
+ if [[ -n "$i" && -n "$irq" && "$irq" != "0" && "$i" == "$irq" ]]; then
+ err "duplicate IRQ $irq — use different IRQ per port"
+ fail=1
+ fi
+ done
+ seen_serial+=("$serial")
+ [[ -n "$irq" && "$irq" != "0" ]] && seen_irq+=("$irq")
+ done
+
+ if [[ "$fail" -eq 0 ]]; then
+ ok "checks passed — you can run: max25-bcpr-ctl start -c $INI"
+ else
+ err "checks failed — fix errors above, then run preflight again"
+ fi
+ return "$fail"
+}
+
+cmd_start() {
+ local bin init dry
+ cmd_preflight || return 1
+ sd="$(ini_get max25-bcpr state_dir)"; [[ -z "$sd" ]] && sd="$(ini_get bcpr state_dir)"; [[ -n "$sd" ]] && STATE_DIR="$sd"
+ mkdir -p "$STATE_DIR"
+ PIDFILE="$STATE_DIR/max25-bcprd.pid"
+ if [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
+ ok "already running (pid $(cat "$PIDFILE"))"
+ return 0
+ fi
+ dry="$(ini_get max25-bcpr dry_run)"; [[ -z "$dry" ]] && dry="$(ini_get bcpr dry_run)"
+ if is_truthy "$dry"; then
+ bin="$(resolve_max25_bcprd)" || {
+ err "test program max25-bcprd not found — run: cmake -B build && cmake --build build"
+ return 1
+ }
+ "$bin" -c "$INI" --dry-run </dev/null >/dev/null 2>&1 &
+ echo $! >"$PIDFILE"
+ ok "started test mode (dry-run) pid $(cat "$PIDFILE") — no radio TX"
+ return 0
+ fi
+ init="$(resolve_max25_bcprd_init)" || {
+ err "start helper max25-bcprd-init not found — run: sudo cmake --install build"
+ return 1
+ }
+ if ! "$init" -c "$INI" </dev/null; then
+ err "PC-COM service failed to start — run preflight again; use sudo if asked"
+ return 1
+ fi
+ if [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
+ ok "PC-COM service running (pid $(cat "$PIDFILE")) — next: run-max25d.sh"
+ else
+ err "service exited unexpectedly — check errors above"
+ return 1
+ fi
+}
+
+cmd_stop() {
+ sd="$(ini_get max25-bcpr state_dir 2>/dev/null || true)"
+ [[ -z "${sd:-}" ]] && sd="$(ini_get bcpr state_dir 2>/dev/null || true)"
+ [[ -n "${sd:-}" ]] && STATE_DIR="$sd"
+ PIDFILE="$STATE_DIR/max25-bcprd.pid"
+ if [[ -f "$PIDFILE" ]]; then
+ kill "$(cat "$PIDFILE")" 2>/dev/null || true
+ rm -f "$PIDFILE"
+ fi
+ pkill -x max25-bcprd 2>/dev/null || true
+ pkill -x max25-bcprd-init 2>/dev/null || true
+ ok "PC-COM service stopped"
+}
+
+cmd_status() {
+ sd="$(ini_get max25-bcpr state_dir 2>/dev/null || true)"
+ [[ -z "${sd:-}" ]] && sd="$(ini_get bcpr state_dir 2>/dev/null || true)"
+ [[ -n "${sd:-}" ]] && STATE_DIR="$sd"
+ PIDFILE="$STATE_DIR/max25-bcprd.pid"
+ if [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
+ msg "PC-COM service: running (pid $(cat "$PIDFILE"))"
+ else
+ msg "PC-COM service: not running — start with: max25-bcpr-ctl start -c $INI"
+ fi
+ for link in "$STATE_DIR"/kiss-bc*; do
+ [[ -e "$link" ]] || continue
+ msg " link $link → $(readlink -f "$link" 2>/dev/null || echo '?')"
+ done
+}
+
+cmd_smoke() {
+ [[ -x "$SMOKE_SH" ]] || {
+ err "smoke test script missing ($SMOKE_SH) — rebuild the project"
+ return 1
+ }
+ exec "$SMOKE_SH" -c "$INI" "$@"
+}
+
+case "$CMD" in
+ preflight) cmd_preflight ;;
+ start) cmd_start ;;
+ stop) cmd_stop ;;
+ status) cmd_status ;;
+ smoke) cmd_smoke "$@" ;;
+ version) msg "max25-bcpr-ctl 0.5.0-lite (BayCom/based PC-COM)" ;;
+ *) usage; exit 1 ;;
+esac
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com