diff options
Diffstat (limited to 'scripts/max25-lite-setup.sh')
| -rwxr-xr-x | scripts/max25-lite-setup.sh | 517 |
1 files changed, 517 insertions, 0 deletions
diff --git a/scripts/max25-lite-setup.sh b/scripts/max25-lite-setup.sh new file mode 100755 index 0000000..412d83e --- /dev/null +++ b/scripts/max25-lite-setup.sh @@ -0,0 +1,517 @@ +#!/usr/bin/env bash +# MAX25-Stack-Lite — semi-automatic setup wizard (dialog / whiptail) +# Detects serial + audio devices, writes max25d.ini (+ crdop.ini for CRDOP path). +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +UI_TITLE="MAX25-Stack-Lite setup" +UI_BACKTITLE="MAX25-Stack-Lite | local packet-radio setup" +UI_WIDTH=72 +UI_HEIGHT=18 +TMP_FILES=() + +UI="" +if command -v whiptail >/dev/null 2>&1; then + UI=whiptail +elif command -v dialog >/dev/null 2>&1; then + UI=dialog +else + echo "max25-lite-setup: install whiptail or dialog, then run this command again." >&2 + exit 1 +fi + +cleanup() { + local file + for file in "${TMP_FILES[@]}"; do + rm -f -- "$file" + done +} +trap cleanup EXIT HUP INT TERM + +require_terminal_size() { + local columns lines + [[ -t 1 && -t 2 ]] || return 0 + columns="$(tput cols 2>/dev/null || true)" + lines="$(tput lines 2>/dev/null || true)" + if [[ "$columns" =~ ^[0-9]+$ && "$lines" =~ ^[0-9]+$ ]] \ + && ((columns < UI_WIDTH || lines < UI_HEIGHT)); then + printf '%s\n' \ + "max25-lite-setup: terminal is ${columns}x${lines}; need at least ${UI_WIDTH}x${UI_HEIGHT}." \ + "Resize the terminal and run max25-lite-setup again." >&2 + exit 1 + fi +} + +show_help() { + "$UI" --backtitle "$UI_BACKTITLE" --title "Setup help" --ok-label "Back" \ + --msgbox "Use Up/Down to move, Space to mark a radio-list choice, and Enter to continue. + +Cancel or Esc stops the wizard without writing configuration files. + +The wizard writes local-only configuration. It does not start the modem, key a radio, or transmit. + +For serial and BayCom/based paths, select the device connected to this computer. For CRDOP, select the local capture/playback device." \ + "$UI_HEIGHT" "$UI_WIDTH" +} + +msgbox() { + "$UI" --backtitle "$UI_BACKTITLE" --title "$UI_TITLE" --ok-label "Continue" \ + --msgbox "$1" 14 "$UI_WIDTH" +} + +yesno() { + local prompt="$1" rc + while :; do + if "$UI" --backtitle "$UI_BACKTITLE" --title "$UI_TITLE" \ + --yes-label "Yes" --no-label "No" --help-button --help-label "Help" \ + --yesno "$prompt" 10 "$UI_WIDTH"; then + return 0 + else + rc=$? + fi + if [[ "$rc" -eq 2 ]]; then + show_help + continue + fi + return "$rc" + done +} + +inputbox() { + local prompt="$1" default="${2:-}" value rc + while :; do + if value=$("$UI" --backtitle "$UI_BACKTITLE" --title "$UI_TITLE" \ + --ok-label "Continue" --cancel-label "Cancel" --help-button --help-label "Help" \ + --inputbox "$prompt" 10 "$UI_WIDTH" "$default" 3>&1 1>&2 2>&3); then + printf '%s\n' "$value" + return 0 + else + rc=$? + fi + if [[ "$rc" -eq 2 ]]; then + show_help + continue + fi + return "$rc" + done +} + +menu() { + local prompt="$1" value rc + shift + while :; do + if value=$("$UI" --backtitle "$UI_BACKTITLE" --title "$UI_TITLE" \ + --ok-label "Continue" --cancel-label "Cancel" --help-button --help-label "Help" \ + --menu "$prompt" "$UI_HEIGHT" "$UI_WIDTH" 10 "$@" 3>&1 1>&2 2>&3); then + printf '%s\n' "$value" + return 0 + else + rc=$? + fi + if [[ "$rc" -eq 2 ]]; then + show_help + continue + fi + return "$rc" + done +} + +radiolist() { + local prompt="$1" value rc + shift + while :; do + if value=$("$UI" --backtitle "$UI_BACKTITLE" --title "$UI_TITLE" \ + --ok-label "Continue" --cancel-label "Cancel" --help-button --help-label "Help" \ + --radiolist "$prompt" "$UI_HEIGHT" "$UI_WIDTH" 10 "$@" 3>&1 1>&2 2>&3); then + printf '%s\n' "$value" + return 0 + else + rc=$? + fi + if [[ "$rc" -eq 2 ]]; then + show_help + continue + fi + return "$rc" + done +} + +require_terminal_size + +list_serial_ports() { + local -a ports=() + shopt -s nullglob + for pat in /dev/ttyUSB* /dev/ttyACM* /dev/ttyS*; do + [[ -e "$pat" ]] || continue + ports+=("$pat") + done + shopt -u nullglob + if ((${#ports[@]} == 0)); then + ports=("/dev/ttyUSB0") + fi + printf '%s\n' "${ports[@]}" +} + +list_alsa_cards() { + if command -v arecord >/dev/null 2>&1; then + arecord -l 2>/dev/null | awk '/card [0-9]+:/ {c=$2; gsub(":", "", c); desc=$0; gsub(/^.*card [0-9]+: /, "", desc); gsub(/,.*/, "", desc); printf "hw:%s,0 %s\n", c, desc}' || true + fi + if command -v aplay >/dev/null 2>&1; then + aplay -l 2>/dev/null | awk '/card [0-9]+:/ {c=$2; gsub(":", "", c); desc=$0; gsub(/^.*card [0-9]+: /, "", desc); gsub(/,.*/, "", desc); printf "hw:%s,0 %s (playback)\n", c, desc}' || true + fi +} + +msgbox "Welcome to MAX25-Stack-Lite setup. + +This wizard detects serial and audio devices on this computer, writes max25d.ini, and shows how to start max25d and max25-terminal locally. + +It changes files only after you choose a save location. Use Help for key hints, or Cancel to leave without saving." + +MODEM_KIND="$(menu "Packet radio modem path:" \ + serial "Serial TNC / USB KISS (T-Modem, TNC2)" \ + baycom "BayCom/based PC-COM (SER12 on COM/ttyS)" \ + crdop "CRDOP sound-card soft modem (MAX25-SoftModem)" \ + )" || exit 0 + +DEVICE_ID="tnc2c" +HARDWARE="tncs" +SERIAL_PATH="" +CRDOP_CAPTURE="" +CRDOP_PLAYBACK="" +WRITE_CRDOP_INI=0 +WRITE_BCPR_INI=0 +BCPR_INI_DEST="" + +if [[ "$MODEM_KIND" == "baycom" ]]; then + DEVICE_ID="max25e0" + HARDWARE="tncs" + WRITE_BCPR_INI=1 + mapfile -t PORTS < <(list_serial_ports) + menu_args=() + idx=0 + for p in "${PORTS[@]}"; do + menu_args+=("$p" "serial port" "$((idx == 0 ? 1 : 0))") + idx=$((idx + 1)) + done + SERIAL_PATH="$(radiolist "Select PC-COM serial port (often /dev/ttyS0):" "${menu_args[@]}")" || exit 0 +elif [[ "$MODEM_KIND" == "serial" ]]; then + mapfile -t PORTS < <(list_serial_ports) + menu_args=() + idx=0 + for p in "${PORTS[@]}"; do + menu_args+=("$p" "serial port" "$((idx == 0 ? 1 : 0))") + idx=$((idx + 1)) + done + SERIAL_PATH="$(radiolist "Select serial device:" "${menu_args[@]}")" || exit 0 + if [[ "$SERIAL_PATH" == /dev/ttyACM* ]]; then + if yesno "Device looks like USB KISS (T-Modem). Use device id tmodem instead of tnc2c?"; then + DEVICE_ID="tmodem" + fi + fi +else + HARDWARE="soft-modems" + DEVICE_ID="soft-crdop" + WRITE_CRDOP_INI=1 + mapfile -t ALSA_LINES < <(list_alsa_cards) + if ((${#ALSA_LINES[@]} == 0)); then + msgbox "No ALSA cards listed (arecord/aplay). You can edit ~/.config/crdop/crdop.ini later." + CRDOP_CAPTURE="hw:0,0" + CRDOP_PLAYBACK="hw:0,0" + else + menu_args=() + idx=0 + for line in "${ALSA_LINES[@]}"; do + dev="${line%% *}" + label="${line#* }" + menu_args+=("$dev" "$label" "$((idx == 0 ? 1 : 0))") + idx=$((idx + 1)) + done + sel="$(radiolist "Select capture/playback ALSA device (hw:N,0):" "${menu_args[@]}")" || exit 0 + CRDOP_CAPTURE="$sel" + CRDOP_PLAYBACK="$sel" + fi +fi + +CALLERID="$(inputbox "Your AX.25 caller ID (e.g. CB-0):" "CB-0")" || exit 0 +CALLID="$(inputbox "Default destination callsign for CONNECT tests:" "QST")" || exit 0 + +if yesno "Write system config /etc/max25/max25d.ini? (No = ./local/max25d.ini in repo)"; then + INI_DEST="/etc/max25/max25d.ini" + CRDOP_INI_DEST="${HOME}/.config/crdop/crdop.ini" + BCPR_INI_DEST="/etc/max25/max25-bcpr.ini" + NEED_SUDO=1 +else + INI_DEST="${ROOT}/local/max25d.ini" + CRDOP_INI_DEST="${ROOT}/local/crdop.ini" + BCPR_INI_DEST="${ROOT}/local/max25-bcpr.ini" + NEED_SUDO=0 +fi + +if [[ "$NEED_SUDO" -eq 1 ]]; then + if ! command -v sudo >/dev/null 2>&1; then + msgbox "System configuration needs sudo, but sudo is not available. + +Choose No at the save-location screen to write local configuration in this repository." + exit 1 + fi + sudo mkdir -p "$(dirname "$INI_DEST")" + mkdir -p "$(dirname "$CRDOP_INI_DEST")" +else + mkdir -p "$(dirname "$INI_DEST")" +fi + +write_max25d_ini() { + local dest="$1" + if [[ "$MODEM_KIND" == "baycom" ]]; then + cat >"$dest" <<EOF +; MAX25-Stack-Lite — generated by max25-lite-setup.sh (BayCom/based) + +[features] +max25_bcpr = yes + +[daemon] +mode = standalone +hardware = tncs +device = max25e0 + +[network] +tcp_host = 127.0.0.1 +tcp_port = 7325 +unix_socket = /run/max25/modem.sock +tcp_password = + +[modem] +callerid = ${CALLERID} +callid = ${CALLID} +ax25_ui = yes + +[reporting] +error_transmissions = yes +voice_transmissions = yes +data_passes = 3 +data_quality_min = 50 +data_pass_seconds = 20 + +[stack] +auto_start = yes + +[devices] +default = max25e0 +max25e0 = max25-bcpr:bc0 + +[device.max25e0] +kiss_link = /tmp/max25-bcpr/kiss-bc0 +max25_bcpr_ini = ${BCPR_INI_DEST} +EOF + elif [[ "$MODEM_KIND" == "serial" ]]; then + cat >"$dest" <<EOF +; MAX25-Stack-Lite — generated by max25-lite-setup.sh + +[daemon] +mode = standalone +hardware = ${HARDWARE} +device = ${DEVICE_ID} + +[network] +tcp_host = 127.0.0.1 +tcp_port = 7325 +unix_socket = /run/max25/modem.sock +; Lite: localhost only — terminal on same host as max25d +tcp_password = + +[modem] +callerid = ${CALLERID} +callid = ${CALLID} +ax25_ui = yes + +[reporting] +error_transmissions = yes +voice_transmissions = yes +data_passes = 3 +data_quality_min = 50 +data_pass_seconds = 20 + +[stack] +auto_start = yes +serial_watch = yes +serial_watch_interval = 60 +stack_recover_only = yes +serial_bootwait_escalate = yes +serial_bootwait_escalate_after = 3 + +[devices] +default = ${DEVICE_ID} +${DEVICE_ID} = ${SERIAL_PATH} + +[serial.${DEVICE_ID}] +baud = 19200 +line = 8n1 +dtr_rts = yes +kiss_entry = kiss_on +EOF + else + cat >"$dest" <<EOF +; MAX25-Stack-Lite — generated by max25-lite-setup.sh (CRDOP) + +[daemon] +mode = standalone +hardware = soft-modems +device = soft-crdop + +[network] +tcp_host = 127.0.0.1 +tcp_port = 7325 +unix_socket = /run/max25/modem.sock +; Lite: localhost only — terminal on same host as max25d +tcp_password = + +[modem] +callerid = ${CALLERID} +callid = ${CALLID} +ax25_ui = yes + +[reporting] +error_transmissions = yes +voice_transmissions = yes +data_passes = 3 +data_quality_min = 50 +data_pass_seconds = 20 + +[stack] +auto_start = yes + +[devices] +default = soft-crdop +soft-crdop = crdop:default + +[device.soft-crdop] +host = 127.0.0.1 +port = 8515 +listen = yes +EOF + fi +} + +write_crdop_ini() { + local dest="$1" + mkdir -p "$(dirname "$dest")" + cat >"$dest" <<EOF +; MAX25-SoftModem (CRDOP) — generated by max25-lite-setup.sh + +[profile] +radio_profile = cb + +[modem] +speed_baud = 1200 +duplex = half +max_speed_baud = 19200 + +[host] +port = 8515 + +[mycall] +call = ${CALLERID} + +[audio] +backend = alsa-kernel +no_pulse = yes +capture = ${CRDOP_CAPTURE} +playback = ${CRDOP_PLAYBACK} + +[coupling] +mode = line +EOF +} + +write_bcpr_ini() { + local dest="$1" + mkdir -p "$(dirname "$dest")" + cat >"$dest" <<EOF +; max25-bcpr — BayCom/based PC-COM (generated by max25-lite-setup.sh) + +[max25-bcpr] +dry_run = no +state_dir = /tmp/max25-bcpr +user = max25 +group = max25 + +[bc0] +enabled = yes +serial = ${SERIAL_PATH} +iobase = 0x3f8 +irq = 4 +mode = ser12* +kiss_link = /tmp/max25-bcpr/kiss-bc0 +baud = 1200 +tx_delay = 35 +tx_tail = 2 +slottime = 10 +ppersist = 40 +fulldup = no +EOF +} + +if [[ "$NEED_SUDO" -eq 1 ]]; then + tmp="$(mktemp)" + TMP_FILES+=("$tmp") + write_max25d_ini "$tmp" + sudo cp "$tmp" "$INI_DEST" + if [[ "$WRITE_CRDOP_INI" -eq 1 ]]; then + tmp="$(mktemp)" + TMP_FILES+=("$tmp") + write_crdop_ini "$tmp" + cp "$tmp" "$CRDOP_INI_DEST" + fi + if [[ "$WRITE_BCPR_INI" -eq 1 ]]; then + tmp="$(mktemp)" + TMP_FILES+=("$tmp") + write_bcpr_ini "$tmp" + sudo cp "$tmp" "$BCPR_INI_DEST" + fi +else + mkdir -p "${ROOT}/local" + write_max25d_ini "$INI_DEST" + if [[ "$WRITE_CRDOP_INI" -eq 1 ]]; then + write_crdop_ini "$CRDOP_INI_DEST" + fi + if [[ "$WRITE_BCPR_INI" -eq 1 ]]; then + write_bcpr_ini "$BCPR_INI_DEST" + fi +fi + +NEXT_STEPS="Configuration written: + + max25d.ini → ${INI_DEST}" +if [[ "$WRITE_CRDOP_INI" -eq 1 ]]; then + NEXT_STEPS="${NEXT_STEPS} + crdop.ini → ${CRDOP_INI_DEST}" +fi +if [[ "$WRITE_BCPR_INI" -eq 1 ]]; then + NEXT_STEPS="${NEXT_STEPS} + max25-bcpr.ini → ${BCPR_INI_DEST}" +fi +NEXT_STEPS="${NEXT_STEPS} + +Build (if not done): + cmake -B build && cmake --build build + sudo cmake --install build + +BayCom/based only — start bcpr first (needs root on Linux): + sudo max25-bcpr-ctl start --ini ${BCPR_INI_DEST} + +Start daemon: + ${ROOT}/scripts/run-max25d.sh ${INI_DEST} + +CRDOP only — start soft modem (separate terminal): + max25-ctl start --hardware soft-modems --device soft-crdop + (or: crdop -c ${CRDOP_INI_DEST}) + +Terminal + CONNECT: + ${ROOT}/scripts/run-max25-terminal.sh -U /run/max25/modem.sock + At prompt: CONNECT ${CALLID} + Or F10 menu → Connect + +RX before TX: confirm receive/noise/SQ before live transmit." + +"$UI" --backtitle "$UI_BACKTITLE" --title "Setup complete" --ok-label "Done" \ + --msgbox "$NEXT_STEPS" "$UI_HEIGHT" "$UI_WIDTH" |
