From c3712737e94267334e0f55df71c11693f2cf48c3 Mon Sep 17 00:00:00 2001 From: mikecarper Date: Tue, 12 May 2026 23:50:47 -0700 Subject: [PATCH] build.sh: add optional Cascadia profile overrides --- build.sh | 329 +++++++++++++++++++++++++++- examples/simple_repeater/MyMesh.cpp | 46 +++- 2 files changed, 368 insertions(+), 7 deletions(-) diff --git a/build.sh b/build.sh index 9ef20d11a..ad8f4079f 100755 --- a/build.sh +++ b/build.sh @@ -4,6 +4,16 @@ ALL_PIO_ENVS=() PIO_CONFIG_JSON="" MENU_CHOICE="" SELECTED_TARGET="" +SELECTED_COMMAND_ARGS=() +MESHDEBUG_OVERRIDE="" +PACKET_LOGGING_OVERRIDE="" +RADIO_SETTINGS_API_URL="https://api.meshcore.nz/api/v1/config" +RADIO_SETTING_TITLE="" +RADIO_FREQ_OVERRIDE="" +RADIO_BW_OVERRIDE="" +RADIO_SF_OVERRIDE="" +RADIO_CR_OVERRIDE="" +FIRMWARE_PROFILE_OVERRIDE="" ENV_VARIANT_SUFFIX_PATTERN='companion_radio_serial|companion_radio_wifi|companion_radio_usb|comp_radio_usb|companion_usb|companion_radio_ble|companion_ble|repeater_bridge_rs232_serial1|repeater_bridge_rs232_serial2|repeater_bridge_rs232|repeater_bridge_espnow|terminal_chat|room_server|room_svr|kiss_modem|sensor|repeatr|repeater' BOARD_MODIFIER_WITHOUT_DISPLAY="_without_display" @@ -51,7 +61,7 @@ Examples: Build firmware for the "RAK_4631_repeater" device target $ bash build.sh build-firmware RAK_4631_repeater -Run without arguments to choose a target from an interactive menu +Run without arguments to choose an interactive build action/target, debug options, and radio settings $ bash build.sh Build all firmwares for device targets containing the string "RAK_4631" @@ -187,6 +197,277 @@ prompt_menu_choice() { done } +prompt_on_off_choice() { + local prompt_label=$1 + local default_choice=$2 + local choice + + while true; do + read -r -p "${prompt_label} [on/off] (default: ${default_choice}): " choice + choice=${choice,,} + if [ -z "$choice" ]; then + choice=$default_choice + fi + + case "$choice" in + on|off) + MENU_CHOICE="$choice" + return 0 + ;; + *) + echo "Invalid selection. Choose 'on' or 'off'." + ;; + esac + done +} + +prompt_for_build_mode() { + local options=( + "Build one firmware target" + "Build all firmwares" + "Build all repeater firmwares" + "Build all companion firmwares" + "Build all chat room server firmwares" + ) + + echo "No command provided. Select a build action:" + while true; do + print_numbered_menu "${options[@]}" + prompt_menu_choice "Build action" "${#options[@]}" + if [ "$MENU_CHOICE" == "QUIT" ]; then + echo "Cancelled." + exit 1 + fi + + case "$MENU_CHOICE" in + 1) + prompt_for_board_target + SELECTED_COMMAND_ARGS=(build-firmware "$SELECTED_TARGET") + return 0 + ;; + 2) + SELECTED_COMMAND_ARGS=(build-firmwares) + return 0 + ;; + 3) + SELECTED_COMMAND_ARGS=(build-repeater-firmwares) + return 0 + ;; + 4) + SELECTED_COMMAND_ARGS=(build-companion-firmwares) + return 0 + ;; + 5) + SELECTED_COMMAND_ARGS=(build-room-server-firmwares) + return 0 + ;; + esac + done +} + +prompt_for_debug_build_settings() { + echo "Set debug build options:" + prompt_on_off_choice "Mesh debug (MESH_DEBUG)" "off" + MESHDEBUG_OVERRIDE="$MENU_CHOICE" + + prompt_on_off_choice "Packet logging (MESH_PACKET_LOGGING)" "off" + PACKET_LOGGING_OVERRIDE="$MENU_CHOICE" + + echo "Using debug options: meshdebug=${MESHDEBUG_OVERRIDE}, packet_logging=${PACKET_LOGGING_OVERRIDE}" +} + +clear_radio_overrides() { + RADIO_SETTING_TITLE="" + RADIO_FREQ_OVERRIDE="" + RADIO_BW_OVERRIDE="" + RADIO_SF_OVERRIDE="" + RADIO_CR_OVERRIDE="" +} + +clear_firmware_profile_overrides() { + FIRMWARE_PROFILE_OVERRIDE="" +} + +set_radio_overrides() { + RADIO_SETTING_TITLE=$1 + RADIO_FREQ_OVERRIDE=$2 + RADIO_BW_OVERRIDE=$3 + RADIO_SF_OVERRIDE=$4 + RADIO_CR_OVERRIDE=$5 +} + +fetch_suggested_radio_settings() { + python3 - "$RADIO_SETTINGS_API_URL" <<'PY' +import json +import sys +import urllib.request + +url = sys.argv[1] +with urllib.request.urlopen(url, timeout=8) as response: + payload = json.load(response) + +entries = ( + payload.get("config", {}) + .get("suggested_radio_settings", {}) + .get("entries", []) +) + +for entry in entries: + title = str(entry.get("title", "")).strip() + description = str(entry.get("description", "")).strip() + freq = str(entry.get("frequency", "")).strip() + sf = str(entry.get("spreading_factor", "")).strip() + bw = str(entry.get("bandwidth", "")).strip() + cr = str(entry.get("coding_rate", "")).strip() + if title and freq and sf and bw and cr: + print("\t".join([title, description, freq, bw, sf, cr])) +PY +} + +is_valid_custom_radio_bandwidth() { + python3 - "$1" <<'PY' +import sys + +allowed = [7.81, 10.42, 15.63, 20.83, 31.25, 41.67, 62.5, 125.0, 250.0, 500.0] +try: + value = float(sys.argv[1]) +except Exception: + raise SystemExit(1) + +raise SystemExit(0 if any(abs(value - option) < 1e-6 for option in allowed) else 1) +PY +} + +prompt_for_custom_radio_setting() { + local freq + local sf + local bw + local cr + + echo + echo "Custom radio settings:" + + while true; do + read -r -p "Center frequency (MHz, e.g. 915.000): " freq + if [[ "$freq" =~ ^[0-9]+([.][0-9]+)?$ ]]; then + break + fi + echo "Please enter a numeric MHz value (e.g. 915.000)." + done + + echo "Spreading factor options: 5, 6, 7, 8, 9, 10, 11, 12" + while true; do + read -r -p "SF (5-12): " sf + if [[ "$sf" =~ ^[0-9]+$ ]] && [ "$sf" -ge 5 ] && [ "$sf" -le 12 ]; then + break + fi + echo "Please enter 5, 6, 7, 8, 9, 10, 11, or 12." + done + + echo "Bandwidth options (kHz): 7.81 10.42 15.63 20.83 31.25 41.67 62.5 125 250 500" + while true; do + read -r -p "BW (kHz): " bw + if [[ "$bw" =~ ^[0-9]+([.][0-9]+)?$ ]] && is_valid_custom_radio_bandwidth "$bw"; then + break + fi + echo "Please enter one of: 7.81 10.42 15.63 20.83 31.25 41.67 62.5 125 250 500." + done + + echo "Coding rate options: CR5, CR6, CR7, CR8" + while true; do + read -r -p "CR (5-8): " cr + if [[ "$cr" =~ ^[0-9]+$ ]] && [ "$cr" -ge 5 ] && [ "$cr" -le 8 ]; then + break + fi + echo "Please enter 5, 6, 7, or 8." + done + + set_radio_overrides "Custom" "$freq" "$bw" "$sf" "$cr" +} + +prompt_for_cascadia_profile_enable() { + clear_firmware_profile_overrides + echo + echo "Cascadia profile changes:" + echo " - rxdelay: 1" + echo " - agc.reset.interval: 8" + echo " - advert.interval: 0" + echo " - flood.advert.interval: 83" + echo " - multi.acks: 1" + echo " - path.hash.mode: 2" + echo " - loop.detect: minimal" + echo " - powersaving: on" + prompt_on_off_choice "Enable Cascadia profile overrides" "on" + if [ "$MENU_CHOICE" == "on" ]; then + FIRMWARE_PROFILE_OVERRIDE="cascadia" + echo "Using firmware profile override: ${FIRMWARE_PROFILE_OVERRIDE}" + return 0 + fi + + echo "Using target default firmware profile settings." + return 0 +} + +prompt_for_radio_build_settings() { + local -a preset_rows=() + local -a options=("Keep target defaults (no radio override)") + local row + local title + local description + local freq + local bw + local sf + local cr + local preset_index + local choice_index + local custom_index + + clear_radio_overrides + + if mapfile -t preset_rows < <(fetch_suggested_radio_settings); then + for row in "${preset_rows[@]}"; do + IFS=$'\t' read -r title description freq bw sf cr <<< "$row" + options+=("${title}: ${description}") + done + else + echo "Could not fetch radio presets from ${RADIO_SETTINGS_API_URL}." + preset_rows=() + fi + + options+=("Custom") + custom_index=${#options[@]} + + echo "Set radio build options:" + while true; do + print_numbered_menu "${options[@]}" + prompt_menu_choice "Radio setting" "${#options[@]}" + if [ "$MENU_CHOICE" == "QUIT" ]; then + echo "Cancelled." + exit 1 + fi + + choice_index=$MENU_CHOICE + if [ "$choice_index" -eq 1 ]; then + echo "Using target default radio settings." + return 0 + fi + + if [ "$choice_index" -eq "$custom_index" ]; then + prompt_for_custom_radio_setting + echo "Using radio setting: ${RADIO_SETTING_TITLE} (${RADIO_FREQ_OVERRIDE}MHz / SF${RADIO_SF_OVERRIDE} / BW${RADIO_BW_OVERRIDE} / CR${RADIO_CR_OVERRIDE})" + return 0 + fi + + preset_index=$((choice_index - 2)) + if [ "$preset_index" -ge 0 ] && [ "$preset_index" -lt "${#preset_rows[@]}" ]; then + IFS=$'\t' read -r title description freq bw sf cr <<< "${preset_rows[$preset_index]}" + set_radio_overrides "$title" "$freq" "$bw" "$sf" "$cr" + echo "Using radio setting: ${RADIO_SETTING_TITLE} (${RADIO_FREQ_OVERRIDE}MHz / SF${RADIO_SF_OVERRIDE} / BW${RADIO_BW_OVERRIDE} / CR${RADIO_CR_OVERRIDE})" + return 0 + fi + done +} + get_env_metadata() { local env_name=$1 local trimmed_env_name @@ -394,7 +675,7 @@ prompt_for_board_target() { mapfile -t boards < <(printf '%s\n' "${boards[@]}" | sort_lines_case_insensitive) - echo "No command provided. Select a board family:" + echo "Select a board family:" while true; do print_numbered_menu "${boards[@]}" prompt_menu_choice "Board selection" "${#boards[@]}" @@ -523,6 +804,40 @@ disable_debug_flags() { fi } +apply_debug_overrides() { + case "${MESHDEBUG_OVERRIDE,,}" in + on) + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UMESH_DEBUG -DMESH_DEBUG=1" + ;; + off) + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UMESH_DEBUG" + ;; + esac + + case "${PACKET_LOGGING_OVERRIDE,,}" in + on) + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UMESH_PACKET_LOGGING -DMESH_PACKET_LOGGING=1" + ;; + off) + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UMESH_PACKET_LOGGING" + ;; + esac +} + +apply_radio_overrides() { + if [ -n "$RADIO_FREQ_OVERRIDE" ] && [ -n "$RADIO_BW_OVERRIDE" ] && [ -n "$RADIO_SF_OVERRIDE" ] && [ -n "$RADIO_CR_OVERRIDE" ]; then + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -ULORA_FREQ -ULORA_BW -ULORA_SF -ULORA_CR -DLORA_FREQ=${RADIO_FREQ_OVERRIDE} -DLORA_BW=${RADIO_BW_OVERRIDE} -DLORA_SF=${RADIO_SF_OVERRIDE} -DLORA_CR=${RADIO_CR_OVERRIDE}" + fi +} + +apply_firmware_profile_overrides() { + case "${FIRMWARE_PROFILE_OVERRIDE,,}" in + cascadia) + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DCASCADIA_PROFILE=1 -DDEFAULT_RX_DELAY_BASE=1.0f -DDEFAULT_LOOP_DETECT=1 -DDEFAULT_POWERSAVING_ENABLED=1 -DDEFAULT_AGC_RESET_INTERVAL=2 -DDEFAULT_ADVERT_INTERVAL=0 -DDEFAULT_FLOOD_ADVERT_INTERVAL=83 -DDEFAULT_MULTI_ACKS=1 -DDEFAULT_PATH_HASH_MODE=2" + ;; + esac +} + copy_build_output() { local source_path=$1 local output_path=$2 @@ -616,6 +931,9 @@ build_firmware() { export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${firmware_build_date}\"' -DFIRMWARE_VERSION='\"${firmware_version_string}\"'" disable_debug_flags + apply_debug_overrides + apply_radio_overrides + apply_firmware_profile_overrides pio run -e "$env_name" collect_build_artifacts "$env_name" "$env_platform" "$firmware_filename" @@ -734,8 +1052,11 @@ main() { init_project_context if [ $# -eq 0 ]; then - prompt_for_board_target - set -- build-firmware "$SELECTED_TARGET" + prompt_for_build_mode + prompt_for_debug_build_settings + prompt_for_radio_build_settings + prompt_for_cascadia_profile_enable + set -- "${SELECTED_COMMAND_ARGS[@]}" fi prepare_output_dir diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 228625183..26534931f 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -20,6 +20,31 @@ #define LORA_TX_POWER 20 #endif +#ifndef DEFAULT_RX_DELAY_BASE + #define DEFAULT_RX_DELAY_BASE 0.0f +#endif +#ifndef DEFAULT_LOOP_DETECT + #define DEFAULT_LOOP_DETECT LOOP_DETECT_OFF +#endif +#ifndef DEFAULT_POWERSAVING_ENABLED + #define DEFAULT_POWERSAVING_ENABLED 0 +#endif +#ifndef DEFAULT_AGC_RESET_INTERVAL + #define DEFAULT_AGC_RESET_INTERVAL 0 +#endif +#ifndef DEFAULT_ADVERT_INTERVAL + #define DEFAULT_ADVERT_INTERVAL 1 +#endif +#ifndef DEFAULT_FLOOD_ADVERT_INTERVAL + #define DEFAULT_FLOOD_ADVERT_INTERVAL 12 +#endif +#ifndef DEFAULT_MULTI_ACKS + #define DEFAULT_MULTI_ACKS 0 +#endif +#ifndef DEFAULT_PATH_HASH_MODE + #define DEFAULT_PATH_HASH_MODE 0 +#endif + #ifndef ADVERT_NAME #define ADVERT_NAME "repeater" #endif @@ -2034,7 +2059,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc // defaults memset(&_prefs, 0, sizeof(_prefs)); _prefs.airtime_factor = 1.0; - _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; + _prefs.rx_delay_base = DEFAULT_RX_DELAY_BASE; _prefs.tx_delay_factor = 0.5f; // was 0.25f _prefs.direct_tx_delay_factor = 0.3f; // was 0.2 _prefs.direct_retry_recent_enabled = 1; @@ -2063,10 +2088,15 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.bw = LORA_BW; _prefs.cr = LORA_CR; _prefs.tx_power_dbm = LORA_TX_POWER; - _prefs.advert_interval = 1; // default to 2 minutes for NEW installs - _prefs.flood_advert_interval = 12; // 12 hours + _prefs.advert_interval = DEFAULT_ADVERT_INTERVAL; + _prefs.flood_advert_interval = DEFAULT_FLOOD_ADVERT_INTERVAL; _prefs.flood_max = 64; _prefs.interference_threshold = 0; // disabled + _prefs.agc_reset_interval = DEFAULT_AGC_RESET_INTERVAL; + _prefs.multi_acks = DEFAULT_MULTI_ACKS; + _prefs.path_hash_mode = DEFAULT_PATH_HASH_MODE; + _prefs.loop_detect = DEFAULT_LOOP_DETECT; + _prefs.powersaving_enabled = DEFAULT_POWERSAVING_ENABLED; // bridge defaults _prefs.bridge_enabled = 1; // enabled @@ -2105,6 +2135,16 @@ void MyMesh::begin(FILESYSTEM *fs) { _fs = fs; // load persisted prefs _cli.loadPrefs(_fs); +#ifdef CASCADIA_PROFILE + _prefs.rx_delay_base = DEFAULT_RX_DELAY_BASE; + _prefs.agc_reset_interval = DEFAULT_AGC_RESET_INTERVAL; + _prefs.advert_interval = DEFAULT_ADVERT_INTERVAL; + _prefs.flood_advert_interval = DEFAULT_FLOOD_ADVERT_INTERVAL; + _prefs.multi_acks = DEFAULT_MULTI_ACKS; + _prefs.path_hash_mode = DEFAULT_PATH_HASH_MODE; + _prefs.loop_detect = DEFAULT_LOOP_DETECT; + _prefs.powersaving_enabled = DEFAULT_POWERSAVING_ENABLED; +#endif acl.load(_fs, self_id); // TODO: key_store.begin(); region_map.load(_fs);