From 3d593e211c2d6c712d43c7d7cfa490e2a7413f0d Mon Sep 17 00:00:00 2001 From: mikecarper Date: Wed, 29 Jul 2026 14:26:57 -0700 Subject: [PATCH] Speed Option 3 builds and harden radio IRQ handling --- build.sh | 265 ++++++++++++++++++--- src/helpers/NRF52Board.cpp | 4 +- src/helpers/radiolib/CustomLLCC68.h | 83 ++++++- src/helpers/radiolib/CustomLLCC68Wrapper.h | 8 +- src/helpers/radiolib/CustomLR1110.h | 15 ++ src/helpers/radiolib/CustomSX1262.h | 15 ++ src/helpers/radiolib/CustomSX1268.h | 83 ++++++- src/helpers/radiolib/CustomSX1268Wrapper.h | 8 +- 8 files changed, 432 insertions(+), 49 deletions(-) diff --git a/build.sh b/build.sh index 5640318e..f0e081cc 100755 --- a/build.sh +++ b/build.sh @@ -27,6 +27,11 @@ RADIO_SF_OVERRIDE="" RADIO_CR_OVERRIDE="" FIRMWARE_PROFILE_OVERRIDE="${FIRMWARE_PROFILE_OVERRIDE:-}" BATCH_BUILD_MODE=0 +OPTION3_BUILD_WORKERS="${OPTION3_BUILD_WORKERS:-2}" +OPTION3_PIO_JOBS="${OPTION3_PIO_JOBS:-8}" +PROFILE_BUILD_WORKERS=1 +PIO_BUILD_JOBS_OVERRIDE="" +PIO_BUILD_DIR_OVERRIDE="" RESOLVED_BUILD_TARGETS=() RESUME_BUILD_OUTPUT="${RESUME_BUILD_OUTPUT:-0}" LOGGING_MATRIX_FAILURES=() @@ -60,7 +65,8 @@ FALLBACK_VERSION_PREFIX="dev" FALLBACK_VERSION_DATE_FORMAT='+%Y-%m-%d-%H-%M' # External programs invoked by this script: -# bash, cat, cp, date, git, grep, head, mkdir, pio, python3, rm, sed, sort +# bash, cat, cp, date, find, git, grep, head, mkdir, mv, pgrep, pio, +# python3, rm, sed, sleep, sort, wc # Keep this list in sync when adding or removing non-builtin command usage. global_usage() { @@ -128,8 +134,10 @@ Environment Variables: In interactive builds, this value is offered as the editable default. DISABLE_DEBUG=1: Disables all debug logging flags (MESH_DEBUG, MESH_PACKET_LOGGING, etc.) If not set, debug flags from variant platformio.ini files are used. - RESUME_BUILD_OUTPUT=1: For build-firmwares-logging-matrix, preserves out/ and skips - targets whose expected output artifacts already exist. + RESUME_BUILD_OUTPUT=1: Preserves out/ and skips targets whose expected output + artifacts already exist. Option 3 resumes by default. + OPTION3_BUILD_WORKERS=2: Concurrent targets per Option 3 profile pass. + OPTION3_PIO_JOBS=8: Compiler jobs assigned to each concurrent Option 3 target. Examples: Build without debug logging: @@ -1407,8 +1415,14 @@ normalize_resume_build_output() { } prompt_for_logging_matrix_output_policy() { + local default_policy=${1:-clean} local choice file_count file_label + case "$default_policy" in + resume|clean) ;; + *) default_policy="clean" ;; + esac + normalize_resume_build_output if [ "$OUTPUT_POLICY_EXPLICIT" -eq 1 ]; then @@ -1434,6 +1448,9 @@ prompt_for_logging_matrix_output_policy() { fi if ! [ -t 0 ]; then + if [ "$default_policy" = "resume" ]; then + RESUME_BUILD_OUTPUT=1 + fi if [ "$RESUME_BUILD_OUTPUT" == "1" ]; then echo "Resuming previous profile-build output in ${OUTPUT_DIR} (${file_count} ${file_label})." fi @@ -1441,10 +1458,10 @@ prompt_for_logging_matrix_output_policy() { fi while true; do - read -r -p "Output directory '${OUTPUT_DIR}' exists with ${file_count} ${file_label}. Resume previous profile-build progress or clean it? [resume/clean] (default: clean): " choice + read -r -p "Output directory '${OUTPUT_DIR}' exists with ${file_count} ${file_label}. Resume previous profile-build progress or clean it? [resume/clean] (default: ${default_policy}): " choice choice=${choice,,} if [ -z "$choice" ]; then - choice="clean" + choice=$default_policy fi case "$choice" in @@ -1966,9 +1983,10 @@ collect_esp32_artifacts() { local env_name=$1 local pio_env_name=$2 local firmware_filename=$3 + local build_output_dir="${PIO_BUILD_DIR_OVERRIDE:-${PLATFORMIO_BUILD_DIR:-.pio/build}}/${pio_env_name}" local -a size_check_args=( - ".pio/build/${pio_env_name}/firmware.bin" - ".pio/build/${pio_env_name}/partitions.bin" + "${build_output_dir}/firmware.bin" + "${build_output_dir}/partitions.bin" ) local partsig_name=$env_name @@ -1977,20 +1995,19 @@ collect_esp32_artifacts() { fi python3 scripts/check_esp32_app_size.py "${size_check_args[@]}" || return $? - pio run -t mergebin -e "$pio_env_name" || return $? - copy_build_output ".pio/build/${pio_env_name}/firmware.bin" "${OUTPUT_DIR}/${firmware_filename}.bin" || return $? - copy_build_output ".pio/build/${pio_env_name}/firmware-merged.bin" "${OUTPUT_DIR}/${firmware_filename}-merged.bin" || return $? + copy_build_output "${build_output_dir}/firmware.bin" "${OUTPUT_DIR}/${firmware_filename}.bin" || return $? + copy_build_output "${build_output_dir}/firmware-merged.bin" "${OUTPUT_DIR}/${firmware_filename}-merged.bin" || return $? # Emit the partition-table signature for OTA partition-compatibility checks. # Standard builds keep the env-name key used by the slim-manifest generator; # FULL builds use a suffix so their expanded table does not overwrite the # portable signature. The firmware computes the same signature at runtime. # Best-effort: local builds without the script's deps just skip it. - if [ -f ".pio/build/${pio_env_name}/partitions.bin" ]; then + if [ -f "${build_output_dir}/partitions.bin" ]; then if [ "$ESP32_FULL_BUILD" = "1" ]; then partsig_name="${env_name}-full" fi - python3 scripts/partition_signature.py ".pio/build/${pio_env_name}/partitions.bin" > "${OUTPUT_DIR}/${partsig_name}.partsig" 2>/dev/null || true + python3 scripts/partition_signature.py "${build_output_dir}/partitions.bin" > "${OUTPUT_DIR}/${partsig_name}.partsig" 2>/dev/null || true fi } @@ -1998,28 +2015,33 @@ collect_nrf52_artifacts() { local env_name=$1 local pio_env_name=$2 local firmware_filename=$3 + local build_output_dir="${PIO_BUILD_DIR_OVERRIDE:-${PLATFORMIO_BUILD_DIR:-.pio/build}}/${pio_env_name}" - python3 bin/uf2conv/uf2conv.py ".pio/build/${pio_env_name}/firmware.hex" -c -o ".pio/build/${pio_env_name}/firmware.uf2" -f 0xADA52840 || return $? - copy_build_output ".pio/build/${pio_env_name}/firmware.uf2" "${OUTPUT_DIR}/${firmware_filename}.uf2" || return $? - if [ -f ".pio/build/${pio_env_name}/firmware.zip" ]; then - copy_build_output ".pio/build/${pio_env_name}/firmware.zip" "${OUTPUT_DIR}/${firmware_filename}.zip" || return $? + python3 bin/uf2conv/uf2conv.py "${build_output_dir}/firmware.hex" -c -o "${build_output_dir}/firmware.uf2" -f 0xADA52840 || return $? + copy_build_output "${build_output_dir}/firmware.uf2" "${OUTPUT_DIR}/${firmware_filename}.uf2" || return $? + if [ -f "${build_output_dir}/firmware.zip" ]; then + copy_build_output "${build_output_dir}/firmware.zip" "${OUTPUT_DIR}/${firmware_filename}.zip" || return $? fi } collect_stm32_artifacts() { local env_name=$1 - local firmware_filename=$2 + local pio_env_name=$2 + local firmware_filename=$3 + local build_output_dir="${PIO_BUILD_DIR_OVERRIDE:-${PLATFORMIO_BUILD_DIR:-.pio/build}}/${pio_env_name}" - copy_build_output ".pio/build/${env_name}/firmware.bin" "${OUTPUT_DIR}/${firmware_filename}.bin" || return $? - copy_build_output ".pio/build/${env_name}/firmware.hex" "${OUTPUT_DIR}/${firmware_filename}.hex" || return $? + copy_build_output "${build_output_dir}/firmware.bin" "${OUTPUT_DIR}/${firmware_filename}.bin" || return $? + copy_build_output "${build_output_dir}/firmware.hex" "${OUTPUT_DIR}/${firmware_filename}.hex" || return $? } collect_rp2040_artifacts() { local env_name=$1 - local firmware_filename=$2 + local pio_env_name=$2 + local firmware_filename=$3 + local build_output_dir="${PIO_BUILD_DIR_OVERRIDE:-${PLATFORMIO_BUILD_DIR:-.pio/build}}/${pio_env_name}" - copy_build_output ".pio/build/${env_name}/firmware.bin" "${OUTPUT_DIR}/${firmware_filename}.bin" || return $? - copy_build_output ".pio/build/${env_name}/firmware.uf2" "${OUTPUT_DIR}/${firmware_filename}.uf2" || return $? + copy_build_output "${build_output_dir}/firmware.bin" "${OUTPUT_DIR}/${firmware_filename}.bin" || return $? + copy_build_output "${build_output_dir}/firmware.uf2" "${OUTPUT_DIR}/${firmware_filename}.uf2" || return $? } output_artifact_exists() { @@ -2068,10 +2090,10 @@ collect_build_artifacts() { collect_nrf52_artifacts "$env_name" "$pio_env_name" "$firmware_filename" ;; STM32_PLATFORM) - collect_stm32_artifacts "$env_name" "$firmware_filename" + collect_stm32_artifacts "$env_name" "$pio_env_name" "$firmware_filename" ;; RP2040_PLATFORM) - collect_rp2040_artifacts "$env_name" "$firmware_filename" + collect_rp2040_artifacts "$env_name" "$pio_env_name" "$firmware_filename" ;; *) echo "Unsupported or unknown platform for env: $env_name" @@ -2143,6 +2165,7 @@ build_firmware() { local had_platformio_build_unflags=0 local had_platformio_extra_scripts=0 local build_status + local -a pio_run_args=() env_platform=$(get_platform_for_env "$env_name") if ! is_supported_platform "$env_platform"; then @@ -2246,7 +2269,16 @@ build_firmware() { fi print_build_flags "$env_name" - pio run -e "$pio_env_name" + pio_run_args=(run -e "$pio_env_name") + if [[ "${PIO_BUILD_JOBS_OVERRIDE:-}" =~ ^[1-9][0-9]*$ ]]; then + pio_run_args+=(-j "$PIO_BUILD_JOBS_OVERRIDE") + fi + if [ "$env_platform" = "ESP32_PLATFORM" ]; then + # The custom mergebin target depends on firmware.bin, so it compiles and + # merges in one SCons invocation instead of paying startup twice. + pio_run_args+=(-t mergebin) + fi + pio "${pio_run_args[@]}" build_status=$? if [ "$build_status" -eq 0 ]; then collect_build_artifacts "$env_name" "$env_platform" "$pio_env_name" "$firmware_filename" @@ -2457,6 +2489,18 @@ run_resolved_build_targets() { return "$build_status" } +terminate_process_tree() { + local parent_pid=$1 + local child_pid + local -a child_pids=() + + mapfile -t child_pids < <(pgrep -P "$parent_pid" 2>/dev/null || true) + for child_pid in "${child_pids[@]}"; do + terminate_process_tree "$child_pid" + done + kill -TERM "$parent_pid" 2>/dev/null || true +} + run_logged_build_targets() { local targets=("$@") local env profile log_dir log_path log_tmp @@ -2464,11 +2508,34 @@ run_logged_build_targets() { local build_status=0 local overall_status=0 local preserved_log=0 + local worker_limit=${PROFILE_BUILD_WORKERS:-1} + local pio_job_limit=${OPTION3_PIO_JOBS:-8} + local next_index=0 + local candidate_index + local candidate_env + local candidate_key + local pid pio_env_key + local completed_pid + local running_pid + local interrupted=0 + local interrupt_pid + local -a running_pids=() + local -A active_pio_envs=() + local -A job_env_by_pid=() + local -A job_key_by_pid=() + local -A job_log_by_pid=() + local -A job_tmp_by_pid=() if [ ${#targets[@]} -eq 0 ]; then echo "No build targets resolved." return 1 fi + if ! [[ "$worker_limit" =~ ^[1-9][0-9]*$ ]]; then + worker_limit=1 + fi + if ! [[ "$pio_job_limit" =~ ^[1-9][0-9]*$ ]]; then + pio_job_limit=8 + fi if [ -n "$FIRMWARE_FILENAME_INFIX" ]; then profile=$FIRMWARE_FILENAME_INFIX @@ -2483,13 +2550,127 @@ run_logged_build_targets() { if [ ${#targets[@]} -gt 1 ]; then BATCH_BUILD_MODE=1 fi - for env in "${targets[@]}"; do - log_path="${log_dir}/${env}-${profile}.log" - log_tmp="${log_path}.tmp" + + if [ "$worker_limit" -le 1 ]; then + for env in "${targets[@]}"; do + log_path="${log_dir}/${env}-${profile}.log" + log_tmp="${log_path}.tmp" + preserved_log=0 + echo "Building ${env} (${profile}); log: ${log_path}" + build_firmware "$env" > "$log_tmp" 2>&1 + build_status=$? + if [ "$build_status" -eq 0 ] \ + && grep -q "^Skipping ${env}; existing artifacts found" "$log_tmp" \ + && [ -s "$log_path" ]; then + rm -f -- "$log_tmp" + preserved_log=1 + else + mv -f -- "$log_tmp" "$log_path" + fi + if [ "$build_status" -ne 0 ]; then + overall_status=1 + LOGGING_MATRIX_FAILURES+=("${env} (${profile}) -> ${log_path}") + echo "FAILED: ${env} (${profile}), status ${build_status}" + echo "FAILED: ${env} (${profile}), status ${build_status}" >> "$log_path" + else + echo "SUCCEEDED: ${env} (${profile})" + if [ "$preserved_log" -eq 0 ]; then + echo "SUCCEEDED: ${env} (${profile})" >> "$log_path" + fi + fi + done + BATCH_BUILD_MODE=$previous_batch_build_mode + return "$overall_status" + fi + + echo "Running up to ${worker_limit} target builds concurrently with ${pio_job_limit} PlatformIO jobs each." + mkdir -p -- ".pio/build-option3" || return 1 + trap 'interrupted=1; for interrupt_pid in "${running_pids[@]}"; do terminate_process_tree "$interrupt_pid"; done' INT TERM + + while [ "$next_index" -lt "${#targets[@]}" ] || [ ${#running_pids[@]} -gt 0 ]; do + while [ "$next_index" -lt "${#targets[@]}" ] \ + && [ ${#running_pids[@]} -lt "$worker_limit" ]; do + candidate_index=$next_index + candidate_env="" + candidate_key="" + while [ "$candidate_index" -lt "${#targets[@]}" ]; do + candidate_env=${targets[$candidate_index]} + candidate_key=$(get_pio_build_env "$candidate_env") + if [ -z "${active_pio_envs[$candidate_key]+x}" ]; then + break + fi + candidate_index=$((candidate_index + 1)) + done + if [ "$candidate_index" -ge "${#targets[@]}" ]; then + break + fi + + # Pull an available environment forward so a generated alias waiting on + # its base environment does not leave another worker idle. + if [ "$candidate_index" -ne "$next_index" ]; then + targets[candidate_index]=${targets[next_index]} + targets[next_index]=$candidate_env + fi + env=$candidate_env + pio_env_key=$candidate_key + log_path="${log_dir}/${env}-${profile}.log" + log_tmp="${log_path}.tmp" + echo "Building ${env} (${profile}); log: ${log_path}" + ( + BATCH_BUILD_MODE=1 + PIO_BUILD_JOBS_OVERRIDE=$pio_job_limit + PIO_BUILD_DIR_OVERRIDE=".pio/build-option3/${pio_env_key}" + export PLATFORMIO_BUILD_DIR="$PIO_BUILD_DIR_OVERRIDE" + build_firmware "$env" + ) > "$log_tmp" 2>&1 & + pid=$! + running_pids+=("$pid") + active_pio_envs["$pio_env_key"]=1 + job_env_by_pid["$pid"]=$env + job_key_by_pid["$pid"]=$pio_env_key + job_log_by_pid["$pid"]=$log_path + job_tmp_by_pid["$pid"]=$log_tmp + next_index=$((next_index + 1)) + done + + if [ ${#running_pids[@]} -eq 0 ]; then + echo "Unable to schedule remaining ${profile} targets." + overall_status=1 + break + fi + + completed_pid="" + while [ -z "$completed_pid" ]; do + for running_pid in "${running_pids[@]}"; do + if ! kill -0 "$running_pid" 2>/dev/null; then + completed_pid=$running_pid + break + fi + done + if [ -z "$completed_pid" ]; then + sleep 0.1 + fi + done + if wait "$completed_pid"; then + build_status=0 + else + build_status=$? + fi + if [ "$interrupted" -eq 1 ]; then + for interrupt_pid in "${running_pids[@]}"; do + wait "$interrupt_pid" 2>/dev/null || true + done + BATCH_BUILD_MODE=$previous_batch_build_mode + trap - INT TERM + echo "Interrupted ${profile} profile builds." + return 130 + fi + pid=$completed_pid + env=${job_env_by_pid[$pid]} + pio_env_key=${job_key_by_pid[$pid]} + log_path=${job_log_by_pid[$pid]} + log_tmp=${job_tmp_by_pid[$pid]} preserved_log=0 - echo "Building ${env} (${profile}); log: ${log_path}" - build_firmware "$env" > "$log_tmp" 2>&1 - build_status=$? if [ "$build_status" -eq 0 ] \ && grep -q "^Skipping ${env}; existing artifacts found" "$log_tmp" \ && [ -s "$log_path" ]; then @@ -2509,8 +2690,20 @@ run_logged_build_targets() { echo "SUCCEEDED: ${env} (${profile})" >> "$log_path" fi fi + + unset 'active_pio_envs[$pio_env_key]' + unset 'job_env_by_pid[$pid]' 'job_key_by_pid[$pid]' + unset 'job_log_by_pid[$pid]' 'job_tmp_by_pid[$pid]' + local -a remaining_pids=() + for running_pid in "${running_pids[@]}"; do + if [ "$running_pid" != "$pid" ]; then + remaining_pids+=("$running_pid") + fi + done + running_pids=("${remaining_pids[@]}") done BATCH_BUILD_MODE=$previous_batch_build_mode + trap - INT TERM return "$overall_status" } @@ -2631,6 +2824,7 @@ run_logging_matrix_build_targets() { local original_mqtt_debug_override=$MQTT_DEBUG_OVERRIDE local original_firmware_filename_infix=$FIRMWARE_FILENAME_INFIX local original_esp32_full_build=$ESP32_FULL_BUILD + local original_profile_build_workers=$PROFILE_BUILD_WORKERS local bluetooth_skip_count=0 local lora_ota_only_skip_count=0 local logging_target_count=0 @@ -2642,6 +2836,8 @@ run_logging_matrix_build_targets() { return 1 fi LOGGING_MATRIX_FAILURES=() + PROFILE_BUILD_WORKERS=$OPTION3_BUILD_WORKERS + echo "Option 3 parallelism: ${PROFILE_BUILD_WORKERS} target build(s), ${OPTION3_PIO_JOBS} PlatformIO job(s) per target." for target in "${targets[@]}"; do if is_mqtt_bridge_target "$target"; then @@ -2746,6 +2942,7 @@ run_logging_matrix_build_targets() { MQTT_DEBUG_OVERRIDE=$original_mqtt_debug_override FIRMWARE_FILENAME_INFIX=$original_firmware_filename_infix ESP32_FULL_BUILD=$original_esp32_full_build + PROFILE_BUILD_WORKERS=$original_profile_build_workers if [ ${#LOGGING_MATRIX_FAILURES[@]} -gt 0 ]; then echo "Logging matrix completed with ${#LOGGING_MATRIX_FAILURES[@]} failed build(s):" @@ -2893,7 +3090,11 @@ main() { prompt_for_resolved_firmware_version if is_automatic_profile_command "$1" \ || [ "$SINGLE_TARGET_FULL_BUILD" = "1" ]; then - prompt_for_logging_matrix_output_policy + if is_logging_matrix_command "$1"; then + prompt_for_logging_matrix_output_policy "resume" + else + prompt_for_logging_matrix_output_policy "clean" + fi else RESUME_BUILD_OUTPUT=0 fi diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index df8d70de..08addb83 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -530,7 +530,9 @@ bool NRF52Board::startOTAUpdate(const char *id, char reply[], bool force_ap) { Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - Bluefruit.begin(1, 0); + if (!Bluefruit.begin(1, 0)) { + return false; + } ota_ble_started = true; // To be consistent OTA DFU should be added first if it exists diff --git a/src/helpers/radiolib/CustomLLCC68.h b/src/helpers/radiolib/CustomLLCC68.h index 686b09ec..c988d677 100644 --- a/src/helpers/radiolib/CustomLLCC68.h +++ b/src/helpers/radiolib/CustomLLCC68.h @@ -2,10 +2,12 @@ #include -#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received -#define SX126X_IRQ_PREAMBLE_DETECTED 0x04 - class CustomLLCC68 : public LLCC68 { + uint32_t _preambleMillis = 66; + uint32_t _maxPayloadMillis = 3934; + uint32_t _activityAt = 0; + bool _headerSeen = false; + public: CustomLLCC68(Module *mod) : LLCC68(mod) { } @@ -78,10 +80,77 @@ class CustomLLCC68 : public LLCC68 { return true; // success } + int16_t startReceive() override { + return LLCC68::startReceive( + RADIOLIB_SX126X_RX_TIMEOUT_INF, + RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), + RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); + } + bool isReceiving() { - uint16_t irq = getIrqFlags(); - bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED); - return detected; + uint32_t irq = getIrqFlags(); + bool preamble = irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; + bool header = irq & RADIOLIB_SX126X_IRQ_HEADER_VALID; + bool headerError = irq & RADIOLIB_SX126X_IRQ_HEADER_ERR; + uint32_t now = millis(); + + if (headerError) { + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | + RADIOLIB_SX126X_IRQ_HEADER_VALID | + RADIOLIB_SX126X_IRQ_HEADER_ERR | + RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID); + _activityAt = 0; + _headerSeen = false; + return false; + } + if (!header && _headerSeen) { + _activityAt = 0; + _headerSeen = false; + return false; + } + if (header) { + if (!_headerSeen) { + _headerSeen = true; + _activityAt = now; + } + if (now - _activityAt > _maxPayloadMillis) { + MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis); + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | + RADIOLIB_SX126X_IRQ_HEADER_VALID | + RADIOLIB_SX126X_IRQ_HEADER_ERR | + RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID); + _activityAt = 0; + _headerSeen = false; + return false; + } + return true; + } + if (preamble) { + if (_activityAt == 0) { + _activityAt = now; + } + if (now - _activityAt > _preambleMillis) { + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED); + _activityAt = 0; + MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis); + return false; + } + return true; + } + + _activityAt = 0; + _headerSeen = false; + return false; + } + + void setPreambleMillis(uint32_t preambleMillis) { + _preambleMillis = preambleMillis; + MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis); + } + + void setMaxPayloadMillis(uint32_t payloadMillis) { + _maxPayloadMillis = payloadMillis; + MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis); } bool getRxBoostedGainMode() { @@ -89,4 +158,4 @@ class CustomLLCC68 : public LLCC68 { readRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1); return (rxGain == RADIOLIB_SX126X_RX_GAIN_BOOSTED); } -}; \ No newline at end of file +}; diff --git a/src/helpers/radiolib/CustomLLCC68Wrapper.h b/src/helpers/radiolib/CustomLLCC68Wrapper.h index 00cb6753..877d9757 100644 --- a/src/helpers/radiolib/CustomLLCC68Wrapper.h +++ b/src/helpers/radiolib/CustomLLCC68Wrapper.h @@ -10,11 +10,17 @@ public: protected: bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) override { - return ((CustomLLCC68 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE + bool success = ((CustomLLCC68 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE && ((CustomLLCC68 *)_radio)->setSpreadingFactor(sf) == RADIOLIB_ERR_NONE && ((CustomLLCC68 *)_radio)->setBandwidth(bw) == RADIOLIB_ERR_NONE && ((CustomLLCC68 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE && updatePreamble(sf); + if (!success) return false; + + PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf)); + ((CustomLLCC68 *)_radio)->setPreambleMillis(pm.preambleMillis); + ((CustomLLCC68 *)_radio)->setMaxPayloadMillis(pm.payloadMillis); + return true; } public: diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index 55115652..2416e062 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -74,6 +74,15 @@ class CustomLR1110 : public LR1110 { bool getRxBoostedGainMode() const { return _rx_boosted; } + int16_t startReceive() override { + // Make preamble detection visible to CAD while retaining RadioLib's + // normal RX-complete and error events. + return LR1110::startReceive( + RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED, + RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), + RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); + } + // BUSY high means the chip is asleep (RX duty-cycle sleep window) or mid // command; any SPI access would stall until the chip's next listen window. bool isChipBusy() { @@ -95,6 +104,12 @@ class CustomLR1110 : public LR1110 { _headerSeen = false; return false; } + if (!header && _headerSeen) { + // Another path consumed the header IRQ; reset only our local timer. + _activityAt = 0; + _headerSeen = false; + return false; + } if (header) { if (!_headerSeen) { _headerSeen = true; _activityAt = now; }; if (now - _activityAt > _maxPayloadMillis) { diff --git a/src/helpers/radiolib/CustomSX1262.h b/src/helpers/radiolib/CustomSX1262.h index 1ef152e7..4dd45264 100644 --- a/src/helpers/radiolib/CustomSX1262.h +++ b/src/helpers/radiolib/CustomSX1262.h @@ -140,6 +140,15 @@ class CustomSX1262 : public SX1262 { return RADIOLIB_ERR_NONE; } + int16_t startReceive() override { + // Make preamble detection visible to CAD while retaining RadioLib's + // normal RX-complete and error events. + return SX1262::startReceive( + RADIOLIB_SX126X_RX_TIMEOUT_INF, + RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), + RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); + } + bool isReceiving() { if (isChipBusy()) return false; // asleep, cannot be mid-receive @@ -154,6 +163,12 @@ class CustomSX1262 : public SX1262 { _headerSeen = false; return false; } + if (!header && _headerSeen) { + // Another path consumed the header IRQ; reset only our local timer. + _activityAt = 0; + _headerSeen = false; + return false; + } if (header) { if (!_headerSeen) { _headerSeen = true; _activityAt = now; }; if (now - _activityAt > _maxPayloadMillis) { diff --git a/src/helpers/radiolib/CustomSX1268.h b/src/helpers/radiolib/CustomSX1268.h index 0c6f828b..25aa64c0 100644 --- a/src/helpers/radiolib/CustomSX1268.h +++ b/src/helpers/radiolib/CustomSX1268.h @@ -2,10 +2,12 @@ #include -#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received -#define SX126X_IRQ_PREAMBLE_DETECTED 0x04 - class CustomSX1268 : public SX1268 { + uint32_t _preambleMillis = 66; + uint32_t _maxPayloadMillis = 3934; + uint32_t _activityAt = 0; + bool _headerSeen = false; + public: CustomSX1268(Module *mod) : SX1268(mod) { } @@ -78,10 +80,77 @@ class CustomSX1268 : public SX1268 { return true; // success } + int16_t startReceive() override { + return SX1268::startReceive( + RADIOLIB_SX126X_RX_TIMEOUT_INF, + RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), + RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); + } + bool isReceiving() { - uint16_t irq = getIrqFlags(); - bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED); - return detected; + uint32_t irq = getIrqFlags(); + bool preamble = irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; + bool header = irq & RADIOLIB_SX126X_IRQ_HEADER_VALID; + bool headerError = irq & RADIOLIB_SX126X_IRQ_HEADER_ERR; + uint32_t now = millis(); + + if (headerError) { + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | + RADIOLIB_SX126X_IRQ_HEADER_VALID | + RADIOLIB_SX126X_IRQ_HEADER_ERR | + RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID); + _activityAt = 0; + _headerSeen = false; + return false; + } + if (!header && _headerSeen) { + _activityAt = 0; + _headerSeen = false; + return false; + } + if (header) { + if (!_headerSeen) { + _headerSeen = true; + _activityAt = now; + } + if (now - _activityAt > _maxPayloadMillis) { + MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis); + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | + RADIOLIB_SX126X_IRQ_HEADER_VALID | + RADIOLIB_SX126X_IRQ_HEADER_ERR | + RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID); + _activityAt = 0; + _headerSeen = false; + return false; + } + return true; + } + if (preamble) { + if (_activityAt == 0) { + _activityAt = now; + } + if (now - _activityAt > _preambleMillis) { + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED); + _activityAt = 0; + MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis); + return false; + } + return true; + } + + _activityAt = 0; + _headerSeen = false; + return false; + } + + void setPreambleMillis(uint32_t preambleMillis) { + _preambleMillis = preambleMillis; + MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis); + } + + void setMaxPayloadMillis(uint32_t payloadMillis) { + _maxPayloadMillis = payloadMillis; + MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis); } bool getRxBoostedGainMode() { @@ -89,4 +158,4 @@ class CustomSX1268 : public SX1268 { readRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1); return (rxGain == RADIOLIB_SX126X_RX_GAIN_BOOSTED); } -}; \ No newline at end of file +}; diff --git a/src/helpers/radiolib/CustomSX1268Wrapper.h b/src/helpers/radiolib/CustomSX1268Wrapper.h index 43b44ca8..c9a961ce 100644 --- a/src/helpers/radiolib/CustomSX1268Wrapper.h +++ b/src/helpers/radiolib/CustomSX1268Wrapper.h @@ -14,11 +14,17 @@ public: protected: bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) override { - return ((CustomSX1268 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE + bool success = ((CustomSX1268 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE && ((CustomSX1268 *)_radio)->setSpreadingFactor(sf) == RADIOLIB_ERR_NONE && ((CustomSX1268 *)_radio)->setBandwidth(bw) == RADIOLIB_ERR_NONE && ((CustomSX1268 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE && updatePreamble(sf); + if (!success) return false; + + PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf)); + ((CustomSX1268 *)_radio)->setPreambleMillis(pm.preambleMillis); + ((CustomSX1268 *)_radio)->setMaxPayloadMillis(pm.payloadMillis); + return true; } public: