Home / Documentation / Firmware Reference
ESP32 firmware architecture, sensor drivers, alert state machine, FreeRTOS task structure, and MQTT/BLE telemetry pipeline for the Apex Security jacket.
The Apex Security firmware runs on the ESP32-WROOM-32 using the Arduino framework on top of FreeRTOS. Two cores are used deliberately: Core 0 handles time-critical sensor sampling and the alert finite state machine, while Core 1 manages WiFi, MQTT publishing, and BLE GATT notifications. This separation ensures that a slow network operation never delays a fall or E-field alert.
Core 0 — Sensor polling (MPU6050 @ 100 Hz, MAX30102 @ 25 Hz, MLX90614 @ 10 Hz, LDR/IR @ 20 Hz), fall detection algorithm, alert FSM, haptic + buzzer output.
Core 1 — WiFi stack, MQTT client (PubSubClient), BLE GATT server, OTA update handler, heartbeat watchdog.
FreeRTOS queues pass sensor readings from sensorTask to alertTask. A shared event group (xEventGroupCreate) signals alert conditions to commTask for immediate wireless transmission.
All tasks are statically allocated. Priority levels are assigned to ensure sensor acquisition is never pre-empted by communication tasks during a safety-critical window.
| Task Name | Core | Priority | Stack | Responsibility |
|---|---|---|---|---|
| sensorTask | Core 0 | 2 | 8 KB | I2C polling, ADC reads, circular buffer fill |
| alertTask | Core 0 | 3 | 4 KB | Fall FSM, threshold checks, GPIO alert outputs |
| commTask | Core 1 | 1 | 8 KB | MQTT publish, BLE notify, WiFi reconnect |
| otaTask | Core 1 | 1 | 4 KB | OTA update listener (idle loop) |
| watchdogTask | Core 1 | 2 | 2 KB | Hardware watchdog reset every 30 s |
xSensorQueue (depth 10) passes SensorPacket structs from sensorTask → alertTask. xI2CSemaphore (binary) guards shared I2C bus access. xAlertEventGroup notifies commTask of urgent events using bit flags (BIT0 = fall, BIT1 = EField, BIT2 = vitals).
Each sensor has a dedicated driver module. All I2C drivers share the Wire instance guarded by xI2CSemaphore to prevent bus collisions during concurrent reads.
Library: Adafruit_MPU6050. Configured at ±8g / ±500°/s range. Interrupt pin (GPIO 23) wakes the ESP32 from light sleep on sudden movement. Raw accelerometer XYZ values are fed into the SVM computation at 100 Hz.
Library: SparkFun MAX3010x. Runs in HR+SpO₂ multi-LED mode. A 25-sample circular buffer feeds a peak-detection algorithm. Readings are averaged over 4 s windows to reduce motion artifact noise.
Library: Adafruit_MLX90614. Polled at 10 Hz. Both object temperature (skin) and ambient temperature are read each cycle. The delta (T_skin − T_ambient) is used for heat-stress scoring alongside absolute skin temperature.
IR module (FC-51) on GPIO 34 — digital read, active LOW when object detected. LDR on GPIO 35 — 12-bit ADC reading, averaged over 8 samples per cycle. Both are read in the same 20 Hz polling loop.
The multi-stage fall detection algorithm uses a finite state machine with five states. Each transition requires specific sensor conditions to be met within defined time windows, preventing false positives from ordinary industrial movements.
| State | Condition | Threshold | Timeout |
|---|---|---|---|
| IDLE → FREE_FALL | SVM drops below threshold | < 0.5 g | — |
| FREE_FALL → IMPACT | SVM spike after free fall | > 3.0 g | 1.5 s window |
| IMPACT → ORIENTATION | Check orientation vector angle | > 60° from vertical | 500 ms |
| ORIENTATION → INACTIVITY | Low gyroscope angular velocity | < 15°/s | 10 s hold |
| INACTIVITY → ALERT | No recovery motion detected | — | 15 s total |
A running-average filter on SVM rejects short transients. If the IMPACT phase is not followed by orientation shift within 500 ms, the FSM resets to IDLE. Sitting-down and tool-drop events rarely produce SVM > 3 g sustained for the required window.
Physiological thresholds are based on industrial health guidelines. Alerts only fire if the anomalous reading persists beyond a confirmation window to avoid single-sample noise triggering false alarms.
| Parameter | Alert Condition | Threshold | Confirmation Window | Priority |
|---|---|---|---|---|
| SpO₂ | Possible respiratory distress | < 90 % | 10 s | HIGH |
| Heart Rate | Overexertion / shock response | > 120 BPM | 15 s | MED |
| Heart Rate | Bradycardia / unconsciousness | < 45 BPM | 10 s | HIGH |
| Skin Temp | Heat stress warning | > 39 °C | 20 s | MED |
| Skin Temp | Heat stroke risk | > 41 °C | 5 s | HIGH |
HIGH priority: buzzer (continuous 2 kHz), haptic motor (continuous), LED (red rapid flash), MQTT alert published immediately. MED priority: buzzer (3 short pulses), haptic (2 pulses), LED (amber slow flash), MQTT published on next 5 s tick.
The ESP32 connects to a WiFi network and publishes sensor data to an MQTT broker (Mosquitto or cloud-hosted). Two topic hierarchies are used: periodic health data (every 10 s) and immediate alert events (on detection).
All payloads are compact JSON strings. Health payload includes: {"hr":72,"spo2":98,"temp":36.8,"lux":412,"ts":1709123456}. Alert payload includes: {"type":"fall","svm_peak":4.2,"ts":1709123490}.
If WiFi or MQTT connection drops, commTask enters an exponential back-off retry loop (2 s → 4 s → 8 s → max 30 s). During disconnection, alerts are stored in a 10-entry RAM queue and flushed on reconnection. Local alerts (buzzer/haptic) always fire regardless of connectivity.
The ESP32's built-in BLE stack exposes a custom GATT service for wrist-cuff pairing and real-time data streaming to a companion mobile application. BLE operates concurrently with WiFi on the ESP32's shared radio using time-division multiplexing.
Service: 0x1800 (Apex Safety Service). Characteristics: Heart Rate Measurement 0x2A37, SpO₂ Level 0x2A5F, Temperature 0x2A1C, Alert Level 0x2A06.
Health characteristics notify every 2 s when a client is subscribed. Alert Level characteristic notifies immediately on any alert event. The wrist cuff (a secondary ESP32 BLE peripheral) subscribes to Alert Level and mirrors buzzer/haptic output locally on the wrist.
Firmware power management balances continuous sensor vigilance against battery longevity using ESP32's light sleep and deep sleep modes alongside sensor-level power gating.
When no motion is detected for 30 s and no active alert exists, the ESP32 enters light sleep (~0.8 mA). The MPU6050 interrupt (GPIO 23) acts as a wake source. I2C and UART remain capable of wake-up. Resume latency is < 1 ms.
After 5 minutes of inactivity with no active WiFi connection, the ESP32 enters deep sleep (~10 µA). A ULP co-processor continues basic motion monitoring. Wake sources: ULP threshold trigger, or external RTC timer every 60 s for a MQTT heartbeat ping.
| Mode | Current | Wake Source |
|---|---|---|
| Active (WiFi TX) | ~180 mA | — |
| Active (Sensor idle) | ~50 mA | — |
| Light Sleep | ~0.8 mA | GPIO interrupt (MPU6050) |
| Deep Sleep | ~10 µA | ULP motion / RTC timer |