Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b6e0b41
Add OTA BLE service and device settings support
zjwhitehead Jan 26, 2026
2bf9acf
Update BLE OTA service to Espressif standard
zjwhitehead Jan 26, 2026
dd8cc21
Enable OTA rollback and confirm firmware validity
zjwhitehead Jan 26, 2026
4833b13
Refactor BLE OTA service to Espressif sector/CRC protocol
zjwhitehead Jan 26, 2026
a9a2b10
Update ota_service.cpp
zjwhitehead Jan 26, 2026
cd1304f
Enhance OTA service with image size tracking and validation
zjwhitehead Jan 26, 2026
faeb2ca
Update BLE OTA service UUIDs and protocol
zjwhitehead Jan 26, 2026
756d6fb
linting
zjwhitehead Jan 26, 2026
17f3d00
Remove extra internal logging
zjwhitehead Jan 27, 2026
d2fbb5b
Switch OTA CRC to CRC16-CCITT for compatibility
zjwhitehead Jan 28, 2026
8dcd3f5
Improve OTA BLE service and LVGL display handling
zjwhitehead Jan 28, 2026
266f5ec
Update main.cpp
zjwhitehead Jan 28, 2026
fa65b02
Remove unused alt_wire parameter from setupAltimeter
zjwhitehead Jan 28, 2026
f0b0b5a
Defer BLE advertising until after splash screen
zjwhitehead Jan 28, 2026
e77322e
Refactor boot sequence and RTOS setup for clarity
zjwhitehead Jan 28, 2026
5638fc0
linting and cleanup
zjwhitehead Jan 29, 2026
3e8a503
Enable app rollback and improve OTA validation logic
zjwhitehead Jan 29, 2026
eb8ab17
tweak LED ram usage
zjwhitehead Jan 29, 2026
5de7963
Refactor altimeter sensor handling to dedicated task
zjwhitehead Jan 29, 2026
fd40868
Refactor altimeter data sharing using FreeRTOS queue
zjwhitehead Jan 30, 2026
0e70ac1
Improve BLE OTA timeout and conn params
zjwhitehead Feb 17, 2026
b5cd41e
Merge branch 'master' into esp-idf
zjwhitehead Feb 17, 2026
b613c50
Merge remote-tracking branch 'origin/master' into esp-idf
zjwhitehead Mar 18, 2026
da81624
Rename parse_serial_commands to poll_serial_commands
zjwhitehead Mar 18, 2026
6fd4eda
Pin CI Python version + format source comments
zjwhitehead Mar 19, 2026
517e75d
Skip fastlink/controls during OTA and log stats
zjwhitehead Mar 19, 2026
be721b1
Fix OTA CRC log, image parse and write abort
zjwhitehead Mar 19, 2026
2d5aa78
Remove altimeter globals, reset OTA, tweak BLE
zjwhitehead Mar 19, 2026
f339065
Refactor OTA state, handlers, and abort flow
zjwhitehead Mar 19, 2026
dabda44
Update sdkconfig.OpenPPG-CESP32S3-CAN-SP140
zjwhitehead Mar 25, 2026
e39f027
Initial plan
Copilot Mar 25, 2026
ab89131
Use ESP_PWR_LVL_P9 enum for BLE TX power instead of raw integer
Copilot Mar 25, 2026
6e1491c
Merge pull request #99 from openppg/copilot/sub-pr-89
zjwhitehead Mar 25, 2026
177c7a6
Update build configs, remove altimeter & fixes
zjwhitehead Mar 26, 2026
f1b5ce5
lint
zjwhitehead Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
python-version: "3.13"
- name: Cache PlatformIO
uses: actions/cache@v5
with:
Expand Down Expand Up @@ -70,7 +70,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
python-version: "3.13"
- name: Cache PlatformIO
uses: actions/cache@v5
with:
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.16.0)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(openppg_controller)
7 changes: 7 additions & 0 deletions default_8MB.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x330000,
app1, app, ota_1, 0x340000,0x330000,
spiffs, data, spiffs, 0x670000,0x180000,
coredump, data, coredump,0x7F0000,0x10000,
2 changes: 1 addition & 1 deletion extra_script.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import subprocess

Import("env")
folder = env.GetProjectOption("src_folder")
folder = env.GetProjectOption("custom_src_folder")

# Generic
env.Replace(
Expand Down
56 changes: 28 additions & 28 deletions inc/sp140/altimeter.h
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
#ifndef INC_SP140_ALTIMETER_H_
#define INC_SP140_ALTIMETER_H_
#include "sp140/shared-config.h"
#include "sp140/structs.h"
#include <Arduino.h>
#include <CircularBuffer.hpp>
#include <freertos/semphr.h>
#ifndef INC_SP140_ALTIMETER_H_
#define INC_SP140_ALTIMETER_H_

#include "sp140/shared-config.h"
#include "sp140/structs.h"
#include <Arduino.h>
#include <CircularBuffer.hpp>
#include <freertos/semphr.h>

// Constants
#define VARIO_BUFFER_SIZE 50 // Number of samples to average for vertical speed
#define MAX_VERTICAL_SPEED 250.0f // Maximum vertical speed to display (m/s)
// Set up the barometer
bool setupAltimeter();
// Get the altitude (in meters)
float getAltitude(const STR_DEVICE_DATA_140_V1 &deviceData);
// Get the vertical speed in meters per second
float getVerticalSpeed();
// Set the ground altitude to the current altitude to compute AGL
void setGroundAltitude(const STR_DEVICE_DATA_140_V1 &deviceData);
// Get the temperature in degrees Celsius
float getBaroTemperature();
// Get the pressure in hPa
float getBaroPressure();

// Set up the barometer
bool setupAltimeter();

// Get the altitude (in meters)
float getAltitude(const STR_DEVICE_DATA_140_V1 &deviceData);

// Get the vertical speed in meters per second
float getVerticalSpeed();

// Set the ground altitude to the current altitude to compute AGL
void setGroundAltitude(const STR_DEVICE_DATA_140_V1 &deviceData);

// Get the temperature in degrees Celsius
float getBaroTemperature();

// Get the pressure in hPa
float getBaroPressure();

#endif // INC_SP140_ALTIMETER_H_
1 change: 0 additions & 1 deletion inc/sp140/ble.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@ extern NimBLECharacteristic* pDeviceStateCharacteristic;
extern NimBLEServer* pServer;
extern volatile uint16_t connectedHandle;
extern volatile bool deviceConnected;
extern volatile bool oldDeviceConnected;

#endif // INC_SP140_BLE_H_
6 changes: 6 additions & 0 deletions inc/sp140/ble/ble_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ void setupBLE();
// Allow modules to trigger advertising after disconnects.
void restartBLEAdvertising();

// Request fast connection parameters (15ms interval) for OTA transfers.
void requestFastConnParams();

// Restore normal connection parameters (~36ms interval) after OTA.
void requestNormalConnParams();

// Temporarily disable whitelist filtering so a new device can bond.
// Advertising reopens for ~60 seconds then whitelisting is restored.
void enterBLEPairingMode();
Expand Down
7 changes: 7 additions & 0 deletions inc/sp140/ble/ble_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,11 @@
// App writes 0x01 here to trigger a GetHwInfo request to the ESC
#define FAST_LINK_COMMAND_UUID "45A17003-B73B-49E1-8B39-5E9ED5E1B930"

// Espressif Standard OTA Service UUIDs (Android esp-ble-ota-android app)
static const NimBLEUUID OTA_SERVICE_UUID("00008018-0000-1000-8000-00805f9b34fb");
static const NimBLEUUID OTA_RECV_FW_UUID("00008020-0000-1000-8000-00805f9b34fb"); // Firmware data (Write/Indicate)
static const NimBLEUUID OTA_PROGRESS_UUID("00008021-0000-1000-8000-00805f9b34fb"); // Progress (Indicate)
static const NimBLEUUID OTA_COMMAND_UUID("00008022-0000-1000-8000-00805f9b34fb"); // Command (Write/Indicate)
static const NimBLEUUID OTA_CUSTOMER_UUID("00008023-0000-1000-8000-00805f9b34fb"); // Customer (Indicate)

#endif // INC_SP140_BLE_BLE_IDS_H_
30 changes: 30 additions & 0 deletions inc/sp140/ble/ota_service.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef INC_SP140_BLE_OTA_SERVICE_H_
#define INC_SP140_BLE_OTA_SERVICE_H_

#include <NimBLEDevice.h>

/**
* Initialize the OTA BLE service.
* @param pServer Pointer to the NimBLEServer instance.
*/
void initOtaBleService(NimBLEServer* pServer);

/**
* Check if an OTA update is currently in progress.
* @return true if OTA is active (should pause other tasks)
*/
bool isOtaInProgress();

/**
* Abort any in-progress OTA update and reset state.
* Safe to call even if no OTA is in progress.
*/
void abortOta();

/**
* Check if OTA has been idle too long and abort if so.
* Call periodically (e.g. from a monitoring task).
*/
void checkOtaTimeout();

#endif // INC_SP140_BLE_OTA_SERVICE_H_
1 change: 1 addition & 0 deletions inc/sp140/bms.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ extern STR_BMS_TELEMETRY_140 bmsTelemetryData;
extern BMS_CAN* bms_can;

// BMS functions
bool initBMSCAN(SPIClass* spi);
void updateBMSData();
void printBMSData();
17 changes: 17 additions & 0 deletions inc/sp140/device_settings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef INC_SP140_DEVICE_SETTINGS_H_
#define INC_SP140_DEVICE_SETTINGS_H_

#include "structs.h"
#include "esp32s3-config.h"
Comment on lines +4 to +5
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.

// Constants
extern const unsigned int DEFAULT_SCREEN_ROTATION;
void refreshDeviceData();
void writeDeviceData();
void resetDeviceData();
void poll_serial_commands();
void send_device_data();
bool sanitizeDeviceData();
void debugHardwareConfig(const HardwareConfig& config);

#endif // INC_SP140_DEVICE_SETTINGS_H_
136 changes: 68 additions & 68 deletions inc/sp140/esc.h
Original file line number Diff line number Diff line change
@@ -1,68 +1,68 @@
#ifndef INC_SP140_ESC_H_
#define INC_SP140_ESC_H_
#include <Arduino.h>
// Motor temp validity range (disconnected/invalid readings are represented as NaN)
constexpr float MOTOR_TEMP_VALID_MIN_C = -20.0f;
constexpr float MOTOR_TEMP_VALID_MAX_C = 140.0f;
inline bool isMotorTempValidC(float tempC) {
return tempC > MOTOR_TEMP_VALID_MIN_C && tempC <= MOTOR_TEMP_VALID_MAX_C;
}
#include "sp140/structs.h"
#include "../../inc/sp140/esp32s3-config.h"
#include <SineEsc.h>
#include <CanardAdapter.h>
void initESC();
void setESCThrottle(int throttlePWM);
void readESCTelemetry();
bool setupTWAI();
// Request ESC hardware info (HW ID, FW version, bootloader, serial number).
// Thread-safe: sets a flag consumed by readESCTelemetry() on its next tick.
// Also called automatically the first time the ESC connects.
void requestEscHardwareInfo();
// ESC Error Decoding Functions
String decodeRunningError(uint16_t errorCode);
String decodeSelfCheckError(uint16_t errorCode);
bool hasRunningError(uint16_t errorCode);
bool hasSelfCheckError(uint16_t errorCode);
bool hasCriticalRunningError(uint16_t errorCode);
bool hasWarningRunningError(uint16_t errorCode);
bool hasCriticalSelfCheckError(uint16_t errorCode);
// Individual running error bit checkers
bool hasOverCurrentError(uint16_t errorCode);
bool hasLockedRotorError(uint16_t errorCode);
bool hasOverTempError(uint16_t errorCode);
bool hasOverVoltError(uint16_t errorCode);
bool hasVoltagDropError(uint16_t errorCode);
bool hasThrottleSatWarning(uint16_t errorCode);
// Individual self-check error bit checkers
bool hasMotorCurrentOutError(uint16_t errorCode);
bool hasTotalCurrentOutError(uint16_t errorCode);
bool hasMotorVoltageOutError(uint16_t errorCode);
bool hasCapNTCError(uint16_t errorCode);
bool hasMosNTCError(uint16_t errorCode);
bool hasBusVoltRangeError(uint16_t errorCode);
bool hasBusVoltSampleError(uint16_t errorCode);
bool hasMotorZLowError(uint16_t errorCode);
bool hasMotorZHighError(uint16_t errorCode);
bool hasMotorVDet1Error(uint16_t errorCode);
bool hasMotorVDet2Error(uint16_t errorCode);
bool hasMotorIDet2Error(uint16_t errorCode);
bool hasSwHwIncompatError(uint16_t errorCode);
bool hasBootloaderBadError(uint16_t errorCode);
// for debugging
void dumpThrottleResponse(const sine_esc_SetThrottleSettings2Response *res);
void dumpESCMessages(); // dumps all messages to USBSerial
// External declaration of telemetry data structure
extern STR_ESC_TELEMETRY_140 escTelemetryData;
#endif // INC_SP140_ESC_H_
#ifndef INC_SP140_ESC_H_
#define INC_SP140_ESC_H_

#include <Arduino.h>

// Motor temp validity range (disconnected/invalid readings are represented as NaN)
constexpr float MOTOR_TEMP_VALID_MIN_C = -20.0f;
constexpr float MOTOR_TEMP_VALID_MAX_C = 140.0f;

inline bool isMotorTempValidC(float tempC) {
return tempC > MOTOR_TEMP_VALID_MIN_C && tempC <= MOTOR_TEMP_VALID_MAX_C;
}
#include "sp140/structs.h"
#include "../../inc/sp140/esp32s3-config.h"
#include <SineEsc.h>
#include <CanardAdapter.h>

void initESC();
void setESCThrottle(int throttlePWM);
void readESCTelemetry();
bool setupTWAI();

// Request ESC hardware info (HW ID, FW version, bootloader, serial number).
// Thread-safe: sets a flag consumed by readESCTelemetry() on its next tick.
// Also called automatically the first time the ESC connects.
void requestEscHardwareInfo();

// ESC Error Decoding Functions
String decodeRunningError(uint16_t errorCode);
String decodeSelfCheckError(uint16_t errorCode);
bool hasRunningError(uint16_t errorCode);
bool hasSelfCheckError(uint16_t errorCode);
bool hasCriticalRunningError(uint16_t errorCode);
bool hasWarningRunningError(uint16_t errorCode);
bool hasCriticalSelfCheckError(uint16_t errorCode);

// Individual running error bit checkers
bool hasOverCurrentError(uint16_t errorCode);
bool hasLockedRotorError(uint16_t errorCode);
bool hasOverTempError(uint16_t errorCode);
bool hasOverVoltError(uint16_t errorCode);
bool hasVoltagDropError(uint16_t errorCode);
bool hasThrottleSatWarning(uint16_t errorCode);

// Individual self-check error bit checkers
bool hasMotorCurrentOutError(uint16_t errorCode);
bool hasTotalCurrentOutError(uint16_t errorCode);
bool hasMotorVoltageOutError(uint16_t errorCode);
bool hasCapNTCError(uint16_t errorCode);
bool hasMosNTCError(uint16_t errorCode);
bool hasBusVoltRangeError(uint16_t errorCode);
bool hasBusVoltSampleError(uint16_t errorCode);
bool hasMotorZLowError(uint16_t errorCode);
bool hasMotorZHighError(uint16_t errorCode);
bool hasMotorVDet1Error(uint16_t errorCode);
bool hasMotorVDet2Error(uint16_t errorCode);
bool hasMotorIDet2Error(uint16_t errorCode);
bool hasSwHwIncompatError(uint16_t errorCode);
bool hasBootloaderBadError(uint16_t errorCode);

// for debugging
void dumpThrottleResponse(const sine_esc_SetThrottleSettings2Response *res);
void dumpESCMessages(); // dumps all messages to USBSerial

// External declaration of telemetry data structure
extern STR_ESC_TELEMETRY_140 escTelemetryData;

#endif // INC_SP140_ESC_H_
10 changes: 10 additions & 0 deletions inc/sp140/structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ typedef struct {
} STR_BMS_TELEMETRY_140;
#pragma pack(pop)

// Altimeter telemetry - read by multiple tasks, written by altimeter task only
struct AltimeterTelemetry {
float altitude; // Relative altitude AGL (meters)
float temperature; // Barometer temperature (°C)
float pressure; // Barometric pressure (hPa)
float verticalSpeed; // Vertical speed (m/s)
unsigned long lastUpdate; // Timestamp of last reading (millis)
bool connected; // Sensor available and responding
};

// Add this struct definition near other structs
struct MelodyRequest {
uint16_t *notes;
Expand Down
6 changes: 6 additions & 0 deletions partitions.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 0x4000,
otadata, data, ota, , 0x2000,
app0, app, ota_0, , 3M,
app1, app, ota_1, , 3M,
spiffs, data, spiffs, , 1500K,
22 changes: 8 additions & 14 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
; https://docs.platformio.org/page/projectconf.html

[platformio]
libr_dir = libraries
lib_dir = libraries
include_dir = inc
default_envs =
OpenPPG-CESP32S3-CAN-SP140
Expand All @@ -23,8 +23,11 @@ lib_ignore =
[env:OpenPPG-CESP32S3-CAN-SP140]
platform = espressif32@6.13.0
board = m5stack-stamps3
framework = arduino
src_folder = sp140
framework = arduino, espidf
board_build.f_cpu = 240000000L
board_build.flash_mode = qio
board_build.partitions = default_8MB.csv
custom_src_folder = sp140 ; Used by extra_script.py to rewrite PROJECT_SRC_DIR
; If esptool.py complains about missing intelhex, install into the tool package:
; ~/.platformio/penv/bin/python -m pip install intelhex -t ~/.platformio/packages/tool-esptoolpy
extra_scripts =
Expand All @@ -37,17 +40,8 @@ build_flags =
-I inc/sp140/lvgl
-D LV_LVGL_H_INCLUDE_SIMPLE
-D CORE_DEBUG_LEVEL=2
; NimBLE tuning
-D CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
-D CONFIG_BT_NIMBLE_MAX_BONDS=5
-D CONFIG_BT_NIMBLE_MAX_CCCDS=16
-D CONFIG_BT_NIMBLE_ROLE_CENTRAL=0
-D CONFIG_BT_NIMBLE_ROLE_OBSERVER=0
; Extended advertising disabled — NimBLE ext adv API has unrecoverable
; EBUSY (rc=15) state machine bug. Legacy advertising is reliable.
; 2M PHY for data transfer still works (negotiated after connection).
-D CONFIG_BT_NIMBLE_EXT_ADV=0

-D CONFIG_ARDUINO_LOOP_STACK_SIZE=8192
-Wno-error=format
build_type = debug
debug_speed = 12000
debug_tool = esp-builtin
Expand Down
Loading