Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions .github/workflows/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
python-version: "3.11"
- run: pip install cpplint
- run: cpplint --linelength 140 --filter=-legal/copyright,-runtime/int,-build/include_subdir,-readability/casting,-readability/todo,-build/include_order,-build/include_what_you_use --recursive ./inc/ ./lib/ ./src/

Expand All @@ -40,7 +40,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.x
python-version: "3.11"
- 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.11"
- name: Cache PlatformIO
uses: actions/cache@v5
with:
Expand Down
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python 3.11.9
3 changes: 1 addition & 2 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"ms-vscode.cpptools-extension-pack",
"pioarduino.pioarduino-ide",
"platformio.platformio-ide"
],
"unwantedRecommendations": [
Expand Down
172 changes: 141 additions & 31 deletions SIMPLE_MONITOR_README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,160 @@
# Sensor Monitoring System
# Simple Sensor Monitoring System

## Overview
Real-time health monitoring for ESP32 electric ultralight sensors. Monitors ESC, BMS, altimeter, and system health with threshold-based alerting.
A lightweight, category-based monitoring system for ESP32 electric ultralight sensors. Features intelligent alert suppression when controllers disconnect and modern OOP design with device categories.

## Features
- **Comprehensive**: 50+ sensors monitored across all flight systems
- **Smart Alerting**: Hysteresis protection prevents false alerts
- **Multiple Outputs**: Serial, display, and extensible logging
- **Efficient**: Minimal overhead, change-only notifications
## Design Philosophy
- **Safety-First**: Automatically suppresses ESC alerts when ESC disconnected, BMS alerts when BMS disconnected
- **Category-Based**: Organizes sensors by device type (ESC, BMS, ALTIMETER, INTERNAL) for grouped management
- **Modern OOP**: Interface-based design with virtual methods and clean inheritance
- **Thread-Safe**: Queue-based telemetry snapshots for safe multi-task operation

## What's Monitored
## Usage

### ESC
- Temperatures (MOS, MCU, CAP, Motor)
- Error states (over-current, over-temp, voltage issues)
- Hardware faults and diagnostics
### Device Categories & Alert Suppression
```cpp
enum class SensorCategory {
ESC, // ESC temps, motor temp, ESC errors
BMS, // Battery voltage, current, temps, SOC
ALTIMETER, // Barometric sensors
INTERNAL // CPU temp, system sensors
};

### BMS
- Cell voltages and temperature sensors
- State of charge and charge/discharge status
- Voltage differential monitoring
// When ESC disconnects: All ESC alerts automatically cleared
// When BMS disconnects: All BMS alerts automatically cleared
// ALTIMETER/INTERNAL: Always monitored (no connection dependency)
```

### System
- CPU temperature and altimeter readings
- System health and diagnostics
### Current Monitoring Coverage
```cpp
// ESC Category (suppressed when ESC disconnected):
// - ESC MOS: Warning 90°C, Critical 110°C
// - ESC MCU: Warning 80°C, Critical 95°C
// - ESC CAP: Warning 85°C, Critical 100°C
// - Motor: Warning 90°C, Critical 110°C
// - ESC Error Conditions (overcurrent, overtemp, etc.)

## How It Works
// BMS Category (suppressed when BMS disconnected):
// - Battery voltage, current, SOC, cell voltages
// - BMS temperatures, charge/discharge MOS status
```

System automatically monitors all sensors and alerts on threshold violations. Hysteresis prevents false alerts from sensor noise.
### Adding New Sensors
```cpp
// Example: New ESC sensor
static SensorMonitor* newEscSensor = new SensorMonitor(
SensorID::ESC_New_Sensor, // Unique ID from enum
SensorCategory::ESC, // Category determines suppression behavior
{.warnLow = 10, .warnHigh = 80, .critLow = 5, .critHigh = 90},
[]() { return escTelemetryData.new_value; }, // Data source
&multiLogger // Fan-out to multiple outputs
);
monitors.push_back(newEscSensor);

### Alert Levels
- **OK**: Normal operation
- **WARN**: Advisory thresholds exceeded
- **CRIT**: Critical thresholds requiring attention
// Example: Boolean error monitor
static BooleanMonitor* newError = new BooleanMonitor(
SensorID::ESC_New_Error,
SensorCategory::ESC,
[]() { return checkErrorCondition(); },
true, // Alert when condition is true
AlertLevel::CRIT_HIGH,
&multiLogger
);
monitors.push_back(newError);
```

### Example Output
```
[15234] [WARN_HIGH] ESC_MOS_Temp = 92.50
[15467] [CRIT_HIGH] Motor_Temp = 112.30
[15890] [OK] ESC_MOS_Temp = 88.20
[16123] ESC disconnected - clearing all ESC alerts
[16450] [WARN_LOW] BMS_SOC = 12.3
```

## Easy Extensions

### SD Card Logging
```cpp
struct SDLogger : ILogger {
void log(SensorID id, AlertLevel lvl, float v) override {
File logFile = SD.open("/alerts.log", FILE_APPEND);
logFile.printf("%lu,%s,%d,%.2f\n", millis(), sensorIDToString(id), (int)lvl, v);
logFile.close();
}
};
```

### Custom Alert Processing
```cpp
struct CustomLogger : ILogger {
void log(SensorID id, AlertLevel lvl, float v) override {
// Custom processing based on sensor category
SensorCategory cat = getSensorCategory(id);
if (cat == SensorCategory::ESC && lvl >= AlertLevel::CRIT_HIGH) {
triggerEmergencyShutdown();
}
}
};
```

### Integration
- Runs every 40ms in FreeRTOS task
- Displays alerts on screen with haptic feedback
- Serial logging for diagnostics
- Thread-safe with minimal CPU overhead
### Multiple Outputs (Built-in)
```cpp
// MultiLogger automatically fans out to all registered sinks
multiLogger.addSink(&serialLogger); // Console output
multiLogger.addSink(&uiLogger); // LVGL alerts
multiLogger.addSink(&customLogger); // Your custom handler

// All monitors automatically use multiLogger
```

## Architecture

```cpp
IMonitor (interface)
├── virtual SensorID getSensorID() = 0
├── virtual SensorCategory getCategory() = 0
└── virtual void check() = 0

SensorMonitor : IMonitor BooleanMonitor : IMonitor
├── SensorID id ├── SensorID id
├── SensorCategory category ├── SensorCategory category
├── Thresholds thr ├── std::function<bool()> read
├── std::function<float()> read ├── bool alertOnTrue
└── ILogger* logger └── AlertLevel level

ILogger (interface)
├── SerialLogger (debug output)
├── AlertUILogger (LVGL display)
└── MultiLogger (fan-out to multiple sinks)

Categories & Connection Logic:
├── ESC sensors → suppressed when escState != CONNECTED
├── BMS sensors → suppressed when bmsState != CONNECTED
├── ALTIMETER sensors → always active
└── INTERNAL sensors → always active
```

## Integration
- **Queue-Based**: Dedicated monitoring task receives telemetry snapshots via FreeRTOS queue
- **Thread-Safe**: No direct access to volatile telemetry data from monitoring task
- **Connection-Aware**: Automatically clears alerts when devices disconnect
- **Smart Suppression**: Only runs ESC monitors when ESC connected, BMS monitors when BMS connected
- **Change Detection**: Only logs when alert level changes (no spam)
- **Zero Overhead**: Minimal CPU usage when all sensors in OK state

## Memory Usage
- **Code Size**: ~3KB total (includes OOP infrastructure, queue handling, UI integration)
- **Per Sensor**: ~150 bytes (SensorMonitor) or ~120 bytes (BooleanMonitor)
- **Static Allocation**: All monitors allocated at compile-time (embedded-friendly)
- **Queue Memory**: ~200 bytes for telemetry snapshot queue
- **Category Lookup**: O(1) via virtual methods (no external mapping tables)

## Key Benefits
1. **Safety**: ESC alerts disappear when ESC disconnects (no false warnings during connection issues)
2. **Maintainability**: Add new sensors by category, automatic suppression behavior
3. **Robustness**: Thread-safe design prevents race conditions between telemetry and monitoring
4. **Extensibility**: Clean OOP interfaces for new monitor types and output methods
5. **Performance**: Smart suppression reduces unnecessary checks when devices offline

The system provides comprehensive flight safety monitoring while maintaining performance for electric ultralight operation.
This design balances safety, maintainability, and performance - essential for flight-critical embedded systems.
3 changes: 2 additions & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ lib_ignore =


[env:OpenPPG-CESP32S3-CAN-SP140]
platform = espressif32@6.12.0
platform = https://github.com/pioarduino/platform-espressif32.git#55.03.35
board = m5stack-stamps3
framework = arduino
src_folder = sp140
Expand All @@ -38,6 +38,7 @@ build_flags =
-D LV_LVGL_H_INCLUDE_SIMPLE
; Disable legacy individual BLE characteristics (use packed binary only)
-D DISABLE_LEGACY_BLE_TELEMETRY
-D USBSerial=Serial

build_type = debug
debug_speed = 12000
Expand Down
12 changes: 6 additions & 6 deletions src/sp140/buzzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ static bool buzzerInitialized = false;
* @return Returns true if initialization was successful, false otherwise
*/
bool initBuzz() {
// Setup LEDC channel for buzzer
ledcSetup(BUZZER_PWM_CHANNEL, BUZZER_PWM_FREQUENCY, BUZZER_PWM_RESOLUTION);
ledcAttachPin(board_config.buzzer_pin, BUZZER_PWM_CHANNEL);
// Setup LEDC channel for buzzer (Arduino-ESP32 3.x API)
// Channels are implicitly allocated; attach pin to channel and set frequency/resolution
ledcAttach(board_config.buzzer_pin, BUZZER_PWM_FREQUENCY, BUZZER_PWM_RESOLUTION);
buzzerInitialized = true;
return true;
}
Expand All @@ -43,9 +43,9 @@ void startTone(uint16_t frequency) {
if (!buzzerInitialized || !ENABLE_BUZZ) return;

// Change the frequency for this channel
ledcChangeFrequency(BUZZER_PWM_CHANNEL, frequency, BUZZER_PWM_RESOLUTION);
ledcWriteTone(board_config.buzzer_pin, frequency);
// Set 50% duty cycle (square wave)
ledcWrite(BUZZER_PWM_CHANNEL, 128);
ledcWrite(board_config.buzzer_pin, 128);
}

/**
Expand All @@ -55,7 +55,7 @@ void stopTone() {
if (!buzzerInitialized) return;

// Set duty cycle to 0 to stop the tone
ledcWrite(BUZZER_PWM_CHANNEL, 0);
ledcWrite(board_config.buzzer_pin, 0);
}

/**
Expand Down
14 changes: 9 additions & 5 deletions src/sp140/esc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ static CanardAdapter adapter;
static uint8_t memory_pool[1024] __attribute__((aligned(8)));
static SineEsc esc(adapter);
static unsigned long lastSuccessfulCommTimeMs = 0; // Store millis() time of last successful ESC comm
static bool escReady = false;


STR_ESC_TELEMETRY_140 escTelemetryData = {
Expand All @@ -36,14 +37,13 @@ void initESC() {
}

adapter.begin(memory_pool, sizeof(memory_pool));
adapter.setLocalNodeId(LOCAL_NODE_ID);
esc.begin(0x20); // Default ID for the ESC
adapter.setLocalNodeId(LOCAL_NODE_ID);

// Set idle throttle only if ESC is found
const uint16_t IdleThrottle_us = 10000; // 1000us (0.1us resolution)
esc.setThrottleSettings2(IdleThrottle_us);
// Defer sending throttle until after first adapter process to avoid null pointer in CANARD
adapter.processTxRxOnce();
vTaskDelay(pdMS_TO_TICKS(20)); // Wait for ESC to process the command
vTaskDelay(pdMS_TO_TICKS(20)); // Give ESC time to be ready
escReady = true;
}

/**
Expand All @@ -54,6 +54,10 @@ void initESC() {
* Important: The ESC requires messages at least every 300ms or it will reset
*/
void setESCThrottle(int throttlePWM) {
// Ensure TWAI/ESC subsystem is initialized
if (!escTwaiInitialized || !escReady) {
return;
}
// Input validation
if (throttlePWM < 1000 || throttlePWM > 2000) {
return; // Ignore invalid throttle values
Expand Down
12 changes: 10 additions & 2 deletions src/sp140/sp140.ino
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,15 @@ void setupAnalogRead() {
void setupWatchdog() {
#ifndef OPENPPG_DEBUG
// Initialize Task Watchdog
ESP_ERROR_CHECK(esp_task_wdt_init(3000, true)); // 3 second timeout, panic on timeout
esp_task_wdt_config_t twdt_config = {
.timeout_ms = 3000,
.idle_core_mask = 0, // do not subscribe idle tasks
.trigger_panic = true,
};
esp_err_t wdt_init_result = esp_task_wdt_init(&twdt_config);
if (wdt_init_result != ESP_OK && wdt_init_result != ESP_ERR_INVALID_STATE) {
ESP_ERROR_CHECK(wdt_init_result);
}
#endif // OPENPPG_DEBUG
}

Expand All @@ -617,8 +625,8 @@ void setup() {

// Pull CSB (pin 42) high to activate I2C mode
// temporary fix TODO remove
digitalWrite(42, HIGH);
pinMode(42, OUTPUT);
digitalWrite(42, HIGH);

// Initialize LVGL mutex before anything else
lvglMutex = xSemaphoreCreateMutex();
Expand Down
Loading