ESP32-S3 — Zephyr RTOS BLE Gateway
The ESP32-S3 serves as a BLE-to-MQTT edge gateway. It aggregates BLE advertisements from nRF52840 sensors, runs on-device TFLite Micro anomaly inference, and publishes telemetry/alerts to the MQTT broker.
Role in the Architecture
Technical Specifications
| Feature | Detail |
|---|---|
| MCU | ESP32-S3 (Xtensa LX7 dual-core, 240MHz) |
| RAM | 512KB SRAM + 8MB PSRAM |
| RTOS | Zephyr RTOS |
| Language | C (Zephyr SDK) |
| Connectivity | WiFi 802.11 b/g/n + BLE 5.0 |
| Device ID | esp32s3_gw_01 |
| Telemetry interval | 10 seconds |
| Attestation interval | 5 minutes |
Why Zephyr RTOS?
| Feature | Zephyr | FreeRTOS | ESP-IDF Native |
|---|---|---|---|
| BLE Mesh support | Native (Bluetooth Mesh) | Limited | Limited |
| TFLite Micro | Built-in module | Manual port | Manual port |
| Hardware abstraction | Devicetree + Kconfig | Minimal | ESP-specific |
| OTA updates | MCUboot integration | Manual | ESP OTA |
| Upstream support | Linux Foundation | Amazon | Espressif |
Zephyr's built-in BLE Mesh stack and TFLite Micro module make it ideal for an edge AI gateway.
System Architecture
// main.c — Entry point
void main(void) {
// Initialize subsystems
mqtt_client_init(); // Connect to Mosquitto
ble_scanner_init(); // Start BLE scanning
edge_inference_init(); // Load TFLite model
ota_manager_init(); // Enable OTA updates
attestation_init(); // Prepare attestation
// Start periodic timers
k_timer_start(&telemetry_timer, K_MSEC(TELEMETRY_INTERVAL_MS), K_MSEC(TELEMETRY_INTERVAL_MS));
k_timer_start(&attestation_timer, K_MSEC(ATTESTATION_INTERVAL_MS), K_MSEC(ATTESTATION_INTERVAL_MS));
// Publish startup status
mqtt_publish(TOPIC_STATUS, status_payload, MQTT_QOS_1_AT_LEAST_ONCE);
// Main loop — process BLE events
while (1) {
k_sleep(K_MSEC(100));
}
}
Telemetry Pipeline
Every 10 seconds:
Telemetry Handler
static void telemetry_timer_handler(struct k_timer *timer) {
// 1. Aggregate BLE sensor data from all nRF52 nodes
struct sensor_aggregate agg;
ble_scanner_aggregate(&agg);
// 2. Run TFLite Micro anomaly detection
float anomaly_score = edge_inference_predict(
agg.temperature,
agg.motion_count,
agg.ble_node_count
);
// 3. Build telemetry payload
snprintk(payload, sizeof(payload),
"{\"device_id\":\"%s\","
"\"temperature\":%.1f,"
"\"motion_count\":%d,"
"\"ble_nodes\":%d,"
"\"anomaly_score\":%.3f}",
DEVICE_ID, agg.temperature, agg.motion_count,
agg.ble_node_count, anomaly_score);
// 4. Publish telemetry (QoS 1 — At Least Once)
mqtt_publish(TOPIC_TELEMETRY, payload, MQTT_QOS_1_AT_LEAST_ONCE);
// 5. Alert if anomaly detected
if (anomaly_score > CONFIG_AURORA_ANOMALY_THRESHOLD) {
snprintk(alert_payload, sizeof(alert_payload),
"{\"device_id\":\"%s\","
"\"alert_type\":\"anomaly_detected\","
"\"severity\":\"high\","
"\"anomaly_score\":%.3f,"
"\"details\":\"Edge ML anomaly threshold exceeded\"}",
DEVICE_ID, anomaly_score);
// QoS 2 — Exactly Once (critical alert must not be duplicated)
mqtt_publish(TOPIC_ALERT, alert_payload, MQTT_QOS_2_EXACTLY_ONCE);
}
}
Edge AI Inference
The ESP32-S3 runs a TensorFlow Lite Micro model for on-device anomaly detection:
Model: Autoencoder (reconstruction-based anomaly detection)
Input: [temperature, motion_count, ble_node_count] — 3 features
Output: anomaly_score (0.0 = normal, 1.0 = anomalous)
Size: ~15KB (quantized INT8)
Inference time: ~2ms on ESP32-S3
Why edge inference? Sending all telemetry to the cloud for analysis adds latency and bandwidth cost. By running inference on-device, the ESP32-S3 can detect anomalies in real-time and only send alerts for significant events.
MQTT Topics
| Topic | QoS | Direction | Content |
|---|---|---|---|
aurora/sensors/esp32s3_gw_01/telemetry | 1 | Publish | Sensor readings + anomaly score |
aurora/sensors/esp32s3_gw_01/alerts | 2 | Publish | Anomaly alerts |
aurora/sensors/esp32s3_gw_01/status | 1 | Publish | Device status (startup, heartbeat) |
aurora/command/esp32s3_gw_01/action | 1 | Subscribe | Commands from SOC |
BLE Scanning
The ESP32-S3 scans for BLE advertisements from nRF52840 nodes:
void ble_scanner_init(void) {
struct bt_le_scan_param scan_param = {
.type = BT_LE_SCAN_TYPE_PASSIVE,
.options = BT_LE_SCAN_OPT_NONE,
.interval = BT_GAP_SCAN_FAST_INTERVAL,
.window = BT_GAP_SCAN_FAST_WINDOW,
};
bt_le_scan_start(&scan_param, scan_callback);
}
static void scan_callback(const bt_addr_le_t *addr, int8_t rssi,
uint8_t adv_type, struct net_buf_simple *buf) {
// Parse manufacturer-specific data from nRF52840 nodes
// Extract: temperature, motion_detected, tamper_status, battery_mv
// Update aggregate sensor data
}
Attestation
Every 5 minutes, the ESP32-S3 performs firmware attestation:
static void attestation_timer_handler(struct k_timer *timer) {
// 1. Compute SHA-256 of firmware partition
char firmware_hash[65];
compute_firmware_sha256(firmware_hash);
// 2. Sign attestation payload
// Uses software ECDSA (ESP32-S3 lacks hardware crypto accelerator)
// 3. Publish to attestation topic
mqtt_publish(TOPIC_STATUS, attestation_payload, MQTT_QOS_2_EXACTLY_ONCE);
}
Build System
# Configure for ESP32-S3
west build -b esp32s3_devkitm firmware/esp32s3
# Flash
west flash
# Monitor serial output
west espressif monitor
Kconfig Options
# prj.conf
CONFIG_BT=y
CONFIG_BT_OBSERVER=y
CONFIG_MQTT_LIB=y
CONFIG_MQTT_LIB_TLS=y
CONFIG_WIFI=y
CONFIG_AURORA_ANOMALY_THRESHOLD=0.7