From ab2c82db163993ea8e8c377b6e62b6c7f772c2f1 Mon Sep 17 00:00:00 2001 From: Yellowcooln <12516003+yellowcooln@users.noreply.github.com> Date: Thu, 23 Apr 2026 16:01:32 -0400 Subject: [PATCH] Drive Buildroot radio config from JSON --- buildroot-manage.sh | 322 +++++++++++++++++++++++++++------- radio-settings-buildroot.json | 73 ++++++++ 2 files changed, 327 insertions(+), 68 deletions(-) create mode 100644 radio-settings-buildroot.json diff --git a/buildroot-manage.sh b/buildroot-manage.sh index 8cdd62a..2a5f6be 100644 --- a/buildroot-manage.sh +++ b/buildroot-manage.sh @@ -23,6 +23,9 @@ PIWHEELS_INDEX_URL="https://www.piwheels.org/simple" R2_ENABLED=1 PYMC_CORE_REPO="${PYMC_CORE_REPO:-https://github.com/rightup/pyMC_core.git}" PYMC_CORE_REF="${PYMC_CORE_REF:-dev}" +RADIO_SETTINGS_JSON="$SCRIPT_DIR/radio-settings.json" +RADIO_PRESETS_JSON="$SCRIPT_DIR/radio-presets.json" +BUILDROOT_RADIO_SETTINGS_JSON="$SCRIPT_DIR/radio-settings-buildroot.json" set_wheel_dependencies() { set -- \ "cherrypy>=18.0.0" \ @@ -73,10 +76,6 @@ prompt_value() { local default_value="${2:-}" local reply="" - if [ -n "${reply:-}" ]; then - : - fi - if [ ! -t 0 ]; then printf '%s\n' "$default_value" return 0 @@ -125,14 +124,6 @@ prompt_secret() { done } -normalize_radio_profile() { - case "$1" in - 1|v2|V2|pimesh-v2|pimesh_v2|pimesh2|PiMeshV2) printf 'v2\n' ;; - 2|v1|V1|meshadv|MeshAdv|pimesh-v1|pimesh_v1|pimesh1) printf 'v1\n' ;; - *) return 1 ;; - esac -} - ensure_root() { [ "$(id -u 2>/dev/null || echo 1)" -eq 0 ] || fail "This command must be run as root." } @@ -466,6 +457,146 @@ else: PY } +list_buildroot_boards() { + python3 - "$BUILDROOT_RADIO_SETTINGS_JSON" <<'PY' +import json +import sys + +with open(sys.argv[1], "r", encoding="utf-8") as fh: + data = json.load(fh) + +for index, (key, entry) in enumerate(data.get("buildroot_hardware", {}).items(), start=1): + print(f"{index}|{key}|{entry.get('name', key)}|{entry.get('description', '')}") +PY +} + +resolve_buildroot_board() { + local choice="$1" + + python3 - "$BUILDROOT_RADIO_SETTINGS_JSON" "$choice" <<'PY' +import json +import sys + +with open(sys.argv[1], "r", encoding="utf-8") as fh: + data = json.load(fh) + +choice = sys.argv[2].strip().lower() +boards = list((data.get("buildroot_hardware") or {}).items()) + +for index, (key, entry) in enumerate(boards, start=1): + aliases = {str(index), key.lower(), str(entry.get("name", "")).lower()} + aliases.update(alias.lower() for alias in entry.get("aliases", [])) + if choice in aliases: + print(key) + raise SystemExit(0) + +raise SystemExit(1) +PY +} + +get_default_buildroot_board() { + python3 - "$BUILDROOT_RADIO_SETTINGS_JSON" <<'PY' +import json +import sys + +with open(sys.argv[1], "r", encoding="utf-8") as fh: + data = json.load(fh) + +default_board = data.get("default_board") +boards = data.get("buildroot_hardware", {}) +if default_board and default_board in boards: + print(default_board) +else: + for key in boards: + print(key) + break +PY +} + +get_buildroot_board_label() { + local board_key="$1" + python3 - "$BUILDROOT_RADIO_SETTINGS_JSON" "$board_key" <<'PY' +import json +import sys + +with open(sys.argv[1], "r", encoding="utf-8") as fh: + data = json.load(fh) + +entry = (data.get("buildroot_hardware") or {}).get(sys.argv[2], {}) +print(entry.get("name", sys.argv[2])) +PY +} + +list_radio_presets() { + python3 - "$RADIO_PRESETS_JSON" <<'PY' +import json +import sys + +with open(sys.argv[1], "r", encoding="utf-8") as fh: + data = json.load(fh) + +entries = ((data.get("config") or {}).get("suggested_radio_settings") or {}).get("entries", []) +for index, entry in enumerate(entries, start=1): + print(f"{index}|{entry.get('title', '')}|{entry.get('description', '')}") +PY +} + +resolve_radio_preset() { + local choice="$1" + + python3 - "$RADIO_PRESETS_JSON" "$choice" <<'PY' +import json +import sys + +with open(sys.argv[1], "r", encoding="utf-8") as fh: + data = json.load(fh) + +choice = sys.argv[2].strip().lower() +entries = ((data.get("config") or {}).get("suggested_radio_settings") or {}).get("entries", []) +for index, entry in enumerate(entries, start=1): + title = entry.get("title", "") + if choice in {str(index), title.lower()}: + print(title) + raise SystemExit(0) + +raise SystemExit(1) +PY +} + +get_default_radio_preset() { + python3 - "$BUILDROOT_RADIO_SETTINGS_JSON" <<'PY' +import json +import sys + +with open(sys.argv[1], "r", encoding="utf-8") as fh: + data = json.load(fh) + +print(data.get("default_radio_preset", "")) +PY +} + +get_radio_preset_field() { + local preset_title="$1" + local field="$2" + + python3 - "$RADIO_PRESETS_JSON" "$preset_title" "$field" <<'PY' +import json +import sys + +with open(sys.argv[1], "r", encoding="utf-8") as fh: + data = json.load(fh) + +preset_title, field = sys.argv[2:4] +entries = ((data.get("config") or {}).get("suggested_radio_settings") or {}).get("entries", []) +for entry in entries: + if entry.get("title") == preset_title: + print(entry.get(field, "")) + raise SystemExit(0) + +raise SystemExit(1) +PY +} + get_radio_frequency_mhz() { python3 - "$CONFIG_DIR/config.yaml" <<'PY' import yaml @@ -488,6 +619,56 @@ print(f"{float(bw) / 1000:.3f}".rstrip("0").rstrip(".")) PY } +select_buildroot_board() { + local choice="${LUCKFOX_RADIO_PROFILE:-${PYMC_RADIO_PROFILE:-${PYMC_BUILDROOT_BOARD:-}}}" + local default_board + + default_board=$(get_default_buildroot_board) + + if [ -n "$choice" ]; then + resolve_buildroot_board "$choice" || fail "Unknown Buildroot board choice: $choice" + return 0 + fi + + printf 'Select Luckfox radio board:\n' >&2 + while IFS='|' read -r index key name description; do + [ -n "$index" ] || continue + printf ' %s) %s' "$index" "$name" >&2 + [ -n "$description" ] && printf ' - %s' "$description" >&2 + printf '\n' >&2 + done <&2 + while IFS='|' read -r index title description; do + [ -n "$index" ] || continue + printf ' %s) %s' "$index" "$title" >&2 + [ -n "$description" ] && printf ' - %s' "$description" >&2 + printf '\n' >&2 + done <&2 - printf ' 1) PiMesh V2\n' >&2 - printf ' 2) PiMesh V1 / MeshAdv\n' >&2 - choice=$(prompt_value "Profile" "1") - normalize_radio_profile "$choice" || fail "Unknown radio profile choice: $choice" -} - seed_repeater_config() { - local node_name admin_password jwt_secret profile freq_mhz sf bw_khz coding_rate tx_power + local node_name admin_password jwt_secret board_key board_name preset_title freq_mhz sf bw_khz coding_rate tx_power stage "Configuring repeater" @@ -579,7 +759,9 @@ seed_repeater_config() { [ -n "$node_name" ] || node_name=$(prompt_value "Repeater name" "$(get_config_value repeater.node_name luckfox-repeater)") [ -n "$node_name" ] || fail "Repeater name cannot be empty." - profile=$(select_radio_profile) + board_key=$(select_buildroot_board) + board_name=$(get_buildroot_board_label "$board_key") + info "Selected board: $board_name" admin_password="${PYMC_ADMIN_PASSWORD:-}" [ -n "$admin_password" ] || admin_password=$(prompt_secret "Admin password") @@ -587,22 +769,25 @@ seed_repeater_config() { jwt_secret="${PYMC_JWT_SECRET:-}" [ -n "$jwt_secret" ] || jwt_secret=$(python3 -c 'import secrets; print(secrets.token_hex(32))') + preset_title=$(select_radio_preset) + info "Using preset: $preset_title" + freq_mhz="${PYMC_RADIO_FREQUENCY_MHZ:-}" - [ -n "$freq_mhz" ] || freq_mhz=$(prompt_value "Frequency MHz" "$(get_radio_frequency_mhz)") + [ -n "$freq_mhz" ] || freq_mhz=$(prompt_value "Frequency MHz" "$(get_radio_preset_field "$preset_title" frequency)") sf="${PYMC_RADIO_SF:-}" - [ -n "$sf" ] || sf=$(prompt_value "Spreading factor" "$(get_config_value radio.spreading_factor 7)") + [ -n "$sf" ] || sf=$(prompt_value "Spreading factor" "$(get_radio_preset_field "$preset_title" spreading_factor)") bw_khz="${PYMC_RADIO_BANDWIDTH_KHZ:-}" - [ -n "$bw_khz" ] || bw_khz=$(prompt_value "Bandwidth kHz" "$(get_radio_bandwidth_khz)") + [ -n "$bw_khz" ] || bw_khz=$(prompt_value "Bandwidth kHz" "$(get_radio_preset_field "$preset_title" bandwidth)") coding_rate="${PYMC_RADIO_CODING_RATE:-}" - [ -n "$coding_rate" ] || coding_rate=$(prompt_value "Coding rate" "$(get_config_value radio.coding_rate 5)") + [ -n "$coding_rate" ] || coding_rate=$(prompt_value "Coding rate" "$(get_radio_preset_field "$preset_title" coding_rate)") tx_power="${PYMC_RADIO_TX_POWER_DBM:-}" [ -n "$tx_power" ] || tx_power=$(prompt_value "TX power dBm" "$(get_config_value radio.tx_power 22)") - write_repeater_config "$node_name" "$admin_password" "$jwt_secret" "$freq_mhz" "$sf" "$bw_khz" "$coding_rate" "$tx_power" "$profile" + write_repeater_config "$node_name" "$admin_password" "$jwt_secret" "$freq_mhz" "$sf" "$bw_khz" "$coding_rate" "$tx_power" "$board_key" info "Saved config for ${node_name}" } @@ -616,10 +801,11 @@ configure_repeater() { } configure_radio_profile() { - local profile + local board_key + ensure_root [ -f "$CONFIG_DIR/config.yaml" ] || fail "Config file is missing. Run install first." - profile=$(select_radio_profile) + board_key=$(select_buildroot_board) write_repeater_config \ "$(get_config_value repeater.node_name luckfox-repeater)" \ "$(get_config_value repeater.security.admin_password admin123)" \ @@ -629,8 +815,8 @@ configure_radio_profile() { "$(get_radio_bandwidth_khz)" \ "$(get_config_value radio.coding_rate 5)" \ "$(get_config_value radio.tx_power 22)" \ - "$profile" - info "Applied Luckfox radio profile: $profile" + "$board_key" + info "Applied board profile: $(get_buildroot_board_label "$board_key")" if service_exists; then "$INIT_SCRIPT" restart fi @@ -891,7 +1077,7 @@ Commands: install Install pyMC Repeater on the Buildroot image upgrade Upgrade the Buildroot installation from the checked-out repo configure Prompt for repeater settings and rewrite config.yaml - radio-profile Reapply the Luckfox radio pin profile only + radio-profile Reapply the Luckfox board radio config only config Run the stock interactive config flow start Start the init.d service stop Stop the init.d service diff --git a/radio-settings-buildroot.json b/radio-settings-buildroot.json new file mode 100644 index 0000000..db2d720 --- /dev/null +++ b/radio-settings-buildroot.json @@ -0,0 +1,73 @@ +{ + "default_board": "luckfox-pimesh-v2", + "default_radio_preset": "USA/Canada (Recommended)", + "buildroot_hardware": { + "luckfox-pimesh-v2": { + "name": "Luckfox PiMesh V2", + "description": "Luckfox Pico Pi with PiMesh-1W V2 / E22P wiring", + "hardware_id": "pimesh-1w-v2", + "aliases": [ + "1", + "v2", + "pimesh-v2", + "pimesh-1w-v2" + ], + "sx1262_overrides": { + "cs_pin": -1, + "reset_pin": 54, + "busy_pin": 122, + "irq_pin": 121, + "en_pin": 0, + "txen_pin": -1, + "rxen_pin": -1, + "use_dio2_rf": true, + "use_dio3_tcxo": true, + "dio3_tcxo_voltage": 1.8 + } + }, + "luckfox-pimesh-v1": { + "name": "Luckfox PiMesh V1", + "description": "Luckfox Pico Pi with PiMesh-1W V1 wiring", + "hardware_id": "pimesh-1w-v1", + "aliases": [ + "2", + "v1", + "pimesh-v1", + "pimesh-1w-v1" + ], + "sx1262_overrides": { + "cs_pin": 145, + "reset_pin": 54, + "busy_pin": 123, + "irq_pin": 55, + "en_pin": -1, + "txen_pin": 52, + "rxen_pin": 53, + "use_dio2_rf": false, + "use_dio3_tcxo": true, + "dio3_tcxo_voltage": 1.8 + } + }, + "luckfox-meshadv": { + "name": "Luckfox MeshAdv", + "description": "Luckfox Pico Pi with MeshAdv wiring", + "hardware_id": "meshadv", + "aliases": [ + "3", + "meshadv" + ], + "sx1262_overrides": { + "cs_pin": 145, + "reset_pin": 54, + "busy_pin": 123, + "irq_pin": 55, + "en_pin": -1, + "txen_pin": 52, + "rxen_pin": 53, + "use_dio2_rf": false, + "use_dio3_tcxo": true, + "dio3_tcxo_voltage": 1.8 + } + } + } +}