Skip to content

ESP-IDF + BLE OTA#89

Merged
zjwhitehead merged 35 commits intomasterfrom
esp-idf
Mar 26, 2026
Merged

ESP-IDF + BLE OTA#89
zjwhitehead merged 35 commits intomasterfrom
esp-idf

Conversation

@zjwhitehead
Copy link
Member

@zjwhitehead zjwhitehead commented Jan 26, 2026

Migrates the project to an ESP‑IDF + Arduino hybrid build, adds BLE OTA firmware updates compatible with Espressif’s Android app, and tightens OTA robustness/rollback behavior.
It also cleans up settings persistence and lint compliance.

Highlights

  • ESP‑IDF + Arduino hybrid build
    Updated the build setup and CMake layout to compile under ESP‑IDF while keeping Arduino libraries and setup()/loop() behavior.
  • BLE OTA (Espressif Android app compatible)
    Implemented the sector/CRC BLE OTA protocol, aligned service/characteristic UUIDs, and wired the OTA command/ACK + sector ACK flow to match Espressif’s client.
  • OTA reliability improvements
    Added rollback marking on successful boot, image length validation, CRC validation, sector resync handling, and proper abort handling.
  • Device settings refactor
    Settings persistence moved into a dedicated module, keeping preferences validation and serialization consistent.

Notes for reviewers

  • OTA is blocked when the device is ARMED/CRUISING.
  • BLE telemetry notifications are paused during OTA to avoid throughput conflicts.
  • Partition table + SDK defaults are included to enable OTA slots and rollback.

Introduces OTA (Over-the-Air) firmware update BLE service with new UUIDs and implementation files. Adds device settings management (header and source), updates build system for ESP-IDF/Arduino hybrid, and includes new partition and configuration files for dual OTA support. Also updates BMS CAN initialization and various build flags for improved compatibility.
Refactored OTA BLE service to use Espressif standard UUIDs and characteristics, separating command, data, and status. Added checks to suppress BLE notifications during OTA updates across all telemetry and config services. Improves compatibility and prevents notification conflicts during firmware updates.
Added CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE to sdkconfig.defaults to support OTA rollback. In main.cpp, added logic to mark the firmware as valid and cancel rollback after successful boot, ensuring OTA updates are confirmed.
Reworks OTA BLE service to use Espressif's sector-based protocol with CRC16 validation, sector buffering, and explicit ACK/ERR responses. OTA UUIDs are now NimBLEUUID constants for compatibility with official Espressif OTA apps. Improves reliability and error handling, and merges command/status characteristics as per Espressif's protocol.
Added tracking of total image length and received bytes during OTA updates. The service now validates that the received image size matches the expected size before completing the update, improving reliability and error handling. Also refined CRC and sector error handling to allow client-side retries instead of aborting the OTA process immediately.
Revised BLE OTA characteristic UUIDs to match Android esp-ble-ota-android app requirements and updated protocol handling in ota_service.cpp. ACK and command response logic now use new characteristics and payload formats. Also added 'rbl' as an alias for the 'reboot' serial command.
@zjwhitehead
Copy link
Member Author

Needs tested with this https://github.com/EspressifApps/esp-ble-ota-android

@zjwhitehead zjwhitehead added this to the v7.5 milestone Jan 27, 2026
@zjwhitehead
Copy link
Member Author

Likely conflicts with the work for #69

Replaces the previous CRC16 implementation with CRC16-CCITT (poly 0x1021, init 0) in ota_service.cpp to match the Espressif Android app. Updates all relevant CRC checks and calculations to use the new function.
Switched OTA BLE characteristics to use notify instead of indicate for ACKs and command responses, and added NOTIFY property to relevant characteristics. Added packet count logging and sector write progress in OTA service. In LVGL flush callback, ensured flush completion is signaled on SPI timeout to prevent deadlock, and moved CS pin selection after SPI bus acquisition. Added boot and running partition info logging during setup.
The setupAltimeter function no longer requires the alt_wire parameter, so it has been removed from the function signature and all call sites. This simplifies the interface and eliminates unused code.
BLE advertising is now started after the splash screen completes, rather than during BLE setup. This change ensures the device does not advertise until the UI is ready, improving startup flow and user experience. Added log messages to clarify advertising state.
Reorganize the main setup() into explicit boot phases, separating RTOS primitive creation, hardware initialization, UI setup, and task creation. Remove startup synchronization semaphores and flags in favor of a deterministic, phased boot. Move all queue and mutex creation to dedicated functions called before any tasks are started. Simplify UI task startup and ensure all dependencies are initialized before tasks run.
App rollback is now enabled in the configuration. The OTA validation logic in main.cpp has been improved to only mark the firmware as valid after confirming the first boot post-update, ensuring rollback protection is properly activated.
Introduced AltimeterTelemetry struct and global instance for thread-safe sharing of barometric sensor data. Added a dedicated altimeterTask for polling the sensor at 10 Hz, reducing I2C contention and simplifying access to altitude, temperature, pressure, and vertical speed across tasks. Updated BLE and display logic to read from the shared telemetry struct instead of direct sensor calls.
Introduces an AltimeterData struct and a FreeRTOS queue for thread-safe sharing of altimeter telemetry between tasks. Updates BLE and display tasks to read from the queue when available, with fallback to direct sensor reads. The altimeter task now samples at 20 Hz and publishes data to the queue, improving concurrency and reducing I2C contention.
Add OTA timeout/abort handling and dynamic BLE connection parameter switching to improve OTA reliability and normal operation battery usage. Introduces requestFastConnParams/requestNormalConnParams and activeConnHandle to tighten connection interval for OTA (15ms) and relax it for telemetry (~36ms). Implements abortOta() and checkOtaTimeout() (30s idle timeout), hooks timeout check into the BLE update task, and aborts OTA on disconnect. Other tweaks: set BLE TX power, advertise only primary UUID to fit payload, include ble_core in OTA code, and remove unused oldDeviceConnected variable. Also permit Bash(grep:*) in local claude settings.
Update the function prototype in inc/sp140/device_settings.h: replace parse_serial_commands() with poll_serial_commands() to reflect a change in naming/behavior. Ensure implementation and callers are updated to match the new symbol.
Set GitHub Actions python-version to "3.13" and normalize inline comment/whitespace formatting across source files for consistency. Changes touch .github/workflows/config.yml, inc/sp140/structs.h and src/sp140/main.cpp and are purely stylistic (spacing and comment alignment) with no functional logic changes.
@zjwhitehead zjwhitehead modified the milestones: v7.5, v8.0 Mar 19, 2026
Include OTA service and add handling to avoid interfering with over-the-air updates. Introduces gFastLinkSkippedOtaCount and checks isOtaInProgress() in fastlink telemetry to skip/not notify during OTA (and increments the skipped-OTA counter). Updates periodic FASTLINK stats output to include skippedOta and OTA-in-progress flag. Also blocks arming and cruise activation while an OTA update is in progress, printing a brief message when those actions are attempted.
Adjust OTA handling in src/sp140/ble/ota_service.cpp: correct the printf order for CMD CRC failure (expected vs got), use explicit uint32_t casts when assembling the image length to avoid integer promotion issues, reset packetCount on START, and change write-failure handling to abort the OTA instead of attempting a retry (preventing partial-write offset corruption). Includes a clarifying comment about why abort is required.
@zjwhitehead zjwhitehead marked this pull request as ready for review March 19, 2026 18:26
@zjwhitehead zjwhitehead requested a review from Copilot March 19, 2026 18:26
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates the OpenPPG controller firmware to an ESP-IDF + Arduino hybrid build and adds a NimBLE-based OTA GATT service compatible with Espressif’s Android BLE OTA app, alongside related boot/rollback and settings refactors.

Changes:

  • Add BLE OTA service + pause BLE notifications during OTA; add OTA timeout/abort handling and post-boot rollback validation.
  • Restructure setup() into phased initialization and add an altimeter sampling task/telemetry scaffolding.
  • Introduce ESP-IDF CMake/project config, partition table for OTA slots, and updated CI/build configuration.

Reviewed changes

Copilot reviewed 28 out of 31 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/sp140/main.cpp Phased boot sequence, OTA rollback marking, OTA gating for arm/cruise, new altimeter task + queue, BLE notify pacing/OTA timeout checks.
src/sp140/lvgl/lvgl_updates.cpp Increase LVGL digit buffer sizes used with snprintf.
src/sp140/lvgl/lvgl_core.cpp Fix LVGL flush deadlock by calling lv_disp_flush_ready() on SPI mutex timeout; adjust CS handling.
src/sp140/globals.cpp Add global altimeterData struct instance.
src/sp140/esc.cpp Fix enum logging by casting TelemetryState to int.
src/sp140/device_settings.cpp Update includes for new settings module wiring.
src/sp140/buzzer.cpp Switch to ESP-IDF-style FreeRTOS include paths.
src/sp140/ble/ota_service.cpp New OTA GATT service implementing sector/CRC protocol + OTA state machine.
src/sp140/ble/fastlink_service.cpp Skip FastLink notifications during OTA and add OTA skip stats.
src/sp140/ble/config_service.cpp Skip throttle notifications during OTA.
src/sp140/ble/ble_core.cpp Register OTA service, add conn-param tuning helpers, abort OTA on disconnect, set MTU/TX power.
src/sp140/ble.cpp Remove unused oldDeviceConnected.
src/sp140/altimeter.cpp Reformat/normalize file; altimeter logic unchanged functionally.
src/sp140/CMakeLists.txt Add IDF component registration via broad source glob.
src/CMakeLists.txt Add IDF component registration for src/sp140/*.cpp with Arduino requirement.
sdkconfig.defaults Add default IDF settings (tick rate, Arduino autostart, BT enable, rollback enable, etc.).
sdkconfig.OpenPPG-CESP32S3-CAN-SP140 Commit generated IDF sdkconfig for the target environment.
platformio.ini Switch to arduino, espidf, set partitions CSV, adjust build flags.
partitions.csv Define NVS/otadata + app0/app1 OTA slots + spiffs layout.
inc/sp140/structs.h Add AltimeterTelemetry struct.
inc/sp140/globals.h Export altimeterData.
inc/sp140/esc.h Whitespace/line ending normalization.
inc/sp140/device_settings.h New public header for device settings module APIs.
inc/sp140/bms.h Add initBMSCAN(SPIClass*) declaration.
inc/sp140/ble/ota_service.h New OTA service API header.
inc/sp140/ble/ble_ids.h Add standard Espressif OTA UUID definitions.
inc/sp140/ble/ble_core.h Add conn-parameter tuning API declarations.
inc/sp140/ble.h Remove oldDeviceConnected extern.
inc/sp140/altimeter.h Whitespace/line ending normalization.
CMakeLists.txt Add top-level ESP-IDF project CMake entrypoint.
.github/workflows/config.yml Pin Python to 3.13 for PlatformIO build/test jobs.
Comments suppressed due to low confidence (4)

src/sp140/main.cpp:566

  • altimeterTask() calls getAltitude()/getVerticalSpeed() on a 20Hz loop, but those APIs mutate the shared vario buffer and are already called from other tasks (UI + CtrlSensor). This introduces a multi-writer race on the altitude buffer and increases I2C contention. Prefer a single sampling task that owns the buffer/sensor and publishes results (TelemetryHub/queue), or add proper locking inside the altimeter module around buffer updates/reads.
    src/sp140/main.cpp:879
  • esp_ota_mark_app_valid_cancel_rollback() return value is ignored. If marking the app valid fails (e.g., OTA data corruption / flash write issue), the device may remain in PENDING_VERIFY and roll back unexpectedly on next boot. Consider checking the returned esp_err_t and logging/handling failures explicitly.
    src/sp140/main.cpp:731
  • setup() no longer calls diagnosticsInit(), but diagnostics are still used elsewhere (e.g., planned restart marking / diag_sync). This likely disables persistent boot diagnostics and shutdown snapshot registration. Consider calling diagnosticsInit() early in Phase 1 after serial init (before any code that records/reads diagnostics).
    src/sp140/main.cpp:764
  • The barometer enable workaround (setupBarometer() sets GPIO 9 HIGH) is still present but is no longer called during hardware init. If the V2 board requires this pin, setupAltimeter() may fail or readings may stay invalid. Either call setupBarometer() before setupAltimeter() in Phase 3, or remove the function/comment if the workaround is obsolete.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +38 to +47
// Altimeter telemetry - single writer (altimeter task), multiple readers
AltimeterTelemetry altimeterData = {
.altitude = 0.0f,
.temperature = 0.0f,
.pressure = 0.0f,
.verticalSpeed = 0.0f,
.lastUpdate = 0,
.connected = false
};

Comment on lines +111 to +121
void abortOta() {
if (otaInProgress) {
if (updateHandle) esp_ota_abort(updateHandle);
otaInProgress = false;
requestNormalConnParams();
USBSerial.println("OTA: Aborted.");
}
sectorBufferLen = 0;
currentSectorIndex = 0;
receivedBytes = 0;
}
if (otaInProgress) {
// Defensive: Verify total bytes received match manifest
if (receivedBytes != imageTotalLen) {
USBSerial.printf("OTA Error: Size Mismatch Rx:%u Exp:%u\n", receivedBytes, imageTotalLen);
Comment on lines 308 to +315
initConfigBleService(pServer, uniqueId);
initFastLinkBleService(pServer);
initOtaBleService(pServer);

startAdvertising(pServer);

USBSerial.println("BLE device ready");
USBSerial.println("Waiting for a client connection...");
USBSerial.println("BLE device ready (advertising deferred)");
}
# This file was automatically generated for projects
# without default 'CMakeLists.txt' file.

FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/sp140/*.*)
Comment on lines +145 to +152
CONFIG_PARTITION_TABLE_SINGLE_APP=y
# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
# CONFIG_PARTITION_TABLE_CUSTOM is not set
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
Comment on lines +19 to +29
float getAltitude(const STR_DEVICE_DATA_140_V1& deviceData) {
if (bmpPresent && i2cMutex != nullptr) {
if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(20)) == pdTRUE) {
const float altitude = bmp.readAltitude(deviceData.sea_pressure);
xSemaphoreGive(i2cMutex);
float relativeAltitude = altitude - groundAltitude;

// Add new reading to buffer with timestamp
AltitudeReading reading = {relativeAltitude, millis()};
altitudeBuffer.push(reading);

Remove the AltimeterTelemetry extern declaration and its global instance to eliminate the unused/duplicated altimeter state. In ota_service::abortOta(), fully clear OTA-related state (updateHandle, updatePartition, imageTotalLen) so aborted updates leave no residual state. Simplify BLE startup log to "BLE device ready". Update platformio.ini comment for src_folder usage by extra_script.py.
Extract and consolidate OTA state/reset logic and command handling: add resetOtaState(), move CMD_START handling into handleCmdStart(), and centralize abort logic into abortOtaImpl() with an abortOta() wrapper. Expose isOtaInProgress(), relocate checkOtaTimeout() to use the new abort implementation, and remove duplicated functions/comments. These changes tidy control flow for OTA begin/abort, improve error handling, and make timeout/command handling clearer and more maintainable.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the firmware build toward an ESP-IDF + Arduino hybrid setup and adds a NimBLE-based BLE OTA implementation compatible with Espressif’s Android OTA app, along with boot/rollback handling and some settings/build cleanups.

Changes:

  • Added a new BLE OTA GATT service (sector/CRC protocol) and integrated OTA state into BLE notification flow and connection parameter tuning.
  • Reworked setup() into a phased boot sequence (sync primitives → hardware → UI → services → tasks → interfaces) and added OTA rollback validation on successful first boot.
  • Added ESP-IDF build scaffolding/config (CMakeLists, partitions, sdkconfig defaults) and refactored settings module headers/includes.

Reviewed changes

Copilot reviewed 27 out of 30 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/sp140/main.cpp Phased boot sequence, OTA gating for arm/cruise + BLE notifications, new altimeter task/queue, OTA rollback marking
src/sp140/ble/ota_service.cpp New BLE OTA service implementation (commands/data/CRC/sector ACKs) + timeout abort logic
src/sp140/ble/ble_core.cpp OTA service registration, connection param tuning helpers, disconnect-abort behavior, TX power config
inc/sp140/device_settings.h / src/sp140/device_settings.cpp Settings module interface and includes for the refactor
sdkconfig.*, partitions.csv, platformio.ini, CMakeLists.txt ESP-IDF+Arduino build wiring, partitioning, and CI/build configuration updates
src/sp140/lvgl/*, src/sp140/esc.cpp, src/sp140/buzzer.cpp, etc. Targeted robustness/lint/format adjustments supporting the migration
Comments suppressed due to low confidence (5)

src/sp140/main.cpp:731

  • setup() no longer calls diagnosticsInit(), but watchdogTask() still calls diagnosticsRefreshHeartbeat() and other code paths rely on diagnostics state/history. This looks like a regression from the previous boot sequence (diagnostics init was done up-front) and will likely break persistent boot diagnostics and shutdown handler registration. Re-introduce diagnosticsInit() early in setup() (before tasks start) or move diagnostics initialization into an appropriate boot phase.
    src/sp140/main.cpp:593
  • setupBarometer() is still present (and documented as a V2-board fix), but it’s no longer called anywhere in the new phased setup(). If that GPIO enable is still required for the BMP3xx to function, the altimeter will silently fail on those boards. Either call setupBarometer() during hardware init (before setupAltimeter()), or remove/replace the function and its comment so this doesn’t become dead/forgotten code.
    src/sp140/main.cpp:573
  • altimeterTask() adds an additional ~20Hz caller of getAltitude() / getBaroTemperature() / getVerticalSpeed(), but there are no consumers of altimeterQueue and both ctrlSensorTask and refreshDisplay already call getAltitude() (the UI comment even notes I2C mutex contention). As written, this increases I2C traffic/lock contention without any benefit. Either (a) remove the task/queue, or (b) make this task the sole sensor reader and have UI/TelemetryHub consume from the queue (or TelemetryHub) to eliminate contention.
    src/sp140/main.cpp:644
  • eepromSemaphore is created and documented as guarding Preferences writes, but it is not referenced anywhere else in the codebase (no takes/gives outside this function). This adds complexity without providing synchronization. Either wire it into the settings persistence paths (e.g. writeDeviceData() / diagnostics prefs) or remove it to avoid a false sense of thread safety.
    src/sp140/main.cpp:866
  • The comment says setupBLE() creates services but does not start advertising, and then restartBLEAdvertising() is called later. However, setupBLE() currently calls startAdvertising(pServer) (see ble_core.cpp), so advertising is already started by the time Phase 7 runs. Please either adjust the comment/remove the redundant restartBLEAdvertising() call, or change setupBLE() so it truly defers advertising until Phase 7.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4 to +5
#include "structs.h"
#include "esp32s3-config.h"
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

device_settings.h includes "structs.h" and "esp32s3-config.h" without the sp140/ prefix. With the current include path setup (e.g. include_dir = inc), these headers won’t be found (there is no inc/structs.h), which will break compilation for any TU including this header. Update the includes to the correct paths (e.g. sp140/structs.h and sp140/esp32s3-config.h) or adjust the build include directories consistently.

Suggested change
#include "structs.h"
#include "esp32s3-config.h"
#include "sp140/structs.h"
#include "sp140/esp32s3-config.h"

Copilot uses AI. Check for mistakes.
abortOtaImpl();
}
} else {
USBSerial.printf("OTA Error: CRC fail exp 0x%04X got 0x%04X\n", rxCrc, calcCrc);
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CRC failure log message swaps expected vs actual: rxCrc is the received CRC and calcCrc is the calculated one, but the log prints exp rxCrc got calcCrc. This makes troubleshooting OTA corruption confusing. Swap the arguments (or update the text) so “expected” corresponds to the calculated CRC.

Suggested change
USBSerial.printf("OTA Error: CRC fail exp 0x%04X got 0x%04X\n", rxCrc, calcCrc);
USBSerial.printf("OTA Error: CRC fail exp 0x%04X got 0x%04X\n", calcCrc, rxCrc);

Copilot uses AI. Check for mistakes.
Comment on lines +282 to +284
// Set TX power to +9dBm for reliable connections at distance
NimBLEDevice::setPower(9);

Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NimBLEDevice::setPower(9) uses a raw integer value; in ESP-IDF/NimBLE APIs the power argument is typically an enum (e.g. ESP_PWR_LVL_P9), and numeric values don’t map 1:1 to dBm. Using a literal risks selecting the wrong power level and makes intent unclear. Prefer the appropriate enum constant (and optional power type) for +9dBm.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +145 to +150
CONFIG_PARTITION_TABLE_SINGLE_APP=y
# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
# CONFIG_PARTITION_TABLE_CUSTOM is not set
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv"
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sdkconfig selects the single-app partition table (CONFIG_PARTITION_TABLE_SINGLE_APP=y and CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv"), which conflicts with the repo’s partitions.csv (app0/app1/otadata) and the PR’s OTA/rollback goals. If this sdkconfig is used for builds, OTA slots/otadata won’t exist at runtime. Switch to a two-OTA or custom partition table selection (and ensure the build actually uses partitions.csv).

Suggested change
CONFIG_PARTITION_TABLE_SINGLE_APP=y
# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
# CONFIG_PARTITION_TABLE_CUSTOM is not set
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv"
# CONFIG_PARTITION_TABLE_SINGLE_APP is not set
# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"

Copilot uses AI. Check for mistakes.
Comment on lines +310 to +331
void abortOtaImpl() {
if (otaInProgress) {
if (updateHandle) esp_ota_abort(updateHandle);
otaInProgress = false;
requestNormalConnParams();
USBSerial.println("OTA: Aborted.");
}
resetOtaState();
}

static OtaCommandCallback cmdCallback;
static OtaDataCallback dataCallback;

} // namespace

void abortOta() { abortOtaImpl(); }

void checkOtaTimeout() {
if (otaInProgress && (millis() - lastOtaActivityMs > OTA_TIMEOUT_MS)) {
USBSerial.println("OTA: Idle timeout, aborting.");
abortOtaImpl();
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkOtaTimeout() can call abortOtaImpl() from a non-BLE task context (it’s invoked from bleNotifyTask()), while OtaDataCallback::onWrite() may concurrently be appending to sectorBuffer and calling esp_ota_write(updateHandle, ...). Since abortOtaImpl() resets updateHandle/state without synchronization, this creates a real race (use-after-abort / corrupted OTA state). Protect all OTA state transitions and esp_ota_* calls with a mutex/critical section, or ensure timeout/abort runs in the same serialized context as the BLE write callbacks.

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +6
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/sp140/*.*)

idf_component_register(SRCS ${app_sources})
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FILE(GLOB_RECURSE app_sources ... *.*) will pick up non-source files (headers, CMakeLists, etc.) and can cause ESP-IDF to try compiling unsupported inputs. It also omits include dirs/requirements, so the component may not build even if the glob works. Restrict the glob to compilable sources (e.g. *.c/*.cpp/*.S) and declare INCLUDE_DIRS / REQUIRES as needed.

Suggested change
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/sp140/*.*)
idf_component_register(SRCS ${app_sources})
FILE(GLOB_RECURSE app_sources
${CMAKE_SOURCE_DIR}/src/sp140/*.c
${CMAKE_SOURCE_DIR}/src/sp140/*.cpp
${CMAKE_SOURCE_DIR}/src/sp140/*.S
)
idf_component_register(SRCS ${app_sources}
INCLUDE_DIRS ".")

Copilot uses AI. Check for mistakes.
zjwhitehead and others added 2 commits March 25, 2026 18:41
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link

Copilot AI commented Mar 25, 2026

@zjwhitehead I've opened a new pull request, #99, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 4 commits March 25, 2026 22:46
Use ESP_PWR_LVL_P9 enum for BLE TX power
Add 8MB partition table (default_8MB.csv) and switch PlatformIO/project settings to use QIO 8MB flash, 240MHz CPU and custom partition table. Fix platformio.ini typos (lib_dir) and rename src_folder -> custom_src_folder (also updated extra_script.py). Remove NimBLE tuning build flags and enable Bluedroid in sdkconfigs; enable several mbedTLS options and adjust core/flash/rollback settings. Code fixes: stop implicit BLE advertising in setupBLE, correct CRC args in OTA printf, tighten core-dump checks, and remove altimeter task/queue and EEPROM semaphore (cleanup of related declarations/creates/start). Also call diagnosticsInit earlier in setup.
@zjwhitehead zjwhitehead merged commit bedf3ac into master Mar 26, 2026
7 checks passed
@zjwhitehead zjwhitehead deleted the esp-idf branch March 26, 2026 01:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants